Updated on 2026-08-14
This commit is contained in:
commit
6d005c98f6
52 changed files with 583 additions and 273 deletions
|
|
@ -66,7 +66,7 @@ class PurgeFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.No
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// init NFC Antenna
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna.init()
|
||||
|
||||
tvCardID.text = ctx.card.cidDescription
|
||||
|
|
@ -99,7 +99,7 @@ class PurgeFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.No
|
|||
}
|
||||
|
||||
override fun onReadWait(msec: Int) {
|
||||
WaitSecurityDelayDialog.onReadWait(activity, msec)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) }
|
||||
|
||||
// val readWait = ReadWait()
|
||||
// readWait.msec = msec
|
||||
|
|
@ -107,7 +107,7 @@ class PurgeFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.No
|
|||
}
|
||||
|
||||
override fun onReadBeforeRequest(timeout: Int) {
|
||||
WaitSecurityDelayDialog.onReadBeforeRequest(activity, timeout)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) }
|
||||
|
||||
// if (!waitSecurityDelayDialogNew.isAdded)
|
||||
// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG)
|
||||
|
|
@ -119,16 +119,16 @@ class PurgeFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.No
|
|||
}
|
||||
|
||||
override fun onReadAfterRequest() {
|
||||
WaitSecurityDelayDialog.onReadAfterRequest(activity)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) }
|
||||
|
||||
// val readAfterRequest = ReadAfterRequest()
|
||||
// EventBus.getDefault().post(readAfterRequest)
|
||||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
|
||||
progressBar.post {
|
||||
progressBar?.post {
|
||||
progressBar.visibility = View.VISIBLE
|
||||
progressBar.progress = 5
|
||||
}
|
||||
|
|
@ -179,7 +179,7 @@ class PurgeFragment : BaseFragment(), NfcAdapter.ReaderCallback, CardProtocol.No
|
|||
progressBar?.post {
|
||||
if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
|
||||
if (!NoExtendedLengthSupportDialog.allReadyShowed)
|
||||
NoExtendedLengthSupportDialog().show(activity!!.supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
|
||||
NoExtendedLengthSupportDialog().show(requireActivity().supportFragmentManager, NoExtendedLengthSupportDialog.TAG)
|
||||
} else
|
||||
Toast.makeText(context, R.string.general_notification_scan_again_to_verify, Toast.LENGTH_LONG).show()
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class ValidateIdFragment : BaseFragment(), NavigationResultListener,
|
|||
mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
|
||||
|
||||
// init NFC Antenna
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna.init()
|
||||
|
||||
progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
|
||||
|
|
@ -198,7 +198,7 @@ class ValidateIdFragment : BaseFragment(), NavigationResultListener,
|
|||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
|
||||
progressBar?.post {
|
||||
progressBar?.visibility = View.VISIBLE
|
||||
|
|
@ -265,7 +265,7 @@ class ValidateIdFragment : BaseFragment(), NavigationResultListener,
|
|||
NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG)
|
||||
}
|
||||
} else {
|
||||
(activity as MainActivity).toastHelper.showSingleToast(
|
||||
(activity as? MainActivity)?.toastHelper?.showSingleToast(
|
||||
context,
|
||||
getString(R.string.general_notification_scan_again)
|
||||
)
|
||||
|
|
@ -313,17 +313,17 @@ class ValidateIdFragment : BaseFragment(), NavigationResultListener,
|
|||
|
||||
override fun onReadBeforeRequest(timeout: Int) {
|
||||
LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
|
||||
WaitSecurityDelayDialog.onReadBeforeRequest(activity!!, timeout)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) }
|
||||
}
|
||||
|
||||
override fun onReadAfterRequest() {
|
||||
LOG.i(TAG, "onReadAfterRequest")
|
||||
WaitSecurityDelayDialog.onReadAfterRequest(activity!!)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) }
|
||||
}
|
||||
|
||||
override fun onReadWait(msec: Int) {
|
||||
LOG.i(TAG, "onReadWait msec $msec")
|
||||
WaitSecurityDelayDialog.onReadWait(activity!!, msec)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener,
|
|||
mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
|
||||
|
||||
// init NFC Antenna
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna.init()
|
||||
|
||||
tvCardID.text = ctx.card!!.cidDescription
|
||||
|
|
@ -137,7 +137,7 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener,
|
|||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
|
||||
progressBar?.post {
|
||||
progressBar?.visibility = View.VISIBLE
|
||||
|
|
@ -279,17 +279,17 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener,
|
|||
|
||||
override fun onReadBeforeRequest(timeout: Int) {
|
||||
LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
|
||||
WaitSecurityDelayDialog.onReadBeforeRequest(activity!!, timeout)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) }
|
||||
}
|
||||
|
||||
override fun onReadAfterRequest() {
|
||||
LOG.i(TAG, "onReadAfterRequest")
|
||||
WaitSecurityDelayDialog.onReadAfterRequest(activity!!)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) }
|
||||
}
|
||||
|
||||
override fun onReadWait(msec: Int) {
|
||||
LOG.i(TAG, "onReadWait msec $msec")
|
||||
WaitSecurityDelayDialog.onReadWait(activity!!, msec)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) }
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -9,7 +9,6 @@ import android.nfc.Tag
|
|||
import android.nfc.tech.IsoDep
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
|
|
@ -80,7 +79,7 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
|||
mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
|
||||
|
||||
// init NFC Antenna
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(context!!, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
|
||||
nfcDeviceAntenna.init()
|
||||
|
||||
amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT), arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY))
|
||||
|
|
@ -165,7 +164,7 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
|||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE }
|
||||
|
||||
progressBar?.post {
|
||||
progressBar?.visibility = View.VISIBLE
|
||||
|
|
@ -234,7 +233,9 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
|||
NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_LONG).show()
|
||||
(activity as? MainActivity)?.toastHelper?.showSingleToast(
|
||||
context, getString(R.string.general_notification_scan_again)
|
||||
)
|
||||
}
|
||||
progressBar?.progress = 100
|
||||
progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
|
||||
|
|
@ -280,7 +281,7 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
|||
|
||||
override fun onReadBeforeRequest(timeout: Int) {
|
||||
LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
|
||||
WaitSecurityDelayDialog.onReadBeforeRequest(activity!!, timeout)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) }
|
||||
|
||||
// if (!waitSecurityDelayDialogNew.isAdded)
|
||||
// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG)
|
||||
|
|
@ -293,7 +294,7 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
|||
|
||||
override fun onReadAfterRequest() {
|
||||
LOG.i(TAG, "onReadAfterRequest")
|
||||
WaitSecurityDelayDialog.onReadAfterRequest(activity!!)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) }
|
||||
|
||||
// val readAfterRequest = ReadAfterRequest()
|
||||
// EventBus.getDefault().post(readAfterRequest)
|
||||
|
|
@ -301,7 +302,7 @@ class SignTransactionFragment : BaseFragment(), NavigationResultListener,
|
|||
|
||||
override fun onReadWait(msec: Int) {
|
||||
LOG.i(TAG, "onReadWait msec $msec")
|
||||
WaitSecurityDelayDialog.onReadWait(activity!!, msec)
|
||||
activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) }
|
||||
|
||||
// val readWait = ReadWait()
|
||||
// readWait.msec = msec
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.blockchain.blockchains.bitcoin
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import org.junit.Test
|
||||
|
||||
class BitcoinAddressTest {
|
||||
|
||||
private val addressService = BitcoinAddressService()
|
||||
private val addressService = BitcoinAddressService(Blockchain.Bitcoin)
|
||||
|
||||
@Test
|
||||
fun makeAddressFromCorrectPublicKey() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.blockchain.blockchains.litecoin
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressService
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import org.junit.Test
|
||||
|
||||
class LitecoinAddressTest {
|
||||
|
||||
private val addressService = BitcoinAddressService(Blockchain.Litecoin)
|
||||
|
||||
@Test
|
||||
fun makeAddressFromCorrectPublicKey() {
|
||||
val walletPublicKey = "044A76C9A70422160F515F956D0F50C71BBBA4F9862A22913817D63F0B1EF7C2FAF512E1C91B1BE827560EFE24FB1652B47337E296C778DFB1014D080CDD35EF65".hexToBytes()
|
||||
val expected = "LeweDi2SMmishGyCqQN2972qNByaSRdcfT"
|
||||
|
||||
Truth.assertThat(addressService.makeAddress(walletPublicKey))
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateCorrectAddress() {
|
||||
val address = "LeweDi2SMmishGyCqQN2972qNByaSRdcfT"
|
||||
Truth.assertThat(addressService.validate(address))
|
||||
.isTrue()
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
|
|||
import com.tangem.blockchain.blockchains.bitcoincash.BitcoinCashWalletManager
|
||||
import com.tangem.blockchain.blockchains.cardano.CardanoWalletManager
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.blockchains.litecoin.LitecoinWalletManager
|
||||
import com.tangem.blockchain.blockchains.stellar.StellarWalletManager
|
||||
import com.tangem.blockchain.blockchains.xrp.XrpWalletManager
|
||||
import com.tangem.commands.ReadCommand
|
||||
|
|
@ -92,4 +93,15 @@ internal class WalletManagerFactoryTest {
|
|||
Truth.assertThat(walletManager)
|
||||
.isInstanceOf(BitcoinCashWalletManager::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createLitecoinWalletManager() {
|
||||
val data = "0108BB00000000000023200754414E47454D00020102800A322E3432642053444B000341043539F86A40ADD04CE165764A761FD3E4D251028615D2A573B1C3AE652E60AFDBFAF02E3239E89EF2C43FA448A327557ADC5AF36376A0574570F6DBD20113514A0A04041E76310C618102FFFF8A0101820407E40414830B54414E47454D2053444B0084034C5443864004BDEAD0117544886346CB47F7CA84ABA8C34239502F23D28595A4B16CAD72F7DE506BA818B86A649C2BB945986D4574993B3B755B47CBEE31C4FB931F6748183041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A00701006041044A76C9A70422160F515F956D0F50C71BBBA4F9862A22913817D63F0B1EF7C2FAF512E1C91B1BE827560EFE24FB1652B47337E296C778DFB1014D080CDD35EF6562040001869D6304000000030F01009000"
|
||||
val responseApdu = ResponseApdu(data.hexToBytes())
|
||||
val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu)
|
||||
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
|
||||
|
||||
Truth.assertThat(walletManager)
|
||||
.isInstanceOf(LitecoinWalletManager::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -284,7 +284,7 @@ public class TLV {
|
|||
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), ProductMask.getDescription(iValue));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value));
|
||||
return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value));
|
||||
}
|
||||
} else {
|
||||
return String.format("%s[]: [[NULL]]", tag.name());
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import com.tangem.crypto.pbkdf2Hash
|
|||
*/
|
||||
interface CardSessionRunnable<T : CommandResponse> {
|
||||
|
||||
val performPreflightRead: Boolean
|
||||
|
||||
/**
|
||||
* The starting point for custom business logic.
|
||||
* Implement this interface and use [TangemSdk.startSessionWithRunnable] to run.
|
||||
|
|
@ -55,6 +57,8 @@ class CardSession(
|
|||
*/
|
||||
private var isBusy = false
|
||||
|
||||
private var performPreflightRead = true
|
||||
|
||||
/**
|
||||
* This metod starts a card session, performs preflight [ReadCommand],
|
||||
* invokes [CardSessionRunnable.run] and closes the session.
|
||||
|
|
@ -64,6 +68,8 @@ class CardSession(
|
|||
fun <T : CardSessionRunnable<R>, R : CommandResponse> startWithRunnable(
|
||||
runnable: T, callback: (result: CompletionResult<R>) -> Unit) {
|
||||
|
||||
performPreflightRead = runnable.performPreflightRead
|
||||
|
||||
start { session, error ->
|
||||
if (error != null) {
|
||||
callback(CompletionResult.Failure(error))
|
||||
|
|
@ -104,6 +110,11 @@ class CardSession(
|
|||
callback(this, error)
|
||||
}
|
||||
|
||||
if (!performPreflightRead) {
|
||||
callback(this, null)
|
||||
return
|
||||
}
|
||||
|
||||
preflightRead() { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -174,7 +185,7 @@ class CardSession(
|
|||
* Stops the current session on error.
|
||||
* @param error An error that will be shown.
|
||||
*/
|
||||
private fun stopWithError(error: Exception) {
|
||||
private fun stopWithError(error: TangemSdkError) {
|
||||
if (!isBusy) return
|
||||
|
||||
reader.closeSession()
|
||||
|
|
@ -187,7 +198,7 @@ class CardSession(
|
|||
}
|
||||
if (error !is TangemSdkError.UserCancelled) {
|
||||
Log.e(tag, "Finishing with error: $errorMessage")
|
||||
viewDelegate.onError(errorMessage)
|
||||
viewDelegate.onError(error)
|
||||
} else {
|
||||
Log.i(tag, "User cancelled NFC session")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ interface SessionViewDelegate {
|
|||
/**
|
||||
* It is called when some error occur during NFC session.
|
||||
*/
|
||||
fun onError(errorMessage: String)
|
||||
fun onError(error: TangemSdkError)
|
||||
|
||||
/**
|
||||
* It is called when a user is expected to enter pin code.
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ class TangemSdk(
|
|||
* card response in the form of [DepersonalizeResponse] if the task was performed successfully
|
||||
* or [TangemSdkError] in case of an error.
|
||||
* */
|
||||
fun depersonalize(cardId: String, initialMessage: Message? = null,
|
||||
fun depersonalize(cardId: String? = null, initialMessage: Message? = null,
|
||||
callback: (result: CompletionResult<DepersonalizeResponse>) -> Unit) {
|
||||
startSessionWithRunnable(DepersonalizeCommand(), cardId, initialMessage, callback)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,10 @@ sealed class TangemSdkError(val code: Int) : Exception(code.toString()) {
|
|||
* Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand].
|
||||
* This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign.
|
||||
*/
|
||||
class TooManyhHashesInOneTransaction : TangemSdkError(40906)
|
||||
class TooManyHashesInOneTransaction : TangemSdkError(40906)
|
||||
|
||||
//Write Extra Issuer Data Errors
|
||||
class ExendedDataSizeTooLarge : TangemSdkError(41101)
|
||||
|
||||
//General Errors
|
||||
class NotPersonalized() : TangemSdkError(40001)
|
||||
|
|
@ -114,6 +117,7 @@ sealed class TangemSdkError(val code: Int) : Exception(code.toString()) {
|
|||
*/
|
||||
class VerificationFailed : TangemSdkError(40005)
|
||||
class DataSizeTooLarge : TangemSdkError(40006)
|
||||
|
||||
/**
|
||||
* This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter
|
||||
* (when the card's requires it), but the counter is missing.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ interface CommandResponse
|
|||
*/
|
||||
abstract class Command<T : CommandResponse> : CardSessionRunnable<T> {
|
||||
|
||||
override val performPreflightRead: Boolean = true
|
||||
|
||||
/**
|
||||
* Serializes data into an array of [com.tangem.common.tlv.Tlv],
|
||||
* then creates [CommandApdu] with this data.
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ class SignCommand(private val hashes: Array<ByteArray>)
|
|||
|
||||
private fun checkForErrors() {
|
||||
if (hashes.isEmpty()) throw TangemSdkError.EmptyHashes()
|
||||
if (hashes.size > 10) throw TangemSdkError.TooManyhHashesInOneTransaction()
|
||||
if (hashes.size > 10) throw TangemSdkError.TooManyHashesInOneTransaction()
|
||||
if (hashes.any { it.size != hashSizes }) throw TangemSdkError.HashSizeMustBeEqual()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class WriteIssuerExtraDataCommand(
|
|||
return true
|
||||
}
|
||||
if (issuerData.size > MAX_SIZE) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.DataSizeTooLarge()))
|
||||
callback(CompletionResult.Failure(TangemSdkError.ExendedDataSizeTooLarge()))
|
||||
return true
|
||||
}
|
||||
if (!isCounterValid(issuerDataCounter, card)) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
package com.tangem.commands.personalization
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.commands.CardStatus
|
||||
import com.tangem.commands.Command
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -21,17 +17,19 @@ data class DepersonalizeResponse(val success: Boolean) : CommandResponse
|
|||
*/
|
||||
class DepersonalizeCommand : Command<DepersonalizeResponse>() {
|
||||
|
||||
override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<DepersonalizeResponse>) -> Unit): Boolean {
|
||||
if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
return true
|
||||
}
|
||||
if (session.environment.card?.firmwareVersion?.contains("SDK") == false) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.CannotBeDepersonalized()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
override val performPreflightRead = false
|
||||
|
||||
// override fun performPreCheck(session: CardSession, callback: (result: CompletionResult<DepersonalizeResponse>) -> Unit): Boolean {
|
||||
// if (session.environment.card?.status == CardStatus.NotPersonalized) {
|
||||
// callback(CompletionResult.Failure(TangemSdkError.NotPersonalized()))
|
||||
// return true
|
||||
// }
|
||||
// if (session.environment.card?.firmwareVersion?.contains("SDK") == false) {
|
||||
// callback(CompletionResult.Failure(TangemSdkError.CannotBeDepersonalized()))
|
||||
// return true
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
return CommandApdu(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import com.tangem.common.CompletionResult
|
|||
|
||||
class CreateWalletTask : CardSessionRunnable<CreateWalletResponse> {
|
||||
|
||||
override val performPreflightRead = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
val curve = session.environment.card?.curve
|
||||
if (curve == null) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import com.tangem.common.CompletionResult
|
|||
*/
|
||||
internal class ScanTask : CardSessionRunnable<Card> {
|
||||
|
||||
override val performPreflightRead = true
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
|
||||
val card = session.environment.card
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ android {
|
|||
applicationId "com.tangem.devkit"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 3
|
||||
versionName "1.1"
|
||||
versionCode 4
|
||||
versionName "1.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,14 @@
|
|||
package com.tangem.devkit.ucase.domain.actions
|
||||
|
||||
import com.tangem.devkit._arch.structure.Id
|
||||
import com.tangem.devkit._arch.structure.PayloadHolder
|
||||
import com.tangem.devkit._arch.structure.abstraction.findItem
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback
|
||||
import com.tangem.devkit.ucase.variants.TlvId
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction.AfterScanModifier
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DepersonalizeAction : BaseAction() {
|
||||
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
|
||||
val item = attrs.itemList.findItem(TlvId.CardId) ?: return
|
||||
val cardId = item.viewModel.data as? String ?: return
|
||||
|
||||
attrs.tangemSdk.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
|
||||
}
|
||||
|
||||
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
|
||||
return when (id) {
|
||||
TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) }
|
||||
else -> null
|
||||
}
|
||||
attrs.tangemSdk.depersonalize { handleResult(payload, it, AfterScanModifier(), attrs, callback) }
|
||||
}
|
||||
}
|
||||
|
|
@ -10,13 +10,7 @@ import com.tangem.devkit.ucase.variants.TlvId
|
|||
*/
|
||||
class ScanItemsManager : BaseItemsManager(ScanAction())
|
||||
|
||||
class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) {
|
||||
|
||||
init {
|
||||
setItemChangeConsequences(CardIdConsequence())
|
||||
setItems(listOf(EditTextItem(TlvId.CardId, null)))
|
||||
}
|
||||
}
|
||||
class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction())
|
||||
|
||||
class SignItemsManager : BaseItemsManager(SignAction()) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
package com.tangem.devkit.ucase.variants.depersonalize.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.devkit.R
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.devkit.ucase.domain.paramsManager.managers.DepersonalizeItemsManager
|
||||
import com.tangem.devkit.ucase.ui.BaseCardActionFragment
|
||||
import com.tangem.devkit.ucase.variants.responses.ui.ResponseFragment
|
||||
import ru.dev.gbixahue.eu4d.lib.android._android.views.dpToPx
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -10,4 +20,37 @@ import com.tangem.devkit.ucase.ui.BaseCardActionFragment
|
|||
class DepersonalizeActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override val itemsManager: ItemsManager by lazy { DepersonalizeItemsManager() }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val howToUseView = createHowToUse()
|
||||
contentContainer.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
|
||||
contentContainer.addView(howToUseView)
|
||||
}
|
||||
|
||||
private fun createHowToUse(): View {
|
||||
val fl = FrameLayout(requireContext())
|
||||
fl.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER)
|
||||
val tv = TextView(requireContext()).apply {
|
||||
val padding = dpToPx(16f).toInt()
|
||||
setPadding(padding, 0, padding, 0)
|
||||
gravity = Gravity.CENTER
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
||||
setText(R.string.htu_depersonalize_action)
|
||||
}
|
||||
fl.addView(tv)
|
||||
return fl
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
swrLayout.isEnabled = false
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
override fun handleResponseCardData(card: Card) {
|
||||
super.handleResponseCardData(card)
|
||||
val bundle = ResponseFragment.setTittle(R.string.fg_name_response_depersonalization)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,10 +38,11 @@ class ItemTypes {
|
|||
)
|
||||
|
||||
val hiddenList = mutableListOf<Id>(
|
||||
CardNumberId.Series, CardNumberId.BatchId, PinsId.Pin3, SigningMethodId.SignExternal,
|
||||
CardNumberId.Series, CardNumberId.BatchId, SigningMethodId.SignExternal,
|
||||
SignHashExPropId.CryptoExKey, SignHashExPropId.CheckPin3, SettingsMaskId.OneApdu,
|
||||
SettingsMaskId.UseBlock, SettingsMaskId.ProtectIssuerDataAgainstReplay,
|
||||
SignHashExPropId.RequireTerminalCertSig, SignHashExPropId.RequireTerminalTxSig
|
||||
SignHashExPropId.RequireTerminalCertSig, SignHashExPropId.RequireTerminalTxSig,
|
||||
BlockId.Pins, PinsId.Pin, PinsId.Pin2, PinsId.Pin3, PinsId.Cvc
|
||||
)
|
||||
|
||||
val oftenUsedList = listOf<Id>(
|
||||
|
|
|
|||
|
|
@ -30,5 +30,6 @@
|
|||
<string name="info_action_user_write_data"> This command writes to the card User Data and User Counter fields.</string>
|
||||
<string name="info_action_user_write_protected_data"> This command writes to the card User Protected Data and User Protected Counter fields.</string>
|
||||
|
||||
<string name="htu_depersonalize_action">To depersonalize the card, press the button at the right bottom corner of the screen</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -6,12 +6,10 @@ import android.view.HapticFeedbackConstants
|
|||
import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.Log
|
||||
import com.tangem.LoggerInterface
|
||||
import com.tangem.Message
|
||||
import com.tangem.SessionViewDelegate
|
||||
import com.tangem.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangem_sdk_new.extensions.hide
|
||||
import com.tangem.tangem_sdk_new.extensions.localizedDescription
|
||||
import com.tangem.tangem_sdk_new.extensions.show
|
||||
import com.tangem.tangem_sdk_new.nfc.NfcReader
|
||||
import com.tangem.tangem_sdk_new.ui.TouchCardAnimation
|
||||
|
|
@ -142,14 +140,17 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel
|
|||
postUI(300) { readingDialog?.dismiss() }
|
||||
}
|
||||
|
||||
override fun onError(errorMessage: String) {
|
||||
override fun onError(error: TangemSdkError) {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.hide()
|
||||
readingDialog?.flSecurityDelay?.hide()
|
||||
readingDialog?.flCompletion?.hide()
|
||||
readingDialog?.flError?.show()
|
||||
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error)
|
||||
readingDialog?.tvTaskText?.text = errorMessage
|
||||
readingDialog?.tvTaskText?.text = activity.getString(
|
||||
R.string.error_message,
|
||||
error.code.toString(), activity.getString(error.localizedDescription())
|
||||
)
|
||||
performHapticFeedback()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.tangem_sdk_new.extensions
|
||||
|
||||
import com.tangem.TangemSdkError
|
||||
import com.tangem.tangem_sdk_new.R
|
||||
|
||||
fun TangemSdkError.localizedDescription(): Int {
|
||||
return when (this) {
|
||||
is TangemSdkError.TagLost -> R.string.error_tag_lost
|
||||
is TangemSdkError.ExtendedLengthNotSupported -> R.string.error_operation
|
||||
is TangemSdkError.SerializeCommandError -> R.string.error_operation
|
||||
is TangemSdkError.DeserializeApduFailed -> R.string.error_operation
|
||||
is TangemSdkError.EncodingFailedTypeMismatch -> R.string.error_operation
|
||||
is TangemSdkError.EncodingFailed -> R.string.error_operation
|
||||
is TangemSdkError.DecodingFailedMissingTag -> R.string.error_operation
|
||||
is TangemSdkError.DecodingFailedTypeMismatch -> R.string.error_operation
|
||||
is TangemSdkError.DecodingFailed -> R.string.error_operation
|
||||
is TangemSdkError.UnknownStatus -> R.string.error_operation
|
||||
is TangemSdkError.ErrorProcessingCommand -> R.string.error_operation
|
||||
is TangemSdkError.InvalidState -> R.string.error_operation
|
||||
is TangemSdkError.InsNotSupported -> R.string.error_operation
|
||||
is TangemSdkError.InvalidParams -> R.string.error_operation
|
||||
is TangemSdkError.NeedEncryption -> R.string.error_operation
|
||||
is TangemSdkError.AlreadyPersonalized -> R.string.error_already_personalized
|
||||
is TangemSdkError.CannotBeDepersonalized -> R.string.error_cannot_be_depersonalized
|
||||
is TangemSdkError.Pin1Required -> R.string.error_operation
|
||||
is TangemSdkError.AlreadyCreated -> R.string.error_already_created
|
||||
is TangemSdkError.PurgeWalletProhibited -> R.string.error_purge_prohibited
|
||||
is TangemSdkError.Pin1CannotBeChanged -> R.string.error_pin1_cannot_be_changed
|
||||
is TangemSdkError.Pin2CannotBeChanged -> R.string.error_pin2_cannot_be_changed
|
||||
is TangemSdkError.Pin1CannotBeDefault -> R.string.error_pin1_cannot_be_default
|
||||
is TangemSdkError.NoRemainingSignatures -> R.string.error_no_remaining_signatures
|
||||
is TangemSdkError.EmptyHashes -> R.string.error_empty_hashes
|
||||
is TangemSdkError.HashSizeMustBeEqual -> R.string.error_cannot_be_signed
|
||||
is TangemSdkError.CardIsEmpty -> R.string.error_card_is_empty
|
||||
is TangemSdkError.SignHashesNotAvailable -> R.string.error_cannot_be_signed
|
||||
is TangemSdkError.TooManyHashesInOneTransaction -> R.string.error_cannot_be_signed
|
||||
is TangemSdkError.NotPersonalized -> R.string.error_not_personalized
|
||||
is TangemSdkError.NotActivated -> R.string.error_not_activated
|
||||
is TangemSdkError.CardIsPurged -> R.string.error_purged
|
||||
is TangemSdkError.Pin2OrCvcRequired -> R.string.error_operation
|
||||
is TangemSdkError.VerificationFailed -> R.string.error_verification_failed
|
||||
is TangemSdkError.DataSizeTooLarge -> R.string.error_data_size_too_large
|
||||
is TangemSdkError.ExendedDataSizeTooLarge -> R.string.error_data_size_too_large_extended
|
||||
is TangemSdkError.MissingCounter -> R.string.error_missing_counter
|
||||
is TangemSdkError.OverwritingDataIsProhibited -> R.string.error_data_cannot_be_written
|
||||
is TangemSdkError.DataCannotBeWritten -> R.string.error_data_cannot_be_written
|
||||
is TangemSdkError.MissingIssuerPubicKey -> R.string.error_missing_issuer_public_key
|
||||
is TangemSdkError.UnknownError -> R.string.error_operation
|
||||
is TangemSdkError.UserCancelled -> R.string.error_user_cancelled
|
||||
is TangemSdkError.Busy -> R.string.error_busy
|
||||
is TangemSdkError.MissingPreflightRead -> R.string.error_operation
|
||||
is TangemSdkError.WrongCardNumber -> R.string.error_wrong_card_number
|
||||
is TangemSdkError.WrongCardType -> R.string.error_wrong_card_type
|
||||
is TangemSdkError.CardError -> R.string.error_card_error
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.TangemSdkError
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.toHexString
|
||||
|
||||
/**
|
||||
* Provides NFC communication between an Android application and Tangem card.
|
||||
|
|
@ -76,7 +77,9 @@ class NfcReader : CardReader {
|
|||
val rawResponse: ByteArray?
|
||||
try {
|
||||
Log.i(this::class.simpleName!!, "Sending data to the card, size is ${data?.size}")
|
||||
Log.v(this::class.simpleName!!, "Raw data that is to be send to the card: ${data?.toHexString()}")
|
||||
rawResponse = isoDep?.transceive(data)
|
||||
Log.v(this::class.simpleName!!, "Raw data that was received from the card: ${rawResponse?.toHexString()}")
|
||||
} catch (exception: TagLostException) {
|
||||
callback?.invoke(CompletionResult.Failure(TangemSdkError.TagLost()))
|
||||
isoDep = null
|
||||
|
|
|
|||
32
tangem-sdk/src/main/res/values/strings_errors.xml
Normal file
32
tangem-sdk/src/main/res/values/strings_errors.xml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="error_message" translatable="false">%s: %s</string>
|
||||
|
||||
<string name="error_operation">An error occurred during the operation</string>
|
||||
<string name="error_tag_lost">Tag lost.</string>
|
||||
<string name="error_already_personalized">This card has already been personalized</string>
|
||||
<string name="error_cannot_be_depersonalized">This card cannot be depersonalized</string>
|
||||
<string name="error_already_created">A wallet has already been created</string>
|
||||
<string name="error_purge_prohibited">This card’s configuration does not allow you to erase the wallet</string>
|
||||
<string name="error_pin1_cannot_be_changed">PIN1 Access Code cannot be changed</string>
|
||||
<string name="error_pin2_cannot_be_changed">PIN2 Passcode cannot be changed</string>
|
||||
<string name="error_pin1_cannot_be_default"> PIN1 Access Code cannot be changed to the default value. Please create a new PIN1 Access Code.</string>
|
||||
<string name="error_no_remaining_signatures"> You cannot sign data with this wallet because the card has reached its signature limit</string>
|
||||
<string name="error_empty_hashes">There is nothing to sign here</string>
|
||||
<string name="error_cannot_be_signed"> This data cannot be signed</string>
|
||||
<string name="error_card_is_empty">Please create a wallet to sign data</string>
|
||||
<string name="error_not_personalized">This card has not been personalized</string>
|
||||
<string name="error_not_activated">This card has not been activated</string>
|
||||
<string name="error_purged">You cannot sign data with this wallet because the card has been purged</string>
|
||||
<string name="error_verification_failed">Data verification failed</string>
|
||||
<string name="error_data_size_too_large">Data size exceeds the max limit of 512 bytes</string>
|
||||
<string name="error_data_size_too_large_extended">Data size exceeds the max limit of 32 Kbytes</string>
|
||||
<string name="error_missing_counter">The data cannot be written to the card because there is no counter</string>
|
||||
<string name="error_data_cannot_be_written">The data cannot be written to the card</string>
|
||||
<string name="error_missing_issuer_public_key">The issuer public key is missing</string>
|
||||
<string name="error_user_cancelled">The user cancelled the operation</string>
|
||||
<string name="error_busy">The CardSession is running a command. Please wait and try again</string>
|
||||
<string name="error_wrong_card_number">You tapped a different card. Please match the in-app Card ID to the your physical Card ID to continue this process.</string>
|
||||
<string name="error_wrong_card_type">This card is configured for a different app. Please see the information on your card and download the related app.</string>
|
||||
<string name="error_card_error">Your card is missing essential data</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue