Skip to content

Commit

Permalink
AND-8877 Add KRC20 base support
Browse files Browse the repository at this point in the history
  • Loading branch information
nzeeei committed Nov 29, 2024
1 parent 59b049b commit ba0c9bc
Show file tree
Hide file tree
Showing 23 changed files with 747 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
internal object DummyBlockchainDataStorage : BlockchainDataStorage {
override suspend fun getOrNull(key: String): String? = null
override suspend fun store(key: String, value: String) = Unit
override suspend fun remove(key: String) = Unit
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tangem.blockchain.blockchains.kaspa.krc20

import com.tangem.blockchain.blockchains.kaspa.network.KaspaBalanceResponse
import retrofit2.http.*

interface KaspaKRC20Api {
@GET("krc20/address/{address}/token/{token}")
suspend fun getBalance(@Path("address") address: String, @Path("token") token: String): KaspaBalanceResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.tangem.blockchain.blockchains.kaspa.krc20

import com.tangem.blockchain.common.NetworkProvider
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
import java.math.BigDecimal

interface KaspaKRC20NetworkProvider : NetworkProvider {
suspend fun getBalances(address: String, tokens: List<Token>): Result<List<KaspaKRC20InfoResponse>>
}

data class KaspaKRC20InfoResponse(
val token: Token,
val balance: BigDecimal,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.tangem.blockchain.blockchains.kaspa.krc20

import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.network.MultiNetworkProvider

class KaspaKRC20NetworkService(providers: List<KaspaKRC20NetworkProvider>) : KaspaKRC20NetworkProvider {

private val multiNetworkProvider = MultiNetworkProvider(providers)
override val baseUrl: String
get() = multiNetworkProvider.currentProvider.baseUrl

override suspend fun getBalances(address: String, tokens: List<Token>): Result<List<KaspaKRC20InfoResponse>> {
return multiNetworkProvider.performRequest { getBalances(address, tokens) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.tangem.blockchain.blockchains.kaspa.krc20

import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.toBlockchainSdkError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.retryIO
import com.tangem.blockchain.network.createRetrofitInstance
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope

open class KaspaKRC20RestApiNetworkProvider(override val baseUrl: String) : KaspaKRC20NetworkProvider {

private val api: KaspaKRC20Api by lazy {
createRetrofitInstance(baseUrl).create(KaspaKRC20Api::class.java)
}
private val decimals = Blockchain.Kaspa.decimals()

override suspend fun getBalances(address: String, tokens: List<Token>): Result<List<KaspaKRC20InfoResponse>> {
return try {
coroutineScope {
val tokenBalancesDeferred = tokens.associateWith { token ->
async { retryIO { api.getBalance(address, token.contractAddress) } }
}

val tokenBalanceResponses = tokenBalancesDeferred.mapValues { it.value.await() }

Result.Success(
tokenBalanceResponses.map {
try {
KaspaKRC20InfoResponse(
token = it.key,
balance = it.value.balance!!.toBigDecimal().movePointLeft(decimals),
)
} catch (exception: Exception) {
throw exception.toBlockchainSdkError()
}
},
)
}
} catch (exception: Exception) {
Result.Failure(exception.toBlockchainSdkError())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.tangem.blockchain.blockchains.kaspa.krc20

import com.tangem.blockchain.blockchains.kaspa.krc20.model.IncompleteTokenTransactionParams
import com.tangem.blockchain.common.TransactionExtras

data class KaspaKRC20TransactionExtras(
val incompleteTokenTransactionParams: IncompleteTokenTransactionParams,
) : TransactionExtras
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.tangem.blockchain.blockchains.kaspa.krc20.model

import com.tangem.blockchain.blockchains.kaspa.KaspaTransaction

data class CommitTransaction(
val transaction: KaspaTransaction,
val hashes: List<ByteArray>,
val redeemScript: RedeemScript,
val sourceAddress: String,
val params: IncompleteTokenTransactionParams,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tangem.blockchain.blockchains.kaspa.krc20.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Envelope(
@Json(name = "p") val p: String,
@Json(name = "op") val op: String,
@Json(name = "amt") val amt: String,
@Json(name = "to") val to: String,
@Json(name = "tick") val tick: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.tangem.blockchain.blockchains.kaspa.krc20.model

import java.math.BigDecimal

data class IncompleteTokenTransactionParams(
val transactionId: String,
val amount: BigDecimal,
val targetOutputAmount: BigDecimal,
val envelope: Envelope,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.tangem.blockchain.blockchains.kaspa.krc20.model

import com.tangem.blockchain.blockchains.kaspa.krc20.KaspaKRC20NetworkProvider
import com.tangem.blockchain.blockchains.kaspa.krc20.KaspaKRC20RestApiNetworkProvider
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.network.providers.NetworkProvidersBuilder
import com.tangem.blockchain.common.network.providers.ProviderType

internal class KaspaKRC20ProvidersBuilder(
override val providerTypes: List<ProviderType>,
) : NetworkProvidersBuilder<KaspaKRC20NetworkProvider>() {

override fun createProviders(blockchain: Blockchain): List<KaspaKRC20NetworkProvider> {
return listOf(KaspaKRC20RestApiNetworkProvider("https://api.kasplex.org/v1/"))
}

override fun createTestnetProviders(blockchain: Blockchain): List<KaspaKRC20NetworkProvider> {
return listOf(KaspaKRC20RestApiNetworkProvider("https://tn10api.kasplex.org/v1"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.tangem.blockchain.blockchains.kaspa.krc20.model

data class RedeemScript(
val publicKey: ByteArray,
val envelope: Envelope,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tangem.blockchain.blockchains.kaspa.krc20.model

import com.tangem.blockchain.blockchains.kaspa.KaspaTransaction

internal data class RevealTransaction(
val transaction: KaspaTransaction,
val hashes: List<ByteArray>,
val redeemScript: RedeemScript,
)
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ enum class Blockchain(
Hedera, HederaTestnet,
TON, TONTestnet,
Cardano,
Kaspa,
-> true

else -> false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class WalletManagerFactory(
Blockchain.Binance, Blockchain.BinanceTestnet -> BinanceWalletManagerAssembly
Blockchain.Tezos -> TezosWalletManagerAssembly
Blockchain.Tron, Blockchain.TronTestnet -> TronWalletManagerAssembly
Blockchain.Kaspa -> KaspaWalletManagerAssembly
Blockchain.Kaspa -> KaspaWalletManagerAssembly(dataStorage)
Blockchain.TON, Blockchain.TONTestnet -> TonWalletManagerAssembly
Blockchain.Cosmos, Blockchain.CosmosTestnet -> CosmosWalletManagerAssembly
Blockchain.TerraV1 -> TerraV1WalletManagerAssembly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,31 @@ package com.tangem.blockchain.common.assembly.impl
import com.tangem.blockchain.blockchains.kaspa.KaspaProvidersBuilder
import com.tangem.blockchain.blockchains.kaspa.KaspaTransactionBuilder
import com.tangem.blockchain.blockchains.kaspa.KaspaWalletManager
import com.tangem.blockchain.blockchains.kaspa.krc20.KaspaKRC20NetworkService
import com.tangem.blockchain.blockchains.kaspa.krc20.model.KaspaKRC20ProvidersBuilder
import com.tangem.blockchain.blockchains.kaspa.network.KaspaNetworkService
import com.tangem.blockchain.common.assembly.WalletManagerAssembly
import com.tangem.blockchain.common.assembly.WalletManagerAssemblyInput
import com.tangem.blockchain.common.datastorage.implementations.AdvancedDataStorage

internal object KaspaWalletManagerAssembly : WalletManagerAssembly<KaspaWalletManager>() {
internal class KaspaWalletManagerAssembly(
private val dataStorage: AdvancedDataStorage,
) : WalletManagerAssembly<KaspaWalletManager>() {

override fun make(input: WalletManagerAssemblyInput): KaspaWalletManager {
return with(input.wallet) {
KaspaWalletManager(
wallet = this,
transactionBuilder = KaspaTransactionBuilder(),
transactionBuilder = KaspaTransactionBuilder(
publicKey = publicKey,
),
networkProvider = KaspaNetworkService(
providers = KaspaProvidersBuilder(input.providerTypes, input.config).build(blockchain),
),
krc20NetworkProvider = KaspaKRC20NetworkService(
providers = KaspaKRC20ProvidersBuilder(input.providerTypes).build(blockchain),
),
dataStorage = dataStorage,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ interface BlockchainDataStorage {

/** Store [value] in JSON format by [key] */
suspend fun store(key: String, value: String)

/** Remove [value] from storage by [key] */
suspend fun remove(key: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.tangem.blockchain.common.datastorage

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.blockchains.kaspa.krc20.model.Envelope
import java.math.BigDecimal

internal sealed interface BlockchainSavedData {

Expand All @@ -16,4 +18,12 @@ internal sealed interface BlockchainSavedData {
// TODO: Remove this flag in future https://tangem.atlassian.net/browse/AND-7025
@Json(name = "cache_cleared") val isCacheCleared: Boolean = false,
) : BlockchainSavedData

@JsonClass(generateAdapter = true)
data class KaspaKRC20IncompleteTokenTransaction(
@Json(name = "transactionId") val transactionId: String,
@Json(name = "amount") val amount: BigDecimal,
@Json(name = "targetOutputAmount") val targetOutputAmount: BigDecimal,
@Json(name = "envelope") val envelope: Envelope,
) : BlockchainSavedData
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ internal class AdvancedDataStorage(
blockchainDataStorage.store(key = T::class.java.createKey(publicKey), value = adapter.toJson(value))
}

/** Store [value] by [key] */
suspend inline fun <reified T : BlockchainSavedData> store(key: String, value: T) {
val adapter = moshi.adapter(T::class.java)
blockchainDataStorage.store(key = key, value = adapter.toJson(value))
}

/** Remove [value] from storage by [key] */
suspend inline fun remove(key: String) {
blockchainDataStorage.remove(key = key)
}

/**
* Create a unique key by [publicKey] for [BlockchainSavedData].
* Example, Hedera-7BD63F5DE1BF539525C33367592949AE9B99D518BF78F26F3904BCD30CFCF018
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ sealed class Fee {
override val amount: Amount,
val mass: BigInteger,
val feeRate: BigInteger,
val revealTransactionFee: Kaspa? = null,
) : Fee()

data class Filecoin(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package com.tangem.blockchain.blockchains.kaspa

import com.google.common.truth.Truth
import com.tangem.blockchain.blockchains.kaspa.network.*
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.hexToBytes
Expand Down Expand Up @@ -37,7 +34,12 @@ class KaspaTransactionTest {

val sourceAddress = addressService.makeAddress(walletPublicKey)

val transactionBuilder = KaspaTransactionBuilder()
val transactionBuilder = KaspaTransactionBuilder(
publicKey = Wallet.PublicKey(
seedKey = walletPublicKey,
derivationType = null,
),
)
transactionBuilder.unspentOutputs = listOf(
KaspaUnspentOutput(
transactionHash = "deb88e7dd734437c6232a636085ef917d1d13cc549fe14749765508b2782f2fb".hexToBytes(),
Expand Down Expand Up @@ -158,7 +160,12 @@ class KaspaTransactionTest {

val sourceAddress = addressService.makeAddress(walletPublicKey)

val transactionBuilder = KaspaTransactionBuilder()
val transactionBuilder = KaspaTransactionBuilder(
publicKey = Wallet.PublicKey(
seedKey = walletPublicKey,
derivationType = null,
),
)
transactionBuilder.unspentOutputs = listOf(
KaspaUnspentOutput(
transactionHash = "ae96e819429e9da538e84cb213f62fbc8ad32e932d7c7f1fb9bd2fedf8fd7b4a".hexToBytes(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ internal class WalletManagerFactoryTest {
blockchainDataStorage = object : BlockchainDataStorage {
override suspend fun getOrNull(key: String): String? = null
override suspend fun store(key: String, value: String) = Unit
override suspend fun remove(key: String) = Unit
},
accountCreator = accountCreator,
featureToggles = BlockchainFeatureToggles(isEthereumEIP1559Enabled = true),
Expand Down Expand Up @@ -211,6 +212,7 @@ internal class WalletManagerFactoryTest {
blockchainDataStorage = object : BlockchainDataStorage {
override suspend fun getOrNull(key: String): String? = null
override suspend fun store(key: String, value: String) = Unit
override suspend fun remove(key: String) = Unit
},
accountCreator = accountCreator,
featureToggles = BlockchainFeatureToggles(isEthereumEIP1559Enabled = true),
Expand Down

0 comments on commit ba0c9bc

Please sign in to comment.