Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-06 17:29:21 +03:00
commit 86f20cb1b9
23 changed files with 24230 additions and 36 deletions

View file

@ -138,7 +138,7 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
//dependencies for binance
implementation 'com.google.protobuf:protobuf-java:3.6.1'
implementation 'com.google.protobuf:protobuf-java:3.11.4'
implementation 'io.grpc:grpc-protobuf:1.17.1'
implementation 'io.grpc:grpc-stub:1.17.1'
implementation 'commons-codec:commons-codec:1.10'
@ -154,4 +154,8 @@ dependencies {
implementation 'io.fotoapparat:fotoapparat:2.6.1'
implementation 'com.otaliastudios:cameraview:2.6.1'
//dependencies for flow demo
implementation 'io.grpc:grpc-okhttp:1.28.0'
implementation 'io.grpc:grpc-stub:1.28.0'
}

View file

@ -31,7 +31,8 @@ public enum Blockchain {
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"),
Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.ic_logo_tezos, "Tezos");
Tezos("TEZOS", "XTZ", 10000000.0, R.drawable.ic_logo_tezos, "Tezos"),
FlowDemo("FLOW/demo", "", 1.0, R.drawable.tangem2, "Flow demo");
Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) {
mID = ID;

View file

@ -58,11 +58,17 @@ abstract class BaseFragment : Fragment() {
Log.d("LIFECYCLE", "onDestroy: ${this::class.java.simpleName}")
}
protected fun navigateUp(@IdRes destination: Int = DESTINATION_NOT_SET): Boolean {
return if (destination == DESTINATION_NOT_SET) {
findNavController(this).popBackStack()
} else {
findNavController(this).popBackStack(destination, false)
protected fun navigateUp(@IdRes destination: Int = DESTINATION_NOT_SET) {
try {
if (destination == DESTINATION_NOT_SET) {
findNavController(this).popBackStack()
} else {
findNavController(this).popBackStack(destination, false)
}
} catch (e: IllegalArgumentException) {
Log.w(this::class.java.simpleName, e.message)
} catch (e: IllegalStateException) {
Log.w(this::class.java.simpleName, e.message)
}
}
@ -84,7 +90,7 @@ abstract class BaseFragment : Fragment() {
}
protected fun navigateBackWithResult(resultCode: Int, data: Bundle? = null,
@IdRes destination: Int = DESTINATION_NOT_SET): Boolean {
@IdRes destination: Int = DESTINATION_NOT_SET) {
(requireActivity() as MainActivity).viewModel.navigationResult =
NavigationResult(requestCode, resultCode, data)
return navigateUp(destination)

View file

@ -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.crashlytics.FirebaseCrashlytics
import com.tangem.App
@ -89,11 +88,6 @@ class ValidateIdFragment : BaseFragment(), NavigationResultListener,
super.onPause()
}
override fun onStop() {
signTransactionTask?.cancel(true)
super.onStop()
}
override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) {
navigateBackWithResult(resultCode, data)
@ -262,7 +256,10 @@ class ValidateIdFragment : 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)

View file

@ -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.crashlytics.FirebaseCrashlytics
import com.tangem.App
@ -49,7 +48,6 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener,
private lateinit var ctx: TangemContext
private lateinit var tx: ByteArray
private lateinit var mpFinishSignSound: MediaPlayer
private var toast: Toast? = null
private var idWasWritten = false
@ -94,7 +92,6 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener,
override fun onStop() {
writeIDCardTask?.cancel(true)
toast?.cancel()
super.onStop()
}
@ -174,7 +171,6 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener,
if (success) {
navigateUp(R.id.main)
} else {
toast?.cancel()
navigateUp(R.id.issueNewIdFragment)
}
}
@ -231,8 +227,10 @@ class WriteIdFragment : BaseFragment(), NavigationResultListener,
}
} else {
if (!idWasWritten) {
toast = Toast.makeText(context, R.string.general_notification_scan_again, Toast.LENGTH_SHORT)
toast?.show()
(activity as MainActivity).toastHelper.showSingleToast(
context,
getString(R.string.general_notification_scan_again)
)
}
}
progressBar?.progress = 100

View file

@ -12,6 +12,7 @@ import com.tangem.wallet.ducatus.DucatusEngine
import com.tangem.wallet.eos.EosEngine
import com.tangem.wallet.eth.EthEngine
import com.tangem.wallet.ethID.EthIdEngine
import com.tangem.wallet.flowDemo.FlowDemoEngine
import com.tangem.wallet.ltc.LtcEngine
import com.tangem.wallet.matic.MaticTokenEngine
import com.tangem.wallet.nftToken.NftTokenEngine
@ -58,6 +59,7 @@ object CoinEngineFactory {
Blockchain.Ducatus -> DucatusEngine()
Blockchain.Tezos -> TezosEngine()
Blockchain.BitcoinDual -> BtcMultisigEngine()
Blockchain.FlowDemo -> FlowDemoEngine()
else -> null
}
}
@ -105,6 +107,8 @@ object CoinEngineFactory {
TezosEngine(context)
else if (Blockchain.BitcoinDual == context.blockchain)
BtcMultisigEngine(context)
else if (Blockchain.FlowDemo == context.blockchain)
FlowDemoEngine(context)
else
return null
} catch (e: Exception) {

View file

@ -0,0 +1,29 @@
package com.tangem.wallet.flowDemo
import android.os.Bundle
import android.util.Log
import com.tangem.wallet.CoinData
import java.lang.Exception
class FlowDemoData : CoinData() {
var description: String? = null
override fun clearInfo() {
super.clearInfo()
description = null
}
override fun loadFromBundle(B: Bundle) {
super.loadFromBundle(B)
if (B.containsKey("Description")) description = B.getString("Description")
}
override fun saveToBundle(B: Bundle) {
super.saveToBundle(B)
try {
if (description != null) B.putString("Description", description!!)
} catch (e: Exception) {
Log.e("Can't save to bundle ", e.message)
}
}
}

View file

@ -0,0 +1,250 @@
package com.tangem.wallet.flowDemo
import android.net.Uri
import android.text.InputFilter
import com.google.protobuf.ByteString
import com.tangem.tangem_card.reader.CardProtocol.TangemException
import com.tangem.tangem_card.tasks.SignTask
import com.tangem.tangem_card.util.Util
import com.tangem.wallet.*
import com.tangem.wallet.binance.client.domain.Account
import com.tangem.wallet.flowDemo.proto.Observation
import com.tangem.wallet.flowDemo.proto.ObserveServiceGrpc
import io.grpc.ManagedChannelBuilder
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Function
import io.reactivex.observers.DisposableSingleObserver
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit
class FlowDemoEngine : CoinEngine {
constructor()
constructor(context: TangemContext) : super(context) {
if (context.coinData == null) {
coinData = FlowDemoData()
context.coinData = coinData
} else if (context.coinData is FlowDemoData) {
coinData = context.coinData as FlowDemoData
} else {
throw Exception("Invalid type of Blockchain data for XlmEngine")
}
}
private val TAG = FlowDemoEngine::class.java.simpleName
var coinData: FlowDemoData? = null
override fun checkNewTransactionAmount(amount: Amount?): Boolean {
return true
}
override fun requestBalanceAndUnspentTransactions(blockchainRequestsCallbacks: BlockchainRequestsCallbacks?) {
val channel = ManagedChannelBuilder.forAddress("ec2-3-134-137-165.us-east-2.compute.amazonaws.com", 3569).usePlaintext().build();
val flowApi = ObserveServiceGrpc.newBlockingStub(channel)
val script = ByteString.copyFrom(
"""import TangemTest from 0x02
pub fun main(): String? {
let contract = getAccount(0x02)
let collectionRef = contract.published[&TangemTest.TagCollection] ?? panic("missing reference!")
let desc = collectionRef.tags["%s"]?.desc
return desc
}""".format(coinData!!.wallet).toByteArray()
)
val scriptRequest = Observation.ExecuteScriptRequest.newBuilder().setScript(script).build()
val scriptResponseObserver = object : DisposableSingleObserver<Observation.ExecuteScriptResponse>() {
override fun onSuccess(response: Observation.ExecuteScriptResponse) {
coinData!!.isBalanceReceived = true
val byteResult = response.value.toByteArray()
if (byteResult.size > 8) {
coinData!!.description = String(byteResult.copyOfRange(8, byteResult.size)).toUpperCase()
}
blockchainRequestsCallbacks!!.onComplete(true)
}
override fun onError(e: Throwable) {
ctx.error = e.message
blockchainRequestsCallbacks!!.onComplete(false)
}
}
try {
Single.just(Observation.ExecuteScriptResponse.getDefaultInstance())
.map { flowApi.executeScript(scriptRequest) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(10, TimeUnit.SECONDS)
.subscribe(scriptResponseObserver)
} catch (e: Exception) {
ctx.error = e.message
blockchainRequestsCallbacks!!.onComplete(false)
}
}
override fun getBalance(): Amount {
return Amount()
}
override fun isNeedCheckNode(): Boolean {
return false
}
override fun getAmountInputFilters(): Array<InputFilter> {
TODO("Not yet implemented")
}
override fun getWalletExplorerUri(): Uri {
return Uri.parse("https://www.onflow.org/")
}
override fun getBalanceHTML(): String {
val balanceString = if(hasBalanceInfo()) {
if(coinData!!.description != null) {
coinData!!.description!! + " TANGEM CARD"
} else {
"UNKNOWN CARD"
}
} else {
""
}
if (coinData!!.description == "COMMON") {
balanceString == "$balanceString<br><small><small>your usual tangem card</small></small>"
} else if (coinData!!.description == "RARE") {
balanceString == "$balanceString<br><small><small>wow, this one is RARE</small></small>"
}
return balanceString
}
override fun awaitingConfirmation(): Boolean {
return false
}
override fun getFeeCurrency(): String {
return ""
}
override fun convertToByteArray(internalAmount: InternalAmount?): ByteArray {
TODO("Not yet implemented")
}
override fun requestSendTransaction(blockchainRequestsCallbacks: BlockchainRequestsCallbacks?, txForSend: ByteArray?) {
TODO("Not yet implemented")
}
override fun hasBalanceInfo(): Boolean {
return coinData!!.isBalanceReceived
}
override fun getBalanceEquivalent(): String {
return ""
}
// public String getOfflineBalanceHTML() {
// InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
// Amount offlineAmount = convertToAmount(offlineInternalAmount);
// return offlineAmount.toDescriptionString(getDecimals());
// }
@Throws(TangemException::class)
override fun defineWallet() {
try {
val wallet = calculateAddress(ctx.card.walletPublicKeyRar)
ctx.coinData.wallet = wallet
} catch (e: java.lang.Exception) {
ctx.coinData.wallet = "ERROR"
throw TangemException("Can't define wallet address")
}
}
override fun calculateAddress(pkUncompressed: ByteArray?): String {
return Util.byteArrayToHexString(pkUncompressed)
}
override fun getShareWalletUri(): Uri {
return Uri.parse(ctx.coinData.wallet)
}
override fun getUnspentInputsDescription(): String {
return ""
}
override fun checkNewTransactionAmountAndFee(amount: Amount?, fee: Amount?, isFeeIncluded: Boolean?): Boolean {
TODO("Not yet implemented")
}
override fun isBalanceNotZero(): Boolean {
return true
}
override fun getBalanceCurrency(): String {
return ""
}
override fun evaluateFeeEquivalent(fee: String?): String {
return ""
}
override fun convertToAmount(internalAmount: InternalAmount?): Amount {
TODO("Not yet implemented")
}
override fun convertToAmount(strAmount: String?, currency: String?): Amount {
TODO("Not yet implemented")
}
override fun isExtractPossible(): Boolean {
return false
}
override fun convertToInternalAmount(amount: Amount?): InternalAmount {
TODO("Not yet implemented")
}
override fun convertToInternalAmount(bytes: ByteArray?): InternalAmount {
TODO("Not yet implemented")
}
override fun requestFee(blockchainRequestsCallbacks: BlockchainRequestsCallbacks?, targetAddress: String?, amount: Amount?) {
TODO("Not yet implemented")
}
override fun validateAddress(address: String?): Boolean {
TODO("Not yet implemented")
}
override fun constructTransaction(amountValue: Amount?, feeValue: Amount?, IncFee: Boolean, targetAddress: String?): SignTask.TransactionToSign {
TODO("Not yet implemented")
}
override fun validateBalance(balanceValidator: BalanceValidator?): Boolean {
if (hasBalanceInfo()) {
if (coinData!!.description != null) {
balanceValidator!!.setScore(100)
balanceValidator.firstLine = R.string.balance_validator_first_line_verified_in_blockchain
balanceValidator.setSecondLine(R.string.empty_string)
} else {
balanceValidator!!.setScore(0)
balanceValidator.firstLine = R.string.balance_validator_first_line_authenticity
balanceValidator.setSecondLine(R.string.empty_string)
}
} else {
balanceValidator!!.setScore(0)
balanceValidator.firstLine = R.string.balance_validator_first_line_no_connection
balanceValidator.setSecondLine(R.string.balance_validator_second_line_authenticity_not_verified)
return false
}
return true
}
override fun createCoinData(): CoinData {
return FlowDemoData()
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -228,6 +228,12 @@ class LocalStorage
fun getCardArtworkBitmap(card: TangemCard): Bitmap {
// special cases (first series of cards, hardcode CID->artwork), on new series batch<->artwork
if (card.isIDIssuer) {
val artworkId = context.resources.getResourceEntryName(R.drawable.card_id_issuer)
return getArtworkBitmap(artworkId) ?: return getDefaultArtworkBitmap(card)
}
val hexCID = Util.bytesToHex(card.cid)
val artworkResourceId: Int? = when {
hexCID in "AA01000000000000".."AA01000000004999" -> R.drawable.card_ru006

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -634,6 +634,15 @@ public class TangemCard {
}
}
public boolean isIDIssuer() {
try {
return (productMask & ProductMask.IdIssuer) != 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public TLVList tlvIDCardData = null;
public void setTlvIdCardData(TLVList tlvIDCardData) {

View file

@ -8,6 +8,7 @@ public class ProductMask {
public static final int Note = 0x0001;
public static final int Tag = 0x0002;
public static final int IdCard = 0x0004;
public static final int IdIssuer = 0x0008;
public static String getDescription(int iValue) {
StringBuilder sb = new StringBuilder();
@ -17,6 +18,8 @@ public class ProductMask {
sb.append("Tag, ");
if ((iValue & ProductMask.IdCard) != 0)
sb.append("IdCard, ");
if ((iValue & ProductMask.IdIssuer) != 0)
sb.append("IdIssuer, ");
if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
sb.append("]");

View file

@ -119,7 +119,7 @@ data class ProductMask(val rawValue: Int) {
const val note = 0x01
const val tag = 0x02
const val idCard = 0x04
const val idIssuer = 0x0008
const val idIssuer = 0x08
}
}

View file

@ -87,8 +87,8 @@ class PersonalizeResources() {
private fun initProductMask(holder: TypedHolder<Id, Resources>) {
holder.register(ProductMask.Note, Resources(R.string.pers_item_note, R.string.info_pers_item_note))
holder.register(ProductMask.Tag, Resources(R.string.pers_item_tag, R.string.info_pers_item_tag))
holder.register(ProductMask.CardId, Resources(R.string.pers_item_id_card, R.string.info_pers_item_id_card))
holder.register(ProductMask.IssuerId, Resources(R.string.pers_item_id_issuer, R.string.info_pers_item_id_issuer))
holder.register(ProductMask.IdCard, Resources(R.string.pers_item_id_card, R.string.info_pers_item_id_card))
holder.register(ProductMask.IdIssuerCard, Resources(R.string.pers_item_id_issuer_card, R.string.info_pers_item_id_issuer_card))
}
private fun initSettingsMask(holder: TypedHolder<Id, Resources>) {

View file

@ -71,8 +71,8 @@ enum class Token : BlockItem {
enum class ProductMask : BlockItem {
Note,
Tag,
CardId,
IssuerId
IdCard,
IdIssuerCard
}
enum class SettingsMask : BlockItem {

View file

@ -42,8 +42,8 @@ class PersonalizeConfigValuesHolder : BaseTypedHolder<Id, Value>() {
register(Token.Decimal, Value(default.decimal))
register(ProductMask.Note, Value(default.cardData.product_note))
register(ProductMask.Tag, Value(default.cardData.product_tag))
register(ProductMask.CardId, Value(default.cardData.product_id_card))
register(ProductMask.IssuerId, Value(default.cardData.product_id_issuer))
register(ProductMask.IdCard, Value(default.cardData.product_id_card))
register(ProductMask.IdIssuerCard, Value(default.cardData.product_id_issuer))
register(SettingsMask.IsReusable, Value(default.isReusable))
register(SettingsMask.NeedActivation, Value(default.useActivation))
register(SettingsMask.ForbidPurge, Value(default.forbidPurgeWallet))

View file

@ -17,7 +17,7 @@ class ItemTypes {
SigningMethod.SignValidatedTxRaw, SigningMethod.SignValidatedTxIssuer,
SigningMethod.SignValidatedTxRawIssuer, SigningMethod.SignExternal, SignHashExProp.RequireTerminalCertSig,
SignHashExProp.RequireTerminalTxSig, SignHashExProp.CheckPin3, Denomination.WriteOnPersonalize, Token.ItsToken,
ProductMask.Note, ProductMask.Tag, ProductMask.CardId, ProductMask.IssuerId, SettingsMask.IsReusable, SettingsMask.NeedActivation,
ProductMask.Note, ProductMask.Tag, ProductMask.IdCard, ProductMask.IdIssuerCard, SettingsMask.IsReusable, SettingsMask.NeedActivation,
SettingsMask.ForbidPurge, SettingsMask.AllowSelectBlockchain, SettingsMask.UseBlock, SettingsMask.OneApdu,
SettingsMask.UseCvc, SettingsMask.AllowSwapPin, SettingsMask.AllowSwapPin2, SettingsMask.ForbidDefaultPin,
SettingsMask.SmartSecurityDelay, SettingsMask.ProtectIssuerDataAgainstReplay,

View file

@ -67,8 +67,8 @@ class ItemsToPersonalizeConfig : ItemsToModel<PersonalizeConfig> {
export.decimal = getTyped(Token.Decimal)
export.cardData = export.cardData.apply { this.product_note = getTyped(ProductMask.Note) }
export.cardData = export.cardData.apply { this.product_tag = getTyped(ProductMask.Tag) }
export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMask.CardId) }
export.cardData = export.cardData.apply { this.product_id_issuer = getTyped(ProductMask.IssuerId) }
export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMask.IdCard) }
export.cardData = export.cardData.apply { this.product_id_issuer = getTyped(ProductMask.IdIssuerCard) }
export.isReusable = getTyped(SettingsMask.IsReusable)
export.useActivation = getTyped(SettingsMask.NeedActivation)
export.forbidPurgeWallet = getTyped(SettingsMask.ForbidPurge)

View file

@ -121,8 +121,8 @@ class PersonalizeConfigToItem : ModelToItems<PersonalizeConfig> {
mutableListOf(
ProductMask.Note,
ProductMask.Tag,
ProductMask.CardId,
ProductMask.IssuerId
ProductMask.IdCard,
ProductMask.IdIssuerCard
).forEach { createItem(block, it) }
return block
}
@ -217,7 +217,7 @@ class PersonalizeConfigToCardConfig : Converter<PersonalizeConfig, CardConfig> {
val isNote = from.cardData.product_note
val isTag = from.cardData.product_tag
val isIdCard = from.cardData.product_id_card
val isIdIssuer = from.cardData.product_id_card
val isIdIssuer = from.cardData.product_id_issuer
val productMaskBuilder = ProductMaskBuilder()
if (isNote) productMaskBuilder.add(com.tangem.commands.ProductMask.note)

View file

@ -44,7 +44,7 @@
<string name="pers_item_note">Note</string>
<string name="pers_item_tag">Tag</string>
<string name="pers_item_id_card">ID card</string>
<string name="pers_item_id_issuer">ID Issuer</string>
<string name="pers_item_id_issuer_card">ID Issuer</string>
<string name="pers_item_is_reusable">Is reusable</string>
<string name="pers_item_need_activation">Need activation</string>
<string name="pers_item_forbid_purge">Forbid purge</string>
@ -117,7 +117,7 @@
<string name="info_pers_item_note">Self-custodial wallet or note</string>
<string name="info_pers_item_tag">Anti-counterfeit tag</string>
<string name="info_pers_item_id_card">Personal ID card</string>
<string name="info_pers_item_id_issuer"></string>
<string name="info_pers_item_id_issuer_card"></string>
<string name="info_pers_item_is_reusable">Defines what happens when user calls PURGE_WALLET command:
\nOff - Card will switch to Purged state
\nOn - Card will switch to Empty state and let create a new wallet again