Cw 467 mark change outputs in unspent outputs list (#1137)
* CW-490 Use native Coin Freeze * CW-467 Code cleanup * CW-467 Fix native Code * CW-467 Extend Unspend * CW-467 Add isChange * CW-467 Minor Fixes * CW-467 Add isChange to Electrum Unspents * CW-467 Localize Change Tag * CW-467 Fix frozen balance showing on other monero wallets * CW-467 Fix frozen balance showing on other monero wallets
Konstantin Ullrich committed
Nov 16, 2023 at 00:12 UTC
8084f490b593b4dd74956ab00cf516213a69762b
42 files changed
+292
-257
cw_bitcoin/lib/electrum_wallet.dart
+60
-56
@@ -2,41 +2,41 @@ import 'dart:async';
2
import 'dart:convert';
3
import 'dart:io';
4
import 'dart:math';
5
-import 'package:cw_core/pending_transaction.dart';
6
-import 'package:cw_core/unspent_coins_info.dart';
7
-import 'package:cw_core/wallet_type.dart';
8
-import 'package:hive/hive.dart';
9
-import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
10
-import 'package:mobx/mobx.dart';
11
-import 'package:rxdart/subjects.dart';
12
-import 'package:flutter/foundation.dart';
5
+
6
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
14
-import 'package:cw_bitcoin/electrum_transaction_info.dart';
15
-import 'package:cw_core/pathForWallet.dart';
7
+import 'package:collection/collection.dart';
8
import 'package:cw_bitcoin/address_to_output_script.dart';
9
import 'package:cw_bitcoin/bitcoin_address_record.dart';
18
-import 'package:cw_bitcoin/electrum_balance.dart';
10
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
20
-import 'package:cw_bitcoin/electrum_transaction_history.dart';
11
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
12
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
13
import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
14
import 'package:cw_bitcoin/bitcoin_unspent.dart';
15
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
16
+import 'package:cw_bitcoin/electrum.dart';
17
+import 'package:cw_bitcoin/electrum_balance.dart';
18
+import 'package:cw_bitcoin/electrum_transaction_history.dart';
19
+import 'package:cw_bitcoin/electrum_transaction_info.dart';
20
+import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
21
import 'package:cw_bitcoin/file.dart';
22
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
23
import 'package:cw_bitcoin/script_hash.dart';
24
import 'package:cw_bitcoin/utils.dart';
30
-import 'package:cw_core/wallet_base.dart';
25
+import 'package:cw_core/crypto_currency.dart';
26
import 'package:cw_core/node.dart';
27
+import 'package:cw_core/pathForWallet.dart';
28
+import 'package:cw_core/pending_transaction.dart';
29
import 'package:cw_core/sync_status.dart';
30
+import 'package:cw_core/transaction_direction.dart';
31
import 'package:cw_core/transaction_priority.dart';
32
+import 'package:cw_core/unspent_coins_info.dart';
33
+import 'package:cw_core/wallet_base.dart';
34
import 'package:cw_core/wallet_info.dart';
35
-import 'package:cw_bitcoin/electrum.dart';
35
+import 'package:flutter/foundation.dart';
36
import 'package:hex/hex.dart';
37
-import 'package:cw_core/crypto_currency.dart';
38
-import 'package:collection/collection.dart';
39
-import 'package:bip32/bip32.dart';
37
+import 'package:hive/hive.dart';
38
+import 'package:mobx/mobx.dart';
39
+import 'package:rxdart/subjects.dart';
40
41
part 'electrum_wallet.g.dart';
42
@@ -47,18 +47,18 @@ abstract class ElectrumWalletBase
47
with Store {
48
ElectrumWalletBase(
49
{required String password,
50
- required WalletInfo walletInfo,
51
- required Box<UnspentCoinsInfo> unspentCoinsInfo,
52
- required this.networkType,
53
- required this.mnemonic,
54
- required Uint8List seedBytes,
55
- List<BitcoinAddressRecord>? initialAddresses,
56
- ElectrumClient? electrumClient,
57
- ElectrumBalance? initialBalance,
58
- CryptoCurrency? currency})
50
+ required WalletInfo walletInfo,
51
+ required Box<UnspentCoinsInfo> unspentCoinsInfo,
52
+ required this.networkType,
53
+ required this.mnemonic,
54
+ required Uint8List seedBytes,
55
+ List<BitcoinAddressRecord>? initialAddresses,
56
+ ElectrumClient? electrumClient,
57
+ ElectrumBalance? initialBalance,
58
+ CryptoCurrency? currency})
59
: hd = currency == CryptoCurrency.bch
60
- ? bitcoinCashHDWallet(seedBytes)
61
- : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
60
+ ? bitcoinCashHDWallet(seedBytes)
61
+ : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
62
syncStatus = NotConnectedSyncStatus(),
63
_password = password,
64
_feeRates = <int>[],
@@ -67,9 +67,9 @@ abstract class ElectrumWalletBase
67
_scripthashesUpdateSubject = {},
68
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
69
? {
70
- currency:
71
- initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
72
- }
70
+ currency:
71
+ initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
72
+ }
73
: {}),
74
this.unspentCoinsInfo = unspentCoinsInfo,
75
super(walletInfo) {
@@ -79,8 +79,7 @@ abstract class ElectrumWalletBase
79
}
80
81
static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
82
- bitcoin.HDWallet.fromSeed(seedBytes)
83
- .derivePath("m/44'/145'/0'/0");
82
+ bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/0");
83
84
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
85
inputsCount * 146 + outputsCounts * 33 + 8;
@@ -294,10 +293,12 @@ abstract class ElectrumWalletBase
293
if (input.isP2wpkh) {
294
final p2wpkh = bitcoin
295
.P2WPKH(
297
- data: generatePaymentData(
298
- hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
299
- index: input.bitcoinAddressRecord.index),
300
- network: networkType)
296
+ data: generatePaymentData(
297
+ hd: input.bitcoinAddressRecord.isHidden
298
+ ? walletAddresses.sideHd
299
+ : walletAddresses.mainHd,
300
+ index: input.bitcoinAddressRecord.index),
301
+ network: networkType)
302
.data;
303
304
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
@@ -347,12 +348,12 @@ abstract class ElectrumWalletBase
348
}
349
350
String toJSON() => json.encode({
350
- 'mnemonic': mnemonic,
351
- 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
352
- 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
353
- 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
354
- 'balance': balance[currency]?.toJSON()
355
- });
351
+ 'mnemonic': mnemonic,
352
+ 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
353
+ 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
354
+ 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
355
+ 'balance': balance[currency]?.toJSON()
356
+ });
357
358
int feeRate(TransactionPriority priority) {
359
try {
@@ -367,7 +368,7 @@ abstract class ElectrumWalletBase
368
}
369
370
int feeAmountForPriority(
370
- BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
371
+ BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
372
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
373
374
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
@@ -467,18 +468,20 @@ abstract class ElectrumWalletBase
468
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
469
470
Future<void> updateUnspent() async {
470
- final unspent = await Future.wait(walletAddresses
471
- .addresses.map((address) => electrumClient
471
+ final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient
472
.getListUnspentWithAddress(address.address, networkType)
473
- .then((unspent) => unspent
474
- .map((unspent) {
475
- try {
476
- return BitcoinUnspent.fromJSON(address, unspent);
477
- } catch(_) {
478
- return null;
479
- }
480
- }).whereNotNull())));
473
+ .then((unspent) => unspent.map((unspent) {
474
+ try {
475
+ return BitcoinUnspent.fromJSON(address, unspent);
476
+ } catch (_) {
477
+ return null;
478
+ }
479
+ }).whereNotNull())));
480
unspentCoins = unspent.expand((e) => e).toList();
481
+ unspentCoins.forEach((coin) async {
482
+ final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
483
+ coin.isChange = tx!.direction == TransactionDirection.outgoing;
484
+ });
485
486
if (unspentCoinsInfo.isEmpty) {
487
unspentCoins.forEach((coin) => _addCoinInfo(coin));
@@ -515,6 +518,7 @@ abstract class ElectrumWalletBase
518
address: coin.bitcoinAddressRecord.address,
519
value: coin.value,
520
vout: coin.vout,
521
+ isChange: coin.isChange,
522
);
523
524
await unspentCoinsInfo.add(newInfo);
@@ -524,7 +528,7 @@ abstract class ElectrumWalletBase
528
try {
529
final List<dynamic> keys = <dynamic>[];
530
final currentWalletUnspentCoins =
527
- unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
531
+ unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
532
533
if (currentWalletUnspentCoins.isNotEmpty) {
534
currentWalletUnspentCoins.forEach((element) {
@@ -657,7 +661,7 @@ abstract class ElectrumWalletBase
661
final addresses = walletAddresses.addresses.toList();
662
final balanceFutures = <Future<Map<String, dynamic>>>[];
663
for (var i = 0; i < addresses.length; i++) {
660
- final addressRecord = addresses[i] ;
664
+ final addressRecord = addresses[i];
665
final sh = scriptHash(addressRecord.address, networkType: networkType);
666
final balanceFuture = electrumClient.getBalance(sh);
667
balanceFutures.add(balanceFuture);
cw_core/lib/unspent_coins_info.dart
+9
-1
@@ -14,7 +14,9 @@ class UnspentCoinsInfo extends HiveObject {
14
required this.address,
15
required this.vout,
16
required this.value,
17
- this.keyImage = null
17
+ this.keyImage = null,
18
+ this.isChange = false,
19
+ this.accountIndex = 0
20
});
21
22
static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
@@ -47,6 +49,12 @@ class UnspentCoinsInfo extends HiveObject {
49
50
@HiveField(8, defaultValue: null)
51
String? keyImage;
52
+
53
+ @HiveField(9, defaultValue: false)
54
+ bool isChange;
55
+
56
+ @HiveField(10, defaultValue: 0)
57
+ int accountIndex;
58
59
String get note => noteRaw ?? '';
60
cw_core/lib/unspent_transaction_output.dart
+2
@@ -2,6 +2,7 @@ class Unspent {
2
Unspent(this.address, this.hash, this.value, this.vout, this.keyImage)
3
: isSending = true,
4
isFrozen = false,
5
+ isChange = false,
6
note = '';
7
8
final String address;
@@ -10,6 +11,7 @@ class Unspent {
11
final int vout;
12
final String? keyImage;
13
14
+ bool isChange;
15
bool isSending;
16
bool isFrozen;
17
String note;
cw_monero/ios/Classes/monero_api.cpp
+15
@@ -841,6 +841,12 @@ extern "C"
841
return m_transaction_history->count();
842
}
843
844
+ TransactionInfoRow* get_transaction(char * txId)
845
+ {
846
+ Monero::TransactionInfo *row = m_transaction_history->transaction(std::string(txId));
847
+ return new TransactionInfoRow(row);
848
+ }
849
+
850
int LedgerExchange(
851
unsigned char *command,
852
unsigned int cmd_len,
@@ -970,6 +976,15 @@ extern "C"
976
return result;
977
}
978
979
+ void freeze_coin(int index)
980
+ {
981
+ m_coins->setFrozen(index);
982
+ }
983
+
984
+ void thaw_coin(int index)
985
+ {
986
+ m_coins->thaw(index);
987
+ }
988
989
#ifdef __cplusplus
990
}
cw_monero/lib/api/coins_info.dart
+12
@@ -16,8 +16,20 @@ final coinNative = moneroApi
16
.lookup<NativeFunction<coin>>('coin')
17
.asFunction<GetCoin>();
18
19
+final freezeCoinNative = moneroApi
20
+ .lookup<NativeFunction<freeze_coin>>('freeze_coin')
21
+ .asFunction<FreezeCoin>();
22
+
23
+final thawCoinNative = moneroApi
24
+ .lookup<NativeFunction<thaw_coin>>('thaw_coin')
25
+ .asFunction<ThawCoin>();
26
+
27
void refreshCoins(int accountIndex) => refreshCoinsNative(accountIndex);
28
29
int countOfCoins() => coinsCountNative();
30
31
CoinsInfoRow getCoin(int index) => coinNative(index).ref;
32
+
33
+void freezeCoin(int index) => freezeCoinNative(index);
34
+
35
+void thawCoin(int index) => thawCoinNative(index);
cw_monero/lib/api/signatures.dart
+7
@@ -1,6 +1,7 @@
1
import 'dart:ffi';
2
import 'package:cw_monero/api/structs/coins_info_row.dart';
3
import 'package:cw_monero/api/structs/pending_transaction.dart';
4
+import 'package:cw_monero/api/structs/transaction_info_row.dart';
5
import 'package:cw_monero/api/structs/ut8_box.dart';
6
import 'package:ffi/ffi.dart';
7
@@ -81,6 +82,8 @@ typedef account_set_label = Void Function(Int32 accountIndex, Pointer<Utf8> labe
82
83
typedef transactions_refresh = Void Function();
84
85
+typedef get_transaction = Pointer<TransactionInfoRow> Function(Pointer<Utf8> txId);
86
+
87
typedef get_tx_key = Pointer<Utf8>? Function(Pointer<Utf8> txId);
88
89
typedef transactions_count = Int64 Function();
@@ -139,3 +142,7 @@ typedef coins_count = Int64 Function();
142
// typedef coins_from_txid = Pointer<CoinsInfoRow> Function(Pointer<Utf8> txid);
143
144
typedef coin = Pointer<CoinsInfoRow> Function(Int32 index);
145
+
146
+typedef freeze_coin = Void Function(Int32 index);
147
+
148
+typedef thaw_coin = Void Function(Int32 index);
cw_monero/lib/api/transaction_history.dart
+16
-6
@@ -1,15 +1,16 @@
1
import 'dart:ffi';
2
+
3
import 'package:cw_monero/api/convert_utf8_to_string.dart';
4
+import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart';
5
+import 'package:cw_monero/api/monero_api.dart';
6
import 'package:cw_monero/api/monero_output.dart';
7
+import 'package:cw_monero/api/signatures.dart';
8
+import 'package:cw_monero/api/structs/pending_transaction.dart';
9
+import 'package:cw_monero/api/structs/transaction_info_row.dart';
10
import 'package:cw_monero/api/structs/ut8_box.dart';
11
+import 'package:cw_monero/api/types.dart';
12
import 'package:ffi/ffi.dart';
13
import 'package:flutter/foundation.dart';
7
-import 'package:cw_monero/api/signatures.dart';
8
-import 'package:cw_monero/api/types.dart';
9
-import 'package:cw_monero/api/monero_api.dart';
10
-import 'package:cw_monero/api/structs/transaction_info_row.dart';
11
-import 'package:cw_monero/api/structs/pending_transaction.dart';
12
-import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart';
14
15
final transactionsRefreshNative = moneroApi
16
.lookup<NativeFunction<transactions_refresh>>('transactions_refresh')
@@ -38,6 +39,10 @@ final transactionCommitNative = moneroApi
39
final getTxKeyNative =
40
moneroApi.lookup<NativeFunction<get_tx_key>>('get_tx_key').asFunction<GetTxKey>();
41
42
+final getTransactionNative = moneroApi
43
+ .lookup<NativeFunction<get_transaction>>('get_transaction')
44
+ .asFunction<GetTransaction>();
45
+
46
String getTxKey(String txId) {
47
final txIdPointer = txId.toNativeUtf8();
48
final keyPointer = getTxKeyNative(txIdPointer);
@@ -65,6 +70,11 @@ List<TransactionInfoRow> getAllTransactions() {
70
.toList();
71
}
72
73
+TransactionInfoRow getTransaction(String txId) {
74
+ final txIdPointer = txId.toNativeUtf8();
75
+ return getTransactionNative(txIdPointer).ref;
76
+}
77
+
78
PendingTransactionDescription createTransactionSync(
79
{required String address,
80
required String paymentId,
cw_monero/lib/api/types.dart
+7
@@ -1,6 +1,7 @@
1
import 'dart:ffi';
2
import 'package:cw_monero/api/structs/coins_info_row.dart';
3
import 'package:cw_monero/api/structs/pending_transaction.dart';
4
+import 'package:cw_monero/api/structs/transaction_info_row.dart';
5
import 'package:cw_monero/api/structs/ut8_box.dart';
6
import 'package:ffi/ffi.dart';
7
@@ -81,6 +82,8 @@ typedef AccountSetLabel = void Function(int accountIndex, Pointer<Utf8> label);
82
83
typedef TransactionsRefresh = void Function();
84
85
+typedef GetTransaction = Pointer<TransactionInfoRow> Function(Pointer<Utf8> txId);
86
+
87
typedef GetTxKey = Pointer<Utf8>? Function(Pointer<Utf8> txId);
88
89
typedef TransactionsCount = int Function();
@@ -139,3 +142,7 @@ typedef RefreshCoins = void Function(int);
142
typedef CoinsCount = int Function();
143
144
typedef GetCoin = Pointer<CoinsInfoRow> Function(int);
145
+
146
+typedef FreezeCoin = void Function(int);
147
+
148
+typedef ThawCoin = void Function(int);
cw_monero/lib/monero_unspent.dart
+14
-22
@@ -1,28 +1,20 @@
1
+import 'package:cw_core/unspent_transaction_output.dart';
2
import 'package:cw_monero/api/structs/coins_info_row.dart';
3
3
-class MoneroUnspent {
4
- MoneroUnspent(this.address, this.hash, this.keyImage, this.value, this.isFrozen, this.isUnlocked)
5
- : isSending = true,
6
- note = '';
4
+class MoneroUnspent extends Unspent {
5
+ MoneroUnspent(
6
+ String address, String hash, String keyImage, int value, bool isFrozen, this.isUnlocked)
7
+ : super(address, hash, value, 0, keyImage) {
8
+ this.isFrozen = isFrozen;
9
+ }
10
8
- MoneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow)
9
- : address = coinsInfoRow.getAddress(),
10
- hash = coinsInfoRow.getHash(),
11
- keyImage = coinsInfoRow.getKeyImage(),
12
- value = coinsInfoRow.amount,
13
- isFrozen = coinsInfoRow.frozen == 1,
14
- isUnlocked = coinsInfoRow.unlocked == 1,
15
- isSending = true,
16
- note = '';
17
-
18
- final String address;
19
- final String hash;
20
- final String keyImage;
21
- final int value;
11
+ factory MoneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => MoneroUnspent(
12
+ coinsInfoRow.getAddress(),
13
+ coinsInfoRow.getHash(),
14
+ coinsInfoRow.getKeyImage(),
15
+ coinsInfoRow.amount,
16
+ coinsInfoRow.frozen == 1,
17
+ coinsInfoRow.unlocked == 1);
18
19
final bool isUnlocked;
24
-
25
- bool isFrozen;
26
- bool isSending;
27
- String note;
20
}
cw_monero/lib/monero_wallet.dart
+20
-11
@@ -1,5 +1,6 @@
1
import 'dart:async';
2
import 'dart:io';
3
+
4
import 'package:cw_core/account.dart';
5
import 'package:cw_core/crypto_currency.dart';
6
import 'package:cw_core/monero_amount_format.dart';
@@ -22,14 +23,14 @@ import 'package:cw_monero/api/transaction_history.dart' as transaction_history;
23
import 'package:cw_monero/api/wallet.dart' as monero_wallet;
24
import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart';
25
import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart';
25
-import 'package:cw_monero/pending_monero_transaction.dart';
26
import 'package:cw_monero/monero_transaction_creation_credentials.dart';
27
import 'package:cw_monero/monero_transaction_history.dart';
28
import 'package:cw_monero/monero_transaction_info.dart';
29
import 'package:cw_monero/monero_unspent.dart';
30
import 'package:cw_monero/monero_wallet_addresses.dart';
31
-import 'package:mobx/mobx.dart';
31
+import 'package:cw_monero/pending_monero_transaction.dart';
32
import 'package:hive/hive.dart';
33
+import 'package:mobx/mobx.dart';
34
35
part 'monero_wallet.g.dart';
36
@@ -204,7 +205,7 @@ abstract class MoneroWalletBase
205
for (final utx in unspentCoins) {
206
if (utx.isSending) {
207
allInputsAmount += utx.value;
207
- inputs.add(utx.keyImage);
208
+ inputs.add(utx.keyImage!);
209
}
210
}
211
final spendAllCoins = inputs.length == unspentCoins.length;
@@ -395,7 +396,9 @@ abstract class MoneroWalletBase
396
for (var i = 0; i < coinCount; i++) {
397
final coin = getCoin(i);
398
if (coin.spent == 0) {
398
- unspentCoins.add(MoneroUnspent.fromCoinsInfoRow(coin));
399
+ final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
400
+ unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1;
401
+ unspentCoins.add(unspent);
402
}
403
}
404
@@ -406,8 +409,10 @@ abstract class MoneroWalletBase
409
410
if (unspentCoins.isNotEmpty) {
411
unspentCoins.forEach((coin) {
409
- final coinInfoList = unspentCoinsInfo.values
410
- .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
412
+ final coinInfoList = unspentCoinsInfo.values.where((element) =>
413
+ element.walletId.contains(id) &&
414
+ element.accountIndex == walletAddresses.account!.id &&
415
+ element.keyImage!.contains(coin.keyImage!));
416
417
if (coinInfoList.isNotEmpty) {
418
final coinInfo = coinInfoList.first;
@@ -435,7 +440,9 @@ abstract class MoneroWalletBase
440
address: coin.address,
441
value: coin.value,
442
vout: 0,
438
- keyImage: coin.keyImage);
443
+ keyImage: coin.keyImage,
444
+ isChange: coin.isChange,
445
+ accountIndex: walletAddresses.account!.id);
446
447
await unspentCoinsInfo.add(newInfo);
448
}
@@ -443,12 +450,13 @@ abstract class MoneroWalletBase
450
Future<void> _refreshUnspentCoinsInfo() async {
451
try {
452
final List<dynamic> keys = <dynamic>[];
446
- final currentWalletUnspentCoins =
447
- unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
453
+ final currentWalletUnspentCoins = unspentCoinsInfo.values.where((element) =>
454
+ element.walletId.contains(id) && element.accountIndex == walletAddresses.account!.id);
455
456
if (currentWalletUnspentCoins.isNotEmpty) {
457
currentWalletUnspentCoins.forEach((element) {
451
- final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
458
+ final existUnspentCoins =
459
+ unspentCoins.where((coin) => element.keyImage!.contains(coin.keyImage!));
460
461
if (existUnspentCoins.isEmpty) {
462
keys.add(element.key);
@@ -566,7 +574,8 @@ abstract class MoneroWalletBase
574
int _getFrozenBalance() {
575
var frozenBalance = 0;
576
569
- for (var coin in unspentCoinsInfo.values) {
577
+ for (var coin in unspentCoinsInfo.values.where((element) =>
578
+ element.walletId == id && element.accountIndex == walletAddresses.account!.id)) {
579
if (coin.isFrozen) frozenBalance += coin.value;
580
}
581
lib/monero/cw_monero.dart
+1
-4
@@ -321,10 +321,7 @@ class CWMonero extends Monero {
321
@override
322
List<Unspent> getUnspents(Object wallet) {
323
final moneroWallet = wallet as MoneroWallet;
324
- return moneroWallet.unspentCoins
325
- .map((MoneroUnspent moneroUnspent) => Unspent(moneroUnspent.address, moneroUnspent.hash,
326
- moneroUnspent.value, 0, moneroUnspent.keyImage))
327
- .toList();
324
+ return moneroWallet.unspentCoins;
325
}
326
327
@override
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
+15
-66
@@ -1,15 +1,13 @@
1
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
2
+import 'package:cake_wallet/generated/i18n.dart';
3
import 'package:cake_wallet/routes.dart';
4
+import 'package:cake_wallet/src/screens/base_page.dart';
5
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
4
-import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
5
-import 'package:cake_wallet/utils/show_pop_up.dart';
6
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
7
import 'package:cw_core/wallet_type.dart';
8
-import 'package:flutter/material.dart';
8
import 'package:flutter/cupertino.dart';
10
-import 'package:cake_wallet/src/screens/base_page.dart';
9
+import 'package:flutter/material.dart';
10
import 'package:flutter_mobx/flutter_mobx.dart';
12
-import 'package:cake_wallet/generated/i18n.dart';
11
12
class UnspentCoinsListPage extends BasePage {
13
UnspentCoinsListPage({required this.unspentCoinsListViewModel});
@@ -17,31 +15,10 @@ class UnspentCoinsListPage extends BasePage {
15
@override
16
String get title => S.current.unspent_coins_title;
17
20
- //@override
21
- //Widget trailing(BuildContext context) {
22
- // final questionImage = Image.asset('assets/images/question_mark.png',
23
- // color: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
24
-
25
- // return SizedBox(
26
- // height: 20.0,
27
- // width: 20.0,
28
- // child: ButtonTheme(
29
- // minWidth: double.minPositive,
30
- // child: FlatButton(
31
- // highlightColor: Colors.transparent,
32
- // splashColor: Colors.transparent,
33
- // padding: EdgeInsets.all(0),
34
- // onPressed: () => showUnspentCoinsAlert(context),
35
- // child: questionImage),
36
- // ),
37
- // );
38
- //}
39
-
18
final UnspentCoinsListViewModel unspentCoinsListViewModel;
19
20
@override
43
- Widget body(BuildContext context) =>
44
- UnspentCoinsListForm(unspentCoinsListViewModel);
21
+ Widget body(BuildContext context) => UnspentCoinsListForm(unspentCoinsListViewModel);
22
}
23
24
class UnspentCoinsListForm extends StatefulWidget {
@@ -50,8 +27,7 @@ class UnspentCoinsListForm extends StatefulWidget {
27
final UnspentCoinsListViewModel unspentCoinsListViewModel;
28
29
@override
53
- UnspentCoinsListFormState createState() =>
54
- UnspentCoinsListFormState(unspentCoinsListViewModel);
30
+ UnspentCoinsListFormState createState() => UnspentCoinsListFormState(unspentCoinsListViewModel);
31
}
32
33
class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
@@ -59,16 +35,6 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
35
36
final UnspentCoinsListViewModel unspentCoinsListViewModel;
37
62
- @override
63
- void initState() {
64
- super.initState();
65
- WidgetsBinding.instance.addPostFrameCallback(afterLayout);
66
- }
67
-
68
- void afterLayout(dynamic _) {
69
- //showUnspentCoinsAlert(context);
70
- }
71
-
38
@override
39
Widget build(BuildContext context) {
40
return Container(
@@ -76,8 +42,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
42
child: Observer(
43
builder: (_) => ListView.separated(
44
itemCount: unspentCoinsListViewModel.items.length,
79
- separatorBuilder: (_, __) =>
80
- SizedBox(height: 15),
45
+ separatorBuilder: (_, __) => SizedBox(height: 15),
46
itemBuilder: (_, int index) {
47
return Observer(builder: (_) {
48
final item = unspentCoinsListViewModel.items[index];
@@ -86,38 +51,22 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
51
: item.address;
52
53
return GestureDetector(
89
- onTap: () =>
90
- Navigator.of(context)
91
- .pushNamed(Routes.unspentCoinsDetails,
92
- arguments: [item, unspentCoinsListViewModel]),
54
+ onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsDetails,
55
+ arguments: [item, unspentCoinsListViewModel]),
56
child: UnspentCoinsListItem(
57
note: item.note,
58
amount: item.amount,
59
address: address,
60
isSending: item.isSending,
61
isFrozen: item.isFrozen,
62
+ isChange: item.isChange,
63
onCheckBoxTap: item.isFrozen
100
- ? null
101
- : () async {
102
- item.isSending = !item.isSending;
103
- await unspentCoinsListViewModel
104
- .saveUnspentCoinInfo(item);}));
64
+ ? null
65
+ : () async {
66
+ item.isSending = !item.isSending;
67
+ await unspentCoinsListViewModel.saveUnspentCoinInfo(item);
68
+ }));
69
});
106
- }
107
- )
108
- )
109
- );
70
+ })));
71
}
72
}
112
-
113
-void showUnspentCoinsAlert(BuildContext context) {
114
- showPopUp<void>(
115
- context: context,
116
- builder: (BuildContext context) {
117
- return AlertWithOneAction(
118
- alertTitle: '',
119
- alertContent: 'Information about unspent coins',
120
- buttonText: S.of(context).ok,
121
- buttonAction: () => Navigator.of(context).pop());
122
- });
123
-}
\ No newline at end of file
lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart
+32
-14
@@ -1,8 +1,8 @@
1
import 'package:auto_size_text/auto_size_text.dart';
2
+import 'package:cake_wallet/generated/i18n.dart';
3
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
4
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
5
import 'package:flutter/material.dart';
5
-import 'package:cake_wallet/generated/i18n.dart';
6
7
class UnspentCoinsListItem extends StatelessWidget {
8
UnspentCoinsListItem({
@@ -11,6 +11,7 @@ class UnspentCoinsListItem extends StatelessWidget {
11
required this.address,
12
required this.isSending,
13
required this.isFrozen,
14
+ required this.isChange,
15
this.onCheckBoxTap,
16
});
17
@@ -19,6 +20,7 @@ class UnspentCoinsListItem extends StatelessWidget {
20
final String address;
21
final bool isSending;
22
final bool isFrozen;
23
+ final bool isChange;
24
final Function()? onCheckBoxTap;
25
26
@override
@@ -27,9 +29,8 @@ class UnspentCoinsListItem extends StatelessWidget {
29
final selectedItemColor = Theme.of(context).primaryColor;
30
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
31
30
- final amountColor = isSending
31
- ? Colors.white
32
- : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
32
+ final amountColor =
33
+ isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
34
final addressColor = isSending
35
? Colors.white.withOpacity(0.5)
36
: Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor;
@@ -47,7 +48,8 @@ class UnspentCoinsListItem extends StatelessWidget {
48
child: StandardCheckbox(
49
iconColor: amountColor,
50
borderColor: addressColor,
50
- value: isSending, onChanged: (value) => onCheckBoxTap?.call())),
51
+ value: isSending,
52
+ onChanged: (value) => onCheckBoxTap?.call())),
53
Expanded(
54
child: Column(
55
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -57,9 +59,7 @@ class UnspentCoinsListItem extends StatelessWidget {
59
mainAxisAlignment: MainAxisAlignment.spaceBetween,
60
crossAxisAlignment: CrossAxisAlignment.start,
61
children: [
60
- Column(
61
- crossAxisAlignment: CrossAxisAlignment.start,
62
- children: [
62
+ Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
63
if (note.isNotEmpty)
64
AutoSizeText(
65
note,
@@ -69,8 +69,8 @@ class UnspentCoinsListItem extends StatelessWidget {
69
),
70
AutoSizeText(
71
amount,
72
- style:
73
- TextStyle(color: amountColor, fontSize: 15, fontWeight: FontWeight.w600),
72
+ style: TextStyle(
73
+ color: amountColor, fontSize: 15, fontWeight: FontWeight.w600),
74
maxLines: 1,
75
)
76
]),
@@ -84,23 +84,41 @@ class UnspentCoinsListItem extends StatelessWidget {
84
alignment: Alignment.center,
85
child: Text(
86
S.of(context).frozen,
87
- style:
88
- TextStyle(color: amountColor, fontSize: 7, fontWeight: FontWeight.w600),
89
- ))
87
+ style: TextStyle(
88
+ color: amountColor, fontSize: 7, fontWeight: FontWeight.w600),
89
+ )),
90
],
91
),
92
Expanded(
93
child: Row(
94
crossAxisAlignment: CrossAxisAlignment.center,
95
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
96
children: [
97
AutoSizeText(
97
- '${address.substring(0, 5)}...${address.substring(address.length-5)}', // ToDo: Maybe use address label
98
+ '${address.substring(0, 5)}...${address.substring(address.length - 5)}', // ToDo: Maybe use address label
99
style: TextStyle(
100
color: addressColor,
101
fontSize: 12,
102
),
103
maxLines: 1,
104
),
105
+ if (isChange)
106
+ Container(
107
+ height: 17,
108
+ padding: EdgeInsets.only(left: 6, right: 6),
109
+ decoration: BoxDecoration(
110
+ borderRadius: BorderRadius.all(Radius.circular(8.5)),
111
+ color: Colors.white),
112
+ alignment: Alignment.center,
113
+ child: Text(
114
+ S.of(context).unspent_change,
115
+ style: TextStyle(
116
+ color: itemColor,
117
+ fontSize: 7,
118
+ fontWeight: FontWeight.w600,
119
+ ),
120
+ ),
121
+ ),
122
],
123
),
124
),
lib/view_model/dashboard/balance_view_model.dart
+6
-12
@@ -175,10 +175,8 @@ abstract class BalanceViewModelBase with Store {
175
return '---';
176
}
177
178
- return _getFiatBalance(
179
- price: price,
180
- cryptoAmount: getFormattedFrozenBalance(walletBalance)) + ' ' + fiatCurrency.toString();
181
-
178
+ return _getFiatBalance(price: price, cryptoAmount: getFormattedFrozenBalance(walletBalance)) +
179
+ ' ${fiatCurrency.toString()}';
180
}
181
182
@computed
@@ -201,10 +199,8 @@ abstract class BalanceViewModelBase with Store {
199
return '---';
200
}
201
204
- return _getFiatBalance(
205
- price: price,
206
- cryptoAmount: walletBalance.formattedAvailableBalance) + ' ' + fiatCurrency.toString();
207
-
202
+ return _getFiatBalance(price: price, cryptoAmount: walletBalance.formattedAvailableBalance) +
203
+ ' ${fiatCurrency.toString()}';
204
}
205
206
@computed
@@ -216,10 +212,8 @@ abstract class BalanceViewModelBase with Store {
212
return '---';
213
}
214
219
- return _getFiatBalance(
220
- price: price,
221
- cryptoAmount: walletBalance.formattedAdditionalBalance) + ' ' + fiatCurrency.toString();
222
-
215
+ return _getFiatBalance(price: price, cryptoAmount: walletBalance.formattedAdditionalBalance) +
216
+ ' ${fiatCurrency.toString()}';
217
}
218
219
@computed
lib/view_model/unspent_coins/unspent_coins_item.dart
+4
@@ -12,6 +12,7 @@ abstract class UnspentCoinsItemBase with Store {
12
required this.isFrozen,
13
required this.note,
14
required this.isSending,
15
+ required this.isChange,
16
required this.amountRaw,
17
required this.vout,
18
required this.keyImage
@@ -35,6 +36,9 @@ abstract class UnspentCoinsItemBase with Store {
36
@observable
37
bool isSending;
38
39
+ @observable
40
+ bool isChange;
41
+
42
@observable
43
int amountRaw;
44
lib/view_model/unspent_coins/unspent_coins_list_view_model.dart
+20
-39
@@ -1,10 +1,8 @@
1
-import 'package:collection/collection.dart';
1
import 'package:cake_wallet/bitcoin/bitcoin.dart';
3
-import 'package:cw_core/unspent_transaction_output.dart';
2
import 'package:cake_wallet/monero/monero.dart';
3
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
4
import 'package:cw_core/unspent_coins_info.dart';
7
-import 'package:cw_core/wallet_addresses.dart';
5
+import 'package:cw_core/unspent_transaction_output.dart';
6
import 'package:cw_core/wallet_base.dart';
7
import 'package:cw_core/wallet_type.dart';
8
import 'package:hive/hive.dart';
@@ -26,66 +24,49 @@ abstract class UnspentCoinsListViewModelBase with Store {
24
25
@computed
26
ObservableList<UnspentCoinsItem> get items => ObservableList.of(_getUnspents().map((elem) {
29
- final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}';
30
-
27
final info =
28
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
29
30
return UnspentCoinsItem(
31
address: elem.address,
36
- amount: amount,
32
+ amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
33
hash: elem.hash,
38
- isFrozen: info?.isFrozen ?? false,
39
- note: info?.note ?? '',
40
- isSending: info?.isSending ?? true,
34
+ isFrozen: info.isFrozen,
35
+ note: info.note,
36
+ isSending: info.isSending,
37
amountRaw: elem.value,
38
vout: elem.vout,
43
- keyImage: elem.keyImage);
39
+ keyImage: elem.keyImage,
40
+ isChange: elem.isChange,
41
+ );
42
}));
43
44
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
45
try {
46
final info =
47
getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
50
- if (info == null) {
51
- final newInfo = UnspentCoinsInfo(
52
- walletId: wallet.id,
53
- hash: item.hash,
54
- address: item.address,
55
- value: item.amountRaw,
56
- vout: item.vout,
57
- isFrozen: item.isFrozen,
58
- isSending: item.isSending,
59
- noteRaw: item.note,
60
- keyImage: item.keyImage);
48
62
- await _unspentCoinsInfo.add(newInfo);
63
- _updateUnspents();
64
- wallet.updateBalance();
65
- return;
66
- }
49
info.isFrozen = item.isFrozen;
50
info.isSending = item.isSending;
51
info.note = item.note;
52
53
await info.save();
72
- _updateUnspents();
73
- wallet.updateBalance();
54
+ await _updateUnspents();
55
+ await wallet.updateBalance();
56
} catch (e) {
57
print(e.toString());
58
}
59
}
60
79
- UnspentCoinsInfo? getUnspentCoinInfo(
80
- String hash, String address, int value, int vout, String? keyImage) {
81
- return _unspentCoinsInfo.values.firstWhereOrNull((element) =>
82
- element.walletId == wallet.id &&
83
- element.hash == hash &&
84
- element.address == address &&
85
- element.value == value &&
86
- element.vout == vout &&
87
- element.keyImage == keyImage);
88
- }
61
+ UnspentCoinsInfo getUnspentCoinInfo(
62
+ String hash, String address, int value, int vout, String? keyImage) =>
63
+ _unspentCoinsInfo.values.firstWhere((element) =>
64
+ element.walletId == wallet.id &&
65
+ element.hash == hash &&
66
+ element.address == address &&
67
+ element.value == value &&
68
+ element.vout == vout &&
69
+ element.keyImage == keyImage);
70
71
String formatAmountToString(int fullBalance) {
72
if (wallet.type == WalletType.monero)
@@ -95,7 +76,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
76
return '';
77
}
78
98
- void _updateUnspents() {
79
+ Future<void> _updateUnspents() async {
80
if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet);
81
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
82
return bitcoin!.updateUnspents(wallet);
res/values/strings_ar.arb
+2
-1
@@ -726,5 +726,6 @@
726
"domain_looks_up": "ﻝﺎﺠﻤﻟﺍ ﺚﺤﺑ ﺕﺎﻴﻠﻤﻋ",
727
"require_for_exchanges_to_external_wallets": "ﺔﻴﺟﺭﺎﺧ ﻆﻓﺎﺤﻣ ﻰﻟﺇ ﺕﻻﺩﺎﺒﺘﻟﺍ ﺐﻠﻄﺘﺗ",
728
"camera_permission_is_required": ".ﺍﺮﻴﻣﺎﻜﻟﺍ ﻥﺫﺇ ﺏﻮﻠﻄﻣ",
729
- "switchToETHWallet": "ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ Ethereum ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ"
729
+ "switchToETHWallet": "ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ Ethereum ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ",
730
+ "unspent_change": "يتغير"
731
}
res/values/strings_bg.arb
+2
-1
@@ -722,5 +722,6 @@
722
"domain_looks_up": "Търсене на домейни",
723
"require_for_exchanges_to_external_wallets": "Изискване за обмен към външни портфейли",
724
"camera_permission_is_required": "Изисква се разрешение за камерата.\nМоля, активирайте го от настройките на приложението.",
725
- "switchToETHWallet": "Моля, преминете към портфейл Ethereum и опитайте отново"
725
+ "switchToETHWallet": "Моля, преминете към портфейл Ethereum и опитайте отново",
726
+ "unspent_change": "Промяна"
727
}
res/values/strings_cs.arb
+2
-1
@@ -722,5 +722,6 @@
722
"domain_looks_up": "Vyhledávání domén",
723
"require_for_exchanges_to_external_wallets": "Vyžadovat pro výměny do externích peněženek",
724
"camera_permission_is_required": "Vyžaduje se povolení fotoaparátu.\nPovolte jej v nastavení aplikace.",
725
- "switchToETHWallet": "Přejděte na peněženku Ethereum a zkuste to znovu"
725
+ "switchToETHWallet": "Přejděte na peněženku Ethereum a zkuste to znovu",
726
+ "unspent_change": "Změna"
727
}
res/values/strings_de.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Domain-Suchen",
731
"require_for_exchanges_to_external_wallets": "Erforderlich für den Umtausch in externe Wallets",
732
"camera_permission_is_required": "Eine Kameraerlaubnis ist erforderlich.\nBitte aktivieren Sie es in den App-Einstellungen.",
733
- "switchToETHWallet": "Bitte wechseln Sie zu einem Ethereum-Wallet und versuchen Sie es erneut"
733
+ "switchToETHWallet": "Bitte wechseln Sie zu einem Ethereum-Wallet und versuchen Sie es erneut",
734
+ "unspent_change": "Wechselgeld"
735
}
res/values/strings_en.arb
+2
-1
@@ -731,5 +731,6 @@
731
"domain_looks_up": "Domain lookups",
732
"require_for_exchanges_to_external_wallets": "Require for exchanges to external wallets",
733
"camera_permission_is_required": "Camera permission is required. \nPlease enable it from app settings.",
734
- "switchToETHWallet": "Please switch to an Ethereum wallet and try again"
734
+ "switchToETHWallet": "Please switch to an Ethereum wallet and try again",
735
+ "unspent_change": "Change"
736
}
res/values/strings_es.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Búsquedas de dominio",
731
"require_for_exchanges_to_external_wallets": "Requerido para intercambios a billeteras externas",
732
"camera_permission_is_required": "Se requiere permiso de la cámara.\nHabilítelo desde la configuración de la aplicación.",
733
- "switchToETHWallet": "Cambie a una billetera Ethereum e inténtelo nuevamente."
733
+ "switchToETHWallet": "Cambie a una billetera Ethereum e inténtelo nuevamente.",
734
+ "unspent_change": "Cambiar"
735
}
res/values/strings_fr.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Recherches de domaine",
731
"require_for_exchanges_to_external_wallets": "Exiger pour les échanges vers des portefeuilles externes",
732
"camera_permission_is_required": "L'autorisation de la caméra est requise.\nVeuillez l'activer à partir des paramètres de l'application.",
733
- "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer"
733
+ "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer",
734
+ "unspent_change": "Changement"
735
}
res/values/strings_ha.arb
+2
-1
@@ -708,5 +708,6 @@
708
"domain_looks_up": "Binciken yanki",
709
"require_for_exchanges_to_external_wallets": "Bukatar musanya zuwa wallet na waje",
710
"camera_permission_is_required": "Ana buƙatar izinin kyamara.\nDa fatan za a kunna shi daga saitunan app.",
711
- "switchToETHWallet": "Da fatan za a canza zuwa walat ɗin Ethereum kuma a sake gwadawa"
711
+ "switchToETHWallet": "Da fatan za a canza zuwa walat ɗin Ethereum kuma a sake gwadawa",
712
+ "unspent_change": "Canza"
713
}
res/values/strings_hi.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "डोमेन लुकअप",
731
"require_for_exchanges_to_external_wallets": "बाहरी वॉलेट में एक्सचेंज की आवश्यकता है",
732
"camera_permission_is_required": "कैमरे की अनुमति आवश्यक है.\nकृपया इसे ऐप सेटिंग से सक्षम करें।",
733
- "switchToETHWallet": "कृपया एथेरियम वॉलेट पर स्विच करें और पुनः प्रयास करें"
733
+ "switchToETHWallet": "कृपया एथेरियम वॉलेट पर स्विच करें और पुनः प्रयास करें",
734
+ "unspent_change": "परिवर्तन"
735
}
res/values/strings_hr.arb
+2
-1
@@ -728,5 +728,6 @@
728
"domain_looks_up": "Pretraga domena",
729
"require_for_exchanges_to_external_wallets": "Zahtijeva razmjene na vanjske novčanike",
730
"camera_permission_is_required": "Potrebno je dopuštenje kamere.\nOmogućite ga u postavkama aplikacije.",
731
- "switchToETHWallet": "Prijeđite na Ethereum novčanik i pokušajte ponovno"
731
+ "switchToETHWallet": "Prijeđite na Ethereum novčanik i pokušajte ponovno",
732
+ "unspent_change": "Promijeniti"
733
}
res/values/strings_id.arb
+2
-1
@@ -718,5 +718,6 @@
718
"domain_looks_up": "Pencarian domain",
719
"require_for_exchanges_to_external_wallets": "Memerlukan pertukaran ke dompet eksternal",
720
"camera_permission_is_required": "Izin kamera diperlukan.\nSilakan aktifkan dari pengaturan aplikasi.",
721
- "switchToETHWallet": "Silakan beralih ke dompet Ethereum dan coba lagi"
721
+ "switchToETHWallet": "Silakan beralih ke dompet Ethereum dan coba lagi",
722
+ "unspent_change": "Mengubah"
723
}
res/values/strings_it.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Ricerche di domini",
731
"require_for_exchanges_to_external_wallets": "Richiede scambi con portafogli esterni",
732
"camera_permission_is_required": "È richiesta l'autorizzazione della fotocamera.\nAbilitalo dalle impostazioni dell'app.",
733
- "switchToETHWallet": "Passa a un portafoglio Ethereum e riprova"
733
+ "switchToETHWallet": "Passa a un portafoglio Ethereum e riprova",
734
+ "unspent_change": "Modifica"
735
}
res/values/strings_ja.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "ドメイン検索",
731
"require_for_exchanges_to_external_wallets": "外部ウォレットへの交換に必要",
732
"camera_permission_is_required": "カメラの許可が必要です。\nアプリの設定から有効にしてください。",
733
- "switchToETHWallet": "イーサリアムウォレットに切り替えてもう一度お試しください"
733
+ "switchToETHWallet": "イーサリアムウォレットに切り替えてもう一度お試しください",
734
+ "unspent_change": "変化"
735
}
res/values/strings_ko.arb
+2
-1
@@ -728,5 +728,6 @@
728
"domain_looks_up": "도메인 조회",
729
"require_for_exchanges_to_external_wallets": "외부 지갑으로의 교환을 위해 필요",
730
"camera_permission_is_required": "카메라 권한이 필요합니다.\n앱 설정에서 활성화해 주세요.",
731
- "switchToETHWallet": "이더리움 지갑으로 전환한 후 다시 시도해 주세요."
731
+ "switchToETHWallet": "이더리움 지갑으로 전환한 후 다시 시도해 주세요.",
732
+ "unspent_change": "변화"
733
}
res/values/strings_my.arb
+2
-1
@@ -728,5 +728,6 @@
728
"domain_looks_up": "ဒိုမိန်းရှာဖွေမှုများ",
729
"require_for_exchanges_to_external_wallets": "ပြင်ပပိုက်ဆံအိတ်များသို့ လဲလှယ်ရန် လိုအပ်သည်။",
730
"camera_permission_is_required": "ကင်မရာခွင့်ပြုချက် လိုအပ်ပါသည်။\nအက်ပ်ဆက်တင်များမှ ၎င်းကိုဖွင့်ပါ။",
731
- "switchToETHWallet": "ကျေးဇူးပြု၍ Ethereum ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ။"
731
+ "switchToETHWallet": "ကျေးဇူးပြု၍ Ethereum ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ။",
732
+ "unspent_change": "ပေြာင်းလဲခြင်း"
733
}
res/values/strings_nl.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Domein opzoeken",
731
"require_for_exchanges_to_external_wallets": "Vereist voor uitwisselingen naar externe portemonnees",
732
"camera_permission_is_required": "Cameratoestemming is vereist.\nSchakel dit in via de app-instellingen.",
733
- "switchToETHWallet": "Schakel over naar een Ethereum-portemonnee en probeer het opnieuw"
733
+ "switchToETHWallet": "Schakel over naar een Ethereum-portemonnee en probeer het opnieuw",
734
+ "unspent_change": "Wijziging"
735
}
res/values/strings_pl.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Wyszukiwanie domen",
731
"require_for_exchanges_to_external_wallets": "Wymagaj wymiany na portfele zewnętrzne",
732
"camera_permission_is_required": "Wymagane jest pozwolenie na korzystanie z aparatu.\nWłącz tę funkcję w ustawieniach aplikacji.",
733
- "switchToETHWallet": "Przejdź na portfel Ethereum i spróbuj ponownie"
733
+ "switchToETHWallet": "Przejdź na portfel Ethereum i spróbuj ponownie",
734
+ "unspent_change": "Zmiana"
735
}
res/values/strings_pt.arb
+2
-1
@@ -729,5 +729,6 @@
729
"domain_looks_up": "Pesquisas de domínio",
730
"require_for_exchanges_to_external_wallets": "Exigir trocas para carteiras externas",
731
"camera_permission_is_required": "É necessária permissão da câmera.\nAtive-o nas configurações do aplicativo.",
732
- "switchToETHWallet": "Mude para uma carteira Ethereum e tente novamente"
732
+ "switchToETHWallet": "Mude para uma carteira Ethereum e tente novamente",
733
+ "unspent_change": "Mudar"
734
}
res/values/strings_ru.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Поиск доменов",
731
"require_for_exchanges_to_external_wallets": "Требовать обмена на внешние кошельки",
732
"camera_permission_is_required": "Требуется разрешение камеры.\nПожалуйста, включите его в настройках приложения.",
733
- "switchToETHWallet": "Пожалуйста, переключитесь на кошелек Ethereum и повторите попытку."
733
+ "switchToETHWallet": "Пожалуйста, переключитесь на кошелек Ethereum и повторите попытку.",
734
+ "unspent_change": "Изменять"
735
}
res/values/strings_th.arb
+2
-1
@@ -728,5 +728,6 @@
728
"domain_looks_up": "การค้นหาโดเมน",
729
"require_for_exchanges_to_external_wallets": "จำเป็นต้องแลกเปลี่ยนกับกระเป๋าเงินภายนอก",
730
"camera_permission_is_required": "ต้องได้รับอนุญาตจากกล้อง\nโปรดเปิดใช้งานจากการตั้งค่าแอป",
731
- "switchToETHWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงิน Ethereum แล้วลองอีกครั้ง"
731
+ "switchToETHWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงิน Ethereum แล้วลองอีกครั้ง",
732
+ "unspent_change": "เปลี่ยน"
733
}
res/values/strings_tl.arb
+2
-1
@@ -725,5 +725,6 @@
725
"domain_looks_up": "Mga paghahanap ng domain",
726
"require_for_exchanges_to_external_wallets": "Kinakailangan para sa mga palitan sa mga panlabas na wallet",
727
"camera_permission_is_required": "Kinakailangan ang pahintulot sa camera.\nMangyaring paganahin ito mula sa mga setting ng app.",
728
- "switchToETHWallet": "Mangyaring lumipat sa isang Ethereum wallet at subukang muli"
728
+ "switchToETHWallet": "Mangyaring lumipat sa isang Ethereum wallet at subukang muli",
729
+ "unspent_change": "Baguhin"
730
}
res/values/strings_tr.arb
+2
-1
@@ -728,5 +728,6 @@
728
"domain_looks_up": "Etki alanı aramaları",
729
"require_for_exchanges_to_external_wallets": "Harici cüzdanlara geçiş yapılmasını zorunlu kılın",
730
"camera_permission_is_required": "Kamera izni gereklidir.\nLütfen uygulama ayarlarından etkinleştirin.",
731
- "switchToETHWallet": "Lütfen bir Ethereum cüzdanına geçin ve tekrar deneyin"
731
+ "switchToETHWallet": "Lütfen bir Ethereum cüzdanına geçin ve tekrar deneyin",
732
+ "unspent_change": "Değiştirmek"
733
}
res/values/strings_uk.arb
+2
-1
@@ -730,5 +730,6 @@
730
"domain_looks_up": "Пошук доменів",
731
"require_for_exchanges_to_external_wallets": "Потрібен для обміну на зовнішні гаманці",
732
"camera_permission_is_required": "Потрібен дозвіл камери.\nУвімкніть його в налаштуваннях програми.",
733
- "switchToETHWallet": "Перейдіть на гаманець Ethereum і повторіть спробу"
733
+ "switchToETHWallet": "Перейдіть на гаманець Ethereum і повторіть спробу",
734
+ "unspent_change": "Зміна"
735
}
res/values/strings_ur.arb
+2
-1
@@ -722,5 +722,6 @@
722
"domain_looks_up": "ڈومین تلاش کرنا",
723
"require_for_exchanges_to_external_wallets": "۔ﮯﮨ ﺕﺭﻭﺮﺿ ﯽﮐ ﮯﻟﺩﺎﺒﺗ ﮟﯿﻣ ﮮﻮﭩﺑ ﯽﻧﻭﺮﯿﺑ",
724
"camera_permission_is_required": "۔ﮯﮨ ﺭﺎﮐﺭﺩ ﺕﺯﺎﺟﺍ ﯽﮐ ﮮﺮﻤﯿﮐ",
725
- "switchToETHWallet": "۔ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ Ethereum ﻡﺮﮐ ﮦﺍﺮﺑ"
725
+ "switchToETHWallet": "۔ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ Ethereum ﻡﺮﮐ ﮦﺍﺮﺑ",
726
+ "unspent_change": "تبدیل کریں"
727
}
res/values/strings_yo.arb
+2
-1
@@ -724,5 +724,6 @@
724
"domain_looks_up": "Awọn wiwa agbegbe",
725
"require_for_exchanges_to_external_wallets": "Beere fun awọn paṣipaarọ si awọn apamọwọ ita",
726
"camera_permission_is_required": "A nilo igbanilaaye kamẹra.\nJọwọ jeki o lati app eto.",
727
- "switchToETHWallet": "Jọwọ yipada si apamọwọ Ethereum ki o tun gbiyanju lẹẹkansi"
727
+ "switchToETHWallet": "Jọwọ yipada si apamọwọ Ethereum ki o tun gbiyanju lẹẹkansi",
728
+ "unspent_change": "Yipada"
729
}
res/values/strings_zh.arb
+2
-1
@@ -729,5 +729,6 @@
729
"domain_looks_up": "域名查找",
730
"require_for_exchanges_to_external_wallets": "需要兑换到外部钱包",
731
"camera_permission_is_required": "需要相机许可。\n请从应用程序设置中启用它。",
732
- "switchToETHWallet": "请切换到以太坊钱包并重试"
732
+ "switchToETHWallet": "请切换到以太坊钱包并重试",
733
+ "unspent_change": "改变"
734
}