Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-03 18:21:16 +03:00
parent 794e24c69c
commit a21044feee
17 changed files with 252 additions and 134 deletions

View file

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

View file

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

View file

@ -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<UnspentTransaction> = 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<UnspentTransaction>? = null
fun buildToSign(
transactionData: TransactionData): List<ByteArray> {
transactionData: TransactionData): Result<List<ByteArray>> {
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<ByteArray> = mutableListOf()
val hashesForSign: MutableList<ByteArray> = 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<UnspentTransaction>,
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

View file

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

View file

@ -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<UnspentTransaction>
val unspentTransactions: List<UnspentTransaction>?
)
data class BitcoinFee(

View file

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

View file

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

View file

@ -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
}
): BlockcypherTx
}
@JsonClass(generateAdapter = true)
data class BlockcypherBody(val tx: String)

View file

@ -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<BlockcypherTxref>? = null
val txrefs: List<BlockcypherTxref>? = 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)
val priorityFeePerKb: Long? = null
)

View file

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

View file

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

View file

@ -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<Int>
}
interface TransactionSender {

View file

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

View file

@ -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 <T> 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<ByteArray>, cardId: String): TaskEvent<SignResponse> = coroutineScope {
async {
suspendCancellableCoroutine<TaskEvent<SignResponse>> { continuation ->
cardManager.sign(hashes, cardId) { if (continuation.isActive) continuation.resume(it) }
}
}.await()
}
}

View file

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

View file

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

View file

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