diff --git a/blockchain/build.gradle b/blockchain/build.gradle index d8e1ebe426..4f75526d84 100644 --- a/blockchain/build.gradle +++ b/blockchain/build.gradle @@ -24,10 +24,6 @@ android { } } - packagingOptions { - exclude 'org.slf4j:slf4j-jdk14:1.7.25' - } - } dependencies { @@ -49,7 +45,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.3" implementation 'org.bitcoinj:bitcoinj-core:0.15.2' - implementation 'com.github.stellar:java-stellar-sdk:0.11.0' + implementation 'com.github.stellar:java-stellar-sdk:0.13.0' ext.kethereum_version = '0.79.5' implementation "com.github.walleth.kethereum:functions:$kethereum_version" @@ -59,6 +55,8 @@ dependencies { implementation "com.github.walleth.kethereum:crypto:$kethereum_version" implementation "com.github.walleth.kethereum:crypto_api:$kethereum_version" implementation "com.github.walleth.kethereum:model:$kethereum_version" + implementation 'com.github.komputing.khex:core:1.0.0-RC6' + implementation 'com.github.komputing.khex:extensions:1.0.0-RC6' implementation 'co.nstant.in:cbor:0.8' diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt index b533321c6a..1836223aee 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinAddress.kt @@ -12,9 +12,9 @@ import java.security.MessageDigest class BitcoinAddressFactory { companion object { - fun makeAddress(cardPublicKey: ByteArray, testNet: Boolean = false): String { + fun makeAddress(walletPublicKey: ByteArray, testNet: Boolean = false): String { val netSelectionByte = if (testNet) 0x6f.toByte() else 0x00.toByte() - val hash1 = cardPublicKey.calculateSha256().calculateRipemd160() + val hash1 = walletPublicKey.calculateSha256().calculateRipemd160() val hash2 = byteArrayOf(netSelectionByte).plus(hash1).calculateSha256().calculateSha256() val result = byteArrayOf(netSelectionByte) + hash1 + hash2[0] + hash2[1] + hash2[2] + hash2[3] return Base58.encode(result) diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt index ce6271b458..31faf256a9 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinTransactionBuilder.kt @@ -1,66 +1,68 @@ package com.tangem.blockchain.bitcoin import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.toCanonicalised import org.bitcoinj.core.* import org.bitcoinj.crypto.TransactionSignature import org.bitcoinj.script.Script import org.bitcoinj.script.ScriptBuilder -import java.io.ByteArrayOutputStream +import java.math.BigDecimal import java.math.BigInteger class BitcoinTransactionBuilder(private val testNet: Boolean) { private lateinit var transaction: Transaction private var networkParameters: NetworkParameters? = null - var unspentOutputs: List = listOf() - - fun calculateChange(transactionData: TransactionData) : Long { - val fullAmount = unspentOutputs.map { it.amount }.sum() - return fullAmount - (transactionData.amount.value!!.toLong() + (transactionData.fee?.value?.toLong() ?: 0)) - } + var unspentOutputs: List? = null fun buildToSign( - transactionData: TransactionData): List { + transactionData: TransactionData): Result> { - val change: Long = calculateChange(transactionData) + if (unspentOutputs == null) return Result.Failure(Exception("Currently there's an unconfirmed transaction")) + + val change: BigDecimal = calculateChange(transactionData) networkParameters = if (testNet) { NetworkParameters.fromID(NetworkParameters.ID_TESTNET) } else { NetworkParameters.fromID(NetworkParameters.ID_MAINNET) } - transaction = transactionData.toBitcoinJTransaction(networkParameters, unspentOutputs, change) + transaction = transactionData.toBitcoinJTransaction(networkParameters, unspentOutputs!!, change) - val hashesForSign: MutableList = mutableListOf() + val hashesForSign: MutableList = MutableList(transaction.inputs.size) { byteArrayOf() } for (input in transaction.inputs) { val index = input.index hashesForSign[index] = transaction.hashForSignature(index, input.scriptBytes, Transaction.SigHash.ALL, false).bytes } - return hashesForSign + return Result.Success(hashesForSign) + } + + private fun calculateChange(transactionData: TransactionData): BigDecimal { + val fullAmount = unspentOutputs!!.map { it.amount }.reduce { acc, number -> acc + number } + return fullAmount - (transactionData.amount.value!! + (transactionData.fee?.value + ?: 0.toBigDecimal())) } fun buildToSend(signedTransaction: ByteArray, publicKey: ByteArray): ByteArray { for (index in transaction.inputs.indices) { transaction.inputs[index].scriptSig = createScript(index, signedTransaction, publicKey) } - val serializer = BitcoinSerializer(networkParameters, false) - val outputStream = ByteArrayOutputStream() - serializer.serialize(transaction, outputStream) - return outputStream.toByteArray() + return transaction.bitcoinSerialize() } private fun createScript(index: Int, signedTransaction: ByteArray, publicKey: ByteArray): Script { - val r = BigInteger(1, signedTransaction.copyOfRange(index * 64, 32 + index * 64)); - val s = BigInteger(1, signedTransaction.copyOfRange(32 + index * 64, 64 + index * 64)); - val signature = TransactionSignature(r, s.toCanonicalised()) + val r = BigInteger(1, signedTransaction.copyOfRange(index * 64, 32 + index * 64)) + val s = BigInteger(1, signedTransaction.copyOfRange(32 + index * 64, 64 + index * 64)) + val canonicalS = ECKey.ECDSASignature(r, s).toCanonicalised().s + val signature = TransactionSignature(r, canonicalS) return ScriptBuilder.createInputScript(signature, ECKey.fromPublicOnly(publicKey)) } } internal fun TransactionData.toBitcoinJTransaction(networkParameters: NetworkParameters?, unspentOutputs: List, - change: Long): Transaction { + change: BigDecimal): Transaction { val transaction = Transaction(networkParameters) for (utxo in unspentOutputs) { transaction.addInput(Sha256Hash.wrap(utxo.hash), utxo.outputIndex, Script(utxo.outputScript)) @@ -68,9 +70,9 @@ internal fun TransactionData.toBitcoinJTransaction(networkParameters: NetworkPar transaction.addOutput( Coin.parseCoin(this.amount.value!!.toPlainString()), Address.fromString(networkParameters, this.destinationAddress)) - if (change != 0L) { + if (change != 0.toBigDecimal()) { transaction.addOutput( - Coin.parseCoin(change.toString()), + Coin.parseCoin(change.toPlainString()), Address.fromString(networkParameters, this.sourceAddress)) } @@ -78,7 +80,7 @@ internal fun TransactionData.toBitcoinJTransaction(networkParameters: NetworkPar } class UnspentTransaction( - val amount: Long, + val amount: BigDecimal, val outputIndex: Long, val hash: ByteArray, val outputScript: ByteArray diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt index 34a5398a21..8820c732d9 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt @@ -3,14 +3,13 @@ package com.tangem.blockchain.bitcoin import android.util.Log import com.tangem.blockchain.bitcoin.network.BitcoinAddressResponse import com.tangem.blockchain.bitcoin.network.BitcoinNetworkManager +import com.tangem.blockchain.bitcoin.network.BitcoinNetworkManager.Companion.SATOSHI_IN_BTC import com.tangem.blockchain.common.* import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet import com.tangem.common.extensions.toHexString import com.tangem.tasks.TaskEvent -import org.bitcoinj.core.NetworkParameters -import org.bitcoinj.core.Transaction import java.math.BigDecimal class BitcoinWalletManager( @@ -39,8 +38,8 @@ class BitcoinWalletManager( } private fun updateWallet(response: BitcoinAddressResponse) { - Log.d(this::class.java.simpleName, "Balance is ${response.balance.toString()}") - currencyWallet.balances[AmountType.Coin]?.value = response.balance.toBigDecimal() + Log.d(this::class.java.simpleName, "Balance is ${response.balance}") + currencyWallet.balances[AmountType.Coin]?.value = response.balance transactionBuilder.unspentOutputs = response.unspentTransactions if (response.hasUnconfirmed) { if (currencyWallet.pendingTransactions.isEmpty()) { @@ -59,26 +58,31 @@ class BitcoinWalletManager( Log.e(this::class.java.simpleName, error?.message ?: "") } - - override suspend fun getEstimateSize(transactionData: TransactionData): Int { - val transaction: Transaction = transactionData.toBitcoinJTransaction( - NetworkParameters.fromID(NetworkParameters.ID_MAINNET), - transactionBuilder.unspentOutputs, - transactionBuilder.calculateChange(transactionData) - ) - var size: Int = transaction.unsafeBitcoinSerialize().size - size += transaction.inputs.sumBy { 130 } - return size + override suspend fun getEstimateSize(transactionData: TransactionData): Result { + val buildTransactionResult = transactionBuilder.buildToSign(transactionData) + when (buildTransactionResult) { + is Result.Failure -> return buildTransactionResult + is Result.Success -> { + val hashes = buildTransactionResult.data + val finalTransaction = transactionBuilder.buildToSend(ByteArray(64 * hashes.size) {1}, walletPublicKey) + return Result.Success(finalTransaction.size) + } + } } override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { - val hashes = transactionBuilder.buildToSign(transactionData) - when (val signerResponse = signer.sign(hashes.toTypedArray(), cardId)) { - is TaskEvent.Event -> { - val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey) - return networkManager.sendTransaction(transactionToSend.toHexString()) + val buildTransactionResult = transactionBuilder.buildToSign(transactionData) + when (buildTransactionResult) { + is Result.Failure -> return SimpleResult.Failure(buildTransactionResult.error) + is Result.Success -> { + when (val signerResponse = signer.sign(buildTransactionResult.data.toTypedArray(), cardId)) { + is TaskEvent.Event -> { + val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey) + return networkManager.sendTransaction(transactionToSend.toHexString()) + } + is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + } } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) } } @@ -86,28 +90,32 @@ class BitcoinWalletManager( when (val result = networkManager.getFee()) { is Result.Failure -> return result is Result.Success -> { - val bytesInKb = BigDecimal(1024) - val size = getEstimateSize(TransactionData(amount, null, source, destination)).toBigDecimal() - val minFee = result.data.minimalPerKb / bytesInKb * size - val normalFee = result.data.normalPerKb / bytesInKb * size - val priorityFee = result.data.priorityPerKb / bytesInKb * size - return Result.Success( - listOf( - Amount(blockchain.currency, - minFee, - source, - blockchain.decimals), - Amount(blockchain.currency, - normalFee, - source, - blockchain.decimals), - Amount(blockchain.currency, - priorityFee, - source, - blockchain.decimals) - ) + val sizeResult = getEstimateSize( + TransactionData(amount, + Amount(1.toBigDecimal().divide(SATOSHI_IN_BTC), blockchain), + address, destination) ) + when (sizeResult) { + is Result.Failure -> return sizeResult + is Result.Success -> { + val transactionSize = sizeResult.data.toBigDecimal() + val minFee = result.data.minimalPerKb.calculateFee(transactionSize) + val normalFee = result.data.normalPerKb.calculateFee(transactionSize) + val priorityFee = result.data.priorityPerKb.calculateFee(transactionSize) + return Result.Success( + listOf(Amount(minFee, blockchain), + Amount(normalFee, blockchain), + Amount(priorityFee, blockchain)) + ) + } + } } } } + + private fun BigDecimal.calculateFee(transactionSize: BigDecimal): BigDecimal { + val bytesInKb = BigDecimal(1024) + return this.divide(bytesInKb).multiply(transactionSize) + .setScale(8, blockchain.roundingMode()) + } } \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BitcoinNetworkManager.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BitcoinNetworkManager.kt index fbea3c1359..55b686591e 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BitcoinNetworkManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BitcoinNetworkManager.kt @@ -85,12 +85,16 @@ class BitcoinNetworkManager(private val isTestNet: Boolean) : BitcoinProvider { } } } + + companion object { + val SATOSHI_IN_BTC = 100000000.toBigDecimal() + } } data class BitcoinAddressResponse( - val balance: Long, + val balance: BigDecimal, val hasUnconfirmed: Boolean, - val unspentTransactions: List + val unspentTransactions: List? ) data class BitcoinFee( diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockchainInfoProvider.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockchainInfoProvider.kt index a8d1c0f51c..7b2882ec57 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockchainInfoProvider.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockchainInfoProvider.kt @@ -1,11 +1,13 @@ package com.tangem.blockchain.bitcoin.network import com.tangem.blockchain.bitcoin.UnspentTransaction +import com.tangem.blockchain.bitcoin.network.BitcoinNetworkManager.Companion.SATOSHI_IN_BTC import com.tangem.blockchain.bitcoin.network.api.BlockchainInfoApi import com.tangem.blockchain.bitcoin.network.api.EstimatefeeApi import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.common.extensions.retryIO +import com.tangem.common.extensions.hexToBytes import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope @@ -27,16 +29,16 @@ class BlockchainInfoProvider( val bitcoinUnspents = unspents.unspentOutputs.map { UnspentTransaction( - it.amount!!, + it.amount!!.toBigDecimal().divide(SATOSHI_IN_BTC), it.outputIndex!!.toLong(), - it.hash!!.toByteArray(), - it.outputScript!!.toByteArray()) + it.hash!!.hexToBytes(), + it.outputScript!!.hexToBytes()) } Result.Success( BitcoinAddressResponse( - addressData.finalBalance - ?: 0L, unconfirmedTransactions, bitcoinUnspents)) + addressData.finalBalance?.toBigDecimal()?.divide(SATOSHI_IN_BTC) + ?: 0.toBigDecimal(), unconfirmedTransactions, bitcoinUnspents)) } } catch (exception: Exception) { Result.Failure(exception) diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockcypherProvider.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockcypherProvider.kt index 024046d1a8..94c9e3dd17 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockcypherProvider.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/BlockcypherProvider.kt @@ -1,13 +1,15 @@ package com.tangem.blockchain.bitcoin.network import com.tangem.blockchain.bitcoin.UnspentTransaction +import com.tangem.blockchain.bitcoin.network.BitcoinNetworkManager.Companion.SATOSHI_IN_BTC import com.tangem.blockchain.bitcoin.network.api.BlockcypherApi -import com.tangem.blockchain.bitcoin.network.response.BlockcypherBody +import com.tangem.blockchain.bitcoin.network.api.BlockcypherBody import com.tangem.blockchain.bitcoin.network.response.BlockcypherFee import com.tangem.blockchain.bitcoin.network.response.BlockcypherResponse import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.common.extensions.retryIO +import com.tangem.common.extensions.hexToBytes class BlockcypherProvider(private val api: BlockcypherApi, isTestNet: Boolean) : BitcoinProvider { @@ -22,16 +24,16 @@ class BlockcypherProvider(private val api: BlockcypherApi, isTestNet: Boolean) : override suspend fun getInfo(address: String): Result { try { val addressData: BlockcypherResponse = retryIO { api.getAddressData(blockchain, network, address) } - val unspents = addressData.txrefs!!.map { + val unspents = addressData.txrefs?.map { UnspentTransaction( - it.amount!!, + it.amount!!.toBigDecimal().divide(SATOSHI_IN_BTC), it.outputIndex!!.toLong(), - it.hash!!.toByteArray(), - it.outputScript!!.toByteArray() + it.hash!!.hexToBytes(), + it.outputScript!!.hexToBytes() ) } return Result.Success(BitcoinAddressResponse( - addressData.balance!!, + addressData.balance!!.toBigDecimal().divide(SATOSHI_IN_BTC), addressData.unconfirmedBalance != 0L, unspents)) @@ -44,9 +46,9 @@ class BlockcypherProvider(private val api: BlockcypherApi, isTestNet: Boolean) : try { val receivedFee: BlockcypherFee = retryIO { api.getFee(blockchain, network) } return Result.Success( - BitcoinFee(receivedFee.minFeePerKb!!.toBigDecimal() / satoshiInBtc, - receivedFee.normalFeePerKb!!.toBigDecimal() / satoshiInBtc, - receivedFee.priorityFeePerKb!!.toBigDecimal() / satoshiInBtc) + BitcoinFee(receivedFee.minFeePerKb!!.toBigDecimal().divide(SATOSHI_IN_BTC), + receivedFee.normalFeePerKb!!.toBigDecimal().divide(SATOSHI_IN_BTC), + receivedFee.priorityFeePerKb!!.toBigDecimal().divide(SATOSHI_IN_BTC)) ) } catch (error: Exception) { return Result.Failure(error) @@ -78,6 +80,4 @@ private object BlockcypherToken { private enum class BlockcypherNetwork(val network: String) { Main("main"), Test("test3") -} - -val satoshiInBtc = 100000000.toBigDecimal() \ No newline at end of file +} \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/api/BlockcypherApi.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/api/BlockcypherApi.kt index 2984d42a1d..755191e81e 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/api/BlockcypherApi.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/api/BlockcypherApi.kt @@ -1,6 +1,6 @@ package com.tangem.blockchain.bitcoin.network.api -import com.tangem.blockchain.bitcoin.network.response.BlockcypherBody +import com.squareup.moshi.JsonClass import com.tangem.blockchain.bitcoin.network.response.BlockcypherFee import com.tangem.blockchain.bitcoin.network.response.BlockcypherResponse import com.tangem.blockchain.bitcoin.network.response.BlockcypherTx @@ -8,20 +8,20 @@ import retrofit2.http.* interface BlockcypherApi { @GET("v1/{blockchain}/{network}") - fun getFee( + suspend fun getFee( @Path("blockchain") blockchain: String, @Path("network") network: String ): BlockcypherFee @GET("v1/{blockchain}/{network}/addrs/{address}?unspentOnly=true&includeScript=true") - fun getAddressData( + suspend fun getAddressData( @Path("blockchain") blockchain: String, @Path("network") network: String, @Path("address") address: String ): BlockcypherResponse @GET("v1/{blockchain}/{network}/txs/{txHash}?includeHex=true") - fun getTransactions( + suspend fun getTransactions( @Path("blockchain") blockchain: String, @Path("network") network: String, @Path("txHash") txHash: String @@ -29,10 +29,13 @@ interface BlockcypherApi { @Headers("Content-Type: application/json") @POST("v1/{blockchain}/{network}/txs/push") - fun sendTransaction( + suspend fun sendTransaction( @Path("blockchain") blockchain: String, @Path("network") network: String, @Body blockcypherBody: BlockcypherBody, @Query("token") token: String - ): BlockcypherResponse -} \ No newline at end of file + ): BlockcypherTx +} + +@JsonClass(generateAdapter = true) +data class BlockcypherBody(val tx: String) \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/response/BlockcypherResponse.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/response/BlockcypherResponse.kt index eca6fd1bfb..b1e869797a 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/response/BlockcypherResponse.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/network/response/BlockcypherResponse.kt @@ -1,52 +1,55 @@ package com.tangem.blockchain.bitcoin.network.response import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) data class BlockcypherResponse( @Json(name = "address") - var address: String? = null, + val address: String? = null, @Json(name = "balance") - var balance: Long? = null, + val balance: Long? = null, @Json(name = "unconfirmed_balance") - var unconfirmedBalance: Long? = null, + val unconfirmedBalance: Long? = null, @Json(name = "txrefs") - var txrefs: List? = null + val txrefs: List? = null ) +@JsonClass(generateAdapter = true) data class BlockcypherTxref( @Json(name = "tx_hash") - var hash: String? = null, + val hash: String? = null, @Json(name = "tx_output_n") - var outputIndex: Int? = null, + val outputIndex: Int? = null, @Json(name = "value") - var amount: Long? = null, + val amount: Long? = null, @Json(name = "confirmations") - var confirmations: Long? = null, + val confirmations: Long? = null, @Json(name = "script") - var outputScript: String? = null + val outputScript: String? = null ) +@JsonClass(generateAdapter = true) data class BlockcypherTx( @Json(name = "hex") - var hex: String? = null + val hex: String? = null ) +@JsonClass(generateAdapter = true) data class BlockcypherFee( @Json(name = "low_fee_per_kb") - var minFeePerKb: Long? = null, + val minFeePerKb: Long? = null, @Json(name = "medium_fee_per_kb") - var normalFeePerKb: Long? = null, + val normalFeePerKb: Long? = null, @Json(name = "high_fee_per_kb") - var priorityFeePerKb: Long? = null -) - -data class BlockcypherBody(val tx: String) \ No newline at end of file + val priorityFeePerKb: Long? = null +) \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt b/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt index 72edea8f71..d405ead446 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt @@ -2,10 +2,10 @@ package com.tangem.blockchain.common import com.tangem.blockchain.bitcoin.BitcoinAddressFactory import com.tangem.blockchain.bitcoin.BitcoinAddressValidator +import com.tangem.blockchain.ethereum.EthereumAddressFactory +import com.tangem.blockchain.ethereum.EthereumAddressValidator import com.tangem.blockchain.cardano.CardanoAddressFactory import com.tangem.blockchain.cardano.CardanoAddressValidator -import com.tangem.blockchain.eth.EthereumAddressFactory -import com.tangem.blockchain.eth.EthereumAddressValidator import com.tangem.blockchain.stellar.StellarAddressFactory import java.math.BigDecimal @@ -17,14 +17,14 @@ enum class Blockchain( val pendingTransactionTimeout: Int ) { Unknown("", "", 0, "", 0), - Bitcoin("btc", "", 8, "", 0), - BitcoinTestnet("btc", "", 8, "", 0), - Ethereum("", "", 18, "", 0), + Bitcoin("BTC", "BTC", 8, "Bitcoin", 0), + BitcoinTestnet("BTC", "BTC", 8, "Bitcoin Testnet", 0), + Ethereum("ETH", "ETH", 18, "Ethereum", 0), Rootstock("", "", 18, "", 0), - Cardano("", "", 6, "", 0), + Cardano("CARDANO", "ADA", 6, "Cardano", 0), Ripple("", "", 6, "", 0), Binance("", "", 8, "", 0), - Stellar("", "", 7, "", 0); + Stellar("XLM", "XLM", 7, "Stellar", 0); fun roundingMode(): Int = when (this) { Bitcoin, Ethereum, Rootstock, Binance -> BigDecimal.ROUND_DOWN @@ -32,17 +32,17 @@ enum class Blockchain( else -> BigDecimal.ROUND_HALF_UP } - fun makeAddress(cardPublicKey: ByteArray): String { + fun makeAddress(walletPublicKey: ByteArray): String { return when (this) { Unknown -> throw Exception("unsupported blockchain") - Bitcoin -> BitcoinAddressFactory.makeAddress(cardPublicKey) - BitcoinTestnet -> BitcoinAddressFactory.makeAddress(cardPublicKey, testNet = true) - Ethereum -> EthereumAddressFactory.makeAddress(cardPublicKey) + Bitcoin -> BitcoinAddressFactory.makeAddress(walletPublicKey) + BitcoinTestnet -> BitcoinAddressFactory.makeAddress(walletPublicKey, testNet = true) + Ethereum -> EthereumAddressFactory.makeAddress(walletPublicKey) // Rootstock -> RootstockAddressFactory.makeAddress(cardPublicKey) - Cardano -> CardanoAddressFactory.makeAddress(cardPublicKey) + Cardano -> CardanoAddressFactory.makeAddress(walletPublicKey) // Ripple -> RippleAddressFactory.makeAddress(cardPublicKey) // Binance -> BinanceAddressFactory.makeAddress(cardPublicKey) - Stellar -> StellarAddressFactory.makeAddress(cardPublicKey) + Stellar -> StellarAddressFactory.makeAddress(walletPublicKey) else -> throw Exception("unsupported blockchain") } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/Wallet.kt b/blockchain/src/main/java/com/tangem/blockchain/common/Wallet.kt index fc025b619c..68f9855e05 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/Wallet.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/Wallet.kt @@ -23,19 +23,27 @@ data class Amount( val address: String? = null, val decimals: Byte, val type: AmountType = AmountType.Coin - ) +) { + constructor( + value: BigDecimal?, + blockchain: Blockchain, + address: String? = null, + type: AmountType = AmountType.Coin + ) : this(blockchain.currency, value, address, blockchain.decimals, type) +} data class TransactionData( val amount: Amount, val fee: Amount?, val sourceAddress: String, val destinationAddress: String, - var status: TransactionStatus = TransactionStatus.Uncomfirmed + var status: TransactionStatus = TransactionStatus.Unconfirmed, + var date: Calendar? = null ) enum class AmountType { Coin, Token, Reserve } -enum class TransactionStatus {Confirmed, Uncomfirmed} +enum class TransactionStatus { Confirmed, Unconfirmed } enum class ValidationError { WrongAmount, WrongFee, WrongTotal } diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt index 6fe40b1bf7..0b55b6fba3 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.commands.SignResponse import com.tangem.tasks.TaskEvent +import kotlinx.coroutines.flow.Flow interface WalletManager { var wallet: Wallet @@ -13,7 +14,7 @@ interface WalletManager { } interface TransactionEstimator { - suspend fun getEstimateSize(transactionData: TransactionData): Int + suspend fun getEstimateSize(transactionData: TransactionData): Result } interface TransactionSender { diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt index ebb54d6efe..9705ab191d 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt @@ -1,9 +1,9 @@ package com.tangem.blockchain.common import com.tangem.blockchain.bitcoin.BitcoinWalletManager +import com.tangem.blockchain.ethereum.Chain +import com.tangem.blockchain.ethereum.EthereumWalletManager import com.tangem.blockchain.cardano.CardanoWalletManager -import com.tangem.blockchain.eth.Chain -import com.tangem.blockchain.eth.EthereumWalletManager import com.tangem.blockchain.stellar.StellarWalletManager import com.tangem.commands.Card @@ -25,7 +25,7 @@ object WalletManagerFactory { val chain = if (isTestNet(blockchainName)) { Chain.EthereumClassicTestnet } else { - Chain.EthereumClassicMainnet + Chain.Mainnet } return EthereumWalletManager( cardId = card.cardId, diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt b/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt index 2dfc103e1a..03d2b2f009 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt @@ -1,10 +1,18 @@ package com.tangem.blockchain.common.extensions -import kotlinx.coroutines.delay +import com.tangem.CardManager +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.commands.SignResponse +import com.tangem.tasks.TaskEvent +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import java.io.IOException +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine suspend fun retryIO( - times: Int = Int.MAX_VALUE, + times: Int = 3, initialDelay: Long = 100, maxDelay: Long = 1000, factor: Double = 2.0, @@ -47,3 +55,12 @@ sealed class SimpleResult { data class Failure(val error: Throwable?) : SimpleResult() } +class Signer(private val cardManager: CardManager) : TransactionSigner { + override suspend fun sign(hashes: Array, cardId: String): TaskEvent = coroutineScope { + async { + suspendCancellableCoroutine> { continuation -> + cardManager.sign(hashes, cardId) { if (continuation.isActive) continuation.resume(it) } + } + }.await() + } +} diff --git a/blockchain/src/test/java/com/tangem/blockchain/bitcoin/BitcoinAddressTest.kt b/blockchain/src/test/java/com/tangem/blockchain/bitcoin/BitcoinAddressTest.kt new file mode 100644 index 0000000000..ea4b181980 --- /dev/null +++ b/blockchain/src/test/java/com/tangem/blockchain/bitcoin/BitcoinAddressTest.kt @@ -0,0 +1,25 @@ +package com.tangem.blockchain.bitcoin + +import com.google.common.truth.Truth +import com.tangem.common.extensions.hexToBytes +import org.junit.Test +import org.junit.jupiter.api.Assertions.* + +class BitcoinAddressTest { + + @Test + fun makeAddressFromCorrectPublicKey() { + val walletPublicKey = "04752A727E14BBA5BD73B6714D72500F61FFD11026AD1196D2E1C54577CBEEAC3D11FC68A64700F8D533F4E311964EA8FB3AA26C588295F2133868D69C3E628693".hexToBytes() + val expected = "1D3vYSjCvzrsVVK5bNaPTjU3NxcN7NNXMN" + + Truth.assertThat(BitcoinAddressFactory.makeAddress(walletPublicKey)) + .isEqualTo(expected) + } + + @Test + fun validateCorrectAddress() { + val address = "1D3vYSjCvzrsVVK5bNaPTjU3NxcN7NNXMN" + Truth.assertThat(BitcoinAddressValidator.validate(address)) + .isTrue() + } +} \ No newline at end of file diff --git a/blockchain/src/test/java/com/tangem/blockchain/ethereum/EthereumAddressTest.kt b/blockchain/src/test/java/com/tangem/blockchain/ethereum/EthereumAddressTest.kt new file mode 100644 index 0000000000..b1325c8493 --- /dev/null +++ b/blockchain/src/test/java/com/tangem/blockchain/ethereum/EthereumAddressTest.kt @@ -0,0 +1,24 @@ +package com.tangem.blockchain.ethereum + +import com.google.common.truth.Truth +import com.tangem.common.extensions.hexToBytes +import org.junit.Test + +internal class EthereumAddressTest { + + @Test + fun makeAddressFromCorrectPublicKey() { + val walletPublicKey = "04BAEC8CD3BA50FDFE1E8CF2B04B58E17041245341CD1F1C6B3A496B48956DB4C896A6848BCF8FCFC33B88341507DD25E5F4609386C68086C74CF472B86E5C3820".hexToBytes() + val expected = "0xc63763572d45171e4c25ca0818b44e5dd7f5c15b" + + Truth.assertThat(EthereumAddressFactory.makeAddress(walletPublicKey)) + .isEqualTo(expected) + } + + @Test + fun validateCorrectAddress() { + val address = "0xc63763572d45171e4c25ca0818b44e5dd7f5c15b" + Truth.assertThat(EthereumAddressValidator.validate(address)) + .isTrue() + } +} \ No newline at end of file diff --git a/blockchain/src/test/java/com/tangem/blockchain/stellar/StellarAddressTest.kt b/blockchain/src/test/java/com/tangem/blockchain/stellar/StellarAddressTest.kt new file mode 100644 index 0000000000..5c6f18561f --- /dev/null +++ b/blockchain/src/test/java/com/tangem/blockchain/stellar/StellarAddressTest.kt @@ -0,0 +1,23 @@ +package com.tangem.blockchain.stellar + +import com.google.common.truth.Truth +import com.tangem.common.extensions.hexToBytes +import org.junit.Test + +internal class StellarAddressTest { + @Test + fun makeAddressFromCorrectPublicKey() { + val walletPublicKey = "EC5387D8B38BD9EF80BDBC78D0D7E1C53F08E269436C99D5B3C2DF4B2CE73012".hexToBytes() + val expected = "GDWFHB6YWOF5T34AXW6HRUGX4HCT6CHCNFBWZGOVWPBN6SZM44YBFUDZ" + + Truth.assertThat(StellarAddressFactory.makeAddress(walletPublicKey)) + .isEqualTo(expected) + } + + @Test + fun validateCorrectAddress() { + val address = "GDWFHB6YWOF5T34AXW6HRUGX4HCT6CHCNFBWZGOVWPBN6SZM44YBFUDZ" + Truth.assertThat(StellarAddressValidator.validate(address)) + .isTrue() + } +} \ No newline at end of file