Cw 537 integrate thor chain swaps (#1280)

* thorChain btc to eth swap * eth to btc swap * update the UI * update localization * Update thorchain_exchange.provider.dart * minor fixes * minor fix * fix min amount bug * revert amount_converter changes * fetching thorChain traid info * resolve evm related merge conflicts * minor fix * Fix eth transaction hash for Thorchain Integration * add new status endpoint and refund address for eth * Adjust affiliate fee * Fix conflicts with main * review comments + transaction filter item * taproot addresses check * added 10 outputs check * Update thorchain_exchange.provider.dart * minor fixes * update thorchain title * fix fetching rate for thorchain * Revert "fix fetching rate for thorchain" This reverts commit 3aa1386ecfbca14271bf01a73b424de19c4fd484. * fix thorchain exchange rate --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>

Serhii committed Mar 28, 2024 at 14:41 UTC cdf081edfdab1b4e05e3a8e83e709e624784cafc
55 files changed +534 -102
assets/images/thorchain.png
Binary files /dev/null and b/assets/images/thorchain.png differ
cw_bitcoin/lib/electrum_wallet.dart
+30 -9
@@ -195,7 +195,8 @@ abstract class ElectrumWalletBase
195 List<BitcoinOutput> outputs,
196 int? feeRate,
197 BitcoinTransactionPriority? priority,
198 - {int? inputsCount}) async {
198 + {int? inputsCount,
199 + String? memo}) async {
200 final utxos = <UtxoWithAddress>[];
201 List<ECPrivate> privateKeys = [];
202
@@ -253,7 +254,11 @@ abstract class ElectrumWalletBase
254 }
255
256 final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
256 - utxos: utxos, outputs: outputs, network: network);
257 + utxos: utxos,
258 + outputs: outputs,
259 + network: network,
260 + memo: memo,
261 + );
262
263 int fee = feeRate != null
264 ? feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize)
@@ -300,7 +305,13 @@ abstract class ElectrumWalletBase
305 }
306 }
307
303 - return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount);
308 + return EstimatedTxResult(
309 + utxos: utxos,
310 + privateKeys: privateKeys,
311 + fee: fee,
312 + amount: amount,
313 + memo: memo,
314 + );
315 }
316
317 @override
@@ -348,13 +359,17 @@ abstract class ElectrumWalletBase
359 outputs,
360 transactionCredentials.feeRate,
361 transactionCredentials.priority,
362 + memo: transactionCredentials.outputs.first.memo,
363 );
364
365 final txb = BitcoinTransactionBuilder(
354 - utxos: estimatedTx.utxos,
355 - outputs: outputs,
356 - fee: BigInt.from(estimatedTx.fee),
357 - network: network);
366 + utxos: estimatedTx.utxos,
367 + outputs: outputs,
368 + fee: BigInt.from(estimatedTx.fee),
369 + network: network,
370 + memo: estimatedTx.memo,
371 + outputOrdering: BitcoinOrdering.none,
372 + );
373
374 final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
375 final key = estimatedTx.privateKeys
@@ -888,13 +903,19 @@ class EstimateTxParams {
903 }
904
905 class EstimatedTxResult {
891 - EstimatedTxResult(
892 - {required this.utxos, required this.privateKeys, required this.fee, required this.amount});
906 + EstimatedTxResult({
907 + required this.utxos,
908 + required this.privateKeys,
909 + required this.fee,
910 + required this.amount,
911 + this.memo,
912 + });
913
914 final List<UtxoWithAddress> utxos;
915 final List<ECPrivate> privateKeys;
916 final int fee;
917 final int amount;
918 + final String? memo;
919 }
920
921 BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
cw_bitcoin/lib/pending_bitcoin_transaction.dart
+3
@@ -31,6 +31,9 @@ class PendingBitcoinTransaction with PendingTransaction {
31 @override
32 String get feeFormatted => bitcoinAmountToString(amount: fee);
33
34 + @override
35 + int? get outputCount => _tx.outputs.length;
36 +
37 final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
38
39 @override
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+4 -1
@@ -140,6 +140,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
140
141 var allInputsAmount = 0;
142
143 + final String? opReturnMemo = outputs.first.memo;
144 +
145 if (unspentCoins.isEmpty) await updateUnspent();
146
147 for (final utx in unspentCoins) {
@@ -282,6 +284,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
284 txb.addOutput(changeAddress, changeValue);
285 }
286
287 + if (opReturnMemo != null) txb.addOutputData(opReturnMemo);
288 +
289 for (var i = 0; i < inputs.length; i++) {
290 final input = inputs[i];
291 final keyPair = generateKeyPair(
@@ -290,7 +294,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
294 txb.sign(i, keyPair, input.value);
295 }
296
293 - // Build the transaction
297 final tx = txb.build();
298
299 return PendingBitcoinCashTransaction(tx, type,
cw_core/lib/output_info.dart
+3 -1
@@ -7,7 +7,8 @@ class OutputInfo {
7 this.formattedCryptoAmount,
8 this.fiatAmount,
9 this.note,
10 - this.extractedAddress,});
10 + this.extractedAddress,
11 + this.memo});
12
13 final String? fiatAmount;
14 final String? cryptoAmount;
@@ -17,4 +18,5 @@ class OutputInfo {
18 final bool sendAll;
19 final bool isParsedAddress;
20 final int? formattedCryptoAmount;
21 + final String? memo;
22 }
\ No newline at end of file
cw_core/lib/pending_transaction.dart
+1
@@ -3,6 +3,7 @@ mixin PendingTransaction {
3 String get amountFormatted;
4 String get feeFormatted;
5 String get hex;
6 + int? get outputCount => null;
7
8 Future<void> commit();
9 }
\ No newline at end of file
cw_evm/lib/evm_chain_client.dart
+9
@@ -14,6 +14,7 @@ import 'package:flutter/services.dart';
14 import 'package:http/http.dart';
15 import 'package:erc20/erc20.dart';
16 import 'package:web3dart/web3dart.dart';
17 +import 'package:hex/hex.dart' as hex;
18
19 abstract class EVMChainClient {
20 final httpClient = Client();
@@ -85,6 +86,7 @@ abstract class EVMChainClient {
86 required CryptoCurrency currency,
87 required int exponent,
88 String? contractAddress,
89 + String? data,
90 }) async {
91 assert(currency == CryptoCurrency.eth ||
92 currency == CryptoCurrency.maticpoly ||
@@ -100,6 +102,7 @@ abstract class EVMChainClient {
102 to: EthereumAddress.fromHex(toAddress),
103 maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
104 amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
105 + data: data != null ? hexToBytes(data) : null,
106 );
107
108 final signedTransaction =
@@ -140,12 +143,14 @@ abstract class EVMChainClient {
143 required EthereumAddress to,
144 required EtherAmount amount,
145 EtherAmount? maxPriorityFeePerGas,
146 + Uint8List? data,
147 }) {
148 return Transaction(
149 from: from,
150 to: to,
151 maxPriorityFeePerGas: maxPriorityFeePerGas,
152 value: amount,
153 + data: data,
154 );
155 }
156
@@ -222,6 +227,10 @@ abstract class EVMChainClient {
227 }
228 }
229
230 + Uint8List hexToBytes(String hexString) {
231 + return Uint8List.fromList(hex.HEX.decode(hexString.startsWith('0x') ? hexString.substring(2) : hexString));
232 + }
233 +
234 void stop() {
235 _client?.dispose();
236 }
cw_evm/lib/evm_chain_wallet.dart
+8
@@ -224,6 +224,13 @@ abstract class EVMChainWalletBase
224 final outputs = _credentials.outputs;
225 final hasMultiDestination = outputs.length > 1;
226
227 + final String? opReturnMemo = outputs.first.memo;
228 +
229 + String? hexOpReturnMemo;
230 + if (opReturnMemo != null) {
231 + hexOpReturnMemo = '0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}';
232 + }
233 +
234 final CryptoCurrency transactionCurrency =
235 balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
236
@@ -279,6 +286,7 @@ abstract class EVMChainWalletBase
286 exponent: exponent,
287 contractAddress:
288 transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
289 + data: hexOpReturnMemo,
290 );
291
292 return pendingEVMChainTransaction;
cw_evm/lib/pending_evm_chain_transaction.dart
+9 -1
@@ -3,6 +3,7 @@ import 'dart:typed_data';
3
4 import 'package:cw_core/pending_transaction.dart';
5 import 'package:web3dart/crypto.dart';
6 +import 'package:hex/hex.dart' as Hex;
7
8 class PendingEVMChainTransaction with PendingTransaction {
9 final Function sendTransaction;
@@ -38,5 +39,12 @@ class PendingEVMChainTransaction with PendingTransaction {
39 String get hex => bytesToHex(signedTransaction, include0x: true);
40
41 @override
41 - String get id => '';
42 + String get id {
43 + final String eip1559Hex = '0x02${hex.substring(2)}';
44 + final Uint8List bytes = Uint8List.fromList(Hex.HEX.decode(eip1559Hex.substring(2)));
45 +
46 + var txid = keccak256(bytes);
47 +
48 + return '0x${Hex.HEX.encode(txid)}';
49 + }
50 }
cw_polygon/lib/polygon_client.dart
+2
@@ -13,6 +13,8 @@ class PolygonClient extends EVMChainClient {
13 required EthereumAddress to,
14 required EtherAmount amount,
15 EtherAmount? maxPriorityFeePerGas,
16 + Uint8List? data,
17 +
18 }) {
19 return Transaction(
20 from: from,
lib/bitcoin/cw_bitcoin.dart
+2 -1
@@ -85,7 +85,8 @@ class CWBitcoin extends Bitcoin {
85 sendAll: out.sendAll,
86 extractedAddress: out.extractedAddress,
87 isParsedAddress: out.isParsedAddress,
88 - formattedCryptoAmount: out.formattedCryptoAmount))
88 + formattedCryptoAmount: out.formattedCryptoAmount,
89 + memo: out.memo))
90 .toList(),
91 priority: priority as BitcoinTransactionPriority,
92 feeRate: feeRate);
lib/ethereum/cw_ethereum.dart
+2 -1
@@ -76,7 +76,8 @@ class CWEthereum extends Ethereum {
76 sendAll: out.sendAll,
77 extractedAddress: out.extractedAddress,
78 isParsedAddress: out.isParsedAddress,
79 - formattedCryptoAmount: out.formattedCryptoAmount))
79 + formattedCryptoAmount: out.formattedCryptoAmount,
80 + memo: out.memo))
81 .toList(),
82 priority: priority as EVMChainTransactionPriority,
83 currency: currency,
lib/exchange/exchange_provider_description.dart
+4
@@ -22,6 +22,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
22 ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
23 static const exolix =
24 ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
25 + static const thorChain =
26 + ExchangeProviderDescription(title: 'ThorChain' , raw: 8, image: 'assets/images/thorchain.png');
27
28 static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
29
@@ -41,6 +43,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
43 return trocador;
44 case 6:
45 return exolix;
46 + case 8:
47 + return thorChain;
48 case 7:
49 return all;
50 default:
lib/exchange/provider/thorchain_exchange.provider.dart new
+248
@@ -0,0 +1,248 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cake_wallet/exchange/exchange_provider_description.dart';
4 +import 'package:cake_wallet/exchange/limits.dart';
5 +import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
6 +import 'package:cake_wallet/exchange/trade.dart';
7 +import 'package:cake_wallet/exchange/trade_request.dart';
8 +import 'package:cake_wallet/exchange/trade_state.dart';
9 +import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
10 +import 'package:cw_core/crypto_currency.dart';
11 +import 'package:hive/hive.dart';
12 +import 'package:http/http.dart' as http;
13 +
14 +class ThorChainExchangeProvider extends ExchangeProvider {
15 + ThorChainExchangeProvider({required this.tradesStore})
16 + : super(pairList: supportedPairs(_notSupported));
17 +
18 + static final List<CryptoCurrency> _notSupported = [
19 + ...(CryptoCurrency.all
20 + .where((element) => ![
21 + CryptoCurrency.btc,
22 + CryptoCurrency.eth,
23 + CryptoCurrency.ltc,
24 + CryptoCurrency.bch,
25 + CryptoCurrency.aave,
26 + CryptoCurrency.dai,
27 + CryptoCurrency.gusd,
28 + CryptoCurrency.usdc,
29 + CryptoCurrency.usdterc20,
30 + CryptoCurrency.wbtc,
31 + ].contains(element))
32 + .toList())
33 + ];
34 +
35 + static final isRefundAddressSupported = [CryptoCurrency.eth];
36 +
37 + static const _baseURL = 'thornode.ninerealms.com';
38 + static const _quotePath = '/thorchain/quote/swap';
39 + static const _txInfoPath = '/thorchain/tx/status/';
40 + static const _affiliateName = 'cakewallet';
41 + static const _affiliateBps = '175';
42 +
43 + final Box<Trade> tradesStore;
44 +
45 + @override
46 + String get title => 'THORChain';
47 +
48 + @override
49 + bool get isAvailable => true;
50 +
51 + @override
52 + bool get isEnabled => true;
53 +
54 + @override
55 + bool get supportsFixedRate => false;
56 +
57 + @override
58 + ExchangeProviderDescription get description => ExchangeProviderDescription.thorChain;
59 +
60 + @override
61 + Future<bool> checkIsAvailable() async => true;
62 +
63 + @override
64 + Future<double> fetchRate(
65 + {required CryptoCurrency from,
66 + required CryptoCurrency to,
67 + required double amount,
68 + required bool isFixedRateMode,
69 + required bool isReceiveAmount}) async {
70 + try {
71 + if (amount == 0) return 0.0;
72 +
73 + final params = {
74 + 'from_asset': _normalizeCurrency(from),
75 + 'to_asset': _normalizeCurrency(to),
76 + 'amount': _doubleToThorChainString(amount),
77 + 'affiliate': _affiliateName,
78 + 'affiliate_bps': _affiliateBps
79 + };
80 +
81 + final responseJSON = await _getSwapQuote(params);
82 +
83 + final expectedAmountOut = responseJSON['expected_amount_out'] as String? ?? '0.0';
84 +
85 + return _thorChainAmountToDouble(expectedAmountOut) / amount;
86 + } catch (e) {
87 + print(e.toString());
88 + return 0.0;
89 + }
90 + }
91 +
92 + @override
93 + Future<Limits> fetchLimits(
94 + {required CryptoCurrency from,
95 + required CryptoCurrency to,
96 + required bool isFixedRateMode}) async {
97 + final params = {
98 + 'from_asset': _normalizeCurrency(from),
99 + 'to_asset': _normalizeCurrency(to),
100 + 'amount': _doubleToThorChainString(1),
101 + 'affiliate': _affiliateName,
102 + 'affiliate_bps': _affiliateBps
103 + };
104 +
105 + final responseJSON = await _getSwapQuote(params);
106 + final minAmountIn = responseJSON['recommended_min_amount_in'] as String? ?? '0.0';
107 +
108 + return Limits(min: _thorChainAmountToDouble(minAmountIn));
109 + }
110 +
111 + @override
112 + Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
113 + String formattedToAddress = request.toAddress.startsWith('bitcoincash:')
114 + ? request.toAddress.replaceFirst('bitcoincash:', '')
115 + : request.toAddress;
116 +
117 + final formattedFromAmount = double.parse(request.fromAmount);
118 +
119 + final params = {
120 + 'from_asset': _normalizeCurrency(request.fromCurrency),
121 + 'to_asset': _normalizeCurrency(request.toCurrency),
122 + 'amount': _doubleToThorChainString(formattedFromAmount),
123 + 'destination': formattedToAddress,
124 + 'affiliate': _affiliateName,
125 + 'affiliate_bps': _affiliateBps,
126 + 'refund_address':
127 + isRefundAddressSupported.contains(request.fromCurrency) ? request.refundAddress : '',
128 + };
129 +
130 + final responseJSON = await _getSwapQuote(params);
131 +
132 + final inputAddress = responseJSON['inbound_address'] as String?;
133 + final memo = responseJSON['memo'] as String?;
134 +
135 + return Trade(
136 + id: '',
137 + from: request.fromCurrency,
138 + to: request.toCurrency,
139 + provider: description,
140 + inputAddress: inputAddress,
141 + createdAt: DateTime.now(),
142 + amount: request.fromAmount,
143 + state: TradeState.notFound,
144 + payoutAddress: request.toAddress,
145 + memo: memo);
146 + }
147 +
148 + @override
149 + Future<Trade> findTradeById({required String id}) async {
150 + if (id.isEmpty) throw Exception('Trade id is empty');
151 + final formattedId = id.startsWith('0x') ? id.substring(2) : id;
152 + final uri = Uri.https(_baseURL, '$_txInfoPath$formattedId');
153 + final response = await http.get(uri);
154 +
155 + if (response.statusCode == 404) {
156 + throw Exception('Trade not found for id: $formattedId');
157 + } else if (response.statusCode != 200) {
158 + throw Exception('Unexpected HTTP status: ${response.statusCode}');
159 + }
160 +
161 + final responseJSON = json.decode(response.body);
162 + final Map<String, dynamic> stagesJson = responseJSON['stages'] as Map<String, dynamic>;
163 +
164 + final inboundObservedStarted = stagesJson['inbound_observed']?['started'] as bool? ?? true;
165 + if (!inboundObservedStarted) {
166 + throw Exception('Trade has not started for id: $formattedId');
167 + }
168 +
169 + final currentState = _updateStateBasedOnStages(stagesJson) ?? TradeState.notFound;
170 +
171 + final tx = responseJSON['tx'];
172 + final String fromAddress = tx['from_address'] as String? ?? '';
173 + final String toAddress = tx['to_address'] as String? ?? '';
174 + final List<dynamic> coins = tx['coins'] as List<dynamic>;
175 + final String? memo = tx['memo'] as String?;
176 +
177 + final parts = memo?.split(':') ?? [];
178 +
179 + final String toChain = parts.length > 1 ? parts[1].split('.')[0] : '';
180 + final String toAsset = parts.length > 1 && parts[1].split('.').length > 1 ? parts[1].split('.')[1].split('-')[0] : '';
181 +
182 + final formattedToChain = CryptoCurrency.fromString(toChain);
183 + final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency:formattedToChain);
184 +
185 + final plannedOutTxs = responseJSON['planned_out_txs'] as List<dynamic>?;
186 + final isRefund = plannedOutTxs?.any((tx) => tx['refund'] == true) ?? false;
187 +
188 + return Trade(
189 + id: id,
190 + from: CryptoCurrency.fromString(tx['chain'] as String? ?? ''),
191 + to: toAssetWithChain,
192 + provider: description,
193 + inputAddress: fromAddress,
194 + payoutAddress: toAddress,
195 + amount: coins.first['amount'] as String? ?? '0.0',
196 + state: currentState,
197 + memo: memo,
198 + isRefund: isRefund,
199 + );
200 + }
201 +
202 + Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
203 + Uri uri = Uri.https(_baseURL, _quotePath, params);
204 +
205 + final response = await http.get(uri);
206 +
207 + if (response.statusCode != 200) {
208 + throw Exception('Unexpected HTTP status: ${response.statusCode}');
209 + }
210 +
211 + if (response.body.contains('error')) {
212 + throw Exception('Unexpected response: ${response.body}');
213 + }
214 +
215 + return json.decode(response.body) as Map<String, dynamic>;
216 + }
217 +
218 + String _normalizeCurrency(CryptoCurrency currency) {
219 + final networkTitle = currency.tag == 'ETH' ? 'ETH' : currency.title;
220 + return '$networkTitle.${currency.title}';
221 + }
222 +
223 + String _doubleToThorChainString(double amount) => (amount * 1e8).toInt().toString();
224 +
225 + double _thorChainAmountToDouble(String amount) => double.parse(amount) / 1e8;
226 +
227 + TradeState? _updateStateBasedOnStages(Map<String, dynamic> stages) {
228 + TradeState? currentState;
229 +
230 + if (stages['inbound_observed']['completed'] as bool? ?? false) {
231 + currentState = TradeState.confirmation;
232 + }
233 + if (stages['inbound_confirmation_counted']['completed'] as bool? ?? false) {
234 + currentState = TradeState.confirmed;
235 + }
236 + if (stages['inbound_finalised']['completed'] as bool? ?? false) {
237 + currentState = TradeState.processing;
238 + }
239 + if (stages['swap_finalised']['completed'] as bool? ?? false) {
240 + currentState = TradeState.traded;
241 + }
242 + if (stages['outbound_signed']['completed'] as bool? ?? false) {
243 + currentState = TradeState.success;
244 + }
245 +
246 + return currentState;
247 + }
248 +}
lib/exchange/trade.dart
+21 -3
@@ -27,7 +27,10 @@ class Trade extends HiveObject {
27 this.password,
28 this.providerId,
29 this.providerName,
30 - this.fromWalletAddress
30 + this.fromWalletAddress,
31 + this.memo,
32 + this.txId,
33 + this.isRefund,
34 }) {
35 if (provider != null) providerRaw = provider.raw;
36
@@ -105,6 +108,15 @@ class Trade extends HiveObject {
108 @HiveField(17)
109 String? fromWalletAddress;
110
111 + @HiveField(18)
112 + String? memo;
113 +
114 + @HiveField(19)
115 + String? txId;
116 +
117 + @HiveField(20)
118 + bool? isRefund;
119 +
120 static Trade fromMap(Map<String, Object?> map) {
121 return Trade(
122 id: map['id'] as String,
@@ -115,7 +127,10 @@ class Trade extends HiveObject {
127 map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null,
128 amount: map['amount'] as String,
129 walletId: map['wallet_id'] as String,
118 - fromWalletAddress: map['from_wallet_address'] as String?
130 + fromWalletAddress: map['from_wallet_address'] as String?,
131 + memo: map['memo'] as String?,
132 + txId: map['tx_id'] as String?,
133 + isRefund: map['isRefund'] as bool?
134 );
135 }
136
@@ -128,7 +143,10 @@ class Trade extends HiveObject {
143 'date': createdAt != null ? createdAt!.millisecondsSinceEpoch : null,
144 'amount': amount,
145 'wallet_id': walletId,
131 - 'from_wallet_address': fromWalletAddress
146 + 'from_wallet_address': fromWalletAddress,
147 + 'memo': memo,
148 + 'tx_id': txId,
149 + 'isRefund': isRefund
150 };
151 }
152
lib/exchange/trade_state.dart
+3
@@ -41,6 +41,8 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
41 static const success = TradeState(raw: 'success', title: 'Success');
42 static TradeState deserialize({required String raw}) {
43 switch (raw) {
44 + case 'NOT_FOUND':
45 + return notFound;
46 case 'pending':
47 return pending;
48 case 'confirming':
@@ -98,6 +100,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
100 case 'sending':
101 return sending;
102 case 'success':
103 + case 'done':
104 return success;
105 default:
106 throw Exception('Unexpected token: $raw in TradeState deserialize');
lib/src/screens/dashboard/widgets/filter_tile.dart
+1 -1
@@ -9,7 +9,7 @@ class FilterTile extends StatelessWidget {
9 Widget build(BuildContext context) {
10 return Container(
11 width: double.infinity,
12 - padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
12 + padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 24.0),
13 child: child,
14 );
15 }
lib/src/screens/dashboard/widgets/sync_indicator_icon.dart
+2
@@ -20,6 +20,7 @@ class SyncIndicatorIcon extends StatelessWidget {
20 static const String created = 'created';
21 static const String fetching = 'fetching';
22 static const String finished = 'finished';
23 + static const String success = 'success';
24
25 @override
26 Widget build(BuildContext context) {
@@ -45,6 +46,7 @@ class SyncIndicatorIcon extends StatelessWidget {
46 indicatorColor = Colors.red;
47 break;
48 case finished:
49 + case success:
50 indicatorColor = PaletteDark.brightGreen;
51 break;
52 default:
lib/src/screens/dashboard/widgets/trade_row.dart
+3 -35
@@ -34,7 +34,9 @@ class TradeRow extends StatelessWidget {
34 mainAxisSize: MainAxisSize.max,
35 crossAxisAlignment: CrossAxisAlignment.center,
36 children: [
37 - _getPoweredImage(provider)!,
37 + ClipRRect(
38 + borderRadius: BorderRadius.circular(50),
39 + child: Image.asset(provider.image, width: 36, height: 36)),
40 SizedBox(width: 12),
41 Expanded(
42 child: Column(
@@ -69,38 +71,4 @@ class TradeRow extends StatelessWidget {
71 ),
72 ));
73 }
72 -
73 - Widget? _getPoweredImage(ExchangeProviderDescription provider) {
74 - Widget? image;
75 -
76 - switch (provider) {
77 - case ExchangeProviderDescription.xmrto:
78 - image = Image.asset('assets/images/xmrto.png', height: 36, width: 36);
79 - break;
80 - case ExchangeProviderDescription.changeNow:
81 - image = Image.asset('assets/images/changenow.png', height: 36, width: 36);
82 - break;
83 - case ExchangeProviderDescription.morphToken:
84 - image = Image.asset('assets/images/morph.png', height: 36, width: 36);
85 - break;
86 - case ExchangeProviderDescription.sideShift:
87 - image = Image.asset('assets/images/sideshift.png', width: 36, height: 36);
88 - break;
89 - case ExchangeProviderDescription.simpleSwap:
90 - image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36);
91 - break;
92 - case ExchangeProviderDescription.trocador:
93 - image = ClipRRect(
94 - borderRadius: BorderRadius.circular(50),
95 - child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
96 - break;
97 - case ExchangeProviderDescription.exolix:
98 - image = Image.asset('assets/images/exolix.png', width: 36, height: 36);
99 - break;
100 - default:
101 - image = null;
102 - }
103 -
104 - return image;
105 - }
74 }
lib/src/screens/exchange/exchange_page.dart
+13 -2
@@ -1,3 +1,5 @@
1 +import 'package:cake_wallet/exchange/exchange_provider_description.dart';
2 +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
3 import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
4 import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
5 import 'package:cake_wallet/core/auth_service.dart';
@@ -60,7 +62,7 @@ class ExchangePage extends BasePage {
62 final _receiveAmountFocus = FocusNode();
63 final _receiveAddressFocus = FocusNode();
64 final _receiveAmountDebounce = Debounce(Duration(milliseconds: 500));
63 - final _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
65 + Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
66 var _isReactionsSet = false;
67
68 final arrowBottomPurple = Image.asset(
@@ -431,7 +433,9 @@ class ExchangePage extends BasePage {
433 }
434 if (state is TradeIsCreatedSuccessfully) {
435 exchangeViewModel.reset();
434 - Navigator.of(context).pushNamed(Routes.exchangeConfirm);
436 + (exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.thorChain)
437 + ? Navigator.of(context).pushReplacementNamed(Routes.exchangeTrade)
438 + : Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm);
439 }
440 });
441
@@ -470,6 +474,13 @@ class ExchangePage extends BasePage {
474 if (depositAmountController.text != exchangeViewModel.depositAmount &&
475 depositAmountController.text != S.of(context).all) {
476 exchangeViewModel.isSendAllEnabled = false;
477 + final isThorChain = exchangeViewModel.selectedProviders
478 + .any((provider) => provider is ThorChainExchangeProvider);
479 +
480 + _depositAmountDebounce = isThorChain
481 + ? Debounce(Duration(milliseconds: 1000))
482 + : Debounce(Duration(milliseconds: 500));
483 +
484 _depositAmountDebounce.run(() {
485 exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
486 exchangeViewModel.isReceiveAmountEntered = false;
lib/store/dashboard/trade_filter_store.dart
+26 -13
@@ -3,18 +3,20 @@ import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
3 import 'package:cw_core/wallet_base.dart';
4 import 'package:mobx/mobx.dart';
5
6 -part'trade_filter_store.g.dart';
6 +part 'trade_filter_store.g.dart';
7
8 class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore;
9
10 abstract class TradeFilterStoreBase with Store {
11 - TradeFilterStoreBase() : displayXMRTO = true,
11 + TradeFilterStoreBase()
12 + : displayXMRTO = true,
13 displayChangeNow = true,
14 displaySideShift = true,
15 displayMorphToken = true,
16 displaySimpleSwap = true,
17 displayTrocador = true,
17 - displayExolix = true;
18 + displayExolix = true,
19 + displayThorChain = true;
20
21 @observable
22 bool displayXMRTO;
@@ -37,8 +39,17 @@ abstract class TradeFilterStoreBase with Store {
39 @observable
40 bool displayExolix;
41
42 + @observable
43 + bool displayThorChain;
44 +
45 @computed
41 - bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix;
46 + bool get displayAllTrades =>
47 + displayChangeNow &&
48 + displaySideShift &&
49 + displaySimpleSwap &&
50 + displayTrocador &&
51 + displayExolix &&
52 + displayThorChain;
53
54 @action
55 void toggleDisplayExchange(ExchangeProviderDescription provider) {
@@ -64,6 +75,9 @@ abstract class TradeFilterStoreBase with Store {
75 case ExchangeProviderDescription.exolix:
76 displayExolix = !displayExolix;
77 break;
78 + case ExchangeProviderDescription.thorChain:
79 + displayThorChain = !displayThorChain;
80 + break;
81 case ExchangeProviderDescription.all:
82 if (displayAllTrades) {
83 displayChangeNow = false;
@@ -73,6 +87,7 @@ abstract class TradeFilterStoreBase with Store {
87 displaySimpleSwap = false;
88 displayTrocador = false;
89 displayExolix = false;
90 + displayThorChain = false;
91 } else {
92 displayChangeNow = true;
93 displaySideShift = true;
@@ -81,6 +96,7 @@ abstract class TradeFilterStoreBase with Store {
96 displaySimpleSwap = true;
97 displayTrocador = true;
98 displayExolix = true;
99 + displayThorChain = true;
100 }
101 break;
102 }
@@ -96,16 +112,13 @@ abstract class TradeFilterStoreBase with Store {
112 ? _trades
113 .where((item) =>
114 (displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) ||
99 - (displaySideShift &&
100 - item.trade.provider == ExchangeProviderDescription.sideShift) ||
101 - (displayChangeNow &&
102 - item.trade.provider == ExchangeProviderDescription.changeNow) ||
103 - (displayMorphToken &&
104 - item.trade.provider == ExchangeProviderDescription.morphToken) ||
105 - (displaySimpleSwap &&
106 - item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
115 + (displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) ||
116 + (displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) ||
117 + (displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) ||
118 + (displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
119 (displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
108 - (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix))
120 + (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
121 + (displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain))
122 .toList()
123 : _trades;
124 }
lib/view_model/anonpay_details_view_model.dart
+1 -1
@@ -71,7 +71,7 @@ abstract class AnonpayDetailsViewModelBase with Store {
71 ]);
72
73 items.add(TrackTradeListItem(
74 - title: 'Track',
74 + title: S.current.track,
75 value: invoiceDetail.clearnetStatusUrl,
76 onTap: () => launchUrlString(invoiceDetail.clearnetStatusUrl)));
77 }
lib/view_model/dashboard/dashboard_view_model.dart
+5
@@ -120,6 +120,11 @@ abstract class DashboardViewModelBase with Store {
120 caption: ExchangeProviderDescription.exolix.title,
121 onChanged: () =>
122 tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
123 + FilterItem(
124 + value: () => tradeFilterStore.displayThorChain,
125 + caption: ExchangeProviderDescription.thorChain.title,
126 + onChanged: () =>
127 + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
128 ]
129 },
130 subname = '',
lib/view_model/exchange/exchange_trade_view_model.dart
+12 -1
@@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
6 import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
7 import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
8 import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
9 +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
10 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
11 import 'package:cake_wallet/exchange/trade.dart';
12 import 'package:cake_wallet/generated/i18n.dart';
@@ -47,6 +48,9 @@ abstract class ExchangeTradeViewModelBase with Store {
48 case ExchangeProviderDescription.exolix:
49 _provider = ExolixExchangeProvider();
50 break;
51 + case ExchangeProviderDescription.thorChain:
52 + _provider = ThorChainExchangeProvider(tradesStore: trades);
53 + break;
54 }
55
56 _updateItems();
@@ -100,8 +104,13 @@ abstract class ExchangeTradeViewModelBase with Store {
104 final output = sendViewModel.outputs.first;
105 output.address = trade.inputAddress ?? '';
106 output.setCryptoAmount(trade.amount);
107 + if (_provider is ThorChainExchangeProvider) output.memo = trade.memo;
108 sendViewModel.selectedCryptoCurrency = trade.from;
104 - await sendViewModel.createTransaction();
109 + final pendingTransaction = await sendViewModel.createTransaction(provider: _provider);
110 + if (_provider is ThorChainExchangeProvider) {
111 + trade.id = pendingTransaction?.id ?? '';
112 + trades.add(trade);
113 + }
114 }
115
116 @action
@@ -127,6 +136,8 @@ abstract class ExchangeTradeViewModelBase with Store {
136 tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
137 final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
138 items.clear();
139 +
140 + if(trade.provider != ExchangeProviderDescription.thorChain)
141 items.add(ExchangeTradeItem(
142 title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
143
lib/view_model/exchange/exchange_view_model.dart
+27 -2
@@ -2,6 +2,7 @@ import 'dart:async';
2 import 'dart:collection';
3 import 'dart:convert';
4
5 +import 'package:bitcoin_base/bitcoin_base.dart';
6 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
7 import 'package:cake_wallet/bitcoin/bitcoin.dart';
8 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
@@ -9,6 +10,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
10 import 'package:cake_wallet/entities/preferences_key.dart';
11 import 'package:cake_wallet/entities/wallet_contact.dart';
12 import 'package:cake_wallet/ethereum/ethereum.dart';
13 +import 'package:cake_wallet/exchange/exchange_provider_description.dart';
14 import 'package:cake_wallet/exchange/exchange_template.dart';
15 import 'package:cake_wallet/exchange/exchange_trade_state.dart';
16 import 'package:cake_wallet/exchange/limits.dart';
@@ -18,6 +20,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
20 import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
21 import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
22 import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
23 +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
24 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
25 import 'package:cake_wallet/exchange/trade.dart';
26 import 'package:cake_wallet/exchange/trade_request.dart';
@@ -96,7 +99,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
99
100 /// if the provider is not in the user settings (user's first time or newly added provider)
101 /// then use its default value decided by us
99 - selectedProviders = ObservableList.of(providersForCurrentPair()
102 + selectedProviders = ObservableList.of(providerList
103 .where((element) => exchangeProvidersSelection[element.title] == null
104 ? element.isEnabled
105 : (exchangeProvidersSelection[element.title] as bool))
@@ -148,6 +151,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
151 SimpleSwapExchangeProvider(),
152 TrocadorExchangeProvider(
153 useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
154 + ThorChainExchangeProvider(tradesStore: trades),
155 if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
156 ];
157
@@ -496,8 +500,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
500 await provider.createTrade(request: request, isFixedRateMode: isFixedRateMode);
501 trade.walletId = wallet.id;
502 trade.fromWalletAddress = wallet.walletAddresses.address;
503 +
504 + if (!isCanCreateTrade(trade)) {
505 + tradeState = TradeIsCreatedFailure(
506 + title: S.current.trade_not_created,
507 + error: S.current.thorchain_taproot_address_not_supported);
508 + return;
509 + }
510 +
511 tradesStore.setTrade(trade);
500 - await trades.add(trade);
512 + if (trade.provider != ExchangeProviderDescription.thorChain) await trades.add(trade);
513 tradeState = TradeIsCreatedSuccessfully(trade: trade);
514
515 /// return after the first successful trade
@@ -749,4 +761,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
761 int get depositMaxDigits => depositCurrency.decimals;
762
763 int get receiveMaxDigits => receiveCurrency.decimals;
764 +
765 + bool isCanCreateTrade(Trade trade) {
766 + if (trade.provider == ExchangeProviderDescription.thorChain) {
767 + final payoutAddress = trade.payoutAddress ?? '';
768 + final fromWalletAddress = trade.fromWalletAddress ?? '';
769 + final tapRootPattern = RegExp(P2trAddress.regex.pattern);
770 +
771 + if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) {
772 + return false;
773 + }
774 + }
775 + return true;
776 + }
777 }
lib/view_model/order_details_view_model.dart
+1 -1
@@ -99,7 +99,7 @@ abstract class OrderDetailsViewModelBase with Store {
99 final buildURL = trackUrl + '${order.transferId}';
100 items.add(
101 TrackTradeListItem(
102 - title: 'Track',
102 + title: S.current.track,
103 value: buildURL,
104 onTap: () {
105 try {
lib/view_model/send/output.dart
+3
@@ -66,6 +66,8 @@ abstract class OutputBase with Store {
66 @observable
67 String extractedAddress;
68
69 + String? memo;
70 +
71 @computed
72 bool get isParsedAddress =>
73 parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
@@ -175,6 +177,7 @@ abstract class OutputBase with Store {
177 fiatAmount = '';
178 address = '';
179 note = '';
180 + memo = null;
181 resetParsedAddress();
182 }
183
lib/view_model/send/send_view_model.dart
+9 -1
@@ -2,6 +2,8 @@ import 'package:cake_wallet/entities/contact.dart';
2 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
3 import 'package:cake_wallet/entities/transaction_description.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
5 +import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
6 +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
7 import 'package:cake_wallet/nano/nano.dart';
8 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
9 import 'package:cake_wallet/entities/contact_record.dart';
@@ -296,14 +298,20 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
298 }
299
300 @action
299 - Future<void> createTransaction() async {
301 + Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async {
302 try {
303 state = IsExecutingState();
304 pendingTransaction = await wallet.createTransaction(_credentials());
305 + if (provider is ThorChainExchangeProvider) {
306 + final outputCount = pendingTransaction?.outputCount ?? 0;
307 + if (outputCount > 10) throw Exception("ThorChain does not support more than 10 outputs");
308 + }
309 state = ExecutedSuccessfullyState();
310 + return pendingTransaction;
311 } catch (e) {
312 print('Failed with ${e.toString()}');
313 state = FailureState(e.toString());
314 + return null;
315 }
316 }
317
lib/view_model/trade_details_view_model.dart
+30 -28
@@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
6 import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
7 import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
8 import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
9 +import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
10 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
11 import 'package:cake_wallet/exchange/trade.dart';
12 import 'package:cake_wallet/generated/i18n.dart';
@@ -52,6 +53,9 @@ abstract class TradeDetailsViewModelBase with Store {
53 case ExchangeProviderDescription.exolix:
54 _provider = ExolixExchangeProvider();
55 break;
56 + case ExchangeProviderDescription.thorChain:
57 + _provider = ThorChainExchangeProvider(tradesStore: trades);
58 + break;
59 }
60
61 _updateItems();
@@ -62,6 +66,24 @@ abstract class TradeDetailsViewModelBase with Store {
66 }
67 }
68
69 + static String? getTrackUrl(ExchangeProviderDescription provider, Trade trade) {
70 + switch (provider) {
71 + case ExchangeProviderDescription.changeNow:
72 + return 'https://changenow.io/exchange/txs/${trade.id}';
73 + case ExchangeProviderDescription.sideShift:
74 + return 'https://sideshift.ai/orders/${trade.id}';
75 + case ExchangeProviderDescription.simpleSwap:
76 + return 'https://simpleswap.io/exchange?id=${trade.id}';
77 + case ExchangeProviderDescription.trocador:
78 + return 'https://trocador.app/en/checkout/${trade.id}';
79 + case ExchangeProviderDescription.exolix:
80 + return 'https://exolix.com/transaction/${trade.id}';
81 + case ExchangeProviderDescription.thorChain:
82 + return 'https://track.ninerealms.com/${trade.id}';
83 + }
84 + return null;
85 + }
86 +
87 final Box<Trade> trades;
88
89 @observable
@@ -125,46 +147,26 @@ abstract class TradeDetailsViewModelBase with Store {
147 items.add(StandartListItem(
148 title: S.current.trade_details_provider, value: trade.provider.toString()));
149
128 - if (trade.provider == ExchangeProviderDescription.changeNow) {
129 - final buildURL = 'https://changenow.io/exchange/txs/${trade.id.toString()}';
150 + final trackUrl = TradeDetailsViewModelBase.getTrackUrl(trade.provider, trade);
151 + if (trackUrl != null) {
152 items.add(TrackTradeListItem(
131 - title: 'Track',
132 - value: buildURL,
133 - onTap: () {
134 - _launchUrl(buildURL);
135 - }));
136 - }
137 -
138 - if (trade.provider == ExchangeProviderDescription.sideShift) {
139 - final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}';
140 - items.add(
141 - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
153 + title: S.current.track, value: trackUrl, onTap: () => _launchUrl(trackUrl)));
154 }
155
144 - if (trade.provider == ExchangeProviderDescription.simpleSwap) {
145 - final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}';
146 - items.add(
147 - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
156 + if (trade.isRefund == true) {
157 + items.add(StandartListItem(
158 + title: 'Refund', value: trade.refundAddress ?? ''));
159 }
160
161 if (trade.provider == ExchangeProviderDescription.trocador) {
151 - final buildURL = 'https://trocador.app/en/checkout/${trade.id.toString()}';
152 - items.add(
153 - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
154 -
162 items.add(StandartListItem(
163 title: '${trade.providerName} ${S.current.id.toUpperCase()}',
164 value: trade.providerId ?? ''));
165
159 - if (trade.password != null && trade.password!.isNotEmpty)
166 + if (trade.password != null && trade.password!.isNotEmpty) {
167 items.add(StandartListItem(
168 title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
162 - }
163 -
164 - if (trade.provider == ExchangeProviderDescription.exolix) {
165 - final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}';
166 - items.add(
167 - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
169 + }
170 }
171 }
172
res/values/strings_ar.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "اسم القالب",
644 "third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!",
645 "third_intro_title": "يتماشي Yat بلطف مع الآخرين",
646 + "thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.",
647 "time": "${minutes}د ${seconds}س",
648 "tip": "بقشيش:",
649 "today": "اليوم",
@@ -660,6 +661,7 @@
661 "totp_code": "كود TOTP",
662 "totp_secret_code": "كود TOTP السري",
663 "totp_verification_success": "تم التحقق بنجاح!",
664 + "track": " ﺭﺎﺴﻣ",
665 "trade_details_copied": "تم نسخ ${title} إلى الحافظة",
666 "trade_details_created_at": "أنشئت في",
667 "trade_details_fetching": "جار الجلب",
res/values/strings_bg.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Име на шаблон",
644 "third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!",
645 "third_intro_title": "Yat добре се сработва с други",
646 + "thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.",
647 "time": "${minutes} мин ${seconds} сек",
648 "tip": "Tip:",
649 "today": "Днес",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP код",
662 "totp_secret_code": "TOTP таен код",
663 "totp_verification_success": "Проверката е успешна!",
664 + "track": "Писта",
665 "trade_details_copied": "${title} копирано",
666 "trade_details_created_at": "Създадено",
667 "trade_details_fetching": "Обработка",
res/values/strings_cs.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Název šablony",
644 "third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!",
645 "third_intro_title": "Yat dobře spolupracuje s ostatními",
646 + "thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "Spropitné:",
649 "today": "Dnes",
@@ -660,6 +661,7 @@
661 "totp_code": "Kód TOTP",
662 "totp_secret_code": "Tajný kód TOTP",
663 "totp_verification_success": "Ověření proběhlo úspěšně!",
664 + "track": "Dráha",
665 "trade_details_copied": "${title} zkopírováno do schránky",
666 "trade_details_created_at": "Vytvořeno v",
667 "trade_details_fetching": "Získávám",
res/values/strings_de.arb
+2
@@ -644,6 +644,7 @@
644 "template_name": "Vorlagenname",
645 "third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
646 "third_intro_title": "Yat spielt gut mit anderen",
647 + "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.",
648 "time": "${minutes}m ${seconds}s",
649 "tip": "Hinweis:",
650 "today": "Heute",
@@ -661,6 +662,7 @@
662 "totp_code": "TOTP-Code",
663 "totp_secret_code": "TOTP-Geheimcode",
664 "totp_verification_success": "Verifizierung erfolgreich!",
665 + "track": "Schiene",
666 "trade_details_copied": "${title} in die Zwischenablage kopiert",
667 "trade_details_created_at": "Erzeugt am",
668 "trade_details_fetching": "Wird ermittelt",
res/values/strings_en.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Template Name",
644 "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
645 "third_intro_title": "Yat plays nicely with others",
646 + "thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "Tip:",
649 "today": "Today",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP Code",
662 "totp_secret_code": "TOTP Secret Code",
663 "totp_verification_success": "Verification Successful!",
664 + "track": "Track",
665 "trade_details_copied": "${title} copied to Clipboard",
666 "trade_details_created_at": "Created at",
667 "trade_details_fetching": "Fetching",
res/values/strings_es.arb
+2
@@ -644,6 +644,7 @@
644 "template_name": "Nombre de la plantilla",
645 "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!",
646 "third_intro_title": "Yat juega muy bien con otras",
647 + "thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.",
648 "time": "${minutes}m ${seconds}s",
649 "tip": "Consejo:",
650 "today": "Hoy",
@@ -661,6 +662,7 @@
662 "totp_code": "Código TOTP",
663 "totp_secret_code": "Código secreto TOTP",
664 "totp_verification_success": "¡Verificación exitosa!",
665 + "track": "Pista",
666 "trade_details_copied": "${title} Copiado al portapapeles",
667 "trade_details_created_at": "Creado en",
668 "trade_details_fetching": "Cargando",
res/values/strings_fr.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Nom du modèle",
644 "third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !",
645 "third_intro_title": "Yat est universel",
646 + "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.",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "Pourboire :",
649 "today": "Aujourd'hui",
@@ -660,6 +661,7 @@
661 "totp_code": "Code TOTP",
662 "totp_secret_code": "Secret TOTP",
663 "totp_verification_success": "Vérification réussie !",
664 + "track": "Piste",
665 "trade_details_copied": "${title} copié vers le presse-papier",
666 "trade_details_created_at": "Créé le",
667 "trade_details_fetching": "Récupération",
res/values/strings_ha.arb
+2
@@ -645,6 +645,7 @@
645 "template_name": "Sunan Samfura",
646 "third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!",
647 "third_intro_title": "Yat yana wasa da kyau tare da wasu",
648 + "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.",
649 "time": "${minutes}m ${seconds}s",
650 "tip": "Tukwici:",
651 "today": "Yau",
@@ -662,6 +663,7 @@
663 "totp_code": "Lambar totp",
664 "totp_secret_code": "Lambar sirri",
665 "totp_verification_success": "Tabbatar cin nasara!",
666 + "track": "Waƙa",
667 "trade_details_copied": "${title} an kwafa zuwa cikin kwafin",
668 "trade_details_created_at": "An ƙirƙira a",
669 "trade_details_fetching": "Daukewa",
res/values/strings_hi.arb
+2
@@ -645,6 +645,7 @@
645 "template_name": "टेम्पलेट नाम",
646 "third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
647 "third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है",
648 + "thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।",
649 "time": "${minutes}m ${seconds}s",
650 "tip": "टिप:",
651 "today": "आज",
@@ -662,6 +663,7 @@
663 "totp_code": "टीओटीपी कोड",
664 "totp_secret_code": "टीओटीपी गुप्त कोड",
665 "totp_verification_success": "सत्यापन सफल!",
666 + "track": "रास्ता",
667 "trade_details_copied": "${title} क्लिपबोर्ड पर नकल",
668 "trade_details_created_at": "पर बनाया गया",
669 "trade_details_fetching": "ला रहा है",
res/values/strings_hr.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Naziv predloška",
644 "third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!",
645 "third_intro_title": "Yat se lijepo igra s drugima",
646 + "thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "Savjet:",
649 "today": "Danas",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP kod",
662 "totp_secret_code": "TOTP tajni kod",
663 "totp_verification_success": "Provjera uspješna!",
664 + "track": "Staza",
665 "trade_details_copied": "${title} kopiran u međuspremnik",
666 "trade_details_created_at": "Stvoreno u",
667 "trade_details_fetching": "Dohvaćanje",
res/values/strings_id.arb
+2
@@ -646,6 +646,7 @@
646 "template_name": "Nama Templat",
647 "third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!",
648 "third_intro_title": "Yat bermain baik dengan yang lain",
649 + "thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.",
650 "time": "${minutes}m ${seconds}s",
651 "tip": "Tip:",
652 "today": "Hari ini",
@@ -663,6 +664,7 @@
664 "totp_code": "Kode TOTP",
665 "totp_secret_code": "Kode Rahasia TOTP",
666 "totp_verification_success": "Verifikasi Berhasil!",
667 + "track": "Melacak",
668 "trade_details_copied": "${title} disalin ke Clipboard",
669 "trade_details_created_at": "Dibuat pada",
670 "trade_details_fetching": "Mengambil",
res/values/strings_it.arb
+2
@@ -645,6 +645,7 @@
645 "template_name": "Nome modello",
646 "third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!",
647 "third_intro_title": "Yat gioca bene con gli altri",
648 + "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.",
649 "time": "${minutes}m ${seconds}s",
650 "tip": "Suggerimento:",
651 "today": "Oggi",
@@ -662,6 +663,7 @@
663 "totp_code": "Codice TOTP",
664 "totp_secret_code": "TOTP codice segreto",
665 "totp_verification_success": "Verifica riuscita!",
666 + "track": "Traccia",
667 "trade_details_copied": "${title} copiati negli Appunti",
668 "trade_details_created_at": "Creato alle",
669 "trade_details_fetching": "Recupero",
res/values/strings_ja.arb
+2
@@ -644,6 +644,7 @@
644 "template_name": "テンプレート名",
645 "third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます!",
646 "third_intro_title": "Yatは他の人とうまく遊ぶ",
647 + "thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。",
648 "time": "${minutes}m ${seconds}s",
649 "tip": "ヒント: ",
650 "today": "今日",
@@ -661,6 +662,7 @@
662 "totp_code": "TOTP コード",
663 "totp_secret_code": "TOTPシークレットコード",
664 "totp_verification_success": "検証成功!",
665 + "track": "追跡",
666 "trade_details_copied": "${title} クリップボードにコピーしました",
667 "trade_details_created_at": "で作成",
668 "trade_details_fetching": "フェッチング",
res/values/strings_ko.arb
+2
@@ -644,6 +644,7 @@
644 "template_name": "템플릿 이름",
645 "third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!",
646 "third_intro_title": "Yat는 다른 사람들과 잘 놉니다.",
647 + "thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.",
648 "time": "${minutes}m ${seconds}s",
649 "tip": "팁:",
650 "today": "오늘",
@@ -661,6 +662,7 @@
662 "totp_code": "TOTP 코드",
663 "totp_secret_code": "TOTP 비밀 코드",
664 "totp_verification_success": "확인 성공!",
665 + "track": "길",
666 "trade_details_copied": "${title} 클립 보드에 복사",
667 "trade_details_created_at": "에 작성",
668 "trade_details_fetching": "가져 오는 중",
res/values/strings_my.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "နမူနာပုံစံ",
644 "third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။",
645 "third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။",
646 + "thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "အကြံပြုချက်-",
649 "today": "ဒီနေ့",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP ကုဒ်",
662 "totp_secret_code": "TOTP လျှို့ဝှက်ကုဒ်",
663 "totp_verification_success": "အတည်ပြုခြင်း အောင်မြင်ပါသည်။",
664 + "track": "တစ်ပုဒ်",
665 "trade_details_copied": "${title} ကို Clipboard သို့ ကူးယူထားသည်။",
666 "trade_details_created_at": "တွင်ဖန်တီးခဲ့သည်။",
667 "trade_details_fetching": "ခေါ်ယူခြင်း။",
res/values/strings_nl.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Sjabloonnaam",
644 "third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!",
645 "third_intro_title": "Yat speelt leuk met anderen",
646 + "thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "Tip:",
649 "today": "Vandaag",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP-code",
662 "totp_secret_code": "TOTP-geheime code",
663 "totp_verification_success": "Verificatie geslaagd!",
664 + "track": "Spoor",
665 "trade_details_copied": "${title} gekopieerd naar het klembord",
666 "trade_details_created_at": "Gemaakt bij",
667 "trade_details_fetching": "Ophalen",
res/values/strings_pl.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Nazwa szablonu",
644 "third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!",
645 "third_intro_title": "Yat ładnie bawi się z innymi",
646 + "thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "wskazówka:",
649 "today": "Dzisiaj",
@@ -660,6 +661,7 @@
661 "totp_code": "Kod TOTP",
662 "totp_secret_code": "Tajny kod TOTP",
663 "totp_verification_success": "Weryfikacja powiodła się!",
664 + "track": "Ścieżka",
665 "trade_details_copied": "${title} skopiowane do schowka",
666 "trade_details_created_at": "Utworzono ",
667 "trade_details_fetching": "Pobieranie",
res/values/strings_pt.arb
+2
@@ -645,6 +645,7 @@
645 "template_name": "Nome do modelo",
646 "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!",
647 "third_intro_title": "Yat joga bem com os outros",
648 + "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.",
649 "time": "${minutes}m ${seconds}s",
650 "tip": "Dica:",
651 "today": "Hoje",
@@ -662,6 +663,7 @@
663 "totp_code": "Código TOTP",
664 "totp_secret_code": "Código Secreto TOTP",
665 "totp_verification_success": "Verificação bem-sucedida!",
666 + "track": "Acompanhar",
667 "trade_details_copied": "${title} copiados para a área de transferência",
668 "trade_details_created_at": "Criada em",
669 "trade_details_fetching": "Buscando",
res/values/strings_ru.arb
+2
@@ -644,6 +644,7 @@
644 "template_name": "Имя Шаблона",
645 "third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!",
646 "third_intro_title": "Yat хорошо взаимодействует с другими",
647 + "thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.",
648 "time": "${minutes}мин ${seconds}сек",
649 "tip": "Совет:",
650 "today": "Сегодня",
@@ -661,6 +662,7 @@
662 "totp_code": "TOTP-код",
663 "totp_secret_code": "Секретный код ТОТП",
664 "totp_verification_success": "Проверка прошла успешно!",
665 + "track": "Отслеживать",
666 "trade_details_copied": "${title} скопировано в буфер обмена",
667 "trade_details_created_at": "Создано",
668 "trade_details_fetching": "Получение",
res/values/strings_th.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "ชื่อแม่แบบ",
644 "third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!",
645 "third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น",
646 + "thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "เพิ่มค่าตอบแทน:",
649 "today": "วันนี้",
@@ -660,6 +661,7 @@
661 "totp_code": "รหัสทีโอพี",
662 "totp_secret_code": "รหัสลับ TOTP",
663 "totp_verification_success": "การยืนยันสำเร็จ!",
664 + "track": "ติดตาม",
665 "trade_details_copied": "${title} คัดลอกไปยัง Clipboard",
666 "trade_details_created_at": "สร้างเมื่อ",
667 "trade_details_fetching": "กำลังเรียกข้อมูล",
res/values/strings_tl.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "Pangalan ng Template",
644 "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!",
645 "third_intro_title": "Si Yat ay mahusay na gumaganap sa iba",
646 + "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.",
647 "time": "${minutes} m ${seconds} s",
648 "tip": "Tip:",
649 "today": "Ngayon",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP code",
662 "totp_secret_code": "TOTP Secret Code",
663 "totp_verification_success": "Matagumpay ang pagpapatunay!",
664 + "track": "Subaybayan",
665 "trade_details_copied": "${title} kinopya sa clipboard",
666 "trade_details_created_at": "Nilikha sa",
667 "trade_details_fetching": "Pagkuha",
res/values/strings_tr.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "şablon adı",
644 "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!",
645 "third_intro_title": "Yat diğerleriyle iyi çalışır",
646 + "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.",
647 "time": "${minutes}d ${seconds}s",
648 "tip": "Bahşiş:",
649 "today": "Bugün",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP Kodu",
662 "totp_secret_code": "TOTP Gizli Kodu",
663 "totp_verification_success": "Doğrulama Başarılı!",
664 + "track": "İzlemek",
665 "trade_details_copied": "${title} panoya kopyalandı",
666 "trade_details_created_at": "'da oluşturuldu",
667 "trade_details_fetching": "Getiriliyor",
res/values/strings_uk.arb
+2
@@ -644,6 +644,7 @@
644 "template_name": "Назва шаблону",
645 "third_intro_content": "Yat знаходиться за межами Cake Wallet. Будь-яку адресу гаманця на землі можна замінити на Yat!",
646 "third_intro_title": "Yat добре взаємодіє з іншими",
647 + "thorchain_taproot_address_not_supported": "Постачальник Thorchain не підтримує адреси Taproot. Будь ласка, змініть адресу або виберіть іншого постачальника.",
648 "time": "${minutes}хв ${seconds}сек",
649 "tip": "Порада:",
650 "today": "Сьогодні",
@@ -661,6 +662,7 @@
662 "totp_code": "Код TOTP",
663 "totp_secret_code": "Секретний код TOTP",
664 "totp_verification_success": "Перевірка успішна!",
665 + "track": "Відслідковувати",
666 "trade_details_copied": "${title} скопійовано в буфер обміну",
667 "trade_details_created_at": "Створено",
668 "trade_details_fetching": "Отримання",
res/values/strings_ur.arb
+2
@@ -645,6 +645,7 @@
645 "template_name": "ٹیمپلیٹ کا نام",
646 "third_intro_content": "Yats بھی Cake والیٹ سے باہر رہتے ہیں۔ زمین پر کسی بھی بٹوے کے پتے کو Yat سے تبدیل کیا جا سکتا ہے!",
647 "third_intro_title": "Yat دوسروں کے ساتھ اچھی طرح کھیلتا ہے۔",
648 + "thorchain_taproot_address_not_supported": "تھورچین فراہم کنندہ ٹیپروٹ پتے کی حمایت نہیں کرتا ہے۔ براہ کرم پتہ تبدیل کریں یا ایک مختلف فراہم کنندہ کو منتخب کریں۔",
649 "time": "${minutes}m ${seconds}s",
650 "tip": "ٹپ:",
651 "today": "آج",
@@ -662,6 +663,7 @@
663 "totp_code": "TOTP کوڈ",
664 "totp_secret_code": "TOTP خفیہ کوڈ",
665 "totp_verification_success": "توثیق کامیاب!",
666 + "track": " ﮏﯾﺮﭨ",
667 "trade_details_copied": "${title} کو کلپ بورڈ پر کاپی کیا گیا۔",
668 "trade_details_created_at": "پر تخلیق کیا گیا۔",
669 "trade_details_fetching": "لا رہا ہے۔",
res/values/strings_yo.arb
+2
@@ -644,6 +644,7 @@
644 "template_name": "Orukọ Awoṣe",
645 "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!",
646 "third_intro_title": "Àlàáfíà ni Yat àti àwọn ìmíìn jọ wà",
647 + "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ọ.",
648 "time": "${minutes}ìṣj ${seconds}ìṣs",
649 "tip": "Owó àfikún:",
650 "today": "Lénìí",
@@ -661,6 +662,7 @@
662 "totp_code": "Koodu TOTP",
663 "totp_secret_code": "Koodu iye TOTP",
664 "totp_verification_success": "Ìbẹrẹ dọkita!",
665 + "track": "Orin",
666 "trade_details_copied": "Ti ṣeda ${title} sí àtẹ àkọsílẹ̀",
667 "trade_details_created_at": "Ṣíṣe ní",
668 "trade_details_fetching": "Ń mú wá",
res/values/strings_zh.arb
+2
@@ -643,6 +643,7 @@
643 "template_name": "模板名称",
644 "third_intro_content": "Yats 也住在 Cake Wallet 之外。 地球上任何一個錢包地址都可以用一個Yat來代替!",
645 "third_intro_title": "Yat 和別人玩得很好",
646 + "thorchain_taproot_address_not_supported": "Thorchain提供商不支持Taproot地址。请更改地址或选择其他提供商。",
647 "time": "${minutes}m ${seconds}s",
648 "tip": "提示:",
649 "today": "今天",
@@ -660,6 +661,7 @@
661 "totp_code": "TOTP代码",
662 "totp_secret_code": "TOTP密码",
663 "totp_verification_success": "验证成功!",
664 + "track": "追踪",
665 "trade_details_copied": "${title} 复制到剪贴板",
666 "trade_details_created_at": "创建于",
667 "trade_details_fetching": "正在获取",