Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-30 14:52:43 +00:00
commit fa34a56048
8 changed files with 145 additions and 89 deletions

View file

@ -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()
)
} 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
)
}

View file

@ -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()
}

View file

@ -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<ByteArray>)
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);
}

View file

@ -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<BigDecimal> = 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<ByteArray>)
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
}
}

View file

@ -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) }

View file

@ -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

View file

@ -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 -> {

View file

@ -11,7 +11,7 @@ internal class EthereumAddressTest {
@Test
fun makeAddressFromCorrectPublicKey() {
val walletPublicKey = "04BAEC8CD3BA50FDFE1E8CF2B04B58E17041245341CD1F1C6B3A496B48956DB4C896A6848BCF8FCFC33B88341507DD25E5F4609386C68086C74CF472B86E5C3820".hexToBytes()
val expected = "0xc63763572d45171e4c25ca0818b44e5dd7f5c15b"
val expected = "0xc63763572D45171e4C25cA0818b44E5Dd7F5c15B"
Truth.assertThat(addressService.makeAddress(walletPublicKey))
.isEqualTo(expected)
@ -23,4 +23,11 @@ internal class EthereumAddressTest {
Truth.assertThat(addressService.validate(address))
.isTrue()
}
@Test
fun validateCorrectAddressWithChecksum() {
val address = "0xc63763572D45171e4C25cA0818b44E5Dd7F5c15B"
Truth.assertThat(addressService.validate(address))
.isTrue()
}
}