From fe0c9ecc0eb6480a259152cfbd7d954698427ea5 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Sat, 14 Jun 2025 02:18:46 +0100 Subject: [PATCH 1/6] CW-1084: Solana Issues (#2305) * fix(solana-issues): Fix missing solana transaction history entries * fix(solana-issues): Fixes issues relating to Solana Transaction History This change: - Modifies transaction parsing logic to handle more scenarios and better parse Solana transaction data. - Adds partial filter for spam transactions * fix(solana-issues): Enhance transaction parsing for Associated Token Account (ATA) programs This change: - Adds logic to differentiate between create account and token transfer transactions for the ATA program. - Introduces a check to skip transactions that only create accounts without associated token transfers. * fix(solana-issues): Improve transaction update logic and enhance error handling This change: - Updates the transaction update callback to only trigger when new valid transactions are present. - Enhances error handling for insufficient funds by distinguishing between errors for sender and receiver. --- cw_solana/lib/solana_client.dart | 379 ++++++++++++++--------- lib/view_model/send/send_view_model.dart | 11 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 3 +- res/values/strings_hr.arb | 1 + res/values/strings_hy.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_vi.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 30 files changed, 263 insertions(+), 157 deletions(-) diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 05b0cec82..128366c73 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -13,13 +13,17 @@ import 'package:cw_solana/solana_transaction_model.dart'; import 'package:cw_solana/spl_token.dart'; import 'package:http/http.dart' as http; import 'package:on_chain/solana/solana.dart'; +import 'package:on_chain/solana/src/instructions/associated_token_account/constant.dart'; import 'package:on_chain/solana/src/models/pda/pda.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:on_chain/solana/src/rpc/models/models/confirmed_transaction_meta.dart'; import '.secrets.g.dart' as secrets; class SolanaWalletClient { final httpClient = http.Client(); SolanaRPC? _provider; + // Minimum amount in SOL to consider a transaction valid (to filter spam) + static const double minValidAmount = 0.00000003; bool connect(Node node) { try { @@ -155,170 +159,88 @@ class SolanaWalletClient { if (meta == null || transaction == null) return null; final int fee = meta.fee; + final feeInSol = fee / SolanaUtils.lamportsPerSol; final message = transaction.message; final instructions = message.compiledInstructions; - String sender = ""; - String receiver = ""; - String signature = (txResponse.transaction?.signatures.isEmpty ?? true) ? "" : Base58Encoder.encode(txResponse.transaction!.signatures.first); + for (final instruction in instructions) { final programId = message.accountKeys[instruction.programIdIndex]; - if (programId == SystemProgramConst.programId) { + if (programId == SystemProgramConst.programId || + programId == ComputeBudgetConst.programId) { // For native solana transactions + if (instruction.accounts.length < 2) continue; - if (txResponse.version == TransactionType.legacy) { - // For legacy transfers, the fee payer (index 0) is the sender. - sender = message.accountKeys[0].address; + // Get the fee payer index based on transaction type + // For legacy transfers, the first account is usually the fee payer + // For versioned, the first account in instruction is usually the fee payer + final feePayerIndex = + txResponse.version == TransactionType.legacy ? 0 : instruction.accounts[0]; - final senderPreBalance = meta.preBalances[0]; - final senderPostBalance = meta.postBalances[0]; - final feeForTx = fee / SolanaUtils.lamportsPerSol; + final transactionModel = await _parseNativeTransaction( + message: message, + meta: meta, + fee: fee, + feeInSol: feeInSol, + feePayerIndex: feePayerIndex, + walletAddress: walletAddress, + signature: signature, + blockTime: blockTime, + ); - // The loss on the sender's account would include both the transfer amount and the fee. - // So we would subtract the fee to calculate the actual amount that was transferred (in lamports). - final transferLamports = (senderPreBalance - senderPostBalance) - BigInt.from(fee); - - // Next, we attempt to find the receiver by comparing the balance changes. - // (The index 0 is for the sender so we skip it.) - bool foundReceiver = false; - for (int i = 1; i < meta.preBalances.length; i++) { - // The increase in balance on the receiver account should correspond to the transfer amount we calculated earlieer. - final pre = meta.preBalances[i]; - final post = meta.postBalances[i]; - if ((post - pre) == transferLamports) { - receiver = message.accountKeys[i].address; - foundReceiver = true; - break; - } - } - - if (!foundReceiver) { - // Optionally (and rarely), if no account shows the exact expected change, - // we set the receiver address to unknown. - receiver = "unknown"; - } - - final amount = transferLamports / BigInt.from(1e9); - - return SolanaTransactionModel( - isOutgoingTx: sender == walletAddress, - from: sender, - to: receiver, - id: signature, - amount: amount.abs(), - programId: SystemProgramConst.programId.address, - tokenSymbol: 'SOL', - blockTimeInInt: blockTime?.toInt() ?? 0, - fee: feeForTx, - ); - } else { - if (instruction.accounts.length < 2) continue; - final senderIndex = instruction.accounts[0]; - final receiverIndex = instruction.accounts[1]; - - sender = message.accountKeys[senderIndex].address; - receiver = message.accountKeys[receiverIndex].address; - - final feeForTx = fee / SolanaUtils.lamportsPerSol; - - final preBalances = meta.preBalances; - final postBalances = meta.postBalances; - - final amountInString = - (((preBalances[senderIndex] - postBalances[senderIndex]) / BigInt.from(1e9)) - .toDouble() - - feeForTx) - .toStringAsFixed(6); - - final amount = double.parse(amountInString); - - return SolanaTransactionModel( - isOutgoingTx: sender == walletAddress, - from: sender, - to: receiver, - id: signature, - amount: amount.abs(), - programId: SystemProgramConst.programId.address, - tokenSymbol: 'SOL', - blockTimeInInt: blockTime?.toInt() ?? 0, - fee: feeForTx, - ); + if (transactionModel != null) { + return transactionModel; } } else if (programId == SPLTokenProgramConst.tokenProgramId) { // For SPL Token transactions if (instruction.accounts.length < 2) continue; - final preBalances = meta.preTokenBalances; - final postBalances = meta.postTokenBalances; - - double amount = 0.0; - bool isOutgoing = false; - String? mintAddress; - - double userPreAmount = 0.0; - if (preBalances != null && preBalances.isNotEmpty) { - for (final preBal in preBalances) { - if (preBal.owner?.address == walletAddress) { - userPreAmount = preBal.uiTokenAmount.uiAmount ?? 0.0; - - mintAddress = preBal.mint.address; - break; - } - } - } - - double userPostAmount = 0.0; - if (postBalances != null && postBalances.isNotEmpty) { - for (final postBal in postBalances) { - if (postBal.owner?.address == walletAddress) { - userPostAmount = postBal.uiTokenAmount.uiAmount ?? 0.0; - - mintAddress ??= postBal.mint.address; - break; - } - } - } - - final diff = userPreAmount - userPostAmount; - final rawAmount = diff.abs(); - - final amountInString = rawAmount.toStringAsFixed(6); - amount = double.parse(amountInString); - - isOutgoing = diff > 0; - - if (mintAddress == null && instruction.accounts.length >= 4) { - final mintIndex = instruction.accounts[3]; - mintAddress = message.accountKeys[mintIndex].address; - } - - final sender = message.accountKeys[instruction.accounts[0]].address; - final receiver = message.accountKeys[instruction.accounts[1]].address; - - String? tokenSymbol = splTokenSymbol; - - if (tokenSymbol == null && mintAddress != null) { - final token = await getTokenInfo(mintAddress); - tokenSymbol = token?.symbol; - } - - return SolanaTransactionModel( - isOutgoingTx: isOutgoing, - from: sender, - to: receiver, - id: signature, - amount: amount, - programId: SPLTokenProgramConst.tokenProgramId.address, - blockTimeInInt: blockTime?.toInt() ?? 0, - tokenSymbol: tokenSymbol ?? '', - fee: fee / SolanaUtils.lamportsPerSol, + final transactionModel = await _parseSPLTokenTransaction( + message: message, + meta: meta, + fee: fee, + feeInSol: feeInSol, + instruction: instruction, + walletAddress: walletAddress, + signature: signature, + blockTime: blockTime, + splTokenSymbol: splTokenSymbol, ); + + if (transactionModel != null) { + return transactionModel; + } + } else if (programId == AssociatedTokenAccountProgramConst.associatedTokenProgramId) { + // For ATA program, we need to check if this is a create account transaction + // or if it's part of a normal token transfer + + // We skip this transaction if this is the only instruction (this means that it's a create account transaction) + if (instructions.length == 1) { + return null; + } + + // We look for a token transfer instruction in the same transaction + bool hasTokenTransfer = false; + for (final otherInstruction in instructions) { + final otherProgramId = message.accountKeys[otherInstruction.programIdIndex]; + if (otherProgramId == SPLTokenProgramConst.tokenProgramId) { + hasTokenTransfer = true; + break; + } + } + + // If there's no token transfer instruction, it means this is just an ATA creation transaction + if (!hasTokenTransfer) { + return null; + } + + continue; } else { return null; } @@ -330,6 +252,144 @@ class SolanaWalletClient { return null; } + Future _parseNativeTransaction({ + required VersionedMessage message, + required ConfirmedTransactionMeta meta, + required int fee, + required double feeInSol, + required int feePayerIndex, + required String walletAddress, + required String signature, + required BigInt? blockTime, + }) async { + // Calculate total balance changes across all accounts + BigInt totalBalanceChange = BigInt.zero; + String? sender; + String? receiver; + + for (int i = 0; i < meta.preBalances.length; i++) { + final preBalance = meta.preBalances[i]; + final postBalance = meta.postBalances[i]; + final balanceChange = preBalance - postBalance; + + if (balanceChange > BigInt.zero) { + // This account sent funds + sender = message.accountKeys[i].address; + totalBalanceChange += balanceChange; + } else if (balanceChange < BigInt.zero) { + // This account received funds + receiver = message.accountKeys[i].address; + } + } + + // We subtract the fee from total balance change if the fee payer is the sender + if (sender == message.accountKeys[feePayerIndex].address) { + totalBalanceChange -= BigInt.from(fee); + } + + if (sender == null || receiver == null) { + return null; + } + + final amount = totalBalanceChange / BigInt.from(1e9); + final amountInSol = amount.abs().toDouble(); + + // Skip transactions with very small amounts (likely spam) + if (amountInSol < minValidAmount) { + return null; + } + + return SolanaTransactionModel( + isOutgoingTx: sender == walletAddress, + from: sender, + to: receiver, + id: signature, + amount: amountInSol, + programId: SystemProgramConst.programId.address, + tokenSymbol: 'SOL', + blockTimeInInt: blockTime?.toInt() ?? 0, + fee: feeInSol, + ); + } + + Future _parseSPLTokenTransaction({ + required VersionedMessage message, + required ConfirmedTransactionMeta meta, + required int fee, + required double feeInSol, + required CompiledInstruction instruction, + required String walletAddress, + required String signature, + required BigInt? blockTime, + String? splTokenSymbol, + }) async { + final preBalances = meta.preTokenBalances; + final postBalances = meta.postTokenBalances; + + double amount = 0.0; + bool isOutgoing = false; + String? mintAddress; + + double userPreAmount = 0.0; + if (preBalances != null && preBalances.isNotEmpty) { + for (final preBal in preBalances) { + if (preBal.owner?.address == walletAddress) { + userPreAmount = preBal.uiTokenAmount.uiAmount ?? 0.0; + + mintAddress = preBal.mint.address; + break; + } + } + } + + double userPostAmount = 0.0; + if (postBalances != null && postBalances.isNotEmpty) { + for (final postBal in postBalances) { + if (postBal.owner?.address == walletAddress) { + userPostAmount = postBal.uiTokenAmount.uiAmount ?? 0.0; + + mintAddress ??= postBal.mint.address; + break; + } + } + } + + final diff = userPreAmount - userPostAmount; + final rawAmount = diff.abs(); + + final amountInString = rawAmount.toStringAsFixed(6); + amount = double.parse(amountInString); + + isOutgoing = diff > 0; + + if (mintAddress == null && instruction.accounts.length >= 4) { + final mintIndex = instruction.accounts[3]; + mintAddress = message.accountKeys[mintIndex].address; + } + + final sender = message.accountKeys[instruction.accounts[0]].address; + final receiver = message.accountKeys[instruction.accounts[1]].address; + + String? tokenSymbol = splTokenSymbol; + + if (tokenSymbol == null && mintAddress != null) { + final token = await getTokenInfo(mintAddress); + tokenSymbol = token?.symbol; + } + + return SolanaTransactionModel( + isOutgoingTx: isOutgoing, + from: sender, + to: receiver, + id: signature, + amount: amount, + programId: SPLTokenProgramConst.tokenProgramId.address, + blockTimeInInt: blockTime?.toInt() ?? 0, + tokenSymbol: tokenSymbol ?? '', + fee: feeInSol, + ); + } + /// Load the Address's transactions into the account Future> fetchTransactions( SolAddress address, { @@ -381,11 +441,13 @@ class SolanaWalletClient { transactions.addAll(parsedTransactions.whereType().toList()); - // Calling the callback after each batch is processed, therefore passing the current list of transactions. - onUpdate(List.from(transactions)); + // Only update UI if we have new valid transactions + if (parsedTransactions.isNotEmpty) { + onUpdate(List.from(transactions)); + } if (i + batchSize < signatures.length) { - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 300)); } } @@ -732,19 +794,24 @@ class SolanaWalletClient { SolanaAccountInfo? accountInfo; try { accountInfo = await _provider!.request( - SolanaRPCGetAccountInfo(account: associatedTokenAccount.address), + SolanaRPCGetAccountInfo( + account: associatedTokenAccount.address, + commitment: Commitment.confirmed, + ), ); } catch (e) { accountInfo = null; } - // If aacountInfo is null, signifies that the associatedTokenAccount has only been created locally and not been broadcasted to the blockchain. + // If account exists, we return the associated token account if (accountInfo != null) return associatedTokenAccount; if (!shouldCreateATA) return null; + final payerAddress = payerPrivateKey.publicKey().toAddress(); + final createAssociatedTokenAccount = AssociatedTokenAccountProgram.associatedTokenAccount( - payer: payerPrivateKey.publicKey().toAddress(), + payer: payerAddress, associatedToken: associatedTokenAccount.address, owner: ownerAddress, mint: mintAddress, @@ -753,19 +820,23 @@ class SolanaWalletClient { final blockhash = await _getLatestBlockhash(Commitment.confirmed); final transaction = SolanaTransaction( - payerKey: payerPrivateKey.publicKey().toAddress(), + payerKey: payerAddress, instructions: [createAssociatedTokenAccount], recentBlockhash: blockhash, + type: TransactionType.v0, ); - transaction.sign([payerPrivateKey]); + final serializedTransaction = await _signTransactionInternal( + ownerPrivateKey: payerPrivateKey, + transaction: transaction, + ); await sendTransaction( - serializedTransaction: transaction.serializeString(), + serializedTransaction: serializedTransaction, commitment: Commitment.confirmed, ); - // Delay for propagation on the blockchain for newly created associated token addresses + // Wait for confirmation await Future.delayed(const Duration(seconds: 2)); return associatedTokenAccount; @@ -890,7 +961,7 @@ class SolanaWalletClient { }) async { /// Sign the transaction with the owner's private key. final ownerSignature = ownerPrivateKey.sign(transaction.serializeMessage()); - + transaction.addSignature(ownerPrivateKey.publicKey().toAddress(), ownerSignature); /// Serialize the transaction. diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index be354d976..0a4b7e841 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -767,8 +767,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return S.current.solana_no_associated_token_account_exception; } - if (errorMessage.contains('insufficient funds for rent')) { - return S.current.insufficientFundsForRentError; + if (errorMessage.contains('insufficient funds for rent') && + errorMessage.contains('Transaction simulation failed') && + errorMessage.contains('account_index')) { + final accountIndexMatch = RegExp(r'account_index: (\d+)').firstMatch(errorMessage); + if (accountIndexMatch != null) { + return int.parse(accountIndexMatch.group(1)!) == 0 + ? S.current.insufficientFundsForRentError + : S.current.insufficientFundsForRentErrorReceiver; + } } return errorMessage; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 3c0d4f48e..81f7fcb33 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "ليس لديك ما يكفي من SOL لتغطية المعاملة ورسوم المعاملات الخاصة بها. يرجى إضافة المزيد من SOL إلى محفظتك أو تقليل كمية SOL التي ترسلها.", "insufficient_lamports": "ليس لديك ما يكفي من SOL لتغطية المعاملة ورسوم المعاملات الخاصة بها. تحتاج على الأقل ${solValueNeeded} sol. يرجى إضافة المزيد من sol إلى محفظتك أو تقليل مبلغ sol الذي ترسله", "insufficientFundsForRentError": "ليس لديك ما يكفي من SOL لتغطية رسوم المعاملة والإيجار للحساب. يرجى إضافة المزيد من sol إلى محفظتك أو تقليل مبلغ sol الذي ترسله", + "insufficientFundsForRentErrorReceiver": "لا يحتوي حساب المتلقي على ما يكفي من SOL لتغطية الإيجار. يرجى اطلب من المتلقي إضافة المزيد من SOL إلى حسابه.", "introducing_cake_pay": "نقدم لكم Cake Pay!", "invalid_input": "مدخل غير صالح", "invalid_password": "رمز مرور خاطئ", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index e86a17f95..e952d3728 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Нямате достатъчно SOL, за да покриете транзакцията и таксата му за транзакция. Моля, добавете повече SOL към портфейла си или намалете сумата на SOL, която изпращате.", "insufficient_lamports": "Нямате достатъчно SOL, за да покриете транзакцията и таксата му за транзакция. Имате нужда от поне ${solValueNeeded} sol. Моля, добавете повече SOL към портфейла си или намалете сумата на SOL, която изпращате", "insufficientFundsForRentError": "Нямате достатъчно SOL, за да покриете таксата за транзакцията и наемането на сметката. Моля, добавете повече SOL към портфейла си или намалете сумата на SOL, която изпращате", + "insufficientFundsForRentErrorReceiver": "Сметката на приемника няма достатъчно SOL, за да покрие наема. Моля, помолете приемника да добави още SOL към техния акаунт.", "introducing_cake_pay": "Запознайте се с Cake Pay!", "invalid_input": "Невалиден вход", "invalid_password": "Невалидна парола", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 21a8b2cd4..1b17c4362 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Nemáte dostatek SOL na pokrytí transakce a jejího transakčního poplatku. Laskavě přidejte do své peněženky více solu nebo snižte množství Sol, kterou odesíláte.", "insufficient_lamports": "Nemáte dostatek SOL na pokrytí transakce a jejího transakčního poplatku. Potřebujete alespoň ${solValueNeeded} sol. Laskavě přidejte do své peněženky více SOL nebo snižte množství Sol, kterou odesíláte", "insufficientFundsForRentError": "Nemáte dostatek SOL na pokrytí transakčního poplatku a nájemného za účet. Laskavě přidejte do své peněženky více SOL nebo snižte množství Sol, kterou odesíláte", + "insufficientFundsForRentErrorReceiver": "Účet přijímače nemá dostatek SOL na pokrytí nájemného. Požádejte přijímač, aby na jejich účet přidal další SOL.", "introducing_cake_pay": "Představujeme Cake Pay!", "invalid_input": "Neplatný vstup", "invalid_password": "Neplatné heslo", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 17e75ded0..27a312b5c 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Bitte fügen Sie Ihrer Wallet mehr Sol hinzu oder reduzieren Sie die SOL-Menge, die Sie senden.", "insufficient_lamports": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Sie brauchen mindestens ${solValueNeeded} Sol. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol-Betrag", "insufficientFundsForRentError": "Sie haben nicht genug SOL, um die Transaktionsgebühr und die Miete für das Konto zu decken. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol-Betrag", + "insufficientFundsForRentErrorReceiver": "Das Konto des Empfängers hat nicht genug SOL, um die Miete zu decken. Bitte bitten Sie den Empfänger, ihr Konto mehr Sol hinzuzufügen.", "introducing_cake_pay": "Einführung von Cake Pay!", "invalid_input": "Ungültige Eingabe", "invalid_password": "Ungültiges Passwort", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 59533730e..725ab95a3 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "You do not have enough SOL to cover the transaction and its transaction fee. Kindly add more SOL to your wallet or reduce the SOL amount you\\'re sending.", "insufficient_lamports": "You do not have enough SOL to cover the transaction and its transaction fee. You need at least ${solValueNeeded} SOL. Kindly add more SOL to your wallet or reduce the SOL amount you\\'re sending", "insufficientFundsForRentError": "You do not have enough SOL to cover the transaction fee and rent for the account. Kindly add more SOL to your wallet or reduce the SOL amount you\\'re sending", + "insufficientFundsForRentErrorReceiver": "The receiver's account does not have enough SOL to cover the rent. Please ask the receiver to add more SOL to their account.", "introducing_cake_pay": "Introducing Cake Pay!", "invalid_input": "Invalid input", "invalid_password": "Invalid password", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 06cda2251..7c1bc280c 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "No tienes suficiente SOL para cubrir la transacción y su tarifa de transacción. Por favor, agrega más SOL a su billetera o reduce la cantidad de sol que está enviando.", "insufficient_lamports": "No tienes suficiente SOL para cubrir la transacción y su tarifa de transacción. Necesita al menos ${solValueNeeded} sol. Por favor, agrega más sol a su billetera o reduzca la cantidad de sol que está enviando", "insufficientFundsForRentError": "No tienes suficiente SOL para cubrir la tarifa de transacción y alquilar para la cuenta. Por favor, agrega más sol a su billetera o reduce la cantidad de sol que está enviando", + "insufficientFundsForRentErrorReceiver": "La cuenta del receptor no tiene suficiente SOL para cubrir el alquiler. Pida al receptor que agregue más SOL a su cuenta.", "introducing_cake_pay": "¡Presentamos Cake Pay!", "invalid_input": "Entrada inválida", "invalid_password": "Contraseña invalida", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 302435dcb..d762c558d 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Vous n'avez pas assez de sol pour couvrir la transaction et ses frais de transaction. Veuillez ajouter plus de Sol à votre portefeuille ou réduire la quantité de Sol que vous envoyez.", "insufficient_lamports": "Vous n'avez pas assez de sol pour couvrir la transaction et ses frais de transaction. Vous avez besoin d'au moins ${solValueNeeded} sol. Veuillez ajouter plus de Sol à votre portefeuille ou réduire la quantité de sol que vous envoyez", "insufficientFundsForRentError": "Vous n'avez pas assez de SOL pour couvrir les frais de transaction et le loyer pour le compte. Veuillez ajouter plus de Sol à votre portefeuille ou réduire la quantité de sol que vous envoyez", + "insufficientFundsForRentErrorReceiver": "Le compte du récepteur n'a pas assez de sol pour couvrir le loyer. Veuillez demander au récepteur d'ajouter plus de Sol à son compte.", "introducing_cake_pay": "Présentation de Cake Pay !", "invalid_input": "Entrée invalide", "invalid_password": "Mot de passe incorrect", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 6a3d2f2f0..ad4db22e5 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Ba ku da isasshen sool don rufe ma'amala da kuɗin ma'amala. Da unara ƙara ƙarin sool a cikin walat ɗinku ko rage adadin Sol ɗin da kuke aikawa.", "insufficient_lamports": "Ba ku da isasshen sool don rufe ma'amala da kuɗin ma'amala. Kuna buƙatar aƙalla ${solValueNeeded} Sol. Da kyau ƙara ƙarin sool zuwa walat ɗinku ko rage adadin Sol ɗin da kuke aikawa", "insufficientFundsForRentError": "Ba ku da isasshen Sol don rufe kuɗin ma'amala da haya don asusun. Da kyau ƙara ƙarin sool zuwa walat ɗinku ko rage adadin Sol ɗin da kuke aikawa", + "insufficientFundsForRentErrorReceiver": "Asusun mai karba bashi da isasshen soya don rufe haya. Da fatan za a nemi mai karba don ƙara ƙarin sol zuwa asusun su.", "introducing_cake_pay": "Gabatar da Cake Pay!", "invalid_input": "Shigar da ba daidai ba", "invalid_password": "Kalmar sirri mara inganci", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index bad13b469..819486206 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "आपके पास लेनदेन और इसके लेनदेन शुल्क को कवर करने के लिए पर्याप्त सोल नहीं है। कृपया अपने बटुए में अधिक सोल जोड़ें या आपके द्वारा भेजे जा रहे सोल राशि को कम करें।", "insufficient_lamports": "आपके पास लेनदेन और इसके लेनदेन शुल्क को कवर करने के लिए पर्याप्त सोल नहीं है। आपको कम से कम ${solValueNeeded} सोल की आवश्यकता है। कृपया अपने बटुए में अधिक सोल जोड़ें या सोल राशि को कम करें जिसे आप भेज रहे हैं", "insufficientFundsForRentError": "आपके पास लेन -देन शुल्क और खाते के लिए किराए को कवर करने के लिए पर्याप्त सोल नहीं है। कृपया अपने बटुए में अधिक सोल जोड़ें या सोल राशि को कम करें जिसे आप भेज रहे हैं", + "insufficientFundsForRentErrorReceiver": "रिसीवर के खाते में किराए को कवर करने के लिए पर्याप्त सोल नहीं है। कृपया रिसीवर को उनके खाते में अधिक सोल जोड़ने के लिए कहें।", "introducing_cake_pay": "परिचय Cake Pay!", "invalid_input": "अमान्य निवेश", "invalid_password": "अवैध पासवर्ड", @@ -569,8 +570,8 @@ "payjoin_unavailable_sheet_title": "Payjoin अनुपलब्ध क्यों है?", "payment_id": "भुगतान ID: ", "payment_made_easy": "भुगतान आसान किया गया", - "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।", + "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "payments": "भुगतान", "pending": " (अपूर्ण)", "percentageOf": "${amount} का", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 286ced787..43025f45c 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Nemate dovoljno SOL -a da pokriva transakciju i njegovu transakcijsku naknadu. Ljubazno dodajte više sol u svoj novčanik ili smanjite količinu SOL -a koju šaljete.", "insufficient_lamports": "Nemate dovoljno SOL -a da pokriva transakciju i njegovu transakcijsku naknadu. Trebate najmanje ${solValueNeeded} sol. Ljubazno dodajte više sol u svoj novčanik ili smanjite količinu SOL -a koju šaljete", "insufficientFundsForRentError": "Nemate dovoljno SOL -a za pokrivanje naknade za transakciju i najamninu za račun. Ljubazno dodajte više sol u svoj novčanik ili smanjite količinu SOL -a koju šaljete", + "insufficientFundsForRentErrorReceiver": "Račun prijemnika nema dovoljno SOL -a da pokriva najamninu. Molimo zamolite prijemnika da doda više SOL -a na svoj račun.", "introducing_cake_pay": "Predstavljamo Cake Pay!", "invalid_input": "Pogrešan unos", "invalid_password": "Netočna zaporka", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index b4d095027..57203b934 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Դուք չունեք բավարար SOL՝ գործարքն և գործարքի վարձը ծածկելու համար։ Խնդրում ենք ավելացնել ավելի շատ SOL ձեր դրամապանակում կամ նվազեցնել ուղարկվող SOL-ի քանակը։", "insufficient_lamports": "Դուք չունեք բավարար SOL՝ գործարքն և գործարքի վարձը ծածկելու համար։ Ձեզ անհրաժեշտ է առնվազն ${solValueNeeded} SOL։ Խնդրում ենք ավելացնել ավելի շատ SOL ձեր դրամապանակում կամ նվազեցնել ուղարկվող SOL-ի քանակը։", "insufficientFundsForRentError": "Ձեր մնացորդը բավարար չէ վարձակալության համար: Խնդրում ենք ավելացնել մնացորդը կամ նվազեցնել ուղարկվող գումարը", + "insufficientFundsForRentErrorReceiver": "Ստացողի հաշիվը չունի բավարար SOL, վարձավճարը ծածկելու համար: Խնդրում ենք ստանալ ստացողին ավելի շատ սոլ ավելացնել իրենց հաշվին:", "introducing_cake_pay": "Ներկայացնում ենք Cake Pay!", "invalid_input": "Սխալ մուտք", "invalid_password": "Սխալ գաղտնաբառ", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index d1d699150..b7cf3e84e 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Anda tidak memiliki cukup SOL untuk menutupi transaksi dan biaya transaksinya. Mohon tambahkan lebih banyak sol ke dompet Anda atau kurangi jumlah sol yang Anda kirim.", "insufficient_lamports": "Anda tidak memiliki cukup SOL untuk menutupi transaksi dan biaya transaksinya. Anda membutuhkan setidaknya ${solValueNeeded} sol. Mohon tambahkan lebih banyak sol ke dompet Anda atau kurangi jumlah sol yang Anda kirim", "insufficientFundsForRentError": "Anda tidak memiliki cukup SOL untuk menutupi biaya transaksi dan menyewa untuk akun tersebut. Mohon tambahkan lebih banyak sol ke dompet Anda atau kurangi jumlah sol yang Anda kirim", + "insufficientFundsForRentErrorReceiver": "Akun penerima tidak memiliki cukup SOL untuk menutupi sewa. Silakan minta penerima untuk menambahkan lebih banyak SOL ke akun mereka.", "introducing_cake_pay": "Perkenalkan Cake Pay!", "invalid_input": "Masukan tidak valid", "invalid_password": "Kata sandi salah", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 019885f42..6edfc837a 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Non hai abbastanza SOL per coprire la transazione e la sua quota di transazione. Aggiungi più SOL al tuo portafoglio, o riduci l'importo di SOL che stai inviando.", "insufficient_lamports": "Non hai abbastanza SOL per coprire la transazione e la sua quota di transazione. Hai bisogno di almeno ${solValueNeeded} SOL. Aggiungi più SOL al tuo portafoglio, o riduci l'importo di SOL che stai inviando", "insufficientFundsForRentError": "Non hai abbastanza SOL per coprire la tassa di transazione e l'affitto per il conto. Aggiungi più SOL al tuo portafoglio, o riduci l'importo di SOL che stai inviando", + "insufficientFundsForRentErrorReceiver": "L'account del destinatario non ha abbastanza SOL per coprire l'affitto. Si prega di chiedere al destinatario di aggiungere più SOL al loro account.", "introducing_cake_pay": "Vi presentiamo Cake Pay!", "invalid_input": "Inserimento non valido", "invalid_password": "Password non valida", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index c9021d7da..60b964f6d 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -419,6 +419,7 @@ "insufficient_lamport_for_tx": "トランザクションとその取引手数料をカバーするのに十分なSOLがありません。財布にソルを追加するか、送信するソル量を減らしてください。", "insufficient_lamports": "トランザクションとその取引手数料をカバーするのに十分なSOLがありません。少なくとも${solValueNeeded} solが必要です。財布にソルを追加するか、送信するソル量を減らしてください", "insufficientFundsForRentError": "アカウントの取引料金とレンタルをカバーするのに十分なソルがありません。財布にソルを追加するか、送信するソル量を減らしてください", + "insufficientFundsForRentErrorReceiver": "受信者のアカウントには、家賃をカバーするのに十分なソルがありません。レシーバーにアカウントにソルを追加するように依頼してください。", "introducing_cake_pay": "序章Cake Pay!", "invalid_input": "無効入力", "invalid_password": "無効なパスワード", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index a7ada45e7..31a9c5d0b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "트랜잭션 및 트랜잭션 수수료를 충당하기에 SOL이 부족합니다. 지갑에 SOL을 더 추가하거나 보내는 SOL 금액을 줄이세요.", "insufficient_lamports": "트랜잭션 및 트랜잭션 수수료를 충당하기에 SOL이 부족합니다. 최소 ${solValueNeeded} SOL이 필요합니다. 지갑에 SOL을 더 추가하거나 보내는 SOL 금액을 줄이세요.", "insufficientFundsForRentError": "계정의 트랜잭션 수수료 및 렌트를 충당하기에 SOL이 부족합니다. 지갑에 SOL을 더 추가하거나 보내는 SOL 금액을 줄이세요.", + "insufficientFundsForRentErrorReceiver": "수신기의 계정에는 임대료를 충당하기에 충분한 SOL이 없습니다. 수신기에게 계정에 더 많은 솔을 추가하도록 요청하십시오.", "introducing_cake_pay": "Cake Pay를 소개합니다!", "invalid_input": "잘못된 입력", "invalid_password": "잘못된 비밀번호", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 47f7acd20..9b0bb0025 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "သငျသညျငွေပေးငွေယူနှင့်၎င်း၏ငွေပေးငွေယူကြေးကိုဖုံးလွှမ်းရန် sol ရှိသည်မဟုတ်ကြဘူး။ ကြင်နာစွာသင်၏ပိုက်ဆံအိတ်သို့ပိုမို sol ကိုထပ်ထည့်ပါသို့မဟုတ်သင်ပို့လွှတ်ခြင်း sol ပမာဏကိုလျှော့ချပါ။", "insufficient_lamports": "သငျသညျငွေပေးငွေယူနှင့်၎င်း၏ငွေပေးငွေယူကြေးကိုဖုံးလွှမ်းရန် sol ရှိသည်မဟုတ်ကြဘူး။ သင်အနည်းဆုံး ${solValueNeeded} s ကိုလိုအပ်ပါတယ်။ ကြင်နာစွာသင်၏ပိုက်ဆံအိတ်သို့ပိုမို sol ကိုထပ်ထည့်ပါသို့မဟုတ်သင်ပို့နေသော sol ပမာဏကိုလျှော့ချပါ", "insufficientFundsForRentError": "သင်ငွေပေးချေမှုအခကြေးငွေကိုဖုံးအုပ်ရန်နှင့်အကောင့်ငှားရန်လုံလောက်သော sol ရှိသည်မဟုတ်ကြဘူး။ ကြင်နာစွာသင်၏ပိုက်ဆံအိတ်သို့ပိုမို sol ကိုပိုမိုထည့်ပါသို့မဟုတ်သင်ပို့ခြင်း sol ပမာဏကိုလျှော့ချပါ", + "insufficientFundsForRentErrorReceiver": "လက်ခံသူ၏အကောင့်တွင်အိမ်ငှားခကိုဖုံးအုပ်ရန်အစွမ်းမရှိနိုင်ပါ။ ကျေးဇူးပြု. လက်ခံသူအားသူတို့၏အကောင့်သို့ထပ်မံထည့်သွင်းရန်တောင်းဆိုပါ။", "introducing_cake_pay": "Cake Pay ကို မိတ်ဆက်ခြင်း။", "invalid_input": "ထည့်သွင်းမှု မမှန်ကန်ပါ။", "invalid_password": "မမှန်ကန်သောစကားဝှက်", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 41212aa5e..a58748b4e 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "U hebt niet genoeg SOL om de transactie en de transactiekosten te dekken. Voeg vriendelijk meer SOL toe aan uw portemonnee of verminder de SOL -hoeveelheid die u verzendt.", "insufficient_lamports": "U hebt niet genoeg SOL om de transactie en de transactiekosten te dekken. Je hebt minstens ${solValueNeeded} sol nodig. Voeg vriendelijk meer Sol toe aan uw portemonnee of verminder de SOL -hoeveelheid die u verzendt", "insufficientFundsForRentError": "U hebt niet genoeg SOL om de transactiekosten en huur voor de rekening te dekken. Voeg vriendelijk meer SOL toe aan uw portemonnee of verminder de SOL -hoeveelheid die u verzendt", + "insufficientFundsForRentErrorReceiver": "De account van de ontvanger heeft niet genoeg SOL om de huur te dekken. Vraag de ontvanger om meer SOL aan hun account toe te voegen.", "introducing_cake_pay": "Introductie van Cake Pay!", "invalid_input": "Ongeldige invoer", "invalid_password": "Ongeldig wachtwoord", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 16480285c..9dd895716 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Nie masz wystarczającej ilości SOL, aby pokryć transakcję i opłatę za transakcję. Dodaj więcej SOL do portfela lub zmniejsz wysyłaną kwotę SOL.", "insufficient_lamports": "Nie masz wystarczającej ilości SOL, aby pokryć transakcję i opłatę za transakcję. Potrzebujesz przynajmniej ${solValueNeeded} SOL. Uprzejmie dodaj więcej SOL do portfela lub zmniejsz wysyłaną kwotę SOL, którą wysyłasz", "insufficientFundsForRentError": "Nie masz wystarczającej ilości SOL, aby pokryć opłatę za transakcję i czynsz za konto. Dodaj więcej SOL do portfela lub zmniejsz kwotę, którą wysyłasz", + "insufficientFundsForRentErrorReceiver": "Konto odbiorcy nie ma wystarczającej ilości SOL, aby pokryć czynsz. Poproś odbiorcę o dodanie więcej SOL do ich konta.", "introducing_cake_pay": "Przedstawiamy Cake Pay!", "invalid_input": "Nieprawidłowe dane wejściowe", "invalid_password": "Nieprawidłowe hasło", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 0f2213d2b..fe54b1fd9 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Você não tem Sol suficiente para cobrir a transação e sua taxa de transação. Por favor, adicione mais sol à sua carteira ou reduza a quantidade de sol que você envia.", "insufficient_lamports": "Você não tem Sol suficiente para cobrir a transação e sua taxa de transação. Você precisa de pelo menos ${solValueNeeded} sol. Por favor, adicione mais sol à sua carteira ou reduza a quantidade de sol que você está enviando", "insufficientFundsForRentError": "Você não tem Sol suficiente para cobrir a taxa de transação e o aluguel da conta. Por favor, adicione mais sol à sua carteira ou reduza a quantidade de sol que você envia", + "insufficientFundsForRentErrorReceiver": "A conta do receptor não possui SOL suficiente para cobrir o aluguel. Por favor, peça ao destinatário que adicione mais sol à sua conta.", "introducing_cake_pay": "Apresentando o Cake Pay!", "invalid_input": "Entrada inválida", "invalid_password": "Senha inválida", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index bbbc03164..b1e49867a 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "У вас недостаточно Sol, чтобы покрыть транзакцию и плату за транзакцию. Пожалуйста, добавьте больше Sol в свой кошелек или уменьшите сумму Sol, которую вы отправляете.", "insufficient_lamports": "У вас недостаточно Sol, чтобы покрыть транзакцию и плату за транзакцию. Вам нужен как минимум ${solValueNeeded} sol. Пожалуйста, добавьте больше Sol в свой кошелек или уменьшите сумму Sol, которую вы отправляете", "insufficientFundsForRentError": "У вас недостаточно Sol, чтобы покрыть плату за транзакцию и аренду для счета. Пожалуйста, добавьте больше Sol в свой кошелек или уменьшите сумму Sol, которую вы отправляете", + "insufficientFundsForRentErrorReceiver": "У счета приемника не хватает Sol, чтобы покрыть арендную плату. Пожалуйста, попросите приемника добавить больше SOL в свою учетную запись.", "introducing_cake_pay": "Представляем Cake Pay!", "invalid_input": "Неверный Ввод", "invalid_password": "Неверный пароль", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index c5b540398..b1913d990 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "คุณไม่มีโซลเพียงพอที่จะครอบคลุมการทำธุรกรรมและค่าธรรมเนียมการทำธุรกรรม กรุณาเพิ่มโซลให้มากขึ้นลงในกระเป๋าเงินของคุณหรือลดจำนวนโซลที่คุณส่งมา", "insufficient_lamports": "คุณไม่มีโซลเพียงพอที่จะครอบคลุมการทำธุรกรรมและค่าธรรมเนียมการทำธุรกรรม คุณต้องการอย่างน้อย ${solValueNeeded} SOL กรุณาเพิ่มโซลให้มากขึ้นลงในกระเป๋าเงินของคุณหรือลดจำนวนโซลที่คุณกำลังส่ง", "insufficientFundsForRentError": "คุณไม่มีโซลเพียงพอที่จะครอบคลุมค่าธรรมเนียมการทำธุรกรรมและค่าเช่าสำหรับบัญชี กรุณาเพิ่มโซลให้มากขึ้นลงในกระเป๋าเงินของคุณหรือลดจำนวนโซลที่คุณส่งมา", + "insufficientFundsForRentErrorReceiver": "บัญชีของผู้รับไม่เพียงพอที่จะครอบคลุมค่าเช่า โปรดขอให้ผู้รับเพิ่ม SOL เพิ่มเติมในบัญชีของพวกเขา", "introducing_cake_pay": "ยินดีต้อนรับสู่ Cake Pay!", "invalid_input": "อินพุตไม่ถูกต้อง", "invalid_password": "รหัสผ่านไม่ถูกต้อง", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 23fedcdda..34adc4ad7 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "Wala kang sapat na SOL upang masakop ang transaksyon at ang bayad sa transaksyon nito. Mabuting magdagdag ng higit pa sa iyong pitaka o bawasan ang sol na halaga na iyong ipinapadala.", "insufficient_lamports": "Wala kang sapat na SOL upang masakop ang transaksyon at ang bayad sa transaksyon nito. Kailangan mo ng hindi bababa sa ${solValueNeeded} sol. Mabait na magdagdag ng higit pang sol sa iyong pitaka o bawasan ang dami ng iyong ipinapadala", "insufficientFundsForRentError": "Wala kang sapat na SOL upang masakop ang fee sa transaksyon at upa para sa account. Mabait na magdagdag ng higit pa sa iyong wallet o bawasan ang halaga ng SOL na iyong ipinapadala", + "insufficientFundsForRentErrorReceiver": "Ang account ng tatanggap ay walang sapat na sol upang masakop ang upa. Mangyaring hilingin sa tatanggap na magdagdag ng higit pang SOL sa kanilang account.", "introducing_cake_pay": "Pagpapakilala ng Cake Pay!", "invalid_input": "Di-wastong input", "invalid_password": "Di-wastong password", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 9396a9108..276f0c174 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "İşlemi ve işlem ücretini karşılamak için yeterli SOL'unuz yok. Lütfen cüzdanınıza daha fazla SOL ekleyin veya gönderdiğiniz sol miktarını azaltın.", "insufficient_lamports": "İşlemi ve işlem ücretini karşılamak için yeterli SOL'unuz yok. En az ${solValueNeeded} Sol'a ihtiyacınız var. Lütfen cüzdanınıza daha fazla sol ekleyin veya gönderdiğiniz sol miktarını azaltın", "insufficientFundsForRentError": "İşlem ücretini karşılamak ve hesap için kiralamak için yeterli SOL'nuz yok. Lütfen cüzdanınıza daha fazla sol ekleyin veya gönderdiğiniz sol miktarını azaltın", + "insufficientFundsForRentErrorReceiver": "Alıcının hesabının kirayı karşılamak için yeterli SOL yoktur. Lütfen alıcıdan hesaplarına daha fazla SOL eklemesini isteyin.", "introducing_cake_pay": "Cake Pay ile tanışın!", "invalid_input": "Geçersiz Giriş", "invalid_password": "Geçersiz şifre", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 07e38ff3b..ac9e34fac 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "У вас недостатньо SOL, щоб покрити транзакцію та її плату за трансакцію. Будь ласка, додайте до свого гаманця більше SOL або зменшіть суму, яку ви надсилаєте.", "insufficient_lamports": "У вас недостатньо SOL, щоб покрити транзакцію та її плату за трансакцію. Вам потрібно щонайменше ${solValueNeeded} sol. Будь ласка, додайте до свого гаманця більше SOL або зменшіть суму Sol, яку ви надсилаєте", "insufficientFundsForRentError": "У вас недостатньо SOL, щоб покрити плату за транзакцію та оренду на рахунок. Будь ласка, додайте до свого гаманця більше SOL або зменшіть суму, яку ви надсилаєте", + "insufficientFundsForRentErrorReceiver": "На рахунку одержувача не вистачає SOL, щоб покрити оренду. Будь ласка, попросіть одержувача додати більше SOL до свого рахунку.", "introducing_cake_pay": "Представляємо Cake Pay!", "invalid_input": "Неправильні дані", "invalid_password": "Недійсний пароль", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 9e4676d13..2a511adf9 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "آپ کے پاس ٹرانزیکشن اور اس کے لین دین کی فیس کا احاطہ کرنے کے لئے کافی SOL نہیں ہے۔ برائے مہربانی اپنے بٹوے میں مزید سول شامل کریں یا آپ کو بھیجنے والی سول رقم کو کم کریں۔", "insufficient_lamports": "آپ کے پاس ٹرانزیکشن اور اس کے لین دین کی فیس کا احاطہ کرنے کے لئے کافی SOL نہیں ہے۔ آپ کو کم از کم ${solValueNeeded} sol کی ضرورت ہے۔ برائے مہربانی اپنے بٹوے میں مزید SOL شامل کریں یا آپ جس SOL رقم کو بھیج رہے ہو اسے کم کریں", "insufficientFundsForRentError": "آپ کے پاس ٹرانزیکشن فیس اور اکاؤنٹ کے لئے کرایہ لینے کے ل enough اتنا SOL نہیں ہے۔ برائے مہربانی اپنے بٹوے میں مزید سول شامل کریں یا آپ کو بھیجنے والی سول رقم کو کم کریں", + "insufficientFundsForRentErrorReceiver": "وصول کنندہ کے اکاؤنٹ میں کرایہ کا احاطہ کرنے کے لئے کافی SOL نہیں ہے۔ براہ کرم وصول کنندہ سے ان کے اکاؤنٹ میں مزید SOL شامل کرنے کو کہیں۔", "introducing_cake_pay": "Cake پے کا تعارف!", "invalid_input": "غلط ان پٹ", "invalid_password": "غلط پاسورڈ", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 157232156..133ddbe30 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -417,6 +417,7 @@ "insufficient_lamport_for_tx": "Bạn không có đủ SOL để thanh toán giao dịch và phí giao dịch. Vui lòng thêm SOL vào ví của bạn hoặc giảm số lượng SOL bạn đang gửi.", "insufficient_lamports": "Bạn không có đủ SOL để thanh toán giao dịch và phí giao dịch. Bạn cần ít nhất ${solValueNeeded} SOL. Vui lòng thêm SOL vào ví của bạn hoặc giảm số lượng SOL bạn đang gửi", "insufficientFundsForRentError": "Bạn không có đủ SOL để thanh toán phí giao dịch và phí thuê cho tài khoản. Vui lòng thêm SOL vào ví của bạn hoặc giảm số lượng SOL bạn đang gửi", + "insufficientFundsForRentErrorReceiver": "Tài khoản của người nhận không có đủ SOL để trang trải tiền thuê nhà. Vui lòng yêu cầu người nhận thêm SOL vào tài khoản của họ.", "introducing_cake_pay": "Giới thiệu Cake Pay!", "invalid_input": "Nhập không hợp lệ", "invalid_password": "Mật khẩu không hợp lệ", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 2ab9c0a42..1d9f8ef88 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -419,6 +419,7 @@ "insufficient_lamport_for_tx": "O ko ni sosi to lati bo idunadura ati idiyele iṣowo rẹ. Fi agbara kun Sol diẹ sii si apamọwọ rẹ tabi dinku sodo naa ti o \\ 'tun n firanṣẹ.", "insufficient_lamports": "O ko ni sosi to lati bo idunadura ati idiyele iṣowo rẹ. O nilo o kere ju ${solValueNeeded}. Fi agbara kun Sol diẹ sii si apamọwọ rẹ tabi dinku soso ti o n firanṣẹ", "insufficientFundsForRentError": "O ko ni Sol kan lati bo owo isanwo naa ki o yalo fun iroyin naa. Fi agbara kun Sol diẹ sii si apamọwọ rẹ tabi dinku soso naa ti o \\ 'tun n firanṣẹ", + "insufficientFundsForRentErrorReceiver": "Akọọlẹ olugba ko ni Sol lati bo iyalo naa. Jọwọ beere olugba lati ṣafikun Sol diẹ sii si akọọlẹ wọn.", "introducing_cake_pay": "Ẹ bá Cake Pay!", "invalid_input": "Iṣawọle ti ko tọ", "invalid_password": "Ọrọ igbaniwọle ti ko wulo", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index e1b71508f..fd3526e13 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -418,6 +418,7 @@ "insufficient_lamport_for_tx": "您没有足够的溶胶来支付交易及其交易费用。请在您的钱包中添加更多溶胶或减少您发送的溶胶量。", "insufficient_lamports": "您没有足够的溶胶来支付交易及其交易费用。您至少需要${solValueNeeded} sol。请在您的钱包中添加更多溶胶或减少您发送的溶胶量", "insufficientFundsForRentError": "您没有足够的溶胶来支付该帐户的交易费和租金。请在钱包中添加更多溶胶或减少您发送的溶胶量", + "insufficientFundsForRentErrorReceiver": "接收器的帐户没有足够的溶胶来支付租金。请要求接收器向其帐户添加更多SOL。", "introducing_cake_pay": "介绍 Cake Pay!", "invalid_input": "输入无效", "invalid_password": "无效的密码", From a96b493b60b10837903d1bc59699f8db5dd3bcd6 Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 16 Jun 2025 16:49:43 +0200 Subject: [PATCH 2/6] CW 1080: fix(cw_monero): call store() directly after commiting tx (#2312) * fix(cw_monero): call store() directly after commiting tx to make sure that tx key is written to cache also, store it in TransactionDescription hive box * Update lib/view_model/send/send_view_model.dart --------- Co-authored-by: Omar Hatem --- .../lib/api/structs/pending_transaction.dart | 2 -- cw_monero/lib/api/transaction_history.dart | 16 ++++++++-------- cw_monero/lib/pending_monero_transaction.dart | 2 -- lib/entities/transaction_description.dart | 7 ++++++- lib/monero/cw_monero.dart | 2 +- lib/view_model/send/send_view_model.dart | 9 +++++++++ .../transaction_details_view_model.dart | 8 +++++++- 7 files changed, 31 insertions(+), 15 deletions(-) diff --git a/cw_monero/lib/api/structs/pending_transaction.dart b/cw_monero/lib/api/structs/pending_transaction.dart index dc5fbddd0..22f974f4b 100644 --- a/cw_monero/lib/api/structs/pending_transaction.dart +++ b/cw_monero/lib/api/structs/pending_transaction.dart @@ -5,13 +5,11 @@ class PendingTransactionDescription { required this.fee, required this.hash, required this.hex, - required this.txKey, required this.pointerAddress}); final int amount; final int fee; final String hash; final String hex; - final String txKey; final int pointerAddress; } \ No newline at end of file diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 2fe0888f6..fd6004140 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:ffi'; import 'dart:isolate'; @@ -194,14 +195,12 @@ Future createTransactionSync( final rFee = pendingTx.fee(); final rHash = pendingTx.txid(''); final rHex = pendingTx.hex(''); - final rTxKey = rHash; return PendingTransactionDescription( amount: rAmt, fee: rFee, hash: rHash, hex: rHex, - txKey: rTxKey, pointerAddress: pendingTx.ffiAddress(), ); } @@ -246,7 +245,6 @@ Future createTransactionMultDest( fee: tx.fee(), hash: tx.txid(''), hex: tx.hex(''), - txKey: tx.txid(''), pointerAddress: tx.ffiAddress(), ); } @@ -263,6 +261,7 @@ Future commitTransaction({required Wallet2PendingTransaction tx, requir filename: '', overwrite: false, ); + return null; }); String? error = (() { @@ -285,11 +284,12 @@ Future commitTransaction({required Wallet2PendingTransaction tx, requir if (error != null && error != "no tx keys found for this txid") { throw CreationTransactionException(message: error); } - if (useUR) { - return Future.value(txCommit as String?); - } else { - return Future.value(null); - } + unawaited(() async { + storeSync(force: true); + await Future.delayed(Duration(seconds: 5)); + storeSync(force: true); + }()); + return Future.value(txCommit); } class Transaction { diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart index f29d1ccd2..9909a3021 100644 --- a/cw_monero/lib/pending_monero_transaction.dart +++ b/cw_monero/lib/pending_monero_transaction.dart @@ -31,8 +31,6 @@ class PendingMoneroTransaction with PendingTransaction { @override String get hex => pendingTransactionDescription.hex; - String get txKey => pendingTransactionDescription.txKey; - @override String get amountFormatted => AmountConverter.amountIntToString( CryptoCurrency.xmr, pendingTransactionDescription.amount); diff --git a/lib/entities/transaction_description.dart b/lib/entities/transaction_description.dart index 2ac573652..05f64820e 100644 --- a/lib/entities/transaction_description.dart +++ b/lib/entities/transaction_description.dart @@ -5,7 +5,7 @@ part 'transaction_description.g.dart'; @HiveType(typeId: TransactionDescription.typeId) class TransactionDescription extends HiveObject { - TransactionDescription({required this.id, this.recipientAddress, this.transactionNote}); + TransactionDescription({required this.id, this.recipientAddress, this.transactionNote, this.transactionKey}); static const typeId = TRANSACTION_TYPE_ID; static const boxName = 'TransactionDescriptions'; @@ -20,12 +20,16 @@ class TransactionDescription extends HiveObject { @HiveField(2) String? transactionNote; + @HiveField(3) + String? transactionKey; + String get note => transactionNote ?? ''; Map toJson() => { 'id': id, 'recipientAddress': recipientAddress, 'transactionNote': transactionNote, + 'transactionKey': transactionKey, }; factory TransactionDescription.fromJson(Map json) { @@ -33,6 +37,7 @@ class TransactionDescription extends HiveObject { id: json['id'] as String, recipientAddress: json['recipientAddress'] as String?, transactionNote: json['transactionNote'] as String?, + transactionKey: json['transactionKey'] as String?, ); } } diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 6ae48b863..257ff895e 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -365,7 +365,7 @@ class CWMonero extends Monero { @override Map pendingTransactionInfo(Object transaction) { final ptx = transaction as PendingMoneroTransaction; - return {'id': ptx.id, 'hex': ptx.hex, 'key': ptx.txKey}; + return {'id': ptx.id, 'hex': ptx.hex}; } @override diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 0a4b7e841..edca5636c 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -590,16 +590,25 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (pendingTransaction!.id.isNotEmpty) { + TransactionInfo? tx; + if (walletType == WalletType.monero) { + await Future.delayed(Duration(milliseconds: 450)); + await wallet.fetchTransactions(); + final txhistory = monero!.getTransactionHistory(wallet); + tx = txhistory.transactions.values.last; + } final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; _settingsStore.shouldSaveRecipientAddress ? await transactionDescriptionBox.add(TransactionDescription( id: descriptionKey, recipientAddress: address, transactionNote: note, + transactionKey: tx?.additionalInfo["key"] as String?, )) : await transactionDescriptionBox.add(TransactionDescription( id: descriptionKey, transactionNote: note, + transactionKey: tx?.additionalInfo["key"] as String?, )); } final sharedPreferences = await SharedPreferences.getInstance(); diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 067ca73f9..691c94f56 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -233,7 +233,13 @@ abstract class TransactionDetailsViewModelBase with Store { } void _addMoneroListItems(TransactionInfo tx, DateFormat dateFormat) { - final key = tx.additionalInfo['key'] as String?; + final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; + final description = transactionDescriptionBox.values.firstWhere( + (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, + orElse: () => TransactionDescription(id: descriptionKey)); + + + final key = tx.additionalInfo['key'] as String? ?? description.transactionKey; final accountIndex = tx.additionalInfo['accountIndex'] as int; final addressIndex = tx.additionalInfo['addressIndex'] as int; final feeFormatted = tx.feeFormatted(); From 4fb2fc47ad26f08d2eaeffebd486f9cf4e3ffd41 Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 16 Jun 2025 17:52:15 +0300 Subject: [PATCH 3/6] fix: extra ID for Trocador swap (#2307) --- .../provider/trocador_exchange_provider.dart | 2 ++ .../exchange/exchange_trade_view_model.dart | 24 ++++++++----------- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_hy.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_vi.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 30 files changed, 40 insertions(+), 14 deletions(-) diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index 26a9b2e35..170c53627 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -230,6 +230,7 @@ class TrocadorExchangeProvider extends ExchangeProvider { final providerName = responseJSON['provider'] as String; final amount = responseJSON['amount_from']?.toString(); final receiveAmount = responseJSON['amount_to']?.toString(); + final addressProviderMemo = responseJSON['address_provider_memo'] as String?; return Trade( id: id, @@ -247,6 +248,7 @@ class TrocadorExchangeProvider extends ExchangeProvider { receiveAmount: receiveAmount ?? request.toAmount, payoutAddress: payoutAddress, isSendAll: isSendAll, + extraId: addressProviderMemo, ); } diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index fc7a5429b..76a49e1d5 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -96,11 +96,12 @@ abstract class ExchangeTradeViewModelBase with Store { bool isSendable; @computed - String get extraInfo => trade.from == CryptoCurrency.xlm - ? '\n\n' + S.current.xlm_extra_info - : trade.from == CryptoCurrency.xrp - ? '\n\n' + S.current.xrp_extra_info - : ''; + String get extraInfo => switch (trade.from) { + CryptoCurrency.xlm => '\n\n' + S.current.xlm_extra_info, + CryptoCurrency.xrp => '\n\n' + S.current.xrp_extra_info, + CryptoCurrency.ton => '\n\n' + S.current.ton_extra_info, + _ => '' + }; @computed String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled @@ -203,12 +204,12 @@ abstract class ExchangeTradeViewModelBase with Store { ]); if (trade.extraId != null) { - final shouldAddExtraId = trade.from == CryptoCurrency.xrp || trade.from == CryptoCurrency.xlm; + final shouldAddExtraId = trade.from == CryptoCurrency.xrp || trade.from == CryptoCurrency.xlm || trade.from == CryptoCurrency.ton; if (shouldAddExtraId) { final title = trade.from == CryptoCurrency.xrp ? S.current.destination_tag - : trade.from == CryptoCurrency.xlm + : trade.from == CryptoCurrency.xlm || trade.from == CryptoCurrency.ton ? S.current.memo : S.current.extra_id; @@ -217,13 +218,8 @@ abstract class ExchangeTradeViewModelBase with Store { title: title, data: '${trade.extraId}', isCopied: true, - isReceiveDetail: (trade.from == CryptoCurrency.xrp || trade.from == CryptoCurrency.xlm) - ? false - : true, - isExternalSendDetail: - (trade.from == CryptoCurrency.xrp || trade.from == CryptoCurrency.xlm) - ? true - : false, + isReceiveDetail: !shouldAddExtraId, + isExternalSendDetail: shouldAddExtraId ), ); } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 81f7fcb33..0c63a33b4 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -904,6 +904,7 @@ "token_name": "اسم الرمز ، على سبيل المثال: Tether", "token_symbol": "رمز العملة ، على سبيل المثال: USDT", "tokenID": "ﻒﻳﺮﻌﺗ ﺔﻗﺎﻄﺑ", + "ton_extra_info": "يرجى عدم نسيان تحديد معرف المذكرة أثناء إرسال معاملة TON للتبادل", "tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ", "tor_only": "Tor فقط", "total": "المجموع", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index e952d3728..8163da646 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -904,6 +904,7 @@ "token_name": "Име на токена, напр.: Tether", "token_symbol": "Символ на токена, напр.: USDT", "tokenID": "документ за самоличност", + "ton_extra_info": "Моля, не забравяйте да посочите идентификационния номер на бележката, докато изпращате транзакцията TON за борсата", "tor_connection": "Tor връзка", "tor_only": "Само чрез Tor", "total": "Обща сума", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 1b17c4362..80b386859 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -904,6 +904,7 @@ "token_name": "Název tokenu např.: Tether", "token_symbol": "Symbol tokenu, např.: USDT", "tokenID": "ID", + "ton_extra_info": "Při odeslání TON transakce pro výměnu nezapomeňte zadat ID Memo ID", "tor_connection": "Připojení Tor", "tor_only": "Pouze Tor", "total": "Celkový", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 27a312b5c..479954b23 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -905,6 +905,7 @@ "token_name": "Token-Name, z. B.: Tether", "token_symbol": "Token-Symbol, z. B.: USDT", "tokenID": "AUSWEIS", + "ton_extra_info": "Bitte vergessen Sie nicht, die Memo -ID anzugeben, während Sie die TON -Transaktion für den Austausch senden", "tor_connection": "Tor-Verbindung", "tor_only": "Nur Tor", "total": "Gesamt", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 725ab95a3..971a68455 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -905,6 +905,7 @@ "token_name": "Token name eg: Tether", "token_symbol": "Token symbol eg: USDT", "tokenID": "ID", + "ton_extra_info": "Please don’t forget to specify the Memo ID while sending the TON transaction for the exchange", "tor_connection": "Tor connection", "tor_only": "Tor only", "total": "Total", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 7c1bc280c..fe802237a 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -905,6 +905,7 @@ "token_name": "Nombre del token, por ejemplo: Tether", "token_symbol": "Símbolo de token, por ejemplo: USDT", "tokenID": "IDENTIFICACIÓN", + "ton_extra_info": "No olvide especificar el ID de memo mientras envía la transacción TON para el intercambio", "tor_connection": "conexión tor", "tor_only": "solo Tor", "total": "Total", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index d762c558d..b78ef8bc5 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -904,6 +904,7 @@ "token_name": "Nom du token, par exemple : Tether", "token_symbol": "Symbole de token, par exemple : USDT", "tokenID": "IDENTIFIANT", + "ton_extra_info": "N'oubliez pas de spécifier l'identification de la note lors de l'envoi de la transaction TON pour l'échange", "tor_connection": "Connexion Tor", "tor_only": "Tor uniquement", "total": "Total", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index ad4db22e5..710960b7d 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -906,6 +906,7 @@ "token_name": "Alamar sunan misali: Tether", "token_symbol": "Alamar alama misali: USDT", "tokenID": "ID", + "ton_extra_info": "Don Allah kar a manta su saka ID na Memo yayin aikawa da ma'amala don musayar", "tor_connection": "Tor haɗin gwiwa", "tor_only": "Tor kawai", "total": "Duka", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 819486206..f0a7efaaf 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -906,6 +906,7 @@ "token_name": "टोकन नाम जैसे: टीथर", "token_symbol": "टोकन प्रतीक जैसे: यूएसडीटी", "tokenID": "पहचान", + "ton_extra_info": "कृपया एक्सचेंज के लिए टन लेनदेन भेजते समय मेमो आईडी निर्दिष्ट करना न भूलें", "tor_connection": "टोर कनेक्शन", "tor_only": "Tor केवल", "total": "कुल", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 43025f45c..5ecfb5b21 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -904,6 +904,7 @@ "token_name": "Naziv tokena npr.: Tether", "token_symbol": "Simbol tokena npr.: USDT", "tokenID": "iskaznica", + "ton_extra_info": "Ne zaboravite navesti ID memorandu", "tor_connection": "Tor veza", "tor_only": "Samo Tor", "total": "Ukupno", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 57203b934..5d274c632 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -902,6 +902,7 @@ "token_name": "Token-ի անուն, օրինակ՝ Tether", "token_symbol": "Token-ի նշան, օրինակ՝ USDT", "tokenID": "ID", + "ton_extra_info": "Խնդրում ենք մի մոռացեք նշել MEMO ID- ն, երբ փոխանակման համար տոննա գործարքը ուղարկեք", "tor_connection": "Tor կապ", "tor_only": "Միայն Tor", "total": "Ընդհանուր", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b7cf3e84e..b7847badf 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -907,6 +907,7 @@ "token_name": "Nama token misalnya: Tether", "token_symbol": "Simbol token misalnya: USDT", "tokenID": "PENGENAL", + "ton_extra_info": "Harap jangan lupa untuk menentukan ID memo saat mengirim transaksi ton untuk pertukaran", "tor_connection": "koneksi Tor", "tor_only": "Hanya Tor", "total": "Total", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 6edfc837a..a0ff46c4f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -905,6 +905,7 @@ "token_name": "Nome del token, ad esempio: Tether", "token_symbol": "Simbolo del token, ad esempio: USDT", "tokenID": "ID", + "ton_extra_info": "Non dimenticare di specificare l'ID memo durante l'invio della transazione Ton per lo scambio", "tor_connection": "Connessione Tor", "tor_only": "Solo Tor", "total": "Totale", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 60b964f6d..ac8dafbac 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -905,6 +905,7 @@ "token_name": "トークン名 例: Tether", "token_symbol": "トークンシンボル 例: USDT", "tokenID": "ID", + "ton_extra_info": "Exchangeのトントランザクションを送信しながら、メモIDを指定することを忘れないでください", "tor_connection": "Tor接続", "tor_only": "Torのみ", "total": "合計", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 31a9c5d0b..e2b21aa04 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -905,6 +905,7 @@ "token_name": "토큰 이름 (예: Tether)", "token_symbol": "토큰 심볼 (예: USDT)", "tokenID": "ID", + "ton_extra_info": "교환을 위해 TON 트랜잭션을 보내는 동안 메모 ID를 지정하는 것을 잊지 마십시오.", "tor_connection": "Tor 연결", "tor_only": "Tor 전용", "total": "합계", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 9b0bb0025..edafed5d2 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -904,6 +904,7 @@ "token_name": "တိုကင်အမည် ဥပမာ- Tether", "token_symbol": "တိုကင်သင်္ကေတ ဥပမာ- USDT", "tokenID": "အမှတ်သညာ", + "ton_extra_info": "ငွေလဲလှယ်မှုအတွက်တန်ပြန်ငွေပေးငွေယူကိုပို့နေစဉ် Memo ID ကိုသတ်မှတ်ရန်မမေ့ပါနှင့်", "tor_connection": "Tor ချိတ်ဆက်မှု", "tor_only": "Tor သာ", "total": "လုံးဝသော", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index a58748b4e..c47160a5f 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -904,6 +904,7 @@ "token_name": "Tokennaam bijv.: Tether", "token_symbol": "Tokensymbool bijv.: USDT", "tokenID": "ID kaart", + "ton_extra_info": "Vergeet niet om de memo -ID op te geven tijdens het verzenden van de ton -transactie voor de uitwisseling", "tor_connection": "Tor-verbinding", "tor_only": "Alleen Tor", "total": "Totaal", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 9dd895716..b3b99e28c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -904,6 +904,7 @@ "token_name": "Nazwa tokena, np.: Tether", "token_symbol": "Symbol tokena np.: USDT", "tokenID": "ID", + "ton_extra_info": "Nie zapomnij określić identyfikatora notatki podczas wysyłania transakcji TON dla wymiany", "tor_connection": "Połączenie przez Tor", "tor_only": "Tylko sieć Tor", "total": "Całkowity", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index fe54b1fd9..9b55fc7b6 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -906,6 +906,7 @@ "token_name": "Nome do token, por exemplo: Tether", "token_symbol": "Símbolo de token, por exemplo: USDT", "tokenID": "EU IA", + "ton_extra_info": "Não se esqueça de especificar o ID do memorando ao enviar a transação TON para a troca", "tor_connection": "Conexão Tor", "tor_only": "Tor apenas", "total": "Total", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index b1e49867a..3e61f4277 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -905,6 +905,7 @@ "token_name": "Имя токена, например: Tether", "token_symbol": "Символ токена, например: USDT", "tokenID": "ИДЕНТИФИКАТОР", + "ton_extra_info": "Пожалуйста, не забудьте указать идентификатор записки при отправке TON транзакции для обмена", "tor_connection": "Тор соединение", "tor_only": "Только Tor", "total": "Общий", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index b1913d990..a60f1b332 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -904,6 +904,7 @@ "token_name": "ชื่อโทเค็น เช่น Tether", "token_symbol": "สัญลักษณ์โทเค็น เช่น USDT", "tokenID": "บัตรประจำตัวประชาชน", + "ton_extra_info": "โปรดอย่าลืมระบุรหัสบันทึกในขณะที่ส่งธุรกรรม TON สำหรับการแลกเปลี่ยน", "tor_connection": "การเชื่อมต่อทอร์", "tor_only": "Tor เท่านั้น", "total": "ทั้งหมด", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 34adc4ad7..9ed4e13a8 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -904,6 +904,7 @@ "token_name": "Pangalan ng token, halimbawa: Tether", "token_symbol": "Simbolo ng token, halimbawa: USDT", "tokenID": "ID", + "ton_extra_info": "Mangyaring huwag kalimutan na tukuyin ang memo ID habang nagpapadala ng toneladang transaksyon para sa palitan", "tor_connection": "Koneksyon ng Tor", "tor_only": "Tor lamang", "total": "Kabuuan", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 276f0c174..4d790d1c6 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -904,6 +904,7 @@ "token_name": "Belirteç adı, örneğin: Tether", "token_symbol": "Jeton sembolü, örneğin: USDT", "tokenID": "İD", + "ton_extra_info": "Lütfen değişim için ton işlemini gönderirken not kimliğini belirtmeyi unutmayın", "tor_connection": "Tor bağlantısı", "tor_only": "Yalnızca Tor", "total": "Toplam", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index ac9e34fac..06b6d09e7 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -905,6 +905,7 @@ "token_name": "Назва токена, наприклад: Tether", "token_symbol": "Символ маркера, наприклад: USDT", "tokenID": "ID", + "ton_extra_info": "Не забудьте вказати ідентифікатор пам’яті під час надсилання транзакції TON для обміну", "tor_connection": "Підключення Tor", "tor_only": "Тільки Tor", "total": "Загальний", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 2a511adf9..c83de672b 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -906,6 +906,7 @@ "token_name": "ٹوکن کا نام جیسے: Tether", "token_symbol": "ٹوکن کی علامت جیسے: USDT", "tokenID": "ID", + "ton_extra_info": "ایکسچینج کے لئے ٹن ٹرانزیکشن بھیجتے وقت براہ کرم میمو آئی ڈی کی وضاحت کرنا نہ بھولیں", "tor_connection": "ﻦﺸﮑﻨﮐ ﺭﻮﭨ", "tor_only": "صرف Tor", "total": "کل", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 133ddbe30..14eeee189 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -901,6 +901,7 @@ "token_name": "Tên token ví dụ: Tether", "token_symbol": "Ký hiệu token ví dụ: USDT", "tokenID": "ID", + "ton_extra_info": "Xin đừng quên chỉ định ID ghi nhớ trong khi gửi giao dịch tấn cho trao đổi", "tor_connection": "Kết nối Tor", "tor_only": "Chỉ Tor", "total": "Tổng cộng", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 1d9f8ef88..fee486da4 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -905,6 +905,7 @@ "token_name": "Orukọ àmi fun apẹẹrẹ: Tether", "token_symbol": "Aami aami fun apẹẹrẹ: USDT", "tokenID": "ID", + "ton_extra_info": "Jọwọ maṣe gbagbe lati tokasi ID akọsilẹ lakoko fifiranṣẹ idunadura pupọ fun paṣipaarọ naa", "tor_connection": "Tor asopọ", "tor_only": "Tor nìkan", "total": "Apapọ", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index fd3526e13..ffa8509c4 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -904,6 +904,7 @@ "token_name": "代币名称例如:Tether", "token_symbol": "代币符号例如:USDT", "tokenID": "ID", + "ton_extra_info": "请不要忘记在发送TON交易时指定备忘录", "tor_connection": "Tor连接", "tor_only": "仅限 Tor", "total": "全部的", From 85d3e727e20203b411e3225396f6ca00d280f18c Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 17 Jun 2025 00:31:49 +0200 Subject: [PATCH 4/6] CW-1092-restoring-from-backup-doesnt-maintain-hardware-wallets (#2319) * feat: add hardware wallet verification during backup restoration * style: improve readability of verifyHardwareWallets in backup_service_v3.dart --- lib/core/backup_service.dart | 10 +++++---- lib/core/backup_service_v3.dart | 37 ++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 2e8523024..87bb71ce9 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -110,7 +110,7 @@ class $BackupService { } Future verifyWallets() async { - final walletInfoSource = await _reloadHiveWalletInfoBox(); + final walletInfoSource = await reloadHiveWalletInfoBox(); correctWallets = walletInfoSource.values.where((info) => availableWalletTypes.contains(info.type)).toList(); @@ -119,7 +119,7 @@ class $BackupService { } } - Future> _reloadHiveWalletInfoBox() async { + Future> reloadHiveWalletInfoBox() async { final appDir = await getAppDir(); await CakeHive.close(); CakeHive.init(appDir.path); @@ -288,13 +288,15 @@ class $BackupService { return { 'name': walletInfo.name, 'type': walletInfo.type.toString(), - 'password': await keyService.getWalletPassword(walletName: walletInfo.name) + 'password': await keyService.getWalletPassword(walletName: walletInfo.name), + 'hardwareWalletType': walletInfo.hardwareWalletType?.index, }; } catch (e) { return { 'name': walletInfo.name, 'type': walletInfo.type.toString(), - 'password': '' + 'password': '', + 'hardwareWalletType': walletInfo.hardwareWalletType?.index, }; } })); diff --git a/lib/core/backup_service_v3.dart b/lib/core/backup_service_v3.dart index be678b679..a0640dfd3 100644 --- a/lib/core/backup_service_v3.dart +++ b/lib/core/backup_service_v3.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/utils/package_info.dart'; import 'package:crypto/crypto.dart'; import 'package:cw_core/root_dir.dart'; import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:flutter/foundation.dart'; enum BackupVersion { @@ -305,6 +306,7 @@ class BackupServiceV3 extends $BackupService { // Continue importing the backup the old way await super.verifyWallets(); + await verifyHardwareWallets(password); await super.importKeychainDumpV2(password); await super.importPreferencesDump(); await super.importTransactionDescriptionDump(); @@ -313,6 +315,39 @@ class BackupServiceV3 extends $BackupService { decryptedData.deleteSync(); } + Future verifyHardwareWallets(String password, + {String keychainSalt = secrets.backupKeychainSalt}) async { + final walletInfoSource = await reloadHiveWalletInfoBox(); + final appDir = await getAppDir(); + final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); + final decryptedKeychainDumpFileData = await decryptV2( + keychainDumpFile.readAsBytesSync(), '$keychainSalt$password'); + final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData)) + as Map; + final keychainWalletsInfo = keychainJSON['wallets'] as List; + + final expectedHardwareWallets = keychainWalletsInfo + .where((e) => + (e as Map).containsKey("hardwareWalletType") && + e["hardwareWalletType"] != null) + .toList(); + + for (final expectedHardwareWallet in expectedHardwareWallets) { + final info = expectedHardwareWallet as Map; + final actualWalletInfo = walletInfoSource.values + .where((e) => + e.name == info['name'] && e.type.toString() == info['type']) + .firstOrNull; + if (actualWalletInfo != null && + info["hardwareWalletType"] != + actualWalletInfo.hardwareWalletType?.index) { + actualWalletInfo.hardwareWalletType = + HardwareWalletType.values[info["hardwareWalletType"] as int]; + await actualWalletInfo.save(); + } + } + } + Future exportBackupFileV3(String password, {String nonce = secrets.backupSalt}) async { final metadata = BackupMetadata( version: BackupVersion.v3, @@ -467,4 +502,4 @@ This backup was created on ${DateTime.now().toIso8601String()} file.writeAsBytesSync(data); return file; } -} \ No newline at end of file +} From 17d99e5451d33408859570a80d7c8e502d0d296b Mon Sep 17 00:00:00 2001 From: cyan Date: Tue, 17 Jun 2025 00:32:11 +0200 Subject: [PATCH 5/6] feat(ui): add app logo to qr code (#2072) fix(ui): use high instead of low error correction for QR codes --- assets/images/qr-cake.png | Bin 0 -> 73464 bytes lib/src/screens/receive/widgets/qr_image.dart | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 assets/images/qr-cake.png diff --git a/assets/images/qr-cake.png b/assets/images/qr-cake.png new file mode 100644 index 0000000000000000000000000000000000000000..7c54dedb01811abcfc586284b25288ec3414e854 GIT binary patch literal 73464 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 E0S!xubpQYW literal 0 HcmV?d00001 diff --git a/lib/src/screens/receive/widgets/qr_image.dart b/lib/src/screens/receive/widgets/qr_image.dart index 043958ef4..51c0c7431 100644 --- a/lib/src/screens/receive/widgets/qr_image.dart +++ b/lib/src/screens/receive/widgets/qr_image.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart' as qr; @@ -8,7 +9,7 @@ class QrImage extends StatelessWidget { this.backgroundColor = Colors.white, this.size = 100.0, this.version, - this.errorCorrectionLevel = qr.QrErrorCorrectLevel.L, + this.errorCorrectionLevel = qr.QrErrorCorrectLevel.H, }); final double size; @@ -28,6 +29,7 @@ class QrImage extends StatelessWidget { foregroundColor: foregroundColor, backgroundColor: backgroundColor, padding: const EdgeInsets.all(8.0), + embeddedImage: AssetImage('assets/images/qr-cake.png'), ); } } From 65402ba1eb88c379e19f34f6316897e08df6941f Mon Sep 17 00:00:00 2001 From: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com> Date: Tue, 17 Jun 2025 07:37:49 +0900 Subject: [PATCH 6/6] dcr: Always fetch the current dir path. (#2242) * dcr: Always fetch the current dir path. On ios devices the path will change between updates breaking decred. Never save the path and always check to ensure it is up to date. Previous wallets were also not creating a directory in the correct place. Move those when found. * Update cw_decred/lib/wallet_service.dart * dcr: Update libwallet dep. --------- Co-authored-by: Omar Hatem --- cw_decred/lib/wallet.dart | 15 +++-- cw_decred/lib/wallet_service.dart | 99 +++++++++++++++++++++++++------ scripts/android/build_decred.sh | 2 +- scripts/ios/build_decred.sh | 2 +- scripts/macos/build_decred.sh | 2 +- 5 files changed, 92 insertions(+), 28 deletions(-) diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index ac70a4aaa..97aee775d 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -5,6 +5,8 @@ import 'package:path/path.dart' as p; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:cw_decred/amount_format.dart'; import 'package:cw_decred/pending_transaction.dart'; import 'package:cw_decred/transaction_credentials.dart'; @@ -307,9 +309,10 @@ abstract class DecredWalletBase persistantPeer = addr; await _libwallet.closeWallet(walletInfo.name); final network = isTestnet ? "testnet" : "mainnet"; + final dirPath = await pathForWalletDir(name: walletInfo.name, type: WalletType.decred); final config = { "name": walletInfo.name, - "datadir": walletInfo.dirPath, + "datadir": dirPath, "net": network, "unsyncedaddrs": true, }; @@ -605,22 +608,22 @@ abstract class DecredWalletBase final sourceDir = Directory(currentDirPath); final targetDir = Directory(newDirPath); - + if (!targetDir.existsSync()) { await targetDir.create(recursive: true); } - + await for (final entity in sourceDir.list(recursive: true)) { - final relativePath = entity.path.substring(sourceDir.path.length+1); + final relativePath = entity.path.substring(sourceDir.path.length + 1); final targetPath = p.join(targetDir.path, relativePath); - + if (entity is File) { await entity.rename(targetPath); } else if (entity is Directory) { await Directory(targetPath).create(recursive: true); } } - + await sourceDir.delete(recursive: true); } diff --git a/cw_decred/lib/wallet_service.dart b/cw_decred/lib/wallet_service.dart index e2313904e..93c708886 100644 --- a/cw_decred/lib/wallet_service.dart +++ b/cw_decred/lib/wallet_service.dart @@ -8,6 +8,7 @@ import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:path/path.dart'; import 'package:hive/hive.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -57,42 +58,93 @@ class DecredWalletService extends WalletService< @override Future create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async { await this.init(); + final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType()); + final network = isTestnet == true ? testnet : mainnet; final config = { "name": credentials.walletInfo!.name, - "datadir": credentials.walletInfo!.dirPath, + "datadir": dirPath, "pass": credentials.password!, - "net": isTestnet == true ? testnet : mainnet, + "net": network, "unsyncedaddrs": true, }; await libwallet!.createWallet(jsonEncode(config)); final di = DerivationInfo( derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath); credentials.walletInfo!.derivationInfo = di; + credentials.walletInfo!.network = network; + // ios will move our wallet directory when updating. Since we must + // recalculate the new path every time we open the wallet, ensure this path + // is not used. An older wallet will have a directory here which is a + // condition for moving the wallet when opening, so this must be kept blank + // going forward. + credentials.walletInfo!.dirPath = ""; + credentials.walletInfo!.path = ""; final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await wallet.init(); return wallet; } + void copyDirectorySync(Directory source, Directory destination) { + /// create destination folder if not exist + if (!destination.existsSync()) { + destination.createSync(recursive: true); + } + + /// get all files from source (recursive: false is important here) + source.listSync(recursive: false).forEach((entity) { + final newPath = destination.path + Platform.pathSeparator + basename(entity.path); + if (entity is File) { + entity.rename(newPath); + } else if (entity is Directory) { + copyDirectorySync(entity, Directory(newPath)); + } + }); + } + + Future moveWallet(String fromPath, String toPath) async { + final oldWalletDir = new Directory(fromPath); + final newWalletDir = new Directory(toPath); + copyDirectorySync(oldWalletDir, newWalletDir); + // It would be ideal to delete the old directory here, but ios will error + // sometimes with "OS Error: No such file or directory, errno = 2" even + // after checking if it exists. + } + @override Future openWallet(String name, String password) async { final walletInfo = walletInfoSource.values .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; - final network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet || - walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet - ? testnet - : mainnet; + if (walletInfo.network == null || walletInfo.network == "") { + walletInfo.network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet || + walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet + ? testnet + : mainnet; + } await this.init(); - final walletDirExists = Directory(walletInfo.dirPath).existsSync(); - if (!walletDirExists) { - walletInfo.dirPath = await pathForWalletDir(name: name, type: getType()); + + // Cake wallet version 4.27.0 and earlier gave a wallet dir that did not + // match the name. Move those to the correct place. + final dirPath = await pathForWalletDir(name: name, type: getType()); + if (walletInfo.path != "") { + // On ios the stored dir no longer exists. We can only trust the basename. + // dirPath may already be updated and lost the basename, so look at path. + final randomBasename = basename(walletInfo.path); + final oldDir = await pathForWalletDir(name: randomBasename, type: getType()); + if (oldDir != dirPath) { + await this.moveWallet(oldDir, dirPath); + } + // Clear the path so this does not trigger again. + walletInfo.dirPath = ""; + walletInfo.path = ""; + await walletInfo.save(); } final config = { - "name": walletInfo.name, - "datadir": walletInfo.dirPath, - "net": network, + "name": name, + "datadir": dirPath, + "net": walletInfo.network, "unsyncedaddrs": true, }; await libwallet!.loadWallet(jsonEncode(config)); @@ -127,12 +179,11 @@ class DecredWalletService extends WalletService< await currentWallet.renameWalletFiles(newName); - final newDirPath = await pathForWalletDir(name: newName, type: getType()); final newWalletInfo = currentWalletInfo; newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.name = newName; - newWalletInfo.dirPath = newDirPath; - newWalletInfo.network = network; + newWalletInfo.dirPath = ""; + newWalletInfo.path = ""; await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); } @@ -141,18 +192,23 @@ class DecredWalletService extends WalletService< Future restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { await this.init(); + final network = isTestnet == true ? testnet : mainnet; + final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType()); final config = { "name": credentials.walletInfo!.name, - "datadir": credentials.walletInfo!.dirPath, + "datadir": dirPath, "pass": credentials.password!, "mnemonic": credentials.mnemonic, - "net": isTestnet == true ? testnet : mainnet, + "net": network, "unsyncedaddrs": true, }; await libwallet!.createWallet(jsonEncode(config)); final di = DerivationInfo( derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath); credentials.walletInfo!.derivationInfo = di; + credentials.walletInfo!.network = network; + credentials.walletInfo!.dirPath = ""; + credentials.walletInfo!.path = ""; final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await wallet.init(); @@ -165,17 +221,22 @@ class DecredWalletService extends WalletService< Future restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials, {bool? isTestnet}) async { await this.init(); + final network = isTestnet == true ? testnet : mainnet; + final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType()); final config = { "name": credentials.walletInfo!.name, - "datadir": credentials.walletInfo!.dirPath, + "datadir": dirPath, "pubkey": credentials.pubkey, - "net": isTestnet == true ? testnet : mainnet, + "net": network, "unsyncedaddrs": true, }; await libwallet!.createWatchOnlyWallet(jsonEncode(config)); final di = DerivationInfo( derivationPath: isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath); credentials.walletInfo!.derivationInfo = di; + credentials.walletInfo!.network = network; + credentials.walletInfo!.dirPath = ""; + credentials.walletInfo!.path = ""; final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, this.unspentCoinsInfoSource, libwallet!, closeLibwallet); await wallet.init(); diff --git a/scripts/android/build_decred.sh b/scripts/android/build_decred.sh index 75ed45aca..ce37a7353 100755 --- a/scripts/android/build_decred.sh +++ b/scripts/android/build_decred.sh @@ -7,7 +7,7 @@ cd "$(dirname "$0")" CW_DECRED_DIR=$(realpath ../..)/cw_decred LIBWALLET_PATH="${PWD}/decred/libwallet" LIBWALLET_URL="https://github.com/decred/libwallet.git" -LIBWALLET_VERSION="dba5327d35cb5d5d1ff113b780869deee154511f" +LIBWALLET_VERSION="05f8d7374999400fe4d525eb365c39b77d307b14" if [[ -e $LIBWALLET_PATH ]]; then rm -fr $LIBWALLET_PATH || true diff --git a/scripts/ios/build_decred.sh b/scripts/ios/build_decred.sh index 6860c7776..37384f4e1 100755 --- a/scripts/ios/build_decred.sh +++ b/scripts/ios/build_decred.sh @@ -3,7 +3,7 @@ set -e . ./config.sh LIBWALLET_PATH="${EXTERNAL_IOS_SOURCE_DIR}/libwallet" LIBWALLET_URL="https://github.com/decred/libwallet.git" -LIBWALLET_VERSION="dba5327d35cb5d5d1ff113b780869deee154511f" +LIBWALLET_VERSION="05f8d7374999400fe4d525eb365c39b77d307b14" if [[ -e $LIBWALLET_PATH ]]; then rm -fr $LIBWALLET_PATH diff --git a/scripts/macos/build_decred.sh b/scripts/macos/build_decred.sh index e7e5d492f..b1bbfbc43 100755 --- a/scripts/macos/build_decred.sh +++ b/scripts/macos/build_decred.sh @@ -4,7 +4,7 @@ LIBWALLET_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/libwallet" LIBWALLET_URL="https://github.com/decred/libwallet.git" -LIBWALLET_VERSION="dba5327d35cb5d5d1ff113b780869deee154511f" +LIBWALLET_VERSION="05f8d7374999400fe4d525eb365c39b77d307b14" echo "======================= DECRED LIBWALLET ========================="