Updated on 2026-08-14
This commit is contained in:
parent
b32d9b00d9
commit
ca0a10de61
22 changed files with 321 additions and 125 deletions
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.*
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.blockchain.blockchains.bitcoin.network.api
|
||||
package com.tangem.blockchain.blockchains.bitcoin.network.blockchaininfo
|
||||
|
||||
import retrofit2.http.GET
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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/"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue