Updated on 2026-08-14

This commit is contained in:
Tangem 2020-05-21 21:43:55 +03:00
parent 0f4f44a8c6
commit 9c7a2f3ab1
12 changed files with 263 additions and 10 deletions

View file

@ -1,15 +1,18 @@
package com.tangem.blockchain.blockchains.bitcoin
import com.tangem.blockchain.blockchains.ducatus.DucatusMainNetParams
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.*
import org.bitcoinj.core.Base58
import org.bitcoinj.core.LegacyAddress
import org.bitcoinj.core.NetworkParameters
import org.bitcoinj.core.SegwitAddress
import org.bitcoinj.params.MainNetParams
import org.bitcoinj.params.TestNet3Params
import java.security.MessageDigest
class BitcoinAddressService(private val blockchain: Blockchain) : AddressService {
@ -17,6 +20,7 @@ class BitcoinAddressService(private val blockchain: Blockchain) : AddressService
Blockchain.Bitcoin -> MainNetParams()
Blockchain.BitcoinTestnet -> TestNet3Params()
Blockchain.Litecoin -> LitecoinMainNetParams()
Blockchain.Ducatus -> DucatusMainNetParams()
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}

View file

@ -1,5 +1,6 @@
package com.tangem.blockchain.blockchains.bitcoin
import com.tangem.blockchain.blockchains.ducatus.DucatusMainNetParams
import com.tangem.blockchain.blockchains.litecoin.LitecoinMainNetParams
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
@ -23,6 +24,7 @@ open class BitcoinTransactionBuilder(
Blockchain.Bitcoin, Blockchain.BitcoinCash -> MainNetParams()
Blockchain.BitcoinTestnet -> TestNet3Params()
Blockchain.Litecoin -> LitecoinMainNetParams()
Blockchain.Ducatus -> DucatusMainNetParams()
else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}")
}
var unspentOutputs: List<BitcoinUnspentOutput>? = null
@ -64,7 +66,7 @@ open class BitcoinTransactionBuilder(
}
fun calculateChange(transactionData: TransactionData, unspentOutputs: List<BitcoinUnspentOutput>): BigDecimal {
val fullAmount = unspentOutputs!!.map { it.amount }.reduce { acc, number -> acc + number }
val fullAmount = unspentOutputs.map { it.amount }.reduce { acc, number -> acc + number }
return fullAmount - (transactionData.amount.value!! + (transactionData.fee?.value
?: 0.toBigDecimal()))
}

View file

@ -13,7 +13,7 @@ import java.math.BigDecimal
open class BitcoinWalletManager(
cardId: String,
wallet: Wallet,
private val transactionBuilder: BitcoinTransactionBuilder,
protected val transactionBuilder: BitcoinTransactionBuilder,
private val networkManager: BitcoinProvider
) : WalletManager(cardId, wallet), TransactionSender {

View file

@ -0,0 +1,69 @@
package com.tangem.blockchain.blockchains.ducatus;
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 DucatusMainNetParams 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 DucatusMainNetParams() {
super();
id = "org.bitcoinj.ducatus_mainnet";
// Genesis hash is 12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2
packetMagic = 0xfbc0b6db;
maxTarget = Utils.decodeCompactBits(0x1e0fffffL);
port = 9333;
addressHeader = 49;
p2shHeader = 51; //TODO: is this right? Haven't seen Ducatus p2sh address ever
segwitAddressHrp = "duc"; //TODO: does Ducatus even have bech32 addresses? At least other blockchain's addresses won't pass
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 DucatusMainNetParams instance;
public static synchronized DucatusMainNetParams get() {
if (instance == null) {
instance = new DucatusMainNetParams();
}
return instance;
}
}

View file

@ -0,0 +1,40 @@
package com.tangem.blockchain.blockchains.ducatus
import com.tangem.blockchain.blockchains.bitcoin.BitcoinTransactionBuilder
import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
import com.tangem.blockchain.blockchains.ducatus.network.DucatusNetworkManager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.extensions.Result
import java.math.BigDecimal
class DucatusWalletManager(
cardId: String,
wallet: Wallet,
transactionBuilder: BitcoinTransactionBuilder,
networkManager: DucatusNetworkManager
) : BitcoinWalletManager(cardId, wallet, transactionBuilder, networkManager), TransactionSender {
override suspend fun getFee(amount: Amount, destination: String): Result<List<Amount>> {
val feeValue = BigDecimal.ONE.movePointLeft(blockchain.decimals())
val sizeResult = transactionBuilder.getEstimateSize(
TransactionData(amount, Amount(amount, feeValue), wallet.address, destination)
)
return when (sizeResult) {
is Result.Failure -> sizeResult
is Result.Success -> {
val transactionSize = sizeResult.data.toBigDecimal()
val minFee = BigDecimal.valueOf(0.00000089).multiply(transactionSize)
val normalFee = BigDecimal.valueOf(0.00000144).multiply(transactionSize)
val priorityFee = BigDecimal.valueOf(0.00000350).multiply(transactionSize)
val fees = listOf(
Amount(minFee, blockchain),
Amount(normalFee, blockchain),
Amount(priorityFee, blockchain)
)
Result.Success(fees)
}
}
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.blockchain.blockchains.ducatus.network
import com.tangem.blockchain.blockchains.ducatus.network.bitcore.BitcoreApi
import com.tangem.blockchain.blockchains.ducatus.network.bitcore.BitcoreProvider
import com.tangem.blockchain.network.API_DUCATUS
import com.tangem.blockchain.network.createRetrofitInstance
class DucatusNetworkManager() : BitcoreProvider(createRetrofitInstance(API_DUCATUS).create(BitcoreApi::class.java))

View file

@ -0,0 +1,21 @@
package com.tangem.blockchain.blockchains.ducatus.network.bitcore
import com.squareup.moshi.JsonClass
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
interface BitcoreApi {
@GET("api/DUC/mainnet/address/{address}/balance")
suspend fun getBalance(@Path("address") address: String): BitcoreBalance
@GET("api/DUC/mainnet/address/{address}/?unspent=true")
suspend fun getUnspents(@Path("address") address: String): List<BitcoreUtxo>
@POST("api/DUC/mainnet/tx/send")
suspend fun sendTransaction(@Body body: BitcoreSendBody): BitcoreSendResponse
}
@JsonClass(generateAdapter = true)
data class BitcoreSendBody(val rawTx: List<String>)

View file

@ -0,0 +1,64 @@
package com.tangem.blockchain.blockchains.ducatus.network.bitcore
import com.tangem.blockchain.blockchains.bitcoin.BitcoinUnspentOutput
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
import com.tangem.blockchain.extensions.retryIO
import com.tangem.common.extensions.hexToBytes
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
open class BitcoreProvider(private val api: BitcoreApi) : BitcoinProvider{
private val decimals = Blockchain.Ducatus.decimals()
override suspend fun getInfo(address: String): Result<BitcoinAddressResponse> {
return try {
coroutineScope {
val balanceDeferred = retryIO { async { api.getBalance(address) } }
val unspentsDeferred = retryIO { async { api.getUnspents(address) } }
val balanceData = balanceDeferred.await()
val unspents = unspentsDeferred.await()
val unspentTransactions = unspents.map {
BitcoinUnspentOutput(
amount = it.amount!!.toBigDecimal().movePointLeft(decimals),
outputIndex = it.index!!.toLong(),
transactionHash = it.transactionHash!!.hexToBytes(),
outputScript = it.script!!.hexToBytes()
)
}
Result.Success(BitcoinAddressResponse(
balance = balanceData.confirmed!!.toBigDecimal().movePointLeft(decimals),//only confirmed balance is returned right
hasUnconfirmed = balanceData.unconfirmed != null,
unspentOutputs = unspentTransactions
))
}
} catch (error: Exception) {
Result.Failure(error)
}
}
override suspend fun getFee(): Result<BitcoinFee> {
TODO("Not yet implemented")// bitcore is used only in ducatus and fee is hardcoded there
}
override suspend fun sendTransaction(transaction: String): SimpleResult {
return try {
val response = retryIO { api.sendTransaction(BitcoreSendBody(listOf(transaction))) }
if (response.txid != null) {
SimpleResult.Success
} else {
SimpleResult.Failure(Exception("Unknown send transaction error"))
}
} catch (error: Exception) {
SimpleResult.Failure(error)
}
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.blockchain.blockchains.ducatus.network.bitcore
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class BitcoreBalance(
@Json(name = "confirmed")
var confirmed: Long? = null,
@Json(name = "unconfirmed")
var unconfirmed: Long? = null
)
@JsonClass(generateAdapter = true)
data class BitcoreUtxo(
@Json(name = "mintTxid")
var transactionHash: String? = null,
@Json(name = "mintIndex")
var index: Int? = null,
@Json(name = "value")
var amount: Long? = null,
@Json(name = "script")
var script: String? = null
)
@JsonClass(generateAdapter = true)
data class BitcoreSendResponse(
@Json(name = "txid")
var txid: String? = null
)

View file

@ -19,6 +19,7 @@ enum class Blockchain(
BitcoinTestnet("BTC/test", "BTCt", "Bitcoin Testnet"),
BitcoinCash("BCH", "BCH", "Bitcoin Cash"),
Litecoin("LTC", "LTC", "Litecoin"),
Ducatus("DUC", "DUC", "Ducatus"),
Ethereum("ETH", "ETH", "Ethereum"),
RSK("RSK", "RBTC", "RSK"),
Cardano("CARDANO", "ADA", "Cardano"),
@ -29,7 +30,7 @@ enum class Blockchain(
Tezos("TEZOS", "XTZ", "Tezos");
fun decimals(): Int = when (this) {
Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet, Litecoin -> 8
Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet, Litecoin, Ducatus -> 8
Cardano, XRP, Tezos -> 6
Ethereum, RSK -> 18
Stellar -> 7
@ -45,8 +46,7 @@ enum class Blockchain(
fun validateAddress(address: String): Boolean = getAddressService().validate(address)
private fun getAddressService(): AddressService = when (this) {
Unknown -> throw Exception("unsupported blockchain")
Bitcoin, BitcoinTestnet, Litecoin -> BitcoinAddressService(this)
Bitcoin, BitcoinTestnet, Litecoin, Ducatus -> BitcoinAddressService(this)
BitcoinCash -> BitcoinCashAddressService()
Ethereum, RSK -> EthereumAddressService()
Cardano -> CardanoAddressService()
@ -55,6 +55,7 @@ enum class Blockchain(
BinanceTestnet -> BinanceAddressService(true)
Stellar -> StellarAddressService()
Tezos -> TezosAddressService()
Unknown -> throw Exception("unsupported blockchain")
}
fun getShareUri(address: String): String = when (this) {
@ -71,9 +72,10 @@ enum class Blockchain(
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"
Ducatus -> "https://insight.ducatus.io/#/DUC/mainnet/address/$address"
Cardano -> "https://cardanoexplorer.com/address/$address"
Ethereum -> if (token == null) {
"https://etherscan.io/address/"
"https://etherscan.io/address/$address"
} else {
"https://etherscan.io/token/${token.contractAddress}?a=$address"
}

View file

@ -12,6 +12,8 @@ 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.ducatus.DucatusWalletManager
import com.tangem.blockchain.blockchains.ducatus.network.DucatusNetworkManager
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionBuilder
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.blockchains.ethereum.network.EthereumNetworkManager
@ -19,7 +21,6 @@ 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
import com.tangem.blockchain.blockchains.stellar.StellarWalletManager
import com.tangem.blockchain.blockchains.tezos.TezosTransactionBuilder
import com.tangem.blockchain.blockchains.tezos.TezosWalletManager
@ -72,6 +73,13 @@ object WalletManagerFactory {
LitecoinNetworkManager()
)
}
Blockchain.Ducatus -> {
return DucatusWalletManager(
cardId, wallet,
BitcoinTransactionBuilder(walletPublicKey, blockchain),
DucatusNetworkManager()
)
}
Blockchain.Ethereum, Blockchain.RSK -> {
return EthereumWalletManager(
cardId, wallet,

View file

@ -55,4 +55,5 @@ const val API_RIPPLED = "https://s1.ripple.com:51234/"
const val API_RIPPLED_RESERVE = "https://s2.ripple.com:51234/"
const val API_BLOCKCHAIR = "https://api.blockchair.com/"
const val API_TEZOS = "https://teznode.letzbake.com"
const val API_TEZOS_RESERVE = "https://mainnet.tezrpc.me"
const val API_TEZOS_RESERVE = "https://mainnet.tezrpc.me"
const val API_DUCATUS = "https://ducapi.rocknblock.io/"