Updated on 2026-08-14

This commit is contained in:
Tangem 2020-05-13 18:41:41 +03:00
parent b32d9b00d9
commit ca0a10de61
22 changed files with 321 additions and 125 deletions

View file

@ -1,68 +1,62 @@
package com.tangem.blockchain.blockchains.bitcoin
import com.tangem.blockchain.blockchains.litecoin.LitecoinMainNetParams
import com.tangem.blockchain.common.AddressService
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.calculateRipemd160
import com.tangem.common.extensions.calculateSha256
import org.bitcoinj.core.AddressFormatException
import org.bitcoinj.core.Base58
import org.bitcoinj.core.SegwitAddress
import org.bitcoinj.core.*
import org.bitcoinj.params.MainNetParams
import org.bitcoinj.params.TestNet3Params
import java.security.MessageDigest
class BitcoinAddressService(private val testNet: Boolean = false): AddressService {
class BitcoinAddressService(private val blockchain: Blockchain) : AddressService {
private val networkParameters: NetworkParameters = when (blockchain) {
Blockchain.Bitcoin -> MainNetParams()
Blockchain.BitcoinTestnet -> TestNet3Params()
Blockchain.Litecoin -> LitecoinMainNetParams()
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
override fun makeAddress(walletPublicKey: ByteArray): String {
val netSelectionByte = if (testNet) 0x6f.toByte() else 0x00.toByte()
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)
}
val publicKeyHash = walletPublicKey.calculateSha256().calculateRipemd160()
val checksum = byteArrayOf(networkParameters.addressHeader.toByte()).plus(publicKeyHash)
.calculateSha256().calculateSha256()
val result = byteArrayOf(networkParameters.addressHeader.toByte()) + publicKeyHash + checksum.copyOfRange(0, 4)
return Base58.encode(result)
}
override fun validate(address: String): Boolean {
if (firstLetters.contains(address.first())) {
if (testNet && firstLettersNonTestNet.contains(address.first())) return false
if (address.length !in 26..35) return false
val decoded = address.decodeBase58() ?: return false
val hash = recursiveSha256(decoded, 0, 21, 2)
return hash.sliceArray(0..3).contentEquals(decoded.sliceArray(21..24))
} else {
return validateSegwitAddress(address, testNet)
override fun validate(address: String): Boolean {
return validateLegacyAddress(address) || validateSegwitAddress(address)
}
private fun validateSegwitAddress(address: String): Boolean {
return try {
when (blockchain) {
Blockchain.Bitcoin -> SegwitAddress.fromBech32(MainNetParams(), address)
Blockchain.BitcoinTestnet -> SegwitAddress.fromBech32(TestNet3Params(), address)
Blockchain.Litecoin -> SegwitAddress.fromBech32(LitecoinMainNetParams(), address)
else -> return false
}
true
} catch (e: Exception) {
false
}
}
private fun recursiveSha256(data: ByteArray, start: Int, len: Int, recursion: Int): ByteArray {
if (recursion == 0) return data
val md = MessageDigest.getInstance("SHA-256")
md.update(data.sliceArray(start until start + len))
return recursiveSha256(md.digest(), 0, 32, recursion - 1)
}
private fun String.decodeBase58(): ByteArray? {
return try {
Base58.decode(this)
} catch (exception: AddressFormatException) {
null
private fun validateLegacyAddress(address: String): Boolean {
return try {
when (blockchain) {
Blockchain.Bitcoin -> LegacyAddress.fromBase58(MainNetParams(), address)
Blockchain.BitcoinTestnet -> LegacyAddress.fromBase58(TestNet3Params(), address)
Blockchain.Litecoin -> LegacyAddress.fromBase58(LitecoinMainNetParams(), address)
else -> return false
}
true
} catch (e: Exception) {
false
}
private fun validateSegwitAddress(address: String, testNet: Boolean): Boolean {
return try {
if (testNet) {
SegwitAddress.fromBech32(TestNet3Params(), address)
true
} else {
SegwitAddress.fromBech32(MainNetParams(), address)
true
}
} catch (e: Exception) {
false
}
}
companion object {
private const val firstLetters = "123nm"
private const val firstLettersNonTestNet = "13"
}
}

View file

@ -1,21 +1,30 @@
package com.tangem.blockchain.blockchains.bitcoin
import com.tangem.blockchain.blockchains.litecoin.LitecoinMainNetParams
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
import org.bitcoinj.core.*
import org.bitcoinj.crypto.TransactionSignature
import org.bitcoinj.params.MainNetParams
import org.bitcoinj.params.TestNet3Params
import org.bitcoinj.script.Script
import org.bitcoinj.script.ScriptBuilder
import java.math.BigDecimal
import java.math.BigInteger
open class BitcoinTransactionBuilder(
private val walletPublicKey: ByteArray, private val testNet: Boolean = false
private val walletPublicKey: ByteArray, blockchain: Blockchain
) {
private lateinit var transaction: Transaction
protected var networkParameters: NetworkParameters? = null
protected var networkParameters = when (blockchain) {
Blockchain.Bitcoin, Blockchain.BitcoinCash -> MainNetParams()
Blockchain.BitcoinTestnet -> TestNet3Params()
Blockchain.Litecoin -> LitecoinMainNetParams()
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
var unspentOutputs: List<BitcoinUnspentOutput>? = null
open fun buildToSign(
@ -25,11 +34,6 @@ open class BitcoinTransactionBuilder(
val change: BigDecimal = calculateChange(transactionData, unspentOutputs!!)
networkParameters = if (testNet) {
NetworkParameters.fromID(NetworkParameters.ID_TESTNET)
} else {
NetworkParameters.fromID(NetworkParameters.ID_MAINNET)
}
transaction = transactionData.toBitcoinJTransaction(networkParameters, unspentOutputs!!, change)
val hashesForSign: MutableList<ByteArray> = MutableList(transaction.inputs.size) { byteArrayOf() }

View file

@ -74,11 +74,16 @@ open class BitcoinWalletManager(
val minFee = feeResult.data.minimalPerKb.calculateFee(transactionSize)
val normalFee = feeResult.data.normalPerKb.calculateFee(transactionSize)
val priorityFee = feeResult.data.priorityPerKb.calculateFee(transactionSize)
return Result.Success(
listOf(Amount(minFee, blockchain),
Amount(normalFee, blockchain),
Amount(priorityFee, blockchain))
val fees = listOf(Amount(minFee, blockchain),
Amount(normalFee, blockchain),
Amount(priorityFee, blockchain)
)
val minimalFee = transactionSize.movePointLeft(blockchain.decimals())
for (fee in fees) {
if (fee.value!! < minimalFee) fee.value = minimalFee
}
return Result.Success(fees)
}
}
}

View file

@ -1,26 +1,29 @@
package com.tangem.blockchain.blockchains.bitcoin.network
import com.tangem.blockchain.blockchains.bitcoin.BitcoinUnspentOutput
import com.tangem.blockchain.blockchains.bitcoin.network.api.BlockchainInfoApi
import com.tangem.blockchain.blockchains.bitcoin.network.api.BlockcypherApi
import com.tangem.blockchain.blockchains.bitcoin.network.api.EstimatefeeApi
import com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo.BlockchainInfoApi
import com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo.BlockchainInfoProvider
import com.tangem.blockchain.network.blockcypher.BlockcypherApi
import com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo.EstimatefeeApi
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.network.API_BLOCKCHAIN_INFO
import com.tangem.blockchain.network.API_BLOCKCYPHER
import com.tangem.blockchain.network.API_ESTIMATEFEE
import com.tangem.blockchain.network.blockcypher.BlockcypherProvider
import com.tangem.blockchain.network.createRetrofitInstance
import retrofit2.HttpException
import java.io.IOException
import java.math.BigDecimal
class BitcoinNetworkManager(private val isTestNet: Boolean = false) : BitcoinProvider {
class BitcoinNetworkManager(blockchain: Blockchain) : BitcoinProvider {
private val blockcypherProvider by lazy {
val api = createRetrofitInstance(API_BLOCKCYPHER)
.create(BlockcypherApi::class.java)
BlockcypherProvider(api, isTestNet)
BlockcypherProvider(api, blockchain)
}
private val blockchainInfoProvider by lazy {
@ -31,10 +34,10 @@ class BitcoinNetworkManager(private val isTestNet: Boolean = false) : BitcoinPro
BlockchainInfoProvider(api, estimateFeeApi)
}
private var bitcoinProvider: BitcoinProvider = blockchainInfoProvider
private var provider: BitcoinProvider = blockchainInfoProvider
private fun changeProvider() {
bitcoinProvider = if (bitcoinProvider == blockchainInfoProvider) {
provider = if (provider == blockchainInfoProvider) {
blockcypherProvider
} else {
blockchainInfoProvider
@ -42,13 +45,13 @@ class BitcoinNetworkManager(private val isTestNet: Boolean = false) : BitcoinPro
}
override suspend fun getInfo(address: String): Result<BitcoinAddressResponse> {
val result = bitcoinProvider.getInfo(address)
val result = provider.getInfo(address)
when (result) {
is Result.Success -> return result
is Result.Failure -> {
if (result.error is IOException || result.error is HttpException) {
changeProvider()
return bitcoinProvider.getInfo(address)
return provider.getInfo(address)
} else {
return result
}
@ -57,13 +60,13 @@ class BitcoinNetworkManager(private val isTestNet: Boolean = false) : BitcoinPro
}
override suspend fun getFee(): Result<BitcoinFee> {
val result = bitcoinProvider.getFee()
val result = provider.getFee()
when (result) {
is Result.Success -> return result
is Result.Failure -> {
if (result.error is IOException || result.error is HttpException) {
changeProvider()
return bitcoinProvider.getFee()
return provider.getFee()
} else {
return result
}
@ -72,13 +75,13 @@ class BitcoinNetworkManager(private val isTestNet: Boolean = false) : BitcoinPro
}
override suspend fun sendTransaction(transaction: String): SimpleResult {
val result = bitcoinProvider.sendTransaction(transaction)
val result = provider.sendTransaction(transaction)
when (result) {
is SimpleResult.Success -> return result
is SimpleResult.Failure -> {
if (result.error is IOException || result.error is HttpException) {
changeProvider()
return bitcoinProvider.sendTransaction(transaction)
return provider.sendTransaction(transaction)
} else {
return result
}

View file

@ -1,7 +1,5 @@
package com.tangem.blockchain.blockchains.bitcoin.network.api
package com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo
import com.tangem.blockchain.blockchains.bitcoin.network.response.BlockchainInfoAddress
import com.tangem.blockchain.blockchains.bitcoin.network.response.BlockchainInfoUnspents
import okhttp3.ResponseBody
import retrofit2.http.*

View file

@ -1,8 +1,9 @@
package com.tangem.blockchain.blockchains.bitcoin.network
package com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo
import com.tangem.blockchain.blockchains.bitcoin.BitcoinUnspentOutput
import com.tangem.blockchain.blockchains.bitcoin.network.api.BlockchainInfoApi
import com.tangem.blockchain.blockchains.bitcoin.network.api.EstimatefeeApi
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinAddressResponse
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinFee
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinProvider
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult

View file

@ -1,4 +1,4 @@
package com.tangem.blockchain.blockchains.bitcoin.network.response
package com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View file

@ -1,4 +1,4 @@
package com.tangem.blockchain.blockchains.bitcoin.network.api
package com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo
import retrofit2.http.GET

View file

@ -12,7 +12,7 @@ import com.tangem.blockchain.network.blockchair.BlockchairProvider
import com.tangem.blockchain.network.createRetrofitInstance
class BitcoinCashNetworkManager : BitcoinProvider {
private val blockchain: Blockchain = Blockchain.BitcoinCash
private val blockchain = Blockchain.BitcoinCash
private val blockchairProvider by lazy {
val api = createRetrofitInstance(API_BLOCKCHAIR).create(BlockchairApi::class.java)

View file

@ -2,6 +2,7 @@ package com.tangem.blockchain.blockchains.bitcoincash
import com.tangem.blockchain.blockchains.bitcoin.BitcoinTransactionBuilder
import com.tangem.blockchain.blockchains.bitcoin.BitcoinUnspentOutput
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.extensions.Result
import com.tangem.common.extensions.isZero
@ -13,8 +14,8 @@ import org.bitcoinj.script.ScriptBuilder
import java.math.BigDecimal
import java.math.BigInteger
class BitcoinCashTransactionBuilder(private val walletPublicKey: ByteArray)
: BitcoinTransactionBuilder(walletPublicKey) {
class BitcoinCashTransactionBuilder(private val walletPublicKey: ByteArray, blockchain: Blockchain)
: BitcoinTransactionBuilder(walletPublicKey, blockchain) {
private lateinit var transaction: BitcoinCashTransaction
@ -25,7 +26,6 @@ class BitcoinCashTransactionBuilder(private val walletPublicKey: ByteArray)
val change: BigDecimal = calculateChange(transactionData, unspentOutputs!!)
networkParameters = NetworkParameters.fromID(NetworkParameters.ID_MAINNET)
transaction = transactionData.toBitcoinCashTransaction(networkParameters, unspentOutputs!!, change)
val hashesForSign: MutableList<ByteArray> = MutableList(transaction.inputs.size) { byteArrayOf() }

View file

@ -10,8 +10,8 @@ import java.math.BigDecimal
class BitcoinCashWalletManager(
cardId: String,
wallet: Wallet,
private val transactionBuilder: BitcoinCashTransactionBuilder,
private val networkManager: BitcoinCashNetworkManager
transactionBuilder: BitcoinCashTransactionBuilder,
networkManager: BitcoinCashNetworkManager
) : BitcoinWalletManager(cardId, wallet, transactionBuilder, networkManager), TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
val minimalFee = BigDecimal("0.00001")

View file

@ -22,7 +22,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")
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
fun buildToSign(transactionData: TransactionData, nonce: BigInteger?): TransactionToSign? {

View file

@ -23,7 +23,7 @@ class EthereumNetworkManager(blockchain: Blockchain) {
val baseUrl = when (blockchain) {
Blockchain.Ethereum -> API_INFURA + infuraPath
Blockchain.RSK -> API_RSK
else -> throw Exception("${blockchain.fullName} blockchain is not supported by EthereumNetworkManager")
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
createRetrofitInstance(baseUrl).create(EthereumApi::class.java)
}
@ -31,7 +31,7 @@ class EthereumNetworkManager(blockchain: Blockchain) {
private val apiKey = when (blockchain) {
Blockchain.Ethereum -> INFURA_API_KEY
Blockchain.RSK -> ""
else -> throw Exception("${blockchain.fullName} blockchain is not supported by EthereumNetworkManager")
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
private val provider: EthereumProvider by lazy { EthereumProvider(api, apiKey) }

View file

@ -0,0 +1,69 @@
package com.tangem.blockchain.blockchains.litecoin;
import org.bitcoinj.core.Utils;
import org.bitcoinj.params.AbstractBitcoinNetParams;
import org.bitcoinj.params.MainNetParams;
import org.spongycastle.util.encoders.Hex;
import static com.google.common.base.Preconditions.checkState;
public class LitecoinMainNetParams extends AbstractBitcoinNetParams {
public static final int MAINNET_MAJORITY_WINDOW = MainNetParams.MAINNET_MAJORITY_WINDOW;
public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = MainNetParams.MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = MainNetParams.MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
public LitecoinMainNetParams() {
super();
id = "org.bitcoinj.litecoin_mainnet";
// Genesis hash is 12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2
packetMagic = 0xfbc0b6db;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
port = 9333;
addressHeader = 48;
p2shHeader = 50;
segwitAddressHrp = "ltc";
dumpedPrivateKeyHeader = 176;
spendableCoinbaseDepth = 100;
subsidyDecreaseBlockCount = 840000;
genesisBlock.setTime(1317972665L);
genesisBlock.setDifficultyTarget(0x1e0ffff0L);
genesisBlock.setNonce(2084524493);
String genesisHash = genesisBlock.getHashAsString();
checkState(genesisHash.equals("5155a7ed2219a75c0735c58b5d459c6d07d97917570e27b9d1d4546fb8431381"));
alertSigningKey = Hex.decode("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9");
majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = MAINNET_MAJORITY_WINDOW;
dnsSeeds = new String[]{
"dnsseed.litecointools.com",
"dnsseed.litecoinpool.org",
"dnsseed.ltc.xurious.com",
"dnsseed.koin-project.com",
"dnsseed.weminemnc.com"
};
bip32HeaderP2PKHpub = 0x0488B21E;
bip32HeaderP2PKHpriv = 0x0488ADE4;
}
@Override
public String getPaymentProtocolId() {
return PAYMENT_PROTOCOL_ID_MAINNET;
}
private static LitecoinMainNetParams instance;
public static synchronized LitecoinMainNetParams get() {
if (instance == null) {
instance = new LitecoinMainNetParams();
}
return instance;
}
}

View file

@ -0,0 +1,85 @@
package com.tangem.blockchain.blockchains.litecoin
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinAddressResponse
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinFee
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinProvider
import com.tangem.blockchain.network.blockcypher.BlockcypherProvider
import com.tangem.blockchain.network.blockcypher.BlockcypherApi
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.network.API_BLOCKCHAIR
import com.tangem.blockchain.network.API_BLOCKCYPHER
import com.tangem.blockchain.network.blockchair.BlockchairApi
import com.tangem.blockchain.network.blockchair.BlockchairProvider
import com.tangem.blockchain.network.createRetrofitInstance
import retrofit2.HttpException
import java.io.IOException
class LitecoinNetworkManager : BitcoinProvider {
private val blockchain = Blockchain.Litecoin
private val blockchairProvider by lazy {
val api = createRetrofitInstance(API_BLOCKCHAIR)
.create(BlockchairApi::class.java)
BlockchairProvider(api, blockchain)
}
private val blockcypherProvider by lazy {
val api = createRetrofitInstance(API_BLOCKCYPHER)
.create(BlockcypherApi::class.java)
BlockcypherProvider(api, blockchain)
}
private var provider: BitcoinProvider = blockchairProvider
private fun changeProvider() {
provider = if (provider == blockchairProvider) blockcypherProvider else blockchairProvider
}
override suspend fun getInfo(address: String): Result<BitcoinAddressResponse> {
val result = provider.getInfo(address)
when (result) {
is Result.Success -> return result
is Result.Failure -> {
if (result.error is IOException || result.error is HttpException) {
changeProvider()
return provider.getInfo(address)
} else {
return result
}
}
}
}
override suspend fun getFee(): Result<BitcoinFee> {
val result = provider.getFee()
when (result) {
is Result.Success -> return result
is Result.Failure -> {
if (result.error is IOException || result.error is HttpException) {
changeProvider()
return provider.getFee()
} else {
return result
}
}
}
}
override suspend fun sendTransaction(transaction: String): SimpleResult {
val result = provider.sendTransaction(transaction)
when (result) {
is SimpleResult.Success -> return result
is SimpleResult.Failure -> {
if (result.error is IOException || result.error is HttpException) {
changeProvider()
return provider.sendTransaction(transaction)
} else {
return result
}
}
}
}
}

View file

@ -0,0 +1,29 @@
package com.tangem.blockchain.blockchains.litecoin
import com.tangem.blockchain.blockchains.bitcoin.BitcoinTransactionBuilder
import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.extensions.Result
import java.math.BigDecimal
class LitecoinWalletManager(
cardId: String,
wallet: Wallet,
transactionBuilder: BitcoinTransactionBuilder,
networkManager: LitecoinNetworkManager
) : BitcoinWalletManager(cardId, wallet, transactionBuilder, networkManager), TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
val minimalFee = BigDecimal("0.00001")
when (val result = super.getFee(amount, destination)) {
is Result.Success -> {
for (fee in result.data) {
if (fee.value!! < minimalFee) fee.value = minimalFee
}
return result
}
is Result.Failure -> return result
}
}
}

View file

@ -17,6 +17,7 @@ enum class Blockchain(
Bitcoin("BTC", "BTC", "Bitcoin"),
BitcoinTestnet("BTC/test", "BTCt", "Bitcoin Testnet"),
BitcoinCash("BCH", "BCH", "Bitcoin Cash"),
Litecoin("LTC", "LTC", "Litecoin"),
Ethereum("ETH", "ETH", "Ethereum"),
RSK("RSK", "RBTC", "RSK"),
Cardano("CARDANO", "ADA", "Cardano"),
@ -26,7 +27,7 @@ enum class Blockchain(
Stellar("XLM", "XLM", "Stellar");
fun decimals(): Int = when (this) {
Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet -> 8
Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet, Litecoin -> 8
Cardano, XRP -> 6
Ethereum, RSK -> 18
Stellar -> 7
@ -43,8 +44,7 @@ enum class Blockchain(
private fun getAddressService(): AddressService = when (this) {
Unknown -> throw Exception("unsupported blockchain")
Bitcoin -> BitcoinAddressService()
BitcoinTestnet -> BitcoinAddressService(true)
Bitcoin, BitcoinTestnet, Litecoin -> BitcoinAddressService(this)
BitcoinCash -> BitcoinCashAddressService()
Ethereum, RSK -> EthereumAddressService()
Cardano -> CardanoAddressService()
@ -66,6 +66,7 @@ enum class Blockchain(
Bitcoin -> "https://blockchain.info/address/$address"
BitcoinTestnet -> "https://live.blockcypher.com/btc-testnet/address/$address"
BitcoinCash -> "https://blockchair.com/bitcoin-cash/address/$address"
Litecoin -> "https://live.blockcypher.com/ltc/address/$address"
Cardano -> "https://cardanoexplorer.com/address/$address"
Ethereum -> if (token == null) {
"https://etherscan.io/address/"

View file

@ -15,6 +15,8 @@ import com.tangem.blockchain.blockchains.cardano.network.CardanoNetworkManager
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionBuilder
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.blockchains.ethereum.network.EthereumNetworkManager
import com.tangem.blockchain.blockchains.litecoin.LitecoinNetworkManager
import com.tangem.blockchain.blockchains.litecoin.LitecoinWalletManager
import com.tangem.blockchain.blockchains.stellar.StellarNetworkManager
import com.tangem.blockchain.blockchains.stellar.StellarTransactionBuilder
@ -42,24 +44,31 @@ object WalletManagerFactory {
Blockchain.Bitcoin -> {
return BitcoinWalletManager(
cardId, wallet,
BitcoinTransactionBuilder(walletPublicKey),
BitcoinNetworkManager()
BitcoinTransactionBuilder(walletPublicKey, blockchain),
BitcoinNetworkManager(blockchain)
)
}
Blockchain.BitcoinTestnet -> {
return BitcoinWalletManager(
cardId, wallet,
BitcoinTransactionBuilder(walletPublicKey, true),
BitcoinNetworkManager(true)
BitcoinTransactionBuilder(walletPublicKey, blockchain),
BitcoinNetworkManager(blockchain)
)
}
Blockchain.BitcoinCash -> {
return BitcoinCashWalletManager(
cardId, wallet,
BitcoinCashTransactionBuilder(walletPublicKey.toCompressedPublicKey()),
BitcoinCashTransactionBuilder(walletPublicKey.toCompressedPublicKey(), blockchain),
BitcoinCashNetworkManager()
)
}
Blockchain.Litecoin -> {
return LitecoinWalletManager(
cardId, wallet,
BitcoinTransactionBuilder(walletPublicKey, blockchain),
LitecoinNetworkManager()
)
}
Blockchain.Ethereum, Blockchain.RSK -> {
return EthereumWalletManager(
cardId, wallet,

View file

@ -15,7 +15,8 @@ import java.math.RoundingMode
class BlockchairProvider(private val api: BlockchairApi, blockchain: Blockchain) : BitcoinProvider {
private val blockchainPath = when (blockchain) {
Blockchain.BitcoinCash -> "bitcoin-cash"
else -> throw Exception("${blockchain.fullName} blockchain is not supported by BlockchairProvider")
Blockchain.Litecoin -> "litecoin"
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
private val decimals = blockchain.decimals()

View file

@ -1,9 +1,6 @@
package com.tangem.blockchain.blockchains.bitcoin.network.api
package com.tangem.blockchain.network.blockcypher
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.blockchains.bitcoin.network.response.BlockcypherFee
import com.tangem.blockchain.blockchains.bitcoin.network.response.BlockcypherResponse
import com.tangem.blockchain.blockchains.bitcoin.network.response.BlockcypherTx
import retrofit2.http.*
interface BlockcypherApi {

View file

@ -1,30 +1,35 @@
package com.tangem.blockchain.blockchains.bitcoin.network
package com.tangem.blockchain.network.blockcypher
import com.tangem.blockchain.blockchains.bitcoin.BitcoinUnspentOutput
import com.tangem.blockchain.blockchains.bitcoin.network.api.BlockcypherApi
import com.tangem.blockchain.blockchains.bitcoin.network.api.BlockcypherBody
import com.tangem.blockchain.blockchains.bitcoin.network.response.BlockcypherFee
import com.tangem.blockchain.blockchains.bitcoin.network.response.BlockcypherResponse
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinAddressResponse
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinFee
import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinProvider
import com.tangem.blockchain.network.blockcypher.BlockcypherApi
import com.tangem.blockchain.network.blockcypher.BlockcypherBody
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.extensions.retryIO
import com.tangem.common.extensions.hexToBytes
class BlockcypherProvider(private val api: BlockcypherApi, isTestNet: Boolean) : BitcoinProvider {
class BlockcypherProvider(private val api: BlockcypherApi, blockchain: Blockchain) : BitcoinProvider {
private val blockchain = "btc"
private val decimals = Blockchain.Bitcoin.decimals()
private val network = if (isTestNet) {
BlockcypherNetwork.Test.network
} else {
BlockcypherNetwork.Main.network
private val blockchainPath = when (blockchain) {
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> "btc"
Blockchain.Litecoin -> "ltc"
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
private val network = when (blockchain) {
Blockchain.BitcoinTestnet -> "test3"
else -> "main"
}
private val decimals = blockchain.decimals()
override suspend fun getInfo(address: String): Result<BitcoinAddressResponse> {
try {
val addressData: BlockcypherResponse = retryIO { api.getAddressData(blockchain, network, address) }
val addressData: BlockcypherResponse = retryIO { api.getAddressData(blockchainPath, network, address) }
val unspents = addressData.txrefs?.map {
BitcoinUnspentOutput(
it.amount!!.toBigDecimal().movePointLeft(decimals),
@ -45,7 +50,7 @@ class BlockcypherProvider(private val api: BlockcypherApi, isTestNet: Boolean) :
override suspend fun getFee(): Result<BitcoinFee> {
return try {
val receivedFee: BlockcypherFee = retryIO { api.getFee(blockchain, network) }
val receivedFee: BlockcypherFee = retryIO { api.getFee(blockchainPath, network) }
Result.Success(
BitcoinFee(receivedFee.minFeePerKb!!.toBigDecimal().movePointLeft(decimals),
receivedFee.normalFeePerKb!!.toBigDecimal().movePointLeft(decimals),
@ -60,7 +65,7 @@ class BlockcypherProvider(private val api: BlockcypherApi, isTestNet: Boolean) :
return try {
retryIO {
api.sendTransaction(
blockchain, network, BlockcypherBody(transaction), BlockcypherToken.getToken())
blockchainPath, network, BlockcypherBody(transaction), BlockcypherToken.getToken())
}
SimpleResult.Success
} catch (error: Exception) {
@ -76,9 +81,4 @@ private object BlockcypherToken {
"66a8a37c5e9d4d2c9bb191acfe7f93aa")
fun getToken(): String = tokens.random()
}
private enum class BlockcypherNetwork(val network: String) {
Main("main"),
Test("test3")
}

View file

@ -1,4 +1,4 @@
package com.tangem.blockchain.blockchains.bitcoin.network.response
package com.tangem.blockchain.network.blockcypher
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass