Updated on 2026-08-14
This commit is contained in:
commit
135a7f5127
28 changed files with 1197 additions and 213 deletions
|
|
@ -26,7 +26,9 @@ public enum Blockchain {
|
|||
Stellar("XLM", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"),
|
||||
StellarTestNet("XLM/test", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"),
|
||||
StellarAsset("Asset", "XLM", 10000000.0, R.drawable.ic_logo_stellar, "Stellar"),
|
||||
Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS");
|
||||
StellarTag("XLM-Tag", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"),
|
||||
Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"),
|
||||
Ducatus("DUC", "DUC", 100000000.0, R.drawable.tangem2, "Ducatus");
|
||||
|
||||
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
|
||||
mID = ID;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.network;
|
|||
|
||||
import com.tangem.data.network.model.InsightBody;
|
||||
import com.tangem.data.network.model.InsightResponse;
|
||||
import com.tangem.data.network.model.InsightUtxo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ public interface InsightApi {
|
|||
Call<InsightResponse> insightAddress(@Path("address") String address);
|
||||
|
||||
@GET(ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS)
|
||||
Call<List<InsightResponse>> insightUnspent(@Path("address") String address);
|
||||
Call<List<InsightUtxo>> insightUnspent(@Path("address") String address);
|
||||
|
||||
@GET(ServerApiInsight.INSIGHT_TRANSACTION)
|
||||
Call<InsightResponse> insightTransaction(@Path("txId") String txId);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import com.tangem.data.network.model.InsightBody;
|
||||
import com.tangem.data.network.model.InsightResponse;
|
||||
import com.tangem.data.network.model.InsightUtxo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -18,11 +19,11 @@ import retrofit2.converter.gson.GsonConverterFactory;
|
|||
public class ServerApiInsight {
|
||||
private static String TAG = ServerApiInsight.class.getSimpleName();
|
||||
|
||||
public static final String INSIGHT_ADDRESS = "/addr/{address}";
|
||||
public static final String INSIGHT_UNSPENT_OUTPUTS = "/addr/{address}/utxo";
|
||||
public static final String INSIGHT_TRANSACTION = "/rawtx/{txId}";
|
||||
public static final String INSIGHT_FEE = "/utils/estimatefee?nbBlocks=2,3,6";
|
||||
public static final String INSIGHT_SEND = "/tx/send";
|
||||
public static final String INSIGHT_ADDRESS = "addr/{address}";
|
||||
public static final String INSIGHT_UNSPENT_OUTPUTS = "addr/{address}/utxo";
|
||||
public static final String INSIGHT_TRANSACTION = "rawtx/{txId}";
|
||||
public static final String INSIGHT_FEE = "utils/estimatefee?nbBlocks=2,3,6";
|
||||
public static final String INSIGHT_SEND = "tx/send";
|
||||
|
||||
private int requestsCount = 0;
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ public class ServerApiInsight {
|
|||
public interface ResponseListener {
|
||||
void onSuccess(String method, InsightResponse insightResponse);
|
||||
|
||||
void onSuccess(String method, List<InsightResponse> utxoList);
|
||||
void onSuccess(String method, List<InsightUtxo> utxoList);
|
||||
|
||||
void onFail(String method, String message);
|
||||
}
|
||||
|
|
@ -49,7 +50,7 @@ public class ServerApiInsight {
|
|||
|
||||
public void requestData(String method, String wallet, String tx) {
|
||||
requestsCount++;
|
||||
String insightURL = "http://130.185.109.17:3001/insigth-api"; //TODO: make random selection
|
||||
String insightURL = "https://insight.ducatus.io/insight-lite-api/"; //TODO: make random selection
|
||||
this.lastNode = insightURL; //TODO: show node instead of URL
|
||||
|
||||
Retrofit retrofitInsight = new Retrofit.Builder()
|
||||
|
|
@ -61,10 +62,10 @@ public class ServerApiInsight {
|
|||
InsightApi insightApi = retrofitInsight.create(InsightApi.class);
|
||||
|
||||
if (method.equals(INSIGHT_UNSPENT_OUTPUTS)) {
|
||||
Call<List<InsightResponse>> call = insightApi.insightUnspent(wallet);
|
||||
call.enqueue(new Callback<List<InsightResponse>>() {
|
||||
Call<List<InsightUtxo>> call = insightApi.insightUnspent(wallet);
|
||||
call.enqueue(new Callback<List<InsightUtxo>>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<List<InsightResponse>> call, @NonNull Response<List<InsightResponse>> response) {
|
||||
public void onResponse(@NonNull Call<List<InsightUtxo>> call, @NonNull Response<List<InsightUtxo>> response) {
|
||||
requestsCount--;
|
||||
|
||||
if (response.code() == 200) {
|
||||
|
|
@ -77,7 +78,7 @@ public class ServerApiInsight {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<List<InsightResponse>> call, @NonNull Throwable t) {
|
||||
public void onFailure(@NonNull Call<List<InsightUtxo>> call, @NonNull Throwable t) {
|
||||
requestsCount--;
|
||||
responseListener.onFail(method, String.valueOf(t.getMessage()));
|
||||
Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage());
|
||||
|
|
@ -92,13 +93,9 @@ public class ServerApiInsight {
|
|||
call = insightApi.insightAddress(wallet);
|
||||
break;
|
||||
|
||||
case INSIGHT_TRANSACTION:
|
||||
call = insightApi.insightTransaction(tx);
|
||||
break;
|
||||
|
||||
case INSIGHT_FEE:
|
||||
call = insightApi.insightFee();
|
||||
break;
|
||||
// case INSIGHT_FEE:
|
||||
// call = insightApi.insightFee();
|
||||
// break;
|
||||
|
||||
case INSIGHT_SEND:
|
||||
call = insightApi.insightSend(new InsightBody(tx));
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import io.reactivex.schedulers.Schedulers;
|
|||
public class ServerApiStellar {
|
||||
|
||||
public ServerApiStellar(Blockchain blockchain) {
|
||||
if (blockchain == Blockchain.Stellar || blockchain == Blockchain.StellarAsset) {
|
||||
if (blockchain == Blockchain.Stellar || blockchain == Blockchain.StellarAsset || blockchain == Blockchain.StellarTag) {
|
||||
currentURL = ServerURL.API_STELLAR;
|
||||
} else {
|
||||
currentURL = ServerURL.API_STELLAR_TESTNET;
|
||||
|
|
@ -173,10 +173,11 @@ public class ServerApiStellar {
|
|||
stellarRequest.setError(null);
|
||||
try {
|
||||
Server server;
|
||||
if (ctx.getBlockchain() == Blockchain.Stellar || ctx.getBlockchain() == Blockchain.StellarAsset) {
|
||||
Blockchain blockchain = ctx.getBlockchain();
|
||||
if (blockchain == Blockchain.Stellar || blockchain == Blockchain.StellarAsset || blockchain == Blockchain.StellarTag) {
|
||||
Network.usePublicNetwork();
|
||||
server = new Server(currentURL);
|
||||
} else if (ctx.getBlockchain() == Blockchain.StellarTestNet) {
|
||||
} else if (blockchain == Blockchain.StellarTestNet) {
|
||||
Network.useTestNetwork();
|
||||
server = new Server(currentURL);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -12,27 +12,29 @@ data class InsightResponse(
|
|||
@SerializedName("addrStr")
|
||||
var addrStr: String = "",
|
||||
|
||||
// @SerializedName("2")
|
||||
// var fee2: String = "",
|
||||
//
|
||||
// @SerializedName("3")
|
||||
// var fee3: String = "",
|
||||
//
|
||||
// @SerializedName("6")
|
||||
// var fee6: String = "",
|
||||
|
||||
@SerializedName("error")
|
||||
var error: String = ""
|
||||
)
|
||||
|
||||
data class InsightUtxo(
|
||||
@SerializedName("txid")
|
||||
var txid: String = "",
|
||||
|
||||
@SerializedName("satoshis")
|
||||
var satoshis: Long? = null,
|
||||
|
||||
@SerializedName("height")
|
||||
var height: Int? = null,
|
||||
@SerializedName("vout")
|
||||
var vout: Int? = null,
|
||||
|
||||
@SerializedName("2")
|
||||
var fee2: String = "",
|
||||
|
||||
@SerializedName("3")
|
||||
var fee3: String = "",
|
||||
|
||||
@SerializedName("6")
|
||||
var fee6: String = "",
|
||||
|
||||
@SerializedName("rawtx")
|
||||
var rawtx: String = "",
|
||||
|
||||
@SerializedName("error")
|
||||
var error: String = ""
|
||||
@SerializedName("scriptPubKey")
|
||||
var scriptPubKey: String? = null
|
||||
)
|
||||
|
|
@ -7,10 +7,13 @@ import android.net.Uri
|
|||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.nfc.tech.Ndef
|
||||
import android.nfc.tech.NfcV
|
||||
import android.os.Bundle
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
|
|
@ -20,9 +23,13 @@ import androidx.core.os.bundleOf
|
|||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.tangem.App
|
||||
import com.tangem.Constant
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.data.Logger
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.tangem_card.reader.CardProtocol
|
||||
import com.tangem.tangem_card.reader.TLV
|
||||
import com.tangem.tangem_card.reader.TLVException
|
||||
import com.tangem.tangem_card.reader.TLVList
|
||||
import com.tangem.tangem_card.tasks.CustomReadCardTask
|
||||
import com.tangem.tangem_card.tasks.ReadCardInfoTask
|
||||
import com.tangem.tangem_sdk.android.data.PINStorage
|
||||
|
|
@ -45,10 +52,16 @@ import com.tangem.wallet.BuildConfig
|
|||
import com.tangem.wallet.CoinEngineFactory
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.TangemContext
|
||||
import com.tangem.wallet.xlmtag.XlmTagEngine
|
||||
import kotlinx.android.synthetic.main.fragment_main.*
|
||||
import kotlinx.android.synthetic.main.layout_touch_card.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback,
|
||||
CardProtocol.Notifications, androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener,
|
||||
|
|
@ -68,6 +81,11 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
private var zipFile: File? = null
|
||||
private var unknownBlockchain = false
|
||||
|
||||
private val parentJob = Job()
|
||||
private val coroutineContext: CoroutineContext
|
||||
get() = parentJob + Dispatchers.IO
|
||||
private val scope = CoroutineScope(coroutineContext)
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
setHasOptionsMenu(true)
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
|
@ -152,6 +170,8 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
return
|
||||
}
|
||||
|
||||
parseNfcvTag(tag)
|
||||
|
||||
try {
|
||||
// get IsoDep handle and run cardReader thread
|
||||
val isoDep = IsoDep.get(tag)
|
||||
|
|
@ -175,6 +195,77 @@ class MainFragment : BaseFragment(), NavigationResultListener, NfcAdapter.Reader
|
|||
}
|
||||
}
|
||||
|
||||
private fun parseNfcvTag(tag: Tag) {
|
||||
if (NfcV.get(tag) != null) {
|
||||
if (Ndef.get(tag) != null) {
|
||||
try {
|
||||
onNdefDiscovered(Ndef.get(tag), tag.id)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onNdefDiscovered(ndef: Ndef, uid: ByteArray) {
|
||||
scope.launch {
|
||||
|
||||
|
||||
try {
|
||||
ndef.connect()
|
||||
val records = ndef.ndefMessage.records
|
||||
for (record in records) {
|
||||
if (record.toUri() != null) {
|
||||
when (record.toUri().toString()) {
|
||||
"vnd.android.nfc://ext/tangem.com:wallet" -> {
|
||||
//mConsole.write(Util.bytesToHex(record.getPayload()), MessageAdapter.MSG_OKAY, "", null, false);
|
||||
val payload = record.payload
|
||||
Log.v(TAG, "tangem.com:wallet[${payload.size} bytes]:")
|
||||
try {
|
||||
val tlvNDEF: TLVList = TLVList.fromBytes(Arrays.copyOfRange(payload, 2, payload.size))
|
||||
val cardDataTlv = TLVList.fromBytes((tlvNDEF.getTLV(TLV.Tag.TAG_CardData)).Value)
|
||||
Log.v(TAG, "\n" + tlvNDEF.getParsedTLVs(""))
|
||||
val card = TangemCard(uid.toString())
|
||||
card.batch = cardDataTlv.getTLV(TLV.Tag.TAG_Batch).asHexString
|
||||
card.setIssuer(cardDataTlv.getTLV(TLV.Tag.TAG_Issuer_ID).Value.toString(), null)
|
||||
card.blockchainID = Blockchain.StellarTag.id
|
||||
card.walletPublicKey = tlvNDEF.getTLV(TLV.Tag.TAG_Wallet_PublicKey).Value
|
||||
card.status = TangemCard.Status.Loaded
|
||||
card.tagSignature = tlvNDEF.getTLV(TLV.Tag.TAG_Signature).Value
|
||||
|
||||
val ctx = TangemContext(card)
|
||||
val engineCoin = XlmTagEngine(ctx)
|
||||
engineCoin.defineWallet()
|
||||
launch(Dispatchers.Main) {
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(Constant.EXTRA_LAST_DISCOVERED_TAG, lastTag)
|
||||
ctx.saveToBundle(bundle)
|
||||
navigateForResult(Constant.REQUEST_CODE_SHOW_CARD_ACTIVITY,
|
||||
R.id.action_main_to_tagFragment, bundle)
|
||||
}
|
||||
} catch (e: TLVException) {
|
||||
e.printStackTrace()
|
||||
Log.v(TAG, e.message)
|
||||
}
|
||||
}
|
||||
else -> Log.v(TAG, record.toUri().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
(activity as MainActivity).nfcManager.notifyReadResult(false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReadStart(cardProtocol: CardProtocol) {
|
||||
rlProgressBar?.post { rlProgressBar?.visibility = View.VISIBLE }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
package com.tangem.ui.fragment.additional
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.tangem.data.network.ServerApiCommon
|
||||
import com.tangem.server_android.ServerApiTangem
|
||||
import com.tangem.ui.fragment.BaseFragment
|
||||
import com.tangem.ui.fragment.wallet.LoadedWalletFragment
|
||||
import com.tangem.ui.fragment.wallet.LoadedWalletViewModel
|
||||
import com.tangem.ui.navigation.NavigationResultListener
|
||||
import com.tangem.util.LOG
|
||||
import com.tangem.util.UtilHelper
|
||||
import com.tangem.wallet.*
|
||||
import com.tangem.wallet.xlmtag.XlmTagEngine
|
||||
import kotlinx.android.synthetic.main.fr_loaded_wallet.*
|
||||
import kotlinx.android.synthetic.main.layout_btn_details.*
|
||||
import kotlinx.android.synthetic.main.layout_tangem_card.*
|
||||
|
||||
|
||||
class TagFragment : BaseFragment(), NavigationResultListener {
|
||||
override val layoutId = R.layout.fragment_tag
|
||||
private lateinit var viewModel: LoadedWalletViewModel
|
||||
private lateinit var ctx: TangemContext
|
||||
private var serverApiCommon: ServerApiCommon = ServerApiCommon()
|
||||
private var serverApiTangem: ServerApiTangem = ServerApiTangem()
|
||||
|
||||
private var requestCounter: Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
LOG.i(LoadedWalletFragment.TAG, "requestCounter, set $field")
|
||||
if (field <= 0) {
|
||||
LOG.e(LoadedWalletFragment.TAG, "+++++++++++ FINISH REFRESH")
|
||||
if (srl != null && srl.isRefreshing)
|
||||
srl.isRefreshing = false
|
||||
} else if (srl != null && !srl.isRefreshing)
|
||||
srl.isRefreshing = true
|
||||
}
|
||||
|
||||
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ctx = TangemContext.loadFromBundle(context, arguments)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val engine = XlmTagEngine(ctx)
|
||||
|
||||
|
||||
btnLoad.visibility = View.GONE
|
||||
btnDetails.visibility = View.GONE
|
||||
btnExtract.text = getString(R.string.tag_claim)
|
||||
btnExtract.isEnabled = false //TODO: enable when we implement extraction
|
||||
btnExtract.backgroundTintList =
|
||||
ContextCompat.getColorStateList(requireContext(), R.color.btn_dark)
|
||||
|
||||
ivTangemCard.setImageResource(R.drawable.card_default_nft)
|
||||
|
||||
tvBalance.setSingleLine(!engine.needMultipleLinesForBalance())
|
||||
tvWallet.text = ctx.coinData.wallet
|
||||
tvWallet.setOnClickListener { shareWallet() }
|
||||
btnExplore.setOnClickListener { startActivity(Intent(Intent.ACTION_VIEW, engine.walletExplorerUri)) }
|
||||
btnCopy.setOnClickListener { shareWallet() }
|
||||
btnNewScan.setOnClickListener { navigateUp() }
|
||||
srl?.setOnRefreshListener { refresh(true) }
|
||||
|
||||
requestBalanceAndUnspentTransactions()
|
||||
// update()
|
||||
}
|
||||
|
||||
|
||||
private fun shareWallet() {
|
||||
val txtShare = ctx.coinData.wallet
|
||||
val clipboard = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.primaryClip = ClipData.newPlainText(txtShare, txtShare)
|
||||
Toast.makeText(activity, R.string.loaded_wallet_toast_copied, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
|
||||
private fun update() {
|
||||
ctx.coinData.setIsBalanceEqual(true)
|
||||
|
||||
if (srl.isRefreshing) {
|
||||
tvBalanceLine1.setTextColor(resources.getColor(R.color.primary))
|
||||
tvBalanceLine1.text = getString(R.string.loaded_wallet_verifying_in_blockchain)
|
||||
tvBalanceLine2.text = ""
|
||||
tvBalance.text = ""
|
||||
tvBalanceEquivalent.text = ""
|
||||
} else {
|
||||
val validator = BalanceValidator()
|
||||
validator.check(ctx, false)
|
||||
context?.let { ContextCompat.getColor(it, validator.color) }?.let { tvBalanceLine1?.setTextColor(it) }
|
||||
tvBalanceLine1?.text = getString(validator.firstLine)
|
||||
tvBalanceLine2?.text = getString(validator.getSecondLine(false))
|
||||
}
|
||||
|
||||
val engine = CoinEngineFactory.create(ctx)
|
||||
when {
|
||||
engine!!.hasBalanceInfo() -> {
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(engine.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(engine.balanceHTML)
|
||||
tvBalance.text = html
|
||||
tvBalanceEquivalent.text = engine.balanceEquivalent
|
||||
}
|
||||
|
||||
ctx.card?.offlineBalance != null -> {
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(engine.offlineBalanceHTML, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(engine.offlineBalanceHTML)
|
||||
tvBalance.text = html
|
||||
}
|
||||
|
||||
else -> tvBalance.text = ""
|
||||
}
|
||||
|
||||
if (ctx.card!!.tokenSymbol.length > 1) {
|
||||
@Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(ctx.blockchainName, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(ctx.blockchainName)
|
||||
tvBlockchain.text = html
|
||||
} else
|
||||
tvBlockchain.text = ctx.blockchainName
|
||||
|
||||
}
|
||||
|
||||
private fun requestBalanceAndUnspentTransactions() {
|
||||
if (UtilHelper.isOnline(context as Activity)) {
|
||||
val coinEngine = CoinEngineFactory.create(ctx)
|
||||
requestCounter++
|
||||
coinEngine!!.requestBalanceAndUnspentTransactions(
|
||||
object : CoinEngine.BlockchainRequestsCallbacks {
|
||||
|
||||
override fun onComplete(success: Boolean) {
|
||||
LOG.i(TAG, "requestBalanceAndUnspentTransactions onComplete: $success, request counter $requestCounter")
|
||||
if (activity == null) return
|
||||
requestCounter--
|
||||
if (!success) {
|
||||
LOG.e(TAG, "requestBalanceAndUnspentTransactions ctx.error: " + ctx.error)
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
override fun onProgress() {
|
||||
if (activity == null) return
|
||||
LOG.i(TAG, "requestBalanceAndUnspentTransactions onProgress")
|
||||
// update()
|
||||
}
|
||||
|
||||
override fun allowAdvance(): Boolean {
|
||||
return try {
|
||||
context?.let { UtilHelper.isOnline(it) }!!
|
||||
} catch (e: KotlinNullPointerException) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
ctx.error = getString(R.string.general_error_no_connection)
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh(clearData: Boolean = true) {
|
||||
if (ctx.card == null) return
|
||||
|
||||
// clear all card data and request again
|
||||
ctx.coinData.clearInfo()
|
||||
|
||||
if (clearData) {
|
||||
ctx.error = null
|
||||
ctx.message = null
|
||||
}
|
||||
|
||||
LOG.w(TAG, "============= START REFRESH")
|
||||
requestCounter = 0
|
||||
srl?.isRefreshing = true
|
||||
|
||||
update()
|
||||
|
||||
ctx.coinData.setIsBalanceEqual(true)
|
||||
|
||||
|
||||
requestBalanceAndUnspentTransactions()
|
||||
|
||||
if (requestCounter == 0) {
|
||||
// if no connection and no requests posted
|
||||
srl?.isRefreshing = false
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = TagFragment::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,24 @@
|
|||
package com.tangem.wallet
|
||||
|
||||
import android.util.Log
|
||||
|
||||
import com.tangem.wallet.btc.BtcEngine
|
||||
import com.tangem.wallet.eth.EthEngine
|
||||
import com.tangem.wallet.token.TokenEngine
|
||||
import com.tangem.wallet.bch.BtcCashEngine
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.wallet.eos.EosEngine
|
||||
import com.tangem.wallet.bch.BtcCashEngine
|
||||
import com.tangem.wallet.binance.BinanceEngine
|
||||
import com.tangem.wallet.btc.BtcEngine
|
||||
import com.tangem.wallet.cardano.CardanoData
|
||||
import com.tangem.wallet.cardano.CardanoEngine
|
||||
import com.tangem.wallet.ducatus.DucatusEngine
|
||||
import com.tangem.wallet.eos.EosEngine
|
||||
import com.tangem.wallet.eth.EthEngine
|
||||
import com.tangem.wallet.ltc.LtcEngine
|
||||
import com.tangem.wallet.matic.MaticTokenEngine
|
||||
import com.tangem.wallet.nftToken.NftTokenEngine
|
||||
import com.tangem.wallet.rsk.RskEngine
|
||||
import com.tangem.wallet.rsk.RskTokenEngine
|
||||
import com.tangem.wallet.token.TokenEngine
|
||||
import com.tangem.wallet.xlm.XlmAssetEngine
|
||||
import com.tangem.wallet.xlm.XlmEngine
|
||||
import com.tangem.wallet.xlmtag.XlmTagEngine
|
||||
import com.tangem.wallet.xrp.XrpEngine
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +49,9 @@ object CoinEngineFactory {
|
|||
Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine()
|
||||
Blockchain.StellarTestNet, Blockchain.Stellar -> XlmEngine()
|
||||
Blockchain.StellarAsset -> XlmAssetEngine()
|
||||
Blockchain.StellarTag -> XlmTagEngine()
|
||||
Blockchain.Eos -> EosEngine()
|
||||
Blockchain.Ducatus -> DucatusEngine()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -84,8 +87,12 @@ object CoinEngineFactory {
|
|||
XlmEngine(context)
|
||||
else if (Blockchain.StellarAsset == context.blockchain)
|
||||
XlmAssetEngine(context)
|
||||
else if (Blockchain.StellarTag == context.blockchain)
|
||||
XlmTagEngine(context)
|
||||
else if (Blockchain.Eos == context.blockchain)
|
||||
EosEngine(context)
|
||||
else if (Blockchain.Ducatus == context.blockchain)
|
||||
DucatusEngine(context)
|
||||
else
|
||||
return null
|
||||
} catch (e: Exception) {
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ public final class Transaction {
|
|||
public static Script buildOutput(String address) throws BitcoinException {
|
||||
//noinspection TryWithIdenticalCatches
|
||||
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
|
||||
if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111 || addressWithCheckSumAndNetworkCode[0] == 48) { //0 for BTC/BCH 1 address | 48 for LTC L address
|
||||
if (addressWithCheckSumAndNetworkCode[0] == 0 || addressWithCheckSumAndNetworkCode[0] == 111 || addressWithCheckSumAndNetworkCode[0] == 48 || addressWithCheckSumAndNetworkCode[0] == 49) { //0 for BTC/BCH 1 address | 48 for LTC L address | 49 for Ducatus
|
||||
return buildOutputP2H(address);
|
||||
}
|
||||
|
||||
|
|
@ -601,7 +601,7 @@ public final class Transaction {
|
|||
//noinspection TryWithIdenticalCatches
|
||||
try {
|
||||
byte[] addressWithCheckSumAndNetworkCode = Base58.decodeBase58(address);
|
||||
if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111 && addressWithCheckSumAndNetworkCode[0] != 48) {
|
||||
if (addressWithCheckSumAndNetworkCode[0] != 0 && addressWithCheckSumAndNetworkCode[0] != 111 && addressWithCheckSumAndNetworkCode[0] != 48 && addressWithCheckSumAndNetworkCode[0] != 49) {
|
||||
throw new BitcoinException(BitcoinException.ERR_UNSUPPORTED, "Unknown address type", address);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.wallet.TangemContext;
|
|||
import com.tangem.wallet.Transaction;
|
||||
import com.tangem.wallet.UnspentOutputInfo;
|
||||
import com.tangem.wallet.btc.BtcData;
|
||||
import com.tangem.wallet.btc.Unspents;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
|
@ -424,7 +425,15 @@ public class BtcCashEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public String getUnspentInputsDescription() {
|
||||
return coinData.getUnspentInputsDescription();
|
||||
Unspents unspents = coinData.getUnspentInputsDescription();
|
||||
if (unspents == null) {
|
||||
return "";
|
||||
} else {
|
||||
return String.format(
|
||||
ctx.getContext().getString(R.string.details_unspents_number),
|
||||
unspents.getUnspetns(),
|
||||
unspents.getGatheredUnspents());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public class BinanceData extends CoinData {
|
|||
}
|
||||
|
||||
public CoinEngine.Amount getBalance() {
|
||||
return new CoinEngine.Amount(balance, "BNB");
|
||||
return balance == null ? null : new CoinEngine.Amount(balance, "BNB");
|
||||
}
|
||||
|
||||
public void setBalance(String balance) {
|
||||
|
|
|
|||
|
|
@ -22,18 +22,19 @@ public class BtcData extends CoinData {
|
|||
//for blockchain.info
|
||||
private boolean hasUnconfirmed = false;
|
||||
|
||||
public String getUnspentInputsDescription() {
|
||||
public Unspents getUnspentInputsDescription() {
|
||||
try {
|
||||
int gatheredUnspents = 0;
|
||||
if (unspentTransactions == null) return "";
|
||||
if (unspentTransactions == null) return null;
|
||||
for (int i = 0; i < unspentTransactions.size(); i++) {
|
||||
if (unspentTransactions.get(i).script != null && unspentTransactions.get(i).script.length() > 1)
|
||||
gatheredUnspents++;
|
||||
}
|
||||
return unspentTransactions.size() + " unspents (" + gatheredUnspents + " received)";
|
||||
|
||||
return new Unspents(unspentTransactions.size(), gatheredUnspents);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -437,7 +437,15 @@ public class BtcEngine extends CoinEngine {
|
|||
|
||||
@Override
|
||||
public String getUnspentInputsDescription() {
|
||||
return coinData.getUnspentInputsDescription();
|
||||
Unspents unspents = coinData.getUnspentInputsDescription();
|
||||
if (unspents == null) {
|
||||
return "";
|
||||
} else {
|
||||
return String.format(
|
||||
ctx.getContext().getString(R.string.details_unspents_number),
|
||||
unspents.getUnspetns(),
|
||||
unspents.getGatheredUnspents());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
3
app/src/main/java/com/tangem/wallet/btc/Unspents.kt
Normal file
3
app/src/main/java/com/tangem/wallet/btc/Unspents.kt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.wallet.btc
|
||||
|
||||
data class Unspents(val unspetns: Int, val gatheredUnspents: Int)
|
||||
|
|
@ -105,7 +105,7 @@ public class CardanoData extends CoinData {
|
|||
}
|
||||
|
||||
public CoinEngine.InternalAmount getBalanceInInternalUnits() {
|
||||
return new CoinEngine.InternalAmount(BigDecimal.valueOf(balance),"Lovelace");
|
||||
return balance == null ? null : new CoinEngine.InternalAmount(BigDecimal.valueOf(balance),"Lovelace");
|
||||
}
|
||||
|
||||
public void setBalance(Long balance) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.util.Log;
|
|||
import com.tangem.App;
|
||||
import com.tangem.data.network.ServerApiInsight;
|
||||
import com.tangem.data.network.model.InsightResponse;
|
||||
import com.tangem.data.network.model.InsightUtxo;
|
||||
import com.tangem.tangem_card.data.TangemCard;
|
||||
import com.tangem.tangem_card.reader.CardProtocol;
|
||||
import com.tangem.tangem_card.tasks.SignTask;
|
||||
|
|
@ -24,11 +25,11 @@ import com.tangem.wallet.Transaction;
|
|||
import com.tangem.wallet.UnspentOutputInfo;
|
||||
import com.tangem.wallet.btc.BtcData;
|
||||
import com.tangem.wallet.btc.BtcEngine;
|
||||
import com.tangem.wallet.btc.Unspents;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
|
@ -48,7 +49,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
} else if (context.getCoinData() instanceof BtcData) {
|
||||
coinData = (BtcData) context.getCoinData();
|
||||
} else {
|
||||
throw new Exception("Invalid type of Blockchain data for LtcEngine");
|
||||
throw new Exception("Invalid type of Blockchain data for DucatusEngine");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +84,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public String getBalanceCurrency() {
|
||||
return "LTC";
|
||||
return "DUC";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -118,7 +119,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public String getFeeCurrency() {
|
||||
return "LTC";
|
||||
return "DUC";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -168,16 +169,12 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public Uri getWalletExplorerUri() {
|
||||
return Uri.parse("https://live.blockcypher.com/ltc/address/" + ctx.getCoinData().getWallet());
|
||||
return Uri.parse("https://insight.ducatus.io/insight/address/" + ctx.getCoinData().getWallet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getShareWalletUri() {
|
||||
if (ctx.getCard().getDenomination() != null) {
|
||||
return Uri.parse("litecoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8));
|
||||
} else {
|
||||
return Uri.parse("litecoin:" + ctx.getCoinData().getWallet());
|
||||
}
|
||||
return Uri.parse(ctx.getCoinData().getWallet());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -317,7 +314,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
byte netSelectionByte = (byte) 0x30;
|
||||
byte netSelectionByte = (byte) 0x31;
|
||||
|
||||
byte hash1[] = Util.calculateSHA256(pkUncompressed);
|
||||
byte hash2[] = Util.calculateRIPEMD160(hash1);
|
||||
|
|
@ -381,23 +378,35 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public String getUnspentInputsDescription() {
|
||||
return coinData.getUnspentInputsDescription();
|
||||
Unspents unspents = coinData.getUnspentInputsDescription();
|
||||
if (unspents == null) {
|
||||
return "";
|
||||
} else {
|
||||
return String.format(
|
||||
ctx.getContext().getString(R.string.details_unspents_number),
|
||||
unspents.getUnspetns(),
|
||||
unspents.getGatheredUnspents());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception {
|
||||
final ArrayList<UnspentOutputInfo> unspentOutputs;
|
||||
ArrayList<UnspentOutputInfo> unspentOutputs = new ArrayList<>();
|
||||
checkBlockchainDataExists();
|
||||
|
||||
String myAddress = ctx.getCoinData().getWallet();
|
||||
byte[] pbKey = ctx.getCard().getWalletPublicKey();
|
||||
|
||||
// Build script for our address
|
||||
List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
|
||||
byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
|
||||
// // Build script for our address
|
||||
// List<BtcData.UnspentTransaction> rawTxList = coinData.getUnspentTransactions();
|
||||
// byte[] outputScriptWeAreAbleToSpend = Transaction.Script.buildOutput(myAddress).bytes;
|
||||
//
|
||||
// // Collect unspent
|
||||
// unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
|
||||
|
||||
// Collect unspent
|
||||
unspentOutputs = BTCUtils.getOutputs(rawTxList, outputScriptWeAreAbleToSpend);
|
||||
for (BtcData.UnspentTransaction utxo : coinData.getUnspentTransactions()) {
|
||||
unspentOutputs.add(new UnspentOutputInfo(BTCUtils.fromHex(utxo.txID), new Transaction.Script(BTCUtils.fromHex(utxo.script)), utxo.amount, utxo.outputN, -1, utxo.txID, null));
|
||||
}
|
||||
|
||||
long fullAmount = 0;
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
|
|
@ -413,8 +422,8 @@ public class DucatusEngine extends BtcEngine {
|
|||
change = change - fees;
|
||||
}
|
||||
|
||||
final long amountFinal=amount;
|
||||
final long changeFinal=change;
|
||||
final long amountFinal = amount;
|
||||
final long changeFinal = change;
|
||||
|
||||
if (amount + fees > fullAmount) {
|
||||
throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount));
|
||||
|
|
@ -422,7 +431,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
final byte[][] txForSign = new byte[unspentOutputs.size()][];
|
||||
final byte[][] bodyDoubleHash = new byte[unspentOutputs.size()][];
|
||||
final byte[][] bodyHash= new byte[unspentOutputs.size()][];
|
||||
final byte[][] bodyHash = new byte[unspentOutputs.size()][];
|
||||
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
txForSign[i] = BTCUtils.buildTXForSign(myAddress, targetAddress, myAddress, unspentOutputs, i, amount, change);
|
||||
|
|
@ -434,13 +443,14 @@ public class DucatusEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
|
||||
return signingMethod==TangemCard.SigningMethod.Sign_Hash || signingMethod==TangemCard.SigningMethod.Sign_Raw;
|
||||
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[][] getHashesToSign() throws Exception {
|
||||
byte[][] dataForSign=new byte[unspentOutputs.size()][];
|
||||
if (txForSign.length > 10) throw new Exception("To much hashes in one transaction!");
|
||||
byte[][] dataForSign = new byte[unspentOutputs.size()][];
|
||||
if (txForSign.length > 10)
|
||||
throw new Exception("To much hashes in one transaction!");
|
||||
for (int i = 0; i < unspentOutputs.size(); ++i) {
|
||||
dataForSign[i] = bodyDoubleHash[i];
|
||||
}
|
||||
|
|
@ -479,7 +489,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
unspentOutputs.get(i).scriptForBuild = DerEncodingUtil.packSignDer(r, s, pbKey);
|
||||
}
|
||||
|
||||
byte[] txForSend=BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal);
|
||||
byte[] txForSend = BTCUtils.buildTXForSend(targetAddress, myAddress, unspentOutputs, amountFinal, changeFinal);
|
||||
notifyOnNeedSendTransaction(txForSend);
|
||||
return txForSend;
|
||||
}
|
||||
|
|
@ -493,39 +503,20 @@ public class DucatusEngine extends BtcEngine {
|
|||
ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, InsightResponse insightResponse) {
|
||||
switch (method) {
|
||||
case ServerApiInsight.INSIGHT_ADDRESS: {
|
||||
try {
|
||||
String walletAddress = insightResponse.getAddrStr();
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(insightResponse.getBalanceSat());
|
||||
coinData.setBalanceUnconfirmed(insightResponse.getUnconfirmedBalanceSat());
|
||||
coinData.setValidationNodeDescription(ServerApiInsight.lastNode);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL INSIGHT_ADDRESS Exception");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ServerApiInsight.INSIGHT_TRANSACTION: {
|
||||
try {
|
||||
String raw = insightResponse.getRawtx();
|
||||
String txHash = new String(BTCUtils.reverse(CryptoUtil.doubleSha256(BTCUtils.fromHex(raw)))); //TODO: check
|
||||
for (BtcData.UnspentTransaction tx : coinData.getUnspentTransactions()) {
|
||||
if (tx.txID.equals(txHash))
|
||||
tx.script = raw;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
String walletAddress = insightResponse.getAddrStr();
|
||||
if (!walletAddress.equals(coinData.getWallet())) {
|
||||
// todo - check
|
||||
throw new Exception("Invalid wallet address in answer!");
|
||||
}
|
||||
break;
|
||||
coinData.setBalanceReceived(true);
|
||||
coinData.setBalanceConfirmed(insightResponse.getBalanceSat());
|
||||
coinData.setBalanceUnconfirmed(insightResponse.getUnconfirmedBalanceSat());
|
||||
coinData.setValidationNodeDescription(ServerApiInsight.lastNode);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "FAIL INSIGHT_ADDRESS Exception");
|
||||
}
|
||||
|
||||
if (serverApiInsight.isRequestsSequenceCompleted()) {
|
||||
|
|
@ -535,30 +526,27 @@ public class DucatusEngine extends BtcEngine {
|
|||
}
|
||||
}
|
||||
|
||||
public void onSuccess(String method, List<InsightResponse> utxoList) {
|
||||
// case ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS: TODO: check method
|
||||
try {
|
||||
coinData.getUnspentTransactions().clear();
|
||||
for (InsightResponse utxo : utxoList) {
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = utxo.getTxid();
|
||||
trUnspent.amount = utxo.getSatoshis();
|
||||
trUnspent.outputN = utxo.getHeight();
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
}
|
||||
|
||||
for (InsightResponse utxo : utxoList) {
|
||||
//if (height != -1) { TODO: check
|
||||
if (blockchainRequestsCallbacks.allowAdvance()) {
|
||||
serverApiInsight.requestData(ServerApiInsight.INSIGHT_TRANSACTION, "", utxo.getTxid());
|
||||
} else {
|
||||
ctx.setError("Terminated by user");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
public void onSuccess(String method, List<InsightUtxo> utxoList) {
|
||||
// case ServerApiInsight.INSIGHT_UNSPENT_OUTPUTS: TODO: check method
|
||||
try {
|
||||
coinData.getUnspentTransactions().clear();
|
||||
for (InsightUtxo utxo : utxoList) {
|
||||
BtcData.UnspentTransaction trUnspent = new BtcData.UnspentTransaction();
|
||||
trUnspent.txID = utxo.getTxid();
|
||||
trUnspent.amount = utxo.getSatoshis();
|
||||
trUnspent.outputN = utxo.getVout();
|
||||
trUnspent.script = utxo.getScriptPubKey();
|
||||
coinData.getUnspentTransactions().add(trUnspent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (serverApiInsight.isRequestsSequenceCompleted()) {
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError());
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -582,65 +570,71 @@ public class DucatusEngine extends BtcEngine {
|
|||
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) throws Exception {
|
||||
final int calcSize = calculateEstimatedTransactionSize(targetAddress, amount.toValueString());
|
||||
Log.e(TAG, String.format("Estimated tx size %d", calcSize));
|
||||
coinData.minFee=null;
|
||||
coinData.maxFee=null;
|
||||
coinData.normalFee=null;
|
||||
|
||||
final ServerApiInsight serverApiInsight = new ServerApiInsight();
|
||||
|
||||
final ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() {
|
||||
@Override
|
||||
public void onSuccess(String method, InsightResponse insightResponse) {
|
||||
if ( method.equals(ServerApiInsight.INSIGHT_FEE)) {
|
||||
try {
|
||||
BigDecimal minFee = new BigDecimal(insightResponse.getFee2()); //fee per KB
|
||||
BigDecimal normalFee = new BigDecimal(insightResponse.getFee3());
|
||||
BigDecimal maxFee = new BigDecimal(insightResponse.getFee6());
|
||||
|
||||
if (minFee.equals(BigDecimal.ZERO) || normalFee.equals(BigDecimal.ZERO) || maxFee.equals(BigDecimal.ZERO)) {
|
||||
serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "","");
|
||||
}
|
||||
|
||||
minFee = minFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size
|
||||
normalFee = normalFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024));
|
||||
maxFee = maxFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024));
|
||||
|
||||
// //compare fee to usual relay fee TODO: check if needed after we get access to Ducatus network
|
||||
// if (fee.compareTo(relayFee) < 0) {
|
||||
// fee = relayFee;
|
||||
// coinData.minFee=null;
|
||||
// coinData.maxFee=null;
|
||||
// coinData.normalFee=null;
|
||||
//
|
||||
// final ServerApiInsight serverApiInsight = new ServerApiInsight();
|
||||
//
|
||||
// final ServerApiInsight.ResponseListener responseListener = new ServerApiInsight.ResponseListener() {
|
||||
// @Override
|
||||
// public void onSuccess(String method, InsightResponse insightResponse) {
|
||||
// if ( method.equals(ServerApiInsight.INSIGHT_FEE)) {
|
||||
// try {
|
||||
// BigDecimal minFee = new BigDecimal(insightResponse.getFee2()); //fee per KB
|
||||
// BigDecimal normalFee = new BigDecimal(insightResponse.getFee3());
|
||||
// BigDecimal maxFee = new BigDecimal(insightResponse.getFee6());
|
||||
//
|
||||
// if (minFee.equals(BigDecimal.ZERO) || normalFee.equals(BigDecimal.ZERO) || maxFee.equals(BigDecimal.ZERO)) {
|
||||
// serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "","");
|
||||
// }
|
||||
minFee = minFee.setScale(8, RoundingMode.DOWN);
|
||||
normalFee = normalFee.setScale(8, RoundingMode.DOWN);
|
||||
maxFee = maxFee.setScale(8, RoundingMode.DOWN);
|
||||
//
|
||||
// minFee = minFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024)); // (per KB -> per byte)*size
|
||||
// normalFee = normalFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024));
|
||||
// maxFee = maxFee.multiply(new BigDecimal(calcSize)).divide(new BigDecimal(1024));
|
||||
//
|
||||
//// //compare fee to usual relay fee TODO: check if needed after we get access to Ducatus network
|
||||
//// if (fee.compareTo(relayFee) < 0) {
|
||||
//// fee = relayFee;
|
||||
//// }
|
||||
// minFee = minFee.setScale(8, RoundingMode.DOWN);
|
||||
// normalFee = normalFee.setScale(8, RoundingMode.DOWN);
|
||||
// maxFee = maxFee.setScale(8, RoundingMode.DOWN);
|
||||
//
|
||||
// coinData.minFee = new Amount(minFee, ctx.getBlockchain().getCurrency());
|
||||
// coinData.normalFee = new Amount(normalFee, ctx.getBlockchain().getCurrency());
|
||||
// coinData.maxFee = new Amount(maxFee, ctx.getBlockchain().getCurrency());
|
||||
//
|
||||
// blockchainRequestsCallbacks.onComplete(true);
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onSuccess (String method, List<InsightResponse> utxoList) {
|
||||
// Log.e(TAG, "Wrong response body, InsightResponse expected");
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onFail(String method, String message) {
|
||||
// if (!serverApiInsight.isRequestsSequenceCompleted()) {
|
||||
// ctx.setError(message);
|
||||
// blockchainRequestsCallbacks.onComplete(false);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// serverApiInsight.setResponseListener(responseListener);
|
||||
//
|
||||
// serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "", ""); TODO: fee api returns -1 now
|
||||
|
||||
coinData.minFee = new Amount(minFee, ctx.getBlockchain().getCurrency());
|
||||
coinData.normalFee = new Amount(normalFee, ctx.getBlockchain().getCurrency());
|
||||
coinData.maxFee = new Amount(maxFee, ctx.getBlockchain().getCurrency());
|
||||
coinData.minFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000089)), ctx.getBlockchain().getCurrency()); //fee for byte from Ducatus wallet for android
|
||||
coinData.normalFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000144)), ctx.getBlockchain().getCurrency());
|
||||
coinData.maxFee = new Amount(BigDecimal.valueOf(calcSize).multiply(BigDecimal.valueOf(0.00000350)), ctx.getBlockchain().getCurrency());
|
||||
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess (String method, List<InsightResponse> utxoList) {
|
||||
Log.e(TAG, "Wrong response body, InsightResponse expected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String method, String message) {
|
||||
if (!serverApiInsight.isRequestsSequenceCompleted()) {
|
||||
ctx.setError(message);
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
serverApiInsight.setResponseListener(responseListener);
|
||||
|
||||
serverApiInsight.requestData(ServerApiInsight.INSIGHT_FEE, "", "");
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -657,7 +651,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
if (resultString.isEmpty()) {
|
||||
ctx.setError("No response from node");
|
||||
blockchainRequestsCallbacks.onComplete(false);
|
||||
}else { // TODO: Make check for a valid send response
|
||||
} else { // TODO: Make check for a valid send response
|
||||
ctx.setError(null);
|
||||
blockchainRequestsCallbacks.onComplete(true);
|
||||
}
|
||||
|
|
@ -676,7 +670,7 @@ public class DucatusEngine extends BtcEngine {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess (String method, List<InsightResponse> utxoList) {
|
||||
public void onSuccess(String method, List<InsightUtxo> utxoList) {
|
||||
Log.e(TAG, "Wrong response body, InsightResponse expected");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import com.tangem.wallet.Transaction;
|
|||
import com.tangem.wallet.UnspentOutputInfo;
|
||||
import com.tangem.wallet.btc.BtcData;
|
||||
import com.tangem.wallet.btc.BtcEngine;
|
||||
import com.tangem.wallet.btc.Unspents;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
|
|
@ -389,7 +390,15 @@ public class LtcEngine extends BtcEngine {
|
|||
|
||||
@Override
|
||||
public String getUnspentInputsDescription() {
|
||||
return coinData.getUnspentInputsDescription();
|
||||
Unspents unspents = coinData.getUnspentInputsDescription();
|
||||
if (unspents == null) {
|
||||
return "";
|
||||
} else {
|
||||
return String.format(
|
||||
ctx.getContext().getString(R.string.details_unspents_number),
|
||||
unspents.getUnspetns(),
|
||||
unspents.getGatheredUnspents());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
133
app/src/main/java/com/tangem/wallet/xlmtag/XlmTagData.kt
Normal file
133
app/src/main/java/com/tangem/wallet/xlmtag/XlmTagData.kt
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package com.tangem.wallet.xlmtag
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import com.tangem.wallet.CoinData
|
||||
import com.tangem.wallet.CoinEngine
|
||||
import com.tangem.wallet.CoinEngine.InternalAmount
|
||||
import org.stellar.sdk.KeyPair
|
||||
import org.stellar.sdk.responses.AccountResponse
|
||||
import org.stellar.sdk.responses.LedgerResponse
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
class XlmTagData : CoinData() {
|
||||
class AccountResponseEx internal constructor(accountId: String?, sequenceNumber: Long?) : AccountResponse(KeyPair.fromAccountId(accountId), sequenceNumber)
|
||||
|
||||
private var balance: CoinEngine.Amount? = null
|
||||
private var sequenceNumber: Long? = 0L
|
||||
private var baseReserve: CoinEngine.Amount? = CoinEngine.Amount("0.5", "XLM")
|
||||
var baseFee: CoinEngine.Amount? = CoinEngine.Amount("0.00001", "XLM")
|
||||
private set
|
||||
var isError404 = false
|
||||
var isTargetAccountCreated = false
|
||||
var fundsFromTrustedSource = false
|
||||
var fundsSentToTrustedSource = false
|
||||
|
||||
|
||||
override fun clearInfo() {
|
||||
super.clearInfo()
|
||||
balance = null
|
||||
isError404 = false
|
||||
isTargetAccountCreated = false
|
||||
fundsFromTrustedSource = false
|
||||
fundsSentToTrustedSource = false
|
||||
}
|
||||
|
||||
fun getBalance(): CoinEngine.Amount? {
|
||||
return if (balance != null) {
|
||||
CoinEngine.Amount(balance!!.subtract(reserve), "XLM")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val reserve: CoinEngine.Amount
|
||||
get() = CoinEngine.Amount(baseReserve!!.multiply(BigDecimal.valueOf(2)), "XLM")
|
||||
|
||||
var accountResponse: AccountResponse
|
||||
get() = XlmTagData.AccountResponseEx(wallet, sequenceNumber)
|
||||
set(accountResponse) {
|
||||
if (accountResponse.balances.size > 0) {
|
||||
val balanceResponse = accountResponse.balances[0]
|
||||
balance = CoinEngine.Amount(balanceResponse.balance, "XLM")
|
||||
}
|
||||
sequenceNumber = accountResponse.sequenceNumber
|
||||
isBalanceReceived = true
|
||||
}
|
||||
|
||||
fun setLedgerResponse(ledgerResponse: LedgerResponse) {
|
||||
val xlmEngine = XlmTagEngine()
|
||||
baseReserve = xlmEngine.convertToAmount(InternalAmount(ledgerResponse.baseReserveInStroops, "stroops"))
|
||||
baseFee = xlmEngine.convertToAmount(InternalAmount(ledgerResponse.baseFeeInStroops, "stroops"))
|
||||
}
|
||||
|
||||
fun incSequenceNumber() {
|
||||
sequenceNumber = sequenceNumber?.inc()
|
||||
}
|
||||
|
||||
override fun loadFromBundle(B: Bundle) {
|
||||
super.loadFromBundle(B)
|
||||
balance = if (B.containsKey("BalanceCurrency") && B.containsKey("BalanceDecimal")) {
|
||||
CoinEngine.Amount(B.getString("BalanceDecimal"), B.getString("BalanceCurrency"))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
sequenceNumber = if (B.containsKey("sequenceNumber")) {
|
||||
B.getLong("sequenceNumber")
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
baseReserve = if (B.containsKey("BaseReserveCurrency") && B.containsKey("BaseReserveDecimal")) {
|
||||
CoinEngine.Amount(B.getString("BaseReserveDecimal"), B.getString("BaseReserveCurrency"))
|
||||
} else {
|
||||
CoinEngine.Amount("0.5", "XLM")
|
||||
}
|
||||
baseFee = if (B.containsKey("BaseFeeCurrency") && B.containsKey("BaseFeeDecimal")) {
|
||||
CoinEngine.Amount(B.getString("BaseFeeDecimal"), B.getString("BaseFeeCurrency"))
|
||||
} else {
|
||||
CoinEngine.Amount("0.00001", "XLM")
|
||||
}
|
||||
if (B.containsKey("Error404")) isError404 = B.getBoolean("Error404") else isError404 = false
|
||||
if (B.containsKey("TargetAccountCreated")) isTargetAccountCreated = B.getBoolean("TargetAccountCreated") else isTargetAccountCreated = false
|
||||
fundsFromTrustedSource = if (B.containsKey("FundsFromTrustedSource")) {
|
||||
B.getBoolean("FundsFromTrustedSource")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
fundsSentToTrustedSource = if (B.containsKey("FundsSentToTrustedSource")) {
|
||||
B.getBoolean("FundsSentToTrustedSource")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun saveToBundle(B: Bundle) {
|
||||
super.saveToBundle(B)
|
||||
try {
|
||||
if (balance != null) {
|
||||
B.putString("BalanceCurrency", balance!!.currency)
|
||||
B.putString("BalanceDecimal", balance!!.toValueString())
|
||||
}
|
||||
if (sequenceNumber != null) {
|
||||
B.putLong("sequenceNumber", sequenceNumber!!)
|
||||
}
|
||||
if (baseReserve != null) {
|
||||
B.putString("BaseReserveCurrency", baseReserve!!.currency)
|
||||
B.putString("BaseReserveDecimal", baseReserve!!.toValueString())
|
||||
}
|
||||
if (baseFee != null) {
|
||||
B.putString("BaseFeeCurrency", baseFee!!.currency)
|
||||
B.putString("BaseFeeDecimal", baseFee!!.toValueString())
|
||||
}
|
||||
if (isError404) B.putBoolean("Error404", true)
|
||||
if (isTargetAccountCreated) B.putBoolean("TargetAccountCreated", true)
|
||||
if (fundsFromTrustedSource) B.putBoolean("FundsFromTrustedSource", true)
|
||||
if (fundsSentToTrustedSource) B.putBoolean("FundsSentToTrustedSource", true)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("Can't save to bundle ", e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
471
app/src/main/java/com/tangem/wallet/xlmtag/XlmTagEngine.kt
Normal file
471
app/src/main/java/com/tangem/wallet/xlmtag/XlmTagEngine.kt
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
package com.tangem.wallet.xlmtag
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.StrictMode
|
||||
import android.text.InputFilter
|
||||
import android.util.Log
|
||||
import com.tangem.App
|
||||
import com.tangem.data.Blockchain
|
||||
import com.tangem.data.network.ServerApiStellar
|
||||
import com.tangem.data.network.StellarRequest
|
||||
import com.tangem.data.network.StellarRequest.Ledgers
|
||||
import com.tangem.data.network.StellarRequest.SubmitTransaction
|
||||
import com.tangem.tangem_card.data.TangemCard
|
||||
import com.tangem.tangem_card.tasks.SignTask
|
||||
import com.tangem.tangem_card.util.Util
|
||||
import com.tangem.util.DecimalDigitsInputFilter
|
||||
import com.tangem.wallet.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.stellar.sdk.*
|
||||
import org.stellar.sdk.requests.RequestBuilder
|
||||
import org.stellar.sdk.responses.operations.OperationResponse
|
||||
import org.stellar.sdk.responses.operations.PaymentOperationResponse
|
||||
import java.io.IOException
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
class XlmTagEngine : CoinEngine {
|
||||
var coinData: XlmTagData? = null
|
||||
val operations = mutableListOf<OperationResponse>()
|
||||
|
||||
constructor(context: TangemContext) : super(context) {
|
||||
if (context.coinData == null) {
|
||||
coinData = XlmTagData()
|
||||
context.coinData = coinData
|
||||
} else if (context.coinData is XlmTagData) {
|
||||
coinData = context.coinData as XlmTagData
|
||||
} else {
|
||||
throw Exception("Invalid type of Blockchain data for XlmEngine")
|
||||
}
|
||||
}
|
||||
|
||||
constructor() : super() {}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun checkBlockchainDataExists() {
|
||||
if (coinData == null) throw Exception("No blockchain data")
|
||||
}
|
||||
|
||||
override fun awaitingConfirmation(): Boolean {
|
||||
return App.pendingTransactionsStorage.hasTransactions(ctx.card)
|
||||
}
|
||||
|
||||
override fun getBalanceHTML(): String {
|
||||
return if (hasBalanceInfo() && isBalanceNotZero && coinData!!.fundsFromTrustedSource) {
|
||||
ctx.getString(R.string.tag_genuine)
|
||||
} else if (coinData!!.fundsSentToTrustedSource) {
|
||||
ctx.getString(R.string.tag_claimed)
|
||||
} else {
|
||||
ctx.getString(R.string.tag_not_genuine)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceCurrency(): String {
|
||||
return "XLM"
|
||||
}
|
||||
|
||||
override fun isBalanceNotZero(): Boolean {
|
||||
if (coinData == null) return false
|
||||
return if (coinData!!.getBalance() == null) false else coinData!!.getBalance()!!.notZero()
|
||||
}
|
||||
|
||||
override fun hasBalanceInfo(): Boolean {
|
||||
return if (coinData == null) false else coinData!!.getBalance() != null || coinData!!.isError404
|
||||
}
|
||||
|
||||
override fun isExtractPossible(): Boolean {
|
||||
if (!hasBalanceInfo()) {
|
||||
ctx.setMessage(R.string.loaded_wallet_error_obtaining_blockchain_data)
|
||||
} else if (!isBalanceNotZero) {
|
||||
ctx.setMessage(R.string.general_wallet_empty)
|
||||
} else if (awaitingConfirmation()) {
|
||||
ctx.setMessage(R.string.loaded_wallet_message_wait)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getFeeCurrency(): String {
|
||||
return "XLM"
|
||||
}
|
||||
|
||||
override fun validateAddress(address: String): Boolean {
|
||||
try {
|
||||
val kp = KeyPair.fromAccountId(address)
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isNeedCheckNode(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getWalletExplorerUri(): Uri {
|
||||
return Uri.parse("https://stellar.expert/explorer/public/account/" + ctx.coinData.wallet)
|
||||
}
|
||||
|
||||
override fun getShareWalletUri(): Uri {
|
||||
return if (ctx.card.denomination != null) {
|
||||
Uri.parse(ctx.coinData.wallet + "?amount=" + convertToAmount(convertToInternalAmount(ctx.card.denomination)!!).toValueString())
|
||||
} else {
|
||||
Uri.parse(ctx.coinData.wallet)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAmountInputFilters(): Array<InputFilter> {
|
||||
return arrayOf(DecimalDigitsInputFilter(decimals))
|
||||
}
|
||||
|
||||
override fun checkNewTransactionAmount(amount: Amount): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun checkNewTransactionAmountAndFee(amountValue: Amount, feeValue: Amount, isIncludeFee: Boolean): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun validateBalance(balanceValidator: BalanceValidator): Boolean {
|
||||
return try {
|
||||
if (ctx.card.offlineBalance == null && !ctx.coinData.isBalanceReceived || !ctx.coinData.isBalanceReceived && ctx.card.remainingSignatures != ctx.card.maxSignatures) {
|
||||
if (coinData!!.isError404) {
|
||||
balanceValidator.setScore(0)
|
||||
balanceValidator.firstLine = R.string.balance_validator_first_line_no_account
|
||||
balanceValidator.setSecondLine(R.string.balance_validator_second_line_create_account_instruction)
|
||||
} else {
|
||||
balanceValidator.setScore(0)
|
||||
balanceValidator.firstLine = R.string.balance_validator_first_line_unknown_balance
|
||||
balanceValidator.setSecondLine(R.string.balance_validator_second_line_unverified_balance)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (coinData!!.isBalanceReceived && coinData!!.isBalanceEqual) {
|
||||
balanceValidator.setScore(100)
|
||||
balanceValidator.firstLine = R.string.balance_validator_first_line_verified_balance
|
||||
balanceValidator.setSecondLine(R.string.balance_validator_second_line_confirmed_in_blockchain)
|
||||
if (coinData!!.getBalance()!!.isZero) {
|
||||
balanceValidator.firstLine = R.string.balance_validator_first_line_empty_wallet
|
||||
balanceValidator.setSecondLine(R.string.empty_string)
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.card.offlineBalance != null && !coinData!!.isBalanceReceived && ctx.card.remainingSignatures == ctx.card.maxSignatures && coinData!!.getBalance()!!.notZero()) {
|
||||
balanceValidator.setScore(80)
|
||||
balanceValidator.firstLine = R.string.balance_validator_first_line_verified_offline
|
||||
balanceValidator.setSecondLine(R.string.balance_validator_second_line_internet_to_get_balance)
|
||||
}
|
||||
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalance(): Amount? {
|
||||
return if (!hasBalanceInfo()) null else coinData!!.getBalance()!!
|
||||
}
|
||||
|
||||
override fun evaluateFeeEquivalent(fee: String): String {
|
||||
return if (!coinData!!.amountEquivalentDescriptionAvailable) "" else try {
|
||||
val feeAmount = Amount(fee, feeCurrency)
|
||||
feeAmount.toEquivalentString(coinData!!.rate.toDouble())
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBalanceEquivalent(): String {
|
||||
if (coinData == null || !coinData!!.amountEquivalentDescriptionAvailable) return ""
|
||||
val balance = balance ?: return ""
|
||||
return balance.toEquivalentString(coinData!!.rate.toDouble())
|
||||
}
|
||||
|
||||
override fun calculateAddress(pkUncompressed: ByteArray): String {
|
||||
val kp = KeyPair.fromPublicKey(pkUncompressed)
|
||||
return kp.accountId
|
||||
}
|
||||
|
||||
override fun convertToAmount(internalAmount: InternalAmount): Amount {
|
||||
val d = internalAmount.divide(multiplier)
|
||||
return Amount(d, balanceCurrency)
|
||||
}
|
||||
|
||||
override fun convertToAmount(strAmount: String, currency: String): Amount {
|
||||
return Amount(strAmount, currency)
|
||||
}
|
||||
|
||||
override fun convertToInternalAmount(amount: Amount): InternalAmount {
|
||||
val d = amount.multiply(multiplier)
|
||||
return InternalAmount(d, "stroops")
|
||||
}
|
||||
|
||||
override fun convertToInternalAmount(bytes: ByteArray): InternalAmount? {
|
||||
if (bytes == null) return null
|
||||
val reversed = ByteArray(bytes.size)
|
||||
for (i in bytes.indices) reversed[i] = bytes[bytes.size - i - 1]
|
||||
return InternalAmount(Util.byteArrayToLong(reversed), "stroops")
|
||||
}
|
||||
|
||||
override fun convertToByteArray(internalAmount: InternalAmount): ByteArray {
|
||||
val bytes = Util.longToByteArray(internalAmount.longValueExact())
|
||||
val reversed = ByteArray(bytes.size)
|
||||
for (i in bytes.indices) reversed[i] = bytes[bytes.size - i - 1]
|
||||
return reversed
|
||||
}
|
||||
|
||||
override fun createCoinData(): CoinData {
|
||||
return XlmTagData()
|
||||
}
|
||||
|
||||
override fun getUnspentInputsDescription(): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun constructTransaction(amountValue: Amount, feeValue: Amount, IncFee: Boolean, targetAddress: String): SignTask.TransactionToSign {
|
||||
var amountValue = amountValue
|
||||
checkBlockchainDataExists()
|
||||
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
|
||||
StrictMode.setThreadPolicy(policy)
|
||||
if (IncFee) {
|
||||
amountValue = Amount(amountValue.subtract(feeValue), amountValue.currency)
|
||||
}
|
||||
val operation: Operation
|
||||
operation = if (coinData!!.isTargetAccountCreated) PaymentOperation.Builder(KeyPair.fromAccountId(targetAddress), AssetTypeNative(), amountValue.toValueString()).build() else CreateAccountOperation.Builder(KeyPair.fromAccountId(targetAddress), amountValue.toValueString()).build()
|
||||
val transaction = TransactionEx.buildEx(60, coinData!!.accountResponse, operation)
|
||||
if (transaction.fee != convertToInternalAmount(feeValue).intValueExact()) {
|
||||
throw Exception("Invalid fee!")
|
||||
}
|
||||
return object : SignTask.TransactionToSign {
|
||||
override fun isSigningMethodSupported(signingMethod: TangemCard.SigningMethod): Boolean {
|
||||
return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getHashesToSign(): Array<ByteArray> {
|
||||
val dataForSign = arrayOf(transaction.hash())
|
||||
return dataForSign
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getRawDataToSign(): ByteArray {
|
||||
return transaction.signatureBase()
|
||||
}
|
||||
|
||||
override fun getHashAlgToSign(): String {
|
||||
return "sha-256"
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun getIssuerTransactionSignature(dataToSignByIssuer: ByteArray): ByteArray {
|
||||
throw Exception("Issuer validation not supported!")
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun onSignCompleted(signFromCard: ByteArray): ByteArray { // Sign the transaction to prove you are actually the person sending it.
|
||||
transaction.setSign(signFromCard)
|
||||
val txForSend = transaction.toEnvelopeXdrBase64().toByteArray()
|
||||
notifyOnNeedSendTransaction(txForSend)
|
||||
return txForSend
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTargetAccountCreated(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, targetAddress: String, amount: Amount) {
|
||||
val serverApi = ServerApiStellar(ctx.blockchain)
|
||||
val listener: ServerApiStellar.Listener = object : ServerApiStellar.Listener {
|
||||
override fun onSuccess(request: StellarRequest.Base) {
|
||||
coinData!!.isTargetAccountCreated = true
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
}
|
||||
|
||||
override fun onFail(request: StellarRequest.Base) {
|
||||
Log.i(TAG, "onFail: " + request.javaClass.simpleName + " " + request.error)
|
||||
if (request.errorResponse != null && request.errorResponse.code == 404) {
|
||||
coinData!!.isTargetAccountCreated = false
|
||||
if (amount.compareTo(coinData!!.reserve) >= 0) {
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
} else {
|
||||
ctx.setError(R.string.confirm_transaction_error_not_enough_xlm_for_create)
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
} else { // suppose account is created if anything goes wrong
|
||||
coinData!!.isTargetAccountCreated = true
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
serverApi.setListener(listener)
|
||||
serverApi.requestData(ctx, StellarRequest.Balance(targetAddress))
|
||||
}
|
||||
|
||||
private fun requestPayments(blockchainRequestsCallbacks: BlockchainRequestsCallbacks) {
|
||||
val server = Server("https://horizon.stellar.org/")
|
||||
val accountKeyPair = KeyPair.fromAccountId(coinData!!.wallet)
|
||||
|
||||
try {
|
||||
var operationsPage = server.payments().forAccount(accountKeyPair).order(RequestBuilder.Order.DESC).execute()
|
||||
Log.e("Stellar", operationsPage.records.toString())
|
||||
|
||||
operations.addAll(operationsPage.records)
|
||||
|
||||
while (operations.size < OPERATIONS_LIMIT) {
|
||||
operationsPage = operationsPage.getNextPage(server.httpClient)
|
||||
operations.addAll(operationsPage.records)
|
||||
Log.e("Stellar", operationsPage.records.toString())
|
||||
Log.e("Stellar", "Operations downloaded: " + operationsPage.records.count())
|
||||
Log.e("Stellar", "Operations overall: " + operations.count())
|
||||
}
|
||||
|
||||
parsePayments(blockchainRequestsCallbacks)
|
||||
} catch (e: Exception) {
|
||||
if (e.message != null) {
|
||||
ctx.error = e.message
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
} else {
|
||||
ctx.error = e.javaClass.name
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parsePayments(blockchainRequestsCallbacks: BlockchainRequestsCallbacks) {
|
||||
coinData!!.fundsFromTrustedSource = operations.find { it is PaymentOperationResponse && it.from.accountId == TRUSTED_SOURCE } != null
|
||||
coinData!!.fundsSentToTrustedSource =
|
||||
(operations.find { it is PaymentOperationResponse && it.to.accountId == TRUSTED_DESTINATION } != null) && coinData!!.fundsFromTrustedSource
|
||||
Log.e("Stellar",
|
||||
"fundsFromTrustedSource $coinData!!.fundsFromTrustedSource, " +
|
||||
"fundsSentToTrustedSource $coinData!!.fundsSentToTrustedSource")
|
||||
blockchainRequestsCallbacks.onComplete(!ctx.hasError())
|
||||
}
|
||||
|
||||
override fun requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks: BlockchainRequestsCallbacks) {
|
||||
CoroutineScope(Dispatchers.IO).launch { requestPayments(blockchainRequestsCallbacks) }
|
||||
val serverApi = ServerApiStellar(Blockchain.Stellar)
|
||||
val listener: ServerApiStellar.Listener = object : ServerApiStellar.Listener {
|
||||
override fun onSuccess(request: StellarRequest.Base) {
|
||||
Log.i(TAG, "onSuccess: " + request.javaClass.simpleName)
|
||||
if (request is StellarRequest.Balance) {
|
||||
coinData!!.accountResponse = request.accountResponse
|
||||
coinData!!.validationNodeDescription = serverApi.currentURL
|
||||
|
||||
blockchainRequestsCallbacks.onProgress()
|
||||
} else if (request is Ledgers) {
|
||||
coinData!!.setLedgerResponse(request.ledgerResponse)
|
||||
if (serverApi.isRequestsSequenceCompleted) {
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress()
|
||||
}
|
||||
} else {
|
||||
ctx.error = "Invalid request logic"
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFail(request: StellarRequest.Base) {
|
||||
Log.i(TAG, "onFail: " + request.javaClass.simpleName + " " + request.error)
|
||||
if (request.errorResponse != null && request.errorResponse.code == 404) {
|
||||
coinData!!.isError404 = true
|
||||
} else {
|
||||
ctx.error = request.error
|
||||
}
|
||||
if (serverApi.isRequestsSequenceCompleted) {
|
||||
if (ctx.hasError()) {
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
}
|
||||
} else {
|
||||
blockchainRequestsCallbacks.onProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
serverApi.setListener(listener)
|
||||
serverApi.requestData(ctx, StellarRequest.Balance(coinData!!.wallet))
|
||||
serverApi.requestData(ctx, Ledgers())
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun requestFee(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, targetAddress: String, amount: Amount) {
|
||||
coinData!!.maxFee = coinData!!.baseFee
|
||||
coinData!!.normalFee = coinData!!.maxFee
|
||||
coinData!!.minFee = coinData!!.normalFee
|
||||
checkTargetAccountCreated(blockchainRequestsCallbacks, targetAddress, amount)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun requestSendTransaction(blockchainRequestsCallbacks: BlockchainRequestsCallbacks, txForSend: ByteArray) {
|
||||
val serverApi = ServerApiStellar(ctx.blockchain)
|
||||
val listener: ServerApiStellar.Listener = object : ServerApiStellar.Listener {
|
||||
override fun onSuccess(request: StellarRequest.Base) {
|
||||
try {
|
||||
if (!SubmitTransaction::class.java.isInstance(request)) throw Exception("Invalid request logic")
|
||||
val submitTransactionRequest = request as SubmitTransaction
|
||||
if (submitTransactionRequest.response.isSuccess) {
|
||||
ctx.error = null
|
||||
blockchainRequestsCallbacks.onComplete(true)
|
||||
} else {
|
||||
if (submitTransactionRequest.response.extras != null && submitTransactionRequest.response.extras.resultCodes != null) {
|
||||
var trResult = submitTransactionRequest.response.extras.resultCodes.transactionResultCode
|
||||
if (submitTransactionRequest.response.extras.resultCodes.operationsResultCodes != null && submitTransactionRequest.response.extras.resultCodes.operationsResultCodes.size > 0) {
|
||||
trResult += "/" + submitTransactionRequest.response.extras.resultCodes.operationsResultCodes[0]
|
||||
}
|
||||
ctx.error = trResult
|
||||
} else {
|
||||
ctx.error = "transaction failed"
|
||||
}
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e.message != null) {
|
||||
ctx.error = e.message
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
} else {
|
||||
ctx.error = e.javaClass.name
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFail(request: StellarRequest.Base) {
|
||||
ctx.error = request.error
|
||||
blockchainRequestsCallbacks.onComplete(false)
|
||||
}
|
||||
}
|
||||
serverApi.setListener(listener)
|
||||
val transaction = TransactionEx.fromEnvelopeXdr(String(txForSend))
|
||||
coinData!!.incSequenceNumber()
|
||||
serverApi.requestData(ctx, SubmitTransaction(transaction))
|
||||
}
|
||||
|
||||
override fun needMultipleLinesForBalance(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun allowSelectFeeLevel(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun pendingTransactionTimeoutInSeconds(): Int {
|
||||
return 10
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = XlmTagEngine::class.java.simpleName
|
||||
private val decimals: Int
|
||||
private get() = 7
|
||||
|
||||
private val multiplier = BigDecimal("10000000")
|
||||
|
||||
const val OPERATIONS_LIMIT = 50
|
||||
private val TRUSTED_DESTINATION = "GAYPZMHFZERB42ONEJ4CY6ADDVTINEXMY6OZ5G6CLR4HHVKOSNJSZGMM"
|
||||
private val TRUSTED_SOURCE = "GAZY7H4BWWEVB6QGB4RV3LW7DH5NO5CD5O6JCEQXA7N2UCGZSAPJFYW2"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -198,7 +198,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:fontFamily="@font/maax"
|
||||
android:text="The card is linked"
|
||||
android:text="@string/details_linked_card_title"
|
||||
android:textSize="@dimen/text_size_1_small" />
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
9
app/src/main/res/layout/fragment_tag.xml
Normal file
9
app/src/main/res/layout/fragment_tag.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.tangem.ui.fragment.additional.TagFragment">
|
||||
|
||||
<include layout="@layout/fr_loaded_wallet"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -28,6 +28,9 @@
|
|||
<action
|
||||
android:id="@+id/action_main_to_settingsFragment"
|
||||
app:destination="@id/settingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_main_to_tagFragment"
|
||||
app:destination="@id/tagFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
@ -188,5 +191,10 @@
|
|||
android:id="@+id/prepareKrakenWithdrawalFragment"
|
||||
android:name="com.tangem.ui.fragment.additional.PrepareKrakenWithdrawalFragment"
|
||||
android:label="PrepareKrakenWithdrawalFragment" />
|
||||
<fragment
|
||||
android:id="@+id/tagFragment"
|
||||
android:name="com.tangem.ui.fragment.additional.TagFragment"
|
||||
android:label="fragment_tag"
|
||||
tools:layout="@layout/fragment_tag" />
|
||||
|
||||
</navigation>
|
||||
|
|
@ -9,9 +9,9 @@
|
|||
<string name="general_continue">Continuer</string>
|
||||
<string name="error_empty_pin">Le code PIN est vide</string>
|
||||
<string name="general_blockchain">Blockchain</string>
|
||||
<string name="general_notification_scan_again">Réessayez de numériser</string>
|
||||
<string name="general_notification_scan_again">Réessayez de scanner la carte</string>
|
||||
<string name="general_error_cannot_erase_wallet_with_non_zero_balance">Impossible d\'effacer le porte-monnaie avec un solde différent de zéro</string>
|
||||
<string name="general_send_transaction">Envoyer le paiement</string>
|
||||
<string name="general_send_transaction">Transférer les fonds</string>
|
||||
<string name="general_from_card">Depuis la carte</string>
|
||||
<string name="general_on_card">sur la carte</string>
|
||||
<string name="general_balance">avec solde</string>
|
||||
|
|
@ -49,9 +49,9 @@
|
|||
<string name="dialog_the_nfc_adapter_length_apdu">Oups .. Il semble que votre smartphone ne supporte pas de tels paquets NFC.</string>
|
||||
<string name="dialog_the_nfc_adapter_length_apdu_advice">Essayez d\'envoyer une quantité inférieure ou utilisez un smartphone avec prise en charge complète de NFC.</string>
|
||||
<string name="dialog_title_money_is_at_risk">Votre argent est en jeu!</string>
|
||||
<string name="dialog_this_card_has_enforced_security_delay">Cette carte a imposé un délai de sécurité</string>
|
||||
<string name="dialog_hold_card">Veuillez tenir la carte fermement \ n jusqu\'à ce que l\'opération soit terminée…</string>
|
||||
<string name="dialog_you_may_be_required_to_repeat">Vous devrez peut-être répéter cette opération plusieurs fois en fonction des performances NFC de votre smartphone. \ n Ceci est fait pour sécuriser vos fonds.</string>
|
||||
<string name="dialog_this_card_has_enforced_security_delay">Cette carte impose un délai de sécurité pour confirmer le transfert des fonds</string>
|
||||
<string name="dialog_hold_card">Veuillez maintenir la carte dans cette position jusqu\'à ce que l\'opération soit terminée…</string>
|
||||
<string name="dialog_you_may_be_required_to_repeat">Vous devrez peut-être répéter cette opération plusieurs fois en fonction des performances NFC de votre smartphone.\nCeci est fait pour sécuriser vos fonds.</string>
|
||||
|
||||
<!-- menu main -->
|
||||
<string name="main_menu_manage_pin_1">Gérer le code PIN de l\'utilisateur…</string>
|
||||
|
|
@ -74,12 +74,14 @@
|
|||
|
||||
<!-- Main -->
|
||||
<string name="main_screen_tap_card">Mettez en contact la carte avec votre smartphone</string>
|
||||
<string name="main_screen_scan_card">Scanner une carte avec votre \n %1$s \n comme indiqué ci-dessus</string>
|
||||
<string name="main_screen_scan_card">Scanner votre carte avec votre %1$s comme indiqué ci-dessus</string>
|
||||
<string name="main_screen_phone">phone</string>
|
||||
<string name="main_screen_erased_wallet">Le porte-monnaie a été effacé</string>
|
||||
<string name="main_screen_not_personalized">Non personnalisé</string>
|
||||
<string name="main_screen_new_version_toast">Il existe une nouvelle version de l\'application: %1$s</string>
|
||||
<string name="main_screen_btn_update">Mettre à jour</string>
|
||||
<string name="main_screen_visit_store">Vous n’avez pas de carte?\nVisitez notre boutique sur %1$s</string>
|
||||
|
||||
|
||||
<!-- LoadedWallet -->
|
||||
<string name="loaded_wallet_no_compatible_wallet">Aucun porte-monnaie compatible installé</string>
|
||||
|
|
@ -146,10 +148,10 @@
|
|||
<string name="balance_validator_second_line_authenticity_not_verified">L\'authenticité ne peut pas être vérifiée. Balayez vers le bas pour actualiser.</string>
|
||||
|
||||
<!-- CreateNewWallet, Purge, SignTransaction -->
|
||||
<string name="now_touch_the_card_with_id"> Maintenant, touchez la carte avec l\'ID </string>
|
||||
<string name="now_touch_the_card_with_id">Maintenir la carte avec l\’ID</string>
|
||||
<string name="to_create_the_wallet">pour créer le porte-monnaie </string>
|
||||
<string name="to_erase_the_wallet">pour effacer le porte-monnaie</string>
|
||||
<string name="to_sign_the_transaction">pour signer le paiement</string>
|
||||
<string name="to_sign_the_transaction">pour confirmer le transfert des fonds</string>
|
||||
<string name="to_change_pin_codes">pour changer les codes PIN / PIN2</string>
|
||||
<string name="nfc_purge_warning">En mettant la carte en contact avec votre smartphone, vous retirerez définitivement le porte-monnaie de la blockchain. Assurez-vous qu\'il n\'y aura pas d\'autres transactions entrantes</string>
|
||||
<string name="nfc_error_cannot_erase_wallet">Impossible d\'effacer le porte-monnaie. Veillez à entrer le code PIN2 correct!</string>
|
||||
|
|
@ -178,7 +180,7 @@
|
|||
<!-- PrepareTransaction -->
|
||||
<string name="prepare_transaction_hint_enter_address">entrez l\'adresse du porte-monnaie</string>
|
||||
<string name="prepare_transaction_hint_enter_amount">entrer le montant</string>
|
||||
<string name="prepare_transaction_btn_verify">Vérifiez</string>
|
||||
<string name="prepare_transaction_btn_verify">Vérifier</string>
|
||||
<string name="prepare_transaction_error_not_enough_funds">Pas assez de fonds</string>
|
||||
<string name="prepare_transaction_error_unknown_amount_format">Format de montant inconnu</string>
|
||||
<string name="prepare_transaction_error_incorrect_destination">Adresse du porte-monnaie de destination incorrecte</string>
|
||||
|
|
@ -223,12 +225,12 @@
|
|||
<string name="details_category_manufacturer">Fabricant</string>
|
||||
<string name="details_category_wallet">Porte-monnaie</string>
|
||||
<string name="details_card_identity">Identité de la carte</string>
|
||||
<string name="details_attested">Attested</string>
|
||||
<string name="details_attested">Attestée</string>
|
||||
<string name="details_not_confirmed">Non confirmé</string>
|
||||
<string name="details_reusable">Réutilisable</string>
|
||||
<string name="details_none">None</string>
|
||||
<string name="details_last_one">Le dernier!</string>
|
||||
<string name="details_unlimited">Unlimited</string>
|
||||
<string name="details_unlimited">illimitées</string>
|
||||
<string name="details_one_off_card">Carte ponctuelle</string>
|
||||
<string name="details_firmware">Micrologiciel</string>
|
||||
<string name="details_registration_date">Date d\'enregistrement</string>
|
||||
|
|
@ -238,7 +240,7 @@
|
|||
<string name="details_category_issuer">Émetteur</string>
|
||||
<string name="details_private_key">Clé privée</string>
|
||||
<string name="details_validation_node">Noeud de validation</string>
|
||||
<string name="details_unspents">Unspents</string>
|
||||
<string name="details_unspents">Transferts</string>
|
||||
<string name="details_protected_by_default_pin_1">Cette carte est protégée par le code PIN1 par défaut</string>
|
||||
<string name="details_protected_by_user_pin_1">Cette carte est protégée par le code PIN1 de l’utilisateur</string>
|
||||
<string name="details_protected_by_default_pin_2">Cette carte est protégée par le code PIN2 par défaut</string>
|
||||
|
|
@ -255,8 +257,9 @@
|
|||
<string name="details_blockable">Bloquable\n</string>
|
||||
<string name="details_atomic_commmands">Mode de commande atomique\n</string>
|
||||
<string name="details_linking_card_supported">La liaison au terminal est prise en charge\n</string>
|
||||
<string name="details_linked_card_title">La carte est liée</string>
|
||||
<string name="details_linked_card_title">La carte est associée</string>
|
||||
<string name="details_linked_card_to_phone">à ce téléphone</string>
|
||||
<string name="details_unspents_number">%1$d envoyé(s), %2$d reçu(s)</string>
|
||||
|
||||
<!-- PinSave -->
|
||||
<string name="pin_save_btn_save">Enregistrer</string>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
<string name="main_screen_new_version_toast">There is a new application version: %1$s</string>
|
||||
<string name="main_screen_btn_update">Update</string>
|
||||
<string name="main_screen_visit_store">Don\'t have a card?\n Visit our store at %1$s</string>
|
||||
<string name="main_screen_store_address">tangemcards.com</string>
|
||||
<string name="main_screen_store_address" translatable="false">tangemcards.com</string>
|
||||
|
||||
<!-- LoadedWallet -->
|
||||
<string name="loaded_wallet_no_compatible_wallet">No compatible wallets installed</string>
|
||||
|
|
@ -262,6 +262,13 @@
|
|||
<string name="details_linking_card_supported">Linking to the terminal is supported\n</string>
|
||||
<string name="details_linked_card_title">The card is linked</string>
|
||||
<string name="details_linked_card_to_phone">To this phone</string>
|
||||
<string name="details_unspents_number">%1$d unspents (%2$d received)</string>
|
||||
|
||||
<!-- TagFragment -->
|
||||
<string name="tag_claim">Claim</string>
|
||||
<string name="tag_genuine">GENUINE</string>
|
||||
<string name="tag_not_genuine">NOT GENUINE</string>
|
||||
<string name="tag_claimed">ALREADY CLAIMED</string>
|
||||
|
||||
<!-- PinSave -->
|
||||
<string name="pin_save_btn_save">Save</string>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<tech-list>
|
||||
<tech>android.nfc.tech.IsoDep</tech>
|
||||
<tech>android.nfc.tech.Ndef</tech>
|
||||
<tech>android.nfc.tech.NfcV</tech>
|
||||
</tech-list>
|
||||
</resources>
|
||||
|
|
@ -29,6 +29,7 @@ public class TangemCard {
|
|||
private byte[] terminalPrivateKey;
|
||||
private byte[] terminalPublicKey;
|
||||
private boolean terminalIsLinked = false;
|
||||
private byte[] tagSignature = null;
|
||||
|
||||
public String getBlockchainID() {
|
||||
return blockchainID;
|
||||
|
|
@ -629,6 +630,14 @@ public class TangemCard {
|
|||
DenominationText = null;
|
||||
}
|
||||
|
||||
public byte[] getTagSignature() {
|
||||
return tagSignature;
|
||||
}
|
||||
|
||||
public void setTagSignature(byte[] tagSignature) {
|
||||
this.tagSignature = tagSignature;
|
||||
}
|
||||
|
||||
// public Bundle getAsBundle() {
|
||||
// Bundle B = new Bundle();
|
||||
// saveToBundle(B);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ class NfcManager(private val activity: FragmentActivity, private val readerCallb
|
|||
val TAG: String = NfcManager::class.java.simpleName
|
||||
|
||||
// reader mode flags: listen for type A (not B), skipping ndef check
|
||||
private const val READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK or NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
|
||||
private const val READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_NFC_V or
|
||||
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
|
||||
private const val DELAY_PRESENCE = 1500
|
||||
|
||||
private const val REQUEST_NFC_PERMISSIONS = 1
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ fun TangemCard.loadFromBundle(B: Bundle) {
|
|||
if (B.containsKey("terminalPublicKey")) terminalPublicKey = B.getByteArray("terminalPublicKey")
|
||||
terminalIsLinked = B.getBoolean("terminalIsLinked")
|
||||
|
||||
if (B.containsKey("tagSignature")) tagSignature = B.getByteArray("tagSignature")
|
||||
|
||||
}
|
||||
|
||||
val TangemCard.asBundle: Bundle
|
||||
|
|
@ -112,11 +114,10 @@ fun TangemCard.saveToBundle(B: Bundle) {
|
|||
B.putInt("Health", health)
|
||||
if (settingsMask != null) B.putInt("settingsMask", settingsMask)
|
||||
B.putInt("pauseBeforePIN2", pauseBeforePIN2)
|
||||
if( allowedSigningMethod!=null ){
|
||||
var iSigningMethod=0x80
|
||||
for(sM in allowedSigningMethod)
|
||||
{
|
||||
iSigningMethod=iSigningMethod.or(0x01.shl(sM.ID))
|
||||
if (allowedSigningMethod != null) {
|
||||
var iSigningMethod = 0x80
|
||||
for (sM in allowedSigningMethod) {
|
||||
iSigningMethod = iSigningMethod.or(0x01.shl(sM.ID))
|
||||
}
|
||||
B.putInt("signingMethod", iSigningMethod)
|
||||
}
|
||||
|
|
@ -163,6 +164,8 @@ fun TangemCard.saveToBundle(B: Bundle) {
|
|||
B.putByteArray("terminalPublicKey", terminalPublicKey)
|
||||
B.putBoolean("terminalIsLinked", terminalIsLinked)
|
||||
|
||||
if (tagSignature != null) B.putByteArray("tagSignature", tagSignature)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("Can't save to bundle ", e.message)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue