Updated on 2026-08-14
This commit is contained in:
commit
3f089ec4c1
14 changed files with 263 additions and 61 deletions
|
|
@ -105,6 +105,12 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
dAppName = state.dialog.dAppName,
|
||||
context = context
|
||||
)
|
||||
is WalletConnectDialog.UnsupportedNetwork ->
|
||||
SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect,
|
||||
messageRes = R.string.wallet_connect_scanner_error_unsupported_network,
|
||||
context = context
|
||||
)
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.github.salomonbrys.kotson.fromJson
|
||||
import com.github.salomonbrys.kotson.toMap
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.trustwallet.walletconnect.JSONRPC_VERSION
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
|
||||
|
|
@ -18,9 +20,11 @@ class EthSignHelper {
|
|||
.create()
|
||||
}
|
||||
|
||||
fun tryToParseEthTypedMessage(data: String): WCEthereumSignMessage? {
|
||||
val request =
|
||||
gson.fromJson<CustomJsonRpcRequests>(data)
|
||||
fun parseCustomRequest(data: String): CustomJsonRpcRequest {
|
||||
return gson.fromJson<CustomJsonRpcRequest>(data)
|
||||
}
|
||||
|
||||
fun tryToParseEthTypedMessage(request: CustomJsonRpcRequest): WCEthereumSignMessage? {
|
||||
return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) {
|
||||
WCEthereumSignMessage(
|
||||
listOf(
|
||||
|
|
@ -53,12 +57,26 @@ class EthSignHelper {
|
|||
}
|
||||
}
|
||||
|
||||
data class CustomJsonRpcRequests(
|
||||
data class CustomJsonRpcRequest(
|
||||
val id: Long,
|
||||
val jsonrpc: String = JSONRPC_VERSION,
|
||||
val method: WCMethodExtended?,
|
||||
val params: JsonArray
|
||||
)
|
||||
) {
|
||||
|
||||
fun blockchainFromChainId(): Blockchain? {
|
||||
return try {
|
||||
val hex = params[0].asJsonObject.toMap()[CHAIN_ID_KEY]?.asString ?: ""
|
||||
Blockchain.fromChainId(Integer.decode(hex))
|
||||
} catch (exception: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHAIN_ID_KEY = "chainId"
|
||||
}
|
||||
}
|
||||
|
||||
enum class WCMethodExtended {
|
||||
@SerializedName("wc_sessionRequest")
|
||||
|
|
@ -95,5 +113,10 @@ enum class WCMethodExtended {
|
|||
GET_ACCOUNTS,
|
||||
|
||||
@SerializedName("trust_signTransaction")
|
||||
SIGN_TRANSACTION;
|
||||
SIGN_TRANSACTION,
|
||||
|
||||
@SerializedName("wallet_switchEthereumChain")
|
||||
SWITCH_CHAIN,
|
||||
|
||||
;
|
||||
}
|
||||
|
|
@ -11,14 +11,20 @@ import com.tangem.tap.store
|
|||
import com.tangem.tap.walletConnectRepository
|
||||
import com.trustwallet.walletconnect.WCClient
|
||||
import com.trustwallet.walletconnect.models.WCPeerMeta
|
||||
import com.trustwallet.walletconnect.models.binance.*
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceCancelOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder
|
||||
import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
|
||||
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
|
||||
import com.trustwallet.walletconnect.models.session.WCSession
|
||||
import com.trustwallet.walletconnect.models.session.WCSessionUpdate
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.*
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
|
@ -71,6 +77,22 @@ class WalletConnectManager {
|
|||
}
|
||||
}
|
||||
|
||||
fun updateBlockchain(session: WalletConnectSession) {
|
||||
sessions[session.session]?.client?.updateSession(
|
||||
accounts = listOfNotNull(session.getAddress()),
|
||||
chainId = session.wallet.blockchain?.getChainId(),
|
||||
approved = true
|
||||
)
|
||||
|
||||
|
||||
val updatedSession = sessions[session.session]?.copy(
|
||||
wallet = session.wallet
|
||||
)
|
||||
if (updatedSession != null) {
|
||||
sessions[session.session] = updatedSession
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupConnectionTimeoutCheck(session: WCSession) {
|
||||
scope.launch {
|
||||
delay(20_000)
|
||||
|
|
@ -409,23 +431,43 @@ class WalletConnectManager {
|
|||
Timber.d("Custom Request")
|
||||
Timber.d(data)
|
||||
|
||||
val message = EthSignHelper.tryToParseEthTypedMessage(data)
|
||||
if (message != null) {
|
||||
Timber.d("onEthSign_v4: $message")
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.PersonalSign
|
||||
val request = EthSignHelper.parseCustomRequest(data)
|
||||
|
||||
when (request.method) {
|
||||
WCMethodExtended.ETH_SIGN_TYPE_DATA_V4 -> handleTypedDataV4(request, client, id)
|
||||
WCMethodExtended.SWITCH_CHAIN -> {
|
||||
val blockchain = request.blockchainFromChainId()
|
||||
Timber.d("WC switch chainID\nNew Blockchain: $blockchain")
|
||||
val session = sessions[client.session]?.toWalletConnectSession()
|
||||
if (session != null) {
|
||||
store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.d("WC: unrecognized custom request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleTypedDataV4(request: CustomJsonRpcRequest, client: WCClient, id: Long) {
|
||||
val message = EthSignHelper.tryToParseEthTypedMessage(request)
|
||||
if (message != null) {
|
||||
Timber.d("onEthSign_v4: $message")
|
||||
store.state.globalState.analyticsHandlers?.logWcEvent(
|
||||
Analytics.WcAnalyticsEvent.Action(
|
||||
Analytics.WcAction.PersonalSign
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandlePersonalSignRequest(
|
||||
message,
|
||||
sessionData,
|
||||
id
|
||||
)
|
||||
)
|
||||
sessions[client.session]?.toWalletConnectSession()?.let { sessionData ->
|
||||
store.dispatchOnMain(
|
||||
WalletConnectAction.HandlePersonalSignRequest(
|
||||
message,
|
||||
sessionData,
|
||||
id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.tap.domain.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.CurrenciesRepository
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletForSession
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
||||
class WcWalletManagerFactory(
|
||||
private val factory: WalletManagerFactory,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
) {
|
||||
|
||||
suspend fun getWalletManager(
|
||||
wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState
|
||||
): WalletManager? {
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
blockchain
|
||||
}
|
||||
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
derivationPath = wallet.derivationPath?.rawPath,
|
||||
tokens = emptyList()
|
||||
)
|
||||
|
||||
return if (walletState.cardId == wallet.cardId) {
|
||||
walletState.getWalletManager(blockchainNetwork)
|
||||
} else {
|
||||
val blockchainNetworkWithTokens = currenciesRepository
|
||||
.loadSavedCurrencies(
|
||||
cardId = wallet.cardId,
|
||||
isHdWalletSupported = wallet.derivationPath != null
|
||||
).firstOrNull { it == blockchainNetwork }
|
||||
|
||||
if (blockchainNetworkWithTokens != null) {
|
||||
factory.makeWalletManager(
|
||||
cardId = wallet.cardId,
|
||||
blockchain = blockchainToMake,
|
||||
publicKey = Wallet.PublicKey(
|
||||
wallet.walletPublicKey!!,
|
||||
wallet.derivedPublicKey,
|
||||
wallet.derivationPath
|
||||
),
|
||||
tokens = blockchainNetworkWithTokens.tokens,
|
||||
curve = blockchainToMake.getPrimaryCurve() ?: EllipticCurve.Secp256k1
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getWalletManager(
|
||||
scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState
|
||||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
blockchain
|
||||
}
|
||||
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
card = card
|
||||
)
|
||||
|
||||
return if (walletState.cardId == card.cardId) {
|
||||
walletState.getWalletManager(blockchainNetwork)
|
||||
} else {
|
||||
if (currenciesRepository
|
||||
.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.contains(blockchainNetwork)
|
||||
) {
|
||||
factory.makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchainNetwork = blockchainNetwork
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import android.app.Activity
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.redux.NotificationAction
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -47,6 +48,16 @@ sealed class WalletConnectAction : Action {
|
|||
data class Success(val session: WalletConnectSession) : WalletConnectAction()
|
||||
}
|
||||
|
||||
data class SwitchBlockchain(
|
||||
val blockchain: Blockchain?,
|
||||
val session: WalletConnectSession
|
||||
) : WalletConnectAction()
|
||||
|
||||
data class UpdateBlockchain(
|
||||
val updatedSession: WalletConnectSession
|
||||
) : WalletConnectAction()
|
||||
|
||||
|
||||
data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction()
|
||||
|
||||
data class SetSessionsRestored(val sessions: List<WalletConnectSession>) :
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
|
|
@ -14,11 +12,10 @@ import com.tangem.tap.common.redux.navigation.AppScreen
|
|||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.currenciesRepository
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.domain.walletconnect.BnbHelper
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectManager
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils
|
||||
import com.tangem.tap.domain.walletconnect.WcWalletManagerFactory
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -159,6 +156,45 @@ class WalletConnectMiddleware {
|
|||
action.id, action.data, action.sessionData
|
||||
)
|
||||
}
|
||||
is WalletConnectAction.SwitchBlockchain -> {
|
||||
val blockchain = action.blockchain.guard {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
|
||||
return
|
||||
}
|
||||
|
||||
val factory = WcWalletManagerFactory(
|
||||
factory = store.state.globalState.tapWalletManager.walletManagerFactory,
|
||||
currenciesRepository = currenciesRepository
|
||||
)
|
||||
val walletState = store.state.walletState
|
||||
scope.launch {
|
||||
val walletManager = factory.getWalletManager(
|
||||
wallet = action.session.wallet,
|
||||
blockchain = blockchain,
|
||||
walletState = walletState
|
||||
).guard {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
WalletConnectDialog.AddNetwork(blockchain.fullName)
|
||||
)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val updatedWallet = action.session.wallet.copy(
|
||||
walletPublicKey = walletManager.wallet.publicKey.seedKey,
|
||||
derivedPublicKey = walletManager.wallet.publicKey.derivedKey,
|
||||
derivationPath = walletManager.wallet.publicKey.derivationPath,
|
||||
blockchain = action.blockchain
|
||||
|
||||
)
|
||||
val updatedSession = action.session.copy(wallet = updatedWallet)
|
||||
store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession))
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.UpdateBlockchain -> {
|
||||
walletConnectManager.updateBlockchain(action.updatedSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,8 +225,13 @@ class WalletConnectMiddleware {
|
|||
return
|
||||
}
|
||||
|
||||
val factory = WcWalletManagerFactory(
|
||||
factory = store.state.globalState.tapWalletManager.walletManagerFactory,
|
||||
currenciesRepository = currenciesRepository
|
||||
)
|
||||
val walletState = store.state.walletState
|
||||
scope.launch {
|
||||
val walletManager = getWalletManager(scanResponse, blockchain).guard {
|
||||
val walletManager = factory.getWalletManager(scanResponse, blockchain, walletState).guard {
|
||||
store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
|
|
@ -232,37 +273,4 @@ class WalletConnectMiddleware {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun getWalletManager(
|
||||
scanResponse: ScanResponse, blockchain: Blockchain
|
||||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
|
||||
val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) {
|
||||
Blockchain.EthereumTestnet
|
||||
} else {
|
||||
blockchain
|
||||
}
|
||||
|
||||
val blockchainNetwork = BlockchainNetwork(
|
||||
blockchain = blockchainToMake,
|
||||
card = card
|
||||
)
|
||||
|
||||
return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
|
||||
store.state.walletState.getWalletManager(blockchainNetwork)
|
||||
} else {
|
||||
if (currenciesRepository
|
||||
.loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed)
|
||||
.contains(blockchainNetwork)
|
||||
) {
|
||||
factory.makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchainNetwork = blockchainNetwork
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,11 @@ class WalletConnectReducer {
|
|||
is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false)
|
||||
is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false)
|
||||
is WalletConnectAction.UpdateBlockchain -> state.copy(
|
||||
sessions = state.sessions
|
||||
.filterNot { it.peerId == action.updatedSession.peerId }
|
||||
+ action.updatedSession
|
||||
)
|
||||
|
||||
else -> state
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ data class WalletForSession(
|
|||
sealed class WalletConnectDialog : StateDialog {
|
||||
data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog()
|
||||
object UnsupportedCard : WalletConnectDialog()
|
||||
object UnsupportedNetwork : WalletConnectDialog()
|
||||
data class AddNetwork(val network: String) : WalletConnectDialog()
|
||||
object OpeningSessionRejected : WalletConnectDialog()
|
||||
object SessionTimeout : WalletConnectDialog()
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ data class WalletState(
|
|||
val walletManagers: List<WalletManager>
|
||||
get() = wallets.mapNotNull { it.walletManager }
|
||||
|
||||
val cardId: String?
|
||||
get() = wallets.firstOrNull()?.walletManager?.wallet?.cardId
|
||||
|
||||
fun getWalletManager(currency: Currency?): WalletManager? {
|
||||
if (currency?.blockchain == null) return null
|
||||
return getWalletStore(currency)?.walletManager
|
||||
|
|
|
|||
|
|
@ -59,5 +59,6 @@
|
|||
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
|
||||
|
||||
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
|
||||
<string name="wallet_currency_subtitle">%s network</string>
|
||||
</resources>
|
||||
|
|
@ -59,5 +59,6 @@
|
|||
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
|
||||
|
||||
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
|
||||
<string name="wallet_currency_subtitle">%s network</string>
|
||||
</resources>
|
||||
|
|
@ -59,5 +59,7 @@
|
|||
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
|
||||
|
||||
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
|
||||
|
||||
<string name="wallet_currency_subtitle">%s network</string>
|
||||
</resources>
|
||||
|
|
@ -65,6 +65,8 @@
|
|||
<string name="token_details_unable_hide_alert_message">Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети.</string>
|
||||
|
||||
<string name="wallet_connect_network_not_found_format">Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">Сеть не поддерживается. Пожалуйста, выберите другую сеть.</string>
|
||||
|
||||
<string name="wallet_currency_subtitle">Сеть %s</string>
|
||||
|
||||
<string name="main_no_backup_warning_title">Бэкап кошелька не был произведен</string>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@
|
|||
<string name="token_details_unable_hide_alert_message">The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list.</string>
|
||||
|
||||
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
|
||||
|
||||
<string name="wallet_currency_subtitle">%s network</string>
|
||||
|
||||
<string name="main_no_backup_warning_title">Your wallet has not been backed up</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue