Updated on 2026-08-14
This commit is contained in:
parent
316b2551a5
commit
6155fc7643
7 changed files with 137 additions and 88 deletions
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue