diff --git a/.github/assets/Logo_CakeWallet.png b/.github/assets/Logo_CakeWallet.png new file mode 100644 index 000000000..459a6b37c Binary files /dev/null and b/.github/assets/Logo_CakeWallet.png differ diff --git a/.github/assets/NOTICE.txt b/.github/assets/NOTICE.txt new file mode 100644 index 000000000..9719639a1 --- /dev/null +++ b/.github/assets/NOTICE.txt @@ -0,0 +1,48 @@ +Notice for linux-badge.svg: + +1: +This is the Linux-penguin again... + +Originally drewn by Larry Ewing (http://www.isc.tamu.edu/~lewing/) +(with the GIMP) the Linux Logo has been vectorized by me (Simon Budig, +http://www.home.unix-ag.org/simon/). + +This happened quite some time ago with Corel Draw 4. But luckily +meanwhile there are tools available to handle vector graphics with +Linux. Bernhard Herzog (bernhard@users.sourceforge.net) deserves kudos +for creating Sketch (http://sketch.sourceforge.net), a powerful free +tool for creating vector graphics. He converted the Corel Draw file to +the Sketch native format. Since I am unable to maintain the Corel Draw +file any longer, the Sketch version now is the "official" one. + +Anja Gerwinski (anja@gerwinski.de) has created an alternate version of +the penguin (penguin-variant.sk) with a thinner mouth line and slightly +altered gradients. It also features a nifty drop shadow. + +The third bird (penguin-flat.sk) is a version reduced to three colors +(black/white/yellow) for e.g. silk screen printing. I made this version +for a mug, available at the friendly folks at +http://www.kernelconcepts.de/ - they do good stuff, mail Petra +(pinguin@kernelconcepts.de) if you need something special or don't +understand the german :-) + +These drawings are copyrighted by Larry Ewing and Simon Budig +(penguin-variant.sk also by Anja Gerwinski), redistribution is free but +has to include this README/Copyright notice. + +The use of these drawings is free. However I am happy about a sample of +your mug/t-shirt/whatever with this penguin on it... + +Have fun + Simon Budig + + +Simon.Budig@unix-ag.org +http://www.home.unix-ag.org/simon/ + +Simon Budig +Am Hardtkoeppel 2 +D-61279 Graevenwiesbach + +2: +Attribution: lewing@isc.tamu.edu Larry Ewing and The GIMP \ No newline at end of file diff --git a/.github/assets/app-store-badge.svg b/.github/assets/app-store-badge.svg new file mode 100755 index 000000000..072b425a1 --- /dev/null +++ b/.github/assets/app-store-badge.svg @@ -0,0 +1,46 @@ + + Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/assets/devices.png b/.github/assets/devices.png new file mode 100644 index 000000000..7bdccc5b5 Binary files /dev/null and b/.github/assets/devices.png differ diff --git a/.github/assets/f-droid-badge.png b/.github/assets/f-droid-badge.png new file mode 100644 index 000000000..2c9521de1 Binary files /dev/null and b/.github/assets/f-droid-badge.png differ diff --git a/.github/assets/google-play-badge.png b/.github/assets/google-play-badge.png new file mode 100644 index 000000000..9667c568d Binary files /dev/null and b/.github/assets/google-play-badge.png differ diff --git a/.github/assets/linux-badge.svg b/.github/assets/linux-badge.svg new file mode 100755 index 000000000..8416e1bb1 --- /dev/null +++ b/.github/assets/linux-badge.svg @@ -0,0 +1,1071 @@ + +linux-badgeGET IT ONLinuxlinux-badge diff --git a/.github/assets/mac-store-badge.svg b/.github/assets/mac-store-badge.svg new file mode 100755 index 000000000..c36a76a5a --- /dev/null +++ b/.github/assets/mac-store-badge.svg @@ -0,0 +1,51 @@ + + Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index ddc8869f0..dc231df42 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -139,7 +139,9 @@ jobs: echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart diff --git a/README.md b/README.md index 7b739f980..7823734fb 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,35 @@ -# Cake Wallet for Mobile and Desktop +
-## Open Source Multi-Currency Wallet + -## Links +
-* Website: https://cakewallet.com -* App Store (iOS / MacOS): https://cakewallet.com/ios -* Google Play: https://cakewallet.com/gp -* F-Droid: https://fdroid.cakelabs.com -* APK: https://github.com/cake-tech/cake_wallet/releases -* Linux: https://github.com/cake-tech/cake_wallet/releases +![devices](.github/assets/devices.png) + +
+ +[](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=iphone) +[](https://play.google.com/store/apps/details?id=com.cakewallet.cake_wallet) +[](https://fdroid.cakelabs.com) +[](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=mac) +[](https://github.com/cake-tech/cake_wallet/releases) + +
+ +# Cake Wallet + +Cake Wallet is an open source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux. + +Cake Wallet includes support for several cryptocurrencies, including: +* Monero (XMR) +* Bitcoin (BTC) +* Ethereum (ETH) +* Litecoin (LTC) +* Bitcoin Cash (BCH) +* Polygon (MATIC) +* Solana (SOL) +* Nano (XNO) +* Haven (XHV) ## Features diff --git a/assets/images/thorchain.png b/assets/images/thorchain.png new file mode 100644 index 000000000..674b60f82 Binary files /dev/null and b/assets/images/thorchain.png differ diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 90fcd2a75..e6aab2dda 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,4 +1,2 @@ -Monero enhancements -In-App live status page for the app services -Add Exolix exchange provider -Bug fixes and enhancements \ No newline at end of file +Exchange flow enhancements and fixes +Generic enhancements and bug fixes \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 83e18c18e..b32cd539d 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1 +1,6 @@ -Bug fixes and enhancements \ No newline at end of file +Exchange flow enhancements and fixes +Add MoonPay to Buy options +Add THORChain to Exchange providers +Improve Bitcoin fee calculations +Fixes and enhancements for Solana +Generic enhancements and bug fixes \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart index 3e21bae81..7bf488f3f 100644 --- a/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart +++ b/cw_bitcoin/lib/bitcoin_commit_transaction_exception.dart @@ -1,4 +1,8 @@ class BitcoinCommitTransactionException implements Exception { + String errorMessage; + BitcoinCommitTransactionException(this.errorMessage); + @override - String toString() => 'Transaction commit is failed.'; -} \ No newline at end of file + String toString() => errorMessage; +} + diff --git a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart index bd8f1763c..bda7c39ae 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_credentials.dart @@ -2,7 +2,8 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_core/output_info.dart'; class BitcoinTransactionCredentials { - BitcoinTransactionCredentials(this.outputs, {required this.priority, this.feeRate}); + BitcoinTransactionCredentials(this.outputs, + {required this.priority, this.feeRate}); final List outputs; final BitcoinTransactionPriority? priority; diff --git a/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart b/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart deleted file mode 100644 index fac7e93c4..000000000 --- a/cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart +++ /dev/null @@ -1,4 +0,0 @@ -class BitcoinTransactionNoInputsException implements Exception { - @override - String toString() => 'Not enough inputs available. Please select more under Coin Control'; -} diff --git a/cw_bitcoin/lib/bitcoin_transaction_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index 10953a2e0..d51775368 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -4,13 +4,15 @@ class BitcoinTransactionPriority extends TransactionPriority { const BitcoinTransactionPriority({required String title, required int raw}) : super(title: title, raw: raw); - static const List all = [fast, medium, slow]; + static const List all = [fast, medium, slow, custom]; static const BitcoinTransactionPriority slow = BitcoinTransactionPriority(title: 'Slow', raw: 0); static const BitcoinTransactionPriority medium = BitcoinTransactionPriority(title: 'Medium', raw: 1); static const BitcoinTransactionPriority fast = BitcoinTransactionPriority(title: 'Fast', raw: 2); + static const BitcoinTransactionPriority custom = + BitcoinTransactionPriority(title: 'Custom', raw: 3); static BitcoinTransactionPriority deserialize({required int raw}) { switch (raw) { @@ -20,6 +22,8 @@ class BitcoinTransactionPriority extends TransactionPriority { return medium; case 2: return fast; + case 3: + return custom; default: throw Exception('Unexpected token: $raw for BitcoinTransactionPriority deserialize'); } @@ -39,7 +43,10 @@ class BitcoinTransactionPriority extends TransactionPriority { label = 'Medium'; // S.current.transaction_priority_medium; break; case BitcoinTransactionPriority.fast: - label = 'Fast'; // S.current.transaction_priority_fast; + label = 'Fast'; + break; // S.current.transaction_priority_fast; + case BitcoinTransactionPriority.custom: + label = 'Custom'; break; default: break; @@ -48,7 +55,10 @@ class BitcoinTransactionPriority extends TransactionPriority { return label; } - String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)'; + String labelWithRate(int rate, int? customRate) { + final rateValue = this == custom ? customRate ??= 0 : rate; + return '${toString()} ($rateValue ${units}/byte)'; + } } class LitecoinTransactionPriority extends BitcoinTransactionPriority { diff --git a/cw_bitcoin/lib/bitcoin_transaction_wrong_balance_exception.dart b/cw_bitcoin/lib/bitcoin_transaction_wrong_balance_exception.dart deleted file mode 100644 index 3f379bea0..000000000 --- a/cw_bitcoin/lib/bitcoin_transaction_wrong_balance_exception.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:cw_core/crypto_currency.dart'; - -class BitcoinTransactionWrongBalanceException implements Exception { - BitcoinTransactionWrongBalanceException(this.currency); - - final CryptoCurrency currency; - - @override - String toString() => 'You do not have enough ${currency.title} to send this amount.'; -} \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index 51a53e285..0553170cc 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -7,10 +7,9 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:http/http.dart' as http; String jsonrpcparams(List params) { - final _params = params?.map((val) => '"${val.toString()}"')?.join(','); + final _params = params.map((val) => '"${val.toString()}"').join(','); return '[$_params]'; } @@ -34,6 +33,7 @@ class ElectrumClient { : _id = 0, _isConnected = false, _tasks = {}, + _errors = {}, unterminatedString = ''; static const connectionTimeout = Duration(seconds: 5); @@ -44,6 +44,7 @@ class ElectrumClient { void Function(bool)? onConnectionStatusChange; int _id; final Map _tasks; + final Map _errors; bool _isConnected; Timer? _aliveTimer; String unterminatedString; @@ -243,30 +244,20 @@ class ElectrumClient { }); Future broadcastTransaction( - {required String transactionRaw, BasedUtxoNetwork? network}) async { - if (network == BitcoinNetwork.testnet) { - return http - .post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'), - headers: {'Content-Type': 'application/json; charset=utf-8'}, - body: transactionRaw) - .then((http.Response response) { - if (response.statusCode == 200) { - return response.body; + {required String transactionRaw, + BasedUtxoNetwork? network, + Function(int)? idCallback}) async => + call( + method: 'blockchain.transaction.broadcast', + params: [transactionRaw], + idCallback: idCallback) + .then((dynamic result) { + if (result is String) { + return result; } - throw Exception('Failed to broadcast transaction: ${response.body}'); + return ''; }); - } - - return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw]) - .then((dynamic result) { - if (result is String) { - return result; - } - - return ''; - }); - } Future> getMerkle({required String hash, required int height}) async => await call(method: 'blockchain.transaction.get_merkle', params: [hash, height]) @@ -371,10 +362,12 @@ class ElectrumClient { } } - Future call({required String method, List params = const []}) async { + Future call( + {required String method, List params = const [], Function(int)? idCallback}) async { final completer = Completer(); _id += 1; final id = _id; + idCallback?.call(id); _registryTask(id, completer); socket!.write(jsonrpc(method: method, id: id, params: params)); @@ -456,6 +449,23 @@ class ElectrumClient { final id = response['id'] as String?; final result = response['result']; + try { + final error = response['error'] as Map?; + if (error != null) { + final errorMessage = error['message'] as String?; + if (errorMessage != null) { + _errors[id!] = errorMessage; + } + } + } catch (_) {} + + try { + final error = response['error'] as String?; + if (error != null) { + _errors[id!] = error; + } + } catch (_) {} + if (method is String) { _methodHandler(method: method, request: response); return; @@ -465,6 +475,8 @@ class ElectrumClient { _finish(id, result); } } + + String getErrorMessage(int id) => _errors[id.toString()] ?? ''; } // FIXME: move me diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index cfea0e089..f980bd884 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -11,12 +11,11 @@ import 'package:cw_core/wallet_type.dart'; class ElectrumTransactionBundle { ElectrumTransactionBundle(this.originalTransaction, - {required this.ins, required this.confirmations, this.time, required this.height}); + {required this.ins, required this.confirmations, this.time}); final BtcTransaction originalTransaction; final List ins; final int? time; final int confirmations; - final int height; } class ElectrumTransactionInfo extends TransactionInfo { @@ -25,6 +24,8 @@ class ElectrumTransactionInfo extends TransactionInfo { required int height, required int amount, int? fee, + List? inputAddresses, + List? outputAddresses, required TransactionDirection direction, required bool isPending, required DateTime date, @@ -32,6 +33,8 @@ class ElectrumTransactionInfo extends TransactionInfo { this.id = id; this.height = height; this.amount = amount; + this.inputAddresses = inputAddresses; + this.outputAddresses = outputAddresses; this.fee = fee; this.direction = direction; this.date = date; @@ -100,6 +103,8 @@ class ElectrumTransactionInfo extends TransactionInfo { var amount = 0; var inputAmount = 0; var totalOutAmount = 0; + List inputAddresses = []; + List outputAddresses = []; for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { final input = bundle.originalTransaction.inputs[i]; @@ -108,6 +113,7 @@ class ElectrumTransactionInfo extends TransactionInfo { inputAmount += outTransaction.amount.toInt(); if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) { direction = TransactionDirection.outgoing; + inputAddresses.add(addressFromOutputScript(outTransaction.scriptPubKey, network)); } } @@ -115,6 +121,7 @@ class ElectrumTransactionInfo extends TransactionInfo { for (final out in bundle.originalTransaction.outputs) { totalOutAmount += out.amount.toInt(); final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network)); + outputAddresses.add(addressFromOutputScript(out.scriptPubKey, network)); if (addressExists) { receivedAmounts.add(out.amount.toInt()); @@ -137,6 +144,8 @@ class ElectrumTransactionInfo extends TransactionInfo { id: bundle.originalTransaction.txId(), height: height, isPending: bundle.confirmations == 0, + inputAddresses: inputAddresses, + outputAddresses: outputAddresses, fee: fee, direction: direction, amount: amount, @@ -187,6 +196,8 @@ class ElectrumTransactionInfo extends TransactionInfo { direction: parseTransactionDirectionFromInt(data['direction'] as int), date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), isPending: data['isPending'] as bool, + inputAddresses: data['inputAddresses'] as List, + outputAddresses: data['outputAddresses'] as List, confirmations: data['confirmations'] as int); } @@ -218,6 +229,8 @@ class ElectrumTransactionInfo extends TransactionInfo { direction: direction, date: date, isPending: isPending, + inputAddresses: inputAddresses, + outputAddresses: outputAddresses, confirmations: info.confirmations); } @@ -231,6 +244,8 @@ class ElectrumTransactionInfo extends TransactionInfo { m['isPending'] = isPending; m['confirmations'] = confirmations; m['fee'] = fee; + m['inputAddresses'] = inputAddresses; + m['outputAddresses'] = outputAddresses; return m; } } diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 86fbd6dbe..5bed6a449 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -7,11 +7,11 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base; import 'package:collection/collection.dart'; +import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_wallet_keys.dart'; import 'package:cw_bitcoin/electrum.dart'; @@ -19,6 +19,7 @@ import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/exceptions.dart'; import 'package:cw_bitcoin/litecoin_network.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; @@ -188,26 +189,25 @@ abstract class ElectrumWalletBase } } - Future estimateTxFeeAndInputsToUse( - int credentialsAmount, - bool sendAll, - List outputAddresses, - List outputs, - int? feeRate, - BitcoinTransactionPriority? priority, - {int? inputsCount}) async { + int get _dustAmount => 546; + + bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; + + Future estimateSendAllTx( + List outputs, + int feeRate, { + String? memo, + int credentialsAmount = 0, + }) async { final utxos = []; List privateKeys = []; - - var leftAmount = credentialsAmount; - var allInputsAmount = 0; + int allInputsAmount = 0; for (int i = 0; i < unspentCoins.length; i++) { final utx = unspentCoins[i]; if (utx.isSending) { allInputsAmount += utx.value; - leftAmount = leftAmount - utx.value; final address = addressTypeFromStr(utx.address, network); final privkey = generateECPrivate( @@ -225,15 +225,12 @@ abstract class ElectrumWalletBase vout: utx.vout, scriptType: _getScriptType(address), ), - ownerDetails: - UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address), + ownerDetails: UtxoAddressDetails( + publicKey: privkey.getPublic().toHex(), + address: address, + ), ), ); - - bool amountIsAcquired = !sendAll && leftAmount <= 0; - if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { - break; - } } } @@ -241,120 +238,314 @@ abstract class ElectrumWalletBase throw BitcoinTransactionNoInputsException(); } - var changeValue = allInputsAmount - credentialsAmount; - - if (!sendAll) { - if (changeValue > 0) { - final changeAddress = await walletAddresses.getChangeAddress(); - final address = addressTypeFromStr(changeAddress, network); - outputAddresses.add(address); - outputs.add(BitcoinOutput(address: address, value: BigInt.from(changeValue))); - } + int estimatedSize; + if (network is BitcoinCashNetwork) { + estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( + utxos: utxos, + outputs: outputs, + network: network as BitcoinCashNetwork, + memo: memo, + ); + } else { + estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxos, + outputs: outputs, + network: network, + memo: memo, + ); } - final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( - utxos: utxos, outputs: outputs, network: network); - - int fee = feeRate != null - ? feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize) - : feeAmountForPriority(priority!, 0, 0, size: estimatedSize); + int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize); if (fee == 0) { - throw BitcoinTransactionWrongBalanceException(currency); + throw BitcoinTransactionNoFeeException(); } - var amount = credentialsAmount; + // Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change + int amount = allInputsAmount - fee; - final lastOutput = outputs.last; - if (!sendAll) { - if (changeValue > fee) { - // Here, lastOutput is change, deduct the fee from it - outputs[outputs.length - 1] = - BitcoinOutput(address: lastOutput.address, value: lastOutput.value - BigInt.from(fee)); + // Attempting to send less than the dust limit + if (_isBelowDust(amount)) { + throw BitcoinTransactionNoDustException(); + } + + if (credentialsAmount > 0) { + final amountLeftForFee = amount - credentialsAmount; + if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) { + amount -= amountLeftForFee; + fee += amountLeftForFee; } + } + + outputs[outputs.length - 1] = + BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount)); + + return EstimatedTxResult( + utxos: utxos, + privateKeys: privateKeys, + fee: fee, + amount: amount, + isSendAll: true, + hasChange: false, + memo: memo, + ); + } + + Future estimateTxForAmount( + int credentialsAmount, + List outputs, + int feeRate, { + int? inputsCount, + String? memo, + }) async { + final utxos = []; + List privateKeys = []; + int allInputsAmount = 0; + + int leftAmount = credentialsAmount; + final sendingCoins = unspentCoins.where((utx) => utx.isSending).toList(); + + for (int i = 0; i < sendingCoins.length; i++) { + final utx = sendingCoins[i]; + + allInputsAmount += utx.value; + leftAmount = leftAmount - utx.value; + + final address = addressTypeFromStr(utx.address, network); + final privkey = generateECPrivate( + hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: utx.bitcoinAddressRecord.index, + network: network); + + privateKeys.add(privkey); + + utxos.add( + UtxoWithAddress( + utxo: BitcoinUtxo( + txHash: utx.hash, + value: BigInt.from(utx.value), + vout: utx.vout, + scriptType: _getScriptType(address), + ), + ownerDetails: UtxoAddressDetails( + publicKey: privkey.getPublic().toHex(), + address: address, + ), + ), + ); + + bool amountIsAcquired = leftAmount <= 0; + if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) { + break; + } + } + + if (utxos.isEmpty) { + throw BitcoinTransactionNoInputsException(); + } + + final spendingAllCoins = sendingCoins.length == utxos.length; + + // How much is being spent - how much is being sent + int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount; + + if (amountLeftForChangeAndFee <= 0) { + throw BitcoinTransactionWrongBalanceException(); + } + + final changeAddress = await walletAddresses.getChangeAddress(); + final address = addressTypeFromStr(changeAddress, network); + outputs.add(BitcoinOutput( + address: address, + value: BigInt.from(amountLeftForChangeAndFee), + )); + + int estimatedSize; + if (network is BitcoinCashNetwork) { + estimatedSize = ForkedTransactionBuilder.estimateTransactionSize( + utxos: utxos, + outputs: outputs, + network: network as BitcoinCashNetwork, + memo: memo, + ); } else { - // Here, if sendAll, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount for change - amount = allInputsAmount - fee; + estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxos, + outputs: outputs, + network: network, + memo: memo, + ); + } + + int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize); + + if (fee == 0) { + throw BitcoinTransactionNoFeeException(); + } + + int amount = credentialsAmount; + final lastOutput = outputs.last; + final amountLeftForChange = amountLeftForChangeAndFee - fee; + + if (!_isBelowDust(amountLeftForChange)) { + // Here, lastOutput already is change, return the amount left without the fee to the user's address. outputs[outputs.length - 1] = - BitcoinOutput(address: lastOutput.address, value: BigInt.from(amount)); + BitcoinOutput(address: lastOutput.address, value: BigInt.from(amountLeftForChange)); + } else { + // If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change + outputs.removeLast(); + + // Still has inputs to spend before failing + if (!spendingAllCoins) { + return estimateTxForAmount( + credentialsAmount, + outputs, + feeRate, + inputsCount: utxos.length + 1, + memo: memo, + ); + } + + final estimatedSendAll = await estimateSendAllTx( + outputs, + feeRate, + memo: memo, + ); + + if (estimatedSendAll.amount == credentialsAmount) { + return estimatedSendAll; + } + + // Estimate to user how much is needed to send to cover the fee + final maxAmountWithReturningChange = allInputsAmount - _dustAmount - fee - 1; + throw BitcoinTransactionNoDustOnChangeException( + bitcoinAmountToString(amount: maxAmountWithReturningChange), + bitcoinAmountToString(amount: estimatedSendAll.amount), + ); + } + + // Attempting to send less than the dust limit + if (_isBelowDust(amount)) { + throw BitcoinTransactionNoDustException(); } final totalAmount = amount + fee; if (totalAmount > balance[currency]!.confirmed) { - throw BitcoinTransactionWrongBalanceException(currency); + throw BitcoinTransactionWrongBalanceException(); } if (totalAmount > allInputsAmount) { - if (unspentCoins.where((utx) => utx.isSending).length == utxos.length) { - throw BitcoinTransactionWrongBalanceException(currency); + if (spendingAllCoins) { + throw BitcoinTransactionWrongBalanceException(); } else { - if (changeValue > fee) { - outputAddresses.removeLast(); + if (amountLeftForChangeAndFee > fee) { outputs.removeLast(); } - return estimateTxFeeAndInputsToUse( - credentialsAmount, sendAll, outputAddresses, outputs, feeRate, priority, - inputsCount: utxos.length + 1); + return estimateTxForAmount( + credentialsAmount, + outputs, + feeRate, + inputsCount: utxos.length + 1, + memo: memo, + ); } } - return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount); + return EstimatedTxResult( + utxos: utxos, + privateKeys: privateKeys, + fee: fee, + amount: amount, + hasChange: true, + isSendAll: false, + memo: memo, + ); } @override Future createTransaction(Object credentials) async { try { final outputs = []; - final outputAddresses = []; final transactionCredentials = credentials as BitcoinTransactionCredentials; final hasMultiDestination = transactionCredentials.outputs.length > 1; final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll; + final memo = transactionCredentials.outputs.first.memo; - var credentialsAmount = 0; + int credentialsAmount = 0; for (final out in transactionCredentials.outputs) { - final outputAddress = out.isParsedAddress ? out.extractedAddress! : out.address; - final address = addressTypeFromStr(outputAddress, network); + final outputAmount = out.formattedCryptoAmount!; - outputAddresses.add(address); + if (!sendAll && _isBelowDust(outputAmount)) { + throw BitcoinTransactionNoDustException(); + } if (hasMultiDestination) { - if (out.sendAll || out.formattedCryptoAmount! <= 0) { - throw BitcoinTransactionWrongBalanceException(currency); + if (out.sendAll) { + throw BitcoinTransactionWrongBalanceException(); } + } - final outputAmount = out.formattedCryptoAmount!; - credentialsAmount += outputAmount; + credentialsAmount += outputAmount; - outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount))); + final address = + addressTypeFromStr(out.isParsedAddress ? out.extractedAddress! : out.address, network); + + if (sendAll) { + // The value will be changed after estimating the Tx size and deducting the fee from the total to be sent + outputs.add(BitcoinOutput(address: address, value: BigInt.from(0))); } else { - if (!sendAll) { - final outputAmount = out.formattedCryptoAmount!; - credentialsAmount += outputAmount; - outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount))); - } else { - // The value will be changed after estimating the Tx size and deducting the fee from the total - outputs.add(BitcoinOutput(address: address, value: BigInt.from(0))); - } + outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount))); } } - final estimatedTx = await estimateTxFeeAndInputsToUse( - credentialsAmount, - sendAll, - outputAddresses, - outputs, - transactionCredentials.feeRate, - transactionCredentials.priority, - ); + final feeRateInt = transactionCredentials.feeRate != null + ? transactionCredentials.feeRate! + : feeRate(transactionCredentials.priority!); - final txb = BitcoinTransactionBuilder( + EstimatedTxResult estimatedTx; + if (sendAll) { + estimatedTx = await estimateSendAllTx( + outputs, + feeRateInt, + memo: memo, + credentialsAmount: credentialsAmount, + ); + } else { + estimatedTx = await estimateTxForAmount( + credentialsAmount, + outputs, + feeRateInt, + memo: memo, + ); + } + + BasedBitcoinTransacationBuilder txb; + if (network is BitcoinCashNetwork) { + txb = ForkedTransactionBuilder( utxos: estimatedTx.utxos, outputs: outputs, fee: BigInt.from(estimatedTx.fee), - network: network); + network: network, + memo: estimatedTx.memo, + outputOrdering: BitcoinOrdering.none, + enableRBF: true, + ); + } else { + txb = BitcoinTransactionBuilder( + utxos: estimatedTx.utxos, + outputs: outputs, + fee: BigInt.from(estimatedTx.fee), + network: network, + memo: estimatedTx.memo, + outputOrdering: BitcoinOrdering.none, + enableRBF: true, + ); + } + + bool hasTaprootInputs = false; final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { final key = estimatedTx.privateKeys @@ -365,18 +556,25 @@ abstract class ElectrumWalletBase } if (utxo.utxo.isP2tr()) { + hasTaprootInputs = true; return key.signTapRoot(txDigest, sighash: sighash); } else { return key.signInput(txDigest, sigHash: sighash); } }); - return PendingBitcoinTransaction(transaction, type, - electrumClient: electrumClient, - amount: estimatedTx.amount, - fee: estimatedTx.fee, - network: network) - ..addListener((transaction) async { + return PendingBitcoinTransaction( + transaction, + type, + electrumClient: electrumClient, + amount: estimatedTx.amount, + fee: estimatedTx.fee, + feeRate: feeRateInt.toString(), + network: network, + hasChange: estimatedTx.hasChange, + isSendAll: estimatedTx.isSendAll, + hasTaprootInputs: hasTaprootInputs, + )..addListener((transaction) async { transactionHistory.addOne(transaction); await updateBalance(); }); @@ -408,7 +606,7 @@ abstract class ElectrumWalletBase } } - int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount, + int feeAmountForPriority(TransactionPriority priority, int inputsCount, int outputsCount, {int? size}) => feeRate(priority) * (size ?? estimatedTransactionSize(inputsCount, outputsCount)); @@ -597,8 +795,180 @@ abstract class ElectrumWalletBase } } - Future getTransactionExpanded( - {required String hash, required int height}) async { + Future canReplaceByFee(String hash) async { + final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); + final confirmations = verboseTransaction['confirmations'] as int? ?? 0; + final transactionHex = verboseTransaction['hex'] as String?; + + if (confirmations > 0) return false; + + if (transactionHex == null) { + return false; + } + + final original = bitcoin.Transaction.fromHex(transactionHex); + + return original.ins + .any((element) => element.sequence != null && element.sequence! < 4294967293); + } + + Future isChangeSufficientForFee(String txId, int newFee) async { + final bundle = await getTransactionExpanded(hash: txId); + final outputs = bundle.originalTransaction.outputs; + + final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); + + // look for a change address in the outputs + final changeOutput = outputs.firstWhereOrNull((output) => changeAddresses.any( + (element) => element.address == addressFromOutputScript(output.scriptPubKey, network))); + + var allInputsAmount = 0; + + for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) { + final input = bundle.originalTransaction.inputs[i]; + final inputTransaction = bundle.ins[i]; + final vout = input.txIndex; + final outTransaction = inputTransaction.outputs[vout]; + allInputsAmount += outTransaction.amount.toInt(); + } + + int totalOutAmount = bundle.originalTransaction.outputs + .fold(0, (previousValue, element) => previousValue + element.amount.toInt()); + + var currentFee = allInputsAmount - totalOutAmount; + + int remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee; + + return changeOutput != null && changeOutput.amount.toInt() - remainingFee >= 0; + } + + Future replaceByFee(String hash, int newFee) async { + try { + final bundle = await getTransactionExpanded(hash: hash); + + final utxos = []; + List privateKeys = []; + + var allInputsAmount = 0; + + // Add inputs + for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { + final input = bundle.originalTransaction.inputs[i]; + final inputTransaction = bundle.ins[i]; + final vout = input.txIndex; + final outTransaction = inputTransaction.outputs[vout]; + final address = addressFromOutputScript(outTransaction.scriptPubKey, network); + allInputsAmount += outTransaction.amount.toInt(); + + final addressRecord = + walletAddresses.allAddresses.firstWhere((element) => element.address == address); + + final btcAddress = addressTypeFromStr(addressRecord.address, network); + final privkey = generateECPrivate( + hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: addressRecord.index, + network: network); + + privateKeys.add(privkey); + + utxos.add( + UtxoWithAddress( + utxo: BitcoinUtxo( + txHash: input.txId, + value: outTransaction.amount, + vout: vout, + scriptType: _getScriptType(btcAddress), + ), + ownerDetails: + UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: btcAddress), + ), + ); + } + + int totalOutAmount = bundle.originalTransaction.outputs + .fold(0, (previousValue, element) => previousValue + element.amount.toInt()); + + var currentFee = allInputsAmount - totalOutAmount; + int remainingFee = newFee - currentFee; + + final outputs = []; + + // Add outputs and deduct the fees from it + for (int i = bundle.originalTransaction.outputs.length - 1; i >= 0; i--) { + final out = bundle.originalTransaction.outputs[i]; + final address = addressFromOutputScript(out.scriptPubKey, network); + final btcAddress = addressTypeFromStr(address, network); + + int newAmount; + if (out.amount.toInt() >= remainingFee) { + newAmount = out.amount.toInt() - remainingFee; + remainingFee = 0; + + // if new amount of output is less than dust amount, then don't add this output as well + if (newAmount <= _dustAmount) { + continue; + } + } else { + remainingFee -= out.amount.toInt(); + continue; + } + + outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(newAmount))); + } + + final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden); + + // look for a change address in the outputs + final changeOutput = outputs.firstWhereOrNull((output) => + changeAddresses.any((element) => element.address == output.address.toAddress(network))); + + // deduct the change amount from the output amount + if (changeOutput != null) { + totalOutAmount -= changeOutput.value.toInt(); + } + + final txb = BitcoinTransactionBuilder( + utxos: utxos, + outputs: outputs, + fee: BigInt.from(newFee), + network: network, + enableRBF: true, + ); + + final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { + final key = + privateKeys.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey); + + if (key == null) { + throw Exception("Cannot find private key"); + } + + if (utxo.utxo.isP2tr()) { + return key.signTapRoot(txDigest, sighash: sighash); + } else { + return key.signInput(txDigest, sigHash: sighash); + } + }); + + return PendingBitcoinTransaction( + transaction, + type, + electrumClient: electrumClient, + amount: totalOutAmount, + fee: newFee, + network: network, + hasChange: changeOutput != null, + feeRate: newFee.toString(), + )..addListener((transaction) async { + transactionHistory.addOne(transaction); + await updateBalance(); + }); + } catch (e) { + throw e; + } + } + + Future getTransactionExpanded({required String hash}) async { String transactionHex; int? time; int confirmations = 0; @@ -629,8 +999,12 @@ abstract class ElectrumWalletBase ins.add(tx); } - return ElectrumTransactionBundle(original, - ins: ins, time: time, confirmations: confirmations, height: height); + return ElectrumTransactionBundle( + original, + ins: ins, + time: time, + confirmations: confirmations, + ); } Future fetchTransactionInfo( @@ -640,7 +1014,7 @@ abstract class ElectrumWalletBase bool? retryOnFailure}) async { try { return ElectrumTransactionInfo.fromElectrumBundle( - await getTransactionExpanded(hash: hash, height: height), walletInfo.type, network, + await getTransactionExpanded(hash: hash), walletInfo.type, network, addresses: myAddresses, height: height); } catch (e) { if (e is FormatException && retryOnFailure == true) { @@ -888,16 +1262,35 @@ class EstimateTxParams { } class EstimatedTxResult { - EstimatedTxResult( - {required this.utxos, required this.privateKeys, required this.fee, required this.amount}); + EstimatedTxResult({ + required this.utxos, + required this.privateKeys, + required this.fee, + required this.amount, + required this.hasChange, + required this.isSendAll, + this.memo, + }); final List utxos; final List privateKeys; final int fee; final int amount; + final bool hasChange; + final bool isSendAll; + final String? memo; } BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) { + if (network is BitcoinCashNetwork) { + if (!address.startsWith("bitcoincash:") && + (address.startsWith("q") || address.startsWith("p"))) { + address = "bitcoincash:$address"; + } + + return BitcoinCashAddress(address).baseAddress; + } + if (P2pkhAddress.regex.hasMatch(address)) { return P2pkhAddress.fromAddress(address: address, network: network); } else if (P2shAddress.regex.hasMatch(address)) { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 69d0a6385..ac2397561 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -77,7 +77,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { String get address { String receiveAddress; - final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch); + final typeMatchingReceiveAddresses = + receiveAddresses.where(_isAddressPageTypeMatch).where((addr) => !addr.isUsed); if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || typeMatchingReceiveAddresses.isEmpty) { @@ -220,8 +221,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { Future updateAddressesInBox() async { try { addressesMap.clear(); + addressesMap[address] = ''; + + allAddressesMap.clear(); _addresses.forEach((addressRecord) { - addressesMap[addressRecord.address] = addressRecord.name; + allAddressesMap[addressRecord.address] = addressRecord.name; }); await saveAddressesInBox(); } catch (e) { diff --git a/cw_bitcoin/lib/exceptions.dart b/cw_bitcoin/lib/exceptions.dart new file mode 100644 index 000000000..4b03eb922 --- /dev/null +++ b/cw_bitcoin/lib/exceptions.dart @@ -0,0 +1,27 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/exceptions.dart'; + +class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException { + BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc); +} + +class BitcoinTransactionNoInputsException extends TransactionNoInputsException {} + +class BitcoinTransactionNoFeeException extends TransactionNoFeeException {} + +class BitcoinTransactionNoDustException extends TransactionNoDustException {} + +class BitcoinTransactionNoDustOnChangeException extends TransactionNoDustOnChangeException { + BitcoinTransactionNoDustOnChangeException(super.max, super.min); +} + +class BitcoinTransactionCommitFailed extends TransactionCommitFailed {} + +class BitcoinTransactionCommitFailedDustChange extends TransactionCommitFailedDustChange {} + +class BitcoinTransactionCommitFailedDustOutput extends TransactionCommitFailedDustOutput {} + +class BitcoinTransactionCommitFailedDustOutputSendAll + extends TransactionCommitFailedDustOutputSendAll {} + +class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {} diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index fa413febd..529ac61da 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,4 +1,4 @@ -import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart'; +import 'package:cw_bitcoin/exceptions.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_bitcoin/electrum.dart'; @@ -8,16 +8,29 @@ import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_type.dart'; class PendingBitcoinTransaction with PendingTransaction { - PendingBitcoinTransaction(this._tx, this.type, - {required this.electrumClient, required this.amount, required this.fee, this.network}) - : _listeners = []; + PendingBitcoinTransaction( + this._tx, + this.type, { + required this.electrumClient, + required this.amount, + required this.fee, + required this.feeRate, + this.network, + required this.hasChange, + this.isSendAll = false, + this.hasTaprootInputs = false, + }) : _listeners = []; final WalletType type; final BtcTransaction _tx; final ElectrumClient electrumClient; final int amount; final int fee; + final String feeRate; final BasedUtxoNetwork? network; + final bool hasChange; + final bool isSendAll; + final bool hasTaprootInputs; @override String get id => _tx.txId(); @@ -31,14 +44,37 @@ class PendingBitcoinTransaction with PendingTransaction { @override String get feeFormatted => bitcoinAmountToString(amount: fee); + @override + int? get outputCount => _tx.outputs.length; + final List _listeners; @override Future commit() async { - final result = await electrumClient.broadcastTransaction(transactionRaw: hex, network: network); + int? callId; + + final result = await electrumClient.broadcastTransaction( + transactionRaw: hex, network: network, idCallback: (id) => callId = id); if (result.isEmpty) { - throw BitcoinCommitTransactionException(); + if (callId != null) { + final error = electrumClient.getErrorMessage(callId!); + + if (error.contains("dust")) { + if (hasChange) { + throw BitcoinTransactionCommitFailedDustChange(); + } else if (!isSendAll) { + throw BitcoinTransactionCommitFailedDustOutput(); + } else { + throw BitcoinTransactionCommitFailedDustOutputSendAll(); + } + } + + if (error.contains("bad-txns-vout-negative")) { + throw BitcoinTransactionCommitFailedVoutNegative(); + } + } + throw BitcoinTransactionCommitFailed(); } _listeners.forEach((listener) => listener(transactionInfo())); diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index b39dcae07..3d828243c 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -70,8 +70,8 @@ packages: dependency: "direct main" description: path: "." - ref: master - resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 + ref: Add-Support-For-OP-Return-data + resolved-ref: "57b78afb85bd2c30d3cdb9f7884f3878a62be442" url: "https://github.com/cake-tech/bitbox-flutter.git" source: git version: "1.0.1" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index bcbb55e11..632a3140a 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git - ref: master + ref: Add-Support-For-OP-Return-data rxdart: ^0.27.5 unorm_dart: ^0.2.0 cryptography: ^2.0.5 diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index f5835e728..1f04e5624 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -4,15 +4,10 @@ import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart'; -import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; -import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -130,184 +125,9 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { ); } - @override - Future createTransaction(Object credentials) async { - const minAmount = 546; - final transactionCredentials = credentials as BitcoinTransactionCredentials; - final inputs = []; - final outputs = transactionCredentials.outputs; - final hasMultiDestination = outputs.length > 1; - - var allInputsAmount = 0; - - if (unspentCoins.isEmpty) await updateUnspent(); - - for (final utx in unspentCoins) { - if (utx.isSending) { - allInputsAmount += utx.value; - inputs.add(utx); - } - } - - if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); - - final allAmountFee = transactionCredentials.feeRate != null - ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length) - : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length); - - final allAmount = allInputsAmount - allAmountFee; - - var credentialsAmount = 0; - var amount = 0; - var fee = 0; - - if (hasMultiDestination) { - if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); - - if (allAmount - credentialsAmount < minAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - amount = credentialsAmount; - - if (transactionCredentials.feeRate != null) { - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, - outputsCount: outputs.length + 1); - } else { - fee = calculateEstimatedFee(transactionCredentials.priority, amount, - outputsCount: outputs.length + 1); - } - } else { - final output = outputs.first; - credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; - - if (credentialsAmount > allAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - amount = output.sendAll || allAmount - credentialsAmount < minAmount - ? allAmount - : credentialsAmount; - - if (output.sendAll || amount == allAmount) { - fee = allAmountFee; - } else if (transactionCredentials.feeRate != null) { - fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); - } else { - fee = calculateEstimatedFee(transactionCredentials.priority, amount); - } - } - - if (fee == 0) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - final totalAmount = amount + fee; - - if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - final txb = bitbox.Bitbox.transactionBuilder(testnet: false); - - final changeAddress = await walletAddresses.getChangeAddress(); - var leftAmount = totalAmount; - var totalInputAmount = 0; - - inputs.clear(); - - for (final utx in unspentCoins) { - if (utx.isSending) { - leftAmount = leftAmount - utx.value; - totalInputAmount += utx.value; - inputs.add(utx); - - if (leftAmount <= 0) { - break; - } - } - } - - if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); - - if (amount <= 0 || totalInputAmount < totalAmount) { - throw BitcoinTransactionWrongBalanceException(currency); - } - - inputs.forEach((input) { - txb.addInput(input.hash, input.vout); - }); - - final String bchPrefix = "bitcoincash:"; - - outputs.forEach((item) { - final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; - String outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; - - if (!outputAddress.startsWith(bchPrefix)) { - outputAddress = "$bchPrefix$outputAddress"; - } - - bool isP2sh = outputAddress.startsWith("p", bchPrefix.length); - - if (isP2sh) { - final p2sh = P2shAddress.fromAddress( - address: outputAddress, - network: BitcoinCashNetwork.mainnet, - ); - - txb.addOutput(Uint8List.fromList(p2sh.toScriptPubKey().toBytes()), outputAmount!); - return; - } - - txb.addOutput(outputAddress, outputAmount!); - }); - - final estimatedSize = bitbox.BitcoinCash.getByteCount(inputs.length, outputs.length + 1); - - var feeAmount = 0; - - if (transactionCredentials.feeRate != null) { - feeAmount = transactionCredentials.feeRate! * estimatedSize; - } else { - feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize; - } - - final changeValue = totalInputAmount - amount - feeAmount; - - if (changeValue > minAmount) { - txb.addOutput(changeAddress, changeValue); - } - - for (var i = 0; i < inputs.length; i++) { - final input = inputs[i]; - final keyPair = generateKeyPair( - hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, - index: input.bitcoinAddressRecord.index); - txb.sign(i, keyPair, input.value); - } - - // Build the transaction - final tx = txb.build(); - - return PendingBitcoinCashTransaction(tx, type, - electrumClient: electrumClient, amount: amount, fee: fee); - } - bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) => bitbox.ECPair.fromWIF(hd.derive(index).wif!); - @override - int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount, - {int? size}) => - feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); - - int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) => - feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) { int inputsCount = 0; int totalValue = 0; diff --git a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart index d5ac36ce2..da4710a8b 100644 --- a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart +++ b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart @@ -1,4 +1,4 @@ -import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart'; +import 'package:cw_bitcoin/exceptions.dart'; import 'package:bitbox/bitbox.dart' as bitbox; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_bitcoin/electrum.dart'; @@ -11,7 +11,9 @@ class PendingBitcoinCashTransaction with PendingTransaction { PendingBitcoinCashTransaction(this._tx, this.type, {required this.electrumClient, required this.amount, - required this.fee}) + required this.fee, + required this.hasChange, + required this.isSendAll}) : _listeners = []; final WalletType type; @@ -19,6 +21,8 @@ class PendingBitcoinCashTransaction with PendingTransaction { final ElectrumClient electrumClient; final int amount; final int fee; + final bool hasChange; + final bool isSendAll; @override String get id => _tx.getId(); @@ -36,18 +40,36 @@ class PendingBitcoinCashTransaction with PendingTransaction { @override Future commit() async { - final result = - await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex()); + int? callId; + + final result = await electrumClient.broadcastTransaction( + transactionRaw: hex, idCallback: (id) => callId = id); if (result.isEmpty) { - throw BitcoinCommitTransactionException(); + if (callId != null) { + final error = electrumClient.getErrorMessage(callId!); + + if (error.contains("dust")) { + if (hasChange) { + throw BitcoinTransactionCommitFailedDustChange(); + } else if (!isSendAll) { + throw BitcoinTransactionCommitFailedDustOutput(); + } else { + throw BitcoinTransactionCommitFailedDustOutputSendAll(); + } + } + + if (error.contains("bad-txns-vout-negative")) { + throw BitcoinTransactionCommitFailedVoutNegative(); + } + } + throw BitcoinTransactionCommitFailed(); } - _listeners?.forEach((listener) => listener(transactionInfo())); + _listeners.forEach((listener) => listener(transactionInfo())); } - void addListener( - void Function(ElectrumTransactionInfo transaction) listener) => + void addListener(void Function(ElectrumTransactionInfo transaction) listener) => _listeners.add(listener); ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 7130b3c58..37827f1ba 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git - ref: master + ref: Add-Support-For-OP-Return-data bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base.git diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 9cebce10a..f1c1cd8ae 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -38,6 +38,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.trx, CryptoCurrency.usdt, CryptoCurrency.usdterc20, + CryptoCurrency.sol, + CryptoCurrency.maticpoly, CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.xhv, @@ -50,7 +52,6 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.usdttrc20, CryptoCurrency.hbar, CryptoCurrency.sc, - CryptoCurrency.sol, CryptoCurrency.usdc, CryptoCurrency.usdcsol, CryptoCurrency.zaddr, @@ -61,7 +62,6 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.dcr, CryptoCurrency.kmd, CryptoCurrency.mana, - CryptoCurrency.maticpoly, CryptoCurrency.matic, CryptoCurrency.mkr, CryptoCurrency.near, diff --git a/cw_core/lib/exceptions.dart b/cw_core/lib/exceptions.dart new file mode 100644 index 000000000..848ac40e6 --- /dev/null +++ b/cw_core/lib/exceptions.dart @@ -0,0 +1,30 @@ +import 'package:cw_core/crypto_currency.dart'; + +class TransactionWrongBalanceException implements Exception { + TransactionWrongBalanceException(this.currency); + + final CryptoCurrency currency; +} + +class TransactionNoInputsException implements Exception {} + +class TransactionNoFeeException implements Exception {} + +class TransactionNoDustException implements Exception {} + +class TransactionNoDustOnChangeException implements Exception { + TransactionNoDustOnChangeException(this.max, this.min); + + final String max; + final String min; +} + +class TransactionCommitFailed implements Exception {} + +class TransactionCommitFailedDustChange implements Exception {} + +class TransactionCommitFailedDustOutput implements Exception {} + +class TransactionCommitFailedDustOutputSendAll implements Exception {} + +class TransactionCommitFailedVoutNegative implements Exception {} diff --git a/cw_core/lib/output_info.dart b/cw_core/lib/output_info.dart index e2b1201a8..9e3ac4ffc 100644 --- a/cw_core/lib/output_info.dart +++ b/cw_core/lib/output_info.dart @@ -7,7 +7,8 @@ class OutputInfo { this.formattedCryptoAmount, this.fiatAmount, this.note, - this.extractedAddress,}); + this.extractedAddress, + this.memo}); final String? fiatAmount; final String? cryptoAmount; @@ -17,4 +18,5 @@ class OutputInfo { final bool sendAll; final bool isParsedAddress; final int? formattedCryptoAmount; + final String? memo; } \ No newline at end of file diff --git a/cw_core/lib/pending_transaction.dart b/cw_core/lib/pending_transaction.dart index cc5686fc9..642db9c2c 100644 --- a/cw_core/lib/pending_transaction.dart +++ b/cw_core/lib/pending_transaction.dart @@ -2,7 +2,9 @@ mixin PendingTransaction { String get id; String get amountFormatted; String get feeFormatted; + String? feeRate; String get hex; + int? get outputCount => null; Future commit(); -} \ No newline at end of file +} diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart index 7624b147f..992582ff8 100644 --- a/cw_core/lib/transaction_info.dart +++ b/cw_core/lib/transaction_info.dart @@ -16,6 +16,8 @@ abstract class TransactionInfo extends Object with Keyable { void changeFiatAmount(String amount); String? to; String? from; + List? inputAddresses; + List? outputAddresses; @override dynamic get keyIndex => id; diff --git a/cw_core/lib/wallet_addresses.dart b/cw_core/lib/wallet_addresses.dart index d8c84c80c..a2a2a50a3 100644 --- a/cw_core/lib/wallet_addresses.dart +++ b/cw_core/lib/wallet_addresses.dart @@ -3,8 +3,9 @@ import 'package:cw_core/wallet_info.dart'; abstract class WalletAddresses { WalletAddresses(this.walletInfo) - : addressesMap = {}, - addressInfos = {}; + : addressesMap = {}, + allAddressesMap = {}, + addressInfos = {}; final WalletInfo walletInfo; @@ -15,6 +16,7 @@ abstract class WalletAddresses { set address(String address); Map addressesMap; + Map allAddressesMap; Map> addressInfos; @@ -39,5 +41,5 @@ abstract class WalletAddresses { } } - bool containsAddress(String address) => addressesMap.containsKey(address); + bool containsAddress(String address) => allAddressesMap.containsKey(address); } diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 49f1bdc94..037a26d38 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -67,6 +67,7 @@ abstract class WalletBase> fetchInternalTransactions(String address) async { + try { + final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", { + "module": "account", + "action": "txlistinternal", + "address": address, + "apikey": secrets.etherScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map((e) => EVMChainTransactionModel.fromJson(e as Map, 'ETH')) + .toList(); + } + + return []; + } catch (e) { + log(e.toString()); + return []; + } + } } diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index de5b3874a..eebbe4f4f 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:developer'; import 'package:cw_core/node.dart'; @@ -9,11 +10,13 @@ import 'package:cw_evm/evm_erc20_balance.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:cw_evm/pending_evm_chain_transaction.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:cw_evm/.secrets.g.dart' as secrets; import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:erc20/erc20.dart'; import 'package:web3dart/web3dart.dart'; +import 'package:hex/hex.dart' as hex; abstract class EVMChainClient { final httpClient = Client(); @@ -26,6 +29,8 @@ abstract class EVMChainClient { Future> fetchTransactions(String address, {String? contractAddress}); + Future> fetchInternalTransactions(String address); + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction); //! Common methods across all child classes @@ -79,12 +84,13 @@ abstract class EVMChainClient { Future signTransaction({ required EthPrivateKey privateKey, required String toAddress, - required String amount, + required BigInt amount, required int gas, required EVMChainTransactionPriority priority, required CryptoCurrency currency, required int exponent, String? contractAddress, + String? data, }) async { assert(currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly || @@ -99,7 +105,8 @@ abstract class EVMChainClient { from: privateKey.address, to: EthereumAddress.fromHex(toAddress), maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), - amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), + amount: isEVMCompatibleChain ? EtherAmount.inWei(amount) : EtherAmount.zero(), + data: data != null ? hexToBytes(data) : null, ); final signedTransaction = @@ -119,7 +126,7 @@ abstract class EVMChainClient { _sendTransaction = () async { await erc20.transfer( EthereumAddress.fromHex(toAddress), - BigInt.parse(amount), + amount, credentials: privateKey, transaction: transaction, ); @@ -128,7 +135,7 @@ abstract class EVMChainClient { return PendingEVMChainTransaction( signedTransaction: signedTransaction, - amount: amount, + amount: amount.toString(), fee: BigInt.from(gas) * (await price).getInWei, sendTransaction: _sendTransaction, exponent: exponent, @@ -140,12 +147,14 @@ abstract class EVMChainClient { required EthereumAddress to, required EtherAmount amount, EtherAmount? maxPriorityFeePerGas, + Uint8List? data, }) { return Transaction( from: from, to: to, maxPriorityFeePerGas: maxPriorityFeePerGas, value: amount, + data: data, ); } @@ -204,24 +213,63 @@ abstract class EVMChainClient { return EVMChainERC20Balance(balance, exponent: exponent); } - Future getErc20Token(String contractAddress) async { + Future getErc20Token(String contractAddress, String chainName) async { try { - final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); - final name = await erc20.name(); - final symbol = await erc20.symbol(); - final decimal = await erc20.decimals(); + final uri = Uri.https( + 'deep-index.moralis.io', + '/api/v2.2/erc20/metadata', + { + "chain": chainName, + "addresses": contractAddress, + }, + ); + + final response = await httpClient.get( + uri, + headers: { + "Accept": "application/json", + "X-API-Key": secrets.moralisApiKey, + }, + ); + + final decodedResponse = jsonDecode(response.body)[0] as Map; + + final name = decodedResponse['name'] ?? ''; + final symbol = decodedResponse['symbol'] ?? ''; + final decimal = decodedResponse['decimals'] ?? '0'; + final iconPath = decodedResponse['logo'] ?? ''; return Erc20Token( name: name, symbol: symbol, contractAddress: contractAddress, - decimal: decimal.toInt(), + decimal: int.tryParse(decimal) ?? 0, + iconPath: iconPath, ); } catch (e) { + try { + final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); + final name = await erc20.name(); + final symbol = await erc20.symbol(); + final decimal = await erc20.decimals(); + + return Erc20Token( + name: name, + symbol: symbol, + contractAddress: contractAddress, + decimal: decimal.toInt(), + ); + } catch (_) {} + return null; } } + Uint8List hexToBytes(String hexString) { + return Uint8List.fromList( + hex.HEX.decode(hexString.startsWith('0x') ? hexString.substring(2) : hexString)); + } + void stop() { _client?.dispose(); } diff --git a/cw_evm/lib/evm_chain_exceptions.dart b/cw_evm/lib/evm_chain_exceptions.dart index 1c09ecf6d..8aa371b19 100644 --- a/cw_evm/lib/evm_chain_exceptions.dart +++ b/cw_evm/lib/evm_chain_exceptions.dart @@ -9,3 +9,14 @@ class EVMChainTransactionCreationException implements Exception { @override String toString() => exceptionMessage; } + + +class EVMChainTransactionFeesException implements Exception { + final String exceptionMessage; + + EVMChainTransactionFeesException() + : exceptionMessage = 'Current balance is less than the estimated fees for this transaction.'; + + @override + String toString() => exceptionMessage; +} diff --git a/cw_evm/lib/evm_chain_transaction_model.dart b/cw_evm/lib/evm_chain_transaction_model.dart index a328a2d6d..dfdeab8f5 100644 --- a/cw_evm/lib/evm_chain_transaction_model.dart +++ b/cw_evm/lib/evm_chain_transaction_model.dart @@ -32,15 +32,15 @@ class EVMChainTransactionModel { factory EVMChainTransactionModel.fromJson(Map json, String defaultSymbol) => EVMChainTransactionModel( date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000), - hash: json["hash"], - from: json["from"], - to: json["to"], - amount: BigInt.parse(json["value"]), - gasUsed: int.parse(json["gasUsed"]), - gasPrice: BigInt.parse(json["gasPrice"]), - contractAddress: json["contractAddress"], - confirmations: int.parse(json["confirmations"]), - blockNumber: int.parse(json["blockNumber"]), + hash: json["hash"] ?? "", + from: json["from"] ?? "", + to: json["to"] ?? "", + amount: BigInt.parse(json["value"] ?? "0"), + gasUsed: int.parse(json["gasUsed"] ?? "0"), + gasPrice: BigInt.parse(json["gasPrice"] ?? "0"), + contractAddress: json["contractAddress"] ?? "", + confirmations: int.parse(json["confirmations"] ?? "0"), + blockNumber: int.parse(json["blockNumber"] ?? "0"), tokenSymbol: json["tokenSymbol"] ?? defaultSymbol, tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""), isError: json["isError"] == "1", diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 0fb282960..4193e590a 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -224,10 +224,17 @@ abstract class EVMChainWalletBase final outputs = _credentials.outputs; final hasMultiDestination = outputs.length > 1; + final String? opReturnMemo = outputs.first.memo; + + String? hexOpReturnMemo; + if (opReturnMemo != null) { + hexOpReturnMemo = '0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}'; + } + final CryptoCurrency transactionCurrency = balance.keys.firstWhere((element) => element.title == _credentials.currency.title); - final _erc20Balance = balance[transactionCurrency]!; + final erc20Balance = balance[transactionCurrency]!; BigInt totalAmount = BigInt.zero; int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; num amountToEVMChainMultiplier = pow(10, exponent); @@ -242,7 +249,7 @@ abstract class EVMChainWalletBase outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); - if (_erc20Balance.balance < totalAmount) { + if (erc20Balance.balance < totalAmount) { throw EVMChainTransactionCreationException(transactionCurrency); } } else { @@ -251,18 +258,27 @@ abstract class EVMChainWalletBase // then no need to subtract the fees from the amount if send all final BigInt allAmount; if (transactionCurrency is Erc20Token) { - allAmount = _erc20Balance.balance; + allAmount = erc20Balance.balance; } else { - allAmount = _erc20Balance.balance - - BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); - } - final totalOriginalAmount = - EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0); - totalAmount = output.sendAll - ? allAmount - : BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); + final estimatedFee = BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); - if (_erc20Balance.balance < totalAmount) { + if (estimatedFee > erc20Balance.balance) { + throw EVMChainTransactionFeesException(); + } + + allAmount = erc20Balance.balance - estimatedFee; + } + + if (output.sendAll) { + totalAmount = allAmount; + } else { + final totalOriginalAmount = + EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0); + + totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); + } + + if (erc20Balance.balance < totalAmount) { throw EVMChainTransactionCreationException(transactionCurrency); } } @@ -272,13 +288,14 @@ abstract class EVMChainWalletBase toAddress: _credentials.outputs.first.isParsedAddress ? _credentials.outputs.first.extractedAddress! : _credentials.outputs.first.address, - amount: totalAmount.toString(), + amount: totalAmount, gas: _estimatedGas!, priority: _credentials.priority!, currency: transactionCurrency, exponent: exponent, contractAddress: transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, + data: hexOpReturnMemo, ); return pendingEVMChainTransaction; @@ -310,6 +327,7 @@ abstract class EVMChainWalletBase Future> fetchTransactions() async { final address = _evmChainPrivateKey.address.hex; final transactions = await _client.fetchTransactions(address); + final internalTransactions = await _client.fetchInternalTransactions(address); final List>> erc20TokensTransactions = []; @@ -324,6 +342,7 @@ abstract class EVMChainWalletBase final tokensTransaction = await Future.wait(erc20TokensTransactions); transactions.addAll(tokensTransaction.expand((element) => element)); + transactions.addAll(internalTransactions); final Map result = {}; @@ -420,11 +439,16 @@ abstract class EVMChainWalletBase Future addErc20Token(Erc20Token token) async { String? iconPath; - try { - iconPath = CryptoCurrency.all - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) - .iconPath; - } catch (_) {} + + if (token.iconPath == null || token.iconPath!.isEmpty) { + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + } else { + iconPath = token.iconPath; + } final newToken = createNewErc20TokenObject(token, iconPath); @@ -447,8 +471,8 @@ abstract class EVMChainWalletBase _updateBalance(); } - Future getErc20Token(String contractAddress) async => - await _client.getErc20Token(contractAddress); + Future getErc20Token(String contractAddress, String chainName) async => + await _client.getErc20Token(contractAddress, chainName); void _onNewTransaction() { _updateBalance(); @@ -484,7 +508,7 @@ abstract class EVMChainWalletBase _transactionsUpdateTimer!.cancel(); } - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) { + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 15), (_) { _updateTransactions(); _updateBalance(); }); diff --git a/cw_evm/lib/pending_evm_chain_transaction.dart b/cw_evm/lib/pending_evm_chain_transaction.dart index 8129de728..0b367da68 100644 --- a/cw_evm/lib/pending_evm_chain_transaction.dart +++ b/cw_evm/lib/pending_evm_chain_transaction.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:cw_core/pending_transaction.dart'; import 'package:web3dart/crypto.dart'; +import 'package:hex/hex.dart' as Hex; class PendingEVMChainTransaction with PendingTransaction { final Function sendTransaction; @@ -38,5 +39,12 @@ class PendingEVMChainTransaction with PendingTransaction { String get hex => bytesToHex(signedTransaction, include0x: true); @override - String get id => ''; + String get id { + final String eip1559Hex = '0x02${hex.substring(2)}'; + final Uint8List bytes = Uint8List.fromList(Hex.HEX.decode(eip1559Hex.substring(2))); + + var txid = keccak256(bytes); + + return '0x${Hex.HEX.encode(txid)}'; + } } diff --git a/cw_polygon/lib/polygon_client.dart b/cw_polygon/lib/polygon_client.dart index 055b42f87..d55ee2269 100644 --- a/cw_polygon/lib/polygon_client.dart +++ b/cw_polygon/lib/polygon_client.dart @@ -13,6 +13,7 @@ class PolygonClient extends EVMChainClient { required EthereumAddress to, required EtherAmount amount, EtherAmount? maxPriorityFeePerGas, + Uint8List? data, }) { return Transaction( from: from, @@ -54,4 +55,28 @@ class PolygonClient extends EVMChainClient { return []; } } + + @override + Future> fetchInternalTransactions(String address) async { + try { + final response = await httpClient.get(Uri.https("api.polygonscan.io", "/api", { + "module": "account", + "action": "txlistinternal", + "address": address, + "apikey": secrets.polygonScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map((e) => EVMChainTransactionModel.fromJson(e as Map, 'MATIC')) + .toList(); + } + + return []; + } catch (_) { + return []; + } + } } diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index ea4a9161a..6ed8cab29 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -96,16 +96,30 @@ class SolanaWalletClient { return SolanaBalance(totalBalance); } - Future getGasForMessage(String message) async { + Future getFeeForMessage(String message, Commitment commitment) async { try { - final gasPrice = await _client!.rpcClient.getFeeForMessage(message) ?? 0; - final fee = gasPrice / lamportsPerSol; + final feeForMessage = + await _client!.rpcClient.getFeeForMessage(message, commitment: commitment); + final fee = (feeForMessage ?? 0.0) / lamportsPerSol; return fee; } catch (_) { - return 0; + return 0.0; } } + Future getEstimatedFee(Ed25519HDKeyPair ownerKeypair) async { + const commitment = Commitment.confirmed; + + final message = + _getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol); + + final recentBlockhash = await _getRecentBlockhash(commitment); + + final estimatedFee = + _getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment); + return estimatedFee; + } + /// Load the Address's transactions into the account Future> fetchTransactions( Ed25519HDPublicKey publicKey, { @@ -257,24 +271,15 @@ class SolanaWalletClient { Future signSolanaTransaction({ required String tokenTitle, required int tokenDecimals, - String? tokenMint, required double inputAmount, required String destinationAddress, required Ed25519HDKeyPair ownerKeypair, + required bool isSendAll, + String? tokenMint, List references = const [], }) async { const commitment = Commitment.confirmed; - final latestBlockhash = - await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value; - - final recentBlockhash = RecentBlockhash( - blockhash: latestBlockhash.blockhash, - feeCalculator: const FeeCalculator( - lamportsPerSignature: 500, - ), - ); - if (tokenTitle == CryptoCurrency.sol.title) { final pendingNativeTokenTransaction = await _signNativeTokenTransaction( tokenTitle: tokenTitle, @@ -282,8 +287,8 @@ class SolanaWalletClient { inputAmount: inputAmount, destinationAddress: destinationAddress, ownerKeypair: ownerKeypair, - recentBlockhash: recentBlockhash, commitment: commitment, + isSendAll: isSendAll, ); return pendingNativeTokenTransaction; } else { @@ -294,25 +299,29 @@ class SolanaWalletClient { inputAmount: inputAmount, destinationAddress: destinationAddress, ownerKeypair: ownerKeypair, - recentBlockhash: recentBlockhash, commitment: commitment, ); return pendingSPLTokenTransaction; } } - Future _signNativeTokenTransaction({ - required String tokenTitle, - required int tokenDecimals, - required double inputAmount, - required String destinationAddress, - required Ed25519HDKeyPair ownerKeypair, - required RecentBlockhash recentBlockhash, - required Commitment commitment, - }) async { - // Convert SOL to lamport - int lamports = (inputAmount * lamportsPerSol).toInt(); + Future _getRecentBlockhash(Commitment commitment) async { + final latestBlockhash = + await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value; + final recentBlockhash = RecentBlockhash( + blockhash: latestBlockhash.blockhash, + feeCalculator: const FeeCalculator(lamportsPerSignature: 500), + ); + + return recentBlockhash; + } + + Message _getMessageForNativeTransaction( + Ed25519HDKeyPair ownerKeypair, + String destinationAddress, + int lamports, + ) { final instructions = [ SystemInstruction.transfer( fundingAccount: ownerKeypair.publicKey, @@ -322,21 +331,75 @@ class SolanaWalletClient { ]; final message = Message(instructions: instructions); + return message; + } + + Future _getFeeFromCompiledMessage( + Message message, + Ed25519HDPublicKey feePayer, + RecentBlockhash recentBlockhash, + Commitment commitment, + ) async { + final compile = message.compile( + recentBlockhash: recentBlockhash.blockhash, + feePayer: feePayer, + ); + + final base64Message = base64Encode(compile.toByteArray().toList()); + + final fee = await getFeeForMessage(base64Message, commitment); + + return fee; + } + + Future _signNativeTokenTransaction({ + required String tokenTitle, + required int tokenDecimals, + required double inputAmount, + required String destinationAddress, + required Ed25519HDKeyPair ownerKeypair, + required Commitment commitment, + required bool isSendAll, + }) async { + // Convert SOL to lamport + int lamports = (inputAmount * lamportsPerSol).toInt(); + + Message message = _getMessageForNativeTransaction(ownerKeypair, destinationAddress, lamports); + final signers = [ownerKeypair]; - final signedTx = await _signTransactionInternal( - message: message, - signers: signers, - commitment: commitment, - recentBlockhash: recentBlockhash, - ); + RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment); final fee = await _getFeeFromCompiledMessage( message, - recentBlockhash, signers.first.publicKey, + recentBlockhash, + commitment, ); + SignedTx signedTx; + if (isSendAll) { + final feeInLamports = (fee * lamportsPerSol).toInt(); + final updatedLamports = lamports - feeInLamports; + + final updatedMessage = + _getMessageForNativeTransaction(ownerKeypair, destinationAddress, updatedLamports); + + signedTx = await _signTransactionInternal( + message: updatedMessage, + signers: signers, + commitment: commitment, + recentBlockhash: recentBlockhash, + ); + } else { + signedTx = await _signTransactionInternal( + message: message, + signers: signers, + commitment: commitment, + recentBlockhash: recentBlockhash, + ); + } + sendTx() async => await sendTransaction( signedTransaction: signedTx, commitment: commitment, @@ -360,7 +423,6 @@ class SolanaWalletClient { required double inputAmount, required String destinationAddress, required Ed25519HDKeyPair ownerKeypair, - required RecentBlockhash recentBlockhash, required Commitment commitment, }) async { final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress); @@ -408,8 +470,18 @@ class SolanaWalletClient { ); final message = Message(instructions: [instruction]); + final signers = [ownerKeypair]; + RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment); + + final fee = await _getFeeFromCompiledMessage( + message, + signers.first.publicKey, + recentBlockhash, + commitment, + ); + final signedTx = await _signTransactionInternal( message: message, signers: signers, @@ -417,12 +489,6 @@ class SolanaWalletClient { recentBlockhash: recentBlockhash, ); - final fee = await _getFeeFromCompiledMessage( - message, - recentBlockhash, - signers.first.publicKey, - ); - sendTx() async => await sendTransaction( signedTransaction: signedTx, commitment: commitment, @@ -438,19 +504,6 @@ class SolanaWalletClient { return pendingTransaction; } - Future _getFeeFromCompiledMessage( - Message message, RecentBlockhash recentBlockhash, Ed25519HDPublicKey feePayer) async { - final compile = message.compile( - recentBlockhash: recentBlockhash.blockhash, - feePayer: feePayer, - ); - - final base64Message = base64Encode(compile.toByteArray().toList()); - - final fee = await getGasForMessage(base64Message); - return fee; - } - Future _signTransactionInternal({ required Message message, required List signers, @@ -466,13 +519,35 @@ class SolanaWalletClient { required SignedTx signedTransaction, required Commitment commitment, }) async { - final signature = await _client!.rpcClient.sendTransaction( - signedTransaction.encode(), - preflightCommitment: commitment, - ); + try { + final signature = await _client!.rpcClient.sendTransaction( + signedTransaction.encode(), + preflightCommitment: commitment, + ); - _client!.waitForSignatureStatus(signature, status: commitment); + _client!.waitForSignatureStatus(signature, status: commitment); - return signature; + return signature; + } catch (e) { + print('Error while sending transaction: ${e.toString()}'); + throw Exception(e); + } + } + + Future getIconImageFromTokenUri(String uri) async { + try { + final response = await httpClient.get(Uri.parse(uri)); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300) { + return jsonResponse['image']; + } else { + return null; + } + } catch (e) { + print('Error occurred while fetching token image: \n${e.toString()}'); + return null; + } } } diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index de4d70674..ad58c4293 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -75,6 +75,9 @@ abstract class SolanaWalletBase late SolanaWalletClient _client; + @observable + double? estimatedFee; + Timer? _transactionsUpdateTimer; late final Box splTokensBox; @@ -171,6 +174,14 @@ abstract class SolanaWalletBase } } + Future _getEstimatedFees() async { + try { + estimatedFee = await _client.getEstimatedFee(_walletKeyPair!); + } catch (e) { + estimatedFee = 0.0; + } + } + @override Future createTransaction(Object credentials) async { final solCredentials = credentials as SolanaTransactionCredentials; @@ -188,6 +199,8 @@ abstract class SolanaWalletBase double totalAmount = 0.0; + bool isSendAll = false; + if (hasMultiDestination) { if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { throw SolanaTransactionWrongBalanceException(transactionCurrency); @@ -204,9 +217,15 @@ abstract class SolanaWalletBase } else { final output = outputs.first; - final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); + isSendAll = output.sendAll; - totalAmount = output.sendAll ? walletBalanceForCurrency : totalOriginalAmount; + if (isSendAll) { + totalAmount = walletBalanceForCurrency; + } else { + final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); + + totalAmount = totalOriginalAmount; + } if (walletBalanceForCurrency < totalAmount) { throw SolanaTransactionWrongBalanceException(transactionCurrency); @@ -228,6 +247,7 @@ abstract class SolanaWalletBase destinationAddress: solCredentials.outputs.first.isParsedAddress ? solCredentials.outputs.first.extractedAddress! : solCredentials.outputs.first.address, + isSendAll: isSendAll, ); return pendingSolanaTransaction; @@ -269,7 +289,10 @@ abstract class SolanaWalletBase Future _updateSPLTokenTransactions() async { List splTokenTransactions = []; - for (var token in balance.keys) { + // Make a copy of keys to avoid concurrent modification + var tokenKeys = List.from(balance.keys); + + for (var token in tokenKeys) { if (token is SPLToken) { final tokenTxs = await _client.getSPLTokenTransfers( token.mintAddress, @@ -326,6 +349,7 @@ abstract class SolanaWalletBase _updateBalance(), _updateNativeSOLTransactions(), _updateSPLTokenTransactions(), + _getEstimatedFees(), ]); syncStatus = SyncedSyncStatus(); @@ -433,18 +457,28 @@ abstract class SolanaWalletBase final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress); // Fetch token's metadata account - final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey); + try { + final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey); - if (token == null) { + if (token == null) { + return null; + } + + String? iconPath; + try { + iconPath = await _client.getIconImageFromTokenUri(token.uri); + } catch (_) {} + + return SPLToken.fromMetadata( + name: token.name, + mint: token.mint, + symbol: token.symbol, + mintAddress: mintAddress, + iconPath: iconPath, + ); + } catch (e) { return null; } - - return SPLToken.fromMetadata( - name: token.name, - mint: token.mint, - symbol: token.symbol, - mintAddress: mintAddress, - ); } @override @@ -475,9 +509,9 @@ abstract class SolanaWalletBase } _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) { - _updateSPLTokenTransactions(); - _updateNativeSOLTransactions(); _updateBalance(); + _updateNativeSOLTransactions(); + _updateSPLTokenTransactions(); }); } diff --git a/cw_solana/lib/solana_wallet_service.dart b/cw_solana/lib/solana_wallet_service.dart index b3ff22e7e..83370ff73 100644 --- a/cw_solana/lib/solana_wallet_service.dart +++ b/cw_solana/lib/solana_wallet_service.dart @@ -32,6 +32,7 @@ class SolanaWalletService extends WalletService openWallet(String name, String password) async { final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); - final wallet = await SolanaWalletBase.open( - name: name, - password: password, - walletInfo: walletInfo, - ); - await wallet.init(); - await wallet.save(); + try { + final wallet = await SolanaWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + ); - return wallet; + await wallet.init(); + await wallet.save(); + saveBackup(name); + return wallet; + } catch (_) { + await restoreWalletFilesFromBackup(name); + + final wallet = await SolanaWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + ); + + await wallet.init(); + await wallet.save(); + return wallet; + } } @override @@ -110,6 +126,7 @@ class SolanaWalletService extends WalletService outputs, - {required TransactionPriority priority, int? feeRate}) => - BitcoinTransactionCredentials( - outputs - .map((out) => OutputInfo( - fiatAmount: out.fiatAmount, - cryptoAmount: out.cryptoAmount, - address: out.address, - note: out.note, - sendAll: out.sendAll, - extractedAddress: out.extractedAddress, - isParsedAddress: out.isParsedAddress, - formattedCryptoAmount: out.formattedCryptoAmount)) - .toList(), - priority: priority as BitcoinTransactionPriority, - feeRate: feeRate); + {required TransactionPriority priority, int? feeRate}) { + final bitcoinFeeRate = + priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null; + return BitcoinTransactionCredentials( + outputs + .map((out) => OutputInfo( + fiatAmount: out.fiatAmount, + cryptoAmount: out.cryptoAmount, + address: out.address, + note: out.note, + sendAll: out.sendAll, + extractedAddress: out.extractedAddress, + isParsedAddress: out.isParsedAddress, + formattedCryptoAmount: out.formattedCryptoAmount, + memo: out.memo)) + .toList(), + priority: priority as BitcoinTransactionPriority, + feeRate: bitcoinFeeRate + ); + } @override Object createBitcoinTransactionCredentialsRaw(List outputs, @@ -122,23 +127,30 @@ class CWBitcoin extends Bitcoin { @override Future estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority) async { - final electrumWallet = wallet as ElectrumWallet; - final sk = ECPrivate.random(); - - final p2shAddr = sk.getPublic().toP2pkhInP2sh(); - final p2wpkhAddr = sk.getPublic().toP2wpkhAddress(); try { - final estimatedTx = await electrumWallet.estimateTxFeeAndInputsToUse( - 0, - true, - // Deposit address + change address - [p2shAddr, p2wpkhAddr], - [ - BitcoinOutput(address: p2shAddr, value: BigInt.zero), - BitcoinOutput(address: p2wpkhAddr, value: BigInt.zero) - ], - null, - priority as BitcoinTransactionPriority); + final sk = ECPrivate.random(); + final electrumWallet = wallet as ElectrumWallet; + + if (wallet.type == WalletType.bitcoinCash) { + final p2pkhAddr = sk.getPublic().toP2pkhAddress(); + final estimatedTx = await electrumWallet.estimateSendAllTx( + [BitcoinOutput(address: p2pkhAddr, value: BigInt.zero)], + getFeeRate(wallet, priority as BitcoinCashTransactionPriority), + ); + + return estimatedTx.amount; + } + + final p2shAddr = sk.getPublic().toP2pkhInP2sh(); + final estimatedTx = await electrumWallet.estimateSendAllTx( + [BitcoinOutput(address: p2shAddr, value: BigInt.zero)], + getFeeRate( + wallet, + wallet.type == WalletType.litecoin + ? priority as LitecoinTransactionPriority + : priority as BitcoinTransactionPriority, + ), + ); return estimatedTx.amount; } catch (_) { @@ -164,8 +176,9 @@ class CWBitcoin extends Bitcoin { int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount); @override - String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate) => - (priority as BitcoinTransactionPriority).labelWithRate(rate); + String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate, + {int? customRate}) => + (priority as BitcoinTransactionPriority).labelWithRate(rate, customRate); @override List getUnspents(Object wallet) { @@ -191,6 +204,9 @@ class CWBitcoin extends Bitcoin { @override TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium; + @override + TransactionPriority getBitcoinTransactionPriorityCustom() => BitcoinTransactionPriority.custom; + @override TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium; @@ -231,4 +247,48 @@ class CWBitcoin extends Bitcoin { return SegwitAddresType.p2wpkh; } } + + @override + bool hasTaprootInput(PendingTransaction pendingTransaction) { + return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs; + } + + @override + Future replaceByFee( + Object wallet, String transactionHash, String fee) async { + final bitcoinWallet = wallet as ElectrumWallet; + return await bitcoinWallet.replaceByFee(transactionHash, int.parse(fee)); + } + + @override + Future canReplaceByFee(Object wallet, String transactionHash) async { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.canReplaceByFee(transactionHash); + } + + @override + Future isChangeSufficientForFee(Object wallet, String txId, String newFee) async { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.isChangeSufficientForFee(txId, int.parse(newFee)); + } + + @override + int getFeeAmountForPriority( + Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, + {int? size}) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.feeAmountForPriority( + priority as BitcoinTransactionPriority, inputsCount, outputsCount); + } + + @override + int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, + {int? size}) { + final bitcoinWallet = wallet as ElectrumWallet; + return bitcoinWallet.feeAmountWithFeeRate( + feeRate, + inputsCount, + outputsCount, + ); + } } diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index 02bdedaec..fea8fdabd 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -22,20 +22,24 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:url_launcher/url_launcher.dart'; -class MoonPaySellProvider extends BuyProvider { - MoonPaySellProvider({ +class MoonPayProvider extends BuyProvider { + MoonPayProvider({ required SettingsStore settingsStore, required WalletBase wallet, bool isTestEnvironment = false, - }) : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl, + }) : baseSellUrl = isTestEnvironment ? _baseSellTestUrl : _baseSellProductUrl, + baseBuyUrl = isTestEnvironment ? _baseBuyTestUrl : _baseBuyProductUrl, this._settingsStore = settingsStore, super(wallet: wallet, isTestEnvironment: isTestEnvironment); final SettingsStore _settingsStore; - static const _baseTestUrl = 'sell-sandbox.moonpay.com'; - static const _baseProductUrl = 'sell.moonpay.com'; + static const _baseSellTestUrl = 'sell-sandbox.moonpay.com'; + static const _baseSellProductUrl = 'sell.moonpay.com'; + static const _baseBuyTestUrl = 'buy-staging.moonpay.com'; + static const _baseBuyProductUrl = 'buy.moonpay.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; + static const _apiUrl = 'https://api.moonpay.com'; @override String get providerDescription => @@ -62,8 +66,14 @@ class MoonPaySellProvider extends BuyProvider { static String get _apiKey => secrets.moonPayApiKey; + final String baseBuyUrl; + final String baseSellUrl; + + String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase(); + + String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId='; + static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey; - final String baseUrl; Future getMoonpaySignature(String query) async { final uri = Uri.https(_cIdBaseUrl, "/api/moonpay"); @@ -85,147 +95,92 @@ class MoonPaySellProvider extends BuyProvider { } } - Future requestMoonPayUrl({ + Future requestSellMoonPayUrl({ required CryptoCurrency currency, required String refundWalletAddress, required SettingsStore settingsStore, }) async { - final customParams = { + final params = { 'theme': themeToMoonPayTheme(settingsStore.currentTheme), 'language': settingsStore.languageCode, 'colorCode': settingsStore.currentTheme.type == ThemeType.dark ? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}' : '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}', + 'defaultCurrencyCode': _normalizeCurrency(currency), + 'refundWalletAddress': refundWalletAddress, }; - final originalUri = Uri.https( - baseUrl, - '', - { - 'apiKey': _apiKey, - 'defaultBaseCurrencyCode': _normalizeCurrency(currency), - 'refundWalletAddress': refundWalletAddress, - }..addAll(customParams), - ); + if (_apiKey.isNotEmpty) { + params['apiKey'] = _apiKey; + } - final signature = await getMoonpaySignature('?${originalUri.query}'); + final originalUri = Uri.https( + baseSellUrl, + '', + params, + ); if (isTestEnvironment) { return originalUri; } + final signature = await getMoonpaySignature('?${originalUri.query}'); + final query = Map.from(originalUri.queryParameters); query['signature'] = signature; final signedUri = originalUri.replace(queryParameters: query); return signedUri; } - @override - Future launchProvider(BuildContext context, bool? isBuyAction) async { - try { - final uri = await requestMoonPayUrl( - currency: wallet.currency, - refundWalletAddress: wallet.walletAddresses.address, - settingsStore: _settingsStore, - ); - - if (await canLaunchUrl(uri)) { - if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]); - } else { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } - } else { - throw Exception('Could not launch URL'); - } - } catch (e) { - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: 'MoonPay', - alertContent: 'The MoonPay service is currently unavailable: $e', - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }, - ); - } - } - - String _normalizeCurrency(CryptoCurrency currency) { - if (currency == CryptoCurrency.maticpoly) { - return "MATIC_POLYGON"; - } - - return currency.toString().toLowerCase(); - } -} - -class MoonPayBuyProvider extends BuyProvider { - MoonPayBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) - : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl, - super(wallet: wallet, isTestEnvironment: isTestEnvironment); - - static const _baseTestUrl = 'https://buy-staging.moonpay.com'; - static const _baseProductUrl = 'https://buy.moonpay.com'; - static const _apiUrl = 'https://api.moonpay.com'; + // BUY: static const _currenciesSuffix = '/v3/currencies'; static const _quoteSuffix = '/buy_quote'; static const _transactionsSuffix = '/v1/transactions'; static const _ipAddressSuffix = '/v4/ip_address'; - static const _apiKey = secrets.moonPayApiKey; - static const _secretKey = secrets.moonPaySecretKey; - @override - String get title => 'MoonPay'; + Future requestBuyMoonPayUrl({ + required CryptoCurrency currency, + required SettingsStore settingsStore, + required String walletAddress, + String? amount, + }) async { + final params = { + 'theme': themeToMoonPayTheme(settingsStore.currentTheme), + 'language': settingsStore.languageCode, + 'colorCode': settingsStore.currentTheme.type == ThemeType.dark + ? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}' + : '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}', + 'defaultCurrencyCode': _normalizeCurrency(currency), + 'baseCurrencyCode': _normalizeCurrency(currency), + 'baseCurrencyAmount': amount ?? '0', + 'currencyCode': currencyCode, + 'walletAddress': walletAddress, + 'lockAmount': 'false', + 'showAllCurrencies': 'false', + 'showWalletAddressForm': 'false', + 'enabledPaymentMethods': + 'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment', + }; - @override - String get providerDescription => - 'MoonPay offers a fast and simple way to buy and sell cryptocurrencies'; + if (_apiKey.isNotEmpty) { + params['apiKey'] = _apiKey; + } - @override - String get lightIcon => 'assets/images/moonpay_light.png'; + final originalUri = Uri.https( + baseBuyUrl, + '', + params, + ); - @override - String get darkIcon => 'assets/images/moonpay_dark.png'; + if (isTestEnvironment) { + return originalUri; + } - String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase(); - - String get trackUrl => baseUrl + '/transaction_receipt?transactionId='; - - String baseUrl; - - Future requestUrl(String amount, String sourceCurrency) async { - final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay' - '%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment'; - - final suffix = '?apiKey=' + - _apiKey + - '¤cyCode=' + - currencyCode + - '&enabledPaymentMethods=' + - enabledPaymentMethods + - '&walletAddress=' + - wallet.walletAddresses.address + - '&baseCurrencyCode=' + - sourceCurrency.toLowerCase() + - '&baseCurrencyAmount=' + - amount + - '&lockAmount=true' + - '&showAllCurrencies=false' + - '&showWalletAddressForm=false'; - - final originalUrl = baseUrl + suffix; - - final messageBytes = utf8.encode(suffix); - final key = utf8.encode(_secretKey); - final hmac = Hmac(sha256, key); - final digest = hmac.convert(messageBytes); - final signature = base64.encode(digest.bytes); - final urlWithSignature = originalUrl + '&signature=${Uri.encodeComponent(signature)}'; - - return isTestEnvironment ? originalUrl : urlWithSignature; + final signature = await getMoonpaySignature('?${originalUri.query}'); + final query = Map.from(originalUri.queryParameters); + query['signature'] = signature; + final signedUri = originalUri.replace(queryParameters: query); + return signedUri; } Future calculateAmount(String amount, String sourceCurrency) async { @@ -300,6 +255,52 @@ class MoonPayBuyProvider extends BuyProvider { } @override - Future launchProvider(BuildContext context, bool? isBuyAction) => - throw UnimplementedError(); + Future launchProvider(BuildContext context, bool? isBuyAction) async { + try { + late final Uri uri; + if (isBuyAction ?? true) { + uri = await requestBuyMoonPayUrl( + currency: wallet.currency, + walletAddress: wallet.walletAddresses.address, + settingsStore: _settingsStore, + ); + } else { + uri = await requestSellMoonPayUrl( + currency: wallet.currency, + refundWalletAddress: wallet.walletAddresses.address, + settingsStore: _settingsStore, + ); + } + + if (await canLaunchUrl(uri)) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]); + } else { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } else { + throw Exception('Could not launch URL'); + } + } catch (e) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: 'MoonPay', + alertContent: 'The MoonPay service is currently unavailable: $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } + } + + String _normalizeCurrency(CryptoCurrency currency) { + if (currency == CryptoCurrency.maticpoly) { + return "MATIC_POLYGON"; + } + + return currency.toString().toLowerCase(); + } } diff --git a/lib/core/amount_validator.dart b/lib/core/amount_validator.dart index fb5214d54..38983dfb2 100644 --- a/lib/core/amount_validator.dart +++ b/lib/core/amount_validator.dart @@ -34,6 +34,10 @@ class AmountValidator extends TextValidator { late final DecimalAmountValidator decimalAmountValidator; String? call(String? value) { + if (value == null || value.isEmpty) { + return S.current.error_text_amount; + } + //* Validate for Text(length, symbols, decimals etc) final textValidation = symbolsAmountValidator(value) ?? decimalAmountValidator(value); diff --git a/lib/core/execution_state.dart b/lib/core/execution_state.dart index 18dc81030..6bc906010 100644 --- a/lib/core/execution_state.dart +++ b/lib/core/execution_state.dart @@ -14,4 +14,13 @@ class FailureState extends ExecutionState { FailureState(this.error); final String error; +} + +class AwaitingConfirmationState extends ExecutionState { + AwaitingConfirmationState({this.title, this.message, this.onConfirm, this.onCancel}); + + final String? title; + final String? message; + final Function()? onConfirm; + final Function()? onCancel; } \ No newline at end of file diff --git a/lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart b/lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart index bdc8a7d20..ed80a4f3f 100644 --- a/lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart +++ b/lib/core/wallet_connect/chain_service/solana/solana_chain_id.dart @@ -2,8 +2,8 @@ import 'solana_chain_service.dart'; enum SolanaChainId { mainnet, - testnet, - devnet, + // testnet, + // devnet, } extension SolanaChainIdX on SolanaChainId { @@ -13,13 +13,16 @@ extension SolanaChainIdX on SolanaChainId { switch (this) { case SolanaChainId.mainnet: name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ'; + // solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp break; - case SolanaChainId.testnet: - name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K'; - break; - case SolanaChainId.devnet: - name = ''; - break; + // case SolanaChainId.devnet: + // name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K'; + // // solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 + // break; + // case SolanaChainId.testnet: + // name = ''; + // // solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z + // break; } return '${SolanaChainServiceImpl.namespace}:$name'; diff --git a/lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart b/lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart index f5c696be6..efbf9df74 100644 --- a/lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart +++ b/lib/core/wallet_connect/chain_service/solana/solana_chain_service.dart @@ -43,7 +43,7 @@ class SolanaChainServiceImpl implements ChainService { SolanaClient( rpcUrl: rpcUrl, websocketUrl: Uri.parse(webSocketUrl), - timeout: const Duration(minutes: 2), + timeout: const Duration(minutes: 5), ) { for (final String event in getEvents()) { wallet.registerEventEmitter(chainId: getChainId(), event: event); @@ -72,7 +72,7 @@ class SolanaChainServiceImpl implements ChainService { @override List getEvents() { - return ['']; + return ['chainChanged', 'accountsChanged']; } Future requestAuthorization(String? text) async { @@ -100,8 +100,7 @@ class SolanaChainServiceImpl implements ChainService { Future solanaSignTransaction(String topic, dynamic parameters) async { log('received solana sign transaction request $parameters'); - final solanaSignTx = - SolanaSignTransaction.fromJson(parameters as Map); + final solanaSignTx = SolanaSignTransaction.fromJson(parameters as Map); final String? authError = await requestAuthorization('Confirm request to sign transaction?'); @@ -122,10 +121,13 @@ class SolanaChainServiceImpl implements ChainService { return ''; } - String signature = sign.signatures.first.toBase58(); + String signature = await solanaClient.sendAndConfirmTransaction( + message: message, + signers: [ownerKeyPair!], + commitment: Commitment.confirmed, + ); print(signature); - print(signature.runtimeType); bottomSheetService.queueBottomSheet( isModalDismissible: true, diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart index 4c71abe48..66ccb2d76 100644 --- a/lib/core/wallet_connect/web3wallet_service.dart +++ b/lib/core/wallet_connect/web3wallet_service.dart @@ -133,13 +133,27 @@ abstract class Web3WalletServiceBase with Store { if (appStore.wallet!.type == WalletType.solana) { for (final cId in SolanaChainId.values) { final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type); - final rpcUri = node.uri; - final webSocketUri = 'wss://${node.uriRaw}/ws${node.uri.path}'; + + Uri? rpcUri; + String webSocketUrl; + bool isModifiedNodeUri = false; + + if (node.uriRaw == 'rpc.ankr.com') { + isModifiedNodeUri = true; + + //A better way to handle this instead of adding this to the general secrets? + String ankrApiKey = secrets.ankrApiKey; + + rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey'); + webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey'; + } else { + webSocketUrl = 'wss://${node.uriRaw}'; + } SolanaChainServiceImpl( reference: cId, - rpcUrl: rpcUri, - webSocketUrl: webSocketUri, + rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri, + webSocketUrl: webSocketUrl, wcKeyService: walletKeyService, bottomSheetService: _bottomSheetHandler, wallet: _web3Wallet, diff --git a/lib/di.dart b/lib/di.dart index 782c0f1f4..5262a01e6 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -13,6 +13,7 @@ import 'package:cake_wallet/core/yat_service.dart'; import 'package:cake_wallet/entities/background_tasks.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/nano/nano.dart'; @@ -198,6 +199,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; @@ -806,8 +808,11 @@ Future setup({ getIt .registerFactory(() => DFXBuyProvider(wallet: getIt.get().wallet!)); - getIt.registerFactory(() => MoonPaySellProvider( - settingsStore: getIt.get().settingsStore, wallet: getIt.get().wallet!)); + getIt.registerFactory(() => MoonPayProvider( + settingsStore: getIt.get().settingsStore, + wallet: getIt.get().wallet!, + isTestEnvironment: kDebugMode, + )); getIt.registerFactory(() => OnRamperBuyProvider( getIt.get().settingsStore, @@ -910,7 +915,8 @@ Future setup({ transactionInfo: transactionInfo, transactionDescriptionBox: _transactionDescriptionBox, wallet: wallet, - settingsStore: getIt.get()); + settingsStore: getIt.get(), + sendViewModel: getIt.get()); }); getIt.registerFactoryParam( @@ -1133,6 +1139,11 @@ Future setup({ getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get())); + getIt.registerFactoryParam( + (TransactionInfo transactionInfo, _) => RBFDetailsPage( + transactionDetailsViewModel: + getIt.get(param1: transactionInfo))); + getIt.registerFactory(() => AnonPayApi( useTorOnly: getIt.get().exchangeStatus == ExchangeApiMode.torOnly, wallet: getIt.get().wallet!)); diff --git a/lib/entities/background_tasks.dart b/lib/entities/background_tasks.dart index ce1e2f6d8..5db42381e 100644 --- a/lib/entities/background_tasks.dart +++ b/lib/entities/background_tasks.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; @@ -107,7 +108,7 @@ class BackgroundTasks { final SyncMode syncMode = settingsStore.currentSyncMode; final bool syncAll = settingsStore.currentSyncAll; - if (syncMode.type == SyncType.disabled) { + if (syncMode.type == SyncType.disabled || !FeatureFlag.isBackgroundSyncEnabled) { cancelSyncTask(); return; } diff --git a/lib/entities/biometric_auth.dart b/lib/entities/biometric_auth.dart index a0afc070a..febbfa469 100644 --- a/lib/entities/biometric_auth.dart +++ b/lib/entities/biometric_auth.dart @@ -10,6 +10,7 @@ class BiometricAuth { return await _localAuth.authenticate( localizedReason: S.current.biometric_auth_reason, options: AuthenticationOptions( + biometricOnly: true, useErrorDialogs: true, stickyAuth: false)); } on PlatformException catch (e) { diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 8ce38e4c4..9a2db56af 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -1,6 +1,7 @@ import 'dart:io' show Directory, File, Platform; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; +import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -211,6 +212,10 @@ Future defaultSettingsMigration( await changeDefaultBitcoinNode(nodes, sharedPreferences); break; + case 30: + await disableServiceStatusFiatDisabled(sharedPreferences); + break; + default: break; } @@ -225,6 +230,18 @@ Future defaultSettingsMigration( await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version); } +Future disableServiceStatusFiatDisabled(SharedPreferences sharedPreferences) async { + final currentFiat = + await sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? -1; + if (currentFiat == -1 || currentFiat == FiatApiMode.enabled.raw) { + return; + } + + if (currentFiat == FiatApiMode.disabled.raw || currentFiat == FiatApiMode.torOnly.raw) { + await sharedPreferences.setBool(PreferencesKey.disableBulletinKey, true); + } +} + Future _updateMoneroPriority(SharedPreferences sharedPreferences) async { final currentPriority = await sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority) ?? diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 5c22455d2..ba6d6ef4f 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -42,6 +42,7 @@ class PreferencesKey { static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; static const polygonTransactionPriority = 'current_fee_priority_polygon'; static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; + static const customBitcoinFeeRate = 'custom_electrum_fee_rate'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index f3993e129..701781cc2 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -11,7 +11,7 @@ enum ProviderType { robinhood, dfx, onramper, - moonpaySell, + moonpay, } extension ProviderTypeName on ProviderType { @@ -25,7 +25,7 @@ extension ProviderTypeName on ProviderType { return 'DFX Connect'; case ProviderType.onramper: return 'Onramper'; - case ProviderType.moonpaySell: + case ProviderType.moonpay: return 'MoonPay'; } } @@ -40,7 +40,7 @@ extension ProviderTypeName on ProviderType { return 'dfx_connect_provider'; case ProviderType.onramper: return 'onramper_provider'; - case ProviderType.moonpaySell: + case ProviderType.moonpay: return 'moonpay_provider'; } } @@ -62,10 +62,11 @@ class ProvidersHelper { ProviderType.onramper, ProviderType.dfx, ProviderType.robinhood, + ProviderType.moonpay, ]; case WalletType.litecoin: case WalletType.bitcoinCash: - return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood]; + return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay]; case WalletType.solana: return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood]; case WalletType.none: @@ -82,18 +83,18 @@ class ProvidersHelper { return [ ProviderType.askEachTime, ProviderType.onramper, - ProviderType.moonpaySell, + ProviderType.moonpay, ProviderType.dfx, ]; case WalletType.litecoin: case WalletType.bitcoinCash: - return [ProviderType.askEachTime, ProviderType.moonpaySell]; + return [ProviderType.askEachTime, ProviderType.moonpay]; case WalletType.solana: return [ ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, - ProviderType.moonpaySell, + ProviderType.moonpay, ]; case WalletType.monero: case WalletType.nano: @@ -112,10 +113,10 @@ class ProvidersHelper { return getIt.get(); case ProviderType.onramper: return getIt.get(); + case ProviderType.moonpay: + return getIt.get(); case ProviderType.askEachTime: return null; - case ProviderType.moonpaySell: - return getIt.get(); } } } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 6e658788e..13fe3aafd 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -76,7 +76,8 @@ class CWEthereum extends Ethereum { sendAll: out.sendAll, extractedAddress: out.extractedAddress, isParsedAddress: out.isParsedAddress, - formattedCryptoAmount: out.formattedCryptoAmount)) + formattedCryptoAmount: out.formattedCryptoAmount, + memo: out.memo)) .toList(), priority: priority as EVMChainTransactionPriority, currency: currency, @@ -130,7 +131,7 @@ class CWEthereum extends Ethereum { @override Future getErc20Token(WalletBase wallet, String contractAddress) async { final ethereumWallet = wallet as EthereumWallet; - return await ethereumWallet.getErc20Token(contractAddress); + return await ethereumWallet.getErc20Token(contractAddress, 'eth'); } @override diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index abfac3a6b..4d9691035 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -22,6 +22,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png'); static const exolix = ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png'); + static const thorChain = + ExchangeProviderDescription(title: 'ThorChain' , raw: 8, image: 'assets/images/thorchain.png'); static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); @@ -41,6 +43,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< return trocador; case 6: return exolix; + case 8: + return thorChain; case 7: return all; default: diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart index c4a96bc5b..42f8634fb 100644 --- a/lib/exchange/provider/changenow_exchange_provider.dart +++ b/lib/exchange/provider/changenow_exchange_provider.dart @@ -133,7 +133,11 @@ class ChangeNowExchangeProvider extends ExchangeProvider { } @override - Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { + Future createTrade({ + required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll, + }) async { final distributionPath = await DistributionInfo.instance.getDistributionPath(); final formattedAppVersion = int.tryParse(_settingsStore.appVersion.replaceAll('.', '')) ?? 0; final payload = { @@ -202,7 +206,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { createdAt: DateTime.now(), amount: responseJSON['fromAmount']?.toString() ?? request.fromAmount, state: TradeState.created, - payoutAddress: payoutAddress); + payoutAddress: payoutAddress, + isSendAll: isSendAll); } @override diff --git a/lib/exchange/provider/exchange_provider.dart b/lib/exchange/provider/exchange_provider.dart index d1f69689d..a91a7ac9d 100644 --- a/lib/exchange/provider/exchange_provider.dart +++ b/lib/exchange/provider/exchange_provider.dart @@ -28,7 +28,8 @@ abstract class ExchangeProvider { Future fetchLimits( {required CryptoCurrency from, required CryptoCurrency to, required bool isFixedRateMode}); - Future createTrade({required TradeRequest request, required bool isFixedRateMode}); + Future createTrade( + {required TradeRequest request, required bool isFixedRateMode, required bool isSendAll}); Future findTradeById({required String id}); diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart index 9374439f3..db11a8f58 100644 --- a/lib/exchange/provider/exolix_exchange_provider.dart +++ b/lib/exchange/provider/exolix_exchange_provider.dart @@ -130,7 +130,11 @@ class ExolixExchangeProvider extends ExchangeProvider { } @override - Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { + Future createTrade({ + required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll, + }) async { final headers = {'Content-Type': 'application/json'}; final body = { 'coinFrom': _normalizeCurrency(request.fromCurrency), @@ -180,7 +184,8 @@ class ExolixExchangeProvider extends ExchangeProvider { createdAt: DateTime.now(), amount: amount, state: TradeState.created, - payoutAddress: payoutAddress); + payoutAddress: payoutAddress, + isSendAll: isSendAll); } @override diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart index 261aeedf3..1be4f8045 100644 --- a/lib/exchange/provider/sideshift_exchange_provider.dart +++ b/lib/exchange/provider/sideshift_exchange_provider.dart @@ -144,7 +144,11 @@ class SideShiftExchangeProvider extends ExchangeProvider { } @override - Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { + Future createTrade({ + required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll, + }) async { String url = ''; final body = { 'affiliateId': affiliateId, @@ -197,6 +201,7 @@ class SideShiftExchangeProvider extends ExchangeProvider { amount: depositAmount ?? request.fromAmount, payoutAddress: settleAddress, createdAt: DateTime.now(), + isSendAll: isSendAll, ); } diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart index 5c162a995..df83cf491 100644 --- a/lib/exchange/provider/simpleswap_exchange_provider.dart +++ b/lib/exchange/provider/simpleswap_exchange_provider.dart @@ -117,7 +117,11 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { } @override - Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { + Future createTrade({ + required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll, + }) async { final headers = {'Content-Type': 'application/json'}; final params = {'api_key': apiKey}; final body = { @@ -162,6 +166,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { amount: request.fromAmount, payoutAddress: payoutAddress, createdAt: DateTime.now(), + isSendAll: isSendAll, ); } diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart new file mode 100644 index 000000000..32dce7db8 --- /dev/null +++ b/lib/exchange/provider/thorchain_exchange.provider.dart @@ -0,0 +1,255 @@ +import 'dart:convert'; + +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/limits.dart'; +import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; + +class ThorChainExchangeProvider extends ExchangeProvider { + ThorChainExchangeProvider({required this.tradesStore}) + : super(pairList: supportedPairs(_notSupported)); + + static final List _notSupported = [ + ...(CryptoCurrency.all + .where((element) => ![ + CryptoCurrency.btc, + CryptoCurrency.eth, + CryptoCurrency.ltc, + CryptoCurrency.bch, + CryptoCurrency.aave, + CryptoCurrency.dai, + CryptoCurrency.gusd, + CryptoCurrency.usdc, + CryptoCurrency.usdterc20, + CryptoCurrency.wbtc, + ].contains(element)) + .toList()) + ]; + + static final isRefundAddressSupported = [CryptoCurrency.eth]; + + static const _baseURL = 'thornode.ninerealms.com'; + static const _quotePath = '/thorchain/quote/swap'; + static const _txInfoPath = '/thorchain/tx/status/'; + static const _affiliateName = 'cakewallet'; + static const _affiliateBps = '175'; + + final Box tradesStore; + + @override + String get title => 'THORChain'; + + @override + bool get isAvailable => true; + + @override + bool get isEnabled => true; + + @override + bool get supportsFixedRate => false; + + @override + ExchangeProviderDescription get description => ExchangeProviderDescription.thorChain; + + @override + Future checkIsAvailable() async => true; + + @override + Future fetchRate( + {required CryptoCurrency from, + required CryptoCurrency to, + required double amount, + required bool isFixedRateMode, + required bool isReceiveAmount}) async { + try { + if (amount == 0) return 0.0; + + final params = { + 'from_asset': _normalizeCurrency(from), + 'to_asset': _normalizeCurrency(to), + 'amount': _doubleToThorChainString(amount), + 'affiliate': _affiliateName, + 'affiliate_bps': _affiliateBps + }; + + final responseJSON = await _getSwapQuote(params); + + final expectedAmountOut = responseJSON['expected_amount_out'] as String? ?? '0.0'; + + return _thorChainAmountToDouble(expectedAmountOut) / amount; + } catch (e) { + print(e.toString()); + return 0.0; + } + } + + @override + Future fetchLimits( + {required CryptoCurrency from, + required CryptoCurrency to, + required bool isFixedRateMode}) async { + final params = { + 'from_asset': _normalizeCurrency(from), + 'to_asset': _normalizeCurrency(to), + 'amount': _doubleToThorChainString(1), + 'affiliate': _affiliateName, + 'affiliate_bps': _affiliateBps + }; + + final responseJSON = await _getSwapQuote(params); + final minAmountIn = responseJSON['recommended_min_amount_in'] as String? ?? '0.0'; + + return Limits(min: _thorChainAmountToDouble(minAmountIn)); + } + + @override + Future createTrade({ + required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll, + }) async { + String formattedToAddress = request.toAddress.startsWith('bitcoincash:') + ? request.toAddress.replaceFirst('bitcoincash:', '') + : request.toAddress; + + final formattedFromAmount = double.parse(request.fromAmount); + + final params = { + 'from_asset': _normalizeCurrency(request.fromCurrency), + 'to_asset': _normalizeCurrency(request.toCurrency), + 'amount': _doubleToThorChainString(formattedFromAmount), + 'destination': formattedToAddress, + 'affiliate': _affiliateName, + 'affiliate_bps': _affiliateBps, + 'refund_address': + isRefundAddressSupported.contains(request.fromCurrency) ? request.refundAddress : '', + }; + + final responseJSON = await _getSwapQuote(params); + + final inputAddress = responseJSON['inbound_address'] as String?; + final memo = responseJSON['memo'] as String?; + + return Trade( + id: '', + from: request.fromCurrency, + to: request.toCurrency, + provider: description, + inputAddress: inputAddress, + createdAt: DateTime.now(), + amount: request.fromAmount, + state: TradeState.notFound, + payoutAddress: request.toAddress, + memo: memo, + isSendAll: isSendAll); + } + + @override + Future findTradeById({required String id}) async { + if (id.isEmpty) throw Exception('Trade id is empty'); + final formattedId = id.startsWith('0x') ? id.substring(2) : id; + final uri = Uri.https(_baseURL, '$_txInfoPath$formattedId'); + final response = await http.get(uri); + + if (response.statusCode == 404) { + throw Exception('Trade not found for id: $formattedId'); + } else if (response.statusCode != 200) { + throw Exception('Unexpected HTTP status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body); + final Map stagesJson = responseJSON['stages'] as Map; + + final inboundObservedStarted = stagesJson['inbound_observed']?['started'] as bool? ?? true; + if (!inboundObservedStarted) { + throw Exception('Trade has not started for id: $formattedId'); + } + + final currentState = _updateStateBasedOnStages(stagesJson) ?? TradeState.notFound; + + final tx = responseJSON['tx']; + final String fromAddress = tx['from_address'] as String? ?? ''; + final String toAddress = tx['to_address'] as String? ?? ''; + final List coins = tx['coins'] as List; + final String? memo = tx['memo'] as String?; + + final parts = memo?.split(':') ?? []; + + final String toChain = parts.length > 1 ? parts[1].split('.')[0] : ''; + final String toAsset = parts.length > 1 && parts[1].split('.').length > 1 + ? parts[1].split('.')[1].split('-')[0] + : ''; + + final formattedToChain = CryptoCurrency.fromString(toChain); + final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency: formattedToChain); + + final plannedOutTxs = responseJSON['planned_out_txs'] as List?; + final isRefund = plannedOutTxs?.any((tx) => tx['refund'] == true) ?? false; + + return Trade( + id: id, + from: CryptoCurrency.fromString(tx['chain'] as String? ?? ''), + to: toAssetWithChain, + provider: description, + inputAddress: fromAddress, + payoutAddress: toAddress, + amount: coins.first['amount'] as String? ?? '0.0', + state: currentState, + memo: memo, + isRefund: isRefund, + ); + } + + Future> _getSwapQuote(Map params) async { + Uri uri = Uri.https(_baseURL, _quotePath, params); + + final response = await http.get(uri); + + if (response.statusCode != 200) { + throw Exception('Unexpected HTTP status: ${response.statusCode}'); + } + + if (response.body.contains('error')) { + throw Exception('Unexpected response: ${response.body}'); + } + + return json.decode(response.body) as Map; + } + + String _normalizeCurrency(CryptoCurrency currency) { + final networkTitle = currency.tag == 'ETH' ? 'ETH' : currency.title; + return '$networkTitle.${currency.title}'; + } + + String _doubleToThorChainString(double amount) => (amount * 1e8).toInt().toString(); + + double _thorChainAmountToDouble(String amount) => double.parse(amount) / 1e8; + + TradeState? _updateStateBasedOnStages(Map stages) { + TradeState? currentState; + + if (stages['inbound_observed']['completed'] as bool? ?? false) { + currentState = TradeState.confirmation; + } + if (stages['inbound_confirmation_counted']['completed'] as bool? ?? false) { + currentState = TradeState.confirmed; + } + if (stages['inbound_finalised']['completed'] as bool? ?? false) { + currentState = TradeState.processing; + } + if (stages['swap_finalised']['completed'] as bool? ?? false) { + currentState = TradeState.traded; + } + if (stages['outbound_signed']['completed'] as bool? ?? false) { + currentState = TradeState.success; + } + + return currentState; + } +} diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index faa4cc060..326573016 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -13,7 +13,8 @@ import 'package:http/http.dart'; class TrocadorExchangeProvider extends ExchangeProvider { TrocadorExchangeProvider({this.useTorOnly = false, this.providerStates = const {}}) - : _lastUsedRateId = '', _provider = [], + : _lastUsedRateId = '', + _provider = [], super(pairList: supportedPairs(_notSupported)); bool useTorOnly; @@ -23,7 +24,7 @@ class TrocadorExchangeProvider extends ExchangeProvider { 'Swapter', 'StealthEx', 'Simpleswap', - 'Swapuz' + 'Swapuz', 'ChangeNow', 'Changehero', 'FixedFloat', @@ -144,8 +145,11 @@ class TrocadorExchangeProvider extends ExchangeProvider { } @override - Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { - + Future createTrade({ + required TradeRequest request, + required bool isFixedRateMode, + required bool isSendAll, + }) async { final params = { 'api_key': apiKey, 'ticker_from': _normalizeCurrency(request.fromCurrency), @@ -172,7 +176,6 @@ class TrocadorExchangeProvider extends ExchangeProvider { params['id'] = _lastUsedRateId; } - String firstAvailableProvider = ''; for (var provider in _provider) { @@ -225,7 +228,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { providerName: providerName, createdAt: DateTime.tryParse(date)?.toLocal(), amount: responseJSON['amount_from']?.toString() ?? request.fromAmount, - payoutAddress: payoutAddress); + payoutAddress: payoutAddress, + isSendAll: isSendAll); } @override diff --git a/lib/exchange/trade.dart b/lib/exchange/trade.dart index 4eb48c248..6cc3fddbe 100644 --- a/lib/exchange/trade.dart +++ b/lib/exchange/trade.dart @@ -27,7 +27,11 @@ class Trade extends HiveObject { this.password, this.providerId, this.providerName, - this.fromWalletAddress + this.fromWalletAddress, + this.memo, + this.txId, + this.isRefund, + this.isSendAll, }) { if (provider != null) providerRaw = provider.raw; @@ -105,6 +109,18 @@ class Trade extends HiveObject { @HiveField(17) String? fromWalletAddress; + @HiveField(18) + String? memo; + + @HiveField(19) + String? txId; + + @HiveField(20) + bool? isRefund; + + @HiveField(21) + bool? isSendAll; + static Trade fromMap(Map map) { return Trade( id: map['id'] as String, @@ -115,8 +131,11 @@ class Trade extends HiveObject { map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null, amount: map['amount'] as String, walletId: map['wallet_id'] as String, - fromWalletAddress: map['from_wallet_address'] as String? - ); + fromWalletAddress: map['from_wallet_address'] as String?, + memo: map['memo'] as String?, + txId: map['tx_id'] as String?, + isRefund: map['isRefund'] as bool?, + isSendAll: map['isSendAll'] as bool?); } Map toMap() { @@ -128,7 +147,11 @@ class Trade extends HiveObject { 'date': createdAt != null ? createdAt!.millisecondsSinceEpoch : null, 'amount': amount, 'wallet_id': walletId, - 'from_wallet_address': fromWalletAddress + 'from_wallet_address': fromWalletAddress, + 'memo': memo, + 'tx_id': txId, + 'isRefund': isRefund, + 'isSendAll': isSendAll, }; } diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index ed56d9845..2c58a96f4 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -41,6 +41,8 @@ class TradeState extends EnumerableItem with Serializable { static const success = TradeState(raw: 'success', title: 'Success'); static TradeState deserialize({required String raw}) { switch (raw) { + case 'NOT_FOUND': + return notFound; case 'pending': return pending; case 'confirming': @@ -98,6 +100,7 @@ class TradeState extends EnumerableItem with Serializable { case 'sending': return sending; case 'success': + case 'done': return success; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); diff --git a/lib/main.dart b/lib/main.dart index db505f15a..6868348f6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -149,25 +149,26 @@ Future initializeAppConfigs() async { final unspentCoinsInfoSource = await CakeHive.openBox(UnspentCoinsInfo.boxName); await initialSetup( - sharedPreferences: await SharedPreferences.getInstance(), - nodes: nodes, - powNodes: powNodes, - walletInfoSource: walletInfoSource, - contactSource: contacts, - tradesSource: trades, - ordersSource: orders, - unspentCoinsInfoSource: unspentCoinsInfoSource, - // fiatConvertationService: fiatConvertationService, - templates: templates, - exchangeTemplates: exchangeTemplates, - transactionDescriptions: transactionDescriptions, - secureStorage: secureStorage, - anonpayInvoiceInfo: anonpayInvoiceInfo, - initialMigrationVersion: 29); + sharedPreferences: await SharedPreferences.getInstance(), + nodes: nodes, + powNodes: powNodes, + walletInfoSource: walletInfoSource, + contactSource: contacts, + tradesSource: trades, + ordersSource: orders, + unspentCoinsInfoSource: unspentCoinsInfoSource, + // fiatConvertationService: fiatConvertationService, + templates: templates, + exchangeTemplates: exchangeTemplates, + transactionDescriptions: transactionDescriptions, + secureStorage: secureStorage, + anonpayInvoiceInfo: anonpayInvoiceInfo, + initialMigrationVersion: 30, + ); } Future initialSetup( - {required SharedPreferences sharedPreferences, + {required SharedPreferences sharedPreferences, required Box nodes, required Box powNodes, required Box walletInfoSource, diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index 0ee7457eb..9f0f9a1bf 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -129,7 +129,7 @@ class CWPolygon extends Polygon { @override Future getErc20Token(WalletBase wallet, String contractAddress) async { final polygonWallet = wallet as PolygonWallet; - return await polygonWallet.getErc20Token(contractAddress); + return await polygonWallet.getErc20Token(contractAddress, 'polygon'); } @override diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart index f4487123e..ca908bc65 100644 --- a/lib/reactions/wallet_connect.dart +++ b/lib/reactions/wallet_connect.dart @@ -16,6 +16,7 @@ bool isWalletConnectCompatibleChain(WalletType walletType) { switch (walletType) { case WalletType.polygon: case WalletType.ethereum: + case WalletType.solana: return true; default: return false; diff --git a/lib/router.dart b/lib/router.dart index ef7b7f31e..9f5dfb838 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -54,6 +54,7 @@ import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart import 'package:cake_wallet/src/screens/support/support_page.dart'; import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart'; import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart'; +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; @@ -253,6 +254,12 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get(param1: settings.arguments as TransactionInfo)); + case Routes.bumpFeePage: + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => + getIt.get(param1: settings.arguments as TransactionInfo)); + case Routes.newSubaddress: return CupertinoPageRoute( builder: (_) => getIt.get(param1: settings.arguments)); diff --git a/lib/routes.dart b/lib/routes.dart index 7ad5c70bc..9c4e21651 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -12,6 +12,7 @@ class Routes { static const dashboard = '/dashboard'; static const send = '/send'; static const transactionDetails = '/transaction_info'; + static const bumpFeePage = '/bump_fee_page'; static const receive = '/receive'; static const newSubaddress = '/new_subaddress'; static const walletEdit = '/walletEdit'; diff --git a/lib/solana/cw_solana.dart b/lib/solana/cw_solana.dart index 9f9d81e5f..6f4b17309 100644 --- a/lib/solana/cw_solana.dart +++ b/lib/solana/cw_solana.dart @@ -74,8 +74,23 @@ class CWSolana extends Solana { } @override - Future addSPLToken(WalletBase wallet, CryptoCurrency token) async => - await (wallet as SolanaWallet).addSPLToken(token as SPLToken); + Future addSPLToken( + WalletBase wallet, + CryptoCurrency token, + String contractAddress, + ) async { + final splToken = SPLToken( + name: token.name, + symbol: token.title, + mintAddress: contractAddress, + decimal: token.decimals, + mint: token.name.toUpperCase(), + enabled: token.enabled, + iconPath: token.iconPath, + ); + + await (wallet as SolanaWallet).addSPLToken(splToken); + } @override Future deleteSPLToken(WalletBase wallet, CryptoCurrency token) async => @@ -115,4 +130,9 @@ class CWSolana extends Solana { return null; } + + @override + double? getEstimateFees(WalletBase wallet) { + return (wallet as SolanaWallet).estimatedFee; + } } diff --git a/lib/src/screens/buy/buy_options_page.dart b/lib/src/screens/buy/buy_options_page.dart index 50f041d2e..38f3ed968 100644 --- a/lib/src/screens/buy/buy_options_page.dart +++ b/lib/src/screens/buy/buy_options_page.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/option_tile.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; @@ -25,45 +26,46 @@ class BuySellOptionsPage extends BasePage { ? dashboardViewModel.availableBuyProviders : dashboardViewModel.availableSellProviders; - return Container( - child: Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 330), - child: Column( - children: [ - ...availableProviders.map((provider) { - final icon = Image.asset( - isLightMode ? provider.lightIcon : provider.darkIcon, - height: 40, - width: 40, - ); + return ScrollableWithBottomSection( + content: Container( + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 330), + child: Column( + children: [ + ...availableProviders.map((provider) { + final icon = Image.asset( + isLightMode ? provider.lightIcon : provider.darkIcon, + height: 40, + width: 40, + ); - return Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: icon, - title: provider.toString(), - description: provider.providerDescription, - onPressed: () => provider.launchProvider(context, isBuyAction), - ), - ); - }).toList(), - Spacer(), - Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), - child: Text( - isBuyAction - ? S.of(context).select_buy_provider_notice - : S.of(context).select_sell_provider_notice, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.detailsTitlesColor, - ), - ), - ), - ], + return Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + image: icon, + title: provider.toString(), + description: provider.providerDescription, + onPressed: () => provider.launchProvider(context, isBuyAction), + ), + ); + }).toList(), + ], + ), + ), + ), + ), + bottomSection: Padding( + padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + child: Text( + isBuyAction + ? S.of(context).select_buy_provider_notice + : S.of(context).select_sell_provider_notice, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.detailsTitlesColor, ), ), ), diff --git a/lib/src/screens/buy/buy_webview_page.dart b/lib/src/screens/buy/buy_webview_page.dart index 829bff3d9..ad6970861 100644 --- a/lib/src/screens/buy/buy_webview_page.dart +++ b/lib/src/screens/buy/buy_webview_page.dart @@ -60,7 +60,7 @@ class BuyWebViewPageBodyState extends State { _saveOrder(keyword: 'completed', splitSymbol: '/'); } - if (widget.buyViewModel.selectedProvider is MoonPayBuyProvider) { + if (widget.buyViewModel.selectedProvider is MoonPayProvider) { _saveOrder(keyword: 'transactionId', splitSymbol: '='); } } diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index 720a8cc14..59f7de9e5 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -59,6 +59,7 @@ class _EditTokenPageBodyState extends State { final TextEditingController _tokenNameController = TextEditingController(); final TextEditingController _tokenSymbolController = TextEditingController(); final TextEditingController _tokenDecimalController = TextEditingController(); + final TextEditingController _tokenIconPathController = TextEditingController(); final FocusNode _contractAddressFocusNode = FocusNode(); final FocusNode _tokenNameFocusNode = FocusNode(); @@ -83,6 +84,7 @@ class _EditTokenPageBodyState extends State { _tokenNameController.text = widget.token!.name; _tokenSymbolController.text = widget.token!.title; _tokenDecimalController.text = widget.token!.decimals.toString(); + _tokenIconPathController.text = widget.token?.iconPath ?? ''; } if (widget.initialContractAddress != null) { @@ -195,12 +197,15 @@ class _EditTokenPageBodyState extends State { onPressed: () async { if (_formKey.currentState!.validate() && (!_showDisclaimer || _disclaimerChecked)) { - await widget.homeSettingsViewModel.addToken(Erc20Token( - name: _tokenNameController.text, - symbol: _tokenSymbolController.text, + await widget.homeSettingsViewModel.addToken( + token: CryptoCurrency( + name: _tokenNameController.text, + title: _tokenSymbolController.text.toUpperCase(), + decimals: int.parse(_tokenDecimalController.text), + iconPath: _tokenIconPathController.text, + ), contractAddress: _contractAddressController.text, - decimal: int.parse(_tokenDecimalController.text), - )); + ); if (context.mounted) { Navigator.pop(context); } @@ -226,6 +231,8 @@ class _EditTokenPageBodyState extends State { if (token != null) { if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name; if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.title; + if (_tokenIconPathController.text.isEmpty) + _tokenIconPathController.text = token.iconPath ?? ''; if (_tokenDecimalController.text.isEmpty) _tokenDecimalController.text = token.decimals.toString(); } @@ -303,10 +310,15 @@ class _EditTokenPageBodyState extends State { if (text?.isEmpty ?? true) { return S.of(context).field_required; } + if (int.tryParse(text!) == null) { return S.of(context).invalid_input; } + if (int.tryParse(text) == 0) { + return S.current.decimals_cannot_be_zero; + } + return null; }, ), diff --git a/lib/src/screens/dashboard/home_settings_page.dart b/lib/src/screens/dashboard/home_settings_page.dart index e841423c1..aa6bb12c0 100644 --- a/lib/src/screens/dashboard/home_settings_page.dart +++ b/lib/src/screens/dashboard/home_settings_page.dart @@ -129,25 +129,29 @@ class HomeSettingsPage extends BasePage { 'token': token, }); }, - leading: CakeImageWidget( - imageUrl: token.iconPath, - height: 40, - width: 40, - displayOnError: Container( - height: 30.0, - width: 30.0, - child: Center( - child: Text( - token.title.substring(0, min(token.title.length, 2)), - style: TextStyle(fontSize: 11), - ), - ), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey.shade400, + leading: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration(shape: BoxShape.circle), + child: CakeImageWidget( + imageUrl: token.iconPath, + height: 40, + width: 40, + displayOnError: Container( + height: 30.0, + width: 30.0, + child: Center( + child: Text( + token.title.substring(0, min(token.title.length, 2)), + style: TextStyle(fontSize: 11), ), + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.shade400, + ), + ), ), - ), + ), decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(30), diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index bb3ec70dc..0b7596469 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -323,7 +323,7 @@ class BalanceRowWidget extends StatelessWidget { style: TextStyle( fontSize: 16, fontFamily: 'Lato', - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w500, color: Theme.of(context).extension()!.textColor, height: 1)), ], @@ -334,24 +334,28 @@ class BalanceRowWidget extends StatelessWidget { child: Center( child: Column( children: [ - CakeImageWidget( - imageUrl: currency.iconPath, - height: 40, - width: 40, - displayOnError: Container( - height: 30.0, - width: 30.0, - child: Center( - child: Text( - currency.title.substring(0, min(currency.title.length, 2)), - style: TextStyle(fontSize: 11), - ), - ), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey.shade400, + Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(shape: BoxShape.circle), + child: CakeImageWidget( + imageUrl: currency.iconPath, + height: 40, + width: 40, + displayOnError: Container( + height: 30.0, + width: 30.0, + child: Center( + child: Text( + currency.title.substring(0, min(currency.title.length, 2)), + style: TextStyle(fontSize: 11), ), ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.shade400, + ), + ), + ), ), const SizedBox(height: 10), Text( @@ -410,9 +414,7 @@ class BalanceRowWidget extends StatelessWidget { fontSize: 20, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context) - .extension()! - .balanceAmountColor, + color: Theme.of(context).extension()!.balanceAmountColor, height: 1, ), maxLines: 1, diff --git a/lib/src/screens/dashboard/widgets/filter_tile.dart b/lib/src/screens/dashboard/widgets/filter_tile.dart index 3be96073a..d2f824806 100644 --- a/lib/src/screens/dashboard/widgets/filter_tile.dart +++ b/lib/src/screens/dashboard/widgets/filter_tile.dart @@ -9,7 +9,7 @@ class FilterTile extends StatelessWidget { Widget build(BuildContext context) { return Container( width: double.infinity, - padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0), + padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 24.0), child: child, ); } diff --git a/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart b/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart index 11bde6dfa..21133a438 100644 --- a/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart +++ b/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart @@ -20,6 +20,7 @@ class SyncIndicatorIcon extends StatelessWidget { static const String created = 'created'; static const String fetching = 'fetching'; static const String finished = 'finished'; + static const String success = 'success'; @override Widget build(BuildContext context) { @@ -45,6 +46,7 @@ class SyncIndicatorIcon extends StatelessWidget { indicatorColor = Colors.red; break; case finished: + case success: indicatorColor = PaletteDark.brightGreen; break; default: diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index 7f570b98e..caccb8047 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -34,7 +34,9 @@ class TradeRow extends StatelessWidget { mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ - _getPoweredImage(provider)!, + ClipRRect( + borderRadius: BorderRadius.circular(50), + child: Image.asset(provider.image, width: 36, height: 36)), SizedBox(width: 12), Expanded( child: Column( @@ -69,38 +71,4 @@ class TradeRow extends StatelessWidget { ), )); } - - Widget? _getPoweredImage(ExchangeProviderDescription provider) { - Widget? image; - - switch (provider) { - case ExchangeProviderDescription.xmrto: - image = Image.asset('assets/images/xmrto.png', height: 36, width: 36); - break; - case ExchangeProviderDescription.changeNow: - image = Image.asset('assets/images/changenow.png', height: 36, width: 36); - break; - case ExchangeProviderDescription.morphToken: - image = Image.asset('assets/images/morph.png', height: 36, width: 36); - break; - case ExchangeProviderDescription.sideShift: - image = Image.asset('assets/images/sideshift.png', width: 36, height: 36); - break; - case ExchangeProviderDescription.simpleSwap: - image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36); - break; - case ExchangeProviderDescription.trocador: - image = ClipRRect( - borderRadius: BorderRadius.circular(50), - child: Image.asset('assets/images/trocador.png', width: 36, height: 36)); - break; - case ExchangeProviderDescription.exolix: - image = Image.asset('assets/images/exolix.png', width: 36, height: 36); - break; - default: - image = null; - } - - return image; - } } diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 1a5ab24e6..d9e119038 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/core/auth_service.dart'; @@ -60,7 +62,7 @@ class ExchangePage extends BasePage { final _receiveAmountFocus = FocusNode(); final _receiveAddressFocus = FocusNode(); final _receiveAmountDebounce = Debounce(Duration(milliseconds: 500)); - final _depositAmountDebounce = Debounce(Duration(milliseconds: 500)); + Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500)); var _isReactionsSet = false; final arrowBottomPurple = Image.asset( @@ -184,7 +186,13 @@ class ExchangePage extends BasePage { StandardCheckbox( value: exchangeViewModel.isFixedRateMode, caption: S.of(context).fixed_rate, - onChanged: (value) => exchangeViewModel.isFixedRateMode = value, + onChanged: (value) { + if (value) { + exchangeViewModel.enableFixedRateMode(); + } else { + exchangeViewModel.isFixedRateMode = false; + } + }, ), ], )), @@ -431,7 +439,9 @@ class ExchangePage extends BasePage { } if (state is TradeIsCreatedSuccessfully) { exchangeViewModel.reset(); - Navigator.of(context).pushNamed(Routes.exchangeConfirm); + (exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.thorChain) + ? Navigator.of(context).pushReplacementNamed(Routes.exchangeTrade) + : Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm); } }); @@ -470,6 +480,13 @@ class ExchangePage extends BasePage { if (depositAmountController.text != exchangeViewModel.depositAmount && depositAmountController.text != S.of(context).all) { exchangeViewModel.isSendAllEnabled = false; + final isThorChain = exchangeViewModel.selectedProviders + .any((provider) => provider is ThorChainExchangeProvider); + + _depositAmountDebounce = isThorChain + ? Debounce(Duration(milliseconds: 1000)) + : Debounce(Duration(milliseconds: 500)); + _depositAmountDebounce.run(() { exchangeViewModel.changeDepositAmount(amount: depositAmountController.text); exchangeViewModel.isReceiveAmountEntered = false; @@ -517,7 +534,7 @@ class ExchangePage extends BasePage { _receiveAmountFocus.addListener(() { if (_receiveAmountFocus.hasFocus) { - exchangeViewModel.isFixedRateMode = true; + exchangeViewModel.enableFixedRateMode(); } // exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text); }); diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 8fa809de9..760b0c137 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -485,14 +485,14 @@ class ExchangeCardState extends State { context: context, builder: (dialogContext) { return AlertWithTwoActions( - alertTitle: S.of(context).overwrite_amount, - alertContent: S.of(context).qr_payment_amount, - rightButtonText: S.of(context).ok, - leftButtonText: S.of(context).cancel, + alertTitle: S.of(dialogContext).overwrite_amount, + alertContent: S.of(dialogContext).qr_payment_amount, + rightButtonText: S.of(dialogContext).ok, + leftButtonText: S.of(dialogContext).cancel, actionRightButton: () { widget.amountFocusNode?.requestFocus(); amountController.text = paymentRequest.amount; - Navigator.of(context).pop(); + Navigator.of(dialogContext).pop(); }, actionLeftButton: () => Navigator.of(dialogContext).pop()); }); diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index c4dcae32c..4d3334f9f 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -262,6 +262,7 @@ class ExchangeTradeState extends State { fee: S.of(popupContext).send_fee, feeValue: widget.exchangeTradeViewModel.sendViewModel .pendingTransaction!.feeFormatted, + feeRate: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeRate, rightButtonText: S.of(popupContext).send, leftButtonText: S.of(popupContext).cancel, actionRightButton: () async { diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index a4c095739..970bb31f2 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -100,7 +100,10 @@ class SendPage extends BasePage { AppBarStyle get appBarStyle => AppBarStyle.transparent; double _sendCardHeight(BuildContext context) { - final double initialHeight = sendViewModel.hasCoinControl ? 500 : 465; + double initialHeight = 450; + if (sendViewModel.hasCoinControl) { + initialHeight += 35; + } if (!responsiveLayoutUtil.shouldRenderMobileUI) { return initialHeight - 66; @@ -190,7 +193,7 @@ class SendPage extends BasePage { }, )), Padding( - padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10), + padding: EdgeInsets.only(left: 24, right: 24, bottom: 10), child: Container( height: 10, child: Observer( @@ -426,6 +429,7 @@ class SendPage extends BasePage { fee: isEVMCompatibleChain(sendViewModel.walletType) ? S.of(_dialogContext).send_estimated_fee : S.of(_dialogContext).send_fee, + feeRate: sendViewModel.pendingTransaction!.feeRate, feeValue: sendViewModel.pendingTransaction!.feeFormatted, feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, outputs: sendViewModel.outputs, @@ -455,7 +459,7 @@ class SendPage extends BasePage { ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : ''; final newContactMessage = newContactAddress != null - ? '\n${S.of(context).add_contact_to_address_book}' : ''; + ? '\n${S.of(_dialogContext).add_contact_to_address_book}' : ''; final alertContent = "$successMessage$waitMessage$newContactMessage"; diff --git a/lib/src/screens/send/widgets/confirm_sending_alert.dart b/lib/src/screens/send/widgets/confirm_sending_alert.dart index 87d00ce0f..ce711ce8b 100644 --- a/lib/src/screens/send/widgets/confirm_sending_alert.dart +++ b/lib/src/screens/send/widgets/confirm_sending_alert.dart @@ -16,6 +16,7 @@ class ConfirmSendingAlert extends BaseAlertDialog { required this.amountValue, required this.fiatAmountValue, required this.fee, + this.feeRate, required this.feeValue, required this.feeFiatAmount, required this.outputs, @@ -36,6 +37,7 @@ class ConfirmSendingAlert extends BaseAlertDialog { final String amountValue; final String fiatAmountValue; final String fee; + final String? feeRate; final String feeValue; final String feeFiatAmount; final List outputs; @@ -90,6 +92,7 @@ class ConfirmSendingAlert extends BaseAlertDialog { amountValue: amountValue, fiatAmountValue: fiatAmountValue, fee: fee, + feeRate: feeRate, feeValue: feeValue, feeFiatAmount: feeFiatAmount, outputs: outputs); @@ -103,6 +106,7 @@ class ConfirmSendingAlertContent extends StatefulWidget { required this.amountValue, required this.fiatAmountValue, required this.fee, + this.feeRate, required this.feeValue, required this.feeFiatAmount, required this.outputs}); @@ -113,6 +117,7 @@ class ConfirmSendingAlertContent extends StatefulWidget { final String amountValue; final String fiatAmountValue; final String fee; + final String? feeRate; final String feeValue; final String feeFiatAmount; final List outputs; @@ -125,6 +130,7 @@ class ConfirmSendingAlertContent extends StatefulWidget { amountValue: amountValue, fiatAmountValue: fiatAmountValue, fee: fee, + feeRate: feeRate, feeValue: feeValue, feeFiatAmount: feeFiatAmount, outputs: outputs); @@ -138,6 +144,7 @@ class ConfirmSendingAlertContentState extends State required this.amountValue, required this.fiatAmountValue, required this.fee, + this.feeRate, required this.feeValue, required this.feeFiatAmount, required this.outputs}) @@ -153,6 +160,7 @@ class ConfirmSendingAlertContentState extends State final String amountValue; final String fiatAmountValue; final String fee; + final String? feeRate; final String feeValue; final String feeFiatAmount; final List outputs; @@ -183,7 +191,7 @@ class ConfirmSendingAlertContentState extends State return Stack(alignment: Alignment.center, clipBehavior: Clip.none, children: [ Container( - height: 200, + height: feeRate != null ? 250 : 200, child: SingleChildScrollView( controller: controller, child: Column( @@ -311,6 +319,36 @@ class ConfirmSendingAlertContentState extends State ) ], )), + if (feeRate != null && feeRate!.isNotEmpty) + Padding( + padding: EdgeInsets.only(top: 16), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.current.send_estimated_fee, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + fontFamily: 'Lato', + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ), + Text( + "$feeRate sat/byte", + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + fontFamily: 'Lato', + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ) + ], + )), Padding( padding: EdgeInsets.only(top: 16), child: Column( diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 3f5714be9..7c2bfedd0 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,16 +1,17 @@ -import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency.dart'; -import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; -import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -323,8 +324,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin GestureDetector( - onTap: () => _setTransactionPriority(context), + onTap: sendViewModel.hasFeesPriority + ? () => pickTransactionPriority(context) + : () {}, child: Container( padding: EdgeInsets.only(top: 24), child: Row( @@ -668,22 +670,41 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin _setTransactionPriority(BuildContext context) async { + Future pickTransactionPriority(BuildContext context) async { final items = priorityForWalletType(sendViewModel.walletType); final selectedItem = items.indexOf(sendViewModel.transactionPriority); + final customItemIndex = sendViewModel.getCustomPriorityIndex(items); + final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin; + double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null; await showPopUp( context: context, - builder: (_) => Picker( - items: items, - displayItem: sendViewModel.displayFeeRate, - selectedAtIndex: selectedItem, - title: S.of(context).please_select, - mainAxisAlignment: MainAxisAlignment.center, - onItemSelected: (TransactionPriority priority) => - sendViewModel.setTransactionPriority(priority), - ), + builder: (BuildContext context) { + int selectedIdx = selectedItem; + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Picker( + items: items, + displayItem: (TransactionPriority priority) => + sendViewModel.displayFeeRate(priority, customFeeRate?.round()), + selectedAtIndex: selectedIdx, + customItemIndex: customItemIndex, + title: S.of(context).please_select, + headerEnabled: !isBitcoinWallet, + closeOnItemSelected: !isBitcoinWallet, + mainAxisAlignment: MainAxisAlignment.center, + sliderValue: customFeeRate, + onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue), + onItemSelected: (TransactionPriority priority) { + sendViewModel.setTransactionPriority(priority); + setState(() => selectedIdx = items.indexOf(priority)); + }, + ); + }, + ); + }, ); + if (isBitcoinWallet) sendViewModel.customBitcoinFeeRate = customFeeRate!.round(); } void _presentPicker(BuildContext context) { diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index cc04944b3..8c4da4cc5 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -15,7 +15,6 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -43,7 +42,7 @@ class ConnectionSyncPage extends BasePage { title: S.current.rescan, handler: (context) => Navigator.of(context).pushNamed(Routes.rescan), ), - if (DeviceInfo.instance.isMobile) ...[ + if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[ Observer(builder: (context) { return SettingsPickerCell( title: S.current.background_sync_mode, diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index fcf683050..90139e8c4 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -2,11 +2,12 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -27,13 +28,23 @@ class OtherSettingsPage extends BasePage { child: Column( children: [ if (_otherSettingsViewModel.displayTransactionPriority) - SettingsPickerCell( - title: S.current.settings_fee_priority, - items: priorityForWalletType(_otherSettingsViewModel.walletType), - displayItem: _otherSettingsViewModel.getDisplayPriority, - selectedItem: _otherSettingsViewModel.transactionPriority, - onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, - ), + _otherSettingsViewModel.walletType == WalletType.bitcoin ? + SettingsPriorityPickerCell( + title: S.current.settings_fee_priority, + items: priorityForWalletType(_otherSettingsViewModel.walletType), + displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority, + selectedItem: _otherSettingsViewModel.transactionPriority, + customItemIndex: _otherSettingsViewModel.customPriorityItemIndex, + onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected, + customValue: _otherSettingsViewModel.customBitcoinFeeRate, + ) : + SettingsPickerCell( + title: S.current.settings_fee_priority, + items: priorityForWalletType(_otherSettingsViewModel.walletType), + displayItem: _otherSettingsViewModel.getDisplayPriority, + selectedItem: _otherSettingsViewModel.transactionPriority, + onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, + ), if (_otherSettingsViewModel.changeRepresentativeEnabled) SettingsCellWithArrow( title: S.current.change_rep, diff --git a/lib/src/screens/settings/widgets/setting_priority_picker_cell.dart b/lib/src/screens/settings/widgets/setting_priority_picker_cell.dart new file mode 100644 index 000000000..bba44606d --- /dev/null +++ b/lib/src/screens/settings/widgets/setting_priority_picker_cell.dart @@ -0,0 +1,78 @@ +import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; + +class SettingsPriorityPickerCell extends StandardListRow { + SettingsPriorityPickerCell( + {required String title, + required this.selectedItem, + required this.items, + this.displayItem, + this.images, + this.searchHintText, + this.isGridView = false, + this.matchingCriteria, + this.customValue, + this.customItemIndex, + this.onItemSelected}) + : super( + title: title, + isSelected: false, + onTap: (BuildContext context) async { + var selectedAtIndex = items.indexOf(selectedItem); + double sliderValue = customValue ?? 0.0; + + await showPopUp( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Picker( + items: items, + displayItem: (ItemType item) => displayItem!(item, sliderValue.round()), + selectedAtIndex: selectedAtIndex, + customItemIndex: customItemIndex, + headerEnabled: false, + closeOnItemSelected: false, + mainAxisAlignment: MainAxisAlignment.center, + sliderValue: sliderValue, + onSliderChanged: (double newValue) => + setState(() => sliderValue = newValue), + onItemSelected: (ItemType priority) { + setState(() => selectedAtIndex = items.indexOf(priority)); + onItemSelected?.call(priority, sliderValue); + }, + ); + }, + ); + }, + ); + onItemSelected?.call(items[selectedAtIndex], sliderValue); + }); + + final ItemType selectedItem; + final List items; + final void Function(ItemType item, double customValue)? onItemSelected; + final String Function(ItemType item, int value)? displayItem; + final List? images; + final String? searchHintText; + final bool isGridView; + final bool Function(ItemType, String)? matchingCriteria; + double? customValue; + int? customItemIndex; + + @override + Widget buildTrailing(BuildContext context) { + return Text( + displayItem?.call(selectedItem,customValue?.round() ?? 0) ?? selectedItem.toString(), + textAlign: TextAlign.right, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.detailsTitlesColor, + ), + ); + } +} diff --git a/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart new file mode 100644 index 000000000..8f722ee7e --- /dev/null +++ b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart @@ -0,0 +1,23 @@ +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; + +class StandardPickerListItem extends TransactionDetailsListItem { + StandardPickerListItem( + {required String title, + required String value, + required this.items, + required this.displayItem, + required this.onSliderChanged, + required this.onItemSelected, + required this.selectedIdx, + required this.customItemIndex, + required this.customValue}) + : super(title: title, value: value); + + final List items; + final String Function(T item, double sliderValue) displayItem; + final Function(double) onSliderChanged; + final Function(T) onItemSelected; + final int selectedIdx; + final int customItemIndex; + double customValue; +} diff --git a/lib/src/screens/transaction_details/rbf_details_page.dart b/lib/src/screens/transaction_details/rbf_details_page.dart new file mode 100644 index 000000000..875e0a4ef --- /dev/null +++ b/lib/src/screens/transaction_details/rbf_details_page.dart @@ -0,0 +1,199 @@ +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/send/widgets/confirm_sending_alert.dart'; +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.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/src/widgets/list_row.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/standard_expandable_list.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/src/widgets/standard_picker_list.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; +import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class RBFDetailsPage extends BasePage { + RBFDetailsPage({required this.transactionDetailsViewModel}); + + @override + String get title => S.current.bump_fee; + + final TransactionDetailsViewModel transactionDetailsViewModel; + + bool _effectsInstalled = false; + + @override + Widget body(BuildContext context) { + _setEffects(context); + + return Column( + children: [ + Expanded( + child: SectionStandardList( + sectionCount: 1, + itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length, + itemBuilder: (__, index) { + final item = transactionDetailsViewModel.RBFListItems[index]; + + if (item is StandartListItem) { + return GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: item.value)); + showBar(context, S.of(context).transaction_details_copied(item.title)); + }, + child: ListRow(title: '${item.title}:', value: item.value), + ); + } + + if (item is StandardExpandableListItem) { + return StandardExpandableList( + title: '${item.title}: ${item.expandableItems.length}', + expandableItems: item.expandableItems, + ); + } + + if (item is StandardPickerListItem) { + return StandardPickerList( + title: item.title, + value: item.value, + items: item.items, + displayItem: item.displayItem, + onSliderChanged: item.onSliderChanged, + onItemSelected: item.onItemSelected, + selectedIdx: item.selectedIdx, + customItemIndex: item.customItemIndex, + customValue: item.customValue, + ); + } + + if (item is TextFieldListItem) { + return TextFieldListRow( + title: item.title, + value: item.value, + onSubmitted: item.onSubmitted, + ); + } + + return Container(); + }), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Observer( + builder: (_) => LoadingPrimaryButton( + onPressed: () async { + transactionDetailsViewModel + .replaceByFee(transactionDetailsViewModel.newFee.toString()); + }, + text: S.of(context).send, + isLoading: + transactionDetailsViewModel.sendViewModel.state is IsExecutingState, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + ))), + ], + ); + } + + void _setEffects(BuildContext context) { + if (_effectsInstalled) { + return; + } + + reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) { + if (state is FailureState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext popupContext) { + return AlertWithOneAction( + alertTitle: S.of(popupContext).error, + alertContent: state.error, + buttonText: S.of(popupContext).ok, + buttonAction: () => Navigator.of(popupContext).pop()); + }); + }); + } + if (state is AwaitingConfirmationState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext popupContext) { + return AlertWithTwoActions( + alertTitle: state.title ?? '', + alertContent: state.message ?? '', + rightButtonText: S.of(context).ok, + leftButtonText: S.of(context).cancel, + actionRightButton: () { + state.onConfirm?.call(); + Navigator.of(popupContext).pop(); + }, + actionLeftButton: () { + state.onCancel?.call(); + Navigator.of(popupContext).pop(); + }); + }); + }); + } + + if (state is ExecutedSuccessfullyState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopUp( + context: context, + builder: (BuildContext popupContext) { + return ConfirmSendingAlert( + alertTitle: S.of(popupContext).confirm_sending, + amount: S.of(popupContext).send_amount, + amountValue: transactionDetailsViewModel + .sendViewModel.pendingTransaction!.amountFormatted, + fee: S.of(popupContext).send_fee, + feeValue: + transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted, + rightButtonText: S.of(popupContext).send, + leftButtonText: S.of(popupContext).cancel, + actionRightButton: () async { + Navigator.of(popupContext).pop(); + await transactionDetailsViewModel.sendViewModel.commitTransaction(); + // transactionStatePopup(); + }, + actionLeftButton: () => Navigator.of(popupContext).pop(), + feeFiatAmount: + transactionDetailsViewModel.pendingTransactionFeeFiatAmountFormatted, + fiatAmountValue: + transactionDetailsViewModel.pendingTransactionFiatAmountValueFormatted, + outputs: transactionDetailsViewModel.sendViewModel.outputs); + }); + }); + } + + if (state is TransactionCommitted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + showPopUp( + context: context, + builder: (BuildContext popupContext) { + return AlertWithOneAction( + alertTitle: S.of(popupContext).sending, + alertContent: S.of(popupContext).transaction_sent, + buttonText: S.of(popupContext).ok, + buttonAction: () => Navigator.of(popupContext).pop()); + }); + } + }); + } + }); + + _effectsInstalled = true; + } +} diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index a6f60a52d..7734f37ed 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -1,15 +1,18 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; +import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart'; +import 'package:cake_wallet/src/widgets/list_row.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/widgets/list_row.dart'; -import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; class TransactionDetailsPage extends BasePage { TransactionDetailsPage({required this.transactionDetailsViewModel}); @@ -21,41 +24,62 @@ class TransactionDetailsPage extends BasePage { @override Widget body(BuildContext context) { - return SectionStandardList( - sectionCount: 1, - itemCounter: (int _) => transactionDetailsViewModel.items.length, - itemBuilder: (__, index) { - final item = transactionDetailsViewModel.items[index]; + return Column( + children: [ + Expanded( + child: SectionStandardList( + sectionCount: 1, + itemCounter: (int _) => transactionDetailsViewModel.items.length, + itemBuilder: (__, index) { + final item = transactionDetailsViewModel.items[index]; - if (item is StandartListItem) { - return GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: item.value)); - showBar(context, - S.of(context).transaction_details_copied(item.title)); - }, - child: - ListRow(title: '${item.title}:', value: item.value), - ); - } + if (item is StandartListItem) { + return GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: item.value)); + showBar(context, S.of(context).transaction_details_copied(item.title)); + }, + child: ListRow(title: '${item.title}:', value: item.value), + ); + } - if (item is BlockExplorerListItem) { - return GestureDetector( - onTap: item.onTap, - child: - ListRow(title: '${item.title}:', value: item.value), - ); - } + if (item is BlockExplorerListItem) { + return GestureDetector( + onTap: item.onTap, + child: ListRow(title: '${item.title}:', value: item.value), + ); + } - if (item is TextFieldListItem) { - return TextFieldListRow( - title: item.title, - value: item.value, - onSubmitted: item.onSubmitted, - ); - } + if (item is TextFieldListItem) { + return TextFieldListRow( + title: item.title, + value: item.value, + onSubmitted: item.onSubmitted, + ); + } - return Container(); - }); + return Container(); + }), + ), + Observer( + builder: (_) { + if (transactionDetailsViewModel.canReplaceByFee) { + return Padding( + padding: const EdgeInsets.all(24), + child: SelectButton( + text: S.of(context).bump_fee, + onTap: () async { + Navigator.of(context).pushNamed(Routes.bumpFeePage, + arguments: transactionDetailsViewModel.transactionInfo); + }, + ), + ); + } + + return const SizedBox(); + }, + ), + ], + ); } } diff --git a/lib/src/screens/transaction_details/transaction_expandable_list_item.dart b/lib/src/screens/transaction_details/transaction_expandable_list_item.dart new file mode 100644 index 000000000..e87405de3 --- /dev/null +++ b/lib/src/screens/transaction_details/transaction_expandable_list_item.dart @@ -0,0 +1,7 @@ +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; + +class StandardExpandableListItem extends TransactionDetailsListItem { + StandardExpandableListItem({required String title, required this.expandableItems}) + : super(title: title, value: ''); + final List expandableItems; +} diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index 36cbda641..70ae7ce3f 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -46,9 +46,6 @@ class UnspentCoinsListFormState extends State { itemBuilder: (_, int index) { return Observer(builder: (_) { final item = unspentCoinsListViewModel.items[index]; - final address = unspentCoinsListViewModel.wallet.type == WalletType.bitcoinCash - ? bitcoinCash!.getCashAddrFormat(item.address) - : item.address; return GestureDetector( onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsDetails, @@ -56,7 +53,7 @@ class UnspentCoinsListFormState extends State { child: UnspentCoinsListItem( note: item.note, amount: item.amount, - address: address, + address: item.address, isSending: item.isSending, isFrozen: item.isFrozen, isChange: item.isChange, diff --git a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart index d629e9454..e16026073 100644 --- a/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart +++ b/lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart @@ -27,10 +27,12 @@ class UnspentCoinsListItem extends StatelessWidget { Widget build(BuildContext context) { final unselectedItemColor = Theme.of(context).cardColor; final selectedItemColor = Theme.of(context).primaryColor; - final itemColor = isSending ? selectedItemColor : unselectedItemColor; - - final amountColor = - isSending ? Colors.white : Theme.of(context).extension()!.buttonTextColor; + final itemColor = isSending + ? selectedItemColor + : unselectedItemColor; + final amountColor = isSending + ? Colors.white + : Theme.of(context).extension()!.buttonTextColor; final addressColor = isSending ? Colors.white.withOpacity(0.5) : Theme.of(context).extension()!.buttonSecondaryTextColor; @@ -85,7 +87,7 @@ class UnspentCoinsListItem extends StatelessWidget { child: Text( S.of(context).frozen, style: TextStyle( - color: amountColor, fontSize: 7, fontWeight: FontWeight.w600), + color: Colors.black, fontSize: 7, fontWeight: FontWeight.w600), )), ], ), diff --git a/lib/src/widgets/cake_image_widget.dart b/lib/src/widgets/cake_image_widget.dart index 14c62ad34..ad02c48dd 100644 --- a/lib/src/widgets/cake_image_widget.dart +++ b/lib/src/widgets/cake_image_widget.dart @@ -18,7 +18,7 @@ class CakeImageWidget extends StatelessWidget { @override Widget build(BuildContext context) { try { - if (imageUrl == null) return _displayOnError!; + if (imageUrl == null || imageUrl!.isEmpty) return _displayOnError!; if (imageUrl!.contains('assets/images')) { return Image.asset( diff --git a/lib/src/widgets/checkbox_widget.dart b/lib/src/widgets/checkbox_widget.dart index 1ea3ee698..a3c78d0cf 100644 --- a/lib/src/widgets/checkbox_widget.dart +++ b/lib/src/widgets/checkbox_widget.dart @@ -31,7 +31,6 @@ class CheckboxWidgetState extends State { }, child: Row( mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 24.0, diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index 01b869b1b..d87b5721e 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -24,6 +24,13 @@ class Picker extends StatefulWidget { this.isGridView = false, this.isSeparated = true, this.hintText, + this.headerEnabled = true, + this.closeOnItemSelected = true, + this.sliderValue, + this.customItemIndex, + this.isWrapped = true, + this.borderColor, + this.onSliderChanged, this.matchingCriteria, }) : assert(hintText == null || matchingCriteria != @@ -40,6 +47,13 @@ class Picker extends StatefulWidget { final bool isGridView; final bool isSeparated; final String? hintText; + final bool headerEnabled; + final bool closeOnItemSelected; + final double? sliderValue; + final int? customItemIndex; + final bool isWrapped; + final Color? borderColor; + final Function(double)? onSliderChanged; final bool Function(Item, String)? matchingCriteria; @override @@ -124,8 +138,7 @@ class _PickerState extends State> { containerHeight = height * 0.75; } - return PickerWrapperWidget( - hasTitle: widget.title?.isNotEmpty ?? false, + final content = Column ( children: [ if (widget.title?.isNotEmpty ?? false) Container( @@ -144,61 +157,71 @@ class _PickerState extends State> { ), Padding( padding: EdgeInsets.symmetric(horizontal: padding), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(30)), - child: Container( - color: Theme.of(context).dialogTheme.backgroundColor, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: containerHeight, - maxWidth: ResponsiveLayoutUtilBase.kPopupWidth, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.hintText != null) - Padding( - padding: const EdgeInsets.all(16), - child: SearchBarWidget(searchController: searchController), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: widget.borderColor ?? Colors.transparent, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(30)), + child: Container( + color: Theme.of(context).dialogTheme.backgroundColor, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: containerHeight, + maxWidth: ResponsiveLayoutUtilBase.kPopupWidth, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.hintText != null) + Padding( + padding: const EdgeInsets.all(16), + child: SearchBarWidget( + searchController: searchController, hintText: widget.hintText), + ), + Divider( + color: Theme.of(context).extension()!.dividerColor, + height: 1, ), - Divider( - color: Theme.of(context).extension()!.dividerColor, - height: 1, - ), - if (widget.selectedAtIndex != -1) buildSelectedItem(widget.selectedAtIndex), - Flexible( - child: Stack( - alignment: Alignment.center, - children: [ - filteredItems.length > 3 - ? Scrollbar( - controller: controller, - child: itemsList(), - ) - : itemsList(), - (widget.description?.isNotEmpty ?? false) - ? Positioned( - bottom: padding, - left: padding, - right: padding, - child: Text( - widget.description!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: 'Lato', - decoration: TextDecoration.none, - color: - Theme.of(context).extension()!.titleColor, + if (widget.selectedAtIndex != -1 && widget.headerEnabled) + buildSelectedItem(widget.selectedAtIndex), + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + filteredItems.length > 3 + ? Scrollbar( + controller: controller, + child: itemsList(), + ) + : itemsList(), + (widget.description?.isNotEmpty ?? false) + ? Positioned( + bottom: padding, + left: padding, + right: padding, + child: Text( + widget.description!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + fontFamily: 'Lato', + decoration: TextDecoration.none, + color: + Theme.of(context).extension()!.titleColor, + ), ), - ), - ) - : Offstage(), - ], + ) + : Offstage(), + ], + ), ), - ), - ], + ], + ), ), ), ), @@ -206,9 +229,23 @@ class _PickerState extends State> { ) ], ); + + if (widget.isWrapped) { + return PickerWrapperWidget( + hasTitle: widget.title?.isNotEmpty ?? false, + children: [content], + ); + } else { + return content; + } } Widget itemsList() { + final itemCount = !widget.headerEnabled + ? items.length + : filteredItems.isEmpty + ? 0 + : filteredItems.length; return Container( color: Theme.of(context).extension()!.dividerColor, child: widget.isGridView @@ -216,13 +253,16 @@ class _PickerState extends State> { padding: EdgeInsets.zero, controller: controller, shrinkWrap: true, - itemCount: filteredItems.isEmpty ? 0 : filteredItems.length, + itemCount: itemCount, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 2, childAspectRatio: 3, ), - itemBuilder: (context, index) => buildItem(index), + itemBuilder: (context, index) => + !widget.headerEnabled && widget.selectedAtIndex == index + ? buildSelectedItem(index) + : buildItem(index), ) : ListView.separated( padding: EdgeInsets.zero, @@ -234,83 +274,97 @@ class _PickerState extends State> { height: 1, ) : const SizedBox(), - itemCount: filteredItems.isEmpty ? 0 : filteredItems.length, - itemBuilder: (context, index) => buildItem(index), + itemCount: itemCount, + itemBuilder: (context, index) => + !widget.headerEnabled && widget.selectedAtIndex == index + ? buildSelectedItem(index) + : buildItem(index), ), ); } Widget buildItem(int index) { - final item = filteredItems[index]; + final item = widget.headerEnabled ? filteredItems[index] : items[index]; final tag = item is Currency ? item.tag : null; final icon = _getItemIcon(item); final image = images.isNotEmpty ? filteredImages[index] : icon; + final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex; + + final itemContent = Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: widget.mainAxisAlignment, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image ?? Offstage(), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: image != null ? 12 : 0), + child: Row( + children: [ + Flexible( + child: Text( + widget.displayItem?.call(item) ?? item.toString(), + softWrap: true, + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ), + ), + if (tag != null) + Align( + alignment: Alignment.topCenter, + child: Container( + width: 35.0, + height: 18.0, + child: Center( + child: Text( + tag, + style: TextStyle( + fontSize: 7.0, + fontFamily: 'Lato', + color: Theme.of(context).extension()!.thumbColor, + ), + ), + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + //border: Border.all(color: ), + color: Theme.of(context).extension()!.trackColor, + ), + ), + ), + ], + ), + ), + ), + ], + ); + return GestureDetector( onTap: () { - Navigator.of(context).pop(); + if (widget.closeOnItemSelected) Navigator.of(context).pop(); onItemSelected(item!); }, child: Container( - height: 55, + height: isCustomItem ? 95 : 55, color: Theme.of(context).dialogTheme.backgroundColor, padding: EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: widget.mainAxisAlignment, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image ?? Offstage(), - Expanded( - child: Padding( - padding: EdgeInsets.only(left: image != null ? 12 : 0), - child: Row( - children: [ - Flexible( - child: Text( - widget.displayItem?.call(item) ?? item.toString(), - softWrap: true, - style: TextStyle( - fontSize: 14, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - ), - ), - ), - if (tag != null) - Align( - alignment: Alignment.topCenter, - child: Container( - width: 35.0, - height: 18.0, - child: Center( - child: Text( - tag, - style: TextStyle( - fontSize: 7.0, - fontFamily: 'Lato', - color: - Theme.of(context).extension()!.thumbColor, - ), - ), - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6.0), - //border: Border.all(color: ), - color: Theme.of(context).extension()!.trackColor, - ), - ), - ), - ], - ), - ), - ), - ], - ), + child: isCustomItem + ? Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + itemContent, + buildSlider(index: index, isActivated: widget.selectedAtIndex == index) + ], + ) + : itemContent, ), ); } @@ -323,69 +377,80 @@ class _PickerState extends State> { final image = images.isNotEmpty ? images[index] : icon; - return GestureDetector( - onTap: () { - Navigator.of(context).pop(); - }, - child: Container( - height: 55, - color: Theme.of(context).dialogTheme.backgroundColor, - padding: EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: widget.mainAxisAlignment, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image ?? Offstage(), - Expanded( - child: Padding( - padding: EdgeInsets.only(left: image != null ? 12 : 0), - child: Row( - children: [ - Flexible( - child: Text( - widget.displayItem?.call(item) ?? item.toString(), - softWrap: true, - style: TextStyle( - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.w700, - color: Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, + final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex; + + final itemContent = Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: widget.mainAxisAlignment, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image ?? Offstage(), + Expanded( + child: Padding( + padding: EdgeInsets.only(left: image != null ? 12 : 0), + child: Row( + children: [ + Flexible( + child: Text( + widget.displayItem?.call(item) ?? item.toString(), + softWrap: true, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w700, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ), + ), + if (tag != null) + Align( + alignment: Alignment.topCenter, + child: Container( + width: 35.0, + height: 18.0, + child: Center( + child: Text( + tag, + style: TextStyle( + fontSize: 7.0, + fontFamily: 'Lato', + color: Theme.of(context).extension()!.thumbColor, + ), ), ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + //border: Border.all(color: ), + color: Theme.of(context).extension()!.trackColor, + ), ), - if (tag != null) - Align( - alignment: Alignment.topCenter, - child: Container( - width: 35.0, - height: 18.0, - child: Center( - child: Text( - tag, - style: TextStyle( - fontSize: 7.0, - fontFamily: 'Lato', - color: - Theme.of(context).extension()!.thumbColor, - ), - ), - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6.0), - //border: Border.all(color: ), - color: Theme.of(context).extension()!.trackColor, - ), - ), - ), - ], - ), - ), + ), + ], ), - Icon(Icons.check_circle, color: Theme.of(context).primaryColor), - ], + ), ), + Icon(Icons.check_circle, color: Theme.of(context).primaryColor), + ], + ); + + return GestureDetector( + onTap: () { + if (widget.closeOnItemSelected) Navigator.of(context).pop(); + }, + child: Container( + height: isCustomItem ? 95 : 55, + color: Theme.of(context).dialogTheme.backgroundColor, + padding: EdgeInsets.symmetric(horizontal: 24), + child: isCustomItem + ? Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + itemContent, + buildSlider(index: index, isActivated: widget.selectedAtIndex == index) + ], + ) + : itemContent, ), ); } @@ -418,4 +483,20 @@ class _PickerState extends State> { return null; } + + Widget buildSlider({required int index, required bool isActivated}) { + return Row( + children: [ + Expanded( + child: Slider( + value: widget.sliderValue ?? 1, + onChanged: isActivated ? widget.onSliderChanged : null, + min: 1, + max: 100, + divisions: 100, + ), + ), + ], + ); + } } diff --git a/lib/src/widgets/search_bar_widget.dart b/lib/src/widgets/search_bar_widget.dart index dc604934f..45155b380 100644 --- a/lib/src/widgets/search_bar_widget.dart +++ b/lib/src/widgets/search_bar_widget.dart @@ -19,7 +19,7 @@ class SearchBarWidget extends StatelessWidget { controller: searchController, style: TextStyle(color: Theme.of(context).extension()!.searchHintColor), decoration: InputDecoration( - hintText: hintText ?? S.of(context).search_currency, + hintText: hintText ?? S.of(context).search, hintStyle: TextStyle(color: Theme.of(context).extension()!.searchHintColor), prefixIcon: Image.asset("assets/images/search_icon.png", color: Theme.of(context).extension()!.searchIconColor), diff --git a/lib/src/widgets/services_updates_widget.dart b/lib/src/widgets/services_updates_widget.dart index 65dbe5e40..d094caf7f 100644 --- a/lib/src/widgets/services_updates_widget.dart +++ b/lib/src/widgets/services_updates_widget.dart @@ -111,7 +111,7 @@ class _ServicesUpdatesWidgetState extends State { color: Theme.of(context).extension()!.pageTitleTextColor, width: 30, ), - if (state.hasData && state.data!.hasUpdates) + if (state.hasData && state.data!.hasUpdates && !wasOpened) Container( height: 7, width: 7, diff --git a/lib/src/widgets/standard_expandable_list.dart b/lib/src/widgets/standard_expandable_list.dart new file mode 100644 index 000000000..d1bcae646 --- /dev/null +++ b/lib/src/widgets/standard_expandable_list.dart @@ -0,0 +1,58 @@ +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; +import 'package:flutter/material.dart'; + +class StandardExpandableList extends StatelessWidget { + StandardExpandableList({ + required this.title, + required this.expandableItems, + this.decoration, + }); + + final String title; + final List expandableItems; + final Decoration? decoration; + + @override + Widget build(BuildContext context) { + return Container( + decoration: decoration ?? + BoxDecoration( + color: Theme.of(context).colorScheme.background, + ), + child: Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + iconColor: Theme.of(context).extension()!.detailsTitlesColor, + collapsedIconColor: + Theme.of(context).extension()!.detailsTitlesColor, + title: Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.detailsTitlesColor, + ), + textAlign: TextAlign.left, + ), + children: expandableItems.map((item) { + return Padding( + padding: const EdgeInsets.only(left: 16.0, bottom: 8.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + item.toString(), + maxLines: 1, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.titleColor), + ), + ), + ); + }).toList(), + ), + ), + ); + } +} diff --git a/lib/src/widgets/standard_picker_list.dart b/lib/src/widgets/standard_picker_list.dart new file mode 100644 index 000000000..eb1d16900 --- /dev/null +++ b/lib/src/widgets/standard_picker_list.dart @@ -0,0 +1,81 @@ +import 'package:cake_wallet/src/widgets/list_row.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/themes/extensions/picker_theme.dart'; +import 'package:flutter/material.dart'; + +class StandardPickerList extends StatefulWidget { + StandardPickerList({ + Key? key, + required this.title, + required this.value, + required this.items, + required this.displayItem, + required this.onSliderChanged, + required this.onItemSelected, + required this.selectedIdx, + required this.customItemIndex, + required this.customValue, + }) : super(key: key); + + final String title; + final List items; + final int customItemIndex; + final String Function(T item, double sliderValue) displayItem; + final Function(double) onSliderChanged; + final Function(T) onItemSelected; + final String value; + final int selectedIdx; + final double customValue; + + @override + _StandardPickerListState createState() => _StandardPickerListState(); +} + +class _StandardPickerListState extends State> { + late String value; + late int selectedIdx; + late double customValue; + + @override + void initState() { + super.initState(); + + value = widget.value; + selectedIdx = widget.selectedIdx; + customValue = widget.customValue; + } + + @override + Widget build(BuildContext context) { + String adaptedDisplayItem(T item) => widget.displayItem(item, customValue); + + return Column( + children: [ + ListRow(title: '${widget.title}:', value: value), + Padding( + padding: const EdgeInsets.only(left: 24, right: 24, top: 0, bottom: 24), + child: Picker( + items: widget.items, + displayItem: adaptedDisplayItem, + selectedAtIndex: selectedIdx, + customItemIndex: widget.customItemIndex, + headerEnabled: false, + closeOnItemSelected: false, + mainAxisAlignment: MainAxisAlignment.center, + sliderValue: customValue, + isWrapped: false, + borderColor: Theme.of(context).extension()!.dividerColor, + onSliderChanged: (newValue) { + setState(() => customValue = newValue); + value = widget.onSliderChanged(newValue).toString(); + }, + onItemSelected: (T item) { + setState(() => selectedIdx = widget.items.indexOf(item)); + value = widget.onItemSelected(item).toString(); + }, + ), + ), + ], + ); + } +} diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index 4e901aa5e..c05839578 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -3,18 +3,20 @@ import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:mobx/mobx.dart'; -part'trade_filter_store.g.dart'; +part 'trade_filter_store.g.dart'; class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore; abstract class TradeFilterStoreBase with Store { - TradeFilterStoreBase() : displayXMRTO = true, + TradeFilterStoreBase() + : displayXMRTO = true, displayChangeNow = true, displaySideShift = true, displayMorphToken = true, displaySimpleSwap = true, displayTrocador = true, - displayExolix = true; + displayExolix = true, + displayThorChain = true; @observable bool displayXMRTO; @@ -37,8 +39,17 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayExolix; + @observable + bool displayThorChain; + @computed - bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix; + bool get displayAllTrades => + displayChangeNow && + displaySideShift && + displaySimpleSwap && + displayTrocador && + displayExolix && + displayThorChain; @action void toggleDisplayExchange(ExchangeProviderDescription provider) { @@ -64,6 +75,9 @@ abstract class TradeFilterStoreBase with Store { case ExchangeProviderDescription.exolix: displayExolix = !displayExolix; break; + case ExchangeProviderDescription.thorChain: + displayThorChain = !displayThorChain; + break; case ExchangeProviderDescription.all: if (displayAllTrades) { displayChangeNow = false; @@ -73,6 +87,7 @@ abstract class TradeFilterStoreBase with Store { displaySimpleSwap = false; displayTrocador = false; displayExolix = false; + displayThorChain = false; } else { displayChangeNow = true; displaySideShift = true; @@ -81,6 +96,7 @@ abstract class TradeFilterStoreBase with Store { displaySimpleSwap = true; displayTrocador = true; displayExolix = true; + displayThorChain = true; } break; } @@ -96,16 +112,13 @@ abstract class TradeFilterStoreBase with Store { ? _trades .where((item) => (displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) || - (displaySideShift && - item.trade.provider == ExchangeProviderDescription.sideShift) || - (displayChangeNow && - item.trade.provider == ExchangeProviderDescription.changeNow) || - (displayMorphToken && - item.trade.provider == ExchangeProviderDescription.morphToken) || - (displaySimpleSwap && - item.trade.provider == ExchangeProviderDescription.simpleSwap) || + (displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) || + (displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) || + (displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) || + (displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) || (displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) || - (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix)) + (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) || + (displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain)) .toList() : _trades; } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 9997e2675..df2b438b4 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -105,6 +105,7 @@ abstract class SettingsStoreBase with Store { required this.lookupsUnstoppableDomains, required this.lookupsOpenAlias, required this.lookupsENS, + required this.customBitcoinFeeRate, TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialHavenTransactionPriority, @@ -504,6 +505,11 @@ abstract class SettingsStoreBase with Store { (PinCodeRequiredDuration pinCodeInterval) => secureStorage.write( key: SecureKey.pinTimeOutDuration, value: pinCodeInterval.value.toString())); + reaction( + (_) => customBitcoinFeeRate, + (int customBitcoinFeeRate) => + _sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, customBitcoinFeeRate)); + this.nodes.observe((change) { if (change.newValue != null && change.key != null) { _saveCurrentNode(change.newValue!, change.key!); @@ -690,6 +696,9 @@ abstract class SettingsStoreBase with Store { String deviceName; + @observable + int customBitcoinFeeRate; + final FlutterSecureStorage _secureStorage; final SharedPreferences _sharedPreferences; final BackgroundTasks _backgroundTasks; @@ -834,6 +843,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; + final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; // If no value if (pinLength == null || pinLength == 0) { @@ -1068,6 +1078,7 @@ abstract class SettingsStoreBase with Store { lookupsUnstoppableDomains: lookupsUnstoppableDomains, lookupsOpenAlias: lookupsOpenAlias, lookupsENS: lookupsENS, + customBitcoinFeeRate: customBitcoinFeeRate, initialMoneroTransactionPriority: moneroTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialHavenTransactionPriority: havenTransactionPriority, @@ -1159,7 +1170,8 @@ abstract class SettingsStoreBase with Store { isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy; disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell; - disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin; + disableBulletin = + sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin; walletListOrder = WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0]; walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true; @@ -1200,7 +1212,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; - + customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final bitcoinElectrumServerId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index 2a29bd949..f71e6b489 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -2,4 +2,5 @@ class FeatureFlag { static const bool isCakePayEnabled = false; static const bool isExolixEnabled = true; static const bool isInAppTorEnabled = false; + static const bool isBackgroundSyncEnabled = false; } \ No newline at end of file diff --git a/lib/view_model/anonpay_details_view_model.dart b/lib/view_model/anonpay_details_view_model.dart index 6c528f495..fe4b9da3d 100644 --- a/lib/view_model/anonpay_details_view_model.dart +++ b/lib/view_model/anonpay_details_view_model.dart @@ -71,7 +71,7 @@ abstract class AnonpayDetailsViewModelBase with Store { ]); items.add(TrackTradeListItem( - title: 'Track', + title: S.current.track, value: invoiceDetail.clearnetStatusUrl, onTap: () => launchUrlString(invoiceDetail.clearnetStatusUrl))); } diff --git a/lib/view_model/buy/buy_view_model.dart b/lib/view_model/buy/buy_view_model.dart index d73396e1b..7c2591cbb 100644 --- a/lib/view_model/buy/buy_view_model.dart +++ b/lib/view_model/buy/buy_view_model.dart @@ -93,18 +93,6 @@ abstract class BuyViewModelBase with Store { _providerList.add(WyreBuyProvider(wallet: wallet)); } - var isMoonPayEnabled = false; - try { - isMoonPayEnabled = await MoonPayBuyProvider.onEnabled(); - } catch (e) { - isMoonPayEnabled = false; - print(e.toString()); - } - - if (isMoonPayEnabled) { - _providerList.add(MoonPayBuyProvider(wallet: wallet)); - } - items = _providerList.map((provider) => BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel)) .toList(); diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index b53b67c43..6c3169be1 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -46,6 +46,8 @@ abstract class ContactListViewModelBase with Store { name, walletTypeToCryptoCurrency(info.type), )); + // Only one contact address per wallet + return; }); } else if (info.address != null) { walletContacts.add(WalletContact( diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 9ee0647fc..66d179523 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -120,6 +120,11 @@ abstract class DashboardViewModelBase with Store { caption: ExchangeProviderDescription.exolix.title, onChanged: () => tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)), + FilterItem( + value: () => tradeFilterStore.displayThorChain, + caption: ExchangeProviderDescription.thorChain.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)), ] }, subname = '', diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 6d31a5af8..e60a37ccf 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -44,17 +44,39 @@ abstract class HomeSettingsViewModelBase with Store { @action void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value; - Future addToken(CryptoCurrency token) async { + Future addToken({ + required String contractAddress, + required CryptoCurrency token, + }) async { if (_balanceViewModel.wallet.type == WalletType.ethereum) { - await ethereum!.addErc20Token(_balanceViewModel.wallet, token); + final erc20token = Erc20Token( + name: token.name, + symbol: token.title, + decimal: token.decimals, + contractAddress: contractAddress, + iconPath: token.iconPath, + ); + + await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token); } if (_balanceViewModel.wallet.type == WalletType.polygon) { - await polygon!.addErc20Token(_balanceViewModel.wallet, token); + final polygonToken = Erc20Token( + name: token.name, + symbol: token.title, + decimal: token.decimals, + contractAddress: contractAddress, + iconPath: token.iconPath, + ); + await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken); } if (_balanceViewModel.wallet.type == WalletType.solana) { - await solana!.addSPLToken(_balanceViewModel.wallet, token); + await solana!.addSPLToken( + _balanceViewModel.wallet, + token, + contractAddress, + ); } _updateTokensList(); @@ -117,7 +139,8 @@ abstract class HomeSettingsViewModelBase with Store { } if (_balanceViewModel.wallet.type == WalletType.solana) { - solana!.addSPLToken(_balanceViewModel.wallet, token); + final address = solana!.getTokenAddress(token); + solana!.addSPLToken(_balanceViewModel.wallet, token, address); } _refreshTokensList(); diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 0d40ae240..9bd9ef913 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -47,6 +48,9 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.exolix: _provider = ExolixExchangeProvider(); break; + case ExchangeProviderDescription.thorChain: + _provider = ThorChainExchangeProvider(tradesStore: trades); + break; } _updateItems(); @@ -100,13 +104,21 @@ abstract class ExchangeTradeViewModelBase with Store { final output = sendViewModel.outputs.first; output.address = trade.inputAddress ?? ''; output.setCryptoAmount(trade.amount); + if (_provider is ThorChainExchangeProvider) output.memo = trade.memo; + if (trade.isSendAll == true) output.sendAll = true; sendViewModel.selectedCryptoCurrency = trade.from; - await sendViewModel.createTransaction(); + final pendingTransaction = await sendViewModel.createTransaction(provider: _provider); + if (_provider is ThorChainExchangeProvider) { + trade.id = pendingTransaction?.id ?? ''; + trades.add(trade); + } } @action Future _updateTrade() async { try { + final agreedAmount = tradesStore.trade!.amount; + final isSendAll = tradesStore.trade!.isSendAll; final updatedTrade = await _provider!.findTradeById(id: trade.id); if (updatedTrade.createdAt == null && trade.createdAt != null) @@ -115,6 +127,8 @@ abstract class ExchangeTradeViewModelBase with Store { if (updatedTrade.amount.isEmpty) updatedTrade.amount = trade.amount; trade = updatedTrade; + trade.amount = agreedAmount; + trade.isSendAll = isSendAll; _updateItems(); } catch (e) { @@ -127,8 +141,10 @@ abstract class ExchangeTradeViewModelBase with Store { tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : ''; items.clear(); - items.add(ExchangeTradeItem( - title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true)); + + if (trade.provider != ExchangeProviderDescription.thorChain) + items.add(ExchangeTradeItem( + title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true)); if (trade.extraId != null) { final title = trade.from == CryptoCurrency.xrp diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 1f86847f4..eba347ac4 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert'; +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; @@ -9,6 +10,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/exchange/exchange_trade_state.dart'; import 'package:cake_wallet/exchange/limits.dart'; @@ -18,6 +20,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; @@ -96,7 +99,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with /// if the provider is not in the user settings (user's first time or newly added provider) /// then use its default value decided by us - selectedProviders = ObservableList.of(providersForCurrentPair() + selectedProviders = ObservableList.of(providerList .where((element) => exchangeProvidersSelection[element.title] == null ? element.isEnabled : (exchangeProvidersSelection[element.title] as bool)) @@ -148,6 +151,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SimpleSwapExchangeProvider(), TrocadorExchangeProvider( useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates), + ThorChainExchangeProvider(tradesStore: trades), if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), ]; @@ -466,6 +470,18 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with @action Future createTrade() async { + if (isSendAllEnabled) { + await calculateDepositAllAmount(); + final amount = double.tryParse(depositAmount); + + if (limits.min != null && amount != null && amount < limits.min!) { + tradeState = TradeIsCreatedFailure( + title: S.current.trade_not_created, + error: S.current.amount_is_below_minimum_limit(limits.min!.toString())); + return; + } + } + try { for (var provider in _sortedAvailableProviders.values) { if (!(await provider.checkIsAvailable())) continue; @@ -492,12 +508,23 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with else { try { tradeState = TradeIsCreating(); - final trade = - await provider.createTrade(request: request, isFixedRateMode: isFixedRateMode); + final trade = await provider.createTrade( + request: request, + isFixedRateMode: isFixedRateMode, + isSendAll: isSendAllEnabled, + ); trade.walletId = wallet.id; trade.fromWalletAddress = wallet.walletAddresses.address; + + if (!isCanCreateTrade(trade)) { + tradeState = TradeIsCreatedFailure( + title: S.current.trade_not_created, + error: S.current.thorchain_taproot_address_not_supported); + return; + } + tradesStore.setTrade(trade); - await trades.add(trade); + if (trade.provider != ExchangeProviderDescription.thorChain) await trades.add(trade); tradeState = TradeIsCreatedSuccessfully(trade: trade); /// return after the first successful trade @@ -539,25 +566,24 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with @action void enableSendAllAmount() { isSendAllEnabled = true; + isFixedRateMode = false; calculateDepositAllAmount(); } + @action + void enableFixedRateMode() { + isSendAllEnabled = false; + isFixedRateMode = true; + } + @action Future calculateDepositAllAmount() async { - if (wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash) { - final availableBalance = wallet.balance[wallet.currency]!.available; - final priority = _settingsStore.priority[wallet.type]!; - final fee = wallet.calculateEstimatedFee(priority, null); - - if (availableBalance < fee || availableBalance == 0) return; - - final amount = availableBalance - fee; - changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount)); - } else if (wallet.type == WalletType.bitcoin) { + if (wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoin || + wallet.type == WalletType.bitcoinCash) { final priority = _settingsStore.priority[wallet.type]!; - final amount = await bitcoin!.estimateFakeSendAllTxAmount( - wallet, bitcoin!.deserializeBitcoinTransactionPriority(priority.raw)); + final amount = await bitcoin!.estimateFakeSendAllTxAmount(wallet, priority); changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount)); } @@ -749,4 +775,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with int get depositMaxDigits => depositCurrency.decimals; int get receiveMaxDigits => receiveCurrency.decimals; + + bool isCanCreateTrade(Trade trade) { + if (trade.provider == ExchangeProviderDescription.thorChain) { + final payoutAddress = trade.payoutAddress ?? ''; + final fromWalletAddress = trade.fromWalletAddress ?? ''; + final tapRootPattern = RegExp(P2trAddress.regex.pattern); + + if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) { + return false; + } + } + return true; + } } diff --git a/lib/view_model/order_details_view_model.dart b/lib/view_model/order_details_view_model.dart index 9b00bbb46..412f1b962 100644 --- a/lib/view_model/order_details_view_model.dart +++ b/lib/view_model/order_details_view_model.dart @@ -27,7 +27,7 @@ abstract class OrderDetailsViewModelBase with Store { _provider = WyreBuyProvider(wallet: wallet); break; case BuyProviderDescription.moonPay: - _provider = MoonPayBuyProvider(wallet: wallet); + // _provider = MoonPayProvider(wallet: wallet);// TODO: CW-521 break; } } @@ -50,9 +50,9 @@ abstract class OrderDetailsViewModelBase with Store { @action Future _updateOrder() async { try { - if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { - final updatedOrder = _provider is MoonPayBuyProvider - ? await (_provider as MoonPayBuyProvider).findOrderById(order.id) + if (_provider != null && (_provider is MoonPayProvider || _provider is WyreBuyProvider)) { + final updatedOrder = _provider is MoonPayProvider + ? await (_provider as MoonPayProvider).findOrderById(order.id) : await (_provider as WyreBuyProvider).findOrderById(order.id); updatedOrder.from = order.from; updatedOrder.to = order.to; @@ -89,17 +89,17 @@ abstract class OrderDetailsViewModelBase with Store { value: order.provider.title) ); - if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + if (_provider != null && (_provider is MoonPayProvider || _provider is WyreBuyProvider)) { - final trackUrl = _provider is MoonPayBuyProvider - ? (_provider as MoonPayBuyProvider).trackUrl + final trackUrl = _provider is MoonPayProvider + ? (_provider as MoonPayProvider).trackUrl : (_provider as WyreBuyProvider).trackUrl; if (trackUrl.isNotEmpty ?? false) { final buildURL = trackUrl + '${order.transferId}'; items.add( TrackTradeListItem( - title: 'Track', + title: S.current.track, value: buildURL, onTap: () { try { diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index cc39aca8b..6bb3fbb31 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; @@ -66,6 +67,8 @@ abstract class OutputBase with Store { @observable String extractedAddress; + String? memo; + @computed bool get isParsedAddress => parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty; @@ -114,11 +117,23 @@ abstract class OutputBase with Store { @computed double get estimatedFee { try { - final fee = _wallet.calculateEstimatedFee( + if (_wallet.type == WalletType.solana) { + return solana!.getEstimateFees(_wallet) ?? 0.0; + } + + int? fee = _wallet.calculateEstimatedFee( _settingsStore.priority[_wallet.type]!, formattedCryptoAmount); - if (_wallet.type == WalletType.bitcoin || - _wallet.type == WalletType.litecoin || + if (_wallet.type == WalletType.bitcoin) { + if (_settingsStore.priority[_wallet.type] == bitcoin!.getBitcoinTransactionPriorityCustom()) { + fee = bitcoin!.getFeeAmountWithFeeRate( + _settingsStore.customBitcoinFeeRate, formattedCryptoAmount, 1, 1); + } + + return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); + } + + if (_wallet.type == WalletType.litecoin || _wallet.type == WalletType.bitcoinCash) { return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); } @@ -175,6 +190,7 @@ abstract class OutputBase with Store { fiatAmount = ''; address = ''; note = ''; + memo = null; resetParsedAddress(); } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 7636c485a..28cd0128f 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -2,6 +2,8 @@ import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/contact_record.dart'; @@ -12,6 +14,7 @@ import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; +import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; @@ -36,6 +39,7 @@ import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:collection/collection.dart'; part 'send_view_model.g.dart'; @@ -65,9 +69,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor _settingsStore = appStore.settingsStore, fiatFromSettings = appStore.settingsStore.fiatCurrency, super(appStore: appStore) { + if (wallet.type == WalletType.bitcoin && + _settingsStore.priority[wallet.type] == bitcoinTransactionPriorityCustom) { + setTransactionPriority(bitcoinTransactionPriorityMedium); + } final priority = _settingsStore.priority[wallet.type]; final priorities = priorityForWalletType(wallet.type); - if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) { _settingsStore.priority[wallet.type] = priorities.first; } @@ -103,8 +110,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get isBatchSending => outputs.length > 1; - bool get shouldDisplaySendALL => walletType != WalletType.solana; - @computed String get pendingTransactionFiatAmount { if (pendingTransaction == null) { @@ -151,6 +156,21 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return priority; } + + int? getCustomPriorityIndex(List priorities) { + if (wallet.type == WalletType.bitcoin) { + final customItem = priorities.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom()); + + return customItem != null ? priorities.indexOf(customItem) : null; + } + return null; + } + + @computed + int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate; + + void set customBitcoinFeeRate(int value) => _settingsStore.customBitcoinFeeRate = value; + CryptoCurrency get currency => wallet.currency; Validator get amountValidator => @@ -205,6 +225,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; + @computed + bool get hasFeesPriority => + wallet.type != WalletType.nano && + wallet.type != WalletType.banano && + wallet.type != WalletType.solana; @observable CryptoCurrency selectedCryptoCurrency; @@ -296,14 +321,47 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } @action - Future createTransaction() async { + Future createTransaction({ExchangeProvider? provider}) async { try { state = IsExecutingState(); pendingTransaction = await wallet.createTransaction(_credentials()); + if (provider is ThorChainExchangeProvider) { + final outputCount = pendingTransaction?.outputCount ?? 0; + if (outputCount > 10) { + throw Exception("ThorChain does not support more than 10 outputs"); + } + if (_hasTaprootInput(pendingTransaction)) { + throw Exception("ThorChain does not support Taproot addresses"); + } + } state = ExecutedSuccessfullyState(); + return pendingTransaction; } catch (e) { - print('Failed with ${e.toString()}'); - state = FailureState(e.toString()); + state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); + } + return null; + } + + @action + Future replaceByFee(String txId, String newFee) async { + state = IsExecutingState(); + + final isSufficient = await bitcoin!.isChangeSufficientForFee(wallet, txId, newFee); + + if (!isSufficient) { + state = AwaitingConfirmationState( + title: S.current.confirm_fee_deduction, + message: S.current.confirm_fee_deduction_content, + onConfirm: () async { + pendingTransaction = await bitcoin!.replaceByFee(wallet, txId, newFee); + state = ExecutedSuccessfullyState(); + }, + onCancel: () { + state = FailureState('Insufficient change for fee'); + }); + } else { + pendingTransaction = await bitcoin!.replaceByFee(wallet, txId, newFee); + state = ExecutedSuccessfullyState(); } } @@ -345,8 +403,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor state = TransactionCommitted(); } catch (e) { - String translatedError = translateErrorMessage(e.toString(), wallet.type, wallet.currency); - state = FailureState(translatedError); + state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); } } @@ -365,7 +422,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority!); + return bitcoin!.createBitcoinTransactionCredentials(outputs, + priority: priority!, feeRate: customBitcoinFeeRate); case WalletType.monero: return monero! @@ -391,9 +449,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } } - String displayFeeRate(dynamic priority) { + String displayFeeRate(dynamic priority, int? customValue) { final _priority = priority as TransactionPriority; + if (walletType == WalletType.bitcoin) { + final rate = bitcoin!.getFeeRate(wallet, _priority); + return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue); + } + if (isElectrumWallet) { final rate = bitcoin!.getFeeRate(wallet, _priority); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); @@ -405,6 +468,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor bool _isEqualCurrency(String currency) => wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase()); + TransactionPriority get bitcoinTransactionPriorityCustom => + bitcoin!.getBitcoinTransactionPriorityCustom(); + + TransactionPriority get bitcoinTransactionPriorityMedium => + bitcoin!.getBitcoinTransactionPriorityMedium(); + @action void onClose() => _settingsStore.fiatCurrency = fiatFromSettings; @@ -421,11 +490,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } } - ContactRecord? newContactAddress () { - + ContactRecord? newContactAddress() { final Set contactAddresses = - Set.from(contactListViewModel.contacts.map((contact) => contact.address)) - ..addAll(contactListViewModel.walletContacts.map((contact) => contact.address)); + Set.from(contactListViewModel.contacts.map((contact) => contact.address)) + ..addAll(contactListViewModel.walletContacts.map((contact) => contact.address)); for (var output in outputs) { String address; @@ -436,7 +504,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (address.isNotEmpty && !contactAddresses.contains(address)) { - return ContactRecord( contactListViewModel.contactSource, Contact( @@ -450,22 +517,67 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } String translateErrorMessage( - String error, + Object error, WalletType walletType, CryptoCurrency currency, ) { + String errorMessage = error.toString(); + if (walletType == WalletType.ethereum || walletType == WalletType.polygon || walletType == WalletType.solana || walletType == WalletType.haven) { - if (error.contains('gas required exceeds allowance') || - error.contains('insufficient funds')) { + if (errorMessage.contains('gas required exceeds allowance') || + errorMessage.contains('insufficient funds')) { return S.current.do_not_have_enough_gas_asset(currency.toString()); } - return error; + return errorMessage; } - return error; + if (walletType == WalletType.bitcoin || + walletType == WalletType.litecoin || + walletType == WalletType.bitcoinCash) { + if (error is TransactionWrongBalanceException) { + return S.current.tx_wrong_balance_exception(currency.toString()); + } + if (error is TransactionNoInputsException) { + return S.current.tx_not_enough_inputs_exception; + } + if (error is TransactionNoFeeException) { + return S.current.tx_zero_fee_exception; + } + if (error is TransactionNoDustException) { + return S.current.tx_no_dust_exception; + } + if (error is TransactionCommitFailed) { + return S.current.tx_commit_failed; + } + if (error is TransactionCommitFailedDustChange) { + return S.current.tx_rejected_dust_change; + } + if (error is TransactionCommitFailedDustOutput) { + return S.current.tx_rejected_dust_output; + } + if (error is TransactionCommitFailedDustOutputSendAll) { + return S.current.tx_rejected_dust_output_send_all; + } + if (error is TransactionCommitFailedVoutNegative) { + return S.current.tx_rejected_vout_negative; + } + if (error is TransactionNoDustOnChangeException) { + return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max); + } + } + + return errorMessage; + } + + bool _hasTaprootInput(PendingTransaction? pendingTransaction) { + if (walletType == WalletType.bitcoin && pendingTransaction != null) { + return bitcoin!.hasTaprootInput(pendingTransaction); + } + + return false; } } diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 263532d29..cf410a1a9 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -11,6 +11,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:package_info/package_info.dart'; +import 'package:collection/collection.dart'; part 'other_settings_view_model.g.dart'; @@ -77,6 +78,8 @@ abstract class OtherSettingsViewModelBase with Store { ProviderType get sellProviderType => _settingsStore.defaultSellProviders[walletType] ?? ProviderType.askEachTime; + + String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; @@ -90,6 +93,19 @@ abstract class OtherSettingsViewModelBase with Store { return priority.toString(); } + String getDisplayBitcoinPriority(dynamic priority, int customValue) { + final _priority = priority as TransactionPriority; + + if (_wallet.type == WalletType.bitcoin || + _wallet.type == WalletType.litecoin || + _wallet.type == WalletType.bitcoinCash) { + final rate = bitcoin!.getFeeRate(_wallet, _priority); + return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue); + } + + return priority.toString(); + } + String getBuyProviderType(dynamic buyProviderType) { final _buyProviderType = buyProviderType as ProviderType; return _buyProviderType == ProviderType.askEachTime @@ -105,7 +121,24 @@ abstract class OtherSettingsViewModelBase with Store { } void onDisplayPrioritySelected(TransactionPriority priority) => - _settingsStore.priority[_wallet.type] = priority; + _settingsStore.priority[walletType] = priority; + + void onDisplayBitcoinPrioritySelected(TransactionPriority priority, double customValue) { + if (_wallet.type == WalletType.bitcoin) { + _settingsStore.customBitcoinFeeRate = customValue.round(); + } + _settingsStore.priority[_wallet.type] = priority; + } + + @computed + double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble(); + + int? get customPriorityItemIndex { + final priorities = priorityForWalletType(walletType); + final customItem = priorities + .firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom()); + return customItem != null ? priorities.indexOf(customItem) : null; + } @action ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) => diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index 45502fd74..1da322778 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -52,6 +53,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.exolix: _provider = ExolixExchangeProvider(); break; + case ExchangeProviderDescription.thorChain: + _provider = ThorChainExchangeProvider(tradesStore: trades); + break; } _updateItems(); @@ -62,6 +66,24 @@ abstract class TradeDetailsViewModelBase with Store { } } + static String? getTrackUrl(ExchangeProviderDescription provider, Trade trade) { + switch (provider) { + case ExchangeProviderDescription.changeNow: + return 'https://changenow.io/exchange/txs/${trade.id}'; + case ExchangeProviderDescription.sideShift: + return 'https://sideshift.ai/orders/${trade.id}'; + case ExchangeProviderDescription.simpleSwap: + return 'https://simpleswap.io/exchange?id=${trade.id}'; + case ExchangeProviderDescription.trocador: + return 'https://trocador.app/en/checkout/${trade.id}'; + case ExchangeProviderDescription.exolix: + return 'https://exolix.com/transaction/${trade.id}'; + case ExchangeProviderDescription.thorChain: + return 'https://track.ninerealms.com/${trade.id}'; + } + return null; + } + final Box trades; @observable @@ -125,46 +147,26 @@ abstract class TradeDetailsViewModelBase with Store { items.add(StandartListItem( title: S.current.trade_details_provider, value: trade.provider.toString())); - if (trade.provider == ExchangeProviderDescription.changeNow) { - final buildURL = 'https://changenow.io/exchange/txs/${trade.id.toString()}'; + final trackUrl = TradeDetailsViewModelBase.getTrackUrl(trade.provider, trade); + if (trackUrl != null) { items.add(TrackTradeListItem( - title: 'Track', - value: buildURL, - onTap: () { - _launchUrl(buildURL); - })); + title: S.current.track, value: trackUrl, onTap: () => _launchUrl(trackUrl))); } - if (trade.provider == ExchangeProviderDescription.sideShift) { - final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}'; - items.add( - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); - } - - if (trade.provider == ExchangeProviderDescription.simpleSwap) { - final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}'; - items.add( - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); + if (trade.isRefund == true) { + items.add(StandartListItem( + title: 'Refund', value: trade.refundAddress ?? '')); } if (trade.provider == ExchangeProviderDescription.trocador) { - final buildURL = 'https://trocador.app/en/checkout/${trade.id.toString()}'; - items.add( - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); - items.add(StandartListItem( title: '${trade.providerName} ${S.current.id.toUpperCase()}', value: trade.providerId ?? '')); - if (trade.password != null && trade.password!.isNotEmpty) + if (trade.password != null && trade.password!.isNotEmpty) { items.add(StandartListItem( title: '${trade.providerName} ${S.current.password}', value: trade.password ?? '')); - } - - if (trade.provider == ExchangeProviderDescription.exolix) { - final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}'; - items.add( - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); + } } } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 1b1ceb814..fd6d3ef6e 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -1,20 +1,27 @@ -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/entities/transaction_description.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; -import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; -import 'package:cw_core/transaction_direction.dart'; +import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/date_formatter.dart'; -import 'package:cake_wallet/entities/transaction_description.dart'; +import 'package:cake_wallet/view_model/send/send_view_model.dart'; +import 'package:collection/collection.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:intl/src/intl/date_format.dart'; import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:cake_wallet/monero/monero.dart'; part 'transaction_details_view_model.g.dart'; @@ -26,8 +33,11 @@ abstract class TransactionDetailsViewModelBase with Store { {required this.transactionInfo, required this.transactionDescriptionBox, required this.wallet, - required this.settingsStore}) + required this.settingsStore, + required this.sendViewModel}) : items = [], + RBFListItems = [], + newFee = 0, isRecipientAddressShown = false, showRecipientAddress = settingsStore.shouldSaveRecipientAddress { final dateFormat = DateFormatter.withCurrentLocal(); @@ -38,6 +48,10 @@ abstract class TransactionDetailsViewModelBase with Store { _addMoneroListItems(tx, dateFormat); break; case WalletType.bitcoin: + _addElectrumListItems(tx, dateFormat); + _addBumpFeesListItems(tx); + _checkForRBF(); + break; case WalletType.litecoin: case WalletType.bitcoinCash: _addElectrumListItems(tx, dateFormat); @@ -109,10 +123,20 @@ abstract class TransactionDetailsViewModelBase with Store { final Box transactionDescriptionBox; final SettingsStore settingsStore; final WalletBase wallet; + final SendViewModel sendViewModel; final List items; + final List RBFListItems; bool showRecipientAddress; bool isRecipientAddressShown; + int newFee; + TransactionPriority? transactionPriority; + + @observable + bool _canReplaceByFee = false; + + @computed + bool get canReplaceByFee => _canReplaceByFee /*&& transactionInfo.confirmations <= 0*/; String _explorerUrl(WalletType type, String txId) { switch (type) { @@ -305,4 +329,88 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + + void _addBumpFeesListItems(TransactionInfo tx) { + transactionPriority = bitcoin!.getBitcoinTransactionPriorityMedium(); + + newFee = bitcoin!.getFeeAmountForPriority( + wallet, + bitcoin!.getBitcoinTransactionPriorityMedium(), + transactionInfo.inputAddresses?.length ?? 1, + transactionInfo.outputAddresses?.length ?? 1); + + RBFListItems.add(StandartListItem( + title: S.current.old_fee, + value: tx.feeFormatted() ?? '0.0')); + + final priorities = priorityForWalletType(wallet.type); + final selectedItem = priorities.indexOf(sendViewModel.transactionPriority); + final customItem = priorities.firstWhereOrNull( + (element) => element == sendViewModel.bitcoinTransactionPriorityCustom); + final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null; + + RBFListItems.add(StandardPickerListItem( + title: S.current.estimated_new_fee, + value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}', + items: priorityForWalletType(wallet.type), + customValue: settingsStore.customBitcoinFeeRate.toDouble(), + selectedIdx: selectedItem, + customItemIndex: customItemIndex ?? 0, + displayItem: (dynamic priority, double sliderValue) => + sendViewModel.displayFeeRate(priority, sliderValue.round()), + onSliderChanged: (double newValue) => + setNewFee(value: newValue, priority: transactionPriority!), + onItemSelected: (dynamic item) { + transactionPriority = item as TransactionPriority; + return setNewFee(priority: transactionPriority!); + })); + + if (transactionInfo.inputAddresses != null) { + RBFListItems.add(StandardExpandableListItem( + title: S.current.inputs, expandableItems: transactionInfo.inputAddresses!)); + } + + if (transactionInfo.outputAddresses != null) { + RBFListItems.add(StandardExpandableListItem( + title: S.current.outputs, expandableItems: transactionInfo.outputAddresses!)); + } + } + + @action + Future _checkForRBF() async { + if (wallet.type == WalletType.bitcoin && + transactionInfo.direction == TransactionDirection.outgoing) { + if (await bitcoin!.canReplaceByFee(wallet, transactionInfo.id)) { + _canReplaceByFee = true; + } + } + } + + String setNewFee({double? value, required TransactionPriority priority}) { + newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null + ? bitcoin!.getFeeAmountWithFeeRate( + wallet, + value.round(), + transactionInfo.inputAddresses?.length ?? 1, + transactionInfo.outputAddresses?.length ?? 1) + : bitcoin!.getFeeAmountForPriority( + wallet, + priority, + transactionInfo.inputAddresses?.length ?? 1, + transactionInfo.outputAddresses?.length ?? 1); + + return bitcoin!.formatterBitcoinAmountToString(amount: newFee); + } + + void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo.id, newFee); + + @computed + String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled + ? '' + : sendViewModel.pendingTransactionFiatAmount + ' ' + sendViewModel.fiat.title; + + @computed + String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled + ? '' + : sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title; } diff --git a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart index 4da43c241..fd142dd33 100644 --- a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart @@ -100,7 +100,5 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { final WalletType _type; List items; - String get formattedAddress => WalletType.bitcoinCash == _type - ? bitcoinCash!.getCashAddrFormat(unspentCoinsItem.address) - : unspentCoinsItem.address; + String get formattedAddress => unspentCoinsItem.address; } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 66af15dfa..d0f1a2043 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -43,6 +43,7 @@ "already_have_account": "لديك حساب؟", "always": "دائماً", "amount": "مقدار:", + "amount_is_below_minimum_limit": "سيكون رصيدك بعد الرسوم أقل من الحد الأدنى للمبلغ اللازم للتبادل (${min})", "amount_is_estimate": "المبلغ المستلم هو تقدير", "amount_is_guaranteed": "مبلغ الاستلام مضمون", "and": "و", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.", "Blocks_remaining": "بلوك متبقي ${status}", "bright_theme": "مشرق", + "bump_fee": "رسوم عثرة", "buy": "اشتري", "buy_alert_content": ".ﺎﻬﻴﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ Monero ﻭﺃ Litecoin ﻭﺃ Ethereum ﻭﺃ Bitcoin ﺔﻈﻔﺤﻣ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .", "buy_bitcoin": "شراء Bitcoin", @@ -132,6 +134,8 @@ "confirm": "تأكيد", "confirm_delete_template": "سيؤدي هذا الإجراء إلى حذف هذا القالب. هل ترغب في الاستمرار؟", "confirm_delete_wallet": "سيؤدي هذا الإجراء إلى حذف هذه المحفظة. هل ترغب في الاستمرار؟", + "confirm_fee_deduction": "تأكيد خصم الرسوم", + "confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟", "confirm_sending": "تأكيد الإرسال", "confirmations": "التأكيدات", "confirmed": "رصيد مؤكد", @@ -171,6 +175,7 @@ "debit_card": "بطاقة ائتمان", "debit_card_terms": "يخضع تخزين واستخدام رقم بطاقة الدفع الخاصة بك (وبيانات الاعتماد المقابلة لرقم بطاقة الدفع الخاصة بك) في هذه المحفظة الرقمية لشروط وأحكام اتفاقية حامل البطاقة المعمول بها مع جهة إصدار بطاقة الدفع ، كما هو معمول به من وقت لآخر.", "decimal_places_error": "عدد كبير جدًا من المنازل العشرية", + "decimals_cannot_be_zero": "الرمز العشري لا يمكن أن يكون الصفر.", "default_buy_provider": "مزود شراء الافتراضي", "default_sell_provider": "ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", "delete": "حذف", @@ -211,6 +216,7 @@ "edit_token": "تحرير الرمز المميز", "electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل", "email_address": "عنوان البريد الالكترونى", + "enable_replace_by_fee": "تمكين الاستبدال", "enabled": "ممكنة", "enter_amount": "أدخل المبلغ", "enter_backup_password": "أدخل كلمة المرور الاحتياطية هنا", @@ -247,6 +253,7 @@ "errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ", "errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ", "estimated": "مُقدَّر", + "estimated_new_fee": "رسوم جديدة مقدرة", "etherscan_history": "Etherscan تاريخ", "event": "ﺙﺪﺣ", "events": "ﺙﺍﺪﺣﻷﺍ", @@ -313,6 +320,7 @@ "in_store": "في المتجر", "incoming": "الواردة", "incorrect_seed": "النص الذي تم إدخاله غير صالح.", + "inputs": "المدخلات", "introducing_cake_pay": "نقدم لكم Cake Pay!", "invalid_input": "مدخل غير صالح", "invoice_details": "تفاصيل الفاتورة", @@ -380,6 +388,7 @@ "offer_expires_in": "ينتهي العرض في:", "offline": "غير متصل على الانترنت", "ok": "حسناً", + "old_fee": "الرسوم القديمة", "onion_link": "رابط البصل", "online": "متصل", "onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "محافظ Bitcoin الجديدة التي تم إنشاؤها في Cake الآن سييد مكونة من 24 كلمة. من الضروري أن تقوم بإنشاء محفظة Bitcoin جديدة وتحويل جميع أموالك إلى المحفظة الجديدة المكونة من 24 كلمة ، والتوقف عن استخدام محافظ سييد مكونة من 12 كلمة. يرجى القيام بذلك على الفور لتأمين أموالك.", "outdated_electrum_wallet_receive_warning": "إذا كانت هذه المحفظة تحتوي على سييد مكونة من 12 كلمة وتم إنشاؤها في Cake ، فلا تقم بإيداع Bitcoin في هذه المحفظة. قد يتم فقد أي BTC تم تحويله إلى هذه المحفظة. قم بإنشاء محفظة جديدة مكونة من 24 كلمة (انقر فوق القائمة في الجزء العلوي الأيمن ، وحدد محافظ ، واختر إنشاء محفظة جديدة ، ثم حدد Bitcoin) وقم على الفور بنقل BTC الخاص بك هناك. محافظ BTC الجديدة (24 كلمة) من Cake آمنة", "outgoing": "الصادره", + "outputs": "المخرجات", "overwrite_amount": "تغير المبلغ", "pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ", "password": "كلمة المرور", @@ -643,6 +653,7 @@ "template_name": "اسم القالب", "third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!", "third_intro_title": "يتماشي Yat بلطف مع الآخرين", + "thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.", "time": "${minutes}د ${seconds}س", "tip": "بقشيش:", "today": "اليوم", @@ -660,6 +671,7 @@ "totp_code": "كود TOTP", "totp_secret_code": "كود TOTP السري", "totp_verification_success": "تم التحقق بنجاح!", + "track": " ﺭﺎﺴﻣ", "trade_details_copied": "تم نسخ ${title} إلى الحافظة", "trade_details_created_at": "أنشئت في", "trade_details_fetching": "جار الجلب", @@ -710,6 +722,16 @@ "transactions": "المعاملات", "transactions_by_date": "المعاملات حسب التاريخ", "trusted": "موثوق به", + "tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.", + "tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.", + "tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.", + "tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة", + "tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.", + "tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.", + "tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.", + "tx_rejected_vout_negative": "لا يوجد ما يكفي من الرصيد لدفع رسوم هذه الصفقة. يرجى التحقق من رصيد العملات المعدنية تحت السيطرة على العملة.", + "tx_wrong_balance_exception": "ليس لديك ما يكفي من ${currency} لإرسال هذا المبلغ.", + "tx_zero_fee_exception": "لا يمكن إرسال معاملة مع 0 رسوم. حاول زيادة المعدل أو التحقق من اتصالك للحصول على أحدث التقديرات.", "unavailable_balance": "ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ", "unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ", "unconfirmed": "رصيد غير مؤكد", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 061506b86..249952d75 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -43,6 +43,7 @@ "already_have_account": "Вече имате профил?", "always": "Винаги", "amount": "Сума: ", + "amount_is_below_minimum_limit": "Вашето салдо след такси ще бъде по -малко от минималната сума, необходима за борсата (${min})", "amount_is_estimate": "Сумата за получаване е ", "amount_is_guaranteed": "Сумата за получаване е гарантирана", "and": "и", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.", "Blocks_remaining": "${status} оставащи блока", "bright_theme": "Ярко", + "bump_fee": "Такса за бум", "buy": "Купуване", "buy_alert_content": "В момента поддържаме само закупуването на Bitcoin, Ethereum, Litecoin и Monero. Моля, създайте или превключете към своя портфейл Bitcoin, Ethereum, Litecoin или Monero.", "buy_bitcoin": "Купуване на Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Потвърждаване", "confirm_delete_template": "Този шаблон ще бъде изтрит. Искате ли да продължите?", "confirm_delete_wallet": "Този портфейл ще бъде изтрит. Искате ли да продължите?", + "confirm_fee_deduction": "Потвърдете приспадането на таксите", + "confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?", "confirm_sending": "Потвърждаване на изпращането", "confirmations": "потвърждения", "confirmed": "Потвърден баланс", @@ -171,6 +175,7 @@ "debit_card": "Дебитна карта", "debit_card_terms": "Съхранението и използването на данните от вашата платежна карта в този дигитален портфейл подлежат на условията на съответното съгласие за картодържец от издателя на картата.", "decimal_places_error": "Твърде много знаци след десетичната запетая", + "decimals_cannot_be_zero": "Десетичната точка не може да бъде нула.", "default_buy_provider": "Доставчик по подразбиране купува", "default_sell_provider": "Доставчик за продажба по подразбиране", "delete": "Изтрий", @@ -211,6 +216,7 @@ "edit_token": "Редактиране на токена", "electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят", "email_address": "Имейл адрес", + "enable_replace_by_fee": "Активиране на замяна по забрана", "enabled": "Активирано", "enter_amount": "Въведете сума", "enter_backup_password": "Въведете парола за възстановяване", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни", "errorSigningTransaction": "Възникна грешка при подписване на транзакция", "estimated": "Изчислено", + "estimated_new_fee": "Прогнозна нова такса", "etherscan_history": "История на Etherscan", "event": "Събитие", "events": "събития", @@ -313,6 +320,7 @@ "in_store": "In Store", "incoming": "Входящи", "incorrect_seed": "Въведеният текст е невалиден.", + "inputs": "Входове", "introducing_cake_pay": "Запознайте се с Cake Pay!", "invalid_input": "Невалиден вход", "invoice_details": "IДанни за фактура", @@ -380,6 +388,7 @@ "offer_expires_in": "Предложението изтича след: ", "offline": "Офлайн", "ok": "Ок", + "old_fee": "Стара такса", "onion_link": "Лукова връзка", "online": "Онлайн", "onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Нови Bitcoin портфейли, създадени в Cake, сега имат seed от 24 думи. Трябва да създадете нов Bitcoin адрес и да прехвърлите всичките си средства в него и веднага да спрете използването на стари портфейли. Моля, напревете това незабавно, за да подсигурите средствата си.", "outdated_electrum_wallet_receive_warning": "Ако този адрес има seed от 12 думи и е създаден чрез Cake, НЕ добавяйте Bitcoin в него. Всякакъв Bitcoin, изпратен на този адрес, може да бъде загубен завинаги. Създайте нов портфейл от 24 думи (натиснете менюто горе, вдясно, изберете Портфейли, изберете Създаване на нов портфейл, след това изберете Bitcoin) и НЕЗАБАВНО преместете своя Bitcoin там. Нови (такива с 24 думи) Bitcoin портфейли от Cake са надеждни", "outgoing": "Изходящи", + "outputs": "Изходи", "overwrite_amount": "Промени сума", "pairingInvalidEvent": "Невалидно събитие при сдвояване", "password": "Парола", @@ -643,6 +653,7 @@ "template_name": "Име на шаблон", "third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!", "third_intro_title": "Yat добре се сработва с други", + "thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.", "time": "${minutes} мин ${seconds} сек", "tip": "Tip:", "today": "Днес", @@ -660,6 +671,7 @@ "totp_code": "TOTP код", "totp_secret_code": "TOTP таен код", "totp_verification_success": "Проверката е успешна!", + "track": "Писта", "trade_details_copied": "${title} копирано", "trade_details_created_at": "Създадено", "trade_details_fetching": "Обработка", @@ -710,6 +722,16 @@ "transactions": "Транзакции", "transactions_by_date": "Транзакции по дата", "trusted": "Надежден", + "tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.", + "tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.", + "tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.", + "tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети", + "tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.", + "tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.", + "tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.", + "tx_rejected_vout_negative": "Няма достатъчно баланс, за да платите за таксите на тази транзакция. Моля, проверете баланса на монетите под контрол на монетите.", + "tx_wrong_balance_exception": "Нямате достатъчно ${currency}, за да изпратите тази сума.", + "tx_zero_fee_exception": "Не може да изпраща транзакция с 0 такса. Опитайте да увеличите скоростта или да проверите връзката си за най -новите оценки.", "unavailable_balance": "Неналично салдо", "unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.", "unconfirmed": "Непотвърден баланс", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 2896c11a9..9b3b06ffd 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -43,6 +43,7 @@ "already_have_account": "Máte už účet?", "always": "Vždy", "amount": "Částka: ", + "amount_is_below_minimum_limit": "Váš zůstatek po poplatcích by byl menší než minimální částka potřebná pro burzu (${min})", "amount_is_estimate": "Částka, kterou dostanete, je jen odhad.", "amount_is_guaranteed": "Částka, kterou dostanete, je konečná", "and": "a", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.", "Blocks_remaining": "Zbývá ${status} bloků", "bright_theme": "Jasný", + "bump_fee": "Bump Fee", "buy": "Koupit", "buy_alert_content": "V současné době podporujeme pouze nákup bitcoinů, etherea, litecoinů a monero. Vytvořte nebo přepněte na svou peněženku bitcoinů, etherea, litecoinů nebo monero.", "buy_bitcoin": "Nakoupit Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Potvrdit", "confirm_delete_template": "Tato akce smaže tuto šablonu. Přejete si pokračovat?", "confirm_delete_wallet": "Tato akce smaže tuto peněženku. Přejete si pokračovat?", + "confirm_fee_deduction": "Potvrďte odpočet poplatků", + "confirm_fee_deduction_content": "Souhlasíte s odečtením poplatku z výstupu?", "confirm_sending": "Potvrdit odeslání", "confirmations": "Potvrzení", "confirmed": "Potvrzený zůstatek", @@ -171,6 +175,7 @@ "debit_card": "Debetní karta", "debit_card_terms": "Uložení a použití vašeho čísla platební karty (a přihlašovací údaje k vašemu číslu karty) v této digitální peněžence se řídí Obchodními podmínkami smlouvy příslušného držitele karty s vydavatelem karty (v jejich nejaktuálnější verzi).", "decimal_places_error": "Příliš mnoho desetinných míst", + "decimals_cannot_be_zero": "Desetinná desetinná škola nemůže být nulová.", "default_buy_provider": "Výchozí poskytovatel nákupu", "default_sell_provider": "Výchozí poskytovatel prodeje", "delete": "Smazat", @@ -211,6 +216,7 @@ "edit_token": "Upravit token", "electrum_address_disclaimer": "Po každém použití je generována nová adresa, ale předchozí adresy také stále fungují", "email_address": "E-mailová adresa", + "enable_replace_by_fee": "Povolit výměnu podle poplatku", "enabled": "Povoleno", "enter_amount": "Zadejte částku", "enter_backup_password": "Zde zadejte své heslo pro zálohy", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Selhalo: Chyba při získávání přihlašovacích údajů", "errorSigningTransaction": "Při podepisování transakce došlo k chybě", "estimated": "Odhadováno", + "estimated_new_fee": "Odhadovaný nový poplatek", "etherscan_history": "Historie Etherscanu", "event": "událost", "events": "Události", @@ -313,6 +320,7 @@ "in_store": "V obchodě", "incoming": "Příchozí", "incorrect_seed": "Zadaný text není správný.", + "inputs": "Vstupy", "introducing_cake_pay": "Představujeme Cake Pay!", "invalid_input": "Neplatný vstup", "invoice_details": "detaily faktury", @@ -380,6 +388,7 @@ "offer_expires_in": "Nabídka vyprší: ", "offline": "Offline", "ok": "OK", + "old_fee": "Starý poplatek", "onion_link": "Cibulový odkaz", "online": "Online", "onramper_option_description": "Rychle si koupte krypto s mnoha metodami plateb. K dispozici ve většině zemí. Rozpětí a poplatky se liší.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Nové Bitcoinové peněženky vytvořené v Cake mají nyní seed se 24 slovy. Je třeba si vytvořit novou Bitcoinovou peněženku se 24 slovy, převést na ni všechny prostředky a přestat používat seed se 12 slovy. Prosím udělejte to hned pro zabezpečení svých prostředků.", "outdated_electrum_wallet_receive_warning": "Tato peněženka má seed se 12 slovy a byla vytvořena pomocí Cake, NEUKLÁDEJTE Bitcoin na tuto peněženku. Jakékoliv BTC převedené na tuto peněženku může být ztraceno. Vytvořte si novou peněženku s 24 slovy (otevřete menu vpravo nahoře, vyberte Peněženky, zvolte Vytvořit novou peněženku a pak zvolte Bitcoin) a IHNED tam přesuňte své BTC. Nové (24-slovní) BTC peněženky z Cake jsou bezpečné", "outgoing": "Odchozí", + "outputs": "Výstupy", "overwrite_amount": "Přepsat částku", "pairingInvalidEvent": "Neplatná událost párování", "password": "Heslo", @@ -643,6 +653,7 @@ "template_name": "Název šablony", "third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!", "third_intro_title": "Yat dobře spolupracuje s ostatními", + "thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.", "time": "${minutes}m ${seconds}s", "tip": "Spropitné:", "today": "Dnes", @@ -660,6 +671,7 @@ "totp_code": "Kód TOTP", "totp_secret_code": "Tajný kód TOTP", "totp_verification_success": "Ověření proběhlo úspěšně!", + "track": "Dráha", "trade_details_copied": "${title} zkopírováno do schránky", "trade_details_created_at": "Vytvořeno v", "trade_details_fetching": "Získávám", @@ -710,6 +722,16 @@ "transactions": "Transakce", "transactions_by_date": "Transakce podle data", "trusted": "Důvěřovat", + "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_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.", + "tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí", + "tx_rejected_dust_change": "Transakce zamítnuta podle síťových pravidel, množství nízké změny (prach). Zkuste odeslat vše nebo snížit částku.", + "tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.", + "tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.", + "tx_rejected_vout_negative": "Nedostatek zůstatek na zaplacení poplatků za tuto transakci. Zkontrolujte prosím zůstatek mincí pod kontrolou mincí.", + "tx_wrong_balance_exception": "Nemáte dost ${currency} pro odeslání této částky.", + "tx_zero_fee_exception": "Nelze odeslat transakci s 0 poplatkem. Zkuste zvýšit sazbu nebo zkontrolovat připojení pro nejnovější odhady.", "unavailable_balance": "Nedostupný zůstatek", "unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.", "unconfirmed": "Nepotvrzený zůstatek", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 9dfa4f4db..8a914628f 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -43,6 +43,7 @@ "already_have_account": "Sie haben bereits ein Konto?", "always": "immer", "amount": "Betrag: ", + "amount_is_below_minimum_limit": "Ihr Saldo nach Gebühren wäre geringer als der für den Austausch benötigte Mindestbetrag (${min})", "amount_is_estimate": "Der empfangene Betrag ist eine Schätzung", "amount_is_guaranteed": "Der Empfangsbetrag ist garantiert", "and": "Und", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.", "Blocks_remaining": "${status} verbleibende Blöcke", "bright_theme": "Strahlend hell", + "bump_fee": "Beulengebühr", "buy": "Kaufen", "buy_alert_content": "Derzeit unterstützen wir nur den Kauf von Bitcoin, Ethereum, Litecoin und Monero. Bitte erstellen Sie Ihr Bitcoin-, Ethereum-, Litecoin- oder Monero-Wallet oder wechseln Sie zu diesem.", "buy_bitcoin": "Bitcoin kaufen", @@ -132,6 +134,8 @@ "confirm": "Bestätigen", "confirm_delete_template": "Diese Aktion löscht diese Vorlage. Möchten Sie fortfahren?", "confirm_delete_wallet": "Diese Aktion löscht diese Wallet. Möchten Sie fortfahren?", + "confirm_fee_deduction": "Gebührenabzug bestätigen", + "confirm_fee_deduction_content": "Stimmen Sie zu, die Gebühr von der Ausgabe abzuziehen?", "confirm_sending": "Senden bestätigen", "confirmations": "Bestätigungen", "confirmed": "Bestätigter Saldo", @@ -171,6 +175,7 @@ "debit_card": "Debitkarte", "debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Geldbörse unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.", "decimal_places_error": "Zu viele Nachkommastellen", + "decimals_cannot_be_zero": "Token -Dezimalzahl kann nicht Null sein.", "default_buy_provider": "Standard-Kaufanbieter", "default_sell_provider": "Standard-Verkaufsanbieter", "delete": "Löschen", @@ -211,6 +216,7 @@ "edit_token": "Token bearbeiten", "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", "email_address": "E-Mail-Adresse", + "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee", "enabled": "Ermöglicht", "enter_amount": "Betrag eingeben", "enter_backup_password": "Sicherungskennwort hier eingeben", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen", "errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten", "estimated": "Geschätzt", + "estimated_new_fee": "Geschätzte neue Gebühr", "etherscan_history": "Etherscan-Geschichte", "event": "Ereignis", "events": "Veranstaltungen", @@ -313,6 +320,7 @@ "in_store": "Im Geschäft", "incoming": "Eingehend", "incorrect_seed": "Der eingegebene Text ist ungültig.", + "inputs": "Eingänge", "introducing_cake_pay": "Einführung von Cake Pay!", "invalid_input": "Ungültige Eingabe", "invoice_details": "Rechnungs-Details", @@ -380,6 +388,7 @@ "offer_expires_in": "Angebot läuft ab in: ", "offline": "offline", "ok": "OK", + "old_fee": "Alte Gebühr", "onion_link": "Zwiebel-Link", "online": "online", "onramper_option_description": "Kaufen Sie schnell Krypto mit vielen Zahlungsmethoden. In den meisten Ländern erhältlich. Spreads und Gebühren variieren.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Neue Bitcoin-Wallets, die in Cake erstellt wurden, haben jetzt einen 24-Wort-Seed. Sie müssen eine neue Bitcoin-Wallet erstellen, Ihr gesamtes Geld in die neue 24-Wort-Wallet überweisen und keine Wallet mit einem 12-Wort-Seed mehr verwenden. Bitte tun Sie dies sofort, um Ihr Geld zu sichern.", "outdated_electrum_wallet_receive_warning": "Wenn diese Wallet einen 12-Wort-Seed hat und in Cake erstellt wurde, zahlen Sie KEINE Bitcoins in diese Wallet ein. Alle auf diese Wallet übertragenen BTC können verloren gehen. Erstellen Sie eine neue 24-Wort-Wallet (tippen Sie auf das Menü oben rechts, wählen Sie Wallets, wählen Sie Neue Wallet erstellen und dann Bitcoin) und verschieben Sie Ihre BTC SOFORT dorthin. Neue (24-Wort-)BTC-Wallets von Cake sind sicher", "outgoing": "Ausgehend", + "outputs": "Ausgänge", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Paarung ungültiges Ereignis", "password": "Passwort", @@ -644,6 +654,7 @@ "template_name": "Vorlagenname", "third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!", "third_intro_title": "Yat spielt gut mit anderen", + "thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.", "time": "${minutes}m ${seconds}s", "tip": "Hinweis:", "today": "Heute", @@ -661,6 +672,7 @@ "totp_code": "TOTP-Code", "totp_secret_code": "TOTP-Geheimcode", "totp_verification_success": "Verifizierung erfolgreich!", + "track": "Schiene", "trade_details_copied": "${title} in die Zwischenablage kopiert", "trade_details_created_at": "Erzeugt am", "trade_details_fetching": "Wird ermittelt", @@ -711,6 +723,16 @@ "transactions": "Transaktionen", "transactions_by_date": "Transaktionen nach Datum", "trusted": "Vertrauenswürdige", + "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_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.", + "tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus", + "tx_rejected_dust_change": "Transaktion abgelehnt durch Netzwerkregeln, niedriger Änderungsbetrag (Staub). Versuchen Sie, alle zu senden oder die Menge zu reduzieren.", + "tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte erhöhen Sie den Betrag.", + "tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.", + "tx_rejected_vout_negative": "Nicht genug Guthaben, um die Gebühren dieser Transaktion zu bezahlen. Bitte überprüfen Sie den Restbetrag der Münzen unter Münzkontrolle.", + "tx_wrong_balance_exception": "Sie haben nicht genug ${currency}, um diesen Betrag zu senden.", + "tx_zero_fee_exception": "Transaktion kann nicht mit 0 Gebühren gesendet werden. Versuchen Sie, die Rate zu erhöhen oder Ihre Verbindung auf die neuesten Schätzungen zu überprüfen.", "unavailable_balance": "Nicht verfügbares Guthaben", "unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.", "unconfirmed": "Unbestätigter Saldo", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 902cb062f..3fe9474c5 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -43,6 +43,7 @@ "already_have_account": "Already have an account?", "always": "Always", "amount": "Amount: ", + "amount_is_below_minimum_limit": "Your balance after fees would be less than the minimum amount needed for the exchange (${min})", "amount_is_estimate": "The receive amount is an estimate", "amount_is_guaranteed": "The receive amount is guaranteed", "and": "and", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.", "Blocks_remaining": "${status} Blocks Remaining", "bright_theme": "Bright", + "bump_fee": "Bump fee", "buy": "Buy", "buy_alert_content": "Currently we only support the purchase of Bitcoin, Ethereum, Litecoin, and Monero. Please create or switch to your Bitcoin, Ethereum, Litecoin, or Monero wallet.", "buy_bitcoin": "Buy Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Confirm", "confirm_delete_template": "This action will delete this template. Do you wish to continue?", "confirm_delete_wallet": "This action will delete this wallet. Do you wish to continue?", + "confirm_fee_deduction": "Confirm Fee Deduction", + "confirm_fee_deduction_content": "Do you agree to deduct the fee from the output?", "confirm_sending": "Confirm sending", "confirmations": "Confirmations", "confirmed": "Confirmed Balance", @@ -171,6 +175,7 @@ "debit_card": "Debit Card", "debit_card_terms": "The storage and usage of your payment card number (and credentials corresponding to your payment card number) in this digital wallet are subject to the Terms and Conditions of the applicable cardholder agreement with the payment card issuer, as in effect from time to time.", "decimal_places_error": "Too many decimal places", + "decimals_cannot_be_zero": "Token decimal cannot be zero.", "default_buy_provider": "Default Buy Provider", "default_sell_provider": "Default Sell Provider", "delete": "Delete", @@ -211,6 +216,7 @@ "edit_token": "Edit token", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", "email_address": "Email Address", + "enable_replace_by_fee": "Enable Replace-By-Fee", "enabled": "Enabled", "enter_amount": "Enter Amount", "enter_backup_password": "Enter backup password here", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Failed: Error while getting credentials", "errorSigningTransaction": "An error has occured while signing transaction", "estimated": "Estimated", + "estimated_new_fee": "Estimated new fee", "etherscan_history": "Etherscan history", "event": "Event", "events": "Events", @@ -313,6 +320,7 @@ "in_store": "In Store", "incoming": "Incoming", "incorrect_seed": "The text entered is not valid.", + "inputs": "Inputs", "introducing_cake_pay": "Introducing Cake Pay!", "invalid_input": "Invalid input", "invoice_details": "Invoice details", @@ -380,6 +388,7 @@ "offer_expires_in": "Offer expires in: ", "offline": "Offline", "ok": "OK", + "old_fee": "Old fee", "onion_link": "Onion link", "online": "Online", "onramper_option_description": "Quickly buy crypto with many payment methods. Available in most countries. Spreads and fees vary.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.", "outdated_electrum_wallet_receive_warning": "If this wallet has a 12-word seed and was created in Cake, DO NOT deposit Bitcoin into this wallet. Any BTC transferred to this wallet may be lost. Create a new 24-word wallet (tap the menu at the top right, select Wallets, choose Create New Wallet, then select Bitcoin) and IMMEDIATELY move your BTC there. New (24-word) BTC wallets from Cake are secure", "outgoing": "Outgoing", + "outputs": "Outputs", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Pairing Invalid Event", "password": "Password", @@ -643,6 +653,7 @@ "template_name": "Template Name", "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!", "third_intro_title": "Yat plays nicely with others", + "thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.", "time": "${minutes}m ${seconds}s", "tip": "Tip:", "today": "Today", @@ -660,6 +671,7 @@ "totp_code": "TOTP Code", "totp_secret_code": "TOTP Secret Code", "totp_verification_success": "Verification Successful!", + "track": "Track", "trade_details_copied": "${title} copied to Clipboard", "trade_details_created_at": "Created at", "trade_details_fetching": "Fetching", @@ -710,6 +722,16 @@ "transactions": "Transactions", "transactions_by_date": "Transactions by date", "trusted": "Trusted", + "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_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.", + "tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control", + "tx_rejected_dust_change": "Transaction rejected by network rules, low change amount (dust). Try sending ALL or reducing the amount.", + "tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.", + "tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.", + "tx_rejected_vout_negative": "Not enough balance to pay for this transaction's fees. Please check the balance of coins under Coin Control.", + "tx_wrong_balance_exception": "You do not have enough ${currency} to send this amount.", + "tx_zero_fee_exception": "Cannot send transaction with 0 fee. Try increasing the rate or checking your connection for latest estimates.", "unavailable_balance": "Unavailable balance", "unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.", "unconfirmed": "Unconfirmed Balance", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 4b9a9d26b..47b11bc48 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -43,6 +43,7 @@ "already_have_account": "¿Ya tienes una cuenta?", "always": "siempre", "amount": "Cantidad: ", + "amount_is_below_minimum_limit": "Su saldo después de las tarifas sería menor que la cantidad mínima necesaria para el intercambio (${min})", "amount_is_estimate": "El monto recibido es un estimado", "amount_is_guaranteed": "La cantidad recibida está garantizada", "and": "y", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.", "Blocks_remaining": "${status} Bloques restantes", "bright_theme": "Brillante", + "bump_fee": "Tarifa", "buy": "Comprar", "buy_alert_content": "Actualmente solo admitimos la compra de Bitcoin, Ethereum, Litecoin y Monero. Cree o cambie a su billetera Bitcoin, Ethereum, Litecoin o Monero.", "buy_bitcoin": "Comprar Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Confirmar", "confirm_delete_template": "Esta acción eliminará esta plantilla. ¿Desea continuar?", "confirm_delete_wallet": "Esta acción eliminará esta billetera. ¿Desea continuar?", + "confirm_fee_deduction": "Confirmar la deducción de la tarifa", + "confirm_fee_deduction_content": "¿Acepta deducir la tarifa de la producción?", "confirm_sending": "Confirmar envío", "confirmations": "Confirmaciones", "confirmed": "Saldo confirmado", @@ -171,6 +175,7 @@ "debit_card": "Tarjeta de Débito", "debit_card_terms": "El almacenamiento y el uso de su número de tarjeta de pago (y las credenciales correspondientes a su número de tarjeta de pago) en esta billetera digital están sujetos a los Términos y condiciones del acuerdo del titular de la tarjeta aplicable con el emisor de la tarjeta de pago, en vigor desde tiempo al tiempo.", "decimal_places_error": "Demasiados lugares decimales", + "decimals_cannot_be_zero": "Token Decimal no puede ser cero.", "default_buy_provider": "Proveedor de compra predeterminado", "default_sell_provider": "Proveedor de venta predeterminado", "delete": "Borrar", @@ -211,6 +216,7 @@ "edit_token": "Editar token", "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando", "email_address": "Dirección de correo electrónico", + "enable_replace_by_fee": "Habilitar reemplazar por tarea", "enabled": "Activado", "enter_amount": "Ingrese la cantidad", "enter_backup_password": "Ingrese la contraseña de respaldo aquí", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Error: error al obtener las credenciales", "errorSigningTransaction": "Se ha producido un error al firmar la transacción.", "estimated": "Estimado", + "estimated_new_fee": "Nueva tarifa estimada", "etherscan_history": "historia de etherscan", "event": "Evento", "events": "Eventos", @@ -313,6 +320,7 @@ "in_store": "En la tienda", "incoming": "Entrante", "incorrect_seed": "El texto ingresado no es válido.", + "inputs": "Entradas", "introducing_cake_pay": "¡Presentamos Cake Pay!", "invalid_input": "Entrada inválida", "invoice_details": "Detalles de la factura", @@ -380,6 +388,7 @@ "offer_expires_in": "Oferta expira en: ", "offline": "fuera de línea", "ok": "OK", + "old_fee": "Tarifa antigua", "onion_link": "Enlace de cebolla", "online": "En línea", "onramper_option_description": "Compre rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Las nuevas carteras de Bitcoin creadas en Cake ahora tienen una semilla de 24 palabras. Es obligatorio que cree una nueva billetera de Bitcoin y transfiera todos sus fondos a la nueva billetera de 24 palabras, y deje de usar billeteras con una semilla de 12 palabras. Haga esto de inmediato para asegurar sus fondos.", "outdated_electrum_wallet_receive_warning": "Si esta billetera tiene una semilla de 12 palabras y se creó en Cake, NO deposite Bitcoin en esta billetera. Cualquier BTC transferido a esta billetera se puede perder. Cree una nueva billetera de 24 palabras (toque el menú en la parte superior derecha, seleccione Monederos, elija Crear nueva billetera, luego seleccione Bitcoin) e INMEDIATAMENTE mueva su BTC allí. Las nuevas carteras BTC (24 palabras) de Cake son seguras", "outgoing": "Saliente", + "outputs": "Salidas", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Evento de emparejamiento no válido", "password": "Contraseña", @@ -644,6 +654,7 @@ "template_name": "Nombre de la plantilla", "third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!", "third_intro_title": "Yat juega muy bien con otras", + "thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.", "time": "${minutes}m ${seconds}s", "tip": "Consejo:", "today": "Hoy", @@ -661,6 +672,7 @@ "totp_code": "Código TOTP", "totp_secret_code": "Código secreto TOTP", "totp_verification_success": "¡Verificación exitosa!", + "track": "Pista", "trade_details_copied": "${title} Copiado al portapapeles", "trade_details_created_at": "Creado en", "trade_details_fetching": "Cargando", @@ -711,6 +723,16 @@ "transactions": "Actas", "transactions_by_date": "Transacciones por fecha", "trusted": "de confianza", + "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ó. Póngase en contacto con el soporte.", + "tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.", + "tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas", + "tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.", + "tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.", + "tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.", + "tx_rejected_vout_negative": "No es suficiente saldo para pagar las tarifas de esta transacción. Verifique el saldo de monedas bajo control de monedas.", + "tx_wrong_balance_exception": "No tiene suficiente ${currency} para enviar esta cantidad.", + "tx_zero_fee_exception": "No se puede enviar transacciones con 0 tarifa. Intente aumentar la tasa o verificar su conexión para las últimas estimaciones.", "unavailable_balance": "Saldo no disponible", "unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.", "unconfirmed": "Saldo no confirmado", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 384bb9585..edae901a3 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -43,6 +43,7 @@ "already_have_account": "Vous avez déjà un compte ?", "always": "toujours", "amount": "Montant : ", + "amount_is_below_minimum_limit": "Votre solde après les frais serait inférieur au montant minimum nécessaire à l'échange (${min})", "amount_is_estimate": "Le montant reçu est estimé", "amount_is_guaranteed": "Le montant reçu est garanti", "and": "et", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.", "Blocks_remaining": "Blocs Restants : ${status}", "bright_theme": "Vif", + "bump_fee": "Frais de bosse", "buy": "Acheter", "buy_alert_content": "Actuellement, nous ne prenons en charge que l'achat de Bitcoin, Ethereum, Litecoin et Monero. Veuillez créer ou basculer vers votre portefeuille Bitcoin, Ethereum, Litecoin ou Monero.", "buy_bitcoin": "Acheter du Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Confirmer", "confirm_delete_template": "Cette action va supprimer ce modèle. Souhaitez-vous continuer ?", "confirm_delete_wallet": "Cette action va supprimer ce portefeuille (wallet). Souhaitez-vous contnuer ?", + "confirm_fee_deduction": "Confirmer la déduction des frais", + "confirm_fee_deduction_content": "Acceptez-vous de déduire les frais de la production?", "confirm_sending": "Confirmer l'envoi", "confirmations": "Confirmations", "confirmed": "Solde confirmé", @@ -171,6 +175,7 @@ "debit_card": "Carte de débit", "debit_card_terms": "Le stockage et l'utilisation de votre numéro de carte de paiement (et des informations d'identification correspondant à votre numéro de carte de paiement) dans ce portefeuille (wallet) numérique peuvent être soumis aux conditions générales de l'accord du titulaire de carte parfois en vigueur avec l'émetteur de la carte de paiement.", "decimal_places_error": "Trop de décimales", + "decimals_cannot_be_zero": "La décimale du jeton ne peut pas être nulle.", "default_buy_provider": "Fournisseur d'achat par défaut", "default_sell_provider": "Fournisseur de vente par défaut", "delete": "Effacer", @@ -211,6 +216,7 @@ "edit_token": "Modifier le token", "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner", "email_address": "Adresse e-mail", + "enable_replace_by_fee": "Activer Remplace-by-Fee", "enabled": "Activé", "enter_amount": "Entrez le montant", "enter_backup_password": "Entrez le mot de passe de sauvegarde ici", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Échec : erreur lors de l'obtention des informations d'identification", "errorSigningTransaction": "Une erreur s'est produite lors de la signature de la transaction", "estimated": "Estimé", + "estimated_new_fee": "De nouveaux frais estimés", "etherscan_history": "Historique d'Etherscan", "event": "Événement", "events": "Événements", @@ -313,6 +320,7 @@ "in_store": "En magasin", "incoming": "Entrantes", "incorrect_seed": "Le texte entré est invalide.", + "inputs": "Contributions", "introducing_cake_pay": "Présentation de Cake Pay !", "invalid_input": "Entrée invalide", "invoice_details": "Détails de la facture", @@ -380,6 +388,7 @@ "offer_expires_in": "L'Offre expire dans : ", "offline": "Hors ligne", "ok": "OK", + "old_fee": "Anciens", "onion_link": "Lien .onion", "online": "En ligne", "onramper_option_description": "Achetez rapidement des cryptomonnaies avec de nombreuses méthodes de paiement. Disponible dans la plupart des pays. Les spreads et les frais peuvent varier.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Les nouveaux portefeuilles (wallets) Bitcoin créés dans Cake ont dorénavant une phrase secrète (seed) de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec une phrase secrète de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.", "outdated_electrum_wallet_receive_warning": "Si ce portefeuille (wallet) a une phrase secrète (seed) de 12 mots et a été créé dans Cake, NE PAS y déposer de Bitcoin. Tous les BTC transférés vers ce portefeuille seront perdus. Créez un nouveau portefeuille avec phrase secrète de 24 mots (appuyez sur le menu en haut à droite, sélectionnez Portefeuilles puis Créer un Nouveau Portefeuille et enfin Bitcoin) et transférez y IMMÉDIATEMENT vos BTC. Les nouveaux portefeuilles BTC Cake (avec phrase secrète de 24 mots) sont sécurisés", "outgoing": "Sortantes", + "outputs": "Les sorties", "overwrite_amount": "Remplacer le montant", "pairingInvalidEvent": "Événement de couplage non valide", "password": "Mot de passe", @@ -643,6 +653,7 @@ "template_name": "Nom du modèle", "third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !", "third_intro_title": "Yat est universel", + "thorchain_taproot_address_not_supported": "Le fournisseur de Thorchain ne prend pas en charge les adresses de tapoot. Veuillez modifier l'adresse ou sélectionner un autre fournisseur.", "time": "${minutes}m ${seconds}s", "tip": "Pourboire :", "today": "Aujourd'hui", @@ -660,6 +671,7 @@ "totp_code": "Code TOTP", "totp_secret_code": "Secret TOTP", "totp_verification_success": "Vérification réussie !", + "track": "Piste", "trade_details_copied": "${title} copié vers le presse-papier", "trade_details_created_at": "Créé le", "trade_details_fetching": "Récupération", @@ -710,6 +722,16 @@ "transactions": "Transactions", "transactions_by_date": "Transactions par date", "trusted": "de confiance", + "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_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.", + "tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control", + "tx_rejected_dust_change": "Transaction rejetée par les règles du réseau, montant de faible variation (poussière). Essayez d'envoyer tout ou de réduire le montant.", + "tx_rejected_dust_output": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez augmenter le montant.", + "tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.", + "tx_rejected_vout_negative": "Pas assez de solde pour payer les frais de cette transaction. Veuillez vérifier le solde des pièces sous le contrôle des pièces.", + "tx_wrong_balance_exception": "Vous n'avez pas assez ${currency} pour envoyer ce montant.", + "tx_zero_fee_exception": "Impossible d'envoyer une transaction avec 0 frais. Essayez d'augmenter le taux ou de vérifier votre connexion pour les dernières estimations.", "unavailable_balance": "Solde indisponible", "unavailable_balance_description": "Solde indisponible : ce total comprend les fonds bloqués dans les transactions en attente et ceux que vous avez activement gelés dans vos paramètres de contrôle des pièces. Les soldes bloqués deviendront disponibles une fois leurs transactions respectives terminées, tandis que les soldes gelés resteront inaccessibles aux transactions jusqu'à ce que vous décidiez de les débloquer.", "unconfirmed": "Solde non confirmé", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 7c0a7df12..c7174d23f 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -43,6 +43,7 @@ "already_have_account": "Kuna da asusu?", "always": "Koyaushe", "amount": "Adadi:", + "amount_is_below_minimum_limit": "Daidaitarku bayan kudade zai zama ƙasa da mafi ƙarancin adadin da ake buƙata don musayar (${min}", "amount_is_estimate": "Adadin da aka karɓa shine kimantawa", "amount_is_guaranteed": "Adadin da aka karɓa yana da garanti", "and": "kuma", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.", "Blocks_remaining": "${status} Katanga ya rage", "bright_theme": "Mai haske", + "bump_fee": "Buin", "buy": "Sayi", "buy_alert_content": "A halin yanzu muna tallafawa kawai siyan Bitcoin, Ethereum, Litecoin, da Monero. Da fatan za a ƙirƙiri ko canza zuwa Bitcoin, Ethereum, Litecoin, ko Monero walat.", "buy_bitcoin": "Sayi Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Tabbatar", "confirm_delete_template": "Wannan aikin zai share wannan samfuri. Kuna so ku ci gaba?", "confirm_delete_wallet": "Wannan aikin zai share wannan walat. Kuna so ku ci gaba?", + "confirm_fee_deduction": "Tabbatar da cire kudade", + "confirm_fee_deduction_content": "Shin kun yarda ku cire kuɗin daga fitarwa?", "confirm_sending": "Tabbatar da aikawa", "confirmations": "Tabbatar", "confirmed": "An tabbatar", @@ -171,6 +175,7 @@ "debit_card": "Katin Zare kudi", "debit_card_terms": "Adana da amfani da lambar katin kuɗin ku (da takaddun shaida masu dacewa da lambar katin kuɗin ku) a cikin wannan walat ɗin dijital suna ƙarƙashin Sharuɗɗa da Sharuɗɗa na yarjejeniya mai amfani da katin tare da mai fitar da katin biyan kuɗi, kamar yadda yake aiki daga lokaci zuwa lokaci.", "decimal_places_error": "Wadannan suna da tsawon harsuna", + "decimals_cannot_be_zero": "Alamar alama ba zata iya zama sifili ba.", "default_buy_provider": "Tsohuwar Siyarwa", "default_sell_provider": "Tsohuwar Mai Bayar Siyarwa", "delete": "Share", @@ -211,6 +216,7 @@ "edit_token": "Gyara alamar", "electrum_address_disclaimer": "Muna samar da sababbin adireshi duk lokacin da kuka yi amfani da ɗaya, amma adiresoshin da suka gabata suna ci gaba da aiki", "email_address": "Adireshin i-mel", + "enable_replace_by_fee": "Ba da damar maye gurbin-by-kudin", "enabled": "An kunna", "enter_amount": "Shigar da Adadi", "enter_backup_password": "Shigar da kalmar wucewa ta madadin nan", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Ba a yi nasara ba: Kuskure yayin samun takaddun shaida", "errorSigningTransaction": "An sami kuskure yayin sanya hannu kan ciniki", "estimated": "Kiyasta", + "estimated_new_fee": "An kiyasta sabon kudin", "etherscan_history": "Etherscan tarihin kowane zamani", "event": "Lamarin", "events": "Abubuwan da suka faru", @@ -313,6 +320,7 @@ "in_store": "A cikin Store", "incoming": "Mai shigowa", "incorrect_seed": "rubutun da aka shigar ba shi da inganci.", + "inputs": "Abubuwan da ke ciki", "introducing_cake_pay": "Gabatar da Cake Pay!", "invalid_input": "Shigar da ba daidai ba", "invoice_details": "Bayanin wadannan", @@ -380,6 +388,7 @@ "offer_expires_in": "tayin zai ƙare a:", "offline": "Offline", "ok": "OK", + "old_fee": "Tsohon kudin", "onion_link": "Lambar onion", "online": "Kan layi", "onramper_option_description": "Da sauri sayi Crypto tare da hanyoyin biyan kuɗi da yawa. Akwai a yawancin ƙasashe. Yaduwa da kudade sun bambanta.", @@ -398,6 +407,7 @@ "outdated_electrum_wallet_description": "Sabbin walat ɗin Bitcoin da aka kirkira a cikin Cake yanzu suna da nau'in kalma 24. Ya zama dole ka ƙirƙiri sabon walat ɗin Bitcoin kuma canza duk kuɗin ku zuwa sabon walat ɗin kalmomi 24, kuma ku daina amfani da walat tare da iri mai kalma 12. Da fatan za a yi haka nan take don samun kuɗin ku.", "outdated_electrum_wallet_receive_warning": "Idan wannan walat ɗin yana da nau'in kalma 12 kuma an ƙirƙira shi a cikin Cake, KAR KA saka Bitcoin cikin wannan jakar. Duk wani BTC da aka canjawa wuri zuwa wannan walat na iya ɓacewa. Ƙirƙiri sabon walat mai kalmomi 24 (matsa menu a saman dama, zaɓi Wallets, zaɓi Ƙirƙiri Sabon Wallet, sannan zaɓi Bitcoin) kuma NAN nan take matsar da BTC ɗin ku a can. Sabbin (kalmomi 24) BTC wallets daga Cake suna da tsaro", "outgoing": "Mai fita", + "outputs": "Abubuwan fashewa", "overwrite_amount": "Rubuta adadin", "pairingInvalidEvent": "Haɗa Lamarin mara inganci", "password": "Kalmar wucewa", @@ -645,6 +655,7 @@ "template_name": "Sunan Samfura", "third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!", "third_intro_title": "Yat yana wasa da kyau tare da wasu", + "thorchain_taproot_address_not_supported": "Mai ba da tallafi na ThorChain baya goyan bayan adreshin taproot. Da fatan za a canza adireshin ko zaɓi mai bayarwa daban.", "time": "${minutes}m ${seconds}s", "tip": "Tukwici:", "today": "Yau", @@ -662,6 +673,7 @@ "totp_code": "Lambar totp", "totp_secret_code": "Lambar sirri", "totp_verification_success": "Tabbatar cin nasara!", + "track": "Waƙa", "trade_details_copied": "${title} an kwafa zuwa cikin kwafin", "trade_details_created_at": "An ƙirƙira a", "trade_details_fetching": "Daukewa", @@ -712,6 +724,16 @@ "transactions": "Ma'amaloli", "transactions_by_date": "Ma'amaloli ta kwanan wata", "trusted": "Amintacce", + "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_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.", + "tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin", + "tx_rejected_dust_change": "Ma'amala ta ƙi ta dokokin cibiyar sadarwa, ƙarancin canji (ƙura). Gwada aikawa da duka ko rage adadin.", + "tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.", + "tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", + "tx_rejected_vout_negative": "Bai isa daidai ba don biyan wannan kudin ma'amala. Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", + "tx_wrong_balance_exception": "Ba ku da isasshen ${currency} don aika wannan adadin.", + "tx_zero_fee_exception": "Ba zai iya aika ma'amala da kuɗi 0 ba. Gwada ƙara ƙimar ko bincika haɗin ku don mahimmin ƙididdiga.", "unavailable_balance": "Ma'aunin da ba ya samuwa", "unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.", "unconfirmed": "Ba a tabbatar ba", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 932fb25e7..4ee9bd4f8 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -43,6 +43,7 @@ "already_have_account": "क्या आपके पास पहले से एक खाता मौजूद है?", "always": "हमेशा", "amount": "रकम: ", + "amount_is_below_minimum_limit": "फीस के बाद आपका संतुलन विनिमय के लिए आवश्यक न्यूनतम राशि से कम होगा (${min})", "amount_is_estimate": "प्राप्त राशि एक अनुमान है", "amount_is_guaranteed": "प्राप्त राशि की गारंटी है", "and": "और", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।", "Blocks_remaining": "${status} शेष रहते हैं", "bright_theme": "उज्ज्वल", + "bump_fee": "बम्प फीस", "buy": "खरीदें", "buy_alert_content": "वर्तमान में हम केवल बिटकॉइन, एथेरियम, लाइटकॉइन और मोनेरो की खरीद का समर्थन करते हैं। कृपया अपना बिटकॉइन, एथेरियम, लाइटकॉइन, या मोनेरो वॉलेट बनाएं या उस पर स्विच करें।", "buy_bitcoin": "बिटकॉइन खरीदें", @@ -132,6 +134,8 @@ "confirm": "की पुष्टि करें", "confirm_delete_template": "यह क्रिया इस टेम्पलेट को हटा देगी। क्या आप जारी रखना चाहते हैं?", "confirm_delete_wallet": "यह क्रिया इस वॉलेट को हटा देगी। क्या आप जारी रखना चाहते हैं?", + "confirm_fee_deduction": "शुल्क कटौती की पुष्टि करें", + "confirm_fee_deduction_content": "क्या आप आउटपुट से शुल्क में कटौती करने के लिए सहमत हैं?", "confirm_sending": "भेजने की पुष्टि करें", "confirmations": "पुष्टिकरण", "confirmed": "पुष्टि की गई शेष राशिी", @@ -171,6 +175,7 @@ "debit_card": "डेबिट कार्ड", "debit_card_terms": "इस डिजिटल वॉलेट में आपके भुगतान कार्ड नंबर (और आपके भुगतान कार्ड नंबर से संबंधित क्रेडेंशियल) का भंडारण और उपयोग भुगतान कार्ड जारीकर्ता के साथ लागू कार्डधारक समझौते के नियमों और शर्तों के अधीन है, जैसा कि प्रभावी है समय - समय पर।", "decimal_places_error": "बहुत अधिक दशमलव स्थान", + "decimals_cannot_be_zero": "टोकन दशमलव शून्य नहीं हो सकता।", "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता", "delete": "हटाएं", @@ -211,6 +216,7 @@ "edit_token": "टोकन संपादित करें", "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", "email_address": "ईमेल पता", + "enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें", "enabled": "सक्रिय", "enter_amount": "राशि दर्ज करें", "enter_backup_password": "यहां बैकअप पासवर्ड डालें", @@ -247,6 +253,7 @@ "errorGettingCredentials": "विफल: क्रेडेंशियल प्राप्त करते समय त्रुटि", "errorSigningTransaction": "लेन-देन पर हस्ताक्षर करते समय एक त्रुटि उत्पन्न हुई है", "estimated": "अनुमानित", + "estimated_new_fee": "अनुमानित नया शुल्क", "etherscan_history": "इथरस्कैन इतिहास", "event": "आयोजन", "events": "आयोजन", @@ -313,6 +320,7 @@ "in_store": "स्टोर में", "incoming": "आने वाली", "incorrect_seed": "दर्ज किया गया पाठ मान्य नहीं है।", + "inputs": "इनपुट", "introducing_cake_pay": "परिचय Cake Pay!", "invalid_input": "अमान्य निवेश", "invoice_details": "चालान विवरण", @@ -380,6 +388,7 @@ "offer_expires_in": "में ऑफर समाप्त हो रहा है: ", "offline": "ऑफ़लाइन", "ok": "ठीक है", + "old_fee": "पुराना फीस", "onion_link": "प्याज का लिंक", "online": "ऑनलाइन", "onramper_option_description": "जल्दी से कई भुगतान विधियों के साथ क्रिप्टो खरीदें। अधिकांश देशों में उपलब्ध है। फैलता है और फीस अलग -अलग होती है।", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "केक में बनाए गए नए बिटकॉइन वॉलेट में अब 24-शब्द का बीज है। यह अनिवार्य है कि आप एक नया बिटकॉइन वॉलेट बनाएं और अपने सभी फंड को नए 24-शब्द वाले वॉलेट में स्थानांतरित करें, और 12-शब्द बीज वाले वॉलेट का उपयोग करना बंद करें। कृपया अपने धन को सुरक्षित करने के लिए इसे तुरंत करें।", "outdated_electrum_wallet_receive_warning": "अगर इस वॉलेट में 12 शब्दों का बीज है और इसे केक में बनाया गया है, तो इस वॉलेट में बिटकॉइन जमा न करें। इस वॉलेट में स्थानांतरित किया गया कोई भी बीटीसी खो सकता है। एक नया 24-शब्द वॉलेट बनाएं (ऊपर दाईं ओर स्थित मेनू पर टैप करें, वॉलेट चुनें, नया वॉलेट बनाएं चुनें, फिर बिटकॉइन चुनें) और तुरंत अपना बीटीसी वहां ले जाएं। केक से नए (24-शब्द) बीटीसी वॉलेट सुरक्षित हैं", "outgoing": "निवर्तमान", + "outputs": "आउटपुट", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना", "password": "पारण शब्द", @@ -645,6 +655,7 @@ "template_name": "टेम्पलेट नाम", "third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!", "third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है", + "thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।", "time": "${minutes}m ${seconds}s", "tip": "टिप:", "today": "आज", @@ -662,6 +673,7 @@ "totp_code": "टीओटीपी कोड", "totp_secret_code": "टीओटीपी गुप्त कोड", "totp_verification_success": "सत्यापन सफल!", + "track": "रास्ता", "trade_details_copied": "${title} क्लिपबोर्ड पर नकल", "trade_details_created_at": "पर बनाया गया", "trade_details_fetching": "ला रहा है", @@ -712,6 +724,16 @@ "transactions": "लेन-देन", "transactions_by_date": "तारीख से लेन-देन", "trusted": "भरोसा", + "tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।", + "tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।", + "tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।", + "tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें", + "tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।", + "tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।", + "tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।", + "tx_rejected_vout_negative": "इस लेनदेन की फीस के लिए भुगतान करने के लिए पर्याप्त शेष राशि नहीं है। कृपया सिक्के नियंत्रण के तहत सिक्कों के संतुलन की जाँच करें।", + "tx_wrong_balance_exception": "इस राशि को भेजने के लिए आपके पास पर्याप्त ${currency} नहीं है।", + "tx_zero_fee_exception": "0 शुल्क के साथ लेनदेन नहीं भेज सकते। नवीनतम अनुमानों के लिए दर बढ़ाने या अपने कनेक्शन की जांच करने का प्रयास करें।", "unavailable_balance": "अनुपलब्ध शेष", "unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।", "unconfirmed": "अपुष्ट शेष राशि", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index c8c9a00bd..a4de56dcf 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -43,6 +43,7 @@ "already_have_account": "Već imate račun?", "always": "Uvijek", "amount": "Iznos: ", + "amount_is_below_minimum_limit": "Vaša bilanca nakon naknada bila bi manja od minimalnog iznosa potrebnog za razmjenu (${min})", "amount_is_estimate": "Iznos koji ćete primiti je okviran", "amount_is_guaranteed": "Iznos koji ćete primiti je zajamčen", "and": "i", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin plaćanja zahtijevaju 1 potvrdu, što može potrajati 20 minuta ili dulje. Hvala na Vašem strpljenju! Dobit ćete e-poruku kada plaćanje bude potvrđeno.", "Blocks_remaining": "${status} preostalih blokova", "bright_theme": "Jarka", + "bump_fee": "Naplata", "buy": "Kupi", "buy_alert_content": "Trenutno podržavamo samo kupnju Bitcoina, Ethereuma, Litecoina i Monera. Izradite ili prijeđite na svoj Bitcoin, Ethereum, Litecoin ili Monero novčanik.", "buy_bitcoin": "Kupite Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Potvrdi", "confirm_delete_template": "Ovom ćete radnjom izbrisati ovaj predložak. Želite li nastaviti?", "confirm_delete_wallet": "Ovom ćete radnjom izbrisati ovaj novčanik. Želite li nastaviti?", + "confirm_fee_deduction": "Potvrdite odbitak naknade", + "confirm_fee_deduction_content": "Slažete li se da ćete odbiti naknadu od izlaza?", "confirm_sending": "Potvrdi slanje", "confirmations": "Potvrde", "confirmed": "Potvrđeno stanje", @@ -171,6 +175,7 @@ "debit_card": "Debitna kartica", "debit_card_terms": "Pohranjivanje i korištenje broja vaše platne kartice (i vjerodajnica koje odgovaraju broju vaše platne kartice) u ovom digitalnom novčaniku podliježu Uvjetima i odredbama važećeg ugovora vlasnika kartice s izdavateljem platne kartice, koji su na snazi ​​od S vremena na vrijeme.", "decimal_places_error": "Previše decimalnih mjesta", + "decimals_cannot_be_zero": "Token Decimal ne može biti nula.", "default_buy_provider": "Zadani davatelj kupnje", "default_sell_provider": "Zadani dobavljač prodaje", "delete": "Izbriši", @@ -211,6 +216,7 @@ "edit_token": "Uredi token", "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek", "email_address": "Adresa e-pošte", + "enable_replace_by_fee": "Omogući zamjenu", "enabled": "Omogućeno", "enter_amount": "Unesite iznos", "enter_backup_password": "Unesite svoju lozinku za sigurnosnu kopiju ovdje", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Neuspješno: Pogreška prilikom dobivanja vjerodajnica", "errorSigningTransaction": "Došlo je do pogreške prilikom potpisivanja transakcije", "estimated": "procijenjen", + "estimated_new_fee": "Procijenjena nova naknada", "etherscan_history": "Etherscan povijest", "event": "Događaj", "events": "Događaji", @@ -313,6 +320,7 @@ "in_store": "U trgovini", "incoming": "Dolazno", "incorrect_seed": "Uneseni tekst nije valjan.", + "inputs": "Unosi", "introducing_cake_pay": "Predstavljamo Cake Pay!", "invalid_input": "Pogrešan unos", "invoice_details": "Podaci o fakturi", @@ -380,6 +388,7 @@ "offer_expires_in": "Ponuda istječe za: ", "offline": "izvan mreže", "ok": "OK", + "old_fee": "Stara naknada", "onion_link": "Poveznica luka", "online": "Na mreži", "onramper_option_description": "Brzo kupite kriptovalute s mnogim načinima plaćanja. Dostupno u većini zemalja. Širenja i naknade variraju.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Novi Bitcoin novčanici stvoreni u Cakeu sada imaju sjeme od 24 riječi. Obavezno je stvoriti novi Bitcoin novčanik i prenijeti sva svoja sredstva u novi novčanik od 24 riječi te prestati koristiti novčanike s sjemenkom od 12 riječi. Učinite to odmah kako biste osigurali svoja sredstva.", "outdated_electrum_wallet_receive_warning": "Ako ovaj novčanik sadrži sjeme od 12 riječi i stvoren je u Torti, NEMOJTE polagati Bitcoin u ovaj novčanik. Bilo koji BTC prebačen u ovaj novčanik može se izgubiti. Stvorite novi novčanik od 24 riječi (taknite izbornik u gornjem desnom dijelu, odaberite Novčanici, odaberite Stvori novi novčanik, a zatim odaberite Bitcoin) i ODMAH premjestite svoj BTC tamo. Novi BTC novčanici (s 24 riječi) tvrtke Cake sigurni su", "outgoing": "Odlazno", + "outputs": "Izlazi", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Nevažeći događaj uparivanja", "password": "Lozinka", @@ -643,6 +653,7 @@ "template_name": "Naziv predloška", "third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!", "third_intro_title": "Yat se lijepo igra s drugima", + "thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.", "time": "${minutes}m ${seconds}s", "tip": "Savjet:", "today": "Danas", @@ -660,6 +671,7 @@ "totp_code": "TOTP kod", "totp_secret_code": "TOTP tajni kod", "totp_verification_success": "Provjera uspješna!", + "track": "Staza", "trade_details_copied": "${title} kopiran u međuspremnik", "trade_details_created_at": "Stvoreno u", "trade_details_fetching": "Dohvaćanje", @@ -710,6 +722,16 @@ "transactions": "Transakcije", "transactions_by_date": "Transakcije prema datumu", "trusted": "vjerovao", + "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_no_dust_exception": "Transakcija se odbija slanjem iznosa premalo. Pokušajte povećati iznos.", + "tx_not_enough_inputs_exception": "Nema dovoljno unosa. Molimo odaberite više pod kontrolom novčića", + "tx_rejected_dust_change": "Transakcija odbijena mrežnim pravilima, niska količina promjene (prašina). Pokušajte poslati sve ili smanjiti iznos.", + "tx_rejected_dust_output": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo povećajte iznos.", + "tx_rejected_dust_output_send_all": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo provjerite ravnotežu kovanica odabranih pod kontrolom novčića.", + "tx_rejected_vout_negative": "Nema dovoljno salda za plaćanje naknada ove transakcije. Molimo provjerite ravnotežu kovanica pod kontrolom novčića.", + "tx_wrong_balance_exception": "Nemate dovoljno ${currency} da biste poslali ovaj iznos.", + "tx_zero_fee_exception": "Ne mogu poslati transakciju s 0 naknade. Pokušajte povećati stopu ili provjeriti vezu za najnovije procjene.", "unavailable_balance": "Nedostupno stanje", "unavailable_balance_description": "Nedostupno stanje: Ovaj ukupni iznos uključuje sredstva koja su zaključana u transakcijama na čekanju i ona koja ste aktivno zamrznuli u postavkama kontrole novčića. Zaključani saldi postat će dostupni kada se dovrše njihove transakcije, dok zamrznuti saldi ostaju nedostupni za transakcije sve dok ih ne odlučite odmrznuti.", "unconfirmed": "Nepotvrđeno stanje", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 2168de910..411daf9e0 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -43,6 +43,7 @@ "already_have_account": "Sudah punya akun?", "always": "Selalu", "amount": "Jumlah: ", + "amount_is_below_minimum_limit": "Saldo Anda setelah biaya akan kurang dari jumlah minimum yang dibutuhkan untuk pertukaran (${min})", "amount_is_estimate": "Jumlah penerimaan diperkirakan", "amount_is_guaranteed": "Jumlah penerimaan dijamin", "and": "dan", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Pembayaran Bitcoin memerlukan 1 konfirmasi, yang bisa memakan waktu 20 menit atau lebih. Terima kasih atas kesabaran Anda! Anda akan diemail saat pembayaran dikonfirmasi.", "Blocks_remaining": "${status} Blok Tersisa", "bright_theme": "Cerah", + "bump_fee": "Biaya benjolan", "buy": "Beli", "buy_alert_content": "Saat ini kami hanya mendukung pembelian Bitcoin, Ethereum, Litecoin, dan Monero. Harap buat atau alihkan ke dompet Bitcoin, Ethereum, Litecoin, atau Monero Anda.", "buy_bitcoin": "Beli Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Konfirmasi", "confirm_delete_template": "Tindakan ini akan menghapus template ini. Apakah Anda ingin melanjutkan?", "confirm_delete_wallet": "Tindakan ini akan menghapus dompet ini. Apakah Anda ingin melanjutkan?", + "confirm_fee_deduction": "Konfirmasi pengurangan biaya", + "confirm_fee_deduction_content": "Apakah Anda setuju untuk mengurangi biaya dari output?", "confirm_sending": "Konfirmasi pengiriman", "confirmations": "Konfirmasi", "confirmed": "Saldo Terkonfirmasi", @@ -171,6 +175,7 @@ "debit_card": "Kartu Debit", "debit_card_terms": "Penyimpanan dan penggunaan nomor kartu pembayaran Anda (dan kredensial yang sesuai dengan nomor kartu pembayaran Anda) dalam dompet digital ini tertakluk pada Syarat dan Ketentuan persetujuan pemegang kartu yang berlaku dengan penerbit kartu pembayaran, seperti yang berlaku dari waktu ke waktu.", "decimal_places_error": "Terlalu banyak tempat desimal", + "decimals_cannot_be_zero": "Token desimal tidak bisa nol.", "default_buy_provider": "Penyedia beli default", "default_sell_provider": "Penyedia Penjualan Default", "delete": "Hapus", @@ -211,6 +216,7 @@ "edit_token": "Mengedit token", "electrum_address_disclaimer": "Kami menghasilkan alamat baru setiap kali Anda menggunakan satu, tetapi alamat sebelumnya tetap berfungsi", "email_address": "Alamat Email", + "enable_replace_by_fee": "Aktifkan ganti-by-fee", "enabled": "Diaktifkan", "enter_amount": "Masukkan Jumlah", "enter_backup_password": "Masukkan kata sandi cadangan di sini", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Gagal: Terjadi kesalahan saat mendapatkan kredensial", "errorSigningTransaction": "Terjadi kesalahan saat menandatangani transaksi", "estimated": "Diperkirakan", + "estimated_new_fee": "Perkiraan biaya baru", "etherscan_history": "Sejarah Etherscan", "event": "Peristiwa", "events": "Acara", @@ -313,6 +320,7 @@ "in_store": "Di Toko", "incoming": "Masuk", "incorrect_seed": "Teks yang dimasukkan tidak valid.", + "inputs": "Input", "introducing_cake_pay": "Perkenalkan Cake Pay!", "invalid_input": "Masukan tidak valid", "invoice_details": "Detail faktur", @@ -380,6 +388,7 @@ "offer_expires_in": "Penawaran kedaluwarsa dalam: ", "offline": "Offline", "ok": "OK", + "old_fee": "Biaya lama", "onion_link": "Tautan bawang", "online": "Online", "onramper_option_description": "Beli crypto dengan cepat dengan banyak metode pembayaran. Tersedia di sebagian besar negara. Spread dan biaya bervariasi.", @@ -398,6 +407,7 @@ "outdated_electrum_wallet_description": "Dompet Bitcoin baru yang dibuat di Cake sekarang memiliki biji semai 24 kata. Wajib bagi Anda untuk membuat dompet Bitcoin baru dan mentransfer semua dana Anda ke dompet 24 kata baru, dan berhenti menggunakan dompet dengan biji semai 12 kata. Silakan lakukan ini segera untuk mengamankan dana Anda.", "outdated_electrum_wallet_receive_warning": "Jika dompet ini memiliki biji semai 12 kata dan dibuat di Cake, JANGAN deposit Bitcoin ke dalam dompet ini. BTC apapun yang ditransfer ke dompet ini mungkin hilang. Buat dompet 24 kata baru (ketuk menu di pojok kanan atas, pilih Dompet, pilih Buat Dompet Baru, lalu pilih Bitcoin) dan SEGERA pindahkan BTC Anda ke sana. Dompet BTC (24 kata) baru dari Cake aman", "outgoing": "Keluar", + "outputs": "Output", "overwrite_amount": "Timpa jumlah", "pairingInvalidEvent": "Menyandingkan Acara Tidak Valid", "password": "Kata Sandi", @@ -646,6 +656,7 @@ "template_name": "Nama Templat", "third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!", "third_intro_title": "Yat bermain baik dengan yang lain", + "thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.", "time": "${minutes}m ${seconds}s", "tip": "Tip:", "today": "Hari ini", @@ -663,6 +674,7 @@ "totp_code": "Kode TOTP", "totp_secret_code": "Kode Rahasia TOTP", "totp_verification_success": "Verifikasi Berhasil!", + "track": "Melacak", "trade_details_copied": "${title} disalin ke Clipboard", "trade_details_created_at": "Dibuat pada", "trade_details_fetching": "Mengambil", @@ -713,6 +725,16 @@ "transactions": "Transaksi", "transactions_by_date": "Transaksi berdasarkan tanggal", "trusted": "Dipercayai", + "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_no_dust_exception": "Transaksi ditolak dengan mengirimkan jumlah yang terlalu kecil. Silakan coba tingkatkan jumlahnya.", + "tx_not_enough_inputs_exception": "Tidak cukup input yang tersedia. Pilih lebih banyak lagi di bawah Kontrol Koin", + "tx_rejected_dust_change": "Transaksi ditolak oleh aturan jaringan, jumlah perubahan rendah (debu). Coba kirim semua atau mengurangi jumlahnya.", + "tx_rejected_dust_output": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Harap tingkatkan jumlahnya.", + "tx_rejected_dust_output_send_all": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Silakan periksa saldo koin yang dipilih di bawah kontrol koin.", + "tx_rejected_vout_negative": "Tidak cukup saldo untuk membayar biaya transaksi ini. Silakan periksa saldo koin di bawah kendali koin.", + "tx_wrong_balance_exception": "Anda tidak memiliki cukup ${currency} untuk mengirim jumlah ini.", + "tx_zero_fee_exception": "Tidak dapat mengirim transaksi dengan biaya 0. Coba tingkatkan tarif atau periksa koneksi Anda untuk perkiraan terbaru.", "unavailable_balance": "Saldo tidak tersedia", "unavailable_balance_description": "Saldo Tidak Tersedia: Total ini termasuk dana yang terkunci dalam transaksi yang tertunda dan dana yang telah Anda bekukan secara aktif di pengaturan kontrol koin Anda. Saldo yang terkunci akan tersedia setelah transaksi masing-masing selesai, sedangkan saldo yang dibekukan tetap tidak dapat diakses untuk transaksi sampai Anda memutuskan untuk mencairkannya.", "unconfirmed": "Saldo Belum Dikonfirmasi", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 2700203a0..7cf298cae 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -43,6 +43,7 @@ "already_have_account": "Hai già un account?", "always": "sempre", "amount": "Ammontare: ", + "amount_is_below_minimum_limit": "Il saldo dopo le commissioni sarebbe inferiore all'importo minimo necessario per lo scambio (${min})", "amount_is_estimate": "L'ammontare da ricevere è una stima", "amount_is_guaranteed": "L'ammontare da ricevere è fisso", "and": "e", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "I pagamenti in bitcoin richiedono 1 conferma, che può richiedere 20 minuti o più. Grazie per la vostra pazienza! Riceverai un'e-mail quando il pagamento sarà confermato.", "Blocks_remaining": "${status} Blocchi Rimanenti", "bright_theme": "Colorato", + "bump_fee": "Commissione per bump", "buy": "Comprare", "buy_alert_content": "Attualmente supportiamo solo l'acquisto di Bitcoin, Ethereum, Litecoin e Monero. Crea o passa al tuo portafoglio Bitcoin, Ethereum, Litecoin o Monero.", "buy_bitcoin": "Acquista Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Conferma", "confirm_delete_template": "Questa azione cancellerà questo modello. Desideri continuare?", "confirm_delete_wallet": "Questa azione cancellerà questo portafoglio. Desideri continuare?", + "confirm_fee_deduction": "Conferma la detrazione delle commissioni", + "confirm_fee_deduction_content": "Accetti di detrarre la commissione dall'output?", "confirm_sending": "Conferma l'invio", "confirmations": "Conferme", "confirmed": "Saldo confermato", @@ -172,6 +176,7 @@ "debit_card": "Carta di debito", "debit_card_terms": "L'archiviazione e l'utilizzo del numero della carta di pagamento (e delle credenziali corrispondenti al numero della carta di pagamento) in questo portafoglio digitale sono soggetti ai Termini e condizioni del contratto applicabile con il titolare della carta con l'emittente della carta di pagamento, come in vigore da tempo al tempo.", "decimal_places_error": "Troppe cifre decimali", + "decimals_cannot_be_zero": "Il decimale token non può essere zero.", "default_buy_provider": "Provider di acquisto predefinito", "default_sell_provider": "Fornitore di vendita predefinito", "delete": "Elimina", @@ -212,6 +217,7 @@ "edit_token": "Modifica token", "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare", "email_address": "Indirizzo e-mail", + "enable_replace_by_fee": "Abilita sostituzione per fee", "enabled": "Abilitato", "enter_amount": "Inserisci importo", "enter_backup_password": "Inserisci la password di backup qui", @@ -248,6 +254,7 @@ "errorGettingCredentials": "Non riuscito: errore durante il recupero delle credenziali", "errorSigningTransaction": "Si è verificato un errore durante la firma della transazione", "estimated": "Stimato", + "estimated_new_fee": "Nuova commissione stimata", "etherscan_history": "Storia Etherscan", "event": "Evento", "events": "Eventi", @@ -314,6 +321,7 @@ "in_store": "In negozio", "incoming": "In arrivo", "incorrect_seed": "Il testo inserito non è valido.", + "inputs": "Input", "introducing_cake_pay": "Presentazione di Cake Pay!", "invalid_input": "Inserimento non valido", "invoice_details": "Dettagli della fattura", @@ -381,6 +389,7 @@ "offer_expires_in": "Offerta termina tra: ", "offline": "Offline", "ok": "OK", + "old_fee": "Vecchia tassa", "onion_link": "Collegamento a cipolla", "online": "in linea", "onramper_option_description": "Acquista rapidamente la criptovaluta con molti metodi di pagamento. Disponibile nella maggior parte dei paesi. Gli spread e le commissioni variano.", @@ -398,6 +407,7 @@ "outdated_electrum_wallet_description": "I nuovi portafogli Bitcoin creati in Cake ora hanno un seme di 24 parole. È obbligatorio creare un nuovo portafoglio Bitcoin e trasferire tutti i fondi nel nuovo portafoglio di 24 parole e smettere di usare portafogli con un seme di 12 parole. Ti preghiamo di farlo immediatamente per proteggere i tuoi fondi.", "outdated_electrum_wallet_receive_warning": "Se questo portafoglio ha un seme di 12 parole ed è stato creato in Cake, NON depositare Bitcoin in questo portafoglio. Qualsiasi BTC trasferito su questo portafoglio potrebbe andare perso. Crea un nuovo portafoglio di 24 parole (tocca il menu in alto a destra, seleziona Portafogli, scegli Crea nuovo portafoglio, quindi seleziona Bitcoin) e sposta IMMEDIATAMENTE lì il tuo BTC. I nuovi portafogli BTC (24 parole) di Cake sono sicuri", "outgoing": "In uscita", + "outputs": "Output", "overwrite_amount": "Sovrascrivi quantità", "pairingInvalidEvent": "Associazione evento non valido", "password": "Password", @@ -645,6 +655,7 @@ "template_name": "Nome modello", "third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!", "third_intro_title": "Yat gioca bene con gli altri", + "thorchain_taproot_address_not_supported": "Il provider di Thorchain non supporta gli indirizzi di TapRoot. Si prega di modificare l'indirizzo o selezionare un fornitore diverso.", "time": "${minutes}m ${seconds}s", "tip": "Suggerimento:", "today": "Oggi", @@ -662,6 +673,7 @@ "totp_code": "Codice TOTP", "totp_secret_code": "TOTP codice segreto", "totp_verification_success": "Verifica riuscita!", + "track": "Traccia", "trade_details_copied": "${title} copiati negli Appunti", "trade_details_created_at": "Creato alle", "trade_details_fetching": "Recupero", @@ -712,6 +724,16 @@ "transactions": "Transazioni", "transactions_by_date": "Transazioni per data", "trusted": "di fiducia", + "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_no_dust_exception": "La transazione viene respinta inviando un importo troppo piccolo. Per favore, prova ad aumentare l'importo.", + "tx_not_enough_inputs_exception": "Input non sufficienti disponibili. Seleziona di più sotto il controllo delle monete", + "tx_rejected_dust_change": "Transazione respinta dalle regole di rete, quantità bassa variazione (polvere). Prova a inviare tutto o ridurre l'importo.", + "tx_rejected_dust_output": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di aumentare l'importo.", + "tx_rejected_dust_output_send_all": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di controllare il saldo delle monete selezionate sotto controllo delle monete.", + "tx_rejected_vout_negative": "Non abbastanza saldo per pagare le commissioni di questa transazione. Si prega di controllare il saldo delle monete sotto controllo delle monete.", + "tx_wrong_balance_exception": "Non hai abbastanza ${currency} per inviare questo importo.", + "tx_zero_fee_exception": "Impossibile inviare transazioni con 0 tassa. Prova ad aumentare la tariffa o controlla la connessione per le ultime stime.", "unavailable_balance": "Saldo non disponibile", "unavailable_balance_description": "Saldo non disponibile: questo totale include i fondi bloccati nelle transazioni in sospeso e quelli che hai congelato attivamente nelle impostazioni di controllo delle monete. I saldi bloccati diventeranno disponibili una volta completate le rispettive transazioni, mentre i saldi congelati rimarranno inaccessibili per le transazioni finché non deciderai di sbloccarli.", "unconfirmed": "Saldo non confermato", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 443ce6658..42377fa85 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -43,6 +43,7 @@ "already_have_account": "すでにアカウントをお持ちですか?", "always": "いつも", "amount": "量: ", + "amount_is_below_minimum_limit": "手数料後の残高は、交換に必要な最低額(${min})よりも少なくなります", "amount_is_estimate": "受け取り金額は見積もりです", "amount_is_guaranteed": "受け取り金額は保証されています", "and": "と", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "ビットコインの支払いには 1 回の確認が必要で、これには 20 分以上かかる場合があります。お待ち頂きまして、ありがとうございます!支払いが確認されると、メールが送信されます。", "Blocks_remaining": "${status} 残りのブロック", "bright_theme": "明るい", + "bump_fee": "バンプ料金", "buy": "購入", "buy_alert_content": "現在、ビットコイン、イーサリアム、ライトコイン、モネロの購入のみをサポートしています。ビットコイン、イーサリアム、ライトコイン、またはモネロのウォレットを作成するか、これらのウォレットに切り替えてください。", "buy_bitcoin": "ビットコインを購入する", @@ -132,6 +134,8 @@ "confirm": "確認する", "confirm_delete_template": "この操作により、このテンプレートが削除されます。 続行しますか?", "confirm_delete_wallet": "このアクションにより、このウォレットが削除されます。 続行しますか?", + "confirm_fee_deduction": "料金控除を確認します", + "confirm_fee_deduction_content": "出力から料金を差し引くことに同意しますか?", "confirm_sending": "送信を確認", "confirmations": "確認", "confirmed": "確認済み残高", @@ -171,6 +175,7 @@ "debit_card": "デビットカード", "debit_card_terms": "このデジタルウォレットでの支払いカード番号(および支払いカード番号に対応する資格情報)の保存と使用には、支払いカード発行者との該当するカード所有者契約の利用規約が適用されます。時々。", "decimal_places_error": "小数点以下の桁数が多すぎる", + "decimals_cannot_be_zero": "トークン小数はゼロにすることはできません。", "default_buy_provider": "デフォルトの購入プロバイダー", "default_sell_provider": "デフォルトの販売プロバイダー", "delete": "削除する", @@ -211,6 +216,7 @@ "edit_token": "トークンの編集", "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します", "email_address": "メールアドレス", + "enable_replace_by_fee": "交換ごとに有効にします", "enabled": "有効", "enter_amount": "金額を入力", "enter_backup_password": "ここにバックアップパスワードを入力してください", @@ -247,6 +253,7 @@ "errorGettingCredentials": "失敗: 認証情報の取得中にエラーが発生しました", "errorSigningTransaction": "トランザクションの署名中にエラーが発生しました", "estimated": "推定", + "estimated_new_fee": "推定新しい料金", "etherscan_history": "イーサスキャンの歴史", "event": "イベント", "events": "イベント", @@ -314,6 +321,7 @@ "in_store": "インストア", "incoming": "着信", "incorrect_seed": "入力されたテキストは無効です。", + "inputs": "入力", "introducing_cake_pay": "序章Cake Pay!", "invalid_input": "無効入力", "invoice_details": "請求の詳細", @@ -381,6 +389,7 @@ "offer_expires_in": "で有効期限が切れます: ", "offline": "オフライン", "ok": "OK", + "old_fee": "古い料金", "onion_link": "オニオンリンク", "online": "オンライン", "onramper_option_description": "多くの支払い方法で暗号をすばやく購入してください。ほとんどの国で利用可能です。スプレッドと料金は異なります。", @@ -397,6 +406,7 @@ "outdated_electrum_wallet_description": "Cakeで作成された新しいビットコインウォレットには、24ワードのシードがあります。 新しいビットコインウォレットを作成し、すべての資金を新しい24ワードのウォレットに転送し、12ワードのシードを持つウォレットの使用を停止することが必須です。 あなたの資金を確保するためにこれをすぐに行ってください。", "outdated_electrum_wallet_receive_warning": "このウォレットに 12 ワードのシードがあり、Cake で作成された場合、このウォレットにビットコインを入金しないでください。 このウォレットに転送された BTC は失われる可能性があります。 新しい 24 ワードのウォレットを作成し (右上のメニューをタップし、[ウォレット]、[新しいウォレットの作成]、[ビットコイン] の順に選択)、すぐに BTC をそこに移動します。 Cake の新しい (24 ワード) BTC ウォレットは安全です", "outgoing": "発信", + "outputs": "出力", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "ペアリング無効イベント", "password": "パスワード", @@ -644,6 +654,7 @@ "template_name": "テンプレート名", "third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます!", "third_intro_title": "Yatは他の人とうまく遊ぶ", + "thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。", "time": "${minutes}m ${seconds}s", "tip": "ヒント: ", "today": "今日", @@ -661,6 +672,7 @@ "totp_code": "TOTP コード", "totp_secret_code": "TOTPシークレットコード", "totp_verification_success": "検証成功!", + "track": "追跡", "trade_details_copied": "${title} クリップボードにコピーしました", "trade_details_created_at": "で作成", "trade_details_fetching": "フェッチング", @@ -711,6 +723,16 @@ "transactions": "取引", "transactions_by_date": "日付ごとの取引", "trusted": "信頼できる", + "tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。", + "tx_commit_failed": "トランザクションコミットは失敗しました。サポートに連絡してください。", + "tx_no_dust_exception": "トランザクションは、小さすぎる金額を送信することにより拒否されます。量を増やしてみてください。", + "tx_not_enough_inputs_exception": "利用可能な入力が十分ではありません。コイン制御下でもっと選択してください", + "tx_rejected_dust_change": "ネットワークルール、低い変更量(ほこり)によって拒否されたトランザクション。すべてを送信するか、金額を減らしてみてください。", + "tx_rejected_dust_output": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。金額を増やしてください。", + "tx_rejected_dust_output_send_all": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。コイン管理下で選択されたコインのバランスを確認してください。", + "tx_rejected_vout_negative": "この取引の料金に支払うのに十分な残高はありません。コイン制御下のコインのバランスを確認してください。", + "tx_wrong_balance_exception": "この金額を送信するのに十分な${currency}はありません。", + "tx_zero_fee_exception": "0料金でトランザクションを送信できません。レートを上げて、最新の見積もりについて接続を確認してみてください。", "unavailable_balance": "利用できない残高", "unavailable_balance_description": "利用不可能な残高: この合計には、保留中のトランザクションにロックされている資金と、コイン管理設定でアクティブに凍結した資金が含まれます。ロックされた残高は、それぞれの取引が完了すると利用可能になりますが、凍結された残高は、凍結を解除するまで取引にアクセスできません。", "unconfirmed": "残高未確認", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 3affd803b..c67d531ce 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -43,6 +43,7 @@ "already_have_account": "이미 계정이 있습니까?", "always": "언제나", "amount": "양: ", + "amount_is_below_minimum_limit": "수수료 후 잔액은 Exchange (${min})에 필요한 최소 금액보다 적습니다.", "amount_is_estimate": "수신 금액은 견적입니다", "amount_is_guaranteed": "수령 금액이 보장됩니다.", "and": "그리고", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.", "Blocks_remaining": "${status} 남은 블록", "bright_theme": "선명한", + "bump_fee": "범프 요금", "buy": "구입", "buy_alert_content": "현재 Bitcoin, Ethereum, Litecoin 및 Monero 구매만 지원합니다. Bitcoin, Ethereum, Litecoin 또는 Monero 지갑을 생성하거나 전환하십시오.", "buy_bitcoin": "비트 코인 구매", @@ -132,6 +134,8 @@ "confirm": "확인", "confirm_delete_template": "이 작업은이 템플릿을 삭제합니다. 계속 하시겠습니까?", "confirm_delete_wallet": "이 작업은이 지갑을 삭제합니다. 계속 하시겠습니까?", + "confirm_fee_deduction": "수수료 공제를 확인하십시오", + "confirm_fee_deduction_content": "출력에서 수수료를 공제하는 데 동의하십니까?", "confirm_sending": "전송 확인", "confirmations": "확인", "confirmed": "확인된 잔액", @@ -171,6 +175,7 @@ "debit_card": "직불 카드", "debit_card_terms": "이 디지털 지갑에 있는 귀하의 지불 카드 번호(및 귀하의 지불 카드 번호에 해당하는 자격 증명)의 저장 및 사용은 부터 발효되는 지불 카드 발행자와의 해당 카드 소지자 계약의 이용 약관을 따릅니다. 수시로.", "decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.", + "decimals_cannot_be_zero": "토큰 소수점은 0이 될 수 없습니다.", "default_buy_provider": "기본 구매 제공자", "default_sell_provider": "기본 판매 공급자", "delete": "지우다", @@ -211,6 +216,7 @@ "edit_token": "토큰 편집", "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다.", "email_address": "이메일 주소", + "enable_replace_by_fee": "대체별로 활성화하십시오", "enabled": "사용", "enter_amount": "금액 입력", "enter_backup_password": "여기에 백업 비밀번호를 입력하세요.", @@ -247,6 +253,7 @@ "errorGettingCredentials": "실패: 자격 증명을 가져오는 중 오류가 발생했습니다.", "errorSigningTransaction": "거래에 서명하는 동안 오류가 발생했습니다.", "estimated": "예상", + "estimated_new_fee": "예상 새로운 수수료", "etherscan_history": "이더스캔 역사", "event": "이벤트", "events": "이벤트", @@ -313,6 +320,7 @@ "in_store": "매장 내", "incoming": "들어오는", "incorrect_seed": "입력하신 텍스트가 유효하지 않습니다.", + "inputs": "입력", "introducing_cake_pay": "소개 Cake Pay!", "invalid_input": "잘못된 입력", "invoice_details": "인보이스 세부정보", @@ -380,6 +388,7 @@ "offer_expires_in": "쿠폰 만료일: ", "offline": "오프라인", "ok": "승인", + "old_fee": "옛 수수료", "onion_link": "양파 링크", "online": "온라인", "onramper_option_description": "많은 결제 방법으로 암호화를 신속하게 구입하십시오. 대부분의 국가에서 사용할 수 있습니다. 스프레드와 수수료는 다양합니다.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Cake에서 생성 된 새로운 비트 코인 지갑에는 이제 24 단어 시드가 있습니다. 새로운 비트 코인 지갑을 생성하고 모든 자금을 새로운 24 단어 지갑으로 이체하고 12 단어 시드가있는 지갑 사용을 중지해야합니다. 자금을 확보하려면 즉시이 작업을 수행하십시오.", "outdated_electrum_wallet_receive_warning": "이 지갑에 12 단어 시드가 있고 Cake에서 생성 된 경우이 지갑에 비트 코인을 입금하지 마십시오. 이 지갑으로 전송 된 모든 BTC는 손실 될 수 있습니다. 새로운 24 단어 지갑을 생성하고 (오른쪽 상단의 메뉴를 탭하고 지갑을 선택한 다음 새 지갑 생성을 선택한 다음 비트 코인을 선택하십시오) 즉시 BTC를 그곳으로 이동하십시오. Cake의 새로운 (24 단어) BTC 지갑은 안전합니다", "outgoing": "나가는", + "outputs": "출력", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "잘못된 이벤트 페어링", "password": "암호", @@ -644,6 +654,7 @@ "template_name": "템플릿 이름", "third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!", "third_intro_title": "Yat는 다른 사람들과 잘 놉니다.", + "thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.", "time": "${minutes}m ${seconds}s", "tip": "팁:", "today": "오늘", @@ -661,6 +672,7 @@ "totp_code": "TOTP 코드", "totp_secret_code": "TOTP 비밀 코드", "totp_verification_success": "확인 성공!", + "track": "길", "trade_details_copied": "${title} 클립 보드에 복사", "trade_details_created_at": "에 작성", "trade_details_fetching": "가져 오는 중", @@ -711,6 +723,16 @@ "transactions": "업무", "transactions_by_date": "날짜 별 거래", "trusted": "신뢰할 수 있는", + "tx_commit_exception_no_dust_on_change": "이 금액으로 거래가 거부되었습니다. 이 코인을 사용하면 거스름돈 없이 ${min}를 보내거나 거스름돈을 반환하는 ${max}를 보낼 수 있습니다.", + "tx_commit_failed": "거래 커밋이 실패했습니다. 지원에 연락하십시오.", + "tx_no_dust_exception": "너무 작은 금액을 보내면 거래가 거부됩니다. 금액을 늘리십시오.", + "tx_not_enough_inputs_exception": "사용 가능한 입력이 충분하지 않습니다. 코인 컨트롤에서 더 많은 것을 선택하십시오", + "tx_rejected_dust_change": "네트워크 규칙, 낮은 변경 금액 (먼지)에 의해 거부 된 거래. 전부를 보내거나 금액을 줄이십시오.", + "tx_rejected_dust_output": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 금액을 늘리십시오.", + "tx_rejected_dust_output_send_all": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 동전 제어에서 선택한 동전의 균형을 확인하십시오.", + "tx_rejected_vout_negative": "이 거래 수수료를 지불하기에 잔액이 충분하지 않습니다. 동전 통제하에 동전의 균형을 확인하십시오.", + "tx_wrong_balance_exception": "이 금액을 보내기에 충분한 ${currency}가 충분하지 않습니다.", + "tx_zero_fee_exception": "0 수수료로 거래를 보낼 수 없습니다. 최신 견적에 대해서는 속도를 높이거나 연결을 확인하십시오.", "unavailable_balance": "사용할 수 없는 잔액", "unavailable_balance_description": "사용할 수 없는 잔액: 이 총계에는 보류 중인 거래에 잠겨 있는 자금과 코인 관리 설정에서 적극적으로 동결된 자금이 포함됩니다. 잠긴 잔액은 해당 거래가 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결을 해제하기 전까지 거래에 액세스할 수 없습니다.", "unconfirmed": "확인되지 않은 잔액", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index c52d65c06..e373eefc0 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -43,6 +43,7 @@ "already_have_account": "အကောင့်ရှိပြီးသားလား?", "always": "အမြဲတမ်း", "amount": "ပမာဏ:", + "amount_is_below_minimum_limit": "ငွေလဲလှယ်ရန်လိုအပ်သည့်အနိမ့်ဆုံးပမာဏထက်လျော့နည်းသွားပြီးသည့်နောက်ငွေလက်ကျန်ငွေပမာဏသည်ငွေလဲလှယ်မှုအတွက်လိုအပ်သည့်အနိမ့်ဆုံးပမာဏထက်နည်းသည် (${min})", "amount_is_estimate": "ရရှိသည့်ပမာဏသည် ခန့်မှန်းချက်တစ်ခုဖြစ်သည်။", "amount_is_guaranteed": "ရရှိသည့်ပမာဏကို အာမခံပါသည်။", "and": "နှင့်", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin ငွေပေးချေမှုများသည် မိနစ် 20 သို့မဟုတ် ထို့ထက်ပိုကြာနိုင်သည် 1 အတည်ပြုချက် လိုအပ်သည်။ မင်းရဲ့စိတ်ရှည်မှုအတွက် ကျေးဇူးတင်ပါတယ်။ ငွေပေးချေမှုကို အတည်ပြုပြီးသောအခါ သင့်ထံ အီးမေးလ်ပို့ပါမည်။", "Blocks_remaining": "${status} ဘလောက်များ ကျန်နေပါသည်။", "bright_theme": "တောက်ပ", + "bump_fee": "ဝင်ငွေ", "buy": "ဝယ်ပါ။", "buy_alert_content": "လက်ရှိတွင် ကျွန်ုပ်တို့သည် Bitcoin၊ Ethereum၊ Litecoin နှင့် Monero တို့ကိုသာ ဝယ်ယူမှုကို ပံ့ပိုးပေးပါသည်။ သင်၏ Bitcoin၊ Ethereum၊ Litecoin သို့မဟုတ် Monero ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။", "buy_bitcoin": "Bitcoin ကိုဝယ်ပါ။", @@ -132,6 +134,8 @@ "confirm": "အတည်ပြုပါ။", "confirm_delete_template": "ဤလုပ်ဆောင်ချက်သည် ဤပုံစံပြားကို ဖျက်လိုက်ပါမည်။ ဆက်လုပ်လိုပါသလား။", "confirm_delete_wallet": "ဤလုပ်ဆောင်ချက်သည် ဤပိုက်ဆံအိတ်ကို ဖျက်လိုက်ပါမည်။ ဆက်လုပ်လိုပါသလား။", + "confirm_fee_deduction": "အခကြေးငွေကိုနှုတ်ယူခြင်း", + "confirm_fee_deduction_content": "output မှအခကြေးငွေကိုယူရန်သဘောတူပါသလား။", "confirm_sending": "ပေးပို့အတည်ပြုပါ။", "confirmations": "အတည်ပြုချက်များ", "confirmed": "အတည်ပြုထားသော လက်ကျန်ငွေ", @@ -171,6 +175,7 @@ "debit_card": "ဒက်ဘစ်ကတ်", "debit_card_terms": "ဤဒစ်ဂျစ်တယ်ပိုက်ဆံအိတ်ရှိ သင့်ငွေပေးချေမှုကတ်နံပါတ် (နှင့် သင့်ငွေပေးချေကတ်နံပါတ်နှင့် သက်ဆိုင်သောအထောက်အထားများ) ၏ သိုလှောင်မှုနှင့် အသုံးပြုမှုသည် အချိန်အခါနှင့်အမျှ သက်ရောက်မှုရှိသကဲ့သို့ ကတ်ကိုင်ဆောင်ထားသူ၏ သဘောတူညီချက်၏ စည်းကမ်းသတ်မှတ်ချက်များနှင့် ကိုက်ညီပါသည်။", "decimal_places_error": "ဒဿမနေရာများ များလွန်းသည်။", + "decimals_cannot_be_zero": "တိုကင်ဒ decimal မသုညမဖြစ်နိုင်ပါ။", "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "default_sell_provider": "ပုံသေရောင်းချပေးသူ", "delete": "ဖျက်ပါ။", @@ -211,6 +216,7 @@ "edit_token": "တိုကင်ကို တည်းဖြတ်ပါ။", "electrum_address_disclaimer": "သင်အသုံးပြုသည့်အချိန်တိုင်းတွင် ကျွန်ုပ်တို့သည် လိပ်စာအသစ်များကို ထုတ်ပေးသော်လည်း ယခင်လိပ်စာများသည် ဆက်လက်အလုပ်လုပ်နေပါသည်။", "email_address": "အီးမေးလ်လိပ်စာ", + "enable_replace_by_fee": "အစားထိုး - by- အခကြေးငွေ enable", "enabled": "ဖွင့်ထားသည်။", "enter_amount": "ပမာဏကို ထည့်ပါ။", "enter_backup_password": "အရန်စကားဝှက်ကို ဤနေရာတွင် ထည့်ပါ။", @@ -247,6 +253,7 @@ "errorGettingCredentials": "မအောင်မြင်ပါ- အထောက်အထားများ ရယူနေစဉ် အမှားအယွင်း", "errorSigningTransaction": "ငွေပေးငွေယူ လက်မှတ်ထိုးစဉ် အမှားအယွင်းတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။", "estimated": "ခန့်မှန်း", + "estimated_new_fee": "ခန့်မှန်းသစ်ခန့်မှန်း", "etherscan_history": "Etherscan သမိုင်း", "event": "ပွဲ", "events": "အဲ့ဒါနဲ့", @@ -313,6 +320,7 @@ "in_store": "စတိုးတွင်", "incoming": "ဝင်လာ", "incorrect_seed": "ထည့်သွင်းထားသော စာသားသည် မမှန်ကန်ပါ။", + "inputs": "သွင်းငေှ", "introducing_cake_pay": "Cake Pay ကို မိတ်ဆက်ခြင်း။", "invalid_input": "ထည့်သွင်းမှု မမှန်ကန်ပါ။", "invoice_details": "ပြေစာအသေးစိတ်", @@ -380,6 +388,7 @@ "offer_expires_in": "ကမ်းလှမ်းချက် သက်တမ်းကုန်သည်:", "offline": "အော့ဖ်လိုင်း", "ok": "ရလား", + "old_fee": "ကြေးဟောင်း", "onion_link": "ကြက်သွန်လင့်", "online": "အွန်လိုင်း", "onramper_option_description": "ငွေပေးချေမှုနည်းလမ်းများစွာဖြင့် Crypto ကိုလျင်မြန်စွာ 0 ယ်ပါ။ နိုင်ငံအများစုတွင်ရရှိနိုင်ပါသည်။ ဖြန့်ဖြူးနှင့်အခကြေးငွေကွဲပြားခြားနားသည်။", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "ယခု Cake တွင်ဖန်တီးထားသော Bitcoin ပိုက်ဆံအိတ်အသစ်တွင် စကားလုံး 24 မျိုးရှိသည်။ Bitcoin ပိုက်ဆံအိတ်အသစ်တစ်ခုကို ဖန်တီးပြီး သင့်ငွေအားလုံးကို 24 စကားလုံးပိုက်ဆံအိတ်အသစ်သို့ လွှဲပြောင်းပြီး 12 စကားလုံးမျိုးစေ့ဖြင့် ပိုက်ဆံအိတ်များကို အသုံးပြုခြင်းကို ရပ်တန့်ရန် မဖြစ်မနေလိုအပ်ပါသည်။ သင့်ရန်ပုံငွေများကို လုံခြုံစေရန်အတွက် ၎င်းကိုချက်ချင်းလုပ်ဆောင်ပါ။", "outdated_electrum_wallet_receive_warning": "ဤပိုက်ဆံအိတ်တွင် စာလုံး 12 လုံးပါပြီး ကိတ်မုန့်တွင် ဖန်တီးပါက၊ Bitcoin ကို ဤပိုက်ဆံအိတ်ထဲသို့ မထည့်ပါနှင့်။ ဤပိုက်ဆံအိတ်သို့ လွှဲပြောင်းပေးသည့် မည်သည့် BTC မဆို ဆုံးရှုံးနိုင်သည်။ 24 စကားလုံးပိုက်ဆံအိတ်အသစ်တစ်ခုဖန်တီးပါ (ညာဘက်အပေါ်ထောင့်ရှိမီနူးကိုနှိပ်ပါ၊ Wallets ကိုရွေးချယ်ပါ၊ ပိုက်ဆံအိတ်အသစ်ဖန်တီးရန်ကိုရွေးချယ်ပါ၊ ထို့နောက် Bitcoin ကိုရွေးချယ်ပါ) နှင့်သင်၏ BTC ကိုထိုနေရာသို့ချက်ချင်းရွှေ့ပါ။ Cake မှ (24 စာလုံး) BTC ပိုက်ဆံအိတ်အသစ်များသည် လုံခြုံပါသည်။", "outgoing": "အထွက်", + "outputs": "ထုတ်လုပ်မှု", "overwrite_amount": "ပမာဏကို ထပ်ရေးပါ။", "pairingInvalidEvent": "မမှန်ကန်သောဖြစ်ရပ်ကို တွဲချိတ်ခြင်း။", "password": "စကားဝှက်", @@ -643,6 +653,7 @@ "template_name": "နမူနာပုံစံ", "third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။", "third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။", + "thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။", "time": "${minutes}m ${seconds}s", "tip": "အကြံပြုချက်-", "today": "ဒီနေ့", @@ -660,6 +671,7 @@ "totp_code": "TOTP ကုဒ်", "totp_secret_code": "TOTP လျှို့ဝှက်ကုဒ်", "totp_verification_success": "အတည်ပြုခြင်း အောင်မြင်ပါသည်။", + "track": "တစ်ပုဒ်", "trade_details_copied": "${title} ကို Clipboard သို့ ကူးယူထားသည်။", "trade_details_created_at": "တွင်ဖန်တီးခဲ့သည်။", "trade_details_fetching": "ခေါ်ယူခြင်း။", @@ -710,6 +722,16 @@ "transactions": "ငွေပေးငွေယူ", "transactions_by_date": "ရက်စွဲအလိုက် ငွေလွှဲမှုများ", "trusted": "ယုံတယ်။", + "tx_commit_exception_no_dust_on_change": "အဆိုပါငွေပေးငွေယူကဒီပမာဏနှင့်အတူပယ်ချခံရသည်။ ဤဒင်္ဂါးပြားများနှင့်အတူပြောင်းလဲမှုကိုပြန်လည်ပြောင်းလဲခြင်းသို့မဟုတ် ${min} မပါဘဲ ${max} ပေးပို့နိုင်သည်။", + "tx_commit_failed": "ငွေပေးငွေယူကျူးလွန်မှုပျက်ကွက်။ ကျေးဇူးပြုပြီးပံ့ပိုးမှုဆက်သွယ်ပါ။", + "tx_no_dust_exception": "ငွေပမာဏကိုသေးငယ်လွန်းသောငွေပမာဏကိုပေးပို့ခြင်းဖြင့်ပယ်ဖျက်ခြင်းကိုငြင်းပယ်သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ကြိုးစားပါ။", + "tx_not_enough_inputs_exception": "အလုံအလောက်သွင်းအားစုများမလုံလောက်။ ကျေးဇူးပြုပြီးဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ပိုမိုရွေးချယ်ပါ", + "tx_rejected_dust_change": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ပယ်ဖျက်ခြင်းသည် Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ငြင်းပယ်ခြင်း, အားလုံးပေးပို့ခြင်းသို့မဟုတ်ငွေပမာဏကိုလျှော့ချကြိုးစားပါ။", + "tx_rejected_dust_output": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ပေးပါ။", + "tx_rejected_dust_output_send_all": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ရွေးချယ်ထားသောဒင်္ဂါးများ၏လက်ကျန်ငွေကိုစစ်ဆေးပါ။", + "tx_rejected_vout_negative": "ဒီငွေပေးငွေယူရဲ့အခကြေးငွေအတွက်ပေးဆောင်ဖို့လုံလောက်တဲ့ဟန်ချက်မလုံလောက်။ ဒင်္ဂါးပြား၏လက်ကျန်ငွေလက်ကျန်ငွေကိုစစ်ဆေးပါ။", + "tx_wrong_balance_exception": "ဤငွေပမာဏကိုပေးပို့ရန်သင့်တွင် ${currency} မရှိပါ။", + "tx_zero_fee_exception": "0 ကြေးနှင့်အတူငွေပေးငွေယူပေးပို့လို့မရပါဘူး။ နှုန်းကိုတိုးမြှင့်ခြင်းသို့မဟုတ်နောက်ဆုံးခန့်မှန်းချက်များအတွက်သင်၏ connection ကိုစစ်ဆေးပါ။", "unavailable_balance": "လက်ကျန်ငွေ မရရှိနိုင်ပါ။", "unavailable_balance_description": "မရရှိနိုင်သော လက်ကျန်ငွေ- ဤစုစုပေါင်းတွင် ဆိုင်းငံ့ထားသော ငွေပေးငွေယူများတွင် သော့ခတ်ထားသော ငွေကြေးများနှင့် သင်၏ coin ထိန်းချုပ်မှုဆက်တင်များတွင် သင် တက်ကြွစွာ အေးခဲထားသော ငွေများ ပါဝင်သည်။ သော့ခတ်ထားသော လက်ကျန်ငွေများကို ၎င်းတို့၏ သက်ဆိုင်ရာ ငွေပေးငွေယူများ ပြီးမြောက်သည်နှင့် တပြိုင်နက် ရရှိနိုင်မည်ဖြစ်ပြီး၊ အေးခဲထားသော လက်ကျန်များကို ၎င်းတို့အား ပြန်ဖြုတ်ရန် သင်ဆုံးဖြတ်သည်အထိ ငွေပေးငွေယူများအတွက် ဆက်လက်၍မရနိုင်ပါ။", "unconfirmed": "အတည်မပြုနိုင်သော လက်ကျန်ငွေ", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index fa7825809..1fc85a28b 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -43,6 +43,7 @@ "already_have_account": "Heb je al een account?", "always": "altijd", "amount": "Bedrag: ", + "amount_is_below_minimum_limit": "Uw saldo na vergoedingen zou lager zijn dan het minimale bedrag dat nodig is voor de uitwisseling (${min})", "amount_is_estimate": "Het ontvangen bedrag is een schatting", "amount_is_guaranteed": "Het ontvangen bedrag is gegarandeerd", "and": "en", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin-betalingen vereisen 1 bevestiging, wat 20 minuten of langer kan duren. Dank voor uw geduld! U ontvangt een e-mail wanneer de betaling is bevestigd.", "Blocks_remaining": "${status} Resterende blokken", "bright_theme": "Helder", + "bump_fee": "Bult fee", "buy": "Kopen", "buy_alert_content": "Momenteel ondersteunen we alleen de aankoop van Bitcoin, Ethereum, Litecoin en Monero. Maak of schakel over naar uw Bitcoin-, Ethereum-, Litecoin- of Monero-portemonnee.", "buy_bitcoin": "Koop Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Bevestigen", "confirm_delete_template": "Met deze actie wordt deze sjabloon verwijderd. Wilt u doorgaan?", "confirm_delete_wallet": "Met deze actie wordt deze portemonnee verwijderd. Wilt u doorgaan?", + "confirm_fee_deduction": "Bevestig de aftrek van de kosten", + "confirm_fee_deduction_content": "Stemt u ermee in om de vergoeding af te trekken van de output?", "confirm_sending": "Bevestig verzending", "confirmations": "Bevestigingen", "confirmed": "Bevestigd saldo", @@ -171,6 +175,7 @@ "debit_card": "Debetkaart", "debit_card_terms": "De opslag en het gebruik van uw betaalkaartnummer (en inloggegevens die overeenkomen met uw betaalkaartnummer) in deze digitale portemonnee zijn onderworpen aan de Algemene voorwaarden van de toepasselijke kaarthouderovereenkomst met de uitgever van de betaalkaart, zoals van kracht vanaf tijd tot tijd.", "decimal_places_error": "Te veel decimalen", + "decimals_cannot_be_zero": "Token decimaal kan niet nul zijn.", "default_buy_provider": "Standaard Koopprovider", "default_sell_provider": "Standaard verkoopaanbieder", "delete": "Delete", @@ -211,6 +216,7 @@ "edit_token": "Token bewerken", "electrum_address_disclaimer": "We genereren nieuwe adressen elke keer dat u er een gebruikt, maar eerdere adressen blijven werken", "email_address": "E-mailadres", + "enable_replace_by_fee": "Schakel vervangen door een fee", "enabled": "Ingeschakeld", "enter_amount": "Voer Bedrag in", "enter_backup_password": "Voer hier een back-upwachtwoord in", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Mislukt: fout bij het ophalen van inloggegevens", "errorSigningTransaction": "Er is een fout opgetreden tijdens het ondertekenen van de transactie", "estimated": "Geschatte", + "estimated_new_fee": "Geschatte nieuwe vergoeding", "etherscan_history": "Etherscan-geschiedenis", "event": "Evenement", "events": "Evenementen", @@ -313,6 +320,7 @@ "in_store": "In winkel", "incoming": "inkomend", "incorrect_seed": "De ingevoerde tekst is niet geldig.", + "inputs": "Invoer", "introducing_cake_pay": "Introductie van Cake Pay!", "invalid_input": "Ongeldige invoer", "invoice_details": "Factuurgegevens", @@ -380,6 +388,7 @@ "offer_expires_in": "Aanbieding verloopt over: ", "offline": "Offline", "ok": "OK", + "old_fee": "Oude vergoeding", "onion_link": "Ui koppeling", "online": "online", "onramper_option_description": "Koop snel crypto met veel betaalmethoden. Beschikbaar in de meeste landen. Spreads en vergoedingen variëren.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Nieuwe Bitcoin-portefeuilles die in Cake zijn gemaakt, hebben nu een zaadje van 24 woorden. Het is verplicht dat u een nieuwe Bitcoin-portemonnee maakt en al uw geld overmaakt naar de nieuwe portemonnee van 24 woorden, en stopt met het gebruik van wallets met een seed van 12 woorden. Doe dit onmiddellijk om uw geld veilig te stellen.", "outdated_electrum_wallet_receive_warning": "Als deze portemonnee een seed van 12 woorden heeft en is gemaakt in Cake, stort dan GEEN Bitcoin in deze portemonnee. Elke BTC die naar deze portemonnee is overgebracht, kan verloren gaan. Maak een nieuwe portemonnee van 24 woorden (tik op het menu rechtsboven, selecteer Portefeuilles, kies Nieuwe portemonnee maken en selecteer vervolgens Bitcoin) en verplaats je BTC ONMIDDELLIJK daar. Nieuwe (24-woorden) BTC-portefeuilles van Cake zijn veilig", "outgoing": "Uitgaande", + "outputs": "Uitgangen", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Koppelen Ongeldige gebeurtenis", "password": "Wachtwoord", @@ -643,6 +653,7 @@ "template_name": "Sjabloonnaam", "third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!", "third_intro_title": "Yat speelt leuk met anderen", + "thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.", "time": "${minutes}m ${seconds}s", "tip": "Tip:", "today": "Vandaag", @@ -660,6 +671,7 @@ "totp_code": "TOTP-code", "totp_secret_code": "TOTP-geheime code", "totp_verification_success": "Verificatie geslaagd!", + "track": "Spoor", "trade_details_copied": "${title} gekopieerd naar het klembord", "trade_details_created_at": "Gemaakt bij", "trade_details_fetching": "Ophalen", @@ -710,6 +722,16 @@ "transactions": "Transacties", "transactions_by_date": "Transacties op datum", "trusted": "vertrouwd", + "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_no_dust_exception": "De transactie wordt afgewezen door een te klein bedrag te verzenden. Probeer het bedrag te verhogen.", + "tx_not_enough_inputs_exception": "Niet genoeg ingangen beschikbaar. Selecteer meer onder muntenbesturing", + "tx_rejected_dust_change": "Transactie afgewezen door netwerkregels, laag wijzigingsbedrag (stof). Probeer alles te verzenden of het bedrag te verminderen.", + "tx_rejected_dust_output": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Verhoog het bedrag.", + "tx_rejected_dust_output_send_all": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Controleer het saldo van munten die zijn geselecteerd onder muntcontrole.", + "tx_rejected_vout_negative": "Niet genoeg saldo om te betalen voor de kosten van deze transactie. Controleer het saldo van munten onder muntcontrole.", + "tx_wrong_balance_exception": "Je hebt niet genoeg ${currency} om dit bedrag te verzenden.", + "tx_zero_fee_exception": "Kan geen transactie verzenden met 0 kosten. Probeer het tarief te verhogen of uw verbinding te controleren op de laatste schattingen.", "unavailable_balance": "Onbeschikbaar saldo", "unavailable_balance_description": "Niet-beschikbaar saldo: Dit totaal omvat het geld dat is vergrendeld in lopende transacties en het geld dat u actief hebt bevroren in uw muntcontrole-instellingen. Vergrendelde saldi komen beschikbaar zodra de betreffende transacties zijn voltooid, terwijl bevroren saldi ontoegankelijk blijven voor transacties totdat u besluit ze weer vrij te geven.", "unconfirmed": "Onbevestigd saldo", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 16dc8696a..f279dbaa7 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -43,6 +43,7 @@ "already_have_account": "Masz już konto?", "always": "zawsze", "amount": "Ilość: ", + "amount_is_below_minimum_limit": "Twoje saldo po opłatach byłoby mniejsze niż minimalna kwota potrzebna do wymiany (${min})", "amount_is_estimate": "Otrzymana kwota jest wartością szacunkową", "amount_is_guaranteed": "Otrzymana kwota jest gwarantowana", "and": "i", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Płatności Bitcoin wymagają 1 potwierdzenia, co może zająć 20 minut lub dłużej. Dziękuję za cierpliwość! Otrzymasz wiadomość e-mail, gdy płatność zostanie potwierdzona.", "Blocks_remaining": "Pozostało ${status} bloków", "bright_theme": "Biały", + "bump_fee": "Opłata za nierówność", "buy": "Kup", "buy_alert_content": "Obecnie obsługujemy tylko zakup Bitcoin, Ethereum, Litecoin i Monero. Utwórz lub przełącz się na swój portfel Bitcoin, Ethereum, Litecoin lub Monero.", "buy_bitcoin": "Kup Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Potwierdzać", "confirm_delete_template": "Ta czynność usunie ten szablon. Czy chcesz kontynuować?", "confirm_delete_wallet": "Ta czynność usunie ten portfel. Czy chcesz kontynuować?", + "confirm_fee_deduction": "Potwierdź odliczenie opłaty", + "confirm_fee_deduction_content": "Czy zgadzasz się odliczyć opłatę od wyników?", "confirm_sending": "Potwierdź wysłanie", "confirmations": "Potwierdzenia", "confirmed": "Potwierdzone saldo", @@ -171,6 +175,7 @@ "debit_card": "Karta debetowa", "debit_card_terms": "Przechowywanie i używanie numeru karty płatniczej (oraz danych uwierzytelniających odpowiadających numerowi karty płatniczej) w tym portfelu cyfrowym podlega Warunkom odpowiedniej umowy posiadacza karty z wydawcą karty płatniczej, zgodnie z obowiązującym od od czasu do czasu.", "decimal_places_error": "Za dużo miejsc dziesiętnych", + "decimals_cannot_be_zero": "Token dziesiętny nie może być zerowy.", "default_buy_provider": "Domyślny dostawca zakupu", "default_sell_provider": "Domyślny dostawca sprzedaży", "delete": "Skasuj", @@ -211,6 +216,7 @@ "edit_token": "Edytuj token", "electrum_address_disclaimer": "Za każdym razem, gdy wykorzystasz adres, dla wiekszej prywatności generujemy nowy, ale poprzednie adresy nadal działają, i moga odbierać środki", "email_address": "Adres e-mail", + "enable_replace_by_fee": "Włącz wymianę po lewej", "enabled": "Włączone", "enter_amount": "Wprowadź kwotę", "enter_backup_password": "Wprowadź tutaj hasło kopii zapasowej", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Niepowodzenie: Błąd podczas uzyskiwania poświadczeń", "errorSigningTransaction": "Wystąpił błąd podczas podpisywania transakcji", "estimated": "Oszacowano", + "estimated_new_fee": "Szacowana nowa opłata", "etherscan_history": "Historia Etherscanu", "event": "Wydarzenie", "events": "Wydarzenia", @@ -313,6 +320,7 @@ "in_store": "W Sklepie", "incoming": "Przychodzące", "incorrect_seed": "Wprowadzony seed jest nieprawidłowy.", + "inputs": "Wejścia", "introducing_cake_pay": "Przedstawiamy Cake Pay!", "invalid_input": "Nieprawidłowe dane wejściowe", "invoice_details": "Dane do faktury", @@ -380,6 +388,7 @@ "offer_expires_in": "Oferta wygasa za ", "offline": "Offline", "ok": "Ok", + "old_fee": "Stara opłata", "onion_link": "Łącznik cebulowy", "online": "online", "onramper_option_description": "Szybko kup kryptowaluty z wieloma metodami płatności. Dostępne w większości krajów. Spready i opłaty różnią się.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Nowe portfele Bitcoin utworzone w Cake mają teraz fraze seed składające się z 24 słów. Konieczne jest utworzenie nowego portfela Bitcoin i przeniesienie wszystkich środków do nowego portfela na 24 słowa oraz zaprzestanie korzystania z portfeli z frazą seed na 12 słów. Zrób to natychmiast, aby zabezpieczyć swoje fundusze.", "outdated_electrum_wallet_receive_warning": "Jeśli ten portfel ma 12-wyrazowy seed i został utworzony w Cake, NIE Wpłacaj Bitcoina do tego portfela. Wszelkie BTC przeniesione do tego portfela mogą zostać utracone. Utwórz nowy portfel z 24 słowami (dotknij menu w prawym górnym rogu, wybierz Portfele, wybierz Utwórz nowy portfel, a następnie Bitcoin) i NATYCHMIAST przenieś tam swoje BTC. Nowe (24 słowa) portfele BTC Cake Wallet są bezpieczne", "outgoing": "Wychodzące", + "outputs": "Wyjścia", "overwrite_amount": "Nadpisz ilość", "pairingInvalidEvent": "Nieprawidłowe zdarzenie parowania", "password": "Hasło", @@ -643,6 +653,7 @@ "template_name": "Nazwa szablonu", "third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!", "third_intro_title": "Yat ładnie bawi się z innymi", + "thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.", "time": "${minutes}m ${seconds}s", "tip": "wskazówka:", "today": "Dzisiaj", @@ -660,6 +671,7 @@ "totp_code": "Kod TOTP", "totp_secret_code": "Tajny kod TOTP", "totp_verification_success": "Weryfikacja powiodła się!", + "track": "Ścieżka", "trade_details_copied": "${title} skopiowane do schowka", "trade_details_created_at": "Utworzono ", "trade_details_fetching": "Pobieranie", @@ -710,6 +722,16 @@ "transactions": "Transakcje", "transactions_by_date": "Transakcje według daty", "trusted": "Zaufany", + "tx_commit_exception_no_dust_on_change": "Transakcja jest odrzucana z tą kwotą. Za pomocą tych monet możesz wysłać ${min} bez zmiany lub ${max}, które zwraca zmianę.", + "tx_commit_failed": "Zatwierdzenie transakcji nie powiodło się. Skontaktuj się z obsługą.", + "tx_no_dust_exception": "Transakcja jest odrzucana przez wysyłanie zbyt małej ilości. Spróbuj zwiększyć kwotę.", + "tx_not_enough_inputs_exception": "Za mało dostępnych danych wejściowych. Wybierz więcej pod kontrolą monet", + "tx_rejected_dust_change": "Transakcja odrzucona według reguł sieciowych, niska ilość zmiany (kurz). Spróbuj wysłać całość lub zmniejszyć kwotę.", + "tx_rejected_dust_output": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Zwiększ kwotę.", + "tx_rejected_dust_output_send_all": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Sprawdź saldo monet wybranych pod kontrolą monet.", + "tx_rejected_vout_negative": "Za mało salda, aby zapłacić za opłaty tej transakcji. Sprawdź saldo monet pod kontrolą monet.", + "tx_wrong_balance_exception": "Nie masz wystarczającej ilości ${currency}, aby wysłać tę kwotę.", + "tx_zero_fee_exception": "Nie można wysłać transakcji z 0 opłatą. Spróbuj zwiększyć stawkę lub sprawdzić połączenie w poszukiwaniu najnowszych szacunków.", "unavailable_balance": "Niedostępne saldo", "unavailable_balance_description": "Niedostępne saldo: Suma ta obejmuje środki zablokowane w transakcjach oczekujących oraz te, które aktywnie zamroziłeś w ustawieniach kontroli monet. Zablokowane salda staną się dostępne po zakończeniu odpowiednich transakcji, natomiast zamrożone salda pozostaną niedostępne dla transakcji, dopóki nie zdecydujesz się ich odblokować.", "unconfirmed": "Niepotwierdzone saldo", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 8516ecf3f..ba3acae88 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -43,6 +43,7 @@ "already_have_account": "Já tem uma conta?", "always": "sempre", "amount": "Quantia: ", + "amount_is_below_minimum_limit": "Seu saldo após as taxas seria menor que o valor mínimo necessário para a troca (${min})", "amount_is_estimate": "O valor a ser recebido informado acima é uma estimativa", "amount_is_guaranteed": "O valor recebido é garantido", "and": "e", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Os pagamentos em Bitcoin exigem 1 confirmação, o que pode levar 20 minutos ou mais. Obrigado pela sua paciência! Você receberá um e-mail quando o pagamento for confirmado.", "Blocks_remaining": "${status} blocos restantes", "bright_theme": "Brilhante", + "bump_fee": "Taxa de aumento", "buy": "Comprar", "buy_alert_content": "Atualmente, oferecemos suporte apenas à compra de Bitcoin, Ethereum, Litecoin e Monero. Crie ou troque para sua carteira Bitcoin, Ethereum, Litecoin ou Monero.", "buy_bitcoin": "Compre Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Confirmar", "confirm_delete_template": "Esta ação excluirá este modelo. Você deseja continuar?", "confirm_delete_wallet": "Esta ação excluirá esta carteira. Você deseja continuar?", + "confirm_fee_deduction": "Confirme dedução da taxa", + "confirm_fee_deduction_content": "Você concorda em deduzir a taxa da saída?", "confirm_sending": "Confirmar o envio", "confirmations": "Confirmações", "confirmed": "Saldo Confirmado", @@ -171,6 +175,7 @@ "debit_card": "Cartão de débito", "debit_card_terms": "O armazenamento e uso do número do cartão de pagamento (e credenciais correspondentes ao número do cartão de pagamento) nesta carteira digital estão sujeitos aos Termos e Condições do contrato do titular do cartão aplicável com o emissor do cartão de pagamento, em vigor a partir de tempo ao tempo.", "decimal_places_error": "Muitas casas decimais", + "decimals_cannot_be_zero": "Decimal de token não pode ser zero.", "default_buy_provider": "Provedor de compra padrão", "default_sell_provider": "Provedor de venda padrão", "delete": "Excluir", @@ -211,6 +216,7 @@ "edit_token": "Editar símbolo", "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando", "email_address": "Endereço de e-mail", + "enable_replace_by_fee": "Habilite substituir por taxa", "enabled": "Habilitado", "enter_amount": "Digite o valor", "enter_backup_password": "Digite a senha de backup aqui", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Falha: Erro ao obter credenciais", "errorSigningTransaction": "Ocorreu um erro ao assinar a transação", "estimated": "Estimado", + "estimated_new_fee": "Nova taxa estimada", "etherscan_history": "história Etherscan", "event": "Evento", "events": "Eventos", @@ -313,6 +320,7 @@ "in_store": "Na loja", "incoming": "Recebidas", "incorrect_seed": "O texto digitado não é válido.", + "inputs": "Entradas", "introducing_cake_pay": "Apresentando o Cake Pay!", "invalid_input": "Entrada inválida", "invoice_details": "Detalhes da fatura", @@ -381,6 +389,7 @@ "offer_expires_in": "A oferta expira em: ", "offline": "offline", "ok": "Ok", + "old_fee": "Taxa antiga", "onion_link": "ligação de cebola", "online": "Online", "onramper_option_description": "Compre rapidamente criptografia com muitos métodos de pagamento. Disponível na maioria dos países. Os spreads e taxas variam.", @@ -398,6 +407,7 @@ "outdated_electrum_wallet_description": "As novas carteiras Bitcoin criadas no Cake agora têm uma semente de 24 palavras. É obrigatório que você crie uma nova carteira Bitcoin e transfira todos os seus fundos para a nova carteira de 24 palavras, e pare de usar carteiras com semente de 12 palavras. Faça isso imediatamente para garantir seus fundos.", "outdated_electrum_wallet_receive_warning": "Se esta carteira tiver uma semente de 12 palavras e foi criada no Cake, NÃO deposite Bitcoin nesta carteira. Qualquer BTC transferido para esta carteira pode ser perdido. Crie uma nova carteira de 24 palavras (toque no menu no canto superior direito, selecione Carteiras, escolha Criar Nova Carteira e selecione Bitcoin) e mova IMEDIATAMENTE seu BTC para lá. As novas carteiras BTC (24 palavras) da Cake são seguras", "outgoing": "Enviadas", + "outputs": "Saídas", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Emparelhamento de evento inválido", "password": "Senha", @@ -645,6 +655,7 @@ "template_name": "Nome do modelo", "third_intro_content": "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!", "third_intro_title": "Yat joga bem com os outros", + "thorchain_taproot_address_not_supported": "O provedor de Thorchain não suporta endereços de raiz de Tap. Altere o endereço ou selecione um provedor diferente.", "time": "${minutes}m ${seconds}s", "tip": "Dica:", "today": "Hoje", @@ -662,6 +673,7 @@ "totp_code": "Código TOTP", "totp_secret_code": "Código Secreto TOTP", "totp_verification_success": "Verificação bem-sucedida!", + "track": "Acompanhar", "trade_details_copied": "${title} copiados para a área de transferência", "trade_details_created_at": "Criada em", "trade_details_fetching": "Buscando", @@ -712,6 +724,16 @@ "transactions": "Transações", "transactions_by_date": "Transações por data", "trusted": "confiável", + "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_no_dust_exception": "A transação é rejeitada enviando uma quantia pequena demais. Por favor, tente aumentar o valor.", + "tx_not_enough_inputs_exception": "Não há entradas disponíveis. Selecione mais sob controle de moedas", + "tx_rejected_dust_change": "Transação rejeitada pelas regras de rede, baixa quantidade de troco (poeira). Tente enviar tudo ou reduzir o valor.", + "tx_rejected_dust_output": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, aumente o valor.", + "tx_rejected_dust_output_send_all": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, verifique o saldo de moedas selecionadas sob controle de moedas.", + "tx_rejected_vout_negative": "Não há saldo suficiente para pagar as taxas desta transação. Por favor, verifique o saldo de moedas sob controle de moedas.", + "tx_wrong_balance_exception": "Você não tem o suficiente ${currency} para enviar esse valor.", + "tx_zero_fee_exception": "Não pode enviar transação com taxa 0. Tente aumentar a taxa ou verificar sua conexão para obter as estimativas mais recentes.", "unavailable_balance": "Saldo indisponível", "unavailable_balance_description": "Saldo Indisponível: Este total inclui fundos bloqueados em transações pendentes e aqueles que você congelou ativamente nas configurações de controle de moedas. Os saldos bloqueados ficarão disponíveis assim que suas respectivas transações forem concluídas, enquanto os saldos congelados permanecerão inacessíveis para transações até que você decida descongelá-los.", "unconfirmed": "Saldo não confirmado", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 2bff24414..b24f940dd 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -43,6 +43,7 @@ "already_have_account": "У вас уже есть аккаунт?", "always": "всегда", "amount": "Сумма: ", + "amount_is_below_minimum_limit": "Ваш баланс после сборов будет меньше, чем минимальная сумма, необходимая для обмена (${min}))", "amount_is_estimate": "Полученная сумма является приблизительной", "amount_is_guaranteed": "Полученная сумма гарантирована", "and": "и", @@ -74,10 +75,11 @@ "billing_address_info": "Если вас попросят указать платежный адрес, укажите адрес доставки", "biometric_auth_reason": "Отсканируйте свой отпечаток пальца для аутентификации", "bitcoin_dark_theme": "Биткойн Темная тема", - "bitcoin_light_theme": "Легкая биткойн-тема", + "bitcoin_light_theme": "Светлая биткойн-тема", "bitcoin_payments_require_1_confirmation": "Биткойн-платежи требуют 1 подтверждения, что может занять 20 минут или дольше. Спасибо тебе за твое терпение! Вы получите электронное письмо, когда платеж будет подтвержден.", "Blocks_remaining": "${status} Осталось блоков", "bright_theme": "Яркая", + "bump_fee": "Повысить комиссию", "buy": "Купить", "buy_alert_content": "В настоящее время мы поддерживаем только покупку биткойнов, Ethereum, Litecoin и Monero. Пожалуйста, создайте или переключитесь на свой кошелек Bitcoin, Ethereum, Litecoin или Monero.", "buy_bitcoin": "Купить Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Подтвердить", "confirm_delete_template": "Это действие удалит шаблон. Вы хотите продолжить?", "confirm_delete_wallet": "Это действие удалит кошелек. Вы хотите продолжить?", + "confirm_fee_deduction": "Подтвердите вычет платы", + "confirm_fee_deduction_content": "Согласны ли вы вычесть плату из вывода?", "confirm_sending": "Подтвердить отправку", "confirmations": "Подтверждения", "confirmed": "Подтвержденный баланс", @@ -171,6 +175,7 @@ "debit_card": "Дебетовая карта", "debit_card_terms": "Хранение и использование номера вашей платежной карты (и учетных данных, соответствующих номеру вашей платежной карты) в этом цифровом кошельке регулируются положениями и условиями применимого соглашения держателя карты с эмитентом платежной карты, действующим с время от времени.", "decimal_places_error": "Слишком много десятичных знаков", + "decimals_cannot_be_zero": "Десятичный токен не может быть нулевым.", "default_buy_provider": "По умолчанию поставщик покупки", "default_sell_provider": "Поставщик продаж по умолчанию", "delete": "Удалить", @@ -211,6 +216,7 @@ "edit_token": "Изменить токен", "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать.", "email_address": "Адрес электронной почты", + "enable_replace_by_fee": "Включить замену за пикой", "enabled": "Включено", "enter_amount": "Введите сумму", "enter_backup_password": "Введите пароль резервной копии", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Не удалось: ошибка при получении учетных данных.", "errorSigningTransaction": "Произошла ошибка при подписании транзакции", "estimated": "Примерно", + "estimated_new_fee": "Расчетная новая плата", "etherscan_history": "История Эфириума", "event": "Событие", "events": "События", @@ -313,6 +320,7 @@ "in_store": "В магазине", "incoming": "Входящие", "incorrect_seed": "Введённый текст некорректный.", + "inputs": "Входы", "introducing_cake_pay": "Представляем Cake Pay!", "invalid_input": "Неверный Ввод", "invoice_details": "Детали счета", @@ -380,6 +388,7 @@ "offer_expires_in": "Предложение истекает через: ", "offline": "Не в сети", "ok": "OK", + "old_fee": "Старая плата", "onion_link": "Луковая ссылка", "online": "Онлайн", "onramper_option_description": "Быстро купите крипто со многими способами оплаты. Доступно в большинстве стран. Спреды и сборы различаются.", @@ -397,6 +406,7 @@ "outdated_electrum_wallet_description": "Новые биткойн-кошельки, созданные в Cake, теперь содержат мнемоническую фразу из 24 слов. Вы обязательно должны создать новый биткойн-кошелек и перевести все свои средства в новый кошелек из 24 слов, а также прекратить использование кошельков с мнемонической фразой из 12 слов. Пожалуйста, сделайте это немедленно, чтобы обезопасить свои средства.", "outdated_electrum_wallet_receive_warning": "Если этот кошелек имеет мнемоническую фразу из 12 слов и был создан в Cake, НЕ переводите биткойны на этот кошелек. Любые BTC, переведенные на этот кошелек, могут быть потеряны. Создайте новый кошелек с мнемоническои фразы из 24 слов (коснитесь меню в правом верхнем углу, выберите «Кошельки», выберите «Создать новый кошелек», затем выберите «Bitcoin») и НЕМЕДЛЕННО переведите туда свои BTC. Новые (24 слова) кошельки BTC от Cake безопасны", "outgoing": "Исходящие", + "outputs": "Выходы", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Недействительное событие сопряжения", "password": "Пароль", @@ -644,6 +654,7 @@ "template_name": "Имя Шаблона", "third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!", "third_intro_title": "Yat хорошо взаимодействует с другими", + "thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.", "time": "${minutes}мин ${seconds}сек", "tip": "Совет:", "today": "Сегодня", @@ -661,6 +672,7 @@ "totp_code": "TOTP-код", "totp_secret_code": "Секретный код ТОТП", "totp_verification_success": "Проверка прошла успешно!", + "track": "Отслеживать", "trade_details_copied": "${title} скопировано в буфер обмена", "trade_details_created_at": "Создано", "trade_details_fetching": "Получение", @@ -711,6 +723,16 @@ "transactions": "Транзакции", "transactions_by_date": "Сортировать по дате", "trusted": "доверенный", + "tx_commit_exception_no_dust_on_change": "Транзакция отклоняется с этой суммой. С этими монетами вы можете отправлять ${min} без изменения или ${max}, которые возвращают изменение.", + "tx_commit_failed": "Комплект транзакции не удался. Пожалуйста, свяжитесь с поддержкой.", + "tx_no_dust_exception": "Транзакция отклоняется путем отправки слишком маленькой суммы. Пожалуйста, попробуйте увеличить сумму.", + "tx_not_enough_inputs_exception": "Недостаточно входов доступны. Пожалуйста, выберите больше под контролем монет", + "tx_rejected_dust_change": "Транзакция отклоняется в соответствии с правилами сети, низкой суммой изменений (пыль). Попробуйте отправить все или уменьшить сумму.", + "tx_rejected_dust_output": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, увеличьте сумму.", + "tx_rejected_dust_output_send_all": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, проверьте баланс монет, выбранных под контролем монет.", + "tx_rejected_vout_negative": "Недостаточно баланс, чтобы оплатить плату этой транзакции. Пожалуйста, проверьте баланс монет под контролем монет.", + "tx_wrong_balance_exception": "У вас не хватает ${currency}, чтобы отправить эту сумму.", + "tx_zero_fee_exception": "Не может отправить транзакцию с платой 0. Попробуйте увеличить ставку или проверить соединение на наличие последних оценок.", "unavailable_balance": "Недоступный баланс", "unavailable_balance_description": "Недоступный баланс: в эту сумму входят средства, заблокированные в ожидающих транзакциях, и средства, которые вы активно заморозили в настройках управления монетами. Заблокированные балансы станут доступны после завершения соответствующих транзакций, а замороженные балансы останутся недоступными для транзакций, пока вы не решите их разморозить.", "unconfirmed": "Неподтвержденный баланс", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 9c37ff8bb..0795faf2e 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -43,6 +43,7 @@ "already_have_account": "มีบัญชีอยู่แล้ว?", "always": "เสมอ", "amount": "จำนวน: ", + "amount_is_below_minimum_limit": "ยอดคงเหลือหลังจากค่าธรรมเนียมของคุณจะน้อยกว่าจำนวนเงินขั้นต่ำที่จำเป็นสำหรับการแลกเปลี่ยน (${min})", "amount_is_estimate": "จำนวนที่จะได้รับเป็นการประมาณการ", "amount_is_guaranteed": "จำนวนที่จะได้รับมีการรับประกัน", "and": "และ", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "การชำระเงินด้วย Bitcoin ต้องการการยืนยัน 1 ครั้ง ซึ่งอาจใช้เวลา 20 นาทีหรือนานกว่านั้น ขอบคุณสำหรับความอดทนของคุณ! คุณจะได้รับอีเมลเมื่อการชำระเงินได้รับการยืนยัน", "Blocks_remaining": "${status} บล็อกที่เหลืออยู่", "bright_theme": "สดใส", + "bump_fee": "ค่าธรรมเนียมชน", "buy": "ซื้อ", "buy_alert_content": "ขณะนี้เรารองรับการซื้อ Bitcoin, Ethereum, Litecoin และ Monero เท่านั้น โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงิน Bitcoin, Ethereum, Litecoin หรือ Monero", "buy_bitcoin": "ซื้อ Bitcoin", @@ -132,6 +134,8 @@ "confirm": "ยืนยัน", "confirm_delete_template": "การดำเนินการนี้จะลบแบบฟอร์มนี้ คุณต้องการดำเนินการต่อหรือไม่?", "confirm_delete_wallet": "การดำเนินการนี้จะลบกระเป๋านี้ คุณต้องการดำเนินการต่อหรือไม่?", + "confirm_fee_deduction": "ยืนยันการหักค่าธรรมเนียม", + "confirm_fee_deduction_content": "คุณตกลงที่จะหักค่าธรรมเนียมจากผลลัพธ์หรือไม่?", "confirm_sending": "ยืนยันการส่ง", "confirmations": "การยืนยัน", "confirmed": "ยอดคงเหลือที่ยืนยันแล้ว", @@ -171,6 +175,7 @@ "debit_card": "บัตรเดบิต", "debit_card_terms": "การเก็บรักษาและใช้หมายเลขบัตรจ่ายเงิน (และข้อมูลประจำตัวที่เกี่ยวข้องกับหมายเลขบัตรจ่ายเงิน) ในกระเป๋าดิจิทัลนี้ จะต้องยึดถือข้อกำหนดและเงื่อนไขของข้อตกลงผู้ใช้บัตรของผู้ถือบัตรที่เกี่ยวข้องกับบัตรผู้ถือบัตร ซึ่งจะมีผลตั้งแต่เวลานั้น", "decimal_places_error": "ทศนิยมมากเกินไป", + "decimals_cannot_be_zero": "ทศนิยมโทเค็นไม่สามารถเป็นศูนย์ได้", "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น", "delete": "ลบ", @@ -211,6 +216,7 @@ "edit_token": "แก้ไขโทเค็น", "electrum_address_disclaimer": "เราสร้างที่อยู่ใหม่ทุกครั้งที่คุณใช้หนึ่งอย่าง แต่ที่อยู่เก่ายังสามารถใช้ได้ต่อไป", "email_address": "ที่อยู่อีเมล", + "enable_replace_by_fee": "เปิดใช้งานการเปลี่ยนโดยค่าธรรมเนียม", "enabled": "เปิดใช้งาน", "enter_amount": "กรอกจำนวน", "enter_backup_password": "ป้อนรหัสผ่านสำรองที่นี่", @@ -247,6 +253,7 @@ "errorGettingCredentials": "ล้มเหลว: เกิดข้อผิดพลาดขณะรับข้อมูลรับรอง", "errorSigningTransaction": "เกิดข้อผิดพลาดขณะลงนามธุรกรรม", "estimated": "ประมาณการ", + "estimated_new_fee": "ค่าธรรมเนียมใหม่โดยประมาณ", "etherscan_history": "ประวัติอีเธอร์สแกน", "event": "เหตุการณ์", "events": "กิจกรรม", @@ -313,6 +320,7 @@ "in_store": "ในร้าน", "incoming": "ขาเข้า", "incorrect_seed": "ข้อความที่ป้อนไม่ถูกต้อง", + "inputs": "อินพุต", "introducing_cake_pay": "ยินดีต้อนรับสู่ Cake Pay!", "invalid_input": "อินพุตไม่ถูกต้อง", "invoice_details": "รายละเอียดใบแจ้งหนี้", @@ -380,6 +388,7 @@ "offer_expires_in": "ข้อเสนอจะหมดอายุใน: ", "offline": "ออฟไลน์", "ok": "ตกลง", + "old_fee": "ค่าธรรมเนียมเก่า", "onion_link": "ลิงค์หัวหอม", "online": "ออนไลน์", "onramper_option_description": "ซื้อ crypto อย่างรวดเร็วด้วยวิธีการชำระเงินจำนวนมาก มีให้บริการในประเทศส่วนใหญ่ สเปรดและค่าธรรมเนียมแตกต่างกันไป", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "กระเป๋า Bitcoin ใหม่ที่สร้างใน Cake มี seed ขนาด 24 คำ ซึ่งจำเป็นต้องสร้างกระเป๋า Bitcoin ใหม่และโอนทุกเงินของคุณไปยังกระเป๋าใหม่ขนาด 24 คำ และหยุดใช้กระเป๋าที่มี seed ขนาด 12 คำ กรุณาทำด่วนเพื่อรักษาเงินของคุณ", "outdated_electrum_wallet_receive_warning": "หากกระเป๋านี้มีซีดีที่มี 12 คำและถูกสร้างขึ้นใน Cake อย่าโอน Bitcoin เข้ากระเป๋านี้ ทุกจำนวน BTC ที่โอนเข้ากระเป๋านี้อาจสูญหาย สร้างกระเป๋าใหม่ที่มีซีดีที่มี 24 คำ (กดที่เมนูที่มุมขวาบนแล้วเลือก Wallets และเลือก Create New Wallet จากนั้นเลือก Bitcoin) และย้าย BTC ไปที่นั้นทันที กระเป๋า BTC ที่มีซีดีที่มี 24 คำของ Cake ปลอดภัย", "outgoing": "ขาออก", + "outputs": "เอาต์พุต", "overwrite_amount": "เขียนทับจำนวน", "pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง", "password": "รหัสผ่าน", @@ -643,6 +653,7 @@ "template_name": "ชื่อแม่แบบ", "third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!", "third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น", + "thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น", "time": "${minutes}m ${seconds}s", "tip": "เพิ่มค่าตอบแทน:", "today": "วันนี้", @@ -660,6 +671,7 @@ "totp_code": "รหัสทีโอพี", "totp_secret_code": "รหัสลับ TOTP", "totp_verification_success": "การยืนยันสำเร็จ!", + "track": "ติดตาม", "trade_details_copied": "${title} คัดลอกไปยัง Clipboard", "trade_details_created_at": "สร้างเมื่อ", "trade_details_fetching": "กำลังเรียกข้อมูล", @@ -710,6 +722,16 @@ "transactions": "ธุรกรรม", "transactions_by_date": "ธุรกรรมตามวันที่", "trusted": "มั่นคง", + "tx_commit_exception_no_dust_on_change": "ธุรกรรมถูกปฏิเสธด้วยจำนวนเงินนี้ ด้วยเหรียญเหล่านี้คุณสามารถส่ง ${min} โดยไม่ต้องเปลี่ยนแปลงหรือ ${max} ที่ส่งคืนการเปลี่ยนแปลง", + "tx_commit_failed": "การทำธุรกรรมล้มเหลว กรุณาติดต่อฝ่ายสนับสนุน", + "tx_no_dust_exception": "การทำธุรกรรมถูกปฏิเสธโดยการส่งจำนวนน้อยเกินไป โปรดลองเพิ่มจำนวนเงิน", + "tx_not_enough_inputs_exception": "มีอินพุตไม่เพียงพอ โปรดเลือกเพิ่มเติมภายใต้การควบคุมเหรียญ", + "tx_rejected_dust_change": "ธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนการเปลี่ยนแปลงต่ำ (ฝุ่น) ลองส่งทั้งหมดหรือลดจำนวนเงิน", + "tx_rejected_dust_output": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดเพิ่มจำนวนเงิน", + "tx_rejected_dust_output_send_all": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดตรวจสอบยอดคงเหลือของเหรียญที่เลือกภายใต้การควบคุมเหรียญ", + "tx_rejected_vout_negative": "ยอดคงเหลือไม่เพียงพอที่จะจ่ายสำหรับค่าธรรมเนียมการทำธุรกรรมนี้ โปรดตรวจสอบยอดคงเหลือของเหรียญภายใต้การควบคุมเหรียญ", + "tx_wrong_balance_exception": "คุณมีไม่เพียงพอ ${currency} ในการส่งจำนวนนี้", + "tx_zero_fee_exception": "ไม่สามารถส่งธุรกรรมด้วยค่าธรรมเนียม 0 ลองเพิ่มอัตราหรือตรวจสอบการเชื่อมต่อของคุณสำหรับการประมาณการล่าสุด", "unavailable_balance": "ยอดคงเหลือไม่พร้อมใช้งาน", "unavailable_balance_description": "ยอดคงเหลือที่ไม่พร้อมใช้งาน: ยอดรวมนี้รวมถึงเงินทุนที่ถูกล็อคในการทำธุรกรรมที่รอดำเนินการและที่คุณได้แช่แข็งไว้ในการตั้งค่าการควบคุมเหรียญของคุณ ยอดคงเหลือที่ถูกล็อคจะพร้อมใช้งานเมื่อธุรกรรมที่เกี่ยวข้องเสร็จสมบูรณ์ ในขณะที่ยอดคงเหลือที่แช่แข็งจะไม่สามารถเข้าถึงได้สำหรับธุรกรรมจนกว่าคุณจะตัดสินใจยกเลิกการแช่แข็ง", "unconfirmed": "ยอดคงเหลือที่ไม่ได้รับการยืนยัน", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index d4be21741..91ca5a548 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -43,6 +43,7 @@ "already_have_account": "Mayroon nang account?", "always": "Palagi", "amount": "Halaga:", + "amount_is_below_minimum_limit": "Ang iyong balanse pagkatapos ng mga bayarin ay mas mababa kaysa sa minimum na halaga na kinakailangan para sa palitan (${min})", "amount_is_estimate": "Ang natanggap na halaga ay isang pagtatantya", "amount_is_guaranteed": "Ang natanggap na halaga ay garantisado", "and": "at", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Ang mga pagbabayad sa Bitcoin ay nangangailangan ng 1 kumpirmasyon, na maaaring tumagal ng 20 minuto o mas mahaba. Salamat sa iyong pasensya! Mag -email ka kapag nakumpirma ang pagbabayad.", "Blocks_remaining": "Ang natitirang ${status} ay natitira", "bright_theme": "Maliwanag", + "bump_fee": "Bayad sa paga", "buy": "Bilhin", "buy_alert_content": "Sa kasalukuyan ay sinusuportahan lamang namin ang pagbili ng Bitcoin, Ethereum, Litecoin, at Monero. Mangyaring lumikha o lumipat sa iyong Bitcoin, Ethereum, Litecoin, o Monero Wallet.", "buy_bitcoin": "Bumili ng bitcoin", @@ -132,6 +134,8 @@ "confirm": "Kumpirmahin", "confirm_delete_template": "Ang pagkilos na ito ay tatanggalin ang template na ito. Nais mo bang magpatuloy?", "confirm_delete_wallet": "Ang pagkilos na ito ay tatanggalin ang pitaka na ito. Nais mo bang magpatuloy?", + "confirm_fee_deduction": "Kumpirmahin ang pagbabawas ng bayad", + "confirm_fee_deduction_content": "Sumasang -ayon ka bang bawasan ang bayad mula sa output?", "confirm_sending": "Kumpirmahin ang pagpapadala", "confirmations": "Mga kumpirmasyon", "confirmed": "Nakumpirma na balanse", @@ -171,6 +175,7 @@ "debit_card": "Debit card", "debit_card_terms": "Ang pag -iimbak at paggamit ng numero ng iyong card ng pagbabayad (at mga kredensyal na naaayon sa iyong numero ng card ng pagbabayad) sa digital na pitaka na ito ay napapailalim sa mga termino at kundisyon ng naaangkop na kasunduan sa cardholder kasama ang nagbigay ng card ng pagbabayad, tulad ng sa oras -oras.", "decimal_places_error": "Masyadong maraming mga lugar na desimal", + "decimals_cannot_be_zero": "Ang Token Decimal ay hindi maaaring maging zero.", "default_buy_provider": "Default na Provider ng Pagbili", "default_sell_provider": "Default na Sell Provider", "delete": "Tanggalin", @@ -211,6 +216,7 @@ "edit_token": "I -edit ang token", "electrum_address_disclaimer": "Bumubuo kami ng mga bagong address sa tuwing gumagamit ka ng isa, ngunit ang mga nakaraang address ay patuloy na gumagana", "email_address": "Email address", + "enable_replace_by_fee": "Paganahin ang palitan-by-fee", "enabled": "Pinagana", "enter_amount": "Ipasok ang halaga", "enter_backup_password": "Ipasok ang backup password dito", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Nabigo: Error habang kumukuha ng mga kredensyal", "errorSigningTransaction": "May naganap na error habang pinipirmahan ang transaksyon", "estimated": "Tinatayang", + "estimated_new_fee": "Tinatayang bagong bayad", "etherscan_history": "Kasaysayan ng Etherscan", "event": "Kaganapan", "events": "Mga kaganapan", @@ -313,6 +320,7 @@ "in_store": "Nakatago", "incoming": "Papasok", "incorrect_seed": "Ang teksto na ipinasok ay hindi wasto.", + "inputs": "Mga input", "introducing_cake_pay": "Ipinakikilala ang cake pay!", "invalid_input": "Di -wastong input", "invoice_details": "Mga detalye ng invoice", @@ -380,6 +388,7 @@ "offer_expires_in": "Mag -expire ang alok sa:", "offline": "Offline", "ok": "Ok", + "old_fee": "Matandang bayad", "onion_link": "Link ng Onion", "online": "Online", "onramper_option_description": "Mabilis na bumili ng crypto na may maraming paraan ng pagbabayad. Available sa karamihan ng mga bansa. Iba-iba ang mga spread at bayarin.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Ang mga bagong wallets ng Bitcoin na nilikha sa cake ay mayroon na ngayong 24-salitang binhi. Ipinag-uutos na lumikha ka ng isang bagong pitaka ng Bitcoin at ilipat ang lahat ng iyong mga pondo sa bagong 24-salitang pitaka, at itigil ang paggamit ng mga pitaka na may 12-salitang binhi. Mangyaring gawin ito kaagad upang ma -secure ang iyong mga pondo.", "outdated_electrum_wallet_receive_warning": "Kung ang pitaka na ito ay may 12-salitang binhi at nilikha sa cake, huwag magdeposito sa Bitcoin sa pitaka na ito. Ang anumang BTC na inilipat sa pitaka na ito ay maaaring mawala. Lumikha ng isang bagong 24-word wallet (tapikin ang menu sa kanang tuktok, piliin ang mga pitaka, piliin ang Lumikha ng Bagong Wallet, pagkatapos ay piliin ang Bitcoin) at agad na ilipat ang iyong BTC doon. Ang mga bagong (24-salita) BTC Wallets mula sa cake ay ligtas", "outgoing": "Palabas", + "outputs": "Mga output", "overwrite_amount": "Overwrite na halaga", "pairingInvalidEvent": "Pagpares ng Di-wastong Kaganapan", "password": "Password", @@ -643,6 +653,7 @@ "template_name": "Pangalan ng Template", "third_intro_content": "Ang mga yats ay nakatira sa labas ng cake wallet, din. Ang anumang address ng pitaka sa mundo ay maaaring mapalitan ng isang yat!", "third_intro_title": "Si Yat ay mahusay na gumaganap sa iba", + "thorchain_taproot_address_not_supported": "Ang Tagabigay ng Thorchain ay hindi sumusuporta sa mga address ng taproot. Mangyaring baguhin ang address o pumili ng ibang provider.", "time": "${minutes} m ${seconds} s", "tip": "Tip:", "today": "Ngayon", @@ -660,6 +671,7 @@ "totp_code": "TOTP code", "totp_secret_code": "TOTP Secret Code", "totp_verification_success": "Matagumpay ang pagpapatunay!", + "track": "Subaybayan", "trade_details_copied": "${title} kinopya sa clipboard", "trade_details_created_at": "Nilikha sa", "trade_details_fetching": "Pagkuha", @@ -710,6 +722,16 @@ "transactions": "Mga Transaksyon", "transactions_by_date": "Mga Transaksyon ayon sa Petsa", "trusted": "Pinagkakatiwalaan", + "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 pagbabago o ${max} na nagbabalik ng pagbabago.", + "tx_commit_failed": "Nabigo ang transaksyon sa transaksyon. Mangyaring makipag -ugnay sa suporta.", + "tx_no_dust_exception": "Ang transaksyon ay tinanggihan sa pamamagitan ng pagpapadala ng isang maliit na maliit. Mangyaring subukang dagdagan ang halaga.", + "tx_not_enough_inputs_exception": "Hindi sapat na magagamit ang mga input. Mangyaring pumili ng higit pa sa ilalim ng control ng barya", + "tx_rejected_dust_change": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng pagbabago (alikabok). Subukang ipadala ang lahat o bawasan ang halaga.", + "tx_rejected_dust_output": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring dagdagan ang halaga.", + "tx_rejected_dust_output_send_all": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring suriin ang balanse ng mga barya na napili sa ilalim ng kontrol ng barya.", + "tx_rejected_vout_negative": "Hindi sapat na balanse upang magbayad para sa mga bayarin ng transaksyon na ito. Mangyaring suriin ang balanse ng mga barya sa ilalim ng kontrol ng barya.", + "tx_wrong_balance_exception": "Wala kang sapat na ${currency} upang maipadala ang halagang ito.", + "tx_zero_fee_exception": "Hindi maaaring magpadala ng transaksyon na may 0 bayad. Subukan ang pagtaas ng rate o pagsuri sa iyong koneksyon para sa pinakabagong mga pagtatantya.", "unavailable_balance": "Hindi available na balanse", "unavailable_balance_description": "Hindi Available na Balanse: Kasama sa kabuuang ito ang mga pondong naka-lock sa mga nakabinbing transaksyon at ang mga aktibong na-freeze mo sa iyong mga setting ng kontrol ng coin. Magiging available ang mga naka-lock na balanse kapag nakumpleto na ang kani-kanilang mga transaksyon, habang ang mga nakapirming balanse ay nananatiling hindi naa-access para sa mga transaksyon hanggang sa magpasya kang i-unfreeze ang mga ito.", "unconfirmed": "Hindi nakumpirma na balanse", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index a6209ed33..784ebfb7e 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -43,6 +43,7 @@ "already_have_account": "Zaten bir hesabınız var mı?", "always": "Her Zaman", "amount": "Miktar: ", + "amount_is_below_minimum_limit": "Ücretlerden sonra bakiyeniz, değişim için gereken minimum miktardan daha az olur (${min})", "amount_is_estimate": "Alacağınız tutar tahminidir", "amount_is_guaranteed": "Alacağınız tutar garantilidir", "and": "ve", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin ödemeleri, 20 dakika veya daha uzun sürebilen 1 onay gerektirir. Sabrınız için teşekkürler! Ödeme onaylandığında e-posta ile bilgilendirileceksiniz.", "Blocks_remaining": "${status} Blok Kaldı", "bright_theme": "Parlak", + "bump_fee": "Çarpma ücreti", "buy": "Alış", "buy_alert_content": "Şu anda yalnızca Bitcoin, Ethereum, Litecoin ve Monero satın alımını destekliyoruz. Lütfen Bitcoin, Ethereum, Litecoin veya Monero cüzdanınızı oluşturun veya cüzdanınıza geçin.", "buy_bitcoin": "Bitcoin Satın Al", @@ -132,6 +134,8 @@ "confirm": "Onayla", "confirm_delete_template": "Bu eylem, bu şablonu silecek. Devam etmek istiyor musun?", "confirm_delete_wallet": "Bu eylem, bu cüzdanı silecek. Devam etmek istiyor musun?", + "confirm_fee_deduction": "Ücret kesintisini onaylayın", + "confirm_fee_deduction_content": "Ücreti çıktıdan düşürmeyi kabul ediyor musunuz?", "confirm_sending": "Göndermeyi onayla", "confirmations": "Onay", "confirmed": "Onaylanmış Bakiye", @@ -171,6 +175,7 @@ "debit_card": "Ön ödemeli Kart", "debit_card_terms": "Ödeme kartı numaranızın (ve kart numaranıza karşılık gelen kimlik bilgilerinin) bu dijital cüzdanda saklanması ve kullanılması, zaman zaman yürürlükte olan ödeme kartı veren kuruluşla yapılan ilgili kart sahibi sözleşmesinin Hüküm ve Koşullarına tabidir.", "decimal_places_error": "Çok fazla ondalık basamak", + "decimals_cannot_be_zero": "Token oncial sıfır olamaz.", "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "default_sell_provider": "Varsayılan Satış Sağlayıcısı", "delete": "Sil", @@ -211,6 +216,7 @@ "edit_token": "Belirteci düzenle", "electrum_address_disclaimer": "Adresini her kullandığında yeni adres oluşturuyoruz, ancak önceki adresler de çalışmaya devam eder", "email_address": "E-posta Adresi", + "enable_replace_by_fee": "Farklı Değiştir'i Etkinleştir", "enabled": "Etkin", "enter_amount": "Miktar Girin", "enter_backup_password": "Yedekleme parolasını buraya gir", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Başarısız: Kimlik bilgileri alınırken hata oluştu", "errorSigningTransaction": "İşlem imzalanırken bir hata oluştu", "estimated": "Tahmini", + "estimated_new_fee": "Tahmini yeni ücret", "etherscan_history": "Etherscan geçmişi", "event": "Etkinlik", "events": "Olaylar", @@ -313,6 +320,7 @@ "in_store": "Mağazada", "incoming": "Gelen", "incorrect_seed": "Girilen metin geçerli değil.", + "inputs": "Girişler", "introducing_cake_pay": "Cake Pay ile tanışın!", "invalid_input": "Geçersiz Giriş", "invoice_details": "fatura detayları", @@ -380,6 +388,7 @@ "offer_expires_in": "Teklifin bitmesine kalan: ", "offline": "Çevrimdışı", "ok": "Tamam", + "old_fee": "Eski ücret", "onion_link": "soğan bağlantısı", "online": "Çevrimiçi", "onramper_option_description": "Birçok ödeme yöntemi ile hızlı bir şekilde kripto satın alın. Çoğu ülkede mevcuttur. Forma ve ücretler değişir.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Cake'te oluşturulan yeni Bitcoin cüzdanları artık 24 kelimelik bir tohuma sahip. Yeni bir Bitcoin cüzdanı oluşturmanız ve tüm paranızı 24 kelimelik yeni cüzdana aktarmanız ve 12 kelimelik tohuma sahip cüzdanları kullanmayı bırakmanız zorunludur. Lütfen paranızı güvence altına almak için bunu hemen yapın.", "outdated_electrum_wallet_receive_warning": "Bu cüzdanın 12 kelimelik bir tohumu varsa ve Cake'te oluşturulduysa, bu cüzdana Bitcoin YATIRMAYIN. Bu cüzdana aktarılan tüm BTC'ler kaybolabilir. 24 kelimelik yeni bir cüzdan oluşturun (sağ üstteki menüye dokunun, Cüzdanlar'ı seçin, Yeni Cüzdan Oluştur'u seçin, ardından Bitcoin'i seçin) ve BTC'nizi HEMEN oraya taşıyın. Cake'in yeni (24 kelimelik) BTC cüzdanları güvenlidir", "outgoing": "Giden", + "outputs": "çıktılar", "overwrite_amount": "Miktarın üzerine yaz", "pairingInvalidEvent": "Geçersiz Etkinliği Eşleştirme", "password": "Parola", @@ -643,6 +653,7 @@ "template_name": "şablon adı", "third_intro_content": "Yat'lar Cake Wallet'ın dışında da çalışabilir. Dünya üzerindeki herhangi bir cüzdan adresi Yat ile değiştirilebilir!", "third_intro_title": "Yat diğerleriyle iyi çalışır", + "thorchain_taproot_address_not_supported": "Thorchain sağlayıcısı Taproot adreslerini desteklemiyor. Lütfen adresi değiştirin veya farklı bir sağlayıcı seçin.", "time": "${minutes}d ${seconds}s", "tip": "Bahşiş:", "today": "Bugün", @@ -660,6 +671,7 @@ "totp_code": "TOTP Kodu", "totp_secret_code": "TOTP Gizli Kodu", "totp_verification_success": "Doğrulama Başarılı!", + "track": "İzlemek", "trade_details_copied": "${title} panoya kopyalandı", "trade_details_created_at": "'da oluşturuldu", "trade_details_fetching": "Getiriliyor", @@ -710,6 +722,16 @@ "transactions": "İşlemler", "transactions_by_date": "Tarihe göre transferler", "trusted": "Güvenilir", + "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_no_dust_exception": "İşlem, çok küçük bir miktar gönderilerek reddedilir. Lütfen miktarı artırmayı deneyin.", + "tx_not_enough_inputs_exception": "Yeterli giriş yok. Lütfen madeni para kontrolü altında daha fazlasını seçin", + "tx_rejected_dust_change": "Ağ kurallarına göre reddedilen işlem, düşük değişim miktarı (toz). Tümünü göndermeyi veya miktarı azaltmayı deneyin.", + "tx_rejected_dust_output": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen miktarı artırın.", + "tx_rejected_dust_output_send_all": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen madeni para kontrolü altında seçilen madeni para dengesini kontrol edin.", + "tx_rejected_vout_negative": "Bu işlem ücretleri için ödeme yapmak için yeterli bakiye yok. Lütfen madeni para kontrolü altındaki madeni para dengesini kontrol edin.", + "tx_wrong_balance_exception": "Bu miktarı göndermek için yeterli ${currency} yok.", + "tx_zero_fee_exception": "0 ücret ile işlem gönderilemez. En son tahminler için oranı artırmayı veya bağlantınızı kontrol etmeyi deneyin.", "unavailable_balance": "Kullanılamayan bakiye", "unavailable_balance_description": "Kullanılamayan Bakiye: Bu toplam, bekleyen işlemlerde kilitlenen fonları ve jeton kontrol ayarlarınızda aktif olarak dondurduğunuz fonları içerir. Kilitli bakiyeler, ilgili işlemleri tamamlandıktan sonra kullanılabilir hale gelir; dondurulmuş bakiyeler ise siz onları dondurmaya karar verene kadar işlemler için erişilemez durumda kalır.", "unconfirmed": "Onaylanmamış Bakiye", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 3d60b9e8e..65a1f5c96 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -43,6 +43,7 @@ "already_have_account": "Вже є обліковий запис?", "always": "Завжди", "amount": "Сума: ", + "amount_is_below_minimum_limit": "Ваш баланс після зборів буде меншим, ніж мінімальна сума, необхідна для обміну (${min})", "amount_is_estimate": "Отримана сума є приблизною", "amount_is_guaranteed": "Отримана сума є гарантованою", "and": "і", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Платежі Bitcoin потребують 1 підтвердження, яке може зайняти 20 хвилин або більше. Дякую за Ваше терпіння! Ви отримаєте електронний лист, коли платіж буде підтверджено.", "Blocks_remaining": "${status} Залишилось блоків", "bright_theme": "Яскрава", + "bump_fee": "Підвищити комісію", "buy": "Купити", "buy_alert_content": "Наразі ми підтримуємо купівлю лише Bitcoin, Ethereum, Litecoin і Monero. Створіть або перейдіть на свій гаманець Bitcoin, Ethereum, Litecoin або Monero.", "buy_bitcoin": "Купити Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Підтвердити", "confirm_delete_template": "Ця дія видалить шаблон. Ви хочете продовжити?", "confirm_delete_wallet": "Ця дія видалить гаманець. Ви хочете продовжити?", + "confirm_fee_deduction": "Підтвердьте відрахування комісії", + "confirm_fee_deduction_content": "Чи погоджуєтесь ви вирахувати комісію з сумми одержувача?", "confirm_sending": "Підтвердити відправлення", "confirmations": "Підтвердження", "confirmed": "Підтверджений баланс", @@ -171,6 +175,7 @@ "debit_card": "Дебетова картка", "debit_card_terms": "Зберігання та використання номера вашої платіжної картки (та облікових даних, які відповідають номеру вашої платіжної картки) у цьому цифровому гаманці регулюються Умовами відповідної угоди власника картки з емітентом платіжної картки, що діє з час від часу.", "decimal_places_error": "Забагато знаків після коми", + "decimals_cannot_be_zero": "Десятковий знак не може бути нульовим.", "default_buy_provider": "Постачальник покупки за замовчуванням", "default_sell_provider": "Постачальник продажу за замовчуванням", "delete": "Видалити", @@ -211,6 +216,7 @@ "edit_token": "Редагувати маркер", "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати", "email_address": "Адреса електронної пошти", + "enable_replace_by_fee": "Увімкнути заміну з комісією", "enabled": "Увімкнено", "enter_amount": "Введіть суму", "enter_backup_password": "Введіть пароль резервної копії", @@ -247,6 +253,7 @@ "errorGettingCredentials": "Помилка: помилка під час отримання облікових даних", "errorSigningTransaction": "Під час підписання транзакції сталася помилка", "estimated": "Приблизно ", + "estimated_new_fee": "Орієнтовна нова комісія", "etherscan_history": "Історія Etherscan", "event": "Подія", "events": "Події", @@ -313,6 +320,7 @@ "in_store": "У магазині", "incoming": "Вхідні", "incorrect_seed": "Введений текст невірний.", + "inputs": "Вхoди", "introducing_cake_pay": "Представляємо Cake Pay!", "invalid_input": "Неправильні дані", "invoice_details": "Реквізити рахунку-фактури", @@ -380,6 +388,7 @@ "offer_expires_in": "Пропозиція закінчиться через: ", "offline": "Офлайн", "ok": "OK", + "old_fee": "Стара комісія", "onion_link": "Посилання на цибулю", "online": "Онлайн", "onramper_option_description": "Швидко купуйте криптовалюту з багатьма методами оплати. Доступний у більшості країн. Поширення та збори різняться.", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "Нові біткойн-гаманці, створені в Cake, тепер містять мнемонічну фразу з 24 слів. Обов’язково стовріть новий біткойн-гаманець, переведіть всі кошти на новий гаманець із 24 слів і припиніть використання гаманців із мнемонічною фразою з 12 слів. Зробіть це негайно, щоб убезпечити свої кошти.", "outdated_electrum_wallet_receive_warning": "Якщо цей гаманець має мнемонічну фразу з 12 слів і був створений у Cake, НЕ переводьте біткойни на цей гаманець. Будь-які BTC, переведений на цей гаманець, можуть бути втраченими. Створіть новий гаманець з мнемонічною фразою з 24 слів (торкніться меню у верхньому правому куті, виберіть Гаманці, виберіть Створити новий гаманець, потім виберіть Bitcoin) і НЕГАЙНО переведіть туди свії BTC. Нові (з мнемонічною фразою з 24 слів) гаманці BTC від Cake надійно захищені", "outgoing": "Вихідні", + "outputs": "Виходи", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "Недійсна подія сполучення", "password": "Пароль", @@ -644,6 +654,7 @@ "template_name": "Назва шаблону", "third_intro_content": "Yat знаходиться за межами Cake Wallet. Будь-яку адресу гаманця на землі можна замінити на Yat!", "third_intro_title": "Yat добре взаємодіє з іншими", + "thorchain_taproot_address_not_supported": "Постачальник Thorchain не підтримує адреси Taproot. Будь ласка, змініть адресу або виберіть іншого постачальника.", "time": "${minutes}хв ${seconds}сек", "tip": "Порада:", "today": "Сьогодні", @@ -661,6 +672,7 @@ "totp_code": "Код TOTP", "totp_secret_code": "Секретний код TOTP", "totp_verification_success": "Перевірка успішна!", + "track": "Відслідковувати", "trade_details_copied": "${title} скопійовано в буфер обміну", "trade_details_created_at": "Створено", "trade_details_fetching": "Отримання", @@ -711,6 +723,16 @@ "transactions": "Транзакції", "transactions_by_date": "Сортувати по даті", "trusted": "довіряють", + "tx_commit_exception_no_dust_on_change": "Транзакція відхилена цією сумою. За допомогою цих монет ви можете надіслати ${min} без змін або ${max}, що повертає зміни.", + "tx_commit_failed": "Транзакційна комісія не вдалося. Будь ласка, зв'яжіться з підтримкою.", + "tx_no_dust_exception": "Угода відхиляється, відправивши суму занадто мала. Будь ласка, спробуйте збільшити суму.", + "tx_not_enough_inputs_exception": "Недостатньо доступних входів. Виберіть більше під контролем монети", + "tx_rejected_dust_change": "Транзакція відхилена за допомогою мережевих правил, низька кількість змін (пил). Спробуйте надіслати все або зменшити суму.", + "tx_rejected_dust_output": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, збільшуйте суму.", + "tx_rejected_dust_output_send_all": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, перевірте баланс монет, вибраних під контролем монет.", + "tx_rejected_vout_negative": "Недостатньо балансу, щоб оплатити плату за цю транзакцію. Будь ласка, перевірте баланс монет під контролем монет.", + "tx_wrong_balance_exception": "У вас недостатньо ${currency}, щоб надіслати цю суму.", + "tx_zero_fee_exception": "Не вдається відправити транзакцію з 0 платежами. Спробуйте збільшити ставку або перевірити з'єднання на останні оцінки.", "unavailable_balance": "Недоступний баланс", "unavailable_balance_description": "Недоступний баланс: ця сума включає кошти, заблоковані в незавершених транзакціях, і ті, які ви активно заморозили в налаштуваннях контролю монет. Заблоковані баланси стануть доступними після завершення відповідних транзакцій, тоді як заморожені баланси залишаються недоступними для транзакцій, доки ви не вирішите їх розморозити.", "unconfirmed": "Непідтверджений баланс", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index e0e74e6de..6c768daa8 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -43,6 +43,7 @@ "already_have_account": "پہلے سے ہی اکاؤنٹ ہے؟", "always": "ہمیشہ", "amount": "رقم کی رقم:", + "amount_is_below_minimum_limit": "فیس کے بعد آپ کا توازن تبادلہ کے لئے درکار کم سے کم رقم سے کم ہوگا (${min}", "amount_is_estimate": "وصول شدہ رقم ایک تخمینہ ہے۔", "amount_is_guaranteed": "وصول شدہ رقم کی ضمانت ہے۔", "and": "اور", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "بٹ کوائن کی ادائیگی میں 1 تصدیق کی ضرورت ہوتی ہے ، جس میں 20 منٹ یا اس سے زیادہ وقت لگ سکتا ہے۔ آپ کے صبر کا شکریہ! ادائیگی کی تصدیق ہونے پر آپ کو ای میل کیا جائے گا۔", "Blocks_remaining": "${status} بلاکس باقی ہیں۔", "bright_theme": "روشن", + "bump_fee": "بمپ فیس", "buy": "خریدنے", "buy_alert_content": "۔ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﺱﺍ ﺎﯾ ﮟﯿﺋﺎﻨﺑ ﭧﯿﻟﺍﻭ Monero ﺎﯾ ،Bitcoin، Ethereum، Litecoin ﺎﻨﭘﺍ ﻡ", "buy_bitcoin": "Bitcoin خریدیں۔", @@ -132,6 +134,8 @@ "confirm": "تصدیق کریں۔", "confirm_delete_template": "یہ عمل اس ٹیمپلیٹ کو حذف کر دے گا۔ کیا آپ جاری رکھنا چاہتے ہیں؟", "confirm_delete_wallet": "اس کارروائی سے یہ پرس حذف ہو جائے گا۔ کیا آپ جاری رکھنا چاہتے ہیں؟", + "confirm_fee_deduction": "فیس میں کٹوتی کی تصدیق کریں", + "confirm_fee_deduction_content": "کیا آپ آؤٹ پٹ سے فیس کم کرنے پر راضی ہیں؟", "confirm_sending": "بھیجنے کی تصدیق کریں۔", "confirmations": "تصدیقات", "confirmed": "تصدیق شدہ بیلنس", @@ -171,6 +175,7 @@ "debit_card": "ڈیبٹ کارڈ", "debit_card_terms": "اس ڈیجیٹل والیٹ میں آپ کے ادائیگی کارڈ نمبر (اور آپ کے ادائیگی کارڈ نمبر سے متعلقہ اسناد) کا ذخیرہ اور استعمال ادائیگی کارڈ جاری کنندہ کے ساتھ قابل اطلاق کارڈ ہولڈر کے معاہدے کی شرائط و ضوابط کے ساتھ مشروط ہے، جیسا کہ وقتاً فوقتاً نافذ ہوتا ہے۔", "decimal_places_error": "بہت زیادہ اعشاریہ جگہیں۔", + "decimals_cannot_be_zero": "ٹوکن اعشاریہ صفر نہیں ہوسکتا۔", "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ", "delete": "حذف کریں۔", @@ -211,6 +216,7 @@ "edit_token": "ٹوکن میں ترمیم کریں۔", "electrum_address_disclaimer": "جب بھی آپ ایک کا استعمال کرتے ہیں تو ہم نئے پتے تیار کرتے ہیں، لیکن پچھلے پتے کام کرتے رہتے ہیں۔", "email_address": "ای میل اڈریس", + "enable_replace_by_fee": "فی فیس کو تبدیل کریں", "enabled": "فعال", "enter_amount": "رقم درج کریں۔", "enter_backup_password": "یہاں بیک اپ پاس ورڈ درج کریں۔", @@ -247,6 +253,7 @@ "errorGettingCredentials": "۔ﯽﺑﺍﺮﺧ ﮟﯿﻣ ﮯﻧﺮﮐ ﻞﺻﺎﺣ ﺩﺎﻨﺳﺍ :ﻡﺎﮐﺎﻧ", "errorSigningTransaction": "۔ﮯﮨ ﯽﺌﮔﺁ ﺶﯿﭘ ﯽﺑﺍﺮﺧ ﮏﯾﺍ ﺖﻗﻭ ﮯﺗﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", "estimated": "تخمینہ لگایا", + "estimated_new_fee": "تخمینہ شدہ نئی فیس", "etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ", "event": "ﺐﯾﺮﻘﺗ", "events": "ﺕﺎﺒﯾﺮﻘﺗ", @@ -313,6 +320,7 @@ "in_store": "اسٹور میں", "incoming": "آنے والا", "incorrect_seed": "درج کردہ متن درست نہیں ہے۔", + "inputs": "آدانوں", "introducing_cake_pay": "Cake پے کا تعارف!", "invalid_input": "غلط ان پٹ", "invoice_details": "رسید کی تفصیلات", @@ -380,6 +388,7 @@ "offer_expires_in": "پیشکش کی میعاد اس وقت ختم ہو جاتی ہے:", "offline": "آف لائن", "ok": "ٹھیک ہے", + "old_fee": "پرانی فیس", "onion_link": "پیاز کا لنک", "online": "آن لائن", "onramper_option_description": "ادائیگی کے بہت سے طریقوں سے جلدی سے کرپٹو خریدیں۔ زیادہ تر ممالک میں دستیاب ہے۔ پھیلاؤ اور فیس مختلف ہوتی ہے۔", @@ -398,6 +407,7 @@ "outdated_electrum_wallet_description": "Cake میں بنائے گئے نئے Bitcoin بٹوے میں اب 24 الفاظ کا بیج ہے۔ یہ لازمی ہے کہ آپ ایک نیا Bitcoin والیٹ بنائیں اور اپنے تمام فنڈز کو نئے 24 الفاظ والے والیٹ میں منتقل کریں، اور 12 الفاظ کے بیج والے بٹوے کا استعمال بند کریں۔ براہ کرم اپنے فنڈز کو محفوظ بنانے کے لیے فوری طور پر ایسا کریں۔", "outdated_electrum_wallet_receive_warning": "اگر اس پرس میں 12 الفاظ کا بیج ہے اور اسے Cake میں بنایا گیا ہے، تو اس بٹوے میں Bitcoin جمع نہ کریں۔ اس بٹوے میں منتقل کیا گیا کوئی بھی BTC ضائع ہو سکتا ہے۔ ایک نیا 24 الفاظ والا والیٹ بنائیں (اوپر دائیں جانب مینو کو تھپتھپائیں، Wallets کو منتخب کریں، نیا والیٹ بنائیں، پھر Bitcoin کو منتخب کریں) اور فوری طور پر اپنے BTC کو وہاں منتقل کریں۔ Cake کے نئے (24-لفظوں) BTC بٹوے محفوظ ہیں۔", "outgoing": "سبکدوش ہونے والے", + "outputs": "نتائج", "overwrite_amount": "رقم کو اوور رائٹ کریں۔", "pairingInvalidEvent": "ﭧﻧﻮﯾﺍ ﻂﻠﻏ ﺎﻧﺎﻨﺑ ﺍﮌﻮﺟ", "password": "پاس ورڈ", @@ -645,6 +655,7 @@ "template_name": "ٹیمپلیٹ کا نام", "third_intro_content": "Yats بھی Cake والیٹ سے باہر رہتے ہیں۔ زمین پر کسی بھی بٹوے کے پتے کو Yat سے تبدیل کیا جا سکتا ہے!", "third_intro_title": "Yat دوسروں کے ساتھ اچھی طرح کھیلتا ہے۔", + "thorchain_taproot_address_not_supported": "تھورچین فراہم کنندہ ٹیپروٹ پتے کی حمایت نہیں کرتا ہے۔ براہ کرم پتہ تبدیل کریں یا ایک مختلف فراہم کنندہ کو منتخب کریں۔", "time": "${minutes}m ${seconds}s", "tip": "ٹپ:", "today": "آج", @@ -662,6 +673,7 @@ "totp_code": "TOTP کوڈ", "totp_secret_code": "TOTP خفیہ کوڈ", "totp_verification_success": "توثیق کامیاب!", + "track": " ﮏﯾﺮﭨ", "trade_details_copied": "${title} کو کلپ بورڈ پر کاپی کیا گیا۔", "trade_details_created_at": "پر تخلیق کیا گیا۔", "trade_details_fetching": "لا رہا ہے۔", @@ -712,6 +724,16 @@ "transactions": "لین دین", "transactions_by_date": "تاریخ کے لحاظ سے لین دین", "trusted": "قابل اعتماد", + "tx_commit_exception_no_dust_on_change": "اس رقم سے لین دین کو مسترد کردیا گیا ہے۔ ان سککوں کے ذریعہ آپ بغیر کسی تبدیلی کے ${min} یا ${max} بھیج سکتے ہیں جو لوٹتے ہیں۔", + "tx_commit_failed": "ٹرانزیکشن کمٹ ناکام ہوگیا۔ براہ کرم سپورٹ سے رابطہ کریں۔", + "tx_no_dust_exception": "لین دین کو بہت چھوٹی رقم بھیج کر مسترد کردیا جاتا ہے۔ براہ کرم رقم میں اضافہ کرنے کی کوشش کریں۔", + "tx_not_enough_inputs_exception": "کافی ان پٹ دستیاب نہیں ہے۔ براہ کرم سکے کے کنٹرول میں مزید منتخب کریں", + "tx_rejected_dust_change": "نیٹ ورک کے قواعد ، کم تبدیلی کی رقم (دھول) کے ذریعہ لین دین کو مسترد کردیا گیا۔ سب کو بھیجنے یا رقم کو کم کرنے کی کوشش کریں۔", + "tx_rejected_dust_output": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم رقم میں اضافہ کریں۔", + "tx_rejected_dust_output_send_all": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم سکے کے کنٹرول میں منتخب کردہ سکے کا توازن چیک کریں۔", + "tx_rejected_vout_negative": "اس لین دین کی فیسوں کی ادائیگی کے لئے کافی توازن نہیں ہے۔ براہ کرم سکے کے کنٹرول میں سکے کا توازن چیک کریں۔", + "tx_wrong_balance_exception": "آپ کے پاس یہ رقم بھیجنے کے لئے کافی ${currency} نہیں ہے۔", + "tx_zero_fee_exception": "0 فیس کے ساتھ لین دین نہیں بھیج سکتا۔ شرح کو بڑھانے یا تازہ ترین تخمینے کے ل your اپنے کنکشن کی جانچ پڑتال کرنے کی کوشش کریں۔", "unavailable_balance": "ﺲﻨﻠﯿﺑ ﺏﺎﯿﺘﺳﺩ ﺮﯿﻏ", "unavailable_balance_description": "۔ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﮧﻠﺼﯿﻓ ﺎﮐ ﮯﻧﺮﮐ ﺪﻤﺠﻨﻣ ﻥﺍ ﮟﯿﮩﻧﺍ ﭖﺁ ﮧﮐ ﮏﺗ ﺐﺟ ﮟﯿﮨ ﮯﺘﮨﺭ ﯽﺋﺎﺳﺭ ﻞﺑﺎﻗﺎﻧ ﮏﺗ ﺖﻗﻭ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﻦﯾﺩ ﻦﯿﻟ ﺲﻨﻠﯿﺑ ﺪﻤﺠﻨﻣ ﮧﮐ ﺐﺟ ،ﮯﮔ ﮟﯿﺋﺎﺟ ﻮﮨ ﺏﺎﯿﺘﺳﺩ ﺲﻨﻠﯿﺑ ﻞﻔﻘﻣ ﺪﻌﺑ ﮯﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﻦﯾﺩ ﻦﯿﻟ ﮧﻘﻠﻌﺘﻣ ﮯﮐ ﻥﺍ ۔ﮯﮨ ﺎﮭﮐﺭ ﺮ", "unconfirmed": "غیر تصدیق شدہ بیلنس", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 54df5dbf2..b5f2d69a3 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -43,6 +43,7 @@ "already_have_account": "Ṣé ẹ ti ní àkáǹtì?", "always": "Ní gbogbo àwọn ìgbà", "amount": "Iye: ", + "amount_is_below_minimum_limit": "Iwontunws.funfun rẹ lẹhin awọn idiyele yoo kere ju iye ti o kere ju nilo fun paṣipaarọ (${min}", "amount_is_estimate": "Ìdíyelé ni iye tó ń bọ̀", "amount_is_guaranteed": "ó di dandan pé owó á wọlé", "and": "àti", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "Àwọn àránṣẹ́ Bitcoin nílò ìjẹ́rìísí kan. Ó lè lo ìṣéjú ogun tàbí ìṣéjú jù. A dúpẹ́ fún sùúrù yín! Ẹ máa gba ímeèlì t'ó bá jẹ́rìísí àránṣẹ́ náà.", "Blocks_remaining": "Àkójọpọ̀ ${status} kikù", "bright_theme": "Funfun", + "bump_fee": "Ọya ija", "buy": "Rà", "buy_alert_content": "Lọwọlọwọ a ṣe atilẹyin rira Bitcoin, Ethereum, Litecoin, ati Monero. Jọwọ ṣẹda tabi yipada si Bitcoin, Ethereum, Litecoin, tabi apamọwọ Monero.", "buy_bitcoin": "Ra Bitcoin", @@ -132,6 +134,8 @@ "confirm": "Jẹ́rìísí", "confirm_delete_template": "Ìṣe yìí máa yọ àwòṣe yìí kúrò. Ṣé ẹ fẹ́ tẹ̀síwájú?", "confirm_delete_wallet": "Ìṣe yìí máa yọ àpamọ́wọ́ yìí kúrò. Ṣé ẹ fẹ́ tẹ̀síwájú?", + "confirm_fee_deduction": "Jẹrisi iyọkuro owo", + "confirm_fee_deduction_content": "Ṣe o gba lati yọkuro idiyele naa kuro ni iṣejade?", "confirm_sending": "Jẹ́rìí sí ránṣẹ́", "confirmations": "Àwọn ẹ̀rí", "confirmed": "A ti jẹ́rìí ẹ̀", @@ -171,6 +175,7 @@ "debit_card": "Káàdì ìrajà", "debit_card_terms": "Òfin ti olùṣe àjọrò káàdì ìrajà bójú irú ọ̀nà t'á pamọ́ àti a lo òǹkà ti káàdì ìrajà yín (àti ọ̀rọ̀ ìdánimọ̀ tí káàdì náà) nínú àpamọ́wọ́ yìí.", "decimal_places_error": "Oọ̀rọ̀ ayipada ti o wa ni o dara julọ", + "decimals_cannot_be_zero": "Token eleemel ko le jẹ odo.", "default_buy_provider": "Aiyipada Ra Olupese", "default_sell_provider": "Aiyipada Olupese Tita", "delete": "Pa á", @@ -212,6 +217,7 @@ "edit_token": "Ṣatunkọ àmi", "electrum_address_disclaimer": "A dá àwọn àdírẹ́sì títun ní gbogbo àwọn ìgbà t'ẹ́ lo ó kan ṣùgbọ́n ẹ lè tẹ̀síwájú lo àwọn àdírẹ́sì tẹ́lẹ̀tẹ́lẹ̀.", "email_address": "Àdírẹ́sì ímeèlì", + "enable_replace_by_fee": "Mu ki o rọpo", "enabled": "Wọ́n tíwọn ti tan", "enter_amount": "Tẹ̀ iye", "enter_backup_password": "Tẹ̀ ọ̀rọ̀ aṣínà ti ẹ̀dà ḿbí", @@ -248,6 +254,7 @@ "errorGettingCredentials": "Kuna: Aṣiṣe lakoko gbigba awọn iwe-ẹri", "errorSigningTransaction": "Aṣiṣe kan ti waye lakoko ti o fowo si iṣowo", "estimated": "Ó tó a fojú díwọ̀n", + "estimated_new_fee": "Ifoju tuntun owo tuntun", "etherscan_history": "Etherscan itan", "event": "Iṣẹlẹ", "events": "Awọn iṣẹlẹ", @@ -314,6 +321,7 @@ "in_store": "A níyí", "incoming": "Wọ́n tó ń bọ̀", "incorrect_seed": "Ọ̀rọ̀ tí a tẹ̀ kì í ṣe èyí.", + "inputs": "Igbewọle", "introducing_cake_pay": "Ẹ bá Cake Pay!", "invalid_input": "Iṣawọle ti ko tọ", "invoice_details": "Iru awọn ẹya ọrọ", @@ -381,6 +389,7 @@ "offer_expires_in": "Ìrònúdábàá máa gbẹ́mìí mì ní: ", "offline": "kò wà lórí ayélujára", "ok": "Ó dáa", + "old_fee": "Oya atijọ", "onion_link": "Kọja ilọ alubosa", "online": "Lórí ayélujára", "onramper_option_description": "Ni kiakia Ra Crypto pẹlu ọpọlọpọ awọn ọna isanwo. Wa ni ọpọlọpọ awọn orilẹ-ede. Itankale ati awọn idiyele yatọ.", @@ -397,6 +406,7 @@ "outdated_electrum_wallet_description": "Àwọn àpamọ́wọ́ títun Bitcoin ti a ti dá nínú Cake Wallet lọ́wọ́lọ́wọ́. Àwọn àpamọ́wọ́ títun t'á dá nínú Cake Wallet ni hóró tó ní ọ̀rọ̀ mẹ́rinlélógún. Ẹ gbọ́dọ̀ dá àpamọ́wọ́. Ẹ sì sún gbogbo owó yín sí àpamọ́wọ́ títun náà tó dá lórí ọ̀rọ̀ mẹ́rinlélógún. Ẹ sì gbọ́dọ̀ yé lo àwọn àpamọ́wọ́ tó dá lórí hóró tó ní ọ̀rọ̀ méjìlá. Ẹ jọ̀wọ́ ṣe èyí láìpẹ́ kí ẹ ba owó yín.", "outdated_electrum_wallet_receive_warning": "Ẹ KÒ FI BITCOIN SÍ ÀPAMỌ́WỌ́ YÌÍ t'á ti dá a nínú Cake Wallet àti àpamọ́wọ́ yìí ni hóró ti ọ̀rọ̀ méjìlá. A lè pàdánù BTC t'á ránṣẹ́ sí àpamọ́wọ́ yìí. Ẹ dá àpamọ́wọ́ títun tó ni hóró tó ni ọ̀rọ̀ mẹ́rinlélógún (Ẹ tẹ àkọsílẹ̀ tó wa l’ókè l'ọ́tún nígbàna, ẹ sì yan àwọn àpamọ́wọ́ nígbàna, ẹ sì yan Dá Àpamọ́wọ́ Títun nígbàna, ẹ sì yan Bitcoin) àti sún Bitcoin yín síbẹ̀ ní sinsìn yẹn. Àwọn àpamọ́wọ́ títun (hóró ni ọ̀rọ̀ mẹ́rinlélógún) láti Cake Wallet wa láìléwu.", "outgoing": "Wọ́n tó ń jáde", + "outputs": "Awọn iṣan", "overwrite_amount": "Pààrọ̀ iye owó", "pairingInvalidEvent": "Pipọpọ Iṣẹlẹ Ti ko tọ", "password": "Ọ̀rọ̀ aṣínà", @@ -644,6 +654,7 @@ "template_name": "Orukọ Awoṣe", "third_intro_content": "A sì lè lo Yats níta Cake Wallet. A lè rọ́pò Àdírẹ́sì kankan àpamọ́wọ́ fún Yat!", "third_intro_title": "Àlàáfíà ni Yat àti àwọn ìmíìn jọ wà", + "thorchain_taproot_address_not_supported": "Olupese Trockchain ko ṣe atilẹyin awọn adirẹsi Taproot. Jọwọ yi adirẹsi pada tabi yan olupese ti o yatọ.", "time": "${minutes}ìṣj ${seconds}ìṣs", "tip": "Owó àfikún:", "today": "Lénìí", @@ -661,6 +672,7 @@ "totp_code": "Koodu TOTP", "totp_secret_code": "Koodu iye TOTP", "totp_verification_success": "Ìbẹrẹ dọkita!", + "track": "Orin", "trade_details_copied": "Ti ṣeda ${title} sí àtẹ àkọsílẹ̀", "trade_details_created_at": "Ṣíṣe ní", "trade_details_fetching": "Ń mú wá", @@ -711,6 +723,16 @@ "transactions": "Àwọn àránṣẹ́", "transactions_by_date": "Àwọn àránṣẹ́ t'á ti fi aago ṣa", "trusted": "A ti fọkàn ẹ̀ tán", + "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_no_dust_exception": "Iṣowo naa ni kọ nipa fifiranṣẹ iye ti o kere ju. Jọwọ gbiyanju pọ si iye naa.", + "tx_not_enough_inputs_exception": "Ko to awọn titẹsi to. Jọwọ yan diẹ sii labẹ iṣakoso owo", + "tx_rejected_dust_change": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye iyipada kekere (eruku). Gbiyanju lati firanṣẹ gbogbo rẹ tabi dinku iye.", + "tx_rejected_dust_output": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ mu iye naa pọ si.", + "tx_rejected_dust_output_send_all": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ ṣayẹwo dọgbadọgba ti awọn owo ti a yan labẹ iṣakoso owo.", + "tx_rejected_vout_negative": "Iwontunws.funfun ti o to lati sanwo fun awọn idiyele iṣowo yii. Jọwọ ṣayẹwo iwọntunwọnsi ti awọn owo labẹ iṣakoso owo.", + "tx_wrong_balance_exception": "O ko ni to ${currency} lati firanṣẹ iye yii.", + "tx_zero_fee_exception": "Ko le firanṣẹ idunadura pẹlu ọya 0. Gbiyanju jijẹ oṣuwọn tabi ṣayẹwo asopọ rẹ fun awọn iṣiro tuntun.", "unavailable_balance": "Iwontunwonsi ti ko si", "unavailable_balance_description": "Iwontunws.funfun ti ko si: Lapapọ yii pẹlu awọn owo ti o wa ni titiipa ni awọn iṣowo isunmọ ati awọn ti o ti didi ni itara ninu awọn eto iṣakoso owo rẹ. Awọn iwọntunwọnsi titiipa yoo wa ni kete ti awọn iṣowo oniwun wọn ba ti pari, lakoko ti awọn iwọntunwọnsi tio tutunini ko ni iraye si fun awọn iṣowo titi iwọ o fi pinnu lati mu wọn kuro.", "unconfirmed": "A kò tí ì jẹ́rìí ẹ̀", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index cb1f012fb..e9a6b63f2 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -43,6 +43,7 @@ "already_have_account": "已经有账号了?", "always": "总是", "amount": "金额: ", + "amount_is_below_minimum_limit": "您的余额费用将小于交易所所需的最低金额(${min})", "amount_is_estimate": "收款金额为估算值", "amount_is_guaranteed": "保证收到的金额", "and": "和", @@ -78,6 +79,7 @@ "bitcoin_payments_require_1_confirmation": "比特币支付需要 1 次确认,这可能需要 20 分钟或更长时间。谢谢你的耐心!确认付款后,您将收到电子邮件。", "Blocks_remaining": "${status} 剩余的块", "bright_theme": "明亮", + "bump_fee": "撞费", "buy": "购买", "buy_alert_content": "目前我们仅支持购买比特币、以太坊、莱特币和门罗币。请创建或切换到您的比特币、以太坊、莱特币或门罗币钱包。", "buy_bitcoin": "购买比特币", @@ -132,6 +134,8 @@ "confirm": "确认", "confirm_delete_template": "此操作将刪除此模板。确定吗?", "confirm_delete_wallet": "此操作将刪除此钱包。确定吗?", + "confirm_fee_deduction": "确认费用扣除", + "confirm_fee_deduction_content": "您是否同意从产出中扣除费用?", "confirm_sending": "确认发送", "confirmations": "确认", "confirmed": "确认余额", @@ -171,6 +175,7 @@ "debit_card": "借记卡", "debit_card_terms": "您的支付卡号(以及与您的支付卡号对应的凭证)在此数字钱包中的存储和使用受适用的持卡人与支付卡发卡机构签订的协议的条款和条件的约束,自时不时。", "decimal_places_error": "小数位太多", + "decimals_cannot_be_zero": "代币十进制不能为零。", "default_buy_provider": "默认购买提供商", "default_sell_provider": "默认销售提供商", "delete": "删除", @@ -211,6 +216,7 @@ "edit_token": "编辑令牌", "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效", "email_address": "电子邮件地址", + "enable_replace_by_fee": "启用by-Fee替换", "enabled": "启用", "enter_amount": "输入金额", "enter_backup_password": "在此处输入備用密码", @@ -247,6 +253,7 @@ "errorGettingCredentials": "失败:获取凭据时出错", "errorSigningTransaction": "签署交易时发生错误", "estimated": "估计值", + "estimated_new_fee": "估计新费用", "etherscan_history": "以太扫描历史", "event": "事件", "events": "活动", @@ -313,6 +320,7 @@ "in_store": "店内", "incoming": "收到", "incorrect_seed": "输入的文字无效。", + "inputs": "输入", "introducing_cake_pay": "介绍 Cake Pay!", "invalid_input": "输入无效", "invoice_details": "发票明细", @@ -380,6 +388,7 @@ "offer_expires_in": "优惠有效期至 ", "offline": "离线", "ok": "确认", + "old_fee": "旧费用", "onion_link": "洋葱链接", "online": "在线", "onramper_option_description": "快速使用许多付款方式购买加密货币。在大多数国家 /地区可用。利差和费用各不相同。", @@ -396,6 +405,7 @@ "outdated_electrum_wallet_description": "在Cake创建的新比特币钱包现在有一个24字的种子。你必须创建一个新的比特币钱包,并将你所有的资金转移到新的24字钱包,并停止使用12字种子的钱包。请立即这样做以保证你的资金安全。", "outdated_electrum_wallet_receive_warning": "如果这个钱包有一个 12 字的种子并且是在 Cake 中创建的,不要将比特币存入这个钱包。 任何转移到此钱包的 BTC 都可能丢失。 创建一个新的 24 字钱包(点击右上角的菜单,选择钱包,选择创建新钱包,然后选择比特币)并立即将您的 BTC 移到那里。 Cake 的新(24 字)BTC 钱包是安全的", "outgoing": "发送", + "outputs": "输出", "overwrite_amount": "Overwrite amount", "pairingInvalidEvent": "配对无效事件", "password": "密码", @@ -643,6 +653,7 @@ "template_name": "模板名称", "third_intro_content": "Yats 也住在 Cake Wallet 之外。 地球上任何一個錢包地址都可以用一個Yat來代替!", "third_intro_title": "Yat 和別人玩得很好", + "thorchain_taproot_address_not_supported": "Thorchain提供商不支持Taproot地址。请更改地址或选择其他提供商。", "time": "${minutes}m ${seconds}s", "tip": "提示:", "today": "今天", @@ -660,6 +671,7 @@ "totp_code": "TOTP代码", "totp_secret_code": "TOTP密码", "totp_verification_success": "验证成功!", + "track": "追踪", "trade_details_copied": "${title} 复制到剪贴板", "trade_details_created_at": "创建于", "trade_details_fetching": "正在获取", @@ -710,6 +722,16 @@ "transactions": "交易情况", "transactions_by_date": "按日期交易", "trusted": "值得信赖", + "tx_commit_exception_no_dust_on_change": "交易被此金额拒绝。使用这些硬币,您可以发送${min}无需更改或返回${max}的变化。", + "tx_commit_failed": "交易承诺失败。请联系支持。", + "tx_no_dust_exception": "通过发送太小的金额来拒绝交易。请尝试增加金额。", + "tx_not_enough_inputs_exception": "没有足够的输入。请在硬币控制下选择更多", + "tx_rejected_dust_change": "交易被网络规则拒绝,较低的变化数量(灰尘)。尝试发送全部或减少金额。", + "tx_rejected_dust_output": "交易被网络规则,低输出量(灰尘)拒绝。请增加金额。", + "tx_rejected_dust_output_send_all": "交易被网络规则,低输出量(灰尘)拒绝。请检查在硬币控制下选择的硬币的余额。", + "tx_rejected_vout_negative": "没有足够的余额来支付此交易费用。请检查硬币控制下的硬币余额。", + "tx_wrong_balance_exception": "您没有足够的${currency}来发送此金额。", + "tx_zero_fee_exception": "无法以0费用发送交易。尝试提高速率或检查连接以获取最新估计。", "unavailable_balance": "不可用余额", "unavailable_balance_description": "不可用余额:此总额包括锁定在待处理交易中的资金以及您在硬币控制设置中主动冻结的资金。一旦各自的交易完成,锁定的余额将变得可用,而冻结的余额在您决定解冻之前仍然无法进行交易。", "unconfirmed": "未确认余额", diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index ed2b67de5..8387c1d1f 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.12.0" -MONERO_COM_BUILD_NUMBER=79 +MONERO_COM_VERSION="1.12.1" +MONERO_COM_BUILD_NUMBER=80 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.15.2" -CAKEWALLET_BUILD_NUMBER=200 +CAKEWALLET_VERSION="4.15.3" +CAKEWALLET_BUILD_NUMBER=202 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/append_translation.sh b/scripts/append_translation.sh deleted file mode 100755 index 0cc33fc0f..000000000 --- a/scripts/append_translation.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# to use on Mac first install the translation shell `brew install translate-shell` -# then install the jq `brew install jq` -# then run this file with the English key and value that you want to be translated -# `./append_translation.sh "greetings" "Hello World!"` -# if you get an error `command not found` -# give the correct permissions to this file using `chmod 777 append_translation.sh` - -langs=("ar" "bg" "cs" "de" "en" "es" "fr" "ha" "hi" "hr" "id" "it" "ja" "ko" "my" "nl" "pl" "pt" "ru" "th" "tl" "tr" "uk" "ur" "yo" "zh") - -name=$1 -text=$2 - -for lang in "${langs[@]}"; do - translation="$(trans en:$lang --brief "$text")" - - # Use jq to add the new key-value pair to the JSON object - jq_result=$(jq '. += { "'"$name"'": "'"$translation"'" }' ../res/values/strings_$lang.arb) - - echo "$jq_result" > ../res/values/strings_$lang.arb - echo 'Added { "'"$name"'": "'"$translation"'" } to '"strings_$lang.arb"'' -done diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 53514b39b..7e4f214ab 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.12.0" +MONERO_COM_VERSION="1.12.1" MONERO_COM_BUILD_NUMBER=77 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.15.2" -CAKEWALLET_BUILD_NUMBER=219 +CAKEWALLET_VERSION="4.15.3" +CAKEWALLET_BUILD_NUMBER=221 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 1654a022a..1242945a6 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,13 +16,13 @@ if [ -n "$1" ]; then fi MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.2.0" -MONERO_COM_BUILD_NUMBER=10 +MONERO_COM_VERSION="1.2.1" +MONERO_COM_BUILD_NUMBER=11 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.8.2" -CAKEWALLET_BUILD_NUMBER=59 +CAKEWALLET_VERSION="1.8.3" +CAKEWALLET_BUILD_NUMBER=61 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/tool/configure.dart b/tool/configure.dart index 99d1fe8ee..06243e8ab 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -61,6 +61,7 @@ Future main(List args) async { Future generateBitcoin(bool hasImplementation) async { final outputFile = File(bitcoinOutputPath); const bitcoinCommonHeaders = """ +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/wallet_credentials.dart'; @@ -69,10 +70,12 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/output_info.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:hive/hive.dart'; import 'package:bitcoin_base/bitcoin_base.dart';"""; const bitcoinCWHeaders = """ +import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/bitcoin_receive_page_option.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; @@ -84,6 +87,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; +import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; @@ -132,13 +136,14 @@ abstract class Bitcoin { String formatterBitcoinAmountToString({required int amount}); double formatterBitcoinAmountToDouble({required int amount}); int formatterStringDoubleToBitcoinAmount(String amount); - String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate); + String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate, {int? customRate}); List getUnspents(Object wallet); Future updateUnspents(Object wallet); WalletService createBitcoinWalletService(Box walletInfoSource, Box unspentCoinSource); WalletService createLitecoinWalletService(Box walletInfoSource, Box unspentCoinSource); TransactionPriority getBitcoinTransactionPriorityMedium(); + TransactionPriority getBitcoinTransactionPriorityCustom(); TransactionPriority getLitecoinTransactionPriorityMedium(); TransactionPriority getBitcoinTransactionPrioritySlow(); TransactionPriority getLitecoinTransactionPrioritySlow(); @@ -147,6 +152,13 @@ abstract class Bitcoin { ReceivePageOption getSelectedAddressType(Object wallet); List getBitcoinReceivePageOptions(); BitcoinAddressType getBitcoinAddressType(ReceivePageOption option); + bool hasTaprootInput(PendingTransaction pendingTransaction); + + Future replaceByFee(Object wallet, String transactionHash, String fee); + Future canReplaceByFee(Object wallet, String transactionHash); + Future isChangeSufficientForFee(Object wallet, String txId, String newFee); + int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size}); + int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size}); } """; @@ -947,7 +959,11 @@ abstract class Solana { required CryptoCurrency currency, }); List getSPLTokenCurrencies(WalletBase wallet); - Future addSPLToken(WalletBase wallet, CryptoCurrency token); + Future addSPLToken( + WalletBase wallet, + CryptoCurrency token, + String contractAddress, + ); Future deleteSPLToken(WalletBase wallet, CryptoCurrency token); Future getSPLToken(WalletBase wallet, String contractAddress); @@ -955,6 +971,7 @@ abstract class Solana { double getTransactionAmountRaw(TransactionInfo transactionInfo); String getTokenAddress(CryptoCurrency asset); List? getValidationLength(CryptoCurrency type); + double? getEstimateFees(WalletBase wallet); } """; diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 430de03b6..5d5e61cec 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -37,11 +37,13 @@ class SecretKey { SecretKey('exchangeHelperApiKey', () => ''), SecretKey('walletConnectProjectId', () => ''), SecretKey('moralisApiKey', () => ''), + SecretKey('ankrApiKey', () => ''), ]; static final evmChainsSecrets = [ SecretKey('etherScanApiKey', () => ''), SecretKey('polygonScanApiKey', () => ''), + SecretKey('moralisApiKey', () => ''), ]; static final solanaSecrets = [