From 6155fc764356c66157753612e38ca7bcc3b6d889 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 29 Apr 2020 21:13:06 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../blockchain_demo/BlockchainDemoActivity.kt | 7 +- .../ethereum/EthereumAddressService.kt | 21 +--- .../ethereum/EthereumTransactionBuilder.kt | 114 ++++++++++++++++++ .../ethereum/EthereumWalletManager.kt | 57 +-------- .../network/EthereumNetworkManager.kt | 9 +- .../tangem/blockchain/common/Blockchain.kt | 6 - .../blockchain/common/WalletManagerFactory.kt | 11 +- 7 files changed, 137 insertions(+), 88 deletions(-) create mode 100644 blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionBuilder.kt diff --git a/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt b/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt index bc9a9e7b61..beb7de2173 100644 --- a/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt +++ b/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt @@ -155,7 +155,9 @@ class BlockchainDemoActivity : AppCompatActivity() { private fun formTransactionData(): TransactionData { val amount = if (walletManager.wallet.amounts[AmountType.Token] != null) { - walletManager.wallet.amounts[AmountType.Token]!! + walletManager.wallet.amounts[AmountType.Token]!!.copy( + value = binding.etSumToSend.text.toString().toBigDecimal() - fee + ) } else { walletManager.wallet.amounts[AmountType.Coin]!!.copy( value = binding.etSumToSend.text.toString().toBigDecimal() - fee @@ -165,7 +167,8 @@ class BlockchainDemoActivity : AppCompatActivity() { amount, walletManager.wallet.amounts[AmountType.Coin]!!.copy(value = fee), walletManager.wallet.amounts[AmountType.Coin]!!.address!!, - binding.etReceiverAddress.text.toString() + binding.etReceiverAddress.text.toString(), + contractAddress = walletManager.wallet.amounts[AmountType.Token]?.address ) } diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumAddressService.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumAddressService.kt index 5eadfafd20..50b9ab2bd0 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumAddressService.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumAddressService.kt @@ -2,27 +2,14 @@ package com.tangem.blockchain.blockchains.ethereum import com.tangem.blockchain.common.AddressService import org.kethereum.crypto.toAddress -import org.kethereum.erc55.isValid +import org.kethereum.erc55.hasValidERC55ChecksumOrNoChecksum +import org.kethereum.erc55.withERC55Checksum import org.kethereum.model.Address import org.kethereum.model.PublicKey class EthereumAddressService : AddressService { override fun makeAddress(walletPublicKey: ByteArray): String = - PublicKey(walletPublicKey.sliceArray(1..64)).toAddress().hex + PublicKey(walletPublicKey.sliceArray(1..64)).toAddress().withERC55Checksum().hex - override fun validate(address: String): Boolean = Address(address).isValid() -} - -enum class Chain(val id: Int) { - Mainnet(1), - Morden(2), - Ropsten(3), - Rinkeby(4), - RootstockMainnet(30), - RootstockTestnet(31), - Kovan(42), - EthereumClassicMainnet(61), - EthereumClassicTestnet(62), - Geth_private_chains(1337), - MaticTestnet(8995); + override fun validate(address: String): Boolean = Address(address).hasValidERC55ChecksumOrNoChecksum() } \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionBuilder.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionBuilder.kt new file mode 100644 index 0000000000..8ab1dcc1ad --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionBuilder.kt @@ -0,0 +1,114 @@ +package com.tangem.blockchain.blockchains.ethereum + +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.common.extensions.hexToBytes +import org.kethereum.DEFAULT_GAS_LIMIT +import org.kethereum.crypto.api.ec.ECDSASignature +import org.kethereum.crypto.determineRecId +import org.kethereum.crypto.impl.ec.canonicalise +import org.kethereum.extensions.toBytesPadded +import org.kethereum.extensions.toFixedLengthByteArray +import org.kethereum.extensions.transactions.encodeRLP +import org.kethereum.extensions.transactions.tokenTransferSignature +import org.kethereum.keccakshortcut.keccak +import org.kethereum.model.* +import java.math.BigDecimal +import java.math.BigInteger + +class EthereumTransactionBuilder(private val walletPublicKey: ByteArray, blockchain: Blockchain) { + + private val chainId = when (blockchain) { + Blockchain.Ethereum -> Chain.Mainnet.id + else -> throw Exception("${blockchain.fullName} blockchain is not supported by EthereumTransactionBuilder") + } + + fun buildToSign(transactionData: TransactionData, nonce: BigInteger?): TransactionToSign? { + + val amount: BigDecimal = transactionData.amount.value ?: return null + val transactionFee: BigDecimal = transactionData.fee?.value ?: return null + + val fee = transactionFee.movePointRight(transactionData.fee.decimals).toBigInteger() + val gasLimit = getGasLimit(transactionData.amount).value.toBigInteger() + val bigIntegerAmount = amount.movePointRight(transactionData.amount.decimals).toBigInteger() + + val to: Address + val value: BigInteger + val input: ByteArray//data for smart contract + + if (transactionData.amount.type == AmountType.Coin) { //ETH transfer + to = Address(transactionData.destinationAddress) + value = bigIntegerAmount + input = ByteArray(0) + } else { //Token transfer + to = Address(transactionData.contractAddress ?: throw Exception("Contract address is not specified!")) + value = BigInteger.ZERO + input = createErc20TransferData(transactionData.destinationAddress, bigIntegerAmount) + } + + val transaction = createTransactionWithDefaults( + from = Address(transactionData.sourceAddress), + to = to, + value = value, + gasPrice = fee.divide(gasLimit), + gasLimit = gasLimit, + nonce = nonce, + input = input +// chain = ChainId(chainId.toLong()) + ) + val hash = transaction.encodeRLP(SignatureData(v = chainId.toBigInteger())).keccak() + return TransactionToSign(transaction, listOf(hash)) + } + + fun buildToSend(signature: ByteArray, transactionToSign: TransactionToSign): ByteArray { + val r = BigInteger(1, signature.copyOfRange(0, 32)) + val s = BigInteger(1, signature.copyOfRange(32, 64)) + + val ecdsaSignature = ECDSASignature(r, s).canonicalise() + + val recId = ecdsaSignature.determineRecId(transactionToSign.hashes[0], PublicKey(walletPublicKey.sliceArray(1..64))) + val v = (recId + 27 + 8 + (chainId * 2)).toBigInteger() + val signatureData = SignatureData(ecdsaSignature.r, ecdsaSignature.s, v) + + return transactionToSign.transaction.encodeRLP(signatureData) + } + + private fun createErc20TransferData(recepient: String, amount: BigInteger): ByteArray { + return tokenTransferSignature.toByteArray() + + recepient.substring(2).hexToBytes().toFixedLengthByteArray(32) + + amount.toBytesPadded(32) + } +} + +class TransactionToSign(val transaction: Transaction, val hashes: List) + +enum class GasLimit(val value: Long) { + Default(21000), + Token(60000), + High(300000) +} + +internal fun getGasLimit(amount: Amount): GasLimit { + return when (amount.currencySymbol) { + Blockchain.Ethereum.currency -> GasLimit.Default + "DGX" -> GasLimit.High + "CGT" -> GasLimit.High + else -> GasLimit.Token + } +} + +enum class Chain(val id: Int) { + Mainnet(1), + Morden(2), + Ropsten(3), + Rinkeby(4), + RootstockMainnet(30), + RootstockTestnet(31), + Kovan(42), + EthereumClassicMainnet(61), + EthereumClassicTestnet(62), + Geth_private_chains(1337), + MaticTestnet(8995); +} \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt index 2fbc658fe3..76d48f8ba8 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt @@ -31,6 +31,7 @@ class EthereumWalletManager( private var txCount = -1L override suspend fun update() { + val result = networkManager.getInfo(wallet.address, wallet.amounts[AmountType.Token]?.address) when (result) { is Result.Failure -> updateError(result.error) @@ -73,63 +74,9 @@ class EthereumWalletManager( is Result.Success -> { val feeValues: List = result.data return Result.Success( - feeValues.map { feeValue -> Amount(amount, feeValue) }) + feeValues.map { feeValue -> Amount(wallet.amounts[AmountType.Coin]!!, feeValue) }) } is Result.Failure -> return result } } -} - -class EthereumTransactionBuilder(private val walletPublicKey: ByteArray, private val chain: Chain) { - - fun buildToSign(transactionData: TransactionData, nonce: BigInteger?): TransactionToSign? { - - val amount: BigDecimal = transactionData.amount.value ?: return null - val transactionFee: BigDecimal = transactionData.fee?.value ?: return null - - val value = amount.movePointRight(transactionData.amount.decimals.toInt()).toBigInteger() - val fee = transactionFee.movePointRight(transactionData.fee.decimals.toInt()).toBigInteger() - - val transaction = createTransactionWithDefaults( - from = Address(transactionData.sourceAddress), - to = Address(transactionData.destinationAddress), - value = value, - gasPrice = fee.divide(DEFAULT_GAS_LIMIT), - gasLimit = DEFAULT_GAS_LIMIT, - nonce = nonce, - chain = ChainId(chain.id.toLong()) - ) - val hash = transaction.encodeRLP(SignatureData(v = chain.id.toBigInteger())).keccak() - return TransactionToSign(transaction, listOf(hash)) - } - - fun buildToSend(signature: ByteArray, transactionToSign: TransactionToSign): ByteArray { - val r = BigInteger(1, signature.copyOfRange(0, 32)) - val s = BigInteger(1, signature.copyOfRange(32, 64)) - - val ecdsaSignature = ECDSASignature(r, s).canonicalise() - - val recId = ecdsaSignature.determineRecId(transactionToSign.hashes[0], PublicKey(walletPublicKey.sliceArray(1..64))) - val v = (recId + 27 + 8 + (chain.id * 2)).toBigInteger() - val signatureData = SignatureData(ecdsaSignature.r, ecdsaSignature.s, v) - - return transactionToSign.transaction.encodeRLP(signatureData) - } -} - -class TransactionToSign(val transaction: Transaction, val hashes: List) - -enum class GasLimit(val value: Long) { - Default(21000), - Token(60000), - High(300000) -} - -private fun getGasLimit(amount: Amount): GasLimit { - return when (amount.currencySymbol) { - Blockchain.Ethereum.currency -> GasLimit.Default - "DGX" -> GasLimit.High - "CGT" -> GasLimit.High - else -> GasLimit.Token - } } \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt index 257dd29787..757d6a5a56 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt @@ -1,5 +1,6 @@ package com.tangem.blockchain.blockchains.ethereum.network +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.extensions.retryIO @@ -14,10 +15,14 @@ import java.math.BigInteger import java.math.RoundingMode -class EthereumNetworkManager { +class EthereumNetworkManager(blockchain: Blockchain) { private val api: InfuraApi by lazy { - createRetrofitInstance(API_INFURA).create(InfuraApi::class.java) + val baseUrl = when (blockchain) { + Blockchain.Ethereum -> API_INFURA + else -> throw Exception("${blockchain.fullName} blockchain is not supported by EthereumNetworkManager") + } + createRetrofitInstance(baseUrl).create(InfuraApi::class.java) } private val provider: InfuraProvider by lazy { InfuraProvider(api) } 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 34920ecaca..540e8c71d4 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt @@ -27,12 +27,6 @@ enum class Blockchain( BinanceTestnet("BINANCE/test", "BNBt", "Binance"), Stellar("XLM", "XLM", "Stellar"); - fun roundingMode(): Int = when (this) { - Bitcoin, BitcoinTestnet, Ethereum, Rootstock, Binance, BinanceTestnet -> BigDecimal.ROUND_DOWN - Cardano -> BigDecimal.ROUND_UP - else -> BigDecimal.ROUND_HALF_UP - } - fun decimals(): Int = when (this) { Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet -> 8 Cardano, XRP -> 6 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 b0a50afe28..20d0c1883e 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt @@ -42,31 +42,30 @@ object WalletManagerFactory { when (blockchain) { Blockchain.Bitcoin -> { return BitcoinWalletManager( - card.cardId, wallet, + cardId, wallet, BitcoinTransactionBuilder(walletPublicKey), BitcoinNetworkManager() ) } Blockchain.BitcoinTestnet -> { return BitcoinWalletManager( - card.cardId, wallet, + cardId, wallet, BitcoinTransactionBuilder(walletPublicKey, true), BitcoinNetworkManager(true) ) } Blockchain.BitcoinCash -> { return BitcoinCashWalletManager( - card.cardId, wallet, + cardId, wallet, BitcoinCashTransactionBuilder(walletPublicKey.toCompressedPublicKey()), BitcoinCashNetworkManager() ) } Blockchain.Ethereum -> { - val chain = Chain.Mainnet return EthereumWalletManager( cardId, wallet, - EthereumTransactionBuilder(walletPublicKey, chain), - EthereumNetworkManager() + EthereumTransactionBuilder(walletPublicKey, blockchain), + EthereumNetworkManager(blockchain) ) } Blockchain.Stellar -> {