Updated on 2026-08-14
This commit is contained in:
commit
a70064ade3
10 changed files with 84 additions and 83 deletions
|
|
@ -5,7 +5,6 @@ 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
|
||||
|
|
@ -22,6 +21,7 @@ class EthereumTransactionBuilder(private val walletPublicKey: ByteArray, blockch
|
|||
|
||||
private val chainId = when (blockchain) {
|
||||
Blockchain.Ethereum -> Chain.Mainnet.id
|
||||
Blockchain.RSK -> Chain.RskMainnet.id
|
||||
else -> throw Exception("${blockchain.fullName} blockchain is not supported by EthereumTransactionBuilder")
|
||||
}
|
||||
|
||||
|
|
@ -36,14 +36,15 @@ class EthereumTransactionBuilder(private val walletPublicKey: ByteArray, blockch
|
|||
|
||||
val to: Address
|
||||
val value: BigInteger
|
||||
val input: ByteArray//data for smart contract
|
||||
val input: ByteArray //data for smart contract
|
||||
|
||||
if (transactionData.amount.type == AmountType.Coin) { //ETH transfer
|
||||
if (transactionData.amount.type == AmountType.Coin) { //coin transfer
|
||||
to = Address(transactionData.destinationAddress)
|
||||
value = bigIntegerAmount
|
||||
input = ByteArray(0)
|
||||
} else { //Token transfer
|
||||
to = Address(transactionData.contractAddress ?: throw Exception("Contract address is not specified!"))
|
||||
} else { //token transfer
|
||||
to = Address(transactionData.contractAddress
|
||||
?: throw Exception("Contract address is not specified!"))
|
||||
value = BigInteger.ZERO
|
||||
input = createErc20TransferData(transactionData.destinationAddress, bigIntegerAmount)
|
||||
}
|
||||
|
|
@ -104,8 +105,8 @@ enum class Chain(val id: Int) {
|
|||
Morden(2),
|
||||
Ropsten(3),
|
||||
Rinkeby(4),
|
||||
RootstockMainnet(30),
|
||||
RootstockTestnet(31),
|
||||
RskMainnet(30),
|
||||
RskTestnet(31),
|
||||
Kovan(42),
|
||||
EthereumClassicMainnet(61),
|
||||
EthereumClassicTestnet(62),
|
||||
|
|
|
|||
|
|
@ -2,21 +2,13 @@ package com.tangem.blockchain.blockchains.ethereum
|
|||
|
||||
import android.util.Log
|
||||
import com.tangem.blockchain.blockchains.ethereum.network.EthereumNetworkManager
|
||||
import com.tangem.blockchain.blockchains.ethereum.network.EthereumResponse
|
||||
import com.tangem.blockchain.blockchains.ethereum.network.EthereumInfoResponse
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.toHexString
|
||||
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.transactions.encodeRLP
|
||||
import org.kethereum.keccakshortcut.keccak
|
||||
import org.kethereum.model.*
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
class EthereumWalletManager(
|
||||
cardId: String,
|
||||
|
|
@ -39,7 +31,7 @@ class EthereumWalletManager(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateWallet(data: EthereumResponse) {
|
||||
private fun updateWallet(data: EthereumInfoResponse) {
|
||||
wallet.amounts[AmountType.Coin]?.value = data.balance
|
||||
wallet.amounts[AmountType.Token]?.value = data.tokenBalance
|
||||
txCount = data.txCount
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import com.squareup.moshi.JsonClass
|
|||
import retrofit2.http.Body
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface InfuraApi {
|
||||
interface EthereumApi {
|
||||
@Headers("Content-Type: application/json")
|
||||
@POST("v3/613a0b14833145968b1f656240c7d245")
|
||||
suspend fun postToInfura(@Body body: InfuraBody?): InfuraResponse
|
||||
@POST("{apiKey}")
|
||||
suspend fun post(@Body body: EthereumBody?, @Path("apiKey") apiKey: String): EthereumResponse
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class InfuraBody(
|
||||
data class EthereumBody(
|
||||
val jsonrpc: String = "2.0",
|
||||
val id: Int = 67,
|
||||
val method: String? = null,
|
||||
|
|
@ -21,7 +22,7 @@ data class InfuraBody(
|
|||
|
||||
data class EthCallParams(private val data: String, private val to: String)
|
||||
|
||||
enum class InfuraMethod(val value: String) {
|
||||
enum class EthereumMethod(val value: String) {
|
||||
GET_BALANCE("eth_getBalance"),
|
||||
GET_TRANSACTION_COUNT("eth_getTransactionCount"),
|
||||
GET_PENDING_COUNT("eth_getPendingCount"),
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchain.extensions.Result
|
|||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.blockchain.extensions.retryIO
|
||||
import com.tangem.blockchain.network.API_INFURA
|
||||
import com.tangem.blockchain.network.API_RSK
|
||||
import com.tangem.blockchain.network.createRetrofitInstance
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -16,16 +17,24 @@ import java.math.RoundingMode
|
|||
|
||||
|
||||
class EthereumNetworkManager(blockchain: Blockchain) {
|
||||
private val infuraPath = "v3/"
|
||||
|
||||
private val api: InfuraApi by lazy {
|
||||
private val api: EthereumApi by lazy {
|
||||
val baseUrl = when (blockchain) {
|
||||
Blockchain.Ethereum -> API_INFURA
|
||||
Blockchain.Ethereum -> API_INFURA + infuraPath
|
||||
Blockchain.RSK -> API_RSK
|
||||
else -> throw Exception("${blockchain.fullName} blockchain is not supported by EthereumNetworkManager")
|
||||
}
|
||||
createRetrofitInstance(baseUrl).create(InfuraApi::class.java)
|
||||
createRetrofitInstance(baseUrl).create(EthereumApi::class.java)
|
||||
}
|
||||
|
||||
private val provider: InfuraProvider by lazy { InfuraProvider(api) }
|
||||
private val apiKey = when (blockchain) {
|
||||
Blockchain.Ethereum -> INFURA_API_KEY
|
||||
Blockchain.RSK -> ""
|
||||
else -> throw Exception("${blockchain.fullName} blockchain is not supported by EthereumNetworkManager")
|
||||
}
|
||||
|
||||
private val provider: EthereumProvider by lazy { EthereumProvider(api, apiKey) }
|
||||
|
||||
suspend fun sendTransaction(transaction: String): SimpleResult {
|
||||
return try {
|
||||
|
|
@ -50,17 +59,17 @@ class EthereumNetworkManager(blockchain: Blockchain) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getInfo(address: String, contractAddress: String? = null): Result<EthereumResponse> {
|
||||
suspend fun getInfo(address: String, contractAddress: String? = null): Result<EthereumInfoResponse> {
|
||||
return try {
|
||||
coroutineScope {
|
||||
val balanceResponse = retryIO { async { provider.getBalance(address) } }
|
||||
val txCountResponse = retryIO { async { provider.getTxCount(address) } }
|
||||
val pendingTxCountResponse = retryIO { async { provider.getPendingTxCount(address) } }
|
||||
var tokenBalanceResponse: Deferred<InfuraResponse>? = null
|
||||
var tokenBalanceResponse: Deferred<EthereumResponse>? = null
|
||||
if (contractAddress != null) {
|
||||
tokenBalanceResponse = retryIO { async { provider.getTokenBalance(address, contractAddress) } }
|
||||
}
|
||||
Result.Success(EthereumResponse(
|
||||
Result.Success(EthereumInfoResponse(
|
||||
balanceResponse.await().result!!.parseAmount(),
|
||||
tokenBalanceResponse?.await()?.result?.parseAmount(),
|
||||
txCountResponse.await().result?.responseToNumber()?.toLong() ?: 0,
|
||||
|
|
@ -96,9 +105,11 @@ class EthereumNetworkManager(blockchain: Blockchain) {
|
|||
|
||||
}
|
||||
|
||||
data class EthereumResponse(
|
||||
data class EthereumInfoResponse(
|
||||
val balance: BigDecimal,
|
||||
val tokenBalance: BigDecimal?,
|
||||
val txCount: Long,
|
||||
val pendingTxCount: Long
|
||||
)
|
||||
)
|
||||
|
||||
private const val INFURA_API_KEY = "613a0b14833145968b1f656240c7d245"
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.blockchain.blockchains.ethereum.network
|
||||
|
||||
class EthereumProvider(private val api: EthereumApi, private val apiKey: String) {
|
||||
suspend fun getBalance(address: String) = api.post(createEthereumBody(EthereumMethod.GET_BALANCE, address), apiKey)
|
||||
suspend fun getTokenBalance(address: String, contractAddress: String) = api.post(createEthereumBody(EthereumMethod.CALL, address, contractAddress), apiKey)
|
||||
suspend fun getTxCount(address: String) = api.post(createEthereumBody(EthereumMethod.GET_TRANSACTION_COUNT, address), apiKey)
|
||||
suspend fun getPendingTxCount(address: String) = api.post(createEthereumBody(EthereumMethod.GET_PENDING_COUNT, address), apiKey)
|
||||
suspend fun getGasPrice() = api.post(createEthereumBody(EthereumMethod.GAS_PRICE), apiKey)
|
||||
suspend fun sendTransaction(transaction: String) = api.post(createEthereumBody(EthereumMethod.SEND_RAW_TRANSACTION, transaction = transaction), apiKey)
|
||||
}
|
||||
|
||||
private fun createEthereumBody(
|
||||
method: EthereumMethod,
|
||||
address: String? = null,
|
||||
contractAddress: String? = null,
|
||||
transaction: String? = null): EthereumBody {
|
||||
|
||||
return when (method) {
|
||||
EthereumMethod.GET_BALANCE ->
|
||||
EthereumBody(method = EthereumMethod.GET_BALANCE.value, params = listOf(address ?: "", "latest"))
|
||||
EthereumMethod.GET_TRANSACTION_COUNT ->
|
||||
EthereumBody(method = EthereumMethod.GET_TRANSACTION_COUNT.value, params = listOf(address ?: "", "latest"))
|
||||
EthereumMethod.GET_PENDING_COUNT ->
|
||||
EthereumBody(method = EthereumMethod.GET_TRANSACTION_COUNT.value, params = listOf(address ?: "", "pending"))
|
||||
EthereumMethod.GAS_PRICE ->
|
||||
EthereumBody(method = EthereumMethod.GAS_PRICE.value)
|
||||
EthereumMethod.SEND_RAW_TRANSACTION ->
|
||||
EthereumBody(method = EthereumMethod.SEND_RAW_TRANSACTION.value, params = listOf(transaction ?: ""))
|
||||
EthereumMethod.CALL -> {
|
||||
EthereumBody(
|
||||
method = EthereumMethod.CALL.value,
|
||||
params = listOf(EthCallParams(
|
||||
"0x70a08231000000000000000000000000" + address?.substring(2), contractAddress
|
||||
?: ""),
|
||||
"latest"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class InfuraResponse(
|
||||
data class EthereumResponse(
|
||||
@Json(name = "jsonrpc")
|
||||
val jsonrpc: String = "",
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ data class InfuraResponse(
|
|||
val result: String? = null,
|
||||
|
||||
@Json(name = "error")
|
||||
val error: InfuraError? = null
|
||||
val error: EthereumError? = null
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class InfuraError(
|
||||
data class EthereumError(
|
||||
@Json(name = "code")
|
||||
val code: Int? = null,
|
||||
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.blockchain.blockchains.ethereum.network
|
||||
|
||||
class InfuraProvider(private val api: InfuraApi) {
|
||||
suspend fun getBalance(address: String) = api.postToInfura(createInfuraBody(InfuraMethod.GET_BALANCE, address))
|
||||
suspend fun getTokenBalance(address: String, contractAddress: String) = api.postToInfura(createInfuraBody(InfuraMethod.CALL, address, contractAddress))
|
||||
suspend fun getTxCount(address: String) = api.postToInfura(createInfuraBody(InfuraMethod.GET_TRANSACTION_COUNT, address))
|
||||
suspend fun getPendingTxCount(address: String) = api.postToInfura(createInfuraBody(InfuraMethod.GET_PENDING_COUNT, address))
|
||||
suspend fun getGasPrice() = api.postToInfura(createInfuraBody(InfuraMethod.GAS_PRICE))
|
||||
suspend fun sendTransaction(transaction: String) = api.postToInfura(createInfuraBody(InfuraMethod.SEND_RAW_TRANSACTION, transaction = transaction))
|
||||
}
|
||||
|
||||
private fun createInfuraBody(
|
||||
method: InfuraMethod,
|
||||
address: String? = null,
|
||||
contractAddress: String? = null,
|
||||
transaction: String? = null): InfuraBody {
|
||||
|
||||
return when (method) {
|
||||
InfuraMethod.GET_BALANCE ->
|
||||
InfuraBody(method = InfuraMethod.GET_BALANCE.value, params = listOf(address ?: "", "latest"))
|
||||
InfuraMethod.GET_TRANSACTION_COUNT ->
|
||||
InfuraBody(method = InfuraMethod.GET_TRANSACTION_COUNT.value, params = listOf(address ?: "", "latest"))
|
||||
InfuraMethod.GET_PENDING_COUNT ->
|
||||
InfuraBody(method = InfuraMethod.GET_TRANSACTION_COUNT.value, params = listOf(address ?: "", "pending"))
|
||||
InfuraMethod.GAS_PRICE ->
|
||||
InfuraBody(method = InfuraMethod.GAS_PRICE.value)
|
||||
InfuraMethod.SEND_RAW_TRANSACTION ->
|
||||
InfuraBody(method = InfuraMethod.SEND_RAW_TRANSACTION.value, params = listOf(transaction ?: ""))
|
||||
InfuraMethod.CALL -> {
|
||||
InfuraBody(
|
||||
method = InfuraMethod.CALL.value,
|
||||
params = listOf(EthCallParams(
|
||||
"0x70a08231000000000000000000000000" + address?.substring(2), contractAddress
|
||||
?: ""),
|
||||
"latest"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumAddressService
|
|||
import com.tangem.blockchain.blockchains.stellar.StellarAddressService
|
||||
import com.tangem.blockchain.blockchains.xrp.XrpAddressService
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
enum class Blockchain(
|
||||
val id: String,
|
||||
val currency: String,
|
||||
|
|
@ -20,7 +18,7 @@ enum class Blockchain(
|
|||
BitcoinTestnet("BTC/test", "BTCt", "Bitcoin Testnet"),
|
||||
BitcoinCash("BCH", "BCH", "Bitcoin Cash"),
|
||||
Ethereum("ETH", "ETH", "Ethereum"),
|
||||
Rootstock("", "", ""),
|
||||
RSK("RSK", "RBTC", "RSK"),
|
||||
Cardano("CARDANO", "ADA", "Cardano"),
|
||||
XRP("XRP", "XRP", "XRP Ledger"),
|
||||
Binance("BINANCE", "BNB", "Binance"),
|
||||
|
|
@ -30,7 +28,7 @@ enum class Blockchain(
|
|||
fun decimals(): Int = when (this) {
|
||||
Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet -> 8
|
||||
Cardano, XRP -> 6
|
||||
Ethereum, Rootstock -> 18
|
||||
Ethereum, RSK -> 18
|
||||
Stellar -> 7
|
||||
Unknown -> 0
|
||||
}
|
||||
|
|
@ -48,8 +46,7 @@ enum class Blockchain(
|
|||
Bitcoin -> BitcoinAddressService()
|
||||
BitcoinTestnet -> BitcoinAddressService(true)
|
||||
BitcoinCash -> BitcoinCashAddressService()
|
||||
Ethereum -> EthereumAddressService()
|
||||
Rootstock -> throw Exception("unsupported blockchain")
|
||||
Ethereum, RSK -> EthereumAddressService()
|
||||
Cardano -> CardanoAddressService()
|
||||
XRP -> XrpAddressService()
|
||||
Binance -> BinanceAddressService()
|
||||
|
|
@ -75,7 +72,7 @@ enum class Blockchain(
|
|||
} else {
|
||||
"https://etherscan.io/token/${token.contractAddress}?a=$address"
|
||||
}
|
||||
Rootstock -> {
|
||||
RSK -> {
|
||||
var url = "https://explorer.rsk.co/address/$address"
|
||||
if (token != null) {
|
||||
url += "?__tab=tokens"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.blockchain.blockchains.bitcoincash.BitcoinCashWalletManager
|
|||
import com.tangem.blockchain.blockchains.cardano.CardanoTransactionBuilder
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoWalletManager
|
||||
import com.tangem.blockchain.blockchains.cardano.network.CardanoNetworkManager
|
||||
import com.tangem.blockchain.blockchains.ethereum.Chain
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionBuilder
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.ethereum.network.EthereumNetworkManager
|
||||
|
|
@ -61,7 +60,7 @@ object WalletManagerFactory {
|
|||
BitcoinCashNetworkManager()
|
||||
)
|
||||
}
|
||||
Blockchain.Ethereum -> {
|
||||
Blockchain.Ethereum, Blockchain.RSK -> {
|
||||
return EthereumWalletManager(
|
||||
cardId, wallet,
|
||||
EthereumTransactionBuilder(walletPublicKey, blockchain),
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const val API_INFURA = "https://mainnet.infura.io/"
|
|||
const val API_SOCHAIN_V2 = "https://chain.so/"
|
||||
const val API_ESTIMATEFEE = "https://estimatefee.com/"
|
||||
const val API_UPDATE_VERSION = "https://raw.githubusercontent.com/"
|
||||
const val API_ROOTSTOCK = "https://public-node.rsk.co/"
|
||||
const val API_RSK = "https://public-node.rsk.co/"
|
||||
const val API_BLOCKCYPHER = "https://api.blockcypher.com/"
|
||||
const val API_BINANCE = "https://dex.binance.org/"
|
||||
const val API_BINANCE_TESTNET = "https://testnet-dex.binance.org/"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue