Merge branch 'main' into CW-519-add-tor-connection-feature-to-cake

This commit is contained in:
Omar Hatem 2025-06-20 22:25:58 +03:00 committed by GitHub
commit 152b458190
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
73 changed files with 2684 additions and 187 deletions

View file

@ -266,6 +266,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
Future<void> close({bool shouldCleanup = false}) async {
payjoinManager.cleanupSessions();
super.close(shouldCleanup: shouldCleanup);
}
late final PayjoinManager payjoinManager;
bool get isPayjoinAvailable => unspentCoinsInfo.values

View file

@ -59,19 +59,26 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
@action
Future<void> initPayjoin() async {
await payjoinManager.initPayjoin();
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
try {
await payjoinManager.initPayjoin();
currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress);
payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
payjoinManager.resumeSessions();
payjoinManager.resumeSessions();
} catch (e) {
printV(e);
}
}
@action
Future<void> newPayjoinReceiver() async {
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
try {
currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress);
payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
printV("Initializing new Payjoin Receiver");
payjoinManager.spawnNewReceiver(receiver: currentPayjoinReceiver!);
payjoinManager.spawnReceiver(receiver: currentPayjoinReceiver!);
} catch (e) {
printV(e);
}
}
}

View file

@ -53,7 +53,7 @@ class PayjoinManager {
}
final receiver = Receiver.fromJson(json: session.receiver!);
printV("Resuming Payjoin Receiver Session ${receiver.id()}");
return _spawnReceiver(receiver: receiver);
return spawnReceiver(receiver: receiver);
});
printV("Resumed ${spawnedSessions.length} Payjoin Sessions");
@ -121,15 +121,13 @@ class PayjoinManager {
}
} catch (e) {
_cleanupSession(pjUri);
printV(e);
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri);
completer.completeError(e);
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, e.toString());
completer.complete();
}
} else if (message is PayjoinSessionError) {
_cleanupSession(pjUri);
if (message is UnrecoverableError) {
printV(message.message);
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri);
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, message.message);
completer.complete();
} else if (message is RecoverableError) {
completer.complete();
@ -149,42 +147,41 @@ class PayjoinManager {
return completer.future;
}
Future<Receiver> initReceiver(String address,
Future<Receiver> getUnusedReceiver(String address,
[bool isTestnet = false]) async {
try {
final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
ohttpRelay: await randomOhttpRelayUrl(),
payjoinDirectory: payjoinDirectoryUrl,
);
final session = _payjoinStorage.getUnusedActiveReceiverSession(_wallet.id);
final newReceiver = await NewReceiver.create(
address: address,
network: isTestnet ? Network.testnet : Network.bitcoin,
directory: payjoinDirectoryUrl,
ohttpKeys: ohttpKeys,
);
final persister = PayjoinReceiverPersister.impl();
final receiverToken = await newReceiver.persist(persister: persister);
final receiver =
await Receiver.load(persister: persister, token: receiverToken);
if (session != null) {
await PayjoinUri.Url.fromStr(payjoinDirectoryUrl);
await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
return receiver;
} catch (e) {
throw Exception('Error initializing Payjoin Receiver: $e');
return Receiver.fromJson(json: session.receiver!);
}
return initReceiver(address);
}
Future<void> spawnNewReceiver({
required Receiver receiver,
bool isTestnet = false,
}) async {
Future<Receiver> initReceiver(String address, [bool isTestnet = false]) async {
final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
ohttpRelay: await randomOhttpRelayUrl(),
payjoinDirectory: payjoinDirectoryUrl,
);
final newReceiver = await NewReceiver.create(
address: address,
network: isTestnet ? Network.testnet : Network.bitcoin,
directory: payjoinDirectoryUrl,
ohttpKeys: ohttpKeys,
);
final persister = PayjoinReceiverPersister.impl();
final receiverToken = await newReceiver.persist(persister: persister);
final receiver = await Receiver.load(persister: persister, token: receiverToken);
await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
return _spawnReceiver(isTestnet: isTestnet, receiver: receiver);
return receiver;
}
Future<void> _spawnReceiver({
Future<void> spawnReceiver({
required Receiver receiver,
bool isTestnet = false,
}) async {
@ -229,6 +226,10 @@ class PayjoinManager {
case PayjoinReceiverRequestTypes.getCandidateInputs:
utxos = _wallet.getUtxoWithPrivateKeys();
if (utxos.isEmpty) {
await _wallet.updateAllUnspents();
utxos = _wallet.getUtxoWithPrivateKeys();
}
mainToIsolateSendPort?.send({
'requestId': message['requestId'],
'result': utxos,

View file

@ -174,7 +174,7 @@ class PayjoinReceiverWorker {
final listUnspent =
await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs);
final unspent = listUnspent as List<UtxoWithPrivateKey>;
if (unspent.isEmpty) throw Exception('No unspent outputs available');
if (unspent.isEmpty) throw RecoverableError('No unspent outputs available');
final selectedUtxo = await _inputPairFromUtxo(unspent[0]);
final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]);

View file

@ -23,6 +23,14 @@ class PayjoinStorage {
),
);
PayjoinSession? getUnusedActiveReceiverSession(String walletId) =>
_payjoinSessionSources.values
.where((session) =>
session.walletId == walletId &&
session.status == PayjoinSessionStatus.created.name &&
!session.isSenderSession)
.firstOrNull;
Future<void> markReceiverSessionComplete(
String sessionId, String txId, String amount) async {
final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!;
@ -76,10 +84,11 @@ class PayjoinStorage {
await session.save();
}
Future<void> markSenderSessionUnrecoverable(String pjUrl) async {
Future<void> markSenderSessionUnrecoverable(String pjUrl, String reason) async {
final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!;
session.status = PayjoinSessionStatus.unrecoverable.name;
session.error = reason;
await session.save();
}

View file

@ -0,0 +1,121 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_ethereum/deuro/deuro_savings_contract.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_evm/contract/erc20.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:web3dart/web3dart.dart';
const String savingsGatewayAddress =
"0x073493d73258C4BEb6542e8dd3e1b2891C972303";
const String dEuroAddress = "0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea";
class DEuro {
final SavingsGateway _savingsGateway;
final ERC20 _dEuro;
final EthereumWallet _wallet;
DEuro(EthereumWallet wallet)
: _wallet = wallet,
_savingsGateway = _getSavingsGateway(wallet.getWeb3Client()!),
_dEuro = _getDEuroToken(wallet.getWeb3Client()!);
static SavingsGateway _getSavingsGateway(Web3Client client) => SavingsGateway(
address: EthereumAddress.fromHex(savingsGatewayAddress),
client: client,
);
static ERC20 _getDEuroToken(Web3Client client) => ERC20(
address: EthereumAddress.fromHex(dEuroAddress),
client: client,
);
final frontendCode =
Uint8List.fromList(sha256.convert(utf8.encode("wallet")).bytes);
EthereumAddress get _address =>
EthereumAddress.fromHex(_wallet.walletAddresses.primaryAddress);
Future<BigInt> get savingsBalance async =>
(await _savingsGateway.savings(accountOwner: _address)).saved;
Future<BigInt> get accruedInterest =>
_savingsGateway.accruedInterest(accountOwner: _address);
Future<BigInt> get interestRate => _savingsGateway.currentRatePPM();
Future<BigInt> get approvedBalance =>
_dEuro.allowance(_address, _savingsGateway.self.address);
Future<PendingEVMChainTransaction> depositSavings(
BigInt amount, EVMChainTransactionPriority priority) async {
final signedTransaction = await _savingsGateway.save(
(amount: amount, frontendCode: frontendCode),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[17]
.encodeCall([amount, frontendCode]),
);
final sendTransaction =
() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: BigInt.from(fee.estimatedGasFee),
amount: amount.toString(),
exponent: 18);
}
Future<PendingEVMChainTransaction> withdrawSavings(
BigInt amount, EVMChainTransactionPriority priority) async {
final signedTransaction = await _savingsGateway.withdraw(
(target: _address, amount: amount, frontendCode: frontendCode),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[17]
.encodeCall([amount, frontendCode]),
);
final sendTransaction =
() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: BigInt.from(fee.estimatedGasFee),
amount: amount.toString(),
exponent: 18);
}
// Set an infinite approval to save gas in the future
Future<PendingEVMChainTransaction> enableSavings(
EVMChainTransactionPriority priority) async =>
(await _wallet.createApprovalTransaction(
BigInt.parse(
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
radix: 16,
),
_savingsGateway.self.address.hexEip55,
CryptoCurrency.deuro,
priority,
)) as PendingEVMChainTransaction;
}

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,7 @@ author: Cake Wallet
homepage: https://cakewallet.com
environment:
sdk: '>=2.18.2 <3.0.0'
sdk: ^3.5.0
flutter: ">=1.17.0"
dependencies:

View file

@ -76,7 +76,7 @@ abstract class EVMChainClient {
Future<int> getGasUnitPrice() async {
try {
final gasPrice = await _client!.getGasPrice();
return gasPrice.getInWei.toInt();
} catch (_) {
return 0;
@ -101,6 +101,7 @@ abstract class EVMChainClient {
String? contractAddress,
EtherAmount? gasPrice,
EtherAmount? maxFeePerGas,
Uint8List? data,
}) async {
try {
if (contractAddress == null) {
@ -124,7 +125,7 @@ abstract class EVMChainClient {
final gasEstimate = await _client!.estimateGas(
sender: senderAddress,
to: EthereumAddress.fromHex(contractAddress),
data: transfer.encodeCall([
data: data ?? transfer.encodeCall([
toAddress,
value.getInWei,
]),
@ -137,6 +138,21 @@ abstract class EVMChainClient {
}
}
Uint8List getEncodedDataForApprovalTransaction({
required EthereumAddress toAddress,
required EtherAmount value,
required EthereumAddress contractAddress,
}) {
final contract = DeployedContract(ethereumContractAbi, contractAddress);
final approve = contract.function('approve');
return approve.encodeCall([
toAddress,
value.getInWei,
]);
}
Future<PendingEVMChainTransaction> signTransaction({
required Credentials privateKey,
required String toAddress,
@ -198,6 +214,50 @@ abstract class EVMChainClient {
);
}
Future<PendingEVMChainTransaction> signApprovalTransaction({
required Credentials privateKey,
required String spender,
required BigInt amount,
required BigInt gasFee,
required int estimatedGasUnits,
required int maxFeePerGas,
required EVMChainTransactionPriority priority,
required int exponent,
required String contractAddress,
}) async {
final Transaction transaction = createTransaction(
from: privateKey.address,
to: EthereumAddress.fromHex(contractAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
amount: EtherAmount.zero(),
maxGas: estimatedGasUnits,
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
);
final erc20 = ERC20(
client: _client!,
address: EthereumAddress.fromHex(contractAddress),
chainId: chainId,
);
final signedTransaction = await erc20.approve(
EthereumAddress.fromHex(spender),
amount,
credentials: privateKey,
transaction: transaction,
);
return PendingEVMChainTransaction(
signedTransaction: prepareSignedTransactionForSending(signedTransaction),
amount: amount.toString(),
fee: gasFee,
sendTransaction: () => sendTransaction(signedTransaction),
exponent: exponent,
isInfiniteApproval: amount.toRadixString(16) == 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
);
}
Transaction createTransaction({
required EthereumAddress from,
required EthereumAddress to,

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
@ -255,6 +256,7 @@ abstract class EVMChainWalletBase
required String? contractAddress,
required String receivingAddressHex,
required TransactionPriority priority,
Uint8List? data,
}) async {
try {
if (priority is EVMChainTransactionPriority) {
@ -276,6 +278,7 @@ abstract class EVMChainWalletBase
gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
toAddress: EthereumAddress.fromHex(receivingAddressHex),
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
data: data,
);
final totalGasFee = estimatedGas * maxFeePerGas;
@ -478,6 +481,43 @@ abstract class EVMChainWalletBase
return pendingEVMChainTransaction;
}
Future<PendingTransaction> createApprovalTransaction(
BigInt amount,
String spender,
CryptoCurrency token,
EVMChainTransactionPriority priority) async {
final CryptoCurrency transactionCurrency =
balance.keys.firstWhere((element) => element.title == token.title);
assert(transactionCurrency is Erc20Token);
final data = _client.getEncodedDataForApprovalTransaction(
contractAddress: EthereumAddress.fromHex(
(transactionCurrency as Erc20Token).contractAddress),
value: EtherAmount.fromBigInt(EtherUnit.wei, amount),
toAddress: EthereumAddress.fromHex(spender),
);
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
receivingAddressHex: spender,
priority: priority,
contractAddress: transactionCurrency.contractAddress,
data: data,
);
return _client.signApprovalTransaction(
privateKey: _evmChainPrivateKey,
spender: spender,
amount: amount,
priority: priority,
gasFee: BigInt.from(gasFeesModel.estimatedGasFee),
maxFeePerGas: gasFeesModel.maxFeePerGas,
estimatedGasUnits: gasFeesModel.estimatedGasUnits,
exponent: transactionCurrency.decimal,
contractAddress: transactionCurrency.contractAddress,
);
}
Future<void> _updateTransactions() async {
try {
if (_isTransactionUpdating) {

View file

@ -11,6 +11,7 @@ class PendingEVMChainTransaction with PendingTransaction {
final BigInt fee;
final String amount;
final int exponent;
final bool isInfiniteApproval;
PendingEVMChainTransaction({
required this.sendTransaction,
@ -18,10 +19,12 @@ class PendingEVMChainTransaction with PendingTransaction {
required this.fee,
required this.amount,
required this.exponent,
this.isInfiniteApproval = false,
});
@override
String get amountFormatted {
if (isInfiniteApproval) return "";
final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
return _amount.substring(0, min(10, _amount.length));
}

View file

@ -30,6 +30,7 @@ dependencies:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-ethereum
ref: f4761cd5171d4c1e2e42fd3298261650539fb2db
dependency_overrides:
web3dart:

View file

@ -38,9 +38,9 @@ class AddressValidator extends TextValidator {
'|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}';
case CryptoCurrency.btc:
pattern =
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{25,39}}').pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
case CryptoCurrency.ltc:
pattern = '^${RegExp(r'ltc1q[ac-hj-np-z02-9]{25,39}').pattern}\$|^${MwebAddress.regex.pattern}\$';
pattern = '${P2wpkhAddress.regex.pattern}|${MwebAddress.regex.pattern}';
case CryptoCurrency.nano:
pattern = '[0-9a-zA-Z_]+';
case CryptoCurrency.banano:
@ -335,10 +335,6 @@ class AddressValidator extends TextValidator {
}
}
if (pattern != null) {
return "$BEFORE_REGEX($pattern)$AFTER_REGEX";
}
return null;
return pattern != null ? "($pattern)" : null;
}
}

View file

@ -73,8 +73,11 @@ class WalletLoadingService {
return wallet;
} catch (error, stack) {
await ExceptionHandler.resetLastPopupDate();
final isLedgerError = await ExceptionHandler.isLedgerError(error);
if (isLedgerError) rethrow;
await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
// try fetching the seeds of the corrupted wallet to show it to the user
String corruptedWalletsSeeds = "Corrupted wallets seeds (if retrievable, empty otherwise):";
try {

View file

@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart';
import 'package:cake_wallet/src/screens/settings/background_sync_page.dart';
import 'package:cake_wallet/src/screens/start_tor/start_tor_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
@ -45,6 +46,7 @@ import 'package:cake_wallet/themes/core/theme_store.dart';
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
@ -1516,5 +1518,9 @@ Future<void> setup({
getIt.registerFactory(() => StartTorPage(StartTorViewModel(),));
getIt.registerFactory(() => DEuroViewModel(getIt<AppStore>()));
getIt.registerFactory(() => DEuroSavingsPage(getIt<DEuroViewModel>()));
_isSetupFinished = true;
}

View file

@ -165,13 +165,19 @@ class AddressResolver {
"zone"
];
static String? extractAddressByType({required String raw, required CryptoCurrency type}) {
final addressPattern = AddressValidator.getAddressFromStringPattern(type);
static String? extractAddressByType(
{required String raw,
required CryptoCurrency type,
bool requireSurroundingWhitespaces = true}) {
var addressPattern = AddressValidator.getAddressFromStringPattern(type);
if (addressPattern == null) {
throw Exception('Unexpected token: $type for getAddressFromStringPattern');
}
if (requireSurroundingWhitespaces)
addressPattern = "$BEFORE_REGEX$addressPattern$AFTER_REGEX";
final match = RegExp(addressPattern, multiLine: true).firstMatch(raw);
return match?.group(0)?.replaceAllMapped(RegExp('[^0-9a-zA-Z]|bitcoincash:|nano_|ban_'),
(Match match) {

View file

@ -67,8 +67,7 @@ class CWEthereum extends Ethereum {
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as EthereumWallet).evmChainPrivateKey;
final publicKey = privateKeyInUnitInt.address.hex;
return publicKey;
return privateKeyInUnitInt.address.hex;
}
@override
@ -138,29 +137,24 @@ class CWEthereum extends Ethereum {
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) {
final ethereumWallet = wallet as EthereumWallet;
return ethereumWallet.erc20Currencies;
}
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
(wallet as EthereumWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) async {
await (wallet as EthereumWallet).addErc20Token(token as Erc20Token);
}
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as EthereumWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as EthereumWallet).deleteErc20Token(token as Erc20Token);
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as EthereumWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
(wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
final ethereumWallet = wallet as EthereumWallet;
return await ethereumWallet.getErc20Token(contractAddress, 'eth');
}
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
(wallet as EthereumWallet).getErc20Token(contractAddress, 'eth');
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
@ -177,23 +171,19 @@ class CWEthereum extends Ethereum {
}
@override
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
(wallet as EthereumWallet).updateScanProviderUsageState(isEnabled);
}
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as EthereumWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) {
return (wallet as EthereumWallet).getWeb3Client();
}
Web3Client? getWeb3Client(WalletBase wallet) => (wallet as EthereumWallet).getWeb3Client();
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
void setLedgerConnection(
WalletBase wallet, ledger.LedgerConnection connection) {
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
.setLedgerConnection(
connection, wallet.walletInfo.derivationInfo?.derivationPath);
.setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath);
}
@override
@ -209,7 +199,44 @@ class CWEthereum extends Ethereum {
}
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
}
List<String> getDefaultTokenContractAddresses() =>
DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender,
CryptoCurrency token, TransactionPriority priority) =>
(wallet as EVMChainWallet).createApprovalTransaction(
amount, spender, token, priority as EVMChainTransactionPriority);
// Integrations
@override
Future<BigInt> getDEuroSavingsBalance(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).savingsBalance;
@override
Future<BigInt> getDEuroAccruedInterest(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).accruedInterest;
@override
Future<BigInt> getDEuroInterestRate(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).interestRate;
@override
Future<BigInt> getDEuroSavingsApproved(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).approvedBalance;
@override
Future<PendingTransaction> addDEuroSaving(
WalletBase wallet, BigInt amount, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet)
.depositSavings(amount, priority as EVMChainTransactionPriority);
@override
Future<PendingTransaction> removeDEuroSaving(
WalletBase wallet, BigInt amount, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet)
.withdrawSavings(amount, priority as EVMChainTransactionPriority);
@override
Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet).enableSavings(priority as EVMChainTransactionPriority);
}

View file

@ -67,8 +67,7 @@ class CWPolygon extends Polygon {
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as PolygonWallet).evmChainPrivateKey;
final publicKey = privateKeyInUnitInt.address.hex;
return publicKey;
return privateKeyInUnitInt.address.hex;
}
@override
@ -137,28 +136,27 @@ class CWPolygon extends Polygon {
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) {
final polygonWallet = wallet as PolygonWallet;
return polygonWallet.erc20Currencies;
}
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
(wallet as PolygonWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as PolygonWallet).addErc20Token(token as Erc20Token);
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as PolygonWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as PolygonWallet).deleteErc20Token(token as Erc20Token);
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as PolygonWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as PolygonWallet).removeTokenTransactionsInHistory(token as Erc20Token);
Future<void> removeTokenTransactionsInHistory(
WalletBase wallet, CryptoCurrency token) =>
(wallet as PolygonWallet)
.removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
final polygonWallet = wallet as PolygonWallet;
return await polygonWallet.getErc20Token(contractAddress, 'polygon');
}
Future<Erc20Token?> getErc20Token(
WalletBase wallet, String contractAddress) =>
(wallet as PolygonWallet).getErc20Token(contractAddress, 'polygon');
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
@ -176,23 +174,29 @@ class CWPolygon extends Polygon {
}
@override
void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) {
(wallet as PolygonWallet).updateScanProviderUsageState(isEnabled);
}
void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as PolygonWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) {
return (wallet as PolygonWallet).getWeb3Client();
}
Web3Client? getWeb3Client(WalletBase wallet) =>
(wallet as PolygonWallet).getWeb3Client();
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
String getTokenAddress(CryptoCurrency asset) =>
(asset as Erc20Token).contractAddress;
@override
Future<PendingTransaction> createTokenApproval(WalletBase wallet,
BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority) =>
(wallet as EVMChainWallet)
.createApprovalTransaction(amount, spender, token, priority as EVMChainTransactionPriority);
@override
void setLedgerConnection(
WalletBase wallet, ledger.LedgerConnection connection) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
.setLedgerConnection(
connection, wallet.walletInfo.derivationInfo?.derivationPath);
connection, wallet.walletInfo.derivationInfo?.derivationPath);
}
@override
@ -206,9 +210,10 @@ class CWPolygon extends Polygon {
throw err;
}
}
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
}
List<String> getDefaultTokenContractAddresses() => DefaultPolygonErc20Tokens()
.initialPolygonErc20Tokens
.map((e) => e.contractAddress)
.toList();
}

View file

@ -9,6 +9,7 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
@ -63,14 +64,49 @@ void startAuthenticationStateChange(
monero!.setGlobalLedgerConnection(ledgerVM.connection);
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
alertBarrierDismissible: false,
buttonAction: () => Navigator.of(context).pop()),
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
alertBarrierDismissible: false,
buttonAction: () => Navigator.of(context).pop(),
),
);
await loadCurrentWallet();
bool tryOpening = true;
while (tryOpening) {
try {
await loadCurrentWallet();
tryOpening = false;
} on Exception catch (e) {
final errorCode = RegExp(r'0x\S*?(?= )').firstMatch(
e.toString());
final errorMessage = ledgerVM.interpretErrorCode(
errorCode?.group(0).toString().replaceAll("0x", "") ??
"");
if (errorMessage != null) {
await showPopUp<void>(
context: context,
builder: (context) => AlertWithTwoActions(
alertTitle: "Ledger Error",
alertContent: errorMessage,
leftButtonText: S.of(context).try_again,
alertBarrierDismissible: false,
actionLeftButton: () => Navigator.of(context).pop(),
rightButtonText: S.of(context).cancel,
actionRightButton: () {
tryOpening = false;
Navigator.of(context).pop();
},
),
);
} else {
tryOpening = false;
rethrow;
}
}
}
getIt.get<BottomSheetService>().showNext();
await navigatorKey.currentState!
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);

View file

@ -66,7 +66,7 @@ void startCurrentWalletChangeReaction(
final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
startWalletSyncStatusChangeReaction(wallet, settingsStore);
startCheckConnectionReaction(wallet, settingsStore);
await Future.delayed(Duration.zero);

View file

@ -1,5 +1,7 @@
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/wallet_base.dart';
@ -12,7 +14,7 @@ ReactionDisposer? _onWalletSyncStatusChangeReaction;
void startWalletSyncStatusChangeReaction(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
FiatConversionStore fiatConversionStore) {
SettingsStore settingsStore) {
_onWalletSyncStatusChangeReaction?.reaction.dispose();
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
try {
@ -25,6 +27,12 @@ void startWalletSyncStatusChangeReaction(
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
await WakelockPlus.disable();
}
if (status is SyncedSyncStatus &&
wallet.type == WalletType.bitcoin &&
settingsStore.usePayjoin) {
bitcoin!.resumePayjoinSessions(wallet);
}
} catch (e) {
printV(e.toString());
}

View file

@ -49,6 +49,7 @@ import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dar
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
import 'package:cake_wallet/src/screens/faq/faq_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
@ -928,6 +929,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<StartTorPage>(),
);
case Routes.dEuroSavings:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<DEuroSavingsPage>(),
);
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(

View file

@ -129,4 +129,6 @@ class Routes {
static const walletGroupExistingSeedDescriptionPage = '/wallet_group_existing_seed_description_page';
static const walletSeedVerificationPage = '/wallet_seed_verification_page';
static const exchangeTradeExternalSendPage = '/exchange_trade_external_send_page';
static const dEuroSavings = '/integration/dEuro/savings';
}

View file

@ -165,8 +165,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
}
Future<void> _connectToDevice(LedgerDevice device) async {
await widget.ledgerVM.connectLedger(device, widget.walletType);
widget.onConnectDevice(context, widget.ledgerVM);
final isConnected =
await widget.ledgerVM.connectLedger(device, widget.walletType);
if (isConnected) widget.onConnectDevice(context, widget.ledgerVM);
}
String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {

View file

@ -5,15 +5,16 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class CakeFeaturesPage extends StatelessWidget {
CakeFeaturesPage({required this.dashboardViewModel, required this.cakeFeaturesViewModel});
CakeFeaturesPage(
{required this.dashboardViewModel, required this.cakeFeaturesViewModel});
final DashboardViewModel dashboardViewModel;
final CakeFeaturesViewModel cakeFeaturesViewModel;
@ -58,6 +59,23 @@ class CakeFeaturesPage extends StatelessWidget {
fit: BoxFit.cover,
),
),
if (dashboardViewModel.type == WalletType.ethereum) ...[
DashBoardRoundedCardWidget(
isDarkTheme: dashboardViewModel.isDarkTheme,
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
onTap: () =>
Navigator.of(context).pushNamed(Routes.dEuroSavings),
title: S.of(context).deuro_savings,
subTitle: S.of(context).deuro_savings_subtitle,
image: Image.asset(
'assets/images/deuro_icon.png',
height: 80,
width: 80,
fit: BoxFit.cover,
),
),
],
DashBoardRoundedCardWidget(
isDarkTheme: dashboardViewModel.isDarkTheme,
shadowBlur: dashboardViewModel.getShadowBlur(),

View file

@ -0,0 +1,197 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/interest_card_widget.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_edit_sheet.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class DEuroSavingsPage extends BasePage {
final DEuroViewModel _dEuroViewModel;
DEuroSavingsPage(this._dEuroViewModel);
@override
bool get gradientBackground => true;
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(context, scaffold) => GradientBackground(scaffold: scaffold);
@override
String get title => S.current.deuro_savings;
Widget trailing(BuildContext context) => MergeSemantics(
child: SizedBox(
height: 37,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
label: "Refresh",
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurface,
overlayColor: WidgetStateColor.resolveWith(
(states) => Colors.transparent),
),
onPressed: _dEuroViewModel.reloadSavingsUserData,
child: Icon(
Icons.refresh,
color: pageIconColor(context),
size: 20,
),
),
),
),
),
);
@override
Widget body(BuildContext context) {
WidgetsBinding.instance
.addPostFrameCallback((_) => _setReactions(context, _dEuroViewModel));
return Container(
width: double.infinity,
child: Column(
children: <Widget>[
Observer(
builder: (_) => SavingsCard(
isDarkTheme: currentTheme.isDark,
interestRate: "${_dEuroViewModel.interestRate}%",
savingsBalance: _dEuroViewModel.savingsBalance,
currency: CryptoCurrency.deuro,
onAddSavingsPressed: () => _onSavingsAdd(context),
onRemoveSavingsPressed: () => _onSavingsRemove(context),
onApproveSavingsPressed: _dEuroViewModel.prepareApproval,
isEnabled: _dEuroViewModel.isEnabled,
),
),
Observer(
builder: (_) => InterestCardWidget(
isDarkTheme: currentTheme.isDark,
title: S.of(context).deuro_savings_collect_interest,
collectedInterest: _dEuroViewModel.accruedInterest,
onCollectInterest: _dEuroViewModel.prepareCollectInterest,
),
),
],
),
);
}
Future<void> _onSavingsAdd(BuildContext context) async {
final amount = await Navigator.of(context).push(MaterialPageRoute<String>(
builder: (BuildContext context) => SavingEditPage(isAdding: true)));
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, true);
}
Future<void> _onSavingsRemove(BuildContext context) async {
final amount = await Navigator.of(context).push(MaterialPageRoute<String>(
builder: (BuildContext context) => SavingEditPage(isAdding: false)));
if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, false);
}
bool _isReactionsSet = false;
void _setReactions(BuildContext context, DEuroViewModel dEuroViewModel) {
if (_isReactionsSet) return;
reaction((_) => dEuroViewModel.transaction, (PendingTransaction? tx) async {
if (tx == null) return;
final result = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) => ConfirmSendingBottomSheet(
key: ValueKey('savings_page_confirm_sending_dialog_key'),
titleText: S.of(bottomSheetContext).confirm_transaction,
currentTheme: currentTheme,
walletType: WalletType.ethereum,
titleIconPath: CryptoCurrency.deuro.iconPath,
currency: CryptoCurrency.deuro,
amount: S.of(bottomSheetContext).send_amount,
amountValue: tx.amountFormatted,
fiatAmountValue: "",
fee: S.of(bottomSheetContext).send_estimated_fee,
feeValue: tx.feeFormatted,
feeFiatAmount: "",
outputs: [],
onSlideComplete: () async {
Navigator.of(bottomSheetContext).pop(true);
dEuroViewModel.commitTransaction();
},
change: tx.change,
),
);
if (result == null) dEuroViewModel.dismissTransaction();
});
reaction((_) => dEuroViewModel.approvalTransaction, (PendingTransaction? tx) async {
if (tx == null) return;
final result = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) => ConfirmSendingBottomSheet(
key: ValueKey('savings_page_confirm_approval_dialog_key'),
titleText: S.of(bottomSheetContext).approve_tokens,
currentTheme: currentTheme,
walletType: WalletType.ethereum,
titleIconPath: CryptoCurrency.deuro.iconPath,
currency: CryptoCurrency.deuro,
amount: S.of(bottomSheetContext).send_amount,
amountValue: tx.amountFormatted,
fiatAmountValue: "",
fee: S.of(bottomSheetContext).send_estimated_fee,
feeValue: tx.feeFormatted,
feeFiatAmount: "",
outputs: [],
onSlideComplete: () {
Navigator.of(bottomSheetContext).pop(true);
dEuroViewModel.commitApprovalTransaction();
},
change: tx.change,
),
);
if (result == null) dEuroViewModel.dismissTransaction();
});
reaction((_) => dEuroViewModel.state, (ExecutionState state) async {
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!context.mounted) return;
await showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
currentTheme: currentTheme,
titleText: S.of(bottomSheetContext).transaction_sent,
contentImage: 'assets/images/birthday_cake.png',
content: S.of(bottomSheetContext).deuro_tx_commited_content,
actionButtonText: S.of(bottomSheetContext).close,
actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
actionButton: () => Navigator.of(bottomSheetContext).pop(),
),
);
});
}
});
_isReactionsSet = true;
}
}

View file

@ -0,0 +1,67 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart';
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class InterestCardWidget extends StatelessWidget {
InterestCardWidget({
required this.title,
required this.collectedInterest,
super.key,
required this.isDarkTheme,
required this.onCollectInterest,
});
final String title;
final String collectedInterest;
final bool isDarkTheme;
final VoidCallback onCollectInterest;
@override
Widget build(BuildContext context) {
return Stack(children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 16),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
isDarkTheme
? CustomThemeColors.cardGradientColorPrimaryDark
: CustomThemeColors.cardGradientColorPrimaryLight,
isDarkTheme
? CustomThemeColors.cardGradientColorSecondaryDark
: CustomThemeColors.cardGradientColorSecondaryLight,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
children: [
SavingsCard.getAssetBalanceRow(
context,
title: title,
subtitle: collectedInterest,
currency: CryptoCurrency.deuro,
hideSymbol: true,
),
SizedBox(height: 10),
SavingsCard.getButton(
context,
label: S.of(context).deuro_collect_interest,
onPressed: onCollectInterest,
backgroundColor: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
],
),
),
),
]);
}
}

View file

@ -0,0 +1,107 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NumberPad extends StatelessWidget {
final VoidCallback? onDecimalPressed;
final VoidCallback onDeletePressed;
final void Function(int index) onNumberPressed;
final FocusNode focusNode;
const NumberPad({
super.key,
required this.onNumberPressed,
required this.onDeletePressed,
required this.focusNode,
this.onDecimalPressed,
});
@override
Widget build(BuildContext context) => KeyboardListener(
focusNode: focusNode,
onKeyEvent: (keyEvent) {
if (keyEvent is KeyDownEvent) {
if (keyEvent.logicalKey.keyLabel == "Backspace") {
return onDeletePressed();
}
if ([".", ","].contains(keyEvent.logicalKey.keyLabel) &&
onDecimalPressed != null) {
return onDecimalPressed!();
}
int? number = int.tryParse(keyEvent.character ?? '');
if (number != null) return onNumberPressed(number);
}
},
child: SizedBox(
height: 300,
child: GridView.count(
childAspectRatio: 2,
shrinkWrap: true,
crossAxisCount: 3,
physics: const NeverScrollableScrollPhysics(),
children: List.generate(12, (index) {
if (index == 9) {
if (onDecimalPressed == null) return Container();
return InkWell(
onTap: onDecimalPressed,
child: Center(
child: Text(
'.',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 30,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
),
);
} else if (index == 10) {
index = 0;
} else if (index == 11) {
return MergeSemantics(
child: Container(
child: Semantics(
label: S.of(context).delete,
button: true,
onTap: onDeletePressed,
child: TextButton(
onPressed: onDeletePressed,
style: TextButton.styleFrom(
backgroundColor:
Colors.transparent,
shape: CircleBorder(),
),
child: Image.asset(
'assets/images/delete_icon.png',
color: Theme.of(context).colorScheme.primary,
),
),
),
),
);
} else {
index++;
}
return InkWell(
onTap: () => onNumberPressed(index),
child: Center(
child: Text(
'$index',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 30,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
),
);
}),
),
),
);
}

View file

@ -0,0 +1,263 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
class SavingsCard extends StatelessWidget {
final bool isDarkTheme;
final bool isEnabled;
final String interestRate;
final String savingsBalance;
final CryptoCurrency currency;
final VoidCallback onAddSavingsPressed;
final VoidCallback onRemoveSavingsPressed;
final VoidCallback onApproveSavingsPressed;
const SavingsCard({
super.key,
required this.isDarkTheme,
required this.interestRate,
required this.savingsBalance,
required this.currency,
required this.onAddSavingsPressed,
required this.onRemoveSavingsPressed,
required this.onApproveSavingsPressed,
this.isEnabled = true,
});
@override
Widget build(BuildContext context) => Container(
margin: const EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
colors: [
isDarkTheme
? CustomThemeColors.cardGradientColorPrimaryDark
: CustomThemeColors.cardGradientColorPrimaryLight,
isDarkTheme
? CustomThemeColors.cardGradientColorSecondaryDark
: CustomThemeColors.cardGradientColorSecondaryLight,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
getAssetBalanceRow(context,
title: S.of(context).deuro_savings_balance,
subtitle: savingsBalance,
currency: currency),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Expanded(
child: Text(
'Current APR',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color:
Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
softWrap: true,
),
),
Text(
interestRate,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
softWrap: true,
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: isEnabled
? [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_add,
imagePath: 'assets/images/received.png',
onPressed: onAddSavingsPressed,
backgroundColor:
Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
),
SizedBox(width: 12),
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_remove,
imagePath: 'assets/images/upload.png',
onPressed: onRemoveSavingsPressed,
backgroundColor:
Theme.of(context).colorScheme.surface,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
]
: [
Expanded(
child: getButton(
context,
label: S.of(context).deuro_savings_set_approval,
onPressed: onApproveSavingsPressed,
backgroundColor:
Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.onPrimary,
),
)
],
),
],
),
));
static Widget getButton(
BuildContext context, {
required String label,
String? imagePath,
required VoidCallback onPressed,
required Color backgroundColor,
required Color color,
}) =>
Semantics(
label: label,
child: OutlinedButton(
onPressed: onPressed,
style: OutlinedButton.styleFrom(
backgroundColor: backgroundColor,
side: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant.withAlpha(0),
width: 0,
),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (imagePath != null) ...[
Image.asset(
imagePath,
height: 30,
width: 30,
color: color,
),
const SizedBox(width: 8),
],
Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
);
static Widget getAssetBalanceRow(
BuildContext context, {
required String title,
required String subtitle,
required CryptoCurrency currency,
bool hideSymbol = true,
}) =>
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
),
SizedBox(height: 6),
AutoSizeText(
subtitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w900,
fontSize: 24,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.start,
),
],
),
SizedBox(
//width: min(MediaQuery.of(context).size.width * 0.2, 100),
child: Center(
child: Column(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
errorWidget: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title
.substring(0, min(currency.title.length, 2)),
style:
Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainer,
),
),
),
if (!hideSymbol) ...[
const SizedBox(height: 10),
Text(
currency.title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.onSurface,
height: 1,
),
),
]
],
),
),
),
],
);
}

View file

@ -0,0 +1,90 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/integrations/deuro/widgets/numpad.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:flutter/material.dart';
class SavingEditPage extends BasePage {
final bool isAdding;
SavingEditPage({required this.isAdding});
String get title =>
isAdding ? S.current.deuro_savings_add : S.current.deuro_savings_remove;
@override
Widget body(BuildContext context) => _SavingsEditBody();
}
class _SavingsEditBody extends StatefulWidget {
const _SavingsEditBody();
@override
State<StatefulWidget> createState() => _SavingsEditBodyState();
}
class _SavingsEditBodyState extends State<_SavingsEditBody> {
@override
void initState() {
WidgetsBinding.instance
.addPostFrameCallback((_) => _numpadFocusNode.requestFocus());
super.initState();
}
@override
void dispose() {
_numpadFocusNode.dispose();
super.dispose();
}
String amount = '0';
final FocusNode _numpadFocusNode = FocusNode();
@override
Widget build(BuildContext context) => SafeArea(
child: Column(children: [
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.only(left: 26, right: 26, top: 10),
child: AutoSizeText(
"${amount.toString()} dEuro",
maxLines: 1,
maxFontSize: 60,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 60,
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
),
),
)),
NumberPad(
focusNode: _numpadFocusNode,
onNumberPressed: (i) => setState(
() => amount = amount == '0' ? i.toString() : '${amount}${i}',
),
onDeletePressed: () => setState(
() => amount = amount.length > 1
? amount.substring(0, amount.length - 1)
: '0',
),
onDecimalPressed: () =>
setState(() => amount = '${amount.replaceAll('.', '')}.'),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 34),
child: LoadingPrimaryButton(
onPressed: () => Navigator.pop(context, amount),
text: S.of(context).confirm,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
isLoading: false,
isDisabled: false,
),
)
]),
);
}

View file

@ -8,6 +8,8 @@ import 'choose_yat_address_alert.dart';
Future<String> extractAddressFromParsed(
BuildContext context,
ParsedAddress parsedAddress) async {
if (!context.mounted) return parsedAddress.addresses.first;
var title = '';
var content = '';
var address = '';
@ -95,16 +97,17 @@ Future<String> extractAddressFromParsed(
content += S.of(context).choose_address;
address = await showPopUp<String?>(
context: context,
builder: (BuildContext context) {
return WillPopScope(
context: context,
builder: (context) => PopScope(
child: ChooseYatAddressAlert(
alertTitle: title,
alertContent: content,
addresses: parsedAddress.addresses),
onWillPop: () async => false);
}) ?? '';
addresses: parsedAddress.addresses,
),
canPop: false,
),
) ??
'';
if (address.isEmpty) {
return parsedAddress.name;
@ -113,22 +116,20 @@ Future<String> extractAddressFromParsed(
return address;
case ParseFrom.contact:
case ParseFrom.notParsed:
address = parsedAddress.addresses.first;
return address;
return parsedAddress.addresses.first;
}
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: title,
headerTitleText: profileName.isEmpty ? null : profileName,
headerImageProfileUrl: profileImageUrl.isEmpty ? null : profileImageUrl,
alertContent: content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
context: context,
builder: (context) => AlertWithOneAction(
alertTitle: title,
headerTitleText: profileName.isEmpty ? null : profileName,
headerImageProfileUrl: profileImageUrl.isEmpty ? null : profileImageUrl,
alertContent: content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
),
);
return address;
}

View file

@ -1,6 +1,5 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';

View file

@ -7,7 +7,7 @@ class CakeImageWidget extends StatelessWidget {
this.imageUrl,
this.height,
this.width,
this.fit = BoxFit.cover,
this.fit,
this.loadingWidget,
this.errorWidget,
this.color,
@ -16,7 +16,7 @@ class CakeImageWidget extends StatelessWidget {
final String? imageUrl;
final double? height;
final double? width;
final BoxFit fit;
final BoxFit? fit;
final Widget? loadingWidget;
final Widget? errorWidget;
final Color? color;
@ -37,8 +37,7 @@ class CakeImageWidget extends StatelessWidget {
imageUrl!,
height: height,
width: width,
fit: fit,
color: color,
fit: fit ?? BoxFit.contain,
)
: Image.asset(
imageUrl!,
@ -53,8 +52,7 @@ class CakeImageWidget extends StatelessWidget {
imageUrl!,
height: height,
width: width,
fit: fit,
color: color,
fit: fit ?? BoxFit.contain,
placeholderBuilder: (_) {
return loadingWidget ?? const Center(child: CircularProgressIndicator());
},
@ -64,7 +62,7 @@ class CakeImageWidget extends StatelessWidget {
imageUrl!,
height: height,
width: width,
fit: fit,
fit: fit ?? BoxFit.cover,
color: color,
loadingBuilder: (_, Widget child, ImageChunkEvent? progress) {
if (progress == null) return child;

View file

@ -109,6 +109,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
],
),
),
Padding(padding: EdgeInsets.only(left: 10)),
if (image != null) image! else if (svgPicture != null) svgPicture!,
if (icon != null) icon!
],

View file

@ -4,8 +4,10 @@ import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/root_dir.dart';
@ -15,7 +17,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mailer/flutter_mailer.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ExceptionHandler {
@ -113,6 +114,8 @@ class ExceptionHandler {
}
static Future<void> onError(FlutterErrorDetails errorDetails) async {
if (await onLedgerError(errorDetails)) return;
if (kDebugMode || kProfileMode) {
FlutterError.presentError(errorDetails);
printV(errorDetails.toString());
@ -183,6 +186,57 @@ class ExceptionHandler {
_hasError = false;
}
static const List<String> _ledgerErrors = [
'Wrong Device Status',
'PlatformException(133, Failed to write: (Unknown Error: 133), null, null)',
'PlatformException(IllegalArgument, Unknown deviceId:',
'ServiceNotSupportedException(ConnectionType.ble, Required service not supported. Write characteristic: false, Notify characteristic: false)',
'Exception: 6e01', // Wrong App
'Exception: 6d02',
'Exception: 6511',
'Exception: 6e00',
'Exception: 6985',
'Exception: 5515',
];
static bool isLedgerError(Object exception) =>
_ledgerErrors.any((element) => exception.toString().contains(element));
static Future<bool> onLedgerError(FlutterErrorDetails errorDetails) async {
if (!isLedgerError(errorDetails.exception)) return false;
String? interpretErrorCode(String errorCode) {
if (errorCode.contains("6985")) {
return S.current.ledger_error_tx_rejected_by_user;
} else if (errorCode.contains("5515")) {
return S.current.ledger_error_device_locked;
} else
if (["6e01", "6d02", "6511", "6e00"].any((e) => errorCode.contains(e))) {
return S.current.ledger_error_wrong_app;
}
return null;
}
printV(errorDetails.exception);
if (navigatorKey.currentContext != null) {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (context) => AlertWithOneAction(
alertTitle: "Ledger Error",
alertContent:
interpretErrorCode(errorDetails.exception.toString()) ??
S.of(context).ledger_connection_error,
buttonText: S.of(context).close,
buttonAction: () => Navigator.of(context).pop(),
),
);
}
_hasError = false;
return true;
}
/// Ignore User related errors or system errors
static bool _ignoreError(String error) =>
_ignoredErrors.any((element) => error.contains(element));
@ -227,6 +281,7 @@ class ExceptionHandler {
"core/auth_service.dart:64",
"core/key_service.dart:14",
"core/wallet_loading_service.dart:139",
"Wrong Device Status: 0x5515 (UNKNOWN)",
];
static Future<void> _addDeviceInfo(File file) async {

View file

@ -20,9 +20,8 @@ class PayjoinTransactionListItem extends ActionListItem {
String get status {
switch (session.status) {
case 'success':
if (transaction?.isPending == true)
return S.current.payjoin_request_awaiting_tx;
return S.current.successful;
if (transaction?.isPending == false) return S.current.successful;
return S.current.payjoin_request_awaiting_tx;
case 'inProgress':
return S.current.payjoin_request_in_progress;
case 'unrecoverable':

View file

@ -96,7 +96,8 @@ abstract class LedgerViewModelBase with Store {
if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
}
Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
Future<bool> connectLedger(sdk.LedgerDevice device, WalletType type) async {
if (_isConnecting) return false;
_isConnecting = true;
_connectingWalletType = type;
if (isConnected) {
@ -110,17 +111,25 @@ abstract class LedgerViewModelBase with Store {
: ledgerPlusUSB;
if (_connectionChangeSubscription == null) {
_connectionChangeSubscription =
ledger.deviceStateChanges.listen(_connectionChangeListener);
_connectionChangeSubscription = ledger
.deviceStateChanges(device.id)
.listen(_connectionChangeListener);
}
_connection = await ledger.connect(device);
try {
_connection = await ledger.connect(device);
_isConnecting = false;
return true;
} catch (e) {
printV(e);
}
_isConnecting = false;
return false;
}
StreamSubscription<sdk.BleConnectionState>? _connectionChangeSubscription;
sdk.LedgerConnection? _connection;
bool _isConnecting = true;
bool _isConnecting = false;
WalletType? _connectingWalletType;
void _connectionChangeListener(sdk.BleConnectionState event) {
@ -168,17 +177,14 @@ abstract class LedgerViewModelBase with Store {
}
String? interpretErrorCode(String errorCode) {
switch (errorCode) {
case "6985":
return S.current.ledger_error_tx_rejected_by_user;
case "5515":
return S.current.ledger_error_device_locked;
case "6d02": // UNKNOWN_APDU
case "6511":
case "6e00":
return S.current.ledger_error_wrong_app;
default:
return null;
if (errorCode.contains("6985")) {
return S.current.ledger_error_tx_rejected_by_user;
} else if (errorCode.contains("5515")) {
return S.current.ledger_error_device_locked;
} else
if (["6e01", "6a87", "6d02", "6511", "6e00"].any((e) => errorCode.contains(e))) {
return S.current.ledger_error_wrong_app;
}
return null;
}
}

View file

@ -0,0 +1,119 @@
import 'dart:math';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
part 'deuro_view_model.g.dart';
class DEuroViewModel = DEuroViewModelBase with _$DEuroViewModel;
abstract class DEuroViewModelBase with Store {
final AppStore _appStore;
DEuroViewModelBase(this._appStore) {
reloadInterestRate();
reloadSavingsUserData();
}
@observable
String savingsBalance = '0.00';
@observable
ExecutionState state = InitialExecutionState();
@observable
String interestRate = '0';
@observable
String accruedInterest = '0.00';
@observable
BigInt approvedTokens = BigInt.zero;
@computed
bool get isEnabled => approvedTokens > BigInt.zero;
@observable
PendingTransaction? transaction = null;
@observable
PendingTransaction? approvalTransaction = null;
@action
Future<void> reloadSavingsUserData() async {
final savingsBalanceRaw =
ethereum!.getDEuroSavingsBalance(_appStore.wallet!);
final accruedInterestRaw =
ethereum!.getDEuroAccruedInterest(_appStore.wallet!);
approvedTokens = await ethereum!.getDEuroSavingsApproved(_appStore.wallet!);
savingsBalance = ethereum!
.formatterEthereumAmountToDouble(amount: await savingsBalanceRaw)
.toStringAsFixed(6);
accruedInterest = ethereum!
.formatterEthereumAmountToDouble(amount: await accruedInterestRaw)
.toStringAsFixed(6);
}
@action
Future<void> reloadInterestRate() async {
final interestRateRaw =
await ethereum!.getDEuroInterestRate(_appStore.wallet!);
interestRate = (interestRateRaw / BigInt.from(10000)).toString();
}
@action
Future<void> prepareApproval() async {
final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
approvalTransaction =
await ethereum!.enableDEuroSaving(_appStore.wallet!, priority);
}
@action
Future<void> prepareSavingsEdit(String amountRaw, bool isAdding) async {
final amount = BigInt.from(num.parse(amountRaw) * pow(10, 18));
final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
transaction = await (isAdding
? ethereum!.addDEuroSaving(_appStore.wallet!, amount, priority)
: ethereum!.removeDEuroSaving(_appStore.wallet!, amount, priority));
}
Future<void> prepareCollectInterest() =>
prepareSavingsEdit(accruedInterest, false);
@action
Future<void> commitTransaction() async {
if (transaction != null) {
state = TransactionCommitting();
await transaction!.commit();
transaction = null;
reloadSavingsUserData();
state = TransactionCommitted();
}
}
@action
Future<void> commitApprovalTransaction() async {
if (approvalTransaction != null) {
state = TransactionCommitting();
await approvalTransaction!.commit();
approvalTransaction = null;
reloadSavingsUserData();
state = TransactionCommitted();
}
}
@action
void dismissTransaction() {
transaction == null;
approvalTransaction = null;
state = InitialExecutionState();
}
}

View file

@ -69,7 +69,7 @@ abstract class PayjoinDetailsViewModelBase with Store {
title: S.current.error,
value: payjoinSession.error!,
),
if (payjoinSession.txId?.isNotEmpty == true)
if (payjoinSession.txId?.isNotEmpty == true && transactionInfo != null)
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: payjoinSession.txId!,
@ -107,9 +107,8 @@ abstract class PayjoinDetailsViewModelBase with Store {
String _getStatusString() {
switch (payjoinSession.status) {
case 'success':
if (transactionInfo?.isPending == true)
return S.current.payjoin_request_awaiting_tx;
return S.current.successful;
if (transactionInfo?.isPending == false) return S.current.successful;
return S.current.payjoin_request_awaiting_tx;
case 'inProgress':
return S.current.payjoin_request_in_progress;
case 'unrecoverable':

View file

@ -74,7 +74,10 @@ class WalletRestoreFromQRCode {
static String? _extractAddressFromUrl(String rawString, WalletType type) {
try {
return AddressResolver.extractAddressByType(
raw: rawString, type: walletTypeToCryptoCurrency(type));
raw: rawString,
type: walletTypeToCryptoCurrency(type),
requireSurroundingWhitespaces: false,
);
} catch (_) {
return null;
}

View file

@ -80,11 +80,9 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
availableAccounts.addAll(accounts);
_nextIndex += limit;
// } on LedgerException catch (e) {
// error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16));
} catch (e) {
printV(e);
error = S.current.ledger_connection_error;
error = ledgerViewModel.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
}
isLoadingMoreAccounts = false;

View file

@ -114,7 +114,7 @@ dependencies:
ledger_flutter_plus:
git:
url: https://github.com/vespr-wallet/ledger-flutter-plus
ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76
ref: 60817d4b20144f9da9029f5034790272795b9d38
hashlib: ^1.19.2
on_chain:
git:
@ -171,7 +171,7 @@ dependency_overrides:
ledger_flutter_plus:
git:
url: https://github.com/vespr-wallet/ledger-flutter-plus
ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76
ref: 60817d4b20144f9da9029f5034790272795b9d38
socks5_proxy:
git:
url: https://github.com/LacticWhale/socks_dart.git
@ -180,6 +180,8 @@ dependency_overrides:
git:
url: https://github.com/MrCyjaneK/flutter-tor_binary
ref: cb811c610871a9517d47134b87c2f590c15c96c5
web_socket_channel: ^3.0.2
freezed_annotation: 2.4.4
flutter_icons:
image_path: "assets/images/app_logo.png"

View file

@ -56,6 +56,7 @@
"apk_update": "تحديث APK",
"approve": "ﺪﻤﺘﻌﻳ",
"approve_request": "الموافقة على الطلب",
"approve_tokens": "الموافقة على الرموز",
"arrive_in_this_address": "سيصل ${currency} ${tag}إلى هذا العنوان",
"ascending": "تصاعدي",
"ask_each_time": "اسأل في كل مرة",
@ -244,6 +245,15 @@
"descending": "النزول",
"description": "ﻒﺻﻭ",
"destination_tag": "علامة الوجهة:",
"deuro_collect_interest": "يجمع",
"deuro_savings": "الادخار ديورو",
"deuro_savings_add": "إيداع",
"deuro_savings_balance": "توازن الادخار",
"deuro_savings_collect_interest": "جمع الاهتمام",
"deuro_savings_remove": "ينسحب",
"deuro_savings_set_approval": "تعيين الموافقة",
"deuro_savings_subtitle": "كسب ما يصل إلى 10 ٪ فائدة على مقتنيات Deuro Stablecoin",
"deuro_tx_commited_content": "قد يستغرق الأمر بضع ثوانٍ حتى يتم تأكيد المعاملة وينعكس على الشاشة",
"device_is_signing": "الجهاز يوقع",
"dfx_option_description": "شراء التشفير مع EUR & CHF. لعملاء البيع بالتجزئة والشركات في أوروبا",
"didnt_get_code": "لم تحصل على رمز؟",
@ -975,6 +985,7 @@
"transport_type": "نوع النقل",
"trongrid_history": "تاريخ ترونغريد",
"trusted": "موثوق به",
"try_again": "حاول ثانية",
"tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.",
"tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.",
"tx_commit_failed_no_peers": "فشل المعاملة في البث ، يرجى المحاولة مرة أخرى في ثانية أو نحو ذلك",

View file

@ -56,6 +56,7 @@
"apk_update": "APK ъпдейт",
"approve": "Одобряване",
"approve_request": "Одобрете искане",
"approve_tokens": "Одобрете жетоните",
"arrive_in_this_address": "${currency} ${tag}ще отидат на този адрес",
"ascending": "Възходящ",
"ask_each_time": "Питайте всеки път",
@ -244,6 +245,15 @@
"descending": "Низходящ",
"description": "Описание",
"destination_tag": "Destination tag:",
"deuro_collect_interest": "Събиране",
"deuro_savings": "Спестявания на Деуро",
"deuro_savings_add": "Депозит",
"deuro_savings_balance": "Спестотен баланс",
"deuro_savings_collect_interest": "Събиране на интерес",
"deuro_savings_remove": "Оттегляне",
"deuro_savings_set_approval": "Задайте одобрение",
"deuro_savings_subtitle": "Печелете до 10% лихва за вашите Deuro Stablecoin Holdings",
"deuro_tx_commited_content": "Може да отнеме няколко секунди, за да може транзакцията да се потвърди и да бъде отразена на екрана",
"device_is_signing": "Устройството подписва",
"dfx_option_description": "Купете криптовалута с Eur & CHF. За търговски и корпоративни клиенти в Европа",
"didnt_get_code": "Не получихте код?",
@ -975,6 +985,7 @@
"transport_type": "Тип транспорт",
"trongrid_history": "Trongrid History",
"trusted": "Надежден",
"try_again": "Опитайте отново",
"tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.",
"tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.",
"tx_commit_failed_no_peers": "Сделката не успя да излъчи, моля, опитайте отново след секунда или така",

View file

@ -56,6 +56,7 @@
"apk_update": "aktualizace APK",
"approve": "Schvalovat",
"approve_request": "Schválit žádost",
"approve_tokens": "Schválit tokeny",
"arrive_in_this_address": "${currency} ${tag}přijde na tuto adresu",
"ascending": "Vzestupné",
"ask_each_time": "Zeptejte se pokaždé",
@ -244,6 +245,15 @@
"descending": "Klesající",
"description": "Popis",
"destination_tag": "Destination Tag:",
"deuro_collect_interest": "Sbírat",
"deuro_savings": "dEuro úspory",
"deuro_savings_add": "Vklad",
"deuro_savings_balance": "Úspora zůstatek",
"deuro_savings_collect_interest": "Sbírat zájem",
"deuro_savings_remove": "Odstoupit",
"deuro_savings_set_approval": "Stanovit schválení",
"deuro_savings_subtitle": "Získejte až 10% úrok z vašeho Deuro Stablecoin Holdings",
"deuro_tx_commited_content": "Transakce může trvat několik sekund, aby se potvrdila a odrážela se na obrazovce",
"device_is_signing": "Zařízení se podpisu",
"dfx_option_description": "Koupit krypto s EUR & CHF. Pro maloobchodní a firemní zákazníky v Evropě",
"didnt_get_code": "Nepřišel Vám kód?",
@ -975,6 +985,7 @@
"transport_type": "Typ transportu",
"trongrid_history": "Trongridní historie",
"trusted": "Důvěřovat",
"try_again": "Zkuste to znovu",
"tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.",
"tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.",
"tx_commit_failed_no_peers": "Transakce se nepodařilo vysílat, zkuste to prosím znovu za vteřinu",

View file

@ -56,6 +56,7 @@
"apk_update": "APK-Update",
"approve": "Genehmigen",
"approve_request": "Anfrage genehmigen",
"approve_tokens": "Token genehmigen",
"arrive_in_this_address": "${currency} ${tag} wird an dieser Adresse ankommen",
"ascending": "Aufsteigend",
"ask_each_time": "Jedes Mal fragen",
@ -206,7 +207,7 @@
"copy": "Kopieren",
"copy_address": "Adresse kopieren",
"copy_id": "ID kopieren",
"copy_payjoin_address": "Kopieren Sie Payjoin -Adresse",
"copy_payjoin_address": "Kopieren Sie Payjoin-Adresse",
"copy_payjoin_url": "Payjoin URL kopieren",
"copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein",
"corrupted_seed_notice": "Die Dateien für diese Wallet sind beschädigt und können nicht geöffnet werden. Bitte sehen Sie sich die Seeds an, speichern Sie sie und stellen Sie die Wallet wieder her.\n\nWenn der Wert leer ist, konnte der Seed nicht korrekt wiederhergestellt werden.",
@ -244,6 +245,15 @@
"descending": "Absteigend",
"description": "Beschreibung",
"destination_tag": "Ziel-Tag:",
"deuro_collect_interest": "Auszahlen",
"deuro_savings": "dEuro-Savings",
"deuro_savings_add": "Einzahlen",
"deuro_savings_balance": "Sparguthaben",
"deuro_savings_collect_interest": "Interesse sammeln",
"deuro_savings_remove": "Auszahlen",
"deuro_savings_set_approval": "Genehmigung festlegen",
"deuro_savings_subtitle": "Verdienen Sie bis zu 10% Zinsen für Ihre dEuro Stablecoin Holdings",
"deuro_tx_commited_content": "Es kann ein paar Sekunden dauern, bis die Transaktion bestätigt und auf dem Bildschirm angezeigt",
"device_is_signing": "Das Gerät unterschreibt",
"dfx_option_description": "Kaufen Sie Krypto mit EUR & CHF. Für Einzelhandel und Unternehmenskunden in Europa",
"didnt_get_code": "Kein Code?",
@ -976,6 +986,7 @@
"transport_type": "Transporttyp",
"trongrid_history": "Trongrid-Historie",
"trusted": "Vertrauenswürdige",
"try_again": "Erneut versuchen",
"tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.",
"tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.",
"tx_commit_failed_no_peers": "Transaktion konnte nicht übertragen werden. Bitte versuchen Sie es in einer Sekunde oder so erneut",
@ -1104,4 +1115,4 @@
"you_will_send": "Konvertieren von",
"youCanGoBackToYourDapp": "Sie können jetzt zu Ihrem Dapp zurückkehren",
"yy": "YY"
}
}

View file

@ -56,6 +56,7 @@
"apk_update": "APK update",
"approve": "Approve",
"approve_request": "Approve Request",
"approve_tokens": "Approve tokens",
"arrive_in_this_address": "${currency} ${tag}will arrive in this address",
"ascending": "Ascending",
"ask_each_time": "Ask each time",
@ -244,6 +245,15 @@
"descending": "Descending",
"description": "Description",
"destination_tag": "Destination tag:",
"deuro_collect_interest": "Collect",
"deuro_savings": "dEuro Savings",
"deuro_savings_add": "Deposit",
"deuro_savings_balance": "Savings Balance",
"deuro_savings_collect_interest": "Collect interest",
"deuro_savings_remove": "Withdraw",
"deuro_savings_set_approval": "Set approval",
"deuro_savings_subtitle": "Earn up to 10% interest on your dEuro Stablecoin holdings",
"deuro_tx_commited_content": "It might take a couple of seconds for the transaction to confirm and be reflected on screen",
"device_is_signing": "Device is signing",
"dfx_option_description": "Buy crypto with EUR & CHF. For retail and corporate customers in Europe",
"didnt_get_code": "Didn't get code?",
@ -976,6 +986,7 @@
"transport_type": "Transport Type",
"trongrid_history": "TronGrid history",
"trusted": "Trusted",
"try_again": "Try again",
"tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.",
"tx_commit_failed": "Transaction commit failed. Please contact support.",
"tx_commit_failed_no_peers": "Transaction failed to broadcast, please try again in a second or so",

View file

@ -56,6 +56,7 @@
"apk_update": "Actualización de APK",
"approve": "Aprobar",
"approve_request": "Aprobar la solicitud",
"approve_tokens": "Aprobar tokens",
"arrive_in_this_address": "${currency} ${tag}llegará a esta dirección",
"ascending": "Ascendente",
"ask_each_time": "Pregunta cada vez",
@ -244,6 +245,15 @@
"descending": "Descendente",
"description": "Descripción",
"destination_tag": "Etiqueta de destino:",
"deuro_collect_interest": "Recolectar",
"deuro_savings": "ahorros de deuro",
"deuro_savings_add": "Depósito",
"deuro_savings_balance": "Saldo de ahorro",
"deuro_savings_collect_interest": "Cobrar interés",
"deuro_savings_remove": "Retirar",
"deuro_savings_set_approval": "Establecer aprobación",
"deuro_savings_subtitle": "Gane hasta un 10% de interés en sus Holdings de Deuro Stablecoin",
"deuro_tx_commited_content": "La transacción puede tardar un par de segundos en confirmar y reflejarse en la pantalla",
"device_is_signing": "El dispositivo está firmando",
"dfx_option_description": "Compre cripto con EUR y CHF. Para clientes minoristas y corporativos en Europa",
"didnt_get_code": "¿No recibiste el código?",
@ -976,6 +986,7 @@
"transport_type": "Tipo de transporte",
"trongrid_history": "Historia trongrid",
"trusted": "de confianza",
"try_again": "Intentar otra vez",
"tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.",
"tx_commit_failed": "La confirmación de transacción falló. Ponte en contacto con el soporte.",
"tx_commit_failed_no_peers": "La transacción no se transmitió, intenta nuevamente en un segundo más o menos",

View file

@ -56,6 +56,7 @@
"apk_update": "Mise à jour d'APK",
"approve": "Approuver",
"approve_request": "Approuver la demande",
"approve_tokens": "Approuver les jetons",
"arrive_in_this_address": "${currency} ${tag}arrivera à cette adresse",
"ascending": "Ascendant",
"ask_each_time": "Demander à chaque fois",
@ -244,6 +245,15 @@
"descending": "Descendant",
"description": "Description",
"destination_tag": "Tag de destination :",
"deuro_collect_interest": "Collecter",
"deuro_savings": "Économies de deuro",
"deuro_savings_add": "Dépôt",
"deuro_savings_balance": "Solde d'épargne",
"deuro_savings_collect_interest": "Percevoir l'intérêt",
"deuro_savings_remove": "Retirer",
"deuro_savings_set_approval": "Établir l'approbation",
"deuro_savings_subtitle": "Gagnez jusqu'à 10% d'intérêt sur vos avoirs de Deuro Stablecoin",
"deuro_tx_commited_content": "Il pourrait prendre quelques secondes pour que la transaction confirme et soit reflétée à l'écran",
"device_is_signing": "L'appareil signale",
"dfx_option_description": "Achetez de la crypto avec EUR & CHF. Pour les clients de la vente au détail et des entreprises en Europe",
"didnt_get_code": "Vous n'avez pas reçu le code ?",
@ -975,6 +985,7 @@
"transport_type": "Type de transport",
"trongrid_history": "Histoire de la trongride",
"trusted": "de confiance",
"try_again": "Essayer à nouveau",
"tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.",
"tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.",
"tx_commit_failed_no_peers": "La transaction n'a pas été diffusée, veuillez réessayer dans une seconde environ",

View file

@ -56,6 +56,7 @@
"apk_update": "apk sabunta",
"approve": "Amincewa",
"approve_request": "Amince da bukata",
"approve_tokens": "Amince da Alamu",
"arrive_in_this_address": "${currency} ${tag} zai je wurin wannan adireshi",
"ascending": "Hau",
"ask_each_time": "Tambaya kowane lokaci",
@ -244,6 +245,15 @@
"descending": "Saukowa",
"description": "Bayani",
"destination_tag": "Tambarin makoma:",
"deuro_collect_interest": "Tara",
"deuro_savings": "deuro tanadi",
"deuro_savings_add": "Yi ajiya",
"deuro_savings_balance": "Ma'auni",
"deuro_savings_collect_interest": "Tattara amfani da sha'awa",
"deuro_savings_remove": "Janye",
"deuro_savings_set_approval": "Saita yarda",
"deuro_savings_subtitle": "Sami har zuwa 10% sha'awa a kan Deuro Stovecoin Rike",
"deuro_tx_commited_content": "Yana iya ɗaukar wasu secondsan seconds don ma'amala don tabbatarwa kuma a nuna shi a allon",
"device_is_signing": "Na'urar tana shiga",
"dfx_option_description": "Buy crypto tare da Eur & Chf. Don Retail da abokan ciniki na kamfanoni a Turai",
"didnt_get_code": "Ba a samun code?",
@ -977,6 +987,7 @@
"transport_type": "Nau'in sufuri",
"trongrid_history": "Tarihin Trongrid",
"trusted": "Amintacce",
"try_again": "Gwada kuma",
"tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.",
"tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.",
"tx_commit_failed_no_peers": "Kasuwanci ya kasa watsa, don Allah sake gwadawa a cikin na biyu ko",

View file

@ -56,6 +56,7 @@
"apk_update": "APK अद्यतन",
"approve": "मंज़ूरी देना",
"approve_request": "अनुरोध को स्वीकृत करें",
"approve_tokens": "टोकन को मंजूरी देना",
"arrive_in_this_address": "${currency} ${tag}इस पते पर पहुंचेंगे",
"ascending": "आरोही",
"ask_each_time": "हर बार पूछें",
@ -244,6 +245,15 @@
"descending": "अवरोही",
"description": "विवरण",
"destination_tag": "गंतव्य टैग:",
"deuro_collect_interest": "इकट्ठा करना",
"deuro_savings": "देउरो बचत",
"deuro_savings_add": "जमा",
"deuro_savings_balance": "बचत शेष",
"deuro_savings_collect_interest": "ब्याज इकट्ठा करना",
"deuro_savings_remove": "निकालना",
"deuro_savings_set_approval": "अनुमोदन निर्धारित करना",
"deuro_savings_subtitle": "अपने Deuro Stablecoin होल्डिंग्स पर 10% ब्याज कमाएँ",
"deuro_tx_commited_content": "लेन -देन की पुष्टि करने और स्क्रीन पर प्रतिबिंबित होने के लिए कुछ सेकंड लग सकते हैं",
"device_is_signing": "उपकरण हस्ताक्षर कर रहा है",
"dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए",
"didnt_get_code": "कोड नहीं मिला?",
@ -977,6 +987,7 @@
"transport_type": "परिवहन प्रकार",
"trongrid_history": "ट्रॉन्ग्रिड का इतिहास",
"trusted": "भरोसा",
"try_again": "पुनः प्रयास करें",
"tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।",
"tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।",
"tx_commit_failed_no_peers": "लेन -देन प्रसारित करने में विफल रहा, कृपया एक या दो सेकंड में पुनः प्रयास करें",

View file

@ -56,6 +56,7 @@
"apk_update": "APK ažuriranje",
"approve": "Odobriti",
"approve_request": "Odobriti zahtjev",
"approve_tokens": "Odobriti tokene",
"arrive_in_this_address": "${currency} ${tag}će stići na ovu adresu",
"ascending": "Uzlazni",
"ask_each_time": "Pitajte svaki put",
@ -244,6 +245,15 @@
"descending": "Silazni",
"description": "Opis",
"destination_tag": "Odredišna oznaka:",
"deuro_collect_interest": "Prikupiti",
"deuro_savings": "deuro ušteda",
"deuro_savings_add": "Depozit",
"deuro_savings_balance": "Ravnoteža uštede",
"deuro_savings_collect_interest": "Prikupiti interes",
"deuro_savings_remove": "Povući",
"deuro_savings_set_approval": "Odrediti odobrenje",
"deuro_savings_subtitle": "Zaradite do 10% kamate na svoje Deuro Stablecoin Holdings",
"deuro_tx_commited_content": "Možda će trebati nekoliko sekundi da se transakcija potvrdi i odrazi na zaslonu",
"device_is_signing": "Uređaj se potpisuje",
"dfx_option_description": "Kupite kriptovalute s Eur & CHF. Za maloprodajne i korporativne kupce u Europi",
"didnt_get_code": "Ne dobivate kod?",
@ -975,6 +985,7 @@
"transport_type": "Transportni tip",
"trongrid_history": "Povijest Trongrida",
"trusted": "vjerovao",
"try_again": "Pokušajte ponovo",
"tx_commit_exception_no_dust_on_change": "Transakcija se odbija s tim iznosom. Pomoću ovih kovanica možete poslati ${min} bez promjene ili ${max} koja vraća promjenu.",
"tx_commit_failed": "Obveza transakcije nije uspjela. Molimo kontaktirajte podršku.",
"tx_commit_failed_no_peers": "Transakcija nije uspjela emitirati, pokušajte ponovo u sekundi ili tako",

View file

@ -56,6 +56,7 @@
"apk_update": "APK թարմացում",
"approve": "Հաստատել",
"approve_request": "Հաստատում է հայցը",
"approve_tokens": "Հաստատում է նշանները",
"arrive_in_this_address": "${currency} ${tag}կժամանի այս հասցեում",
"ascending": "Աճող",
"ask_each_time": "Հարցնել ամեն անգամ",
@ -244,6 +245,15 @@
"descending": "Նվազող",
"description": "Նկարագրություն",
"destination_tag": "Նպատակակետի պիտակ:",
"deuro_collect_interest": "Հավաքել",
"deuro_savings": "dEuro խնայողություններ",
"deuro_savings_add": "Ավանդ",
"deuro_savings_balance": "Խնայողական հավասարակշռություն",
"deuro_savings_collect_interest": "Հավաքեք հետաքրքրություն",
"deuro_savings_remove": "Հեռացնել",
"deuro_savings_set_approval": "Սահմանել հաստատում",
"deuro_savings_subtitle": "Վաստակեք մինչեւ 10% տոկոսադրույքներ ձեր Deuro Stablecoin Holdings- ի համար",
"deuro_tx_commited_content": "Գործարքի հաստատման եւ արտացոլվելու համար գործարքի համար կարող է տեւել մի քանի վայրկյան",
"device_is_signing": "Սարքը ստորագրում է",
"dfx_option_description": "Գնեք կրիպտոարժույթ EUR և CHF: Կորպորատիվ և մանրածախ հաճախորդների համար Եվրոպայում",
"didnt_get_code": "Չեք ստացել կոդը?",
@ -973,6 +983,7 @@
"transport_type": "Տրանսպորտի տեսակը",
"trongrid_history": "TronGrid պատմություն",
"trusted": "Վստահելի",
"try_again": "Կրկին փորձեք",
"tx_commit_exception_no_dust_on_change": "Փոխանցումը մերժվել է այս գումարով: Այս արժույթներով կարող եք ուղարկել ${min} առանց փոփոխության կամ ${max} որը վերադարձնում է փոփոխությունը",
"tx_commit_failed": "Փոխանցումը ձախողվել է: Խնդրում ենք դիմել աջակցությանը",
"tx_commit_failed_no_peers": "Գործարքը չի հաջողվել հեռարձակել, խնդրում ենք կրկին փորձել մեկ վայրկյանում",

View file

@ -56,6 +56,7 @@
"apk_update": "Pembaruan APK",
"approve": "Menyetujui",
"approve_request": "Menyetujui permintaan",
"approve_tokens": "Menyetujui token",
"arrive_in_this_address": "${currency} ${tag} akan tiba di alamat ini",
"ascending": "Naik",
"ask_each_time": "Tanyakan setiap kali",
@ -244,6 +245,15 @@
"descending": "Menurun",
"description": "Keterangan",
"destination_tag": "Tag tujuan:",
"deuro_collect_interest": "Mengumpulkan",
"deuro_savings": "Tabungan dEuro",
"deuro_savings_add": "Deposito",
"deuro_savings_balance": "Keseimbangan tabungan",
"deuro_savings_collect_interest": "Mengumpulkan minat",
"deuro_savings_remove": "Menarik",
"deuro_savings_set_approval": "Tetapkan persetujuan",
"deuro_savings_subtitle": "Hasilkan hingga 10% bunga di Deuro Stablecoin Holdings Anda",
"deuro_tx_commited_content": "Mungkin butuh beberapa detik untuk transaksi untuk mengkonfirmasi dan direfleksikan di layar",
"device_is_signing": "Perangkat sedang menandatangani",
"dfx_option_description": "Beli crypto dengan EUR & CHF. Untuk pelanggan ritel dan perusahaan di Eropa",
"didnt_get_code": "Tidak mendapatkan kode?",
@ -978,6 +988,7 @@
"transport_type": "Jenis transportasi",
"trongrid_history": "Sejarah Trongrid",
"trusted": "Dipercayai",
"try_again": "Coba lagi",
"tx_commit_exception_no_dust_on_change": "Transaksi ditolak dengan jumlah ini. Dengan koin ini Anda dapat mengirim ${min} tanpa perubahan atau ${max} yang mengembalikan perubahan.",
"tx_commit_failed": "Transaksi Gagal. Silakan hubungi Dukungan.",
"tx_commit_failed_no_peers": "Transaksi gagal untuk disiarkan, silakan coba lagi sebentar lagi",

View file

@ -56,6 +56,7 @@
"apk_update": "Aggiornamento APK",
"approve": "Approvare",
"approve_request": "Approvare la richiesta",
"approve_tokens": "Approvare i token",
"arrive_in_this_address": "${currency} ${tag}arriverà a questo indirizzo",
"ascending": "Ascendente",
"ask_each_time": "Chiedi ogni volta",
@ -244,6 +245,15 @@
"descending": "Discendente",
"description": "Descrizione",
"destination_tag": "Tag destinazione:",
"deuro_collect_interest": "Raccogliere",
"deuro_savings": "Risparmio di dEuro",
"deuro_savings_add": "Depositare",
"deuro_savings_balance": "Saldo di risparmio",
"deuro_savings_collect_interest": "Raccogliere interesse",
"deuro_savings_remove": "Ritirare",
"deuro_savings_set_approval": "Impostare l'approvazione",
"deuro_savings_subtitle": "Guadagna fino al 10% di interesse su Deuro StableCoin Holdings",
"deuro_tx_commited_content": "Potrebbero essere necessari un paio di secondi per confermare la transazione ed essere riflessa sullo schermo",
"device_is_signing": "Il dispositivo sta firmando",
"dfx_option_description": "Acquista Crypto con EUR & CHF. Per i clienti al dettaglio e aziendali in Europa",
"didnt_get_code": "Non hai ricevuto il codice?",
@ -976,6 +986,7 @@
"transport_type": "Tipo di trasporto",
"trongrid_history": "Cronologia TronGrid",
"trusted": "Fidato",
"try_again": "Riprova",
"tx_commit_exception_no_dust_on_change": "La transazione viene respinta con questo importo. Con queste monete è possibile inviare ${min} senza modifiche o ${max} che restituisce il cambiamento.",
"tx_commit_failed": "Commit di transazione non riuscita. Si prega di contattare il supporto.",
"tx_commit_failed_no_peers": "Errore nella trasmissione della transazione, si prega di provare nuovamente",

View file

@ -56,6 +56,7 @@
"apk_update": "APKアップデート",
"approve": "承認する",
"approve_request": "リクエストを承認します",
"approve_tokens": "トークンを承認します",
"arrive_in_this_address": "${currency} ${tag}はこの住所に到着します",
"ascending": "上昇",
"ask_each_time": "毎回尋ねてください",
@ -244,6 +245,15 @@
"descending": "下降",
"description": "説明",
"destination_tag": "宛先タグ:",
"deuro_collect_interest": "集める",
"deuro_savings": "dEuro Savings",
"deuro_savings_add": "デポジット",
"deuro_savings_balance": "貯蓄バランス",
"deuro_savings_collect_interest": "興味を集めます",
"deuro_savings_remove": "撤回する",
"deuro_savings_set_approval": "承認を設定します",
"deuro_savings_subtitle": "Deuro Stablecoin Holdingsで最大10の利息を稼ぐ",
"deuro_tx_commited_content": "トランザクションが確認され、画面に反映されるまでに数秒かかる場合があります",
"device_is_signing": "デバイスが署名しています",
"dfx_option_description": "EURCHFで暗号を購入します。ヨーロッパの小売および企業の顧客向け",
"didnt_get_code": "コードを取得しませんか?",
@ -976,6 +986,7 @@
"transport_type": "輸送タイプ",
"trongrid_history": "トロンリッドの歴史",
"trusted": "信頼できる",
"try_again": "もう一度やり直してください",
"tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。",
"tx_commit_failed": "トランザクションコミットは失敗しました。サポートに連絡してください。",
"tx_commit_failed_no_peers": "トランザクションはブロードキャストに失敗しました。一瞬かそこらで再試行してください",

View file

@ -56,6 +56,7 @@
"apk_update": "APK 업데이트",
"approve": "승인",
"approve_request": "요청 승인",
"approve_tokens": "토큰을 승인합니다",
"arrive_in_this_address": "${currency} ${tag}이(가) 이 주소로 도착합니다",
"ascending": "오름차순",
"ask_each_time": "매번 묻기",
@ -244,6 +245,15 @@
"descending": "내림차순",
"description": "설명",
"destination_tag": "목적지 태그:",
"deuro_collect_interest": "모으다",
"deuro_savings": "도로 저축",
"deuro_savings_add": "보증금",
"deuro_savings_balance": "저축 잔고",
"deuro_savings_collect_interest": "관심을 모으십시오",
"deuro_savings_remove": "철회하다",
"deuro_savings_set_approval": "승인을 설정하십시오",
"deuro_savings_subtitle": "Deuro Stablecoin Holdings에 최대 10%의이자를 받으십시오.",
"deuro_tx_commited_content": "트랜잭션이 확인하고 화면에 반영되는 데 몇 초가 걸릴 수 있습니다.",
"device_is_signing": "장치가 서명 중입니다",
"dfx_option_description": "EUR 및 CHF로 암호화폐 구매. 유럽의 개인 및 기업 고객 대상",
"didnt_get_code": "코드를 받지 못했나요?",
@ -976,6 +986,7 @@
"transport_type": "전송 유형",
"trongrid_history": "TronGrid 내역",
"trusted": "신뢰됨",
"try_again": "다시 시도하십시오",
"tx_commit_exception_no_dust_on_change": "이 금액으로는 트랜잭션이 거부됩니다. 이 코인으로는 잔돈 없이 ${min}을(를) 보내거나 잔돈이 반환되는 ${max}을(를) 보낼 수 있습니다.",
"tx_commit_failed": "트랜잭션 커밋 실패. 지원팀에 문의하세요.",
"tx_commit_failed_no_peers": "트랜잭션 전파 실패. 잠시 후 다시 시도하세요.",

View file

@ -56,6 +56,7 @@
"apk_update": "APK အပ်ဒိတ်",
"approve": "လက်မခံပါ။",
"approve_request": "တောင်းဆိုမှုကိုအတည်ပြု",
"approve_tokens": "တိုကင်အတည်ပြု",
"arrive_in_this_address": "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။",
"ascending": "တက်",
"ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ",
@ -244,6 +245,15 @@
"descending": "ဆင်း",
"description": "ဖော်ပြချက်",
"destination_tag": "ခရီးဆုံးအမှတ်-",
"deuro_collect_interest": "စုဝေး",
"deuro_savings": "dEuro ငွေစု",
"deuro_savings_add": "အပ်ငေှ",
"deuro_savings_balance": "ငွေစုချိန်ခွင်လျှာ",
"deuro_savings_collect_interest": "အကျိုးစီးပွားစုဆောင်းပါ",
"deuro_savings_remove": "ဆုတ်ခွာ",
"deuro_savings_set_approval": "အတည်ပြုချက်ကိုသတ်မှတ်ပါ",
"deuro_savings_subtitle": "သင်၏ Deuro Stabloin Holdings တွင် 10% အထိစိတ်ဝင်စားပါ",
"deuro_tx_commited_content": "၎င်းသည်ငွေပေးငွေယူကိုအတည်ပြုရန်နှင့်မျက်နှာပြင်ပေါ်တွင်ထင်ဟပ်ရန်စက္ကန့်အနည်းငယ်ကြာနိုင်သည်",
"device_is_signing": "ကိရိယာလက်မှတ်ထိုးနေသည်",
"dfx_option_description": "Crypto ကို EUR & CHF ဖြင့် 0 ယ်ပါ။ လက်လီရောင်းဝယ်မှုနှင့်ဥရောပရှိကော်ပိုရိတ်ဖောက်သည်များအတွက်",
"didnt_get_code": "ကုဒ်ကို မရဘူးလား?",
@ -975,6 +985,7 @@
"transport_type": "သယ်ယူပို့ဆောင်ရေးအမျိုးအစား",
"trongrid_history": "Trongrid သမိုင်း",
"trusted": "ယုံတယ်။",
"try_again": "ထပ်ကြိုးစားပါ",
"tx_commit_exception_no_dust_on_change": "အဆိုပါငွေပေးငွေယူကဒီပမာဏနှင့်အတူပယ်ချခံရသည်။ ဤဒင်္ဂါးပြားများနှင့်အတူပြောင်းလဲမှုကိုပြန်လည်ပြောင်းလဲခြင်းသို့မဟုတ် ${min} မပါဘဲ ${max} ပေးပို့နိုင်သည်။",
"tx_commit_failed": "ငွေပေးငွေယူကျူးလွန်မှုပျက်ကွက်။ ကျေးဇူးပြုပြီးပံ့ပိုးမှုဆက်သွယ်ပါ။",
"tx_commit_failed_no_peers": "ငွေပေးငွေယူထုတ်လွှင့်ရန်ပျက်ကွက်ပါက ကျေးဇူးပြု. ဒုတိယသို့မဟုတ်ထိုအတိုင်းထပ်မံကြိုးစားပါ",

View file

@ -56,6 +56,7 @@
"apk_update": "APK-update",
"approve": "Goedkeuren",
"approve_request": "Het verzoek goedkeuren",
"approve_tokens": "Tokens goedkeuren",
"arrive_in_this_address": "${currency} ${tag}komt aan op dit adres",
"ascending": "Stijgend",
"ask_each_time": "Vraag het elke keer",
@ -244,6 +245,15 @@
"descending": "Aflopend",
"description": "Beschrijving",
"destination_tag": "Bestemmingstag:",
"deuro_collect_interest": "Verzamelen",
"deuro_savings": "dEuro -besparingen",
"deuro_savings_add": "Borg",
"deuro_savings_balance": "Spaarbalans",
"deuro_savings_collect_interest": "Verzamel interesse",
"deuro_savings_remove": "Terugtrekken",
"deuro_savings_set_approval": "Goedkeuring instellen",
"deuro_savings_subtitle": "Verdien tot 10% rente op uw Deuro Stablecoin Holdings",
"deuro_tx_commited_content": "Het kan een paar seconden duren voordat de transactie wordt bevestigd en weerspiegeld op het scherm",
"device_is_signing": "Apparaat ondertekent",
"dfx_option_description": "Koop crypto met EUR & CHF. Voor retail- en zakelijke klanten in Europa",
"didnt_get_code": "Geen code?",
@ -975,6 +985,7 @@
"transport_type": "Transporttype",
"trongrid_history": "Trongrid geschiedenis",
"trusted": "vertrouwd",
"try_again": "Probeer het opnieuw",
"tx_commit_exception_no_dust_on_change": "De transactie wordt afgewezen met dit bedrag. Met deze munten kunt u ${min} verzenden zonder verandering of ${max} die wijziging retourneert.",
"tx_commit_failed": "Transactiebewissing is mislukt. Neem contact op met de ondersteuning.",
"tx_commit_failed_no_peers": "De transactie is niet uitgezonden, probeer het opnieuw binnen een seconde of zo",

View file

@ -56,6 +56,7 @@
"apk_update": "Aktualizacja APK",
"approve": "Zatwierdzić",
"approve_request": "Zatwierdzić żądanie",
"approve_tokens": "Zatwierdzić tokeny",
"arrive_in_this_address": "${currency} ${tag}dotrze na ten adres",
"ascending": "Wznoszący się",
"ask_each_time": "Zapytaj za każdym razem",
@ -244,6 +245,15 @@
"descending": "Malejąco",
"description": "Opis",
"destination_tag": "Tag docelowy:",
"deuro_collect_interest": "Zbierać",
"deuro_savings": "dEuro oszczędności",
"deuro_savings_add": "Depozyt",
"deuro_savings_balance": "Równowaga oszczędności",
"deuro_savings_collect_interest": "Zbieraj zainteresowanie",
"deuro_savings_remove": "Wycofać",
"deuro_savings_set_approval": "Ustaw zatwierdzenie",
"deuro_savings_subtitle": "Zarabiaj do 10% odsetek od Deuro Stablecoin Holdings",
"deuro_tx_commited_content": "Potwierdzenie i odbicie na ekranie może potrwać kilka sekund",
"device_is_signing": "Urządzenie podpisuje",
"dfx_option_description": "Kup krypto za EUR & CHF. Dla klientów prywatnych i korporacyjnych w Europie",
"didnt_get_code": "Nie dostałeś kodu?",
@ -974,6 +984,7 @@
"transport_type": "Typ transportu",
"trongrid_history": "Historia Trongrida",
"trusted": "Zaufany",
"try_again": "Spróbuj ponownie",
"tx_commit_exception_no_dust_on_change": "Transakcja została odrzucana z tą kwotą. Za pomocą tych monet możesz wysłać ${min} bez reszty lub ${max}, które zwrócą resztę.",
"tx_commit_failed": "Zatwierdzenie transakcji nie powiodło się. Skontaktuj się z obsługą.",
"tx_commit_failed_no_peers": "Transakcja nie była transmitowana, spróbuj ponownie za około sekundę",

View file

@ -56,6 +56,7 @@
"apk_update": "Atualização de APK",
"approve": "Aprovar",
"approve_request": "Aprovar solicitação",
"approve_tokens": "Aprovar tokens",
"arrive_in_this_address": "${currency} ${tag}chegará neste endereço",
"ascending": "Ascendente",
"ask_each_time": "Pergunte cada vez",
@ -244,6 +245,15 @@
"descending": "descendente",
"description": "Descrição",
"destination_tag": "Tag de destino:",
"deuro_collect_interest": "Coletar",
"deuro_savings": "dEuro Savings",
"deuro_savings_add": "Depósito",
"deuro_savings_balance": "Balanço de poupança",
"deuro_savings_collect_interest": "Coletar juros",
"deuro_savings_remove": "Retirar",
"deuro_savings_set_approval": "Defina aprovação",
"deuro_savings_subtitle": "Ganhe até 10% de juros em sua Deuro Stablecoin Holdings",
"deuro_tx_commited_content": "Pode levar alguns segundos para a transação confirmar e se refletir na tela",
"device_is_signing": "O dispositivo está assinando",
"dfx_option_description": "Compre criptografia com EUR & CHF. Para clientes de varejo e corporativo na Europa",
"didnt_get_code": "Não recebeu o código?",
@ -977,6 +987,7 @@
"transport_type": "Tipo de transporte",
"trongrid_history": "História de Trongrid",
"trusted": "confiável",
"try_again": "Tente novamente",
"tx_commit_exception_no_dust_on_change": "A transação é rejeitada com esse valor. Com essas moedas, você pode enviar ${min} sem alteração ou ${max} que retorna alterações.",
"tx_commit_failed": "A confirmação da transação falhou. Entre em contato com o suporte.",
"tx_commit_failed_no_peers": "A transação não foi transmitida, tente novamente em um segundo",

View file

@ -56,6 +56,7 @@
"apk_update": "Обновление APK",
"approve": "Утвердить",
"approve_request": "Утвердить запрос",
"approve_tokens": "Одобрить токены",
"arrive_in_this_address": "${currency} ${tag}придет на этот адрес",
"ascending": "Восходящий",
"ask_each_time": "Спросите каждый раз",
@ -244,6 +245,15 @@
"descending": "Нисходящий",
"description": "Описание",
"destination_tag": "Целевой тег:",
"deuro_collect_interest": "Собирать",
"deuro_savings": "dEuro Savings",
"deuro_savings_add": "Депозитный",
"deuro_savings_balance": "Сберегательный баланс",
"deuro_savings_collect_interest": "Собирать интерес",
"deuro_savings_remove": "Отзывать",
"deuro_savings_set_approval": "Установить утверждение",
"deuro_savings_subtitle": "Заработайте до 10% процентов на ваших Deuro Stablecoin Holdings",
"deuro_tx_commited_content": "Чтобы подтвердить, может потребоваться пару секунд, чтобы подтвердить и быть отраженным на экране",
"device_is_signing": "Устройство подписывает",
"dfx_option_description": "Купить крипто с Eur & CHF. Для розничных и корпоративных клиентов в Европе",
"didnt_get_code": "Не получить код?",
@ -976,6 +986,7 @@
"transport_type": "Транспортный тип",
"trongrid_history": "История Тронгрида",
"trusted": "доверенный",
"try_again": "Попробуйте еще раз",
"tx_commit_exception_no_dust_on_change": "Транзакция отклоняется с этой суммой. С этими монетами вы можете отправлять ${min} без изменения или ${max}, которые возвращают изменение.",
"tx_commit_failed": "Комплект транзакции не удался. Пожалуйста, свяжитесь с поддержкой.",
"tx_commit_failed_no_peers": "Транзакция не смогла передать, попробуйте еще раз через секунду или около того",

View file

@ -56,6 +56,7 @@
"apk_update": "ปรับปรุง APK",
"approve": "อนุมัติ",
"approve_request": "อนุมัติคำขอ",
"approve_tokens": "อนุมัติโทเค็น",
"arrive_in_this_address": "${currency} ${tag}จะมาถึงที่อยู่นี้",
"ascending": "จากน้อยไปมาก",
"ask_each_time": "ถามทุกครั้ง",
@ -244,6 +245,15 @@
"descending": "ลงมา",
"description": "คำอธิบาย",
"destination_tag": "แท็กปลายทาง:",
"deuro_collect_interest": "เก็บรวบรวม",
"deuro_savings": "การออมของ dEuro",
"deuro_savings_add": "เงินฝาก",
"deuro_savings_balance": "ยอดเงินออม",
"deuro_savings_collect_interest": "เก็บดอกเบี้ย",
"deuro_savings_remove": "ถอน",
"deuro_savings_set_approval": "ตั้งค่าการอนุมัติ",
"deuro_savings_subtitle": "รับดอกเบี้ยมากถึง 10% สำหรับ Deuro Stablecoin Holdings ของคุณ",
"deuro_tx_commited_content": "อาจใช้เวลาสองสามวินาทีในการทำธุรกรรมเพื่อยืนยันและสะท้อนบนหน้าจอ",
"device_is_signing": "อุปกรณ์กำลังลงนาม",
"dfx_option_description": "ซื้อ crypto ด้วย Eur & CHF สำหรับลูกค้ารายย่อยและลูกค้าในยุโรป",
"didnt_get_code": "ไม่ได้รับรหัส?",
@ -975,6 +985,7 @@
"transport_type": "ประเภทการขนส่ง",
"trongrid_history": "ประวัติศาสตร์ Trongrid",
"trusted": "มั่นคง",
"try_again": "ลองอีกครั้ง",
"tx_commit_exception_no_dust_on_change": "ธุรกรรมถูกปฏิเสธด้วยจำนวนเงินนี้ ด้วยเหรียญเหล่านี้คุณสามารถส่ง ${min} โดยไม่ต้องเปลี่ยนแปลงหรือ ${max} ที่ส่งคืนการเปลี่ยนแปลง",
"tx_commit_failed": "การทำธุรกรรมล้มเหลว กรุณาติดต่อฝ่ายสนับสนุน",
"tx_commit_failed_no_peers": "การทำธุรกรรมล้มเหลวในการออกอากาศโปรดลองอีกครั้งในวินาทีหรือมากกว่านั้น",

View file

@ -56,6 +56,7 @@
"apk_update": "APK update",
"approve": "Aprubahan",
"approve_request": "Aprubahan ang kahilingan",
"approve_tokens": "Aprubahan ang mga token",
"arrive_in_this_address": "Ang ${currency} ${tag} ay darating sa address na ito",
"ascending": "Umakyat",
"ask_each_time": "Magtanong sa tuwing",
@ -244,6 +245,15 @@
"descending": "Pababang",
"description": "Paglalarawan",
"destination_tag": "Tag ng patutunguhan:",
"deuro_collect_interest": "Mangolekta",
"deuro_savings": "Pagtipid ni dEuro",
"deuro_savings_add": "Deposito",
"deuro_savings_balance": "Balanse sa pagtitipid",
"deuro_savings_collect_interest": "Mangolekta ng interes",
"deuro_savings_remove": "Umatras",
"deuro_savings_set_approval": "Itakda ang pag -apruba",
"deuro_savings_subtitle": "Kumita ng hanggang sa 10% na interes sa iyong mga hawak na Deuro StableCoin",
"deuro_tx_commited_content": "Maaaring tumagal ng ilang segundo para sa transaksyon upang kumpirmahin at maipakita sa screen",
"device_is_signing": "Nag -sign ang aparato",
"dfx_option_description": "Bumili ng crypto kasama ang EUR & CHF. Para sa mga retail customer at corporate customer sa Europe",
"didnt_get_code": "Hindi nakuha ang code?",
@ -975,6 +985,7 @@
"transport_type": "Uri ng transportasyon",
"trongrid_history": "Kasaysayan ng TronGrid",
"trusted": "Pinagkakatiwalaan",
"try_again": "Subukang muli",
"tx_commit_exception_no_dust_on_change": "Ang transaksyon ay tinanggihan sa halagang ito. Sa mga barya na ito maaari kang magpadala ng ${min} nang walang sukli o ${max} na nagbabalik ng sukli.",
"tx_commit_failed": "Nabigo ang transaksyon. Mangyaring makipag-ugnay sa suporta.",
"tx_commit_failed_no_peers": "Nabigo ang transaksyon na mag -broadcast, mangyaring subukang muli sa isang segundo o higit pa",

View file

@ -56,6 +56,7 @@
"apk_update": "APK güncellemesi",
"approve": "Onaylamak",
"approve_request": "Talebi Onaylama",
"approve_tokens": "Jetonları onaylayın",
"arrive_in_this_address": "${currency} ${tag}bu adrese ulaşacak",
"ascending": "Yükselen",
"ask_each_time": "Her seferinde sor",
@ -244,6 +245,15 @@
"descending": "Azalan",
"description": "Tanım",
"destination_tag": "Hedef Etiketi:",
"deuro_collect_interest": "TOPLAMAK",
"deuro_savings": "dEuro Tasarruf",
"deuro_savings_add": "Yatırmak",
"deuro_savings_balance": "Tasarruf Bakiyesi",
"deuro_savings_collect_interest": "İlgi toplamak",
"deuro_savings_remove": "Geri çekilmek",
"deuro_savings_set_approval": "Onay ayarlamak",
"deuro_savings_subtitle": "Deuro StableCoin Holdings'e% 10'a kadar faiz kazanın",
"deuro_tx_commited_content": "İşlemin onaylaması ve ekrana yansıtılması birkaç saniye sürebilir",
"device_is_signing": "Cihaz imzalıyor",
"dfx_option_description": "Eur & chf ile kripto satın alın. Avrupa'daki perakende ve kurumsal müşteriler için",
"didnt_get_code": "Kod gelmedi mi?",
@ -975,6 +985,7 @@
"transport_type": "Taşıma tipi",
"trongrid_history": "Trongrid tarihi",
"trusted": "Güvenilir",
"try_again": "Tekrar deneyin",
"tx_commit_exception_no_dust_on_change": "İşlem bu miktarla reddedilir. Bu madeni paralarla değişiklik yapmadan ${min} veya değişikliği döndüren ${max} gönderebilirsiniz.",
"tx_commit_failed": "İşlem taahhüdü başarısız oldu. Lütfen Destek ile iletişime geçin.",
"tx_commit_failed_no_peers": "İşlem yayın yapamadı, lütfen bir saniye içinde tekrar deneyin",

View file

@ -56,6 +56,7 @@
"apk_update": "Оновлення APK",
"approve": "Затвердити",
"approve_request": "Запитайте запит",
"approve_tokens": "Затвердити токени",
"arrive_in_this_address": "${currency} ${tag}надійде на цю адресу",
"ascending": "Висхід",
"ask_each_time": "Запитайте кожен раз",
@ -244,6 +245,15 @@
"descending": "Низхідний",
"description": "опис",
"destination_tag": "Тег призначення:",
"deuro_collect_interest": "Збирати",
"deuro_savings": "заощадження dEuro",
"deuro_savings_add": "Депозит",
"deuro_savings_balance": "Баланс заощаджень",
"deuro_savings_collect_interest": "Збирати інтерес",
"deuro_savings_remove": "Відступати",
"deuro_savings_set_approval": "Встановити схвалення",
"deuro_savings_subtitle": "Заробляйте до 10% відсотків на ваших Holdings Deuro StableCoin",
"deuro_tx_commited_content": "Це може знадобитися кілька секунд, щоб транзакція підтвердила та відображалася на екрані",
"device_is_signing": "Пристрій підписується",
"dfx_option_description": "Купуйте криптовалюту з EUR & CHF. Для роздрібних та корпоративних клієнтів у Європі",
"didnt_get_code": "Не отримали код?",
@ -976,6 +986,7 @@
"transport_type": "Транспортний тип",
"trongrid_history": "Тронгрідська історія",
"trusted": "довіряють",
"try_again": "Спробуйте ще раз",
"tx_commit_exception_no_dust_on_change": "Транзакція відхилена цією сумою. За допомогою цих монет ви можете надіслати ${min} без змін або ${max}, що повертає зміни.",
"tx_commit_failed": "Транзакційна комісія не вдалося. Будь ласка, зв'яжіться з підтримкою.",
"tx_commit_failed_no_peers": "Транзакція не вдалося транслювати, спробуйте ще раз за секунду або близько того",

View file

@ -56,6 +56,7 @@
"apk_update": "APK اپ ڈیٹ",
"approve": "ﻭﺮﮐ ﺭﻮﻈﻨﻣ",
"approve_request": "درخواست کو منظور کریں",
"approve_tokens": "ٹوکن کو منظور کریں",
"arrive_in_this_address": "${currency} ${tag}اس پتے پر پہنچے گا۔",
"ascending": "چڑھنے",
"ask_each_time": "ہر بار پوچھیں",
@ -244,6 +245,15 @@
"descending": "اترتے ہوئے",
"description": "ﻞﯿﺼﻔﺗ",
"destination_tag": "منزل کا ٹیگ:",
"deuro_collect_interest": "جمع کریں",
"deuro_savings": "ڈیورو کی بچت",
"deuro_savings_add": "جمع کروائیں",
"deuro_savings_balance": "بچت کا توازن",
"deuro_savings_collect_interest": "دلچسپی جمع کریں",
"deuro_savings_remove": "واپس لے لو",
"deuro_savings_set_approval": "منظوری طے کریں",
"deuro_savings_subtitle": "اپنے ڈیورو اسٹبل کوئن ہولڈنگز پر 10 ٪ سود حاصل کریں",
"deuro_tx_commited_content": "لین دین کی تصدیق اور اسکرین پر عکاسی کرنے میں اس میں کچھ سیکنڈ لگ سکتے ہیں",
"device_is_signing": "ڈیوائس پر دستخط کر رہے ہیں",
"dfx_option_description": "یورو اور سی ایچ ایف کے ساتھ کرپٹو خریدیں۔ یورپ میں خوردہ اور کارپوریٹ صارفین کے لئے",
"didnt_get_code": "کوڈ نہیں ملتا؟",
@ -977,6 +987,7 @@
"transport_type": "ٹرانسپورٹ کی قسم",
"trongrid_history": "ٹرانگریڈ ہسٹری",
"trusted": "قابل اعتماد",
"try_again": "دوبارہ کوشش کریں",
"tx_commit_exception_no_dust_on_change": "اس رقم سے لین دین کو مسترد کردیا گیا ہے۔ ان سککوں کے ذریعہ آپ بغیر کسی تبدیلی کے ${min} یا ${max} بھیج سکتے ہیں جو لوٹتے ہیں۔",
"tx_commit_failed": "ٹرانزیکشن کمٹ ناکام ہوگیا۔ براہ کرم سپورٹ سے رابطہ کریں۔",
"tx_commit_failed_no_peers": "ٹرانزیکشن نشر کرنے میں ناکام ، براہ کرم ایک سیکنڈ یا اس میں دوبارہ کوشش کریں",

View file

@ -56,6 +56,7 @@
"apk_update": "Cập nhật APK",
"approve": "Phê duyệt",
"approve_request": "Phê duyệt yêu cầu",
"approve_tokens": "Phê duyệt mã thông báo",
"arrive_in_this_address": "${currency} ${tag} sẽ đến địa chỉ này",
"ascending": "Tăng dần",
"ask_each_time": "Hỏi mỗi lần",
@ -243,6 +244,15 @@
"descending": "Giảm dần",
"description": "Mô tả",
"destination_tag": "Thẻ đích:",
"deuro_collect_interest": "Sưu tầm",
"deuro_savings": "Tiết kiệm dEuro",
"deuro_savings_add": "Tiền gửi",
"deuro_savings_balance": "Số dư tiết kiệm",
"deuro_savings_collect_interest": "Thu tiền lãi",
"deuro_savings_remove": "Rút",
"deuro_savings_set_approval": "Đặt phê duyệt",
"deuro_savings_subtitle": "Kiếm tới 10% tiền lãi cho Deuro Storcoin Holdings của bạn",
"deuro_tx_commited_content": "Có thể mất vài giây để giao dịch xác nhận và được phản ánh trên màn hình",
"device_is_signing": "Thiết bị đang ký",
"dfx_option_description": "Mua tiền điện tử bằng EUR & CHF. Dành cho khách hàng bán lẻ và doanh nghiệp tại Châu Âu",
"didnt_get_code": "Không nhận được mã?",
@ -972,6 +982,7 @@
"transport_type": "Loại vận chuyển",
"trongrid_history": "Lịch sử TronGrid",
"trusted": "Đã tin cậy",
"try_again": "Hãy thử lại",
"tx_commit_exception_no_dust_on_change": "Giao dịch bị từ chối với số tiền này. Với số tiền này bạn có thể gửi ${min} mà không cần đổi tiền lẻ hoặc ${max} trả lại tiền lẻ.",
"tx_commit_failed": "Giao dịch không thành công. Vui lòng liên hệ với hỗ trợ.",
"tx_commit_failed_no_peers": "Giao dịch không phát sóng, vui lòng thử lại trong một giây hoặc lâu hơn",

View file

@ -56,6 +56,7 @@
"apk_update": "Àtúnse áàpù títun wà",
"approve": "Fi ọwọ si",
"approve_request": "IKILỌ RẸ",
"approve_tokens": "Ṣe fọwọsi awọn àmi",
"arrive_in_this_address": "${currency} ${tag} máa dé sí àdírẹ́sì yìí",
"ascending": "Goke",
"ask_each_time": "Beere lọwọ kọọkan",
@ -244,6 +245,15 @@
"descending": "Sọkalẹ",
"description": "Apejuwe",
"destination_tag": "Orúkọ tí ìbí tó a ránṣẹ́ sí:",
"deuro_collect_interest": "Kojọ",
"deuro_savings": "dEuro Awọn ifowopamọ",
"deuro_savings_add": "Owo ifipamọ",
"deuro_savings_balance": "Iwontunws.funfun ifowopamọ",
"deuro_savings_collect_interest": "Gba iwulo",
"deuro_savings_remove": "Yọkuro",
"deuro_savings_set_approval": "Ṣeto ifọwọsi",
"deuro_savings_subtitle": "Jo'gun to 10% iwulo lori awọn idaduro Duroblockoin rẹ",
"deuro_tx_commited_content": "O le gba tọkọtaya kan ti awọn aaya fun idunadura lati jẹrisi ati ṣe afihan loju iboju",
"device_is_signing": "Ẹrọ n forukọsilẹ",
"dfx_option_description": "Ra Crypto pẹlu EUR & CHF. Fun soobu ati awọn alabara ile-iṣẹ ni Yuroopu",
"didnt_get_code": "Ko gba koodu?",
@ -976,6 +986,7 @@
"transport_type": "Iru irinna",
"trongrid_history": "Itan Trongrid",
"trusted": "A ti fọkàn ẹ̀ tán",
"try_again": "Gbiyanju lẹẹkansi",
"tx_commit_exception_no_dust_on_change": "Iṣowo naa ti kọ pẹlu iye yii. Pẹlu awọn owó wọnyi o le firanṣẹ ${min} laisi ayipada tabi ${max} ni iyipada iyipada.",
"tx_commit_failed": "Idunadura iṣowo kuna. Jọwọ kan si atilẹyin.",
"tx_commit_failed_no_peers": "Idunadura kuna lati wa igbohungbe, jọwọ gbiyanju lẹẹkansi ni iṣẹju keji tabi bẹẹ",

View file

@ -56,6 +56,7 @@
"apk_update": "APK更新",
"approve": "批准",
"approve_request": "批准请求",
"approve_tokens": "批准令牌",
"arrive_in_this_address": "${currency} ${tag}将到达此地址",
"ascending": "上升",
"ask_each_time": "每次问",
@ -244,6 +245,15 @@
"descending": "下降",
"description": "描述",
"destination_tag": "目标Tag:",
"deuro_collect_interest": "收集",
"deuro_savings": "dEuro储蓄",
"deuro_savings_add": "订金",
"deuro_savings_balance": "储蓄平衡",
"deuro_savings_collect_interest": "收集兴趣",
"deuro_savings_remove": "提取",
"deuro_savings_set_approval": "设定批准",
"deuro_savings_subtitle": "您的Deuro Stablecoin Holdings最多可赚取10的利息",
"deuro_tx_commited_content": "交易可能需要几秒钟才能确认并在屏幕上反射",
"device_is_signing": "设备正在签名",
"dfx_option_description": "用EurChf购买加密货币。对于欧洲的零售和企业客户",
"didnt_get_code": "没有获取代码?",
@ -975,6 +985,7 @@
"transport_type": "运输类型",
"trongrid_history": "Trongrid历史",
"trusted": "值得信赖",
"try_again": "再试一次",
"tx_commit_exception_no_dust_on_change": "交易被此金额拒绝。使用这些硬币,您可以发送${min}无需更改或返回${max}的变化。",
"tx_commit_failed": "交易承诺失败。请联系支持。",
"tx_commit_failed_no_peers": "交易无法广播,请在一秒钟左右的时间内重试",

View file

@ -0,0 +1,178 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('AddressValidator', () {
setUpAll(() {
S.current = S();
});
group('getPattern', () {
test('returns correct pattern for Bitcoin', () {
final pattern = AddressValidator.getPattern(CryptoCurrency.btc);
expect(pattern, isNotEmpty);
expect(pattern, contains('(bc|tb)1q'));
});
test('returns correct pattern for Ethereum', () {
final pattern = AddressValidator.getPattern(CryptoCurrency.eth);
expect(pattern, isNotEmpty);
expect(pattern, contains('0x[0-9a-zA-Z]+'));
});
test('returns correct pattern for Monero', () {
final pattern = AddressValidator.getPattern(CryptoCurrency.xmr);
expect(pattern, isNotEmpty);
expect(pattern,
contains('4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}'));
});
test('returns correct pattern for Litecoin', () {
final pattern = AddressValidator.getPattern(CryptoCurrency.ltc);
expect(pattern, isNotEmpty);
expect(
pattern,
contains(
'(bc|tb|ltc)1q[ac-hj-np-z02-9]{25,39}|(ltc|t)mweb1q[ac-hj-np-z02-9]{90,120}'));
});
test('returns empty string for unknown currency', () {
final pattern = AddressValidator.getPattern(CryptoCurrency.btcln);
expect(pattern, isNotEmpty);
});
});
group('getLength', () {
test('returns correct length for Bitcoin', () {
final length = AddressValidator.getLength(CryptoCurrency.btc);
expect(length, isNull);
});
test('returns correct length for Ethereum', () {
final length = AddressValidator.getLength(CryptoCurrency.eth);
expect(length, equals([42]));
});
test('returns correct length for Monero', () {
final length = AddressValidator.getLength(CryptoCurrency.xmr);
expect(length, isNull);
});
test('returns correct length for Dash', () {
final length = AddressValidator.getLength(CryptoCurrency.dash);
expect(length, equals([34]));
});
});
group('getAddressFromStringPattern', () {
test('returns correct pattern for Bitcoin', () {
final pattern =
AddressValidator.getAddressFromStringPattern(CryptoCurrency.btc);
expect(pattern, isNotNull);
expect(pattern, contains('(bc|tb)1q'));
});
test('returns correct pattern for Ethereum', () {
final pattern =
AddressValidator.getAddressFromStringPattern(CryptoCurrency.eth);
expect(pattern, isNotNull);
expect(pattern, contains('0x[0-9a-zA-Z]+'));
});
test('returns correct pattern for Monero', () {
final pattern =
AddressValidator.getAddressFromStringPattern(CryptoCurrency.xmr);
expect(pattern, isNotNull);
expect(pattern, contains('(4[0-9a-zA-Z]{94})'));
});
test('returns null for unsupported currency', () {
final pattern =
AddressValidator.getAddressFromStringPattern(CryptoCurrency.dash);
expect(pattern, isNull);
});
});
// 0.000058158099999999995 BTC
group('validation', () {
test('validates valid Bitcoin address', () {
final validator = AddressValidator(type: CryptoCurrency.btc);
expect(validator.isValid('bc1qhg4l43pmq5v5atmtlr7gnwyuxs043cvrut5hkq'),
isTrue);
expect(validator.isValid('3AD1Btx1MzYGmdpNpeujCfuvU5SsU2LX88'), isTrue);
expect(validator.isValid('1HARAhFcvz8ZQp5MhnLFeUynC4bkha3Hv8'), isTrue);
});
test('rejects invalid Bitcoin address', () {
final validator = AddressValidator(type: CryptoCurrency.btc);
expect(validator.isValid('invalid_address'), isFalse);
expect(validator.isValid('bc1qhg4l43pmq5v5atmtlr7gnwyuxs043CakeWallet'),
isFalse);
});
test('validates valid Ethereum address', () {
final validator = AddressValidator(type: CryptoCurrency.eth);
expect(validator.isValid('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
isTrue); // WETH contract
});
test('rejects invalid Ethereum address', () {
final validator = AddressValidator(type: CryptoCurrency.eth);
expect(validator.isValid('invalid_address'), isFalse);
expect(validator.isValid('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc'),
isFalse); // Too short
expect(validator.isValid('C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
isFalse); // Missing 0x prefix
});
test('validates valid Monero address', () {
final validator = AddressValidator(type: CryptoCurrency.xmr);
expect(
validator.isValid(
'85s6zfxGAkdCN21h566R8EFDSfThxCrFiEkhw3JEtaXN2DDfahABLXTjRj385Ro7om5saGWJG7iuE6EyW5MYcoz93DLvNqh'),
isTrue);
});
test('rejects invalid Monero address', () {
final validator = AddressValidator(type: CryptoCurrency.xmr);
expect(validator.isValid('invalid_address'), isFalse);
expect(
validator.isValid(
'85s6zfxGAkdCN21h566R8EFDSfThxCrFiEkhw3JEtaXN2DDfahABLXTjRj385Ro7om5saGWJG7iuE6EyW5MYcoz93DLvNq'),
isFalse); // Too short
});
test('validates valid Litecoin address', () {
final validator = AddressValidator(type: CryptoCurrency.ltc);
expect(validator.isValid('ltc1qzvxlvlk8wsmue0np20eh3d3qxsusx9jstf8qw8'),
isTrue);
expect(
validator.isValid(
'ltcmweb1qqt9hqch2d0vfdsvt4tf27gullem2tcd57xxrvta9xwvfmwdkn4927q6d8sq6ftw7lkqdkr5g36eqn7w06edgq8tz7gy0nv5d4lhajctkzuath23a'),
isTrue);
});
test('rejects invalid Litecoin address', () {
final validator = AddressValidator(type: CryptoCurrency.ltc);
expect(validator.isValid('invalid_address'), isFalse);
expect(
validator.isValid('ltc1qzvxlvlk8wsmue0np20eh3d3qxsusxCakeWallet'),
isFalse);
});
});
group('silentPaymentAddressPattern', () {
test('returns a non-empty pattern', () {
final pattern = AddressValidator.silentPaymentAddressPattern;
expect(pattern, isNotEmpty);
});
});
group('mWebAddressPattern', () {
test('returns a non-empty pattern', () {
final pattern = AddressValidator.mWebAddressPattern;
expect(pattern, isNotEmpty);
});
});
});
}

View file

@ -0,0 +1,187 @@
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('AddressResolver', () {
// late MockYatService mockYatService;
// late MockWalletBase mockWallet;
// late MockSettingsStore mockSettingsStore;
// late MockBuildContext mockContext;
// late AddressResolver addressResolver;
//
// setUp(() {
// mockYatService = MockYatService();
// mockWallet = MockWalletBase();
// mockSettingsStore = MockSettingsStore();
// mockContext = MockBuildContext();
//
// when(mockWallet.type).thenReturn(WalletType.bitcoin);
// when(mockWallet.currency).thenReturn(CryptoCurrency.btc);
//
// addressResolver = AddressResolver(
// yatService: mockYatService,
// wallet: mockWallet,
// settingsStore: mockSettingsStore,
// );
// });
group('extractAddressByType', () {
test('extracts Bitcoin address correctly', () {
final raw =
'My Bitcoin address is bc1qhg4l43pmq5v5atmtlr7gnwyuxs043cvrut5hkq please use it';
final result = AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.btc,
);
expect(result, 'bc1qhg4l43pmq5v5atmtlr7gnwyuxs043cvrut5hkq');
});
test('extracts Ethereum address correctly', () {
final raw =
'Send ETH to 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 thanks';
final result = AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.eth,
);
expect(result, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2');
});
test('extracts Monero address correctly', () {
final raw =
'XMR: 85s6zfxGAkdCN21h566R8EFDSfThxCrFiEkhw3JEtaXN2DDfahABLXTjRj385Ro7om5saGWJG7iuE6EyW5MYcoz93DLvNqh';
final result = AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.xmr,
);
expect(result,
'85s6zfxGAkdCN21h566R8EFDSfThxCrFiEkhw3JEtaXN2DDfahABLXTjRj385Ro7om5saGWJG7iuE6EyW5MYcoz93DLvNqh');
});
test('extracts Bitcoin Cash address correctly', () {
final raw =
'BCH: bitcoincash:qr2z7dusk64qnq97azhg0u0hlf7qgwwfzyj92jgmqj';
final result = AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.bch,
);
expect(
result, 'bitcoincash:qr2z7dusk64qnq97azhg0u0hlf7qgwwfzyj92jgmqj');
});
test('extracts Nano address correctly', () {
final raw =
'NANO: nano_1natrium1o3z5519ifou7xii8crpxpk8y65qmkih8e8bpsjri651oza8imdd';
final result = AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.nano,
);
expect(result,
'nano_1natrium1o3z5519ifou7xii8crpxpk8y65qmkih8e8bpsjri651oza8imdd');
});
test('returns null for unsupported currency', () {
final raw = 'Some text without an address';
expect(
() => AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.btc,
),
returnsNormally);
final result = AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.btc,
);
expect(result, isNull);
});
test('extracts monero address from URI', () {
final raw =
'monero_wallet:467iotZU5tvG26k2xdZWkJ7gwATFVhfbuV3yDoWx5jHoPwxEi4f5BuJQwkP6GpCb1sZvUVB7nbSkgEuW8NKrh9KKRRga5qz?spend_key=029c559cd7669f14e91fd835144916009f8697ab5ac5c7f7c06e1ff869c17b0b&view_key=afaf646edbff3d3bcee8efd3383ffe5d20c947040f74e1110b70ca0fbb0ef90d';
final result = AddressResolver.extractAddressByType(
raw: raw,
type: CryptoCurrency.xmr,
requireSurroundingWhitespaces: false);
expect(result,
'467iotZU5tvG26k2xdZWkJ7gwATFVhfbuV3yDoWx5jHoPwxEi4f5BuJQwkP6GpCb1sZvUVB7nbSkgEuW8NKrh9KKRRga5qz');
});
test('extracts monero address from Tweet', () {
final raw = '''
#XMR
89bH6i3ftaWSWuPJJYSQuuApWJ8xzinCEbbnAXN1Z3mGGUuAFdpBUg82R9MvJDSheJ6kW2dyMQEFUGM4tsZqRb2Q75UXqvc
#BTC Silent Payments
sp1qq0avpawwjg4l66p6lqafj0vlvm6rlhdc6qt0r6dfual835vhs3gvkq63pechaqezvn7j7uj2jucwj5k7nenpw2r86wf42xv6wqdvxuk5rggrul45
#LTC MWEB
ltcmweb1qq0at62jjucmawxp78qutn0cqwkwahcfx7fxls0r2ma5llg5w6wyy2qe20gxa3rku2658j88zg9d2j4ttpw35k0a5nrg93h5nq3wyvkcgwc3q4dgc
''';
final resultXmr = AddressResolver.extractAddressByType(
raw: raw, type: CryptoCurrency.xmr);
expect(resultXmr,
'89bH6i3ftaWSWuPJJYSQuuApWJ8xzinCEbbnAXN1Z3mGGUuAFdpBUg82R9MvJDSheJ6kW2dyMQEFUGM4tsZqRb2Q75UXqvc');
final resultBtc = AddressResolver.extractAddressByType(
raw: raw, type: CryptoCurrency.btc);
expect(resultBtc,
'sp1qq0avpawwjg4l66p6lqafj0vlvm6rlhdc6qt0r6dfual835vhs3gvkq63pechaqezvn7j7uj2jucwj5k7nenpw2r86wf42xv6wqdvxuk5rggrul45');
final resultLtc = AddressResolver.extractAddressByType(
raw: raw, type: CryptoCurrency.ltc);
expect(resultLtc,
'ltcmweb1qq0at62jjucmawxp78qutn0cqwkwahcfx7fxls0r2ma5llg5w6wyy2qe20gxa3rku2658j88zg9d2j4ttpw35k0a5nrg93h5nq3wyvkcgwc3q4dgc');
});
// test('throws exception for unexpected token', () {
// // Create a custom crypto currency that won't have a pattern
// final customCurrency = CryptoCurrency('CUSTOM', 'Custom');
// expect(() => AddressResolver.extractAddressByType(
// raw: 'Some text',
// type: customCurrency,
// ), throwsException);
// });
});
//
// group('isEmailFormat', () {
// test('returns true for valid email format', () {
// expect(addressResolver.isEmailFormat('user@example.com'), isTrue);
// expect(addressResolver.isEmailFormat('name.surname@domain.co.uk'), isTrue);
// expect(addressResolver.isEmailFormat('user123@subdomain.example.org'), isTrue);
// });
//
// test('returns false for invalid email format', () {
// expect(addressResolver.isEmailFormat('user@'), isFalse);
// expect(addressResolver.isEmailFormat('@domain.com'), isFalse);
// expect(addressResolver.isEmailFormat('user@domain'), isFalse);
// expect(addressResolver.isEmailFormat('user.domain.com'), isFalse);
// expect(addressResolver.isEmailFormat('user@domain@com'), isFalse);
// expect(addressResolver.isEmailFormat('bc1qhg4l43pmq5v5atmtlr7gnwyuxs043cvrut5hkq'), isFalse);
// });
// });
//
// group('resolve', () {
// test('returns ParsedAddress with original text when no resolution is possible', () async {
// final text = 'bc1qhg4l43pmq5v5atmtlr7gnwyuxs043cvrut5hkq';
// final result = await addressResolver.resolve(mockContext, text, CryptoCurrency.btc);
//
// expect(result, isA<ParsedAddress>());
// expect(result.addresses, [text]);
// });
//
// // Note: More comprehensive tests for the resolve method would require
// // mocking all the external services and APIs that the method calls.
// // This would be quite extensive and would require setting up mock
// // responses for each type of address resolution.
// });
group('unstoppableDomains', () {
test('contains expected TLDs', () {
expect(AddressResolver.unstoppableDomains, contains('crypto'));
expect(AddressResolver.unstoppableDomains, contains('eth'));
expect(AddressResolver.unstoppableDomains, contains('bitcoin'));
expect(AddressResolver.unstoppableDomains, contains('x'));
expect(AddressResolver.unstoppableDomains, contains('wallet'));
});
});
});
}

View file

@ -670,6 +670,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart';
@ -697,6 +698,7 @@ import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_ethereum/ethereum_wallet_service.dart';
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
import 'package:cw_ethereum/deuro/deuro_savings.dart';
import 'package:eth_sig_util/util/utils.dart';
@ -744,6 +746,16 @@ abstract class Ethereum {
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled);
Web3Client? getWeb3Client(WalletBase wallet);
String getTokenAddress(CryptoCurrency asset);
Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority);
Future<BigInt> getDEuroSavingsBalance(WalletBase wallet);
Future<BigInt> getDEuroAccruedInterest(WalletBase wallet);
Future<BigInt> getDEuroInterestRate(WalletBase wallet);
Future<BigInt> getDEuroSavingsApproved(WalletBase wallet);
Future<PendingTransaction> addDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority);
Future<PendingTransaction> removeDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority);
Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority);
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
@ -777,6 +789,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart';
@ -846,6 +859,8 @@ abstract class Polygon {
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority);
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled);