From a6141bbbe541f6aefd543a0820409e89bf7f0522 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 2 Sep 2020 12:52:38 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle | 4 +-- .../com/tangem/tap/domain/PayIdManager.kt | 20 +++++++------ .../com/tangem/tap/domain/TangemSdkManager.kt | 13 +++++++-- .../com/tangem/tap/domain/TapWalletManager.kt | 19 +++++++++++-- .../domain/tasks/CreateWalletAndRescanTask.kt | 28 +++++++++++++++++++ .../tangem/tap/domain/tasks/ScanNoteTask.kt | 20 ++++++++----- .../tap/features/home/redux/HomeMiddleware.kt | 8 +----- .../tap/features/wallet/redux/WalletAction.kt | 1 + .../features/wallet/redux/WalletMiddleware.kt | 20 +++++++------ .../features/wallet/redux/WalletReducer.kt | 17 +++++++++-- .../tap/features/wallet/redux/WalletState.kt | 11 ++++++-- .../tap/features/wallet/ui/BalanceWidget.kt | 18 ++++++++++-- .../tap/features/wallet/ui/WalletFragment.kt | 26 +++++++++++++++-- .../coinmarketcap/CoinMarketCapService.kt | 6 ++-- app/src/main/res/layout/fragment_home.xml | 3 +- app/src/main/res/layout/fragment_wallet.xml | 2 +- .../main/res/layout/layout_balance_error.xml | 15 +++++----- app/src/main/res/values/strings.xml | 3 ++ 18 files changed, 175 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt diff --git a/app/build.gradle b/app/build.gradle index 04a330ca2b..6dba2b1d60 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,11 +60,11 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin" implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'com.google.android.material:material:1.2.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' - implementation 'com.tangem:blockchain:1.25.0' + implementation 'com.tangem:blockchain:1.27.0' //lifecycle implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt index 50cb79aca2..4a65bb702f 100644 --- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt @@ -4,40 +4,44 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.commands.common.network.Result import com.tangem.tap.network.payid.PayIdService import com.tangem.tap.network.payid.SetPayIdResponse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import retrofit2.HttpException import java.util.* class PayIdManager { private val payIdService = PayIdService() - suspend fun getPayId(cardId: String, publicKey: String): Result { + suspend fun getPayId(cardId: String, publicKey: String): Result = withContext(Dispatchers.IO) { val result = payIdService.getPayId(cardId, publicKey) when (result) { - is Result.Success -> return Result.Success(result.data.payId) + is Result.Success -> return@withContext Result.Success(result.data.payId) is Result.Failure -> { (result.error as? HttpException)?.let { - if (it.code() == 404) return Result.Success(null) + if (it.code() == 404) return@withContext Result.Success(null) } - return result + return@withContext result } } } + suspend fun setPayId( cardId: String, publicKey: String, payId: String, address: String, blockchain: Blockchain - ): Result { + ): Result = withContext(Dispatchers.IO) { val result = payIdService.setPayId(cardId, publicKey, payId, address, blockchain.getPayIdNetwork()) when (result) { - is Result.Success -> return result + is Result.Success -> return@withContext result is Result.Failure -> { (result.error as? HttpException)?.let { - if (it.code() == 409) return Result.Failure(TapError.PayIdAlreadyCreated) + if (it.code() == 409) return@withContext Result.Failure(TapError.PayIdAlreadyCreated) } - return result + return@withContext result } } } + private fun Blockchain.getPayIdNetwork(): String { return when (this) { Blockchain.XRP -> "XRPL" diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 370261ce46..9fdfca3cd7 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -6,6 +6,7 @@ import com.tangem.commands.CommandResponse import com.tangem.common.CompletionResult import com.tangem.common.extensions.CardType import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask import kotlinx.coroutines.Dispatchers @@ -23,12 +24,18 @@ class TangemSdkManager(val activity: ComponentActivity) { return runTaskAsyncReturnOnMain(ScanNoteTask()) } + suspend fun createWallet(): CompletionResult { + return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask()) + } + private suspend fun runTaskAsync( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null ): CompletionResult = - suspendCoroutine { continuation -> - tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> - continuation.resume(result) + withContext(Dispatchers.IO) { + suspendCoroutine { continuation -> + tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result -> + continuation.resume(result) + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index e7f1af6844..042f947b77 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -6,6 +6,7 @@ import com.tangem.commands.common.network.Result import com.tangem.common.extensions.toHexString import com.tangem.tap.TapConfig import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.features.wallet.redux.PayIdState import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.coinmarketcap.CoinMarketCapService @@ -47,12 +48,26 @@ class TapWalletManager { handleFiatRatesResult(results) } + suspend fun onCardScanned(data: ScanNoteResponse) { + withContext(Dispatchers.Main) { + store.dispatch(GlobalAction.LoadCard(data.card)) + if (data.walletManager != null) { + store.dispatch(GlobalAction.LoadWalletManager(data.walletManager)) + store.dispatch(WalletAction.LoadWallet) + store.dispatch(WalletAction.LoadFiatRate) + store.dispatch(WalletAction.LoadPayId) + } else { + store.dispatch(WalletAction.EmptyWallet) + } + } + } + private suspend fun updateWallet(walletManager: WalletManager) { val result = try { walletManager.update() Result.Success(walletManager.wallet) - } catch (exeption: Exception) { - Result.Failure(exeption) + } catch (exception: Exception) { + Result.Failure(exception) } handleUpdateWalletResult(result) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt new file mode 100644 index 0000000000..8bd2f97c6a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/CreateWalletAndRescanTask.kt @@ -0,0 +1,28 @@ +package com.tangem.tap.domain.tasks + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.commands.ReadCommand +import com.tangem.common.CompletionResult +import com.tangem.tasks.CreateWalletTask + +class CreateWalletAndRescanTask : CardSessionRunnable { + override val requiresPin2 = false + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + CreateWalletTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> ReadCommand().run(session) { readResult -> + when (readResult) { + is CompletionResult.Success -> { + callback(readResult.data.toScanNoteCompletionResult()) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error)) + } + } + + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt index 2d57a07a75..6023e854f4 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -6,11 +6,12 @@ import com.tangem.TangemSdkError import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.commands.Card +import com.tangem.commands.CardStatus import com.tangem.commands.CommandResponse import com.tangem.common.CompletionResult data class ScanNoteResponse( - val walletManager: WalletManager, + val walletManager: WalletManager?, val card: Card ) : CommandResponse @@ -19,11 +20,16 @@ class ScanNoteTask : CardSessionRunnable { override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val card = session.environment.card - val walletManager = card?.let { WalletManagerFactory.makeWalletManager(it) } - if (card == null || walletManager == null) { - callback(CompletionResult.Failure(TangemSdkError.CardError())) - return - } - callback(CompletionResult.Success(ScanNoteResponse(walletManager, card))) + callback(card.toScanNoteCompletionResult()) } +} + +fun Card?.toScanNoteCompletionResult(): CompletionResult { + this ?: return CompletionResult.Failure(TangemSdkError.CardError()) + if (this.status == CardStatus.Empty) { + return CompletionResult.Success(ScanNoteResponse(null, this)) + } + val walletManager = WalletManagerFactory.makeWalletManager(this) + ?: return CompletionResult.Failure(TangemSdkError.CardError()) + return CompletionResult.Success(ScanNoteResponse(walletManager, this)) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 8904e3c22a..6a25e7251d 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -5,10 +5,8 @@ import android.net.Uri import androidx.core.content.ContextCompat.startActivity import com.tangem.common.CompletionResult import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -27,11 +25,7 @@ val homeMiddleware: Middleware = { dispatch, state -> withContext(Dispatchers.Main) { when (result) { is CompletionResult.Success -> { - store.dispatch(GlobalAction.LoadCard(result.data.card)) - store.dispatch(GlobalAction.LoadWalletManager(result.data.walletManager)) - store.dispatch(WalletAction.LoadWallet) - store.dispatch(WalletAction.LoadFiatRate) - store.dispatch(WalletAction.LoadPayId) + store.state.globalState.tapWalletManager.onCardScanned(result.data) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 88630c1f31..e047de3201 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -43,4 +43,5 @@ sealed class WalletAction : Action { object HideQrCode : WalletAction() data class ExploreAddress(val context: Context) : WalletAction() object CreateWallet : WalletAction() + object EmptyWallet : WalletAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index 3d33c6b43d..be727c7415 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -8,7 +8,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapError import com.tangem.tap.scope @@ -39,6 +38,17 @@ val walletMiddleware: Middleware = { dispatch, state -> store.state.globalState.tapWalletManager.loadFiatRate() } } + is WalletAction.CreateWallet -> { + scope.launch { + val result = tangemSdkManager.createWallet() + when (result) { + is CompletionResult.Success -> { + store.state.globalState.tapWalletManager.onCardScanned(result.data) + } + + } + } + } is WalletAction.CreatePayId.CompleteCreatingPayId -> { scope.launch { val cardId = store.state.globalState.card?.cardId @@ -68,13 +78,7 @@ val walletMiddleware: Middleware = { dispatch, state -> val result = tangemSdkManager.scanNote() when (result) { is CompletionResult.Success -> { - withContext(Dispatchers.Main) { - store.dispatch(GlobalAction.LoadCard(result.data.card)) - store.dispatch(GlobalAction.LoadWalletManager(result.data.walletManager)) - store.dispatch(WalletAction.LoadWallet) - store.dispatch(WalletAction.LoadFiatRate) - store.dispatch(WalletAction.LoadPayId) - } + store.state.globalState.tapWalletManager.onCardScanned(result.data) } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index 7041021ace..d943eff64c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.wallet.redux import com.tangem.blockchain.common.AmountType +import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.toFiatString import com.tangem.tap.common.extensions.toFormattedString import com.tangem.tap.common.extensions.toQrCode @@ -17,12 +18,19 @@ fun walletReducer(action: Action, state: AppState): WalletState { var newState = state.walletState when (action) { + is WalletAction.EmptyWallet -> newState = WalletState( + state = ProgressState.Done, + currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), + mainButton = WalletMainButton.CreateWalletButton(true) + ) is WalletAction.LoadWallet -> newState = WalletState( state = ProgressState.Loading, currencyData = BalanceWidgetData( BalanceStatus.Loading, state.globalState.walletManager?.wallet?.blockchain?.fullName - ) + ), + mainButton = WalletMainButton.SendButton(false) + ) is WalletAction.LoadWallet.Success -> { val token = action.wallet.amounts[AmountType.Token] @@ -38,6 +46,8 @@ fun walletReducer(action: Action, state: AppState): WalletState { val amount = action.wallet.amounts[AmountType.Coin]?.value val fiatRate = state.globalState.fiatRates.getRateForCryptoCurrency(action.wallet.blockchain.currency) val fiatAmount = fiatRate?.let { amount?.toFiatString(it) } + + val sendButtonEnabled = amount?.isZero() == false || token?.value?.isZero() == false newState = newState.copy( state = ProgressState.Done, wallet = action.wallet, currencyData = BalanceWidgetData( @@ -45,14 +55,15 @@ fun walletReducer(action: Action, state: AppState): WalletState { amount?.toFormattedString(action.wallet.blockchain), token = tokenData, fiatAmount = fiatAmount - ) + ), + mainButton = WalletMainButton.SendButton(sendButtonEnabled) ) } is WalletAction.LoadFiatRate.Success -> { val rate = action.fiatRates.second val currency = action.fiatRates.first val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) { - newState.wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatString(rate) + newState.wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatString(rate) } else { null } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index e7e0ca4c64..fb19fa5fc9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.wallet.redux import android.graphics.Bitmap import com.tangem.blockchain.common.Wallet +import com.tangem.tap.common.entities.Button import com.tangem.tap.features.wallet.ui.BalanceWidgetData import org.rekotlin.StateType @@ -12,7 +13,8 @@ data class WalletState( val currencyData: BalanceWidgetData = BalanceWidgetData(), val payIdData: PayIdData = PayIdData(), val qrCode: Bitmap? = null, - val creatingPayIdState: CreatingPayIdState? = null + val creatingPayIdState: CreatingPayIdState? = null, + val mainButton: WalletMainButton = WalletMainButton.SendButton(false) ) : StateType @@ -25,4 +27,9 @@ data class PayIdData( val payId: String? = null ) -enum class CreatingPayIdState { EnterPayId, Waiting } \ No newline at end of file +enum class CreatingPayIdState { EnterPayId, Waiting } + +sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { + class SendButton(enabled: Boolean) : WalletMainButton(enabled) + class CreateWalletButton(enabled: Boolean) : WalletMainButton(enabled) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt index 5338921e09..92e3dca6e1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/BalanceWidget.kt @@ -5,7 +5,9 @@ import androidx.fragment.app.Fragment import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.wallet.R +import kotlinx.android.synthetic.main.card_balance.* import kotlinx.android.synthetic.main.layout_balance.* +import kotlinx.android.synthetic.main.layout_balance_error.* import kotlinx.android.synthetic.main.layout_token.view.* enum class PayIdState { @@ -18,7 +20,8 @@ enum class PayIdState { enum class BalanceStatus { VerifiedOnline, Unreachable, - Loading + Loading, + EmptyCard } data class BalanceWidgetData( @@ -45,6 +48,8 @@ class BalanceWidget( when (data.status) { BalanceStatus.Loading -> { + fragment.l_balance.show() + fragment.l_balance_error.hide() fragment.tv_currency.text = data.currency fragment.tv_amount.text = "-" fragment.tv_fiat_amount.hide() @@ -52,6 +57,8 @@ class BalanceWidget( fragment.l_token.hide() } BalanceStatus.VerifiedOnline -> { + fragment.l_balance.show() + fragment.l_balance_error.hide() fragment.tv_currency.text = data.currency fragment.tv_amount.text = data.amount fragment.tv_fiat_amount.show() @@ -70,9 +77,16 @@ class BalanceWidget( } BalanceStatus.Unreachable -> { + fragment.l_balance.show() + fragment.l_balance_error.hide() fragment.l_token.hide() showStatus(R.id.tv_status_error) - + } + BalanceStatus.EmptyCard -> { + fragment.l_balance.hide() + fragment.l_balance_error.show() + fragment.tv_error_title.text = fragment.getText(R.string.wallet_empty_card) + fragment.tv_error_descriptions.text = fragment.getText(R.string.wallet_empty_card_description) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index 72617cc32a..1f3584bfa3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -60,14 +60,34 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber R.string.wallet_button_send + is WalletMainButton.CreateWalletButton -> R.string.wallet_button_create_wallet + } + btn_main.text = getString(buttonTitle) + btn_main.isEnabled = state.mainButton.enabled + + btn_main.setOnClickListener { + when (state.mainButton) { + is WalletMainButton.SendButton -> TODO() + is WalletMainButton.CreateWalletButton -> store.dispatch(WalletAction.CreateWallet) + } + } + if (state.qrCode != null && state.wallet?.shareUrl != null) { qrDialog = QrDialog(requireContext()) qrDialog?.showQr(state.qrCode, state.wallet.shareUrl) diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt index f76dc20cf8..5350bad116 100644 --- a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt @@ -2,14 +2,16 @@ package com.tangem.tap.network.coinmarketcap import com.tangem.commands.common.network.Result import com.tangem.commands.common.network.performRequest +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.math.BigDecimal class CoinMarketCapService { private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() } - suspend fun getRate(currency: String): Result { + suspend fun getRate(currency: String): Result = withContext(Dispatchers.IO) { val response = performRequest { api.getRateInfo(1, currency) } - return when (response) { + return@withContext when (response) { is Result.Success -> Result.Success(response.data.data.quote.usd.price) is Result.Failure -> response } diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 982303e7e0..c41550aa8d 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -2,8 +2,7 @@ + android:layout_height="match_parent"> Tangem Tap Scan Send + Create Wallet Explore address Create PayID Verified Balance Blockchain is unreachable Balance is loading… Account is not created + Empty Card + Create wallet to start using Tangem card Tokens Create PayID