CW-415 add coin control for xmr (#985)

* Added CoinsInfo to monero_api_cpp * Add struct on dart * Add struct on dart * Set coins value * CW-415 Use add-coin-to-monero branch * CW-415 Add get Coin and build Monero Deps using Docker * CW-415 Fix Typo * CW-415 add debug log info * CW-415 Add preferred key Images for coin control to Monero * CW-415 Fix generation * CW-415 Skip GA Cache Externals * CW-415 Skip GA Cache Externals * CW-415 Coin Control: remove Block Explorer for Monero, Add Tx hash, save note on field exit * CW-415 Coin Control: Throw Exception when all outputs are deselected * CW-415 Coin Control: Show Frozen Balance on Dashboard * CW-415 Coin Control: Show Frozen Balance on Dashboard * CW-415 Ignore cached Monero deps in Workflow * CW-415 Fix displaying frozen Balance * Use own Translator with http 1.1.0 * CW-415 Resolve requested Changes * CW-415 Resolve requested Changes * CW-415 Resolve requested Changes * CW-415 Apply requested Changes * CW-415 Apply requested Changes * CW-415 Ensure opening of UnspentCoinsInfo Box, even for Monero.com --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>

Godwin Asuquo committed Aug 24, 2023 at 16:54 UTC f6670c0236f1e5efaf124c9f2570df92d4563e46
56 files changed +1603 -564
.github/workflows/pr_test_build.yml
+1 -1
@@ -55,7 +55,7 @@ jobs:
55 /opt/android/cake_wallet/cw_monero/android/.cxx
56 /opt/android/cake_wallet/cw_monero/ios/External
57 /opt/android/cake_wallet/cw_shared_external/ios/External
58 - key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh') }}
58 + key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }}
59
60 - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
61 name: Generate Externals
cw_bitcoin/lib/bitcoin_transaction_no_inputs_exception.dart
+1 -1
@@ -1,4 +1,4 @@
1 class BitcoinTransactionNoInputsException implements Exception {
2 @override
3 - String toString() => 'Not enough inputs available';
3 + String toString() => 'Not enough inputs available. Please select more under Coin Control';
4 }
cw_core/lib/monero_balance.dart
+11 -4
@@ -2,24 +2,31 @@ import 'package:cw_core/balance.dart';
2 import 'package:cw_core/monero_amount_format.dart';
3
4 class MoneroBalance extends Balance {
5 - MoneroBalance({required this.fullBalance, required this.unlockedBalance})
5 + MoneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0})
6 : formattedFullBalance = moneroAmountToString(amount: fullBalance),
7 - formattedUnlockedBalance =
8 - moneroAmountToString(amount: unlockedBalance),
7 + formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance),
8 + frozenFormatted = moneroAmountToString(amount: frozenBalance),
9 super(unlockedBalance, fullBalance);
10
11 MoneroBalance.fromString(
12 {required this.formattedFullBalance,
13 - required this.formattedUnlockedBalance})
13 + required this.formattedUnlockedBalance,
14 + this.frozenFormatted = '0.0'})
15 : fullBalance = moneroParseAmount(amount: formattedFullBalance),
16 unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance),
17 + frozenBalance = moneroParseAmount(amount: frozenFormatted),
18 super(moneroParseAmount(amount: formattedUnlockedBalance),
19 moneroParseAmount(amount: formattedFullBalance));
20
21 final int fullBalance;
22 final int unlockedBalance;
23 + final int frozenBalance;
24 final String formattedFullBalance;
25 final String formattedUnlockedBalance;
26 + final String frozenFormatted;
27 +
28 + @override
29 + String get formattedFrozenBalance => frozenFormatted == '0.0' ? '' : frozenFormatted;
30
31 @override
32 String get formattedAvailableBalance => formattedUnlockedBalance;
cw_core/lib/unspent_coins_info.dart
+6 -1
@@ -13,7 +13,9 @@ class UnspentCoinsInfo extends HiveObject {
13 required this.noteRaw,
14 required this.address,
15 required this.vout,
16 - required this.value});
16 + required this.value,
17 + this.keyImage = null
18 + });
19
20 static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
21 static const boxName = 'Unspent';
@@ -43,6 +45,9 @@ class UnspentCoinsInfo extends HiveObject {
45 @HiveField(7, defaultValue: 0)
46 int vout;
47
48 + @HiveField(8, defaultValue: null)
49 + String? keyImage;
50 +
51 String get note => noteRaw ?? '';
52
53 set note(String value) => noteRaw = value;
cw_monero/ios/Classes/monero_api.cpp
+176 -4
@@ -6,6 +6,7 @@
6 #include <fstream>
7 #include <unistd.h>
8 #include <mutex>
9 +#include <list>
10 #include "thread"
11 #include "CwWalletListener.h"
12 #if __APPLE__
@@ -182,6 +183,62 @@ extern "C"
183 }
184 };
185
186 + struct CoinsInfoRow
187 + {
188 + uint64_t blockHeight;
189 + char *hash;
190 + uint64_t internalOutputIndex;
191 + uint64_t globalOutputIndex;
192 + bool spent;
193 + bool frozen;
194 + uint64_t spentHeight;
195 + uint64_t amount;
196 + bool rct;
197 + bool keyImageKnown;
198 + uint64_t pkIndex;
199 + uint32_t subaddrIndex;
200 + uint32_t subaddrAccount;
201 + char *address;
202 + char *addressLabel;
203 + char *keyImage;
204 + uint64_t unlockTime;
205 + bool unlocked;
206 + char *pubKey;
207 + bool coinbase;
208 + char *description;
209 +
210 + CoinsInfoRow(Monero::CoinsInfo *coinsInfo)
211 + {
212 + blockHeight = coinsInfo->blockHeight();
213 + std::string *hash_str = new std::string(coinsInfo->hash());
214 + hash = strdup(hash_str->c_str());
215 + internalOutputIndex = coinsInfo->internalOutputIndex();
216 + globalOutputIndex = coinsInfo->globalOutputIndex();
217 + spent = coinsInfo->spent();
218 + frozen = coinsInfo->frozen();
219 + spentHeight = coinsInfo->spentHeight();
220 + amount = coinsInfo->amount();
221 + rct = coinsInfo->rct();
222 + keyImageKnown = coinsInfo->keyImageKnown();
223 + pkIndex = coinsInfo->pkIndex();
224 + subaddrIndex = coinsInfo->subaddrIndex();
225 + subaddrAccount = coinsInfo->subaddrAccount();
226 + address = strdup(coinsInfo->address().c_str()) ;
227 + addressLabel = strdup(coinsInfo->addressLabel().c_str());
228 + keyImage = strdup(coinsInfo->keyImage().c_str());
229 + unlockTime = coinsInfo->unlockTime();
230 + unlocked = coinsInfo->unlocked();
231 + pubKey = strdup(coinsInfo->pubKey().c_str());
232 + coinbase = coinsInfo->coinbase();
233 + description = strdup(coinsInfo->description().c_str());
234 + }
235 +
236 + void setUnlocked(bool unlocked);
237 +
238 + };
239 +
240 + Monero::Coins *m_coins;
241 +
242 Monero::Wallet *m_wallet;
243 Monero::TransactionHistory *m_transaction_history;
244 MoneroWalletListener *m_listener;
@@ -189,6 +246,7 @@ extern "C"
246 Monero::SubaddressAccount *m_account;
247 uint64_t m_last_known_wallet_height;
248 uint64_t m_cached_syncing_blockchain_height = 0;
249 + std::list<Monero::CoinsInfo*> m_coins_info;
250 std::mutex store_lock;
251 bool is_storing = false;
252
@@ -224,6 +282,17 @@ extern "C"
282 {
283 m_subaddress = nullptr;
284 }
285 +
286 + m_coins_info = std::list<Monero::CoinsInfo*>();
287 +
288 + if (wallet != nullptr)
289 + {
290 + m_coins = wallet->coins();
291 + }
292 + else
293 + {
294 + m_coins = nullptr;
295 + }
296 }
297
298 Monero::Wallet *get_current_wallet()
@@ -487,9 +556,18 @@ extern "C"
556 }
557
558 bool transaction_create(char *address, char *payment_id, char *amount,
490 - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction)
559 + uint8_t priority_raw, uint32_t subaddr_account,
560 + char **preferred_inputs, uint32_t preferred_inputs_size,
561 + Utf8Box &error, PendingTransactionRaw &pendingTransaction)
562 {
563 nice(19);
564 +
565 + std::set<std::string> _preferred_inputs;
566 +
567 + for (int i = 0; i < preferred_inputs_size; i++) {
568 + _preferred_inputs.insert(std::string(*preferred_inputs));
569 + preferred_inputs++;
570 + }
571
572 auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
573 std::string _payment_id;
@@ -503,11 +581,11 @@ extern "C"
581 if (amount != nullptr)
582 {
583 uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount));
506 - transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account);
584 + transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs);
585 }
586 else
587 {
510 - transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional<uint64_t>(), m_wallet->defaultMixin(), priority, subaddr_account);
588 + transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional<uint64_t>(), m_wallet->defaultMixin(), priority, subaddr_account, {}, _preferred_inputs);
589 }
590
591 int status = transaction->status();
@@ -527,7 +605,9 @@ extern "C"
605 }
606
607 bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size,
530 - uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction)
608 + uint8_t priority_raw, uint32_t subaddr_account,
609 + char **preferred_inputs, uint32_t preferred_inputs_size,
610 + Utf8Box &error, PendingTransactionRaw &pendingTransaction)
611 {
612 nice(19);
613
@@ -541,6 +621,13 @@ extern "C"
621 amounts++;
622 }
623
624 + std::set<std::string> _preferred_inputs;
625 +
626 + for (int i = 0; i < preferred_inputs_size; i++) {
627 + _preferred_inputs.insert(std::string(*preferred_inputs));
628 + preferred_inputs++;
629 + }
630 +
631 auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
632 std::string _payment_id;
633 Monero::PendingTransaction *transaction;
@@ -800,6 +887,91 @@ extern "C"
887 return m_wallet->trustedDaemon();
888 }
889
890 + CoinsInfoRow* coin(int index)
891 + {
892 + if (index >= 0 && index < m_coins_info.size()) {
893 + std::list<Monero::CoinsInfo*>::iterator it = m_coins_info.begin();
894 + std::advance(it, index);
895 + Monero::CoinsInfo* element = *it;
896 + std::cout << "Element at index " << index << ": " << element << std::endl;
897 + return new CoinsInfoRow(element);
898 + } else {
899 + std::cout << "Invalid index." << std::endl;
900 + return nullptr; // Return a default value (nullptr) for invalid index
901 + }
902 + }
903 +
904 + void refresh_coins(uint32_t accountIndex)
905 + {
906 + m_coins_info.clear();
907 +
908 + m_coins->refresh();
909 + for (const auto i : m_coins->getAll()) {
910 + if (i->subaddrAccount() == accountIndex && !(i->spent())) {
911 + m_coins_info.push_back(i);
912 + }
913 + }
914 + }
915 +
916 + uint64_t coins_count()
917 + {
918 + return m_coins_info.size();
919 + }
920 +
921 + CoinsInfoRow** coins_from_account(uint32_t accountIndex)
922 + {
923 + std::vector<CoinsInfoRow*> matchingCoins;
924 +
925 + for (int i = 0; i < coins_count(); i++) {
926 + CoinsInfoRow* coinInfo = coin(i);
927 + if (coinInfo->subaddrAccount == accountIndex) {
928 + matchingCoins.push_back(coinInfo);
929 + }
930 + }
931 +
932 + CoinsInfoRow** result = new CoinsInfoRow*[matchingCoins.size()];
933 + std::copy(matchingCoins.begin(), matchingCoins.end(), result);
934 + return result;
935 + }
936 +
937 + CoinsInfoRow** coins_from_txid(const char* txid, size_t* count)
938 + {
939 + std::vector<CoinsInfoRow*> matchingCoins;
940 +
941 + for (int i = 0; i < coins_count(); i++) {
942 + CoinsInfoRow* coinInfo = coin(i);
943 + if (std::string(coinInfo->hash) == txid) {
944 + matchingCoins.push_back(coinInfo);
945 + }
946 + }
947 +
948 + *count = matchingCoins.size();
949 + CoinsInfoRow** result = new CoinsInfoRow*[*count];
950 + std::copy(matchingCoins.begin(), matchingCoins.end(), result);
951 + return result;
952 + }
953 +
954 + CoinsInfoRow** coins_from_key_image(const char** keyimages, size_t keyimageCount, size_t* count)
955 + {
956 + std::vector<CoinsInfoRow*> matchingCoins;
957 +
958 + for (int i = 0; i < coins_count(); i++) {
959 + CoinsInfoRow* coinsInfoRow = coin(i);
960 + for (size_t j = 0; j < keyimageCount; j++) {
961 + if (coinsInfoRow->keyImageKnown && std::string(coinsInfoRow->keyImage) == keyimages[j]) {
962 + matchingCoins.push_back(coinsInfoRow);
963 + break;
964 + }
965 + }
966 + }
967 +
968 + *count = matchingCoins.size();
969 + CoinsInfoRow** result = new CoinsInfoRow*[*count];
970 + std::copy(matchingCoins.begin(), matchingCoins.end(), result);
971 + return result;
972 + }
973 +
974 +
975 #ifdef __cplusplus
976 }
977 #endif
cw_monero/lib/api/coins_info.dart new
+23
@@ -0,0 +1,23 @@
1 +import 'dart:ffi';
2 +import 'package:cw_monero/api/signatures.dart';
3 +import 'package:cw_monero/api/structs/coins_info_row.dart';
4 +import 'package:cw_monero/api/types.dart';
5 +import 'package:cw_monero/api/monero_api.dart';
6 +
7 +final refreshCoinsNative = moneroApi
8 + .lookup<NativeFunction<refresh_coins>>('refresh_coins')
9 + .asFunction<RefreshCoins>();
10 +
11 +final coinsCountNative = moneroApi
12 + .lookup<NativeFunction<coins_count>>('coins_count')
13 + .asFunction<CoinsCount>();
14 +
15 +final coinNative = moneroApi
16 + .lookup<NativeFunction<coin>>('coin')
17 + .asFunction<GetCoin>();
18 +
19 +void refreshCoins(int accountIndex) => refreshCoinsNative(accountIndex);
20 +
21 +int countOfCoins() => coinsCountNative();
22 +
23 +CoinsInfoRow getCoin(int index) => coinNative(index).ref;
cw_monero/lib/api/signatures.dart
+19 -10
@@ -1,4 +1,5 @@
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/ut8_box.dart';
5 import 'package:ffi/ffi.dart';
@@ -9,8 +10,8 @@ typedef create_wallet = Int8 Function(
10 typedef restore_wallet_from_seed = Int8 Function(
11 Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
12
12 -typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>,
13 - Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
13 +typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
14 + Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
15
16 typedef is_wallet_exist = Int8 Function(Pointer<Utf8>);
17
@@ -63,8 +64,7 @@ typedef subaddrress_refresh = Void Function(Int32);
64
65 typedef subaddress_get_all = Pointer<Int64> Function();
66
66 -typedef subaddress_add_new = Void Function(
67 - Int32 accountIndex, Pointer<Utf8> label);
67 +typedef subaddress_add_new = Void Function(Int32 accountIndex, Pointer<Utf8> label);
68
69 typedef subaddress_set_label = Void Function(
70 Int32 accountIndex, Int32 addressIndex, Pointer<Utf8> label);
@@ -77,8 +77,7 @@ typedef account_get_all = Pointer<Int64> Function();
77
78 typedef account_add_new = Void Function(Pointer<Utf8> label);
79
80 -typedef account_set_label = Void Function(
81 - Int32 accountIndex, Pointer<Utf8> label);
80 +typedef account_set_label = Void Function(Int32 accountIndex, Pointer<Utf8> label);
81
82 typedef transactions_refresh = Void Function();
83
@@ -94,6 +93,8 @@ typedef transaction_create = Int8 Function(
93 Pointer<Utf8> amount,
94 Int8 priorityRaw,
95 Int32 subaddrAccount,
96 + Pointer<Pointer<Utf8>> preferredInputs,
97 + Int32 preferredInputsSize,
98 Pointer<Utf8Box> error,
99 Pointer<PendingTransactionRaw> pendingTransaction);
100
@@ -104,6 +105,8 @@ typedef transaction_create_mult_dest = Int8 Function(
105 Int32 size,
106 Int8 priorityRaw,
107 Int32 subaddrAccount,
108 + Pointer<Pointer<Utf8>> preferredInputs,
109 + Int32 preferredInputsSize,
110 Pointer<Utf8Box> error,
111 Pointer<PendingTransactionRaw> pendingTransaction);
112
@@ -123,10 +126,16 @@ typedef on_startup = Void Function();
126
127 typedef rescan_blockchain = Void Function();
128
126 -typedef get_subaddress_label = Pointer<Utf8> Function(
127 - Int32 accountIndex,
128 - Int32 addressIndex);
129 +typedef get_subaddress_label = Pointer<Utf8> Function(Int32 accountIndex, Int32 addressIndex);
130
131 typedef set_trusted_daemon = Void Function(Int8 trusted);
132
132 -typedef trusted_daemon = Int8 Function();
\ No newline at end of file
133 +typedef trusted_daemon = Int8 Function();
134 +
135 +typedef refresh_coins = Void Function(Int32 accountIndex);
136 +
137 +typedef coins_count = Int64 Function();
138 +
139 +// typedef coins_from_txid = Pointer<CoinsInfoRow> Function(Pointer<Utf8> txid);
140 +
141 +typedef coin = Pointer<CoinsInfoRow> Function(Int32 index);
cw_monero/lib/api/structs/coins_info_row.dart new
+73
@@ -0,0 +1,73 @@
1 +import 'dart:ffi';
2 +import 'package:ffi/ffi.dart';
3 +
4 +class CoinsInfoRow extends Struct {
5 + @Int64()
6 + external int blockHeight;
7 +
8 + external Pointer<Utf8> hash;
9 +
10 + @Uint64()
11 + external int internalOutputIndex;
12 +
13 + @Uint64()
14 + external int globalOutputIndex;
15 +
16 + @Int8()
17 + external int spent;
18 +
19 + @Int8()
20 + external int frozen;
21 +
22 + @Uint64()
23 + external int spentHeight;
24 +
25 + @Uint64()
26 + external int amount;
27 +
28 + @Int8()
29 + external int rct;
30 +
31 + @Int8()
32 + external int keyImageKnown;
33 +
34 + @Uint64()
35 + external int pkIndex;
36 +
37 + @Uint32()
38 + external int subaddrIndex;
39 +
40 + @Uint32()
41 + external int subaddrAccount;
42 +
43 + external Pointer<Utf8> address;
44 +
45 + external Pointer<Utf8> addressLabel;
46 +
47 + external Pointer<Utf8> keyImage;
48 +
49 + @Uint64()
50 + external int unlockTime;
51 +
52 + @Int8()
53 + external int unlocked;
54 +
55 + external Pointer<Utf8> pubKey;
56 +
57 + @Int8()
58 + external int coinbase;
59 +
60 + external Pointer<Utf8> description;
61 +
62 + String getHash() => hash.toDartString();
63 +
64 + String getAddress() => address.toDartString();
65 +
66 + String getAddressLabel() => addressLabel.toDartString();
67 +
68 + String getKeyImage() => keyImage.toDartString();
69 +
70 + String getPubKey() => pubKey.toDartString();
71 +
72 + String getDescription() => description.toDartString();
73 +}
cw_monero/lib/api/transaction_history.dart
+69 -32
@@ -35,9 +35,8 @@ final transactionCommitNative = moneroApi
35 .lookup<NativeFunction<transaction_commit>>('transaction_commit')
36 .asFunction<TransactionCommit>();
37
38 -final getTxKeyNative = moneroApi
39 - .lookup<NativeFunction<get_tx_key>>('get_tx_key')
40 - .asFunction<GetTxKey>();
38 +final getTxKeyNative =
39 + moneroApi.lookup<NativeFunction<get_tx_key>>('get_tx_key').asFunction<GetTxKey>();
40
41 String getTxKey(String txId) {
42 final txIdPointer = txId.toNativeUtf8();
@@ -71,10 +70,21 @@ PendingTransactionDescription createTransactionSync(
70 required String paymentId,
71 required int priorityRaw,
72 String? amount,
74 - int accountIndex = 0}) {
73 + int accountIndex = 0,
74 + List<String> preferredInputs = const []}) {
75 final addressPointer = address.toNativeUtf8();
76 final paymentIdPointer = paymentId.toNativeUtf8();
77 final amountPointer = amount != null ? amount.toNativeUtf8() : nullptr;
78 +
79 + final int preferredInputsSize = preferredInputs.length;
80 + final List<Pointer<Utf8>> preferredInputsPointers =
81 + preferredInputs.map((output) => output.toNativeUtf8()).toList();
82 + final Pointer<Pointer<Utf8>> preferredInputsPointerPointer = calloc(preferredInputsSize);
83 +
84 + for (int i = 0; i < preferredInputsSize; i++) {
85 + preferredInputsPointerPointer[i] = preferredInputsPointers[i];
86 + }
87 +
88 final errorMessagePointer = calloc<Utf8Box>();
89 final pendingTransactionRawPointer = calloc<PendingTransactionRaw>();
90 final created = transactionCreateNative(
@@ -83,10 +93,16 @@ PendingTransactionDescription createTransactionSync(
93 amountPointer,
94 priorityRaw,
95 accountIndex,
96 + preferredInputsPointerPointer,
97 + preferredInputsSize,
98 errorMessagePointer,
99 pendingTransactionRawPointer) !=
100 0;
101
102 + calloc.free(preferredInputsPointerPointer);
103 +
104 + preferredInputsPointers.forEach((element) => calloc.free(element));
105 +
106 calloc.free(addressPointer);
107 calloc.free(paymentIdPointer);
108
@@ -111,15 +127,16 @@ PendingTransactionDescription createTransactionSync(
127
128 PendingTransactionDescription createTransactionMultDestSync(
129 {required List<MoneroOutput> outputs,
114 - required String paymentId,
115 - required int priorityRaw,
116 - int accountIndex = 0}) {
130 + required String paymentId,
131 + required int priorityRaw,
132 + int accountIndex = 0,
133 + List<String> preferredInputs = const []}) {
134 final int size = outputs.length;
118 - final List<Pointer<Utf8>> addressesPointers = outputs.map((output) =>
119 - output.address.toNativeUtf8()).toList();
135 + final List<Pointer<Utf8>> addressesPointers =
136 + outputs.map((output) => output.address.toNativeUtf8()).toList();
137 final Pointer<Pointer<Utf8>> addressesPointerPointer = calloc(size);
121 - final List<Pointer<Utf8>> amountsPointers = outputs.map((output) =>
122 - output.amount.toNativeUtf8()).toList();
138 + final List<Pointer<Utf8>> amountsPointers =
139 + outputs.map((output) => output.amount.toNativeUtf8()).toList();
140 final Pointer<Pointer<Utf8>> amountsPointerPointer = calloc(size);
141
142 for (int i = 0; i < size; i++) {
@@ -127,25 +144,38 @@ PendingTransactionDescription createTransactionMultDestSync(
144 amountsPointerPointer[i] = amountsPointers[i];
145 }
146
147 + final int preferredInputsSize = preferredInputs.length;
148 + final List<Pointer<Utf8>> preferredInputsPointers =
149 + preferredInputs.map((output) => output.toNativeUtf8()).toList();
150 + final Pointer<Pointer<Utf8>> preferredInputsPointerPointer = calloc(preferredInputsSize);
151 +
152 + for (int i = 0; i < preferredInputsSize; i++) {
153 + preferredInputsPointerPointer[i] = preferredInputsPointers[i];
154 + }
155 +
156 final paymentIdPointer = paymentId.toNativeUtf8();
157 final errorMessagePointer = calloc<Utf8Box>();
158 final pendingTransactionRawPointer = calloc<PendingTransactionRaw>();
159 final created = transactionCreateMultDestNative(
134 - addressesPointerPointer,
135 - paymentIdPointer,
136 - amountsPointerPointer,
137 - size,
138 - priorityRaw,
139 - accountIndex,
140 - errorMessagePointer,
141 - pendingTransactionRawPointer) !=
160 + addressesPointerPointer,
161 + paymentIdPointer,
162 + amountsPointerPointer,
163 + size,
164 + priorityRaw,
165 + accountIndex,
166 + preferredInputsPointerPointer,
167 + preferredInputsSize,
168 + errorMessagePointer,
169 + pendingTransactionRawPointer) !=
170 0;
171
172 calloc.free(addressesPointerPointer);
173 calloc.free(amountsPointerPointer);
174 + calloc.free(preferredInputsPointerPointer);
175
176 addressesPointers.forEach((element) => calloc.free(element));
177 amountsPointers.forEach((element) => calloc.free(element));
178 + preferredInputsPointers.forEach((element) => calloc.free(element));
179
180 calloc.free(paymentIdPointer);
181
@@ -164,13 +194,12 @@ PendingTransactionDescription createTransactionMultDestSync(
194 pointerAddress: pendingTransactionRawPointer.address);
195 }
196
167 -void commitTransactionFromPointerAddress({required int address}) => commitTransaction(
168 - transactionPointer: Pointer<PendingTransactionRaw>.fromAddress(address));
197 +void commitTransactionFromPointerAddress({required int address}) =>
198 + commitTransaction(transactionPointer: Pointer<PendingTransactionRaw>.fromAddress(address));
199
200 void commitTransaction({required Pointer<PendingTransactionRaw> transactionPointer}) {
201 final errorMessagePointer = calloc<Utf8Box>();
172 - final isCommited =
173 - transactionCommitNative(transactionPointer, errorMessagePointer) != 0;
202 + final isCommited = transactionCommitNative(transactionPointer, errorMessagePointer) != 0;
203
204 if (!isCommited) {
205 final message = errorMessagePointer.ref.getValue();
@@ -185,13 +214,15 @@ PendingTransactionDescription _createTransactionSync(Map args) {
214 final amount = args['amount'] as String?;
215 final priorityRaw = args['priorityRaw'] as int;
216 final accountIndex = args['accountIndex'] as int;
217 + final preferredInputs = args['preferredInputs'] as List<String>;
218
219 return createTransactionSync(
220 address: address,
221 paymentId: paymentId,
222 amount: amount,
223 priorityRaw: priorityRaw,
194 - accountIndex: accountIndex);
224 + accountIndex: accountIndex,
225 + preferredInputs: preferredInputs);
226 }
227
228 PendingTransactionDescription _createTransactionMultDestSync(Map args) {
@@ -199,12 +230,14 @@ PendingTransactionDescription _createTransactionMultDestSync(Map args) {
230 final paymentId = args['paymentId'] as String;
231 final priorityRaw = args['priorityRaw'] as int;
232 final accountIndex = args['accountIndex'] as int;
233 + final preferredInputs = args['preferredInputs'] as List<String>;
234
235 return createTransactionMultDestSync(
236 outputs: outputs,
237 paymentId: paymentId,
238 priorityRaw: priorityRaw,
207 - accountIndex: accountIndex);
239 + accountIndex: accountIndex,
240 + preferredInputs: preferredInputs);
241 }
242
243 Future<PendingTransactionDescription> createTransaction(
@@ -212,23 +245,27 @@ Future<PendingTransactionDescription> createTransaction(
245 required int priorityRaw,
246 String? amount,
247 String paymentId = '',
215 - int accountIndex = 0}) =>
248 + int accountIndex = 0,
249 + List<String> preferredInputs = const []}) =>
250 compute(_createTransactionSync, {
251 'address': address,
252 'paymentId': paymentId,
253 'amount': amount,
254 'priorityRaw': priorityRaw,
221 - 'accountIndex': accountIndex
255 + 'accountIndex': accountIndex,
256 + 'preferredInputs': preferredInputs
257 });
258
259 Future<PendingTransactionDescription> createTransactionMultDest(
225 - {required List<MoneroOutput> outputs,
226 - required int priorityRaw,
227 - String paymentId = '',
228 - int accountIndex = 0}) =>
260 + {required List<MoneroOutput> outputs,
261 + required int priorityRaw,
262 + String paymentId = '',
263 + int accountIndex = 0,
264 + List<String> preferredInputs = const []}) =>
265 compute(_createTransactionMultDestSync, {
266 'outputs': outputs,
267 'paymentId': paymentId,
268 'priorityRaw': priorityRaw,
233 - 'accountIndex': accountIndex
269 + 'accountIndex': accountIndex,
270 + 'preferredInputs': preferredInputs
271 });
cw_monero/lib/api/types.dart
+12 -1
@@ -1,4 +1,5 @@
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/ut8_box.dart';
5 import 'package:ffi/ffi.dart';
@@ -92,6 +93,8 @@ typedef TransactionCreate = int Function(
93 Pointer<Utf8> amount,
94 int priorityRaw,
95 int subaddrAccount,
96 + Pointer<Pointer<Utf8>> preferredInputs,
97 + int preferredInputsSize,
98 Pointer<Utf8Box> error,
99 Pointer<PendingTransactionRaw> pendingTransaction);
100
@@ -102,6 +105,8 @@ typedef TransactionCreateMultDest = int Function(
105 int size,
106 int priorityRaw,
107 int subaddrAccount,
108 + Pointer<Pointer<Utf8>> preferredInputs,
109 + int preferredInputsSize,
110 Pointer<Utf8Box> error,
111 Pointer<PendingTransactionRaw> pendingTransaction);
112
@@ -127,4 +132,10 @@ typedef GetSubaddressLabel = Pointer<Utf8> Function(
132
133 typedef SetTrustedDaemon = void Function(int);
134
130 -typedef TrustedDaemon = int Function();
\ No newline at end of file
135 +typedef TrustedDaemon = int Function();
136 +
137 +typedef RefreshCoins = void Function(int);
138 +
139 +typedef CoinsCount = int Function();
140 +
141 +typedef GetCoin = Pointer<CoinsInfoRow> Function(int);
cw_monero/lib/exceptions/monero_transaction_creation_exception.dart renamed
cw_monero/lib/exceptions/monero_transaction_no_inputs_exception.dart new
+4
@@ -0,0 +1,4 @@
1 +class MoneroTransactionNoInputsException implements Exception {
2 + @override
3 + String toString() => 'Not enough inputs available. Please select more under Coin Control';
4 +}
cw_monero/lib/monero_subaddress_list.dart
+3 -1
@@ -1,6 +1,6 @@
1 -import 'package:cw_monero/api/structs/subaddress_row.dart';
1 import 'package:flutter/services.dart';
2 import 'package:mobx/mobx.dart';
3 +import 'package:cw_monero/api/coins_info.dart';
4 import 'package:cw_monero/api/subaddress_list.dart' as subaddress_list;
5 import 'package:cw_core/subaddress.dart';
6
@@ -22,6 +22,8 @@ abstract class MoneroSubaddressListBase with Store {
22 bool _isUpdating;
23
24 void update({required int accountIndex}) {
25 + refreshCoins(accountIndex);
26 +
27 if (_isUpdating) {
28 return;
29 }
cw_monero/lib/monero_unspent.dart new
+28
@@ -0,0 +1,28 @@
1 +import 'package:cw_monero/api/structs/coins_info_row.dart';
2 +
3 +class MoneroUnspent {
4 + MoneroUnspent(this.address, this.hash, this.keyImage, this.value, this.isFrozen, this.isUnlocked)
5 + : isSending = true,
6 + note = '';
7 +
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;
22 +
23 + final bool isUnlocked;
24 +
25 + bool isFrozen;
26 + bool isSending;
27 + String note;
28 +}
cw_monero/lib/monero_wallet.dart
+158 -43
@@ -1,33 +1,35 @@
1 import 'dart:async';
2 import 'dart:io';
3 -import 'package:cw_core/pathForWallet.dart';
4 -import 'package:cw_core/transaction_priority.dart';
3 +import 'package:cw_core/account.dart';
4 +import 'package:cw_core/crypto_currency.dart';
5 import 'package:cw_core/monero_amount_format.dart';
6 -import 'package:cw_monero/monero_transaction_creation_exception.dart';
7 -import 'package:cw_monero/monero_transaction_info.dart';
8 -import 'package:cw_monero/monero_wallet_addresses.dart';
6 +import 'package:cw_core/monero_balance.dart';
7 +import 'package:cw_core/monero_transaction_priority.dart';
8 +import 'package:cw_core/monero_wallet_keys.dart';
9 import 'package:cw_core/monero_wallet_utils.dart';
10 +import 'package:cw_core/node.dart';
11 +import 'package:cw_core/pathForWallet.dart';
12 +import 'package:cw_core/pending_transaction.dart';
13 +import 'package:cw_core/sync_status.dart';
14 +import 'package:cw_core/transaction_priority.dart';
15 +import 'package:cw_core/unspent_coins_info.dart';
16 +import 'package:cw_core/wallet_base.dart';
17 +import 'package:cw_core/wallet_info.dart';
18 +import 'package:cw_monero/api/coins_info.dart';
19 +import 'package:cw_monero/api/monero_output.dart';
20 import 'package:cw_monero/api/structs/pending_transaction.dart';
11 -import 'package:mobx/mobx.dart';
12 -import 'package:cw_monero/api/transaction_history.dart'
13 - as monero_transaction_history;
14 -import 'package:cw_monero/api/wallet.dart';
15 -import 'package:cw_monero/api/wallet.dart' as monero_wallet;
21 import 'package:cw_monero/api/transaction_history.dart' as transaction_history;
17 -import 'package:cw_monero/api/monero_output.dart';
18 -import 'package:cw_monero/monero_transaction_creation_credentials.dart';
22 +import 'package:cw_monero/api/wallet.dart' as monero_wallet;
23 +import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart';
24 +import 'package:cw_monero/exceptions/monero_transaction_no_inputs_exception.dart';
25 import 'package:cw_monero/pending_monero_transaction.dart';
20 -import 'package:cw_core/monero_wallet_keys.dart';
21 -import 'package:cw_core/monero_balance.dart';
26 +import 'package:cw_monero/monero_transaction_creation_credentials.dart';
27 import 'package:cw_monero/monero_transaction_history.dart';
23 -import 'package:cw_core/account.dart';
24 -import 'package:cw_core/pending_transaction.dart';
25 -import 'package:cw_core/wallet_base.dart';
26 -import 'package:cw_core/sync_status.dart';
27 -import 'package:cw_core/wallet_info.dart';
28 -import 'package:cw_core/node.dart';
29 -import 'package:cw_core/monero_transaction_priority.dart';
30 -import 'package:cw_core/crypto_currency.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';
32 +import 'package:hive/hive.dart';
33
34 part 'monero_wallet.g.dart';
35
@@ -37,37 +39,39 @@ class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
39
40 abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
41 MoneroTransactionHistory, MoneroTransactionInfo> with Store {
40 - MoneroWalletBase({required WalletInfo walletInfo})
42 + MoneroWalletBase({required WalletInfo walletInfo,
43 + required Box<UnspentCoinsInfo> unspentCoinsInfo})
44 : balance = ObservableMap<CryptoCurrency, MoneroBalance>.of({
42 - CryptoCurrency.xmr: MoneroBalance(
45 + CryptoCurrency.xmr: MoneroBalance(
46 fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
47 unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0))
45 - }),
48 + }),
49 _isTransactionUpdating = false,
50 _hasSyncAfterStartup = false,
51 walletAddresses = MoneroWalletAddresses(walletInfo),
52 syncStatus = NotConnectedSyncStatus(),
53 + unspentCoins = [],
54 + this.unspentCoinsInfo = unspentCoinsInfo,
55 super(walletInfo) {
56 transactionHistory = MoneroTransactionHistory();
52 - _onAccountChangeReaction = reaction((_) => walletAddresses.account,
53 - (Account? account) {
57 + _onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) {
58 if (account == null) {
59 return;
60 }
61
58 - balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(
59 - <CryptoCurrency, MoneroBalance>{
60 - currency: MoneroBalance(
62 + balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(<CryptoCurrency, MoneroBalance>{
63 + currency: MoneroBalance(
64 fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
62 - unlockedBalance:
63 - monero_wallet.getUnlockedBalance(accountIndex: account.id))
64 - });
65 + unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: account.id))
66 + });
67 walletAddresses.updateSubaddressList(accountIndex: account.id);
68 });
69 }
70
71 static const int _autoSaveInterval = 30;
72
73 + Box<UnspentCoinsInfo> unspentCoinsInfo;
74 +
75 @override
76 MoneroWalletAddresses walletAddresses;
77
@@ -89,11 +93,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
93 publicSpendKey: monero_wallet.getPublicSpendKey(),
94 publicViewKey: monero_wallet.getPublicViewKey());
95
92 - SyncListener? _listener;
96 + monero_wallet.SyncListener? _listener;
97 ReactionDisposer? _onAccountChangeReaction;
98 bool _isTransactionUpdating;
99 bool _hasSyncAfterStartup;
100 Timer? _autoSaveTimer;
101 + List<MoneroUnspent> unspentCoins;
102
103 Future<void> init() async {
104 await walletAddresses.init();
@@ -170,10 +175,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
175 @override
176 Future<PendingTransaction> createTransaction(Object credentials) async {
177 final _credentials = credentials as MoneroTransactionCreationCredentials;
178 + final inputs = <String>[];
179 final outputs = _credentials.outputs;
180 final hasMultiDestination = outputs.length > 1;
181 final unlockedBalance =
182 monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
183 + var allInputsAmount = 0;
184
185 PendingTransactionDescription pendingTransactionDescription;
186
@@ -181,6 +188,21 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
188 throw MoneroTransactionCreationException('The wallet is not synced.');
189 }
190
191 + if (unspentCoins.isEmpty) {
192 + await updateUnspent();
193 + }
194 +
195 + for (final utx in unspentCoins) {
196 + if (utx.isSending) {
197 + allInputsAmount += utx.value;
198 + inputs.add(utx.keyImage);
199 + }
200 + }
201 +
202 + if (inputs.isEmpty) {
203 + throw MoneroTransactionNoInputsException();
204 + }
205 +
206 if (hasMultiDestination) {
207 if (outputs.any((item) => item.sendAll
208 || (item.formattedCryptoAmount ?? 0) <= 0)) {
@@ -208,7 +230,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
230 await transaction_history.createTransactionMultDest(
231 outputs: moneroOutputs,
232 priorityRaw: _credentials.priority.serialize(),
211 - accountIndex: walletAddresses.account!.id);
233 + accountIndex: walletAddresses.account!.id,
234 + preferredInputs: inputs);
235 } else {
236 final output = outputs.first;
237 final address = output.isParsedAddress
@@ -229,12 +252,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
252 'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
253 }
254
232 - pendingTransactionDescription =
233 - await transaction_history.createTransaction(
255 + pendingTransactionDescription = await transaction_history.createTransaction(
256 address: address!,
257 amount: amount,
258 priorityRaw: _credentials.priority.serialize(),
237 - accountIndex: walletAddresses.account!.id);
259 + accountIndex: walletAddresses.account!.id,
260 + preferredInputs: inputs);
261 }
262
263 return PendingMoneroTransaction(pendingTransactionDescription);
@@ -354,6 +377,85 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
377 await walletInfo.save();
378 }
379
380 + Future<void> updateUnspent() async {
381 + refreshCoins(walletAddresses.account!.id);
382 +
383 + unspentCoins.clear();
384 +
385 + final coinCount = countOfCoins();
386 + for (var i = 0; i < coinCount; i++) {
387 + final coin = getCoin(i);
388 + if (coin.spent == 0) {
389 + unspentCoins.add(MoneroUnspent.fromCoinsInfoRow(coin));
390 + }
391 + }
392 +
393 + if (unspentCoinsInfo.isEmpty) {
394 + unspentCoins.forEach((coin) => _addCoinInfo(coin));
395 + return;
396 + }
397 +
398 + if (unspentCoins.isNotEmpty) {
399 + unspentCoins.forEach((coin) {
400 + final coinInfoList = unspentCoinsInfo.values.where((element) =>
401 + element.walletId.contains(id) && element.hash.contains(coin.hash));
402 +
403 + if (coinInfoList.isNotEmpty) {
404 + final coinInfo = coinInfoList.first;
405 +
406 + coin.isFrozen = coinInfo.isFrozen;
407 + coin.isSending = coinInfo.isSending;
408 + coin.note = coinInfo.note;
409 + } else {
410 + _addCoinInfo(coin);
411 + }
412 + });
413 + }
414 +
415 + await _refreshUnspentCoinsInfo();
416 + _askForUpdateBalance();
417 + }
418 +
419 + Future<void> _addCoinInfo(MoneroUnspent coin) async {
420 + final newInfo = UnspentCoinsInfo(
421 + walletId: id,
422 + hash: coin.hash,
423 + isFrozen: coin.isFrozen,
424 + isSending: coin.isSending,
425 + noteRaw: coin.note,
426 + address: coin.address,
427 + value: coin.value,
428 + vout: 0,
429 + keyImage: coin.keyImage
430 + );
431 +
432 + await unspentCoinsInfo.add(newInfo);
433 + }
434 +
435 + Future<void> _refreshUnspentCoinsInfo() async {
436 + try {
437 + final List<dynamic> keys = <dynamic>[];
438 + final currentWalletUnspentCoins = unspentCoinsInfo.values
439 + .where((element) => element.walletId.contains(id));
440 +
441 + if (currentWalletUnspentCoins.isNotEmpty) {
442 + currentWalletUnspentCoins.forEach((element) {
443 + final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
444 +
445 + if (existUnspentCoins.isEmpty) {
446 + keys.add(element.key);
447 + }
448 + });
449 + }
450 +
451 + if (keys.isNotEmpty) {
452 + await unspentCoinsInfo.deleteAll(keys);
453 + }
454 + } catch (e) {
455 + print(e.toString());
456 + }
457 + }
458 +
459 String getTransactionAddress(int accountIndex, int addressIndex) =>
460 monero_wallet.getAddress(
461 accountIndex: accountIndex,
@@ -361,7 +463,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
463
464 @override
465 Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
364 - monero_transaction_history.refreshTransactions();
466 + transaction_history.refreshTransactions();
467 return _getAllTransactions(null).fold<Map<String, MoneroTransactionInfo>>(
468 <String, MoneroTransactionInfo>{},
469 (Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
@@ -392,7 +494,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
494 }
495
496 List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
395 - monero_transaction_history
497 + transaction_history
498 .getAllTransations()
499 .map((row) => MoneroTransactionInfo.fromRow(row))
500 .toList();
@@ -407,7 +509,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
509 return;
510 }
511
410 - final currentHeight = getCurrentHeight();
512 + final currentHeight = monero_wallet.getCurrentHeight();
513
514 if (currentHeight <= 1) {
515 final height = _getHeightByDate(walletInfo.date);
@@ -439,11 +541,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
541 void _askForUpdateBalance() {
542 final unlockedBalance = _getUnlockedBalance();
543 final fullBalance = _getFullBalance();
544 + final frozenBalance = _getFrozenBalance();
545
546 if (balance[currency]!.fullBalance != fullBalance ||
444 - balance[currency]!.unlockedBalance != unlockedBalance) {
547 + balance[currency]!.unlockedBalance != unlockedBalance ||
548 + balance[currency]!.frozenBalance != frozenBalance) {
549 balance[currency] = MoneroBalance(
446 - fullBalance: fullBalance, unlockedBalance: unlockedBalance);
550 + fullBalance: fullBalance, unlockedBalance: unlockedBalance, frozenBalance: frozenBalance);
551 }
552 }
553
@@ -456,6 +560,17 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
560 int _getUnlockedBalance() =>
561 monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
562
563 + int _getFrozenBalance() {
564 + var frozenBalance = 0;
565 +
566 + for (var coin in unspentCoinsInfo.values) {
567 + if (coin.isFrozen)
568 + frozenBalance += coin.value;
569 + }
570 +
571 + return frozenBalance;
572 + }
573 +
574 void _onNewBlock(int height, int blocksLeft, double ptc) async {
575 try {
576 if (walletInfo.isRecovery) {
cw_monero/lib/monero_wallet_service.dart
+19 -14
@@ -1,16 +1,16 @@
1 import 'dart:io';
2 -import 'package:cw_core/wallet_base.dart';
2 import 'package:cw_core/monero_wallet_utils.dart';
4 -import 'package:hive/hive.dart';
5 -import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
6 -import 'package:cw_monero/api/wallet.dart' as monero_wallet;
7 -import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
8 -import 'package:cw_monero/monero_wallet.dart';
9 -import 'package:cw_core/wallet_credentials.dart';
10 -import 'package:cw_core/wallet_service.dart';
3 import 'package:cw_core/pathForWallet.dart';
4 +import 'package:cw_core/unspent_coins_info.dart';
5 +import 'package:cw_core/wallet_base.dart';
6 +import 'package:cw_core/wallet_credentials.dart';
7 import 'package:cw_core/wallet_info.dart';
8 +import 'package:cw_core/wallet_service.dart';
9 import 'package:cw_core/wallet_type.dart';
10 +import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
11 +import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
12 +import 'package:cw_monero/monero_wallet.dart';
13 +import 'package:hive/hive.dart';
14
15 class MoneroNewWalletCredentials extends WalletCredentials {
16 MoneroNewWalletCredentials({required String name, required this.language, String? password})
@@ -53,9 +53,10 @@ class MoneroWalletService extends WalletService<
53 MoneroNewWalletCredentials,
54 MoneroRestoreWalletFromSeedCredentials,
55 MoneroRestoreWalletFromKeysCredentials> {
56 - MoneroWalletService(this.walletInfoSource);
56 + MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
57
58 final Box<WalletInfo> walletInfoSource;
59 + final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
60
61 static bool walletFilesExist(String path) =>
62 !File(path).existsSync() && !File('$path.keys').existsSync();
@@ -71,7 +72,8 @@ class MoneroWalletService extends WalletService<
72 path: path,
73 password: credentials.password!,
74 language: credentials.language);
74 - final wallet = MoneroWallet(walletInfo: credentials.walletInfo!);
75 + final wallet = MoneroWallet(
76 + walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
77 await wallet.init();
78
79 return wallet;
@@ -107,7 +109,7 @@ class MoneroWalletService extends WalletService<
109 .openWalletAsync({'path': path, 'password': password});
110 final walletInfo = walletInfoSource.values.firstWhere(
111 (info) => info.id == WalletBase.idFor(name, getType()));
110 - final wallet = MoneroWallet(walletInfo: walletInfo);
112 + final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
113 final isValid = wallet.walletAddresses.validate();
114
115 if (!isValid) {
@@ -157,7 +159,8 @@ class MoneroWalletService extends WalletService<
159 String currentName, String password, String newName) async {
160 final currentWalletInfo = walletInfoSource.values.firstWhere(
161 (info) => info.id == WalletBase.idFor(currentName, getType()));
160 - final currentWallet = MoneroWallet(walletInfo: currentWalletInfo);
162 + final currentWallet =
163 + MoneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
164
165 await currentWallet.renameWalletFiles(newName);
166
@@ -181,7 +184,8 @@ class MoneroWalletService extends WalletService<
184 address: credentials.address,
185 viewKey: credentials.viewKey,
186 spendKey: credentials.spendKey);
184 - final wallet = MoneroWallet(walletInfo: credentials.walletInfo!);
187 + final wallet = MoneroWallet(
188 + walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
189 await wallet.init();
190
191 return wallet;
@@ -202,7 +206,8 @@ class MoneroWalletService extends WalletService<
206 password: credentials.password!,
207 seed: credentials.mnemonic,
208 restoreHeight: credentials.height!);
205 - final wallet = MoneroWallet(walletInfo: credentials.walletInfo!);
209 + final wallet = MoneroWallet(
210 + walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
211 await wallet.init();
212
213 return wallet;
lib/bitcoin/cw_bitcoin.dart
+3 -2
@@ -128,7 +128,8 @@ class CWBitcoin extends Bitcoin {
128 bitcoinUnspent.address.address,
129 bitcoinUnspent.hash,
130 bitcoinUnspent.value,
131 - bitcoinUnspent.vout))
131 + bitcoinUnspent.vout,
132 + null))
133 .toList();
134 }
135
@@ -160,4 +161,4 @@ class CWBitcoin extends Bitcoin {
161 @override
162 TransactionPriority getLitecoinTransactionPrioritySlow()
163 => LitecoinTransactionPriority.slow;
163 -}
\ No newline at end of file
164 +}
lib/di.dart
+6 -6
@@ -216,10 +216,10 @@ late Box<Template> _templates;
216 late Box<ExchangeTemplate> _exchangeTemplates;
217 late Box<TransactionDescription> _transactionDescriptionBox;
218 late Box<Order> _ordersSource;
219 -late Box<UnspentCoinsInfo>? _unspentCoinsInfoSource;
219 +late Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
220 late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
221
222 -Future setup({
222 +Future<void> setup({
223 required Box<WalletInfo> walletInfoSource,
224 required Box<Node> nodeSource,
225 required Box<Contact> contactSource,
@@ -228,7 +228,7 @@ Future setup({
228 required Box<ExchangeTemplate> exchangeTemplates,
229 required Box<TransactionDescription> transactionDescriptionBox,
230 required Box<Order> ordersSource,
231 - Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
231 + required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
232 required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
233 }) async {
234 _walletInfoSource = walletInfoSource;
@@ -731,11 +731,11 @@ Future setup({
731 case WalletType.haven:
732 return haven!.createHavenWalletService(_walletInfoSource);
733 case WalletType.monero:
734 - return monero!.createMoneroWalletService(_walletInfoSource);
734 + return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
735 case WalletType.bitcoin:
736 - return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
736 + return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
737 case WalletType.litecoin:
738 - return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
738 + return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
739 case WalletType.ethereum:
740 return ethereum!.createEthereumWalletService(_walletInfoSource);
741 default:
lib/entities/unspent_transaction_output.dart new
+18
@@ -0,0 +1,18 @@
1 +class Unspent {
2 + Unspent(this.address, this.hash, this.value, this.vout, this.keyImage)
3 + : isSending = true,
4 + isFrozen = false,
5 + note = '';
6 +
7 + final String address;
8 + final String hash;
9 + final int value;
10 + final int vout;
11 + final String? keyImage;
12 +
13 + bool isSending;
14 + bool isFrozen;
15 + String note;
16 +
17 + bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');
18 +}
lib/main.dart
+2 -6
@@ -133,11 +133,7 @@ Future<void> initializeAppConfigs() async {
133 final templates = await CakeHive.openBox<Template>(Template.boxName);
134 final exchangeTemplates = await CakeHive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
135 final anonpayInvoiceInfo = await CakeHive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
136 - Box<UnspentCoinsInfo>? unspentCoinsInfoSource;
137 -
138 - if (!isMoneroOnly) {
139 - unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
140 - }
136 + final unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
137
138 await initialSetup(
139 sharedPreferences: await SharedPreferences.getInstance(),
@@ -169,7 +165,7 @@ Future<void> initialSetup(
165 required Box<TransactionDescription> transactionDescriptions,
166 required FlutterSecureStorage secureStorage,
167 required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo,
172 - Box<UnspentCoinsInfo>? unspentCoinsInfoSource,
168 + required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
169 int initialMigrationVersion = 15}) async {
170 LanguageService.loadLocaleList();
171 await defaultSettingsMigration(
lib/monero/cw_monero.dart
+295 -293
@@ -1,361 +1,363 @@
1 part of 'monero.dart';
2
3 class CWMoneroAccountList extends MoneroAccountList {
4 - CWMoneroAccountList(this._wallet);
5 - final Object _wallet;
4 + CWMoneroAccountList(this._wallet);
5
7 - @override
8 - @computed
6 + final Object _wallet;
7 +
8 + @override
9 + @computed
10 ObservableList<Account> get accounts {
10 - final moneroWallet = _wallet as MoneroWallet;
11 - final accounts = moneroWallet.walletAddresses.accountList
12 - .accounts
13 - .map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance))
14 - .toList();
15 - return ObservableList<Account>.of(accounts);
11 + final moneroWallet = _wallet as MoneroWallet;
12 + final accounts = moneroWallet.walletAddresses.accountList.accounts
13 + .map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance))
14 + .toList();
15 + return ObservableList<Account>.of(accounts);
16 }
17
18 @override
19 void update(Object wallet) {
20 - final moneroWallet = wallet as MoneroWallet;
21 - moneroWallet.walletAddresses.accountList.update();
20 + final moneroWallet = wallet as MoneroWallet;
21 + moneroWallet.walletAddresses.accountList.update();
22 }
23
24 @override
25 - void refresh(Object wallet) {
26 - final moneroWallet = wallet as MoneroWallet;
27 - moneroWallet.walletAddresses.accountList.refresh();
28 - }
25 + void refresh(Object wallet) {
26 + final moneroWallet = wallet as MoneroWallet;
27 + moneroWallet.walletAddresses.accountList.refresh();
28 + }
29
30 - @override
30 + @override
31 List<Account> getAll(Object wallet) {
32 - final moneroWallet = wallet as MoneroWallet;
33 - return moneroWallet.walletAddresses.accountList
34 - .getAll()
35 - .map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance))
36 - .toList();
32 + final moneroWallet = wallet as MoneroWallet;
33 + return moneroWallet.walletAddresses.accountList
34 + .getAll()
35 + .map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance))
36 + .toList();
37 }
38
39 @override
40 Future<void> addAccount(Object wallet, {required String label}) async {
41 - final moneroWallet = wallet as MoneroWallet;
42 - await moneroWallet.walletAddresses.accountList.addAccount(label: label);
41 + final moneroWallet = wallet as MoneroWallet;
42 + await moneroWallet.walletAddresses.accountList.addAccount(label: label);
43 }
44
45 @override
46 - Future<void> setLabelAccount(Object wallet, {required int accountIndex, required String label}) async {
47 - final moneroWallet = wallet as MoneroWallet;
48 - await moneroWallet.walletAddresses.accountList
49 - .setLabelAccount(
50 - accountIndex: accountIndex,
51 - label: label);
46 + Future<void> setLabelAccount(Object wallet,
47 + {required int accountIndex, required String label}) async {
48 + final moneroWallet = wallet as MoneroWallet;
49 + await moneroWallet.walletAddresses.accountList
50 + .setLabelAccount(accountIndex: accountIndex, label: label);
51 }
52 }
53
54 class CWMoneroSubaddressList extends MoneroSubaddressList {
56 - CWMoneroSubaddressList(this._wallet);
57 - final Object _wallet;
55 + CWMoneroSubaddressList(this._wallet);
56 +
57 + final Object _wallet;
58
59 - @override
60 - @computed
59 + @override
60 + @computed
61 ObservableList<Subaddress> get subaddresses {
62 - final moneroWallet = _wallet as MoneroWallet;
63 - final subAddresses = moneroWallet.walletAddresses.subaddressList
64 - .subaddresses
65 - .map((sub) => Subaddress(
66 - id: sub.id,
67 - address: sub.address,
68 - label: sub.label))
69 - .toList();
70 - return ObservableList<Subaddress>.of(subAddresses);
62 + final moneroWallet = _wallet as MoneroWallet;
63 + final subAddresses = moneroWallet.walletAddresses.subaddressList.subaddresses
64 + .map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label))
65 + .toList();
66 + return ObservableList<Subaddress>.of(subAddresses);
67 }
68
69 @override
70 void update(Object wallet, {required int accountIndex}) {
75 - final moneroWallet = wallet as MoneroWallet;
76 - moneroWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex);
71 + final moneroWallet = wallet as MoneroWallet;
72 + moneroWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex);
73 }
74
75 @override
76 void refresh(Object wallet, {required int accountIndex}) {
81 - final moneroWallet = wallet as MoneroWallet;
82 - moneroWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex);
77 + final moneroWallet = wallet as MoneroWallet;
78 + moneroWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex);
79 }
80
81 @override
82 List<Subaddress> getAll(Object wallet) {
87 - final moneroWallet = wallet as MoneroWallet;
88 - return moneroWallet.walletAddresses
89 - .subaddressList
90 - .getAll()
91 - .map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address))
92 - .toList();
83 + final moneroWallet = wallet as MoneroWallet;
84 + return moneroWallet.walletAddresses.subaddressList
85 + .getAll()
86 + .map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address))
87 + .toList();
88 }
89
90 @override
96 - Future<void> addSubaddress(Object wallet, {required int accountIndex, required String label}) async {
97 - final moneroWallet = wallet as MoneroWallet;
98 - await moneroWallet.walletAddresses.subaddressList
99 - .addSubaddress(
100 - accountIndex: accountIndex,
101 - label: label);
91 + Future<void> addSubaddress(Object wallet,
92 + {required int accountIndex, required String label}) async {
93 + final moneroWallet = wallet as MoneroWallet;
94 + await moneroWallet.walletAddresses.subaddressList
95 + .addSubaddress(accountIndex: accountIndex, label: label);
96 }
97
98 @override
99 Future<void> setLabelSubaddress(Object wallet,
100 {required int accountIndex, required int addressIndex, required String label}) async {
107 - final moneroWallet = wallet as MoneroWallet;
108 - await moneroWallet.walletAddresses.subaddressList
109 - .setLabelSubaddress(
110 - accountIndex: accountIndex,
111 - addressIndex: addressIndex,
112 - label: label);
101 + final moneroWallet = wallet as MoneroWallet;
102 + await moneroWallet.walletAddresses.subaddressList
103 + .setLabelSubaddress(accountIndex: accountIndex, addressIndex: addressIndex, label: label);
104 }
105 }
106
107 class CWMoneroWalletDetails extends MoneroWalletDetails {
117 - CWMoneroWalletDetails(this._wallet);
118 - final Object _wallet;
108 + CWMoneroWalletDetails(this._wallet);
109 +
110 + final Object _wallet;
111
120 - @computed
112 + @computed
113 @override
114 Account get account {
123 - final moneroWallet = _wallet as MoneroWallet;
124 - final acc = moneroWallet.walletAddresses.account;
125 - return Account(id: acc!.id, label: acc.label, balance: acc.balance);
115 + final moneroWallet = _wallet as MoneroWallet;
116 + final acc = moneroWallet.walletAddresses.account;
117 + return Account(id: acc!.id, label: acc.label, balance: acc.balance);
118 }
119
120 @computed
121 @override
130 - MoneroBalance get balance {
131 - final moneroWallet = _wallet as MoneroWallet;
132 - final balance = moneroWallet.balance;
122 + MoneroBalance get balance {
123 + final moneroWallet = _wallet as MoneroWallet;
124 + final balance = moneroWallet.balance;
125 throw Exception('Unimplemented');
134 - // return MoneroBalance();
135 - //return MoneroBalance(
136 - // fullBalance: balance.fullBalance,
137 - // unlockedBalance: balance.unlockedBalance);
138 - }
126 + // return MoneroBalance();
127 + //return MoneroBalance(
128 + // fullBalance: balance.fullBalance,
129 + // unlockedBalance: balance.unlockedBalance);
130 + }
131 }
132
133 class CWMonero extends Monero {
134 @override
143 - MoneroAccountList getAccountList(Object wallet) {
144 - return CWMoneroAccountList(wallet);
145 - }
146 -
147 - @override
148 - MoneroSubaddressList getSubaddressList(Object wallet) {
149 - return CWMoneroSubaddressList(wallet);
150 - }
151 -
152 - @override
153 - TransactionHistoryBase getTransactionHistory(Object wallet) {
154 - final moneroWallet = wallet as MoneroWallet;
155 - return moneroWallet.transactionHistory;
156 - }
157 -
158 - @override
159 - MoneroWalletDetails getMoneroWalletDetails(Object wallet) {
160 - return CWMoneroWalletDetails(wallet);
161 - }
162 -
163 - @override
164 - int getHeigthByDate({required DateTime date}) {
165 - return getMoneroHeigthByDate(date: date);
166 - }
167 -
168 - @override
169 - TransactionPriority getDefaultTransactionPriority() {
170 - return MoneroTransactionPriority.automatic;
171 - }
172 -
173 - @override
174 - TransactionPriority getMoneroTransactionPrioritySlow()
175 - => MoneroTransactionPriority.slow;
176 -
177 - @override
178 - TransactionPriority getMoneroTransactionPriorityAutomatic()
179 - => MoneroTransactionPriority.automatic;
180 -
181 - @override
182 - TransactionPriority deserializeMoneroTransactionPriority({required int raw}) {
183 - return MoneroTransactionPriority.deserialize(raw: raw);
184 - }
185 -
186 - @override
187 - List<TransactionPriority> getTransactionPriorities() {
188 - return MoneroTransactionPriority.all;
189 - }
190 -
191 - @override
192 - List<String> getMoneroWordList(String language) {
193 - switch (language.toLowerCase()) {
194 - case 'english':
195 - return EnglishMnemonics.words;
196 - case 'chinese (simplified)':
197 - return ChineseSimplifiedMnemonics.words;
198 - case 'dutch':
199 - return DutchMnemonics.words;
200 - case 'german':
201 - return GermanMnemonics.words;
202 - case 'japanese':
203 - return JapaneseMnemonics.words;
204 - case 'portuguese':
205 - return PortugueseMnemonics.words;
206 - case 'russian':
207 - return RussianMnemonics.words;
208 - case 'spanish':
209 - return SpanishMnemonics.words;
210 - case 'french':
211 - return FrenchMnemonics.words;
212 - case 'italian':
213 - return ItalianMnemonics.words;
214 - default:
215 - return EnglishMnemonics.words;
216 - }
217 - }
218 -
219 - @override
220 - WalletCredentials createMoneroRestoreWalletFromKeysCredentials({
221 - required String name,
222 - required String spendKey,
223 - required String viewKey,
224 - required String address,
225 - required String password,
226 - required String language,
227 - required int height}) {
228 - return MoneroRestoreWalletFromKeysCredentials(
229 - name: name,
230 - spendKey: spendKey,
231 - viewKey: viewKey,
232 - address: address,
233 - password: password,
234 - language: language,
235 - height: height);
236 - }
237 -
238 - @override
239 - WalletCredentials createMoneroRestoreWalletFromSeedCredentials({
240 - required String name,
241 - required String password,
242 - required int height,
243 - required String mnemonic}) {
244 - return MoneroRestoreWalletFromSeedCredentials(
245 - name: name,
246 - password: password,
247 - height: height,
248 - mnemonic: mnemonic);
249 - }
250 -
251 - @override
252 - WalletCredentials createMoneroNewWalletCredentials({
135 + MoneroAccountList getAccountList(Object wallet) {
136 + return CWMoneroAccountList(wallet);
137 + }
138 +
139 + @override
140 + MoneroSubaddressList getSubaddressList(Object wallet) {
141 + return CWMoneroSubaddressList(wallet);
142 + }
143 +
144 + @override
145 + TransactionHistoryBase getTransactionHistory(Object wallet) {
146 + final moneroWallet = wallet as MoneroWallet;
147 + return moneroWallet.transactionHistory;
148 + }
149 +
150 + @override
151 + MoneroWalletDetails getMoneroWalletDetails(Object wallet) {
152 + return CWMoneroWalletDetails(wallet);
153 + }
154 +
155 + @override
156 + int getHeigthByDate({required DateTime date}) {
157 + return getMoneroHeigthByDate(date: date);
158 + }
159 +
160 + @override
161 + TransactionPriority getDefaultTransactionPriority() {
162 + return MoneroTransactionPriority.automatic;
163 + }
164 +
165 + @override
166 + TransactionPriority getMoneroTransactionPrioritySlow() => MoneroTransactionPriority.slow;
167 +
168 + @override
169 + TransactionPriority getMoneroTransactionPriorityAutomatic() =>
170 + MoneroTransactionPriority.automatic;
171 +
172 + @override
173 + TransactionPriority deserializeMoneroTransactionPriority({required int raw}) {
174 + return MoneroTransactionPriority.deserialize(raw: raw);
175 + }
176 +
177 + @override
178 + List<TransactionPriority> getTransactionPriorities() {
179 + return MoneroTransactionPriority.all;
180 + }
181 +
182 + @override
183 + List<String> getMoneroWordList(String language) {
184 + switch (language.toLowerCase()) {
185 + case 'english':
186 + return EnglishMnemonics.words;
187 + case 'chinese (simplified)':
188 + return ChineseSimplifiedMnemonics.words;
189 + case 'dutch':
190 + return DutchMnemonics.words;
191 + case 'german':
192 + return GermanMnemonics.words;
193 + case 'japanese':
194 + return JapaneseMnemonics.words;
195 + case 'portuguese':
196 + return PortugueseMnemonics.words;
197 + case 'russian':
198 + return RussianMnemonics.words;
199 + case 'spanish':
200 + return SpanishMnemonics.words;
201 + case 'french':
202 + return FrenchMnemonics.words;
203 + case 'italian':
204 + return ItalianMnemonics.words;
205 + default:
206 + return EnglishMnemonics.words;
207 + }
208 + }
209 +
210 + @override
211 + WalletCredentials createMoneroRestoreWalletFromKeysCredentials(
212 + {required String name,
213 + required String spendKey,
214 + required String viewKey,
215 + required String address,
216 + required String password,
217 + required String language,
218 + required int height}) {
219 + return MoneroRestoreWalletFromKeysCredentials(
220 + name: name,
221 + spendKey: spendKey,
222 + viewKey: viewKey,
223 + address: address,
224 + password: password,
225 + language: language,
226 + height: height);
227 + }
228 +
229 + @override
230 + WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
231 + {required String name,
232 + required String password,
233 + required int height,
234 + required String mnemonic}) {
235 + return MoneroRestoreWalletFromSeedCredentials(
236 + name: name, password: password, height: height, mnemonic: mnemonic);
237 + }
238 +
239 + @override
240 + WalletCredentials createMoneroNewWalletCredentials({
241 required String name,
242 required String language,
255 - String? password,}) {
256 - return MoneroNewWalletCredentials(
257 - name: name,
258 - password: password,
259 - language: language);
260 - }
261 -
262 - @override
263 - Map<String, String> getKeys(Object wallet) {
264 - final moneroWallet = wallet as MoneroWallet;
265 - final keys = moneroWallet.keys;
266 - return <String, String>{
267 - 'privateSpendKey': keys.privateSpendKey,
243 + String? password,
244 + }) {
245 + return MoneroNewWalletCredentials(name: name, password: password, language: language);
246 + }
247 +
248 + @override
249 + Map<String, String> getKeys(Object wallet) {
250 + final moneroWallet = wallet as MoneroWallet;
251 + final keys = moneroWallet.keys;
252 + return <String, String>{
253 + 'privateSpendKey': keys.privateSpendKey,
254 'privateViewKey': keys.privateViewKey,
255 'publicSpendKey': keys.publicSpendKey,
270 - 'publicViewKey': keys.publicViewKey};
271 - }
272 -
273 - @override
274 - Object createMoneroTransactionCreationCredentials({
275 - required List<Output> outputs,
276 - required TransactionPriority priority}) {
277 - return MoneroTransactionCreationCredentials(
278 - outputs: outputs.map((out) => OutputInfo(
279 - fiatAmount: out.fiatAmount,
280 - cryptoAmount: out.cryptoAmount,
281 - address: out.address,
282 - note: out.note,
283 - sendAll: out.sendAll,
284 - extractedAddress: out.extractedAddress,
285 - isParsedAddress: out.isParsedAddress,
286 - formattedCryptoAmount: out.formattedCryptoAmount))
287 - .toList(),
288 - priority: priority as MoneroTransactionPriority);
289 - }
290 -
291 - @override
292 - Object createMoneroTransactionCreationCredentialsRaw({
293 - required List<OutputInfo> outputs,
294 - required TransactionPriority priority}) {
295 - return MoneroTransactionCreationCredentials(
296 - outputs: outputs,
297 - priority: priority as MoneroTransactionPriority);
298 - }
299 -
300 - @override
301 - String formatterMoneroAmountToString({required int amount}) {
302 - return moneroAmountToString(amount: amount);
303 - }
304 -
305 - @override
306 - double formatterMoneroAmountToDouble({required int amount}) {
307 - return moneroAmountToDouble(amount: amount);
308 - }
309 -
310 - @override
311 - int formatterMoneroParseAmount({required String amount}) {
312 - return moneroParseAmount(amount: amount);
313 - }
314 -
315 - @override
316 - Account getCurrentAccount(Object wallet) {
317 - final moneroWallet = wallet as MoneroWallet;
318 - final acc = moneroWallet.walletAddresses.account;
319 - return Account(id: acc!.id, label: acc.label, balance: acc.balance);
320 - }
321 -
322 - @override
323 - void setCurrentAccount(Object wallet, int id, String label, String? balance) {
324 - final moneroWallet = wallet as MoneroWallet;
325 - moneroWallet.walletAddresses.account = monero_account.Account(id: id, label: label, balance: balance);
326 - }
327 -
328 - @override
329 - void onStartup() {
330 - monero_wallet_api.onStartup();
331 - }
332 -
333 - @override
334 - int getTransactionInfoAccountId(TransactionInfo tx) {
335 - final moneroTransactionInfo = tx as MoneroTransactionInfo;
336 - return moneroTransactionInfo.accountIndex;
337 - }
338 -
339 - @override
340 - WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource) {
341 - return MoneroWalletService(walletInfoSource);
342 - }
343 -
344 - @override
345 - String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
346 - final moneroWallet = wallet as MoneroWallet;
347 - return moneroWallet.getTransactionAddress(accountIndex, addressIndex);
348 - }
349 -
350 - @override
351 - String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) {
352 - final moneroWallet = wallet as MoneroWallet;
353 - return moneroWallet.getSubaddressLabel(accountIndex, addressIndex);
354 - }
355 -
356 - @override
357 - Map<String, String> pendingTransactionInfo(Object transaction) {
358 - final ptx = transaction as PendingMoneroTransaction;
359 - return {'id': ptx.id, 'hex': ptx.hex, 'key': ptx.txKey};
360 - }
256 + 'publicViewKey': keys.publicViewKey
257 + };
258 + }
259 +
260 + @override
261 + Object createMoneroTransactionCreationCredentials(
262 + {required List<Output> outputs, required TransactionPriority priority}) {
263 + return MoneroTransactionCreationCredentials(
264 + outputs: outputs
265 + .map((out) => OutputInfo(
266 + fiatAmount: out.fiatAmount,
267 + cryptoAmount: out.cryptoAmount,
268 + address: out.address,
269 + note: out.note,
270 + sendAll: out.sendAll,
271 + extractedAddress: out.extractedAddress,
272 + isParsedAddress: out.isParsedAddress,
273 + formattedCryptoAmount: out.formattedCryptoAmount))
274 + .toList(),
275 + priority: priority as MoneroTransactionPriority);
276 + }
277 +
278 + @override
279 + Object createMoneroTransactionCreationCredentialsRaw(
280 + {required List<OutputInfo> outputs, required TransactionPriority priority}) {
281 + return MoneroTransactionCreationCredentials(
282 + outputs: outputs, priority: priority as MoneroTransactionPriority);
283 + }
284 +
285 + @override
286 + String formatterMoneroAmountToString({required int amount}) {
287 + return moneroAmountToString(amount: amount);
288 + }
289 +
290 + @override
291 + double formatterMoneroAmountToDouble({required int amount}) {
292 + return moneroAmountToDouble(amount: amount);
293 + }
294 +
295 + @override
296 + int formatterMoneroParseAmount({required String amount}) {
297 + return moneroParseAmount(amount: amount);
298 + }
299 +
300 + @override
301 + Account getCurrentAccount(Object wallet) {
302 + final moneroWallet = wallet as MoneroWallet;
303 + final acc = moneroWallet.walletAddresses.account;
304 + return Account(id: acc!.id, label: acc.label, balance: acc.balance);
305 + }
306 +
307 + @override
308 + void setCurrentAccount(Object wallet, int id, String label, String? balance) {
309 + final moneroWallet = wallet as MoneroWallet;
310 + moneroWallet.walletAddresses.account =
311 + monero_account.Account(id: id, label: label, balance: balance);
312 + }
313 +
314 + @override
315 + void onStartup() {
316 + monero_wallet_api.onStartup();
317 + }
318 +
319 + @override
320 + int getTransactionInfoAccountId(TransactionInfo tx) {
321 + final moneroTransactionInfo = tx as MoneroTransactionInfo;
322 + return moneroTransactionInfo.accountIndex;
323 + }
324 +
325 + @override
326 + WalletService createMoneroWalletService(
327 + Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
328 + return MoneroWalletService(walletInfoSource, unspentCoinSource);
329 + }
330 +
331 + @override
332 + String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
333 + final moneroWallet = wallet as MoneroWallet;
334 + return moneroWallet.getTransactionAddress(accountIndex, addressIndex);
335 + }
336 +
337 + @override
338 + String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) {
339 + final moneroWallet = wallet as MoneroWallet;
340 + return moneroWallet.getSubaddressLabel(accountIndex, addressIndex);
341 + }
342 +
343 + @override
344 + Map<String, String> pendingTransactionInfo(Object transaction) {
345 + final ptx = transaction as PendingMoneroTransaction;
346 + return {'id': ptx.id, 'hex': ptx.hex, 'key': ptx.txKey};
347 + }
348 +
349 + @override
350 + List<Unspent> getUnspents(Object wallet) {
351 + final moneroWallet = wallet as MoneroWallet;
352 + return moneroWallet.unspentCoins
353 + .map((MoneroUnspent moneroUnspent) => Unspent(moneroUnspent.address, moneroUnspent.hash,
354 + moneroUnspent.value, 0, moneroUnspent.keyImage))
355 + .toList();
356 + }
357 +
358 + @override
359 + void updateUnspents(Object wallet) async {
360 + final moneroWallet = wallet as MoneroWallet;
361 + await moneroWallet.updateUnspent();
362 + }
363 }
lib/src/screens/send/send_page.dart
+1 -1
@@ -100,7 +100,7 @@ class SendPage extends BasePage {
100 AppBarStyle get appBarStyle => AppBarStyle.transparent;
101
102 double _sendCardHeight(BuildContext context) {
103 - final double initialHeight = sendViewModel.isElectrumWallet ? 490 : 465;
103 + final double initialHeight = sendViewModel.hasCoinControl ? 490 : 465;
104
105 if (!ResponsiveLayoutUtil.instance.isMobile) {
106 return initialHeight - 66;
lib/src/screens/send/widgets/send_card.dart
+1 -1
@@ -496,7 +496,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
496 ),
497 ),
498 ),
499 - if (sendViewModel.isElectrumWallet)
499 + if (sendViewModel.hasCoinControl)
500 Padding(
501 padding: EdgeInsets.only(top: 6),
502 child: GestureDetector(
lib/src/screens/transaction_details/transaction_details_page.dart
-1
@@ -21,7 +21,6 @@ class TransactionDetailsPage extends BasePage {
21
22 @override
23 Widget body(BuildContext context) {
24 - // FIX-ME: Added `context` it was not used here before, maby bug ?
24 return SectionStandardList(
25 sectionCount: 1,
26 itemCounter: (int _) => transactionDetailsViewModel.items.length,
lib/src/screens/transaction_details/widgets/textfield_list_row.dart
+8 -5
@@ -6,11 +6,12 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
6 class TextFieldListRow extends StatelessWidget {
7 TextFieldListRow(
8 {required this.title,
9 - required this.value,
10 - this.titleFontSize = 14,
11 - this.valueFontSize = 16,
12 - this.onSubmitted})
13 - : _textController = TextEditingController() {
9 + required this.value,
10 + this.titleFontSize = 14,
11 + this.valueFontSize = 16,
12 + this.onSubmitted,
13 + this.onTapOutside})
14 + : _textController = TextEditingController() {
15 _textController.text = value;
16 }
17
@@ -19,6 +20,7 @@ class TextFieldListRow extends StatelessWidget {
20 final double titleFontSize;
21 final double valueFontSize;
22 final Function(String value)? onSubmitted;
23 + final Function(String value)? onTapOutside;
24 final TextEditingController _textController;
25
26 @override
@@ -58,6 +60,7 @@ class TextFieldListRow extends StatelessWidget {
60 fontWeight: FontWeight.w500,
61 color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
62 border: InputBorder.none),
63 + onTapOutside: (_) => onTapOutside?.call(_textController.text),
64 onSubmitted: (value) => onSubmitted?.call(value),
65 )
66 ]),
lib/src/screens/unspent_coins/unspent_coins_details_page.dart
+1 -1
@@ -24,7 +24,6 @@ class UnspentCoinsDetailsPage extends BasePage {
24
25 @override
26 Widget body(BuildContext context) {
27 - // FIX-ME: Added `context` it was not used here before, maby bug ?
27 return SectionStandardList(
28 sectionCount: 1,
29 itemCounter: (int _) => unspentCoinsDetailsViewModel.items.length,
@@ -45,6 +44,7 @@ class UnspentCoinsDetailsPage extends BasePage {
44 return TextFieldListRow(
45 title: item.title,
46 value: item.value,
47 + onTapOutside: item.onSubmitted,
48 onSubmitted: item.onSubmitted,
49 );
50 }
lib/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart
+1 -1
@@ -94,7 +94,7 @@ class UnspentCoinsListItem extends StatelessWidget {
94 crossAxisAlignment: CrossAxisAlignment.center,
95 children: [
96 AutoSizeText(
97 - address,
97 + '${address.substring(0, 5)}...${address.substring(address.length-5)}', // ToDo: Maybe use address label
98 style: TextStyle(
99 color: addressColor,
100 fontSize: 12,
lib/view_model/send/send_view_model.dart
+6 -2
@@ -172,6 +172,10 @@ abstract class SendViewModelBase with Store {
172 .where((template) => _isEqualCurrency(template.cryptoCurrency))
173 .toList();
174
175 + @computed
176 + bool get hasCoinControl =>
177 + _wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin || _wallet.type == WalletType.monero;
178 +
179 @computed
180 bool get isElectrumWallet =>
181 _wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin;
@@ -206,12 +210,12 @@ abstract class SendViewModelBase with Store {
210
211 @computed
212 List<ContactRecord> get contactsToShow => contactListViewModel.contacts
209 - .where((element) => selectedCryptoCurrency == null || element.type == selectedCryptoCurrency)
213 + .where((element) => element.type == selectedCryptoCurrency)
214 .toList();
215
216 @computed
217 List<WalletContact> get walletContactsToShow => contactListViewModel.walletContacts
214 - .where((element) => selectedCryptoCurrency == null || element.type == selectedCryptoCurrency)
218 + .where((element) => element.type == selectedCryptoCurrency)
219 .toList();
220
221 @action
lib/view_model/unspent_coins/unspent_coins_details_view_model.dart
+15 -11
@@ -23,6 +23,7 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
23 note = unspentCoinsItem.note {
24 items = [
25 StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount),
26 + StandartListItem(title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash),
27 StandartListItem(title: S.current.widgets_address, value: unspentCoinsItem.address),
28 TextFieldListItem(
29 title: S.current.note_tap_to_change,
@@ -42,19 +43,22 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
43 unspentCoinsItem.isSending = !value;
44 }
45 await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
45 - }),
46 - BlockExplorerListItem(
47 - title: S.current.view_in_block_explorer,
48 - value: _explorerDescription(unspentCoinsListViewModel.wallet.type),
49 - onTap: () {
50 - try {
51 - final url = Uri.parse(
52 - _explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash));
53 - return launchUrl(url);
54 - } catch (e) {}
55 -
46 })
47 ];
48 +
49 + if ([WalletType.bitcoin, WalletType.litecoin].contains(unspentCoinsListViewModel.wallet.type)) {
50 + items.add(BlockExplorerListItem(
51 + title: S.current.view_in_block_explorer,
52 + value: _explorerDescription(unspentCoinsListViewModel.wallet.type),
53 + onTap: () {
54 + try {
55 + final url = Uri.parse(
56 + _explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash));
57 + return launchUrl(url);
58 + } catch (e) {}
59 + },
60 + ));
61 + }
62 }
63
64 String _explorerUrl(WalletType type, String txId) {
lib/view_model/unspent_coins/unspent_coins_item.dart
+7 -2
@@ -13,7 +13,9 @@ abstract class UnspentCoinsItemBase with Store {
13 required this.note,
14 required this.isSending,
15 required this.amountRaw,
16 - required this.vout});
16 + required this.vout,
17 + required this.keyImage
18 + });
19
20 @observable
21 String address;
@@ -38,4 +40,7 @@ abstract class UnspentCoinsItemBase with Store {
40
41 @observable
42 int vout;
41 -}
\ No newline at end of file
43 +
44 + @observable
45 + String? keyImage;
46 +}
lib/view_model/unspent_coins/unspent_coins_list_view_model.dart
+49 -15
@@ -1,10 +1,15 @@
1 -import 'package:cw_core/unspent_coins_info.dart';
1 +import 'package:collection/collection.dart';
2 import 'package:cake_wallet/bitcoin/bitcoin.dart';
3 -import 'package:cw_core/wallet_base.dart';
3 +import 'package:cake_wallet/entities/unspent_transaction_output.dart';
4 +import 'package:cake_wallet/monero/monero.dart';
5 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
6 +import 'package:cw_bitcoin/bitcoin_wallet.dart';
7 +import 'package:cw_core/unspent_coins_info.dart';
8 +import 'package:cw_core/wallet_base.dart';
9 +import 'package:cw_core/wallet_type.dart';
10 +import 'package:cw_monero/monero_wallet.dart';
11 import 'package:hive/hive.dart';
12 import 'package:mobx/mobx.dart';
7 -import 'package:collection/collection.dart';
13
14 part 'unspent_coins_list_view_model.g.dart';
15
@@ -14,7 +19,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
19 UnspentCoinsListViewModelBase(
20 {required this.wallet, required Box<UnspentCoinsInfo> unspentCoinsInfo})
21 : _unspentCoinsInfo = unspentCoinsInfo {
17 - bitcoin!.updateUnspents(wallet);
22 + _updateUnspents();
23 }
24
25 WalletBase wallet;
@@ -22,11 +27,10 @@ abstract class UnspentCoinsListViewModelBase with Store {
27
28 @computed
29 ObservableList<UnspentCoinsItem> get items =>
25 - ObservableList.of(bitcoin!.getUnspents(wallet).map((elem) {
26 - final amount = bitcoin!.formatterBitcoinAmountToString(amount: elem.value) +
27 - ' ${wallet.currency.title}';
30 + ObservableList.of(_getUnspents().map((elem) {
31 + final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}';
32
29 - final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout);
33 + final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
34
35 return UnspentCoinsItem(
36 address: elem.address,
@@ -36,12 +40,14 @@ abstract class UnspentCoinsListViewModelBase with Store {
40 note: info?.note ?? '',
41 isSending: info?.isSending ?? true,
42 amountRaw: elem.value,
39 - vout: elem.vout);
43 + vout: elem.vout,
44 + keyImage: elem.keyImage
45 + );
46 }));
47
48 Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
49 try {
44 - final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout);
50 + final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
51 if (info == null) {
52 final newInfo = UnspentCoinsInfo(
53 walletId: wallet.id,
@@ -51,10 +57,12 @@ abstract class UnspentCoinsListViewModelBase with Store {
57 vout: item.vout,
58 isFrozen: item.isFrozen,
59 isSending: item.isSending,
54 - noteRaw: item.note);
60 + noteRaw: item.note,
61 + keyImage: item.keyImage
62 + );
63
64 await _unspentCoinsInfo.add(newInfo);
57 - bitcoin!.updateUnspents(wallet);
65 + _updateUnspents();
66 wallet.updateBalance();
67 return;
68 }
@@ -63,19 +71,45 @@ abstract class UnspentCoinsListViewModelBase with Store {
71 info.note = item.note;
72
73 await info.save();
66 - bitcoin!.updateUnspents(wallet);
74 + _updateUnspents();
75 wallet.updateBalance();
76 } catch (e) {
77 print(e.toString());
78 }
79 }
80
73 - UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout) {
81 + UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout, String? keyImage) {
82 return _unspentCoinsInfo.values.firstWhereOrNull((element) =>
83 element.walletId == wallet.id &&
84 element.hash == hash &&
85 element.address == address &&
86 element.value == value &&
79 - element.vout == vout);
87 + element.vout == vout &&
88 + element.keyImage == keyImage
89 + );
90 + }
91 +
92 + String formatAmountToString(int fullBalance) {
93 + if (wallet.type == WalletType.monero)
94 + return monero!.formatterMoneroAmountToString(amount: fullBalance);
95 + if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
96 + return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance);
97 + return '';
98 + }
99 +
100 +
101 + void _updateUnspents() {
102 + if (wallet.type == WalletType.monero)
103 + return monero!.updateUnspents(wallet);
104 + if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
105 + return bitcoin!.updateUnspents(wallet);
106 + }
107 +
108 + List<Unspent> _getUnspents() {
109 + if (wallet.type == WalletType.monero)
110 + return monero!.getUnspents(wallet);
111 + if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
112 + return bitcoin!.getUnspents(wallet);
113 + return List.empty();
114 }
115 }
pubspec_base.yaml
+4 -1
@@ -99,7 +99,10 @@ dev_dependencies:
99 # check flutter_launcher_icons for usage
100 pedantic: ^1.8.0
101 # replace https://github.com/dart-lang/lints#migrating-from-packagepedantic
102 -# translator: ^0.1.7
102 + translator:
103 + git:
104 + url: https://github.com/cake-tech/google-translator.git
105 + version: 1.0.0
106
107 flutter_icons:
108 image_path: "assets/images/app_logo.png"
res/values/strings_de.arb
+17 -17
@@ -5,10 +5,10 @@
5 "please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.",
6 "create_new": "Neue Wallet erstellen",
7 "restore_wallet": "Wallet wiederherstellen",
8 - "monero_com": "Monero.com by Cake Wallet",
9 - "monero_com_wallet_text": "Awesome wallet for Monero",
10 - "haven_app": "Haven by Cake Wallet",
11 - "haven_app_wallet_text": "Awesome wallet for Haven",
8 + "monero_com": "Monero.com von Cake Wallet",
9 + "monero_com_wallet_text": "Eine großartige Wallet für Monero",
10 + "haven_app": "Haven von Cake Wallet",
11 + "haven_app_wallet_text": "Eine großartige Wallet für Haven",
12 "accounts": "Konten",
13 "edit": "Bearbeiten",
14 "account": "Konto",
@@ -152,10 +152,10 @@
152 "restore_wallet_restore_description": "Beschreibung zur Wallet-Wiederherstellung",
153 "restore_new_seed": "Neuer Seed",
154 "restore_active_seed": "Aktiver Seed",
155 - "restore_bitcoin_description_from_seed": "Stellen Sie Ihre Brieftasche aus dem 24-Wort-Kombinationscode wieder her",
156 - "restore_bitcoin_description_from_keys": "Stellen Sie Ihre Brieftasche aus der generierten WIF-Zeichenfolge aus Ihren privaten Schlüsseln wieder her",
155 + "restore_bitcoin_description_from_seed": "Stellen Sie Ihre Wallet aus dem 24-Wort-Kombinationscode wieder her",
156 + "restore_bitcoin_description_from_keys": "Stellen Sie Ihre Wallet aus der generierten WIF-Zeichenfolge aus Ihren privaten Schlüsseln wieder her",
157 "restore_bitcoin_title_from_keys": "Aus WIF wiederherstellen",
158 - "restore_from_date_or_blockheight": "Bitte geben Sie ein Datum ein, das einige Tage vor dem Erstellen dieser Brieftasche liegt. Oder wenn Sie die Blockhöhe kennen, geben Sie stattdessen diese ein",
158 + "restore_from_date_or_blockheight": "Bitte geben Sie ein Datum ein, das einige Tage vor dem Erstellen dieser Wallet liegt. Oder wenn Sie die Blockhöhe kennen, geben Sie stattdessen diese ein",
159 "seed_reminder": "Bitte notieren Sie diese für den Fall, dass Sie Ihr Telefon verlieren oder es kaputtgeht",
160 "seed_title": "Seed",
161 "seed_share": "Seed teilen",
@@ -403,7 +403,7 @@
403 "buy_bitcoin": "Bitcoin kaufen",
404 "buy_with": "Kaufen mit",
405 "moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein",
406 - "outdated_electrum_wallet_receive_warning": "Wenn diese Brieftasche einen 12-Wort-Seed hat und in Cake erstellt wurde, zahlen Sie KEINE Bitcoins in diese Brieftasche ein. Alle auf diese Wallet übertragenen BTC können verloren gehen. Erstellen Sie eine neue 24-Wort-Wallet (tippen Sie auf das Menü oben rechts, wählen Sie Wallets, wählen Sie Neue Wallet erstellen und dann Bitcoin) und verschieben Sie Ihre BTC SOFORT dorthin. Neue (24-Wort-)BTC-Wallets von Cake sind sicher",
406 + "outdated_electrum_wallet_receive_warning": "Wenn diese Wallet einen 12-Wort-Seed hat und in Cake erstellt wurde, zahlen Sie KEINE Bitcoins in diese Wallet ein. Alle auf diese Wallet übertragenen BTC können verloren gehen. Erstellen Sie eine neue 24-Wort-Wallet (tippen Sie auf das Menü oben rechts, wählen Sie Wallets, wählen Sie Neue Wallet erstellen und dann Bitcoin) und verschieben Sie Ihre BTC SOFORT dorthin. Neue (24-Wort-)BTC-Wallets von Cake sind sicher",
407 "do_not_show_me": "Zeig mir das nicht noch einmal",
408 "unspent_coins_title": "Nicht ausgegebene Münzen",
409 "unspent_coins_details_title": "Details zu nicht ausgegebenen Münzen",
@@ -596,10 +596,10 @@
596 "sweeping_wallet_alert": "Das sollte nicht lange dauern. VERLASSEN SIE DIESEN BILDSCHIRM NICHT, ANDERNFALLS KÖNNEN DIE SWEPT-GELDER VERLOREN GEHEN",
597 "decimal_places_error": "Zu viele Nachkommastellen",
598 "edit_node": "Knoten bearbeiten",
599 - "frozen_balance": "Gefrorenes Gleichgewicht",
599 + "frozen_balance": "Gefrorenes Guthaben",
600 "invoice_details": "Rechnungs-Details",
601 "donation_link_details": "Details zum Spendenlink",
602 - "anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Brieftasche.",
602 + "anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Wallet.",
603 "create_invoice": "Rechnung erstellen",
604 "create_donation_link": "Spendenlink erstellen",
605 "optional_email_hint": "Optionale Benachrichtigungs-E-Mail für den Zahlungsempfänger",
@@ -615,22 +615,22 @@
615 "prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen",
616 "profile": "Profil",
617 "close": "Schließen",
618 - "modify_2fa": "Kuchen 2FA ändern",
619 - "disable_cake_2fa": "Kuchen 2FA deaktivieren",
620 - "question_to_disable_2fa": "Sind Sie sicher, dass Sie Cake 2FA deaktivieren möchten? Für den Zugriff auf die Brieftasche und bestimmte Funktionen wird kein 2FA-Code mehr benötigt.",
618 + "modify_2fa": "Cake 2FA ändern",
619 + "disable_cake_2fa": "Cake 2FA deaktivieren",
620 + "question_to_disable_2fa": "Sind Sie sicher, dass Sie Cake 2FA deaktivieren möchten? Für den Zugriff auf die Wallet und bestimmte Funktionen wird kein 2FA-Code mehr benötigt.",
621 "disable": "Deaktivieren",
622 - "setup_2fa": "Setup-Kuchen 2FA",
622 + "setup_2fa": "Setup-Cake 2FA",
623 "verify_with_2fa": "Verifizieren Sie mit Cake 2FA",
624 "totp_code": "TOTP-Code",
625 "please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist",
626 - "totp_2fa_success": "Erfolg! Cake 2FA für dieses Wallet aktiviert. Denken Sie daran, Ihren mnemonischen Seed zu speichern, falls Sie den Zugriff auf die Brieftasche verlieren.",
626 + "totp_2fa_success": "Erfolg! Cake 2FA für dieses Wallet aktiviert. Denken Sie daran, Ihren mnemonischen Seed zu speichern, falls Sie den Zugriff auf die Wallet verlieren.",
627 "totp_verification_success": "Verifizierung erfolgreich!",
628 "totp_2fa_failure": "Falscher Code. Bitte versuchen Sie es mit einem anderen Code oder generieren Sie einen neuen geheimen Schlüssel. Verwenden Sie eine kompatible 2FA-App, die 8-stellige Codes und SHA512 unterstützt.",
629 "enter_totp_code": "Bitte geben Sie den TOTP-Code ein.",
630 "add_secret_code": "Fügen Sie diesen Geheimcode einem anderen Gerät hinzu",
631 "totp_secret_code": "TOTP-Geheimcode",
632 "important_note": "Wichtiger Hinweis",
633 - "setup_2fa_text": "Cake 2FA ist NICHT so sicher wie eine Kühllagerung. 2FA schützt vor grundlegenden Arten von Angriffen, z. B. wenn Ihr Freund Ihren Fingerabdruck bereitstellt, während Sie schlafen.\n\n Cake 2FA schützt NICHT vor einem kompromittierten Gerät durch einen raffinierten Angreifer.\n\n Wenn Sie den Zugriff auf Ihre 2FA-Codes verlieren , VERLIEREN SIE DEN ZUGANG ZU DIESEM WALLET. Sie müssen Ihre Brieftasche aus mnemonic Seed wiederherstellen. SIE MÜSSEN DESHALB IHRE MNEMONISCHEN SEEDS SICHERN! Außerdem kann jemand mit Zugriff auf Ihre mnemonischen Seed(s) Ihr Geld stehlen und Cake 2FA umgehen.\n\n Cake-Supportmitarbeiter können Ihnen nicht helfen, wenn Sie den Zugriff auf Ihre mnemonischen Seed(s) verlieren, da Cake ein Brieftasche ohne Verwahrung.",
633 + "setup_2fa_text": "Cake 2FA ist NICHT so sicher wie eine Kühllagerung. 2FA schützt vor grundlegenden Arten von Angriffen, z. B. wenn Ihr Freund Ihren Fingerabdruck bereitstellt, während Sie schlafen.\n\n Cake 2FA schützt NICHT vor einem kompromittierten Gerät durch einen raffinierten Angreifer.\n\n Wenn Sie den Zugriff auf Ihre 2FA-Codes verlieren , VERLIEREN SIE DEN ZUGANG ZU DIESEM WALLET. Sie müssen Ihre Wallet aus mnemonic Seed wiederherstellen. SIE MÜSSEN DESHALB IHRE MNEMONISCHEN SEEDS SICHERN! Außerdem kann jemand mit Zugriff auf Ihre mnemonischen Seed(s) Ihr Geld stehlen und Cake 2FA umgehen.\n\n Cake-Supportmitarbeiter können Ihnen nicht helfen, wenn Sie den Zugriff auf Ihre mnemonischen Seed(s) verlieren, da Cake Wallet eine Wallet ohne treuhänderische Verwahrung ist.",
634 "setup_totp_recommended": "TOTP einrichten (empfohlen)",
635 "disable_buy": "Kaufaktion deaktivieren",
636 "disable_sell": "Verkaufsaktion deaktivieren",
@@ -640,7 +640,7 @@
640 "high_contrast_theme": "Kontrastreiches Thema",
641 "matrix_green_dark_theme": "Matrix Green Dark Theme",
642 "monero_light_theme": "Monero Light-Thema",
643 - "cake_2fa_preset": "Kuchen 2FA-Voreinstellung",
643 + "cake_2fa_preset" : "Cake 2FA-Voreinstellung",
644 "narrow": "Eng",
645 "normal": "Normal",
646 "aggressive": "Übereifrig",
scripts/docker/.gitignore new
+1
@@ -0,0 +1 @@
1 +output/
scripts/docker/Dockerfile new
+26
@@ -0,0 +1,26 @@
1 +FROM ubuntu:20.04
2 +LABEL authors="konsti"
3 +
4 +ENV MONERO_BRANCH=release-v0.18.2.2-android
5 +RUN apt-get update && \
6 + echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && \
7 + apt-get install -y dialog apt-utils curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang
8 +
9 +RUN mkdir /opt/android/
10 +
11 +COPY . /opt/android/cakewallet/
12 +
13 +WORKDIR /opt/android/cakewallet/
14 +
15 +
16 +RUN ./install_ndk.sh
17 +
18 +RUN ./build_iconv.sh
19 +RUN ./build_boost.sh
20 +RUN ./build_openssl.sh
21 +RUN ./build_sodium.sh
22 +RUN ./build_unbound.sh
23 +RUN ./build_zmq.sh
24 +
25 +
26 +ENTRYPOINT ["./entrypoint.sh"]
scripts/docker/build_all.sh new
+1
@@ -0,0 +1 @@
1 +#!/bin/sh if [ -z "$APP_ANDROID_TYPE" ]; then echo "Please set APP_ANDROID_TYPE" exit 1 fi DIR=$(dirname "$0") case $APP_ANDROID_TYPE in "monero.com") $DIR/build_monero_all.sh ;; "cakewallet") $DIR/build_monero_all.sh $DIR/build_haven.sh ;; "haven") $DIR/build_haven_all.sh ;; esac
\ No newline at end of file
scripts/docker/build_boost.sh new
+16
@@ -0,0 +1,16 @@
1 +#!/bin/bash
2 +
3 +. ./config.sh
4 +BOOST_SRC_DIR=$WORKDIR/boost_1_72_0
5 +BOOST_FILENAME=boost_1_72_0.tar.bz2
6 +BOOST_VERSION=1.72.0
7 +
8 +for arch in "aarch" "aarch64" "i686" "x86_64"
9 +do
10 +
11 +PREFIX=$WORKDIR/prefix_${arch}
12 +PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}"
13 +./init_boost.sh $arch $PREFIX $BOOST_SRC_DIR $BOOST_FILENAME $BOOST_VERSION
14 +./finish_boost.sh $arch $PREFIX $BOOST_SRC_DIR $BOOST_SRC_DIR
15 +
16 +done
scripts/docker/build_haven.sh new
+1
@@ -0,0 +1 @@
1 +#!/bin/sh . ./config.sh HAVEN_VERSION=tags/v3.0.7 HAVEN_SRC_DIR=${WORKDIR}/haven git clone https://github.com/haven-protocol-org/haven-main.git ${HAVEN_SRC_DIR} git checkout ${HAVEN_VERSION} cd $HAVEN_SRC_DIR git submodule init git submodule update for arch in "aarch" "aarch64" "i686" "x86_64" do FLAGS="" PREFIX=${WORKDIR}/prefix_${arch} DEST_LIB_DIR=${PREFIX}/lib/haven DEST_INCLUDE_DIR=${PREFIX}/include/haven export CMAKE_INCLUDE_PATH="${PREFIX}/include" export CMAKE_LIBRARY_PATH="${PREFIX}/lib" ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" mkdir -p $DEST_LIB_DIR mkdir -p $DEST_INCLUDE_DIR case $arch in "aarch" ) CLANG=arm-linux-androideabi-clang CXXLANG=arm-linux-androideabi-clang++ BUILD_64=OFF TAG="android-armv7" ARCH="armv7-a" ARCH_ABI="armeabi-v7a" FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";; "aarch64" ) CLANG=aarch64-linux-androideabi-clang CXXLANG=aarch64-linux-androideabi-clang++ BUILD_64=ON TAG="android-armv8" ARCH="armv8-a" ARCH_ABI="arm64-v8a";; "i686" ) CLANG=i686-linux-androideabi-clang CXXLANG=i686-linux-androideabi-clang++ BUILD_64=OFF TAG="android-x86" ARCH="i686" ARCH_ABI="x86";; "x86_64" ) CLANG=x86_64-linux-androideabi-clang CXXLANG=x86_64-linux-androideabi-clang++ BUILD_64=ON TAG="android-x86_64" ARCH="x86-64" ARCH_ABI="x86_64";; esac cd $HAVEN_SRC_DIR rm -rf ./build/release mkdir -p ./build/release cd ./build/release CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS ../.. make wallet_api -j$THREADS find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; cp -r ./lib/* $DEST_LIB_DIR cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR done
\ No newline at end of file
scripts/docker/build_haven_all.sh new
+1
@@ -0,0 +1 @@
1 +#!/bin/bash ./build_iconv.sh ./build_boost.sh ./build_openssl.sh ./build_sodium.sh ./build_zmq.sh ./build_haven.sh
\ No newline at end of file
scripts/docker/build_iconv.sh new
+38
@@ -0,0 +1,38 @@
1 +#!/bin/bash
2 +
3 +. ./config.sh
4 +export ICONV_FILENAME=libiconv-1.16.tar.gz
5 +export ICONV_FILE_PATH=$WORKDIR/$ICONV_FILENAME
6 +export ICONV_SRC_DIR=$WORKDIR/libiconv-1.16
7 +ICONV_SHA256="e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04"
8 +
9 +curl http://ftp.gnu.org/pub/gnu/libiconv/$ICONV_FILENAME -o $ICONV_FILE_PATH
10 +echo $ICONV_SHA256 $ICONV_FILE_PATH | sha256sum -c - || exit 1
11 +
12 +for arch in aarch aarch64 i686 x86_64
13 +do
14 +
15 +PREFIX=${WORKDIR}/prefix_${arch}
16 +PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}"
17 +
18 +case $arch in
19 + "aarch" )
20 + CLANG=arm-linux-androideabi-clang
21 + CXXLANG=arm-linux-androideabi-clang++
22 + HOST="arm-linux-android";;
23 + * )
24 + CLANG=${arch}-linux-android-clang
25 + CXXLANG=${arch}-linux-android-clang++
26 + HOST="${arch}-linux-android";;
27 +esac
28 +
29 +cd $WORKDIR
30 +rm -rf $ICONV_SRC_DIR
31 +tar -xzf $ICONV_FILE_PATH -C $WORKDIR
32 +cd $ICONV_SRC_DIR
33 +CC=${CLANG} CXX=${CXXLANG} ./configure --build=x86_64-linux-gnu --host=${HOST} --prefix=${PREFIX} --disable-rpath
34 +make -j$THREADS
35 +make install
36 +
37 +done
38 +
scripts/docker/build_monero.sh new
+69
@@ -0,0 +1,69 @@
1 +#!/bin/bash
2 +
3 +. ./config.sh
4 +
5 +MONERO_SRC_DIR=${WORKDIR}/monero
6 +
7 +git clone https://github.com/cake-tech/monero.git ${MONERO_SRC_DIR} --branch ${MONERO_BRANCH}
8 +cd $MONERO_SRC_DIR
9 +git submodule init
10 +git submodule update
11 +
12 +for arch in "aarch" "aarch64" "i686" "x86_64"
13 +do
14 +FLAGS=""
15 +PREFIX=${WORKDIR}/prefix_${arch}
16 +DEST_LIB_DIR=${PREFIX}/lib/monero
17 +DEST_INCLUDE_DIR=${PREFIX}/include/monero
18 +export CMAKE_INCLUDE_PATH="${PREFIX}/include"
19 +export CMAKE_LIBRARY_PATH="${PREFIX}/lib"
20 +ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}"
21 +PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}"
22 +
23 +mkdir -p $DEST_LIB_DIR
24 +mkdir -p $DEST_INCLUDE_DIR
25 +
26 +case $arch in
27 + "aarch" )
28 + CLANG=arm-linux-androideabi-clang
29 + CXXLANG=arm-linux-androideabi-clang++
30 + BUILD_64=OFF
31 + TAG="android-armv7"
32 + ARCH="armv7-a"
33 + ARCH_ABI="armeabi-v7a"
34 + FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";;
35 + "aarch64" )
36 + CLANG=aarch64-linux-androideabi-clang
37 + CXXLANG=aarch64-linux-androideabi-clang++
38 + BUILD_64=ON
39 + TAG="android-armv8"
40 + ARCH="armv8-a"
41 + ARCH_ABI="arm64-v8a";;
42 + "i686" )
43 + CLANG=i686-linux-androideabi-clang
44 + CXXLANG=i686-linux-androideabi-clang++
45 + BUILD_64=OFF
46 + TAG="android-x86"
47 + ARCH="i686"
48 + ARCH_ABI="x86";;
49 + "x86_64" )
50 + CLANG=x86_64-linux-androideabi-clang
51 + CXXLANG=x86_64-linux-androideabi-clang++
52 + BUILD_64=ON
53 + TAG="android-x86_64"
54 + ARCH="x86-64"
55 + ARCH_ABI="x86_64";;
56 +esac
57 +
58 +cd $MONERO_SRC_DIR
59 +rm -rf ./build/release
60 +mkdir -p ./build/release
61 +cd ./build/release
62 +CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS ../..
63 +
64 +make wallet_api -j$THREADS
65 +find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \;
66 +
67 +cp -r ./lib/* $DEST_LIB_DIR
68 +cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR
69 +done
scripts/docker/build_openssl.sh new
+55
@@ -0,0 +1,55 @@
1 +#!/bin/bash
2 +
3 +set -e
4 +
5 +. ./config.sh
6 +OPENSSL_FILENAME=openssl-1.1.1q.tar.gz
7 +OPENSSL_FILE_PATH=$WORKDIR/$OPENSSL_FILENAME
8 +OPENSSL_SRC_DIR=$WORKDIR/openssl-1.1.1q
9 +OPENSSL_SHA256="d7939ce614029cdff0b6c20f0e2e5703158a489a72b2507b8bd51bf8c8fd10ca"
10 +ZLIB_DIR=$WORKDIR/zlib
11 +ZLIB_TAG=v1.2.11
12 +ZLIB_COMMIT_HASH="cacf7f1d4e3d44d871b605da3b647f07d718623f"
13 +
14 +rm -rf $ZLIB_DIR
15 +git clone -b $ZLIB_TAG --depth 1 https://github.com/madler/zlib $ZLIB_DIR
16 +cd $ZLIB_DIR
17 +git reset --hard $ZLIB_COMMIT_HASH
18 +CC=clang CXX=clang++ ./configure --static
19 +make
20 +
21 +curl https://www.openssl.org/source/$OPENSSL_FILENAME -o $OPENSSL_FILE_PATH
22 +echo $OPENSSL_SHA256 $OPENSSL_FILE_PATH | sha256sum -c - || exit 1
23 +
24 +for arch in "aarch" "aarch64" "i686" "x86_64"
25 +do
26 +PREFIX=$WORKDIR/prefix_${arch}
27 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64
28 +PATH="${TOOLCHAIN}/bin:${ORIGINAL_PATH}"
29 +
30 +case $arch in
31 + "aarch") X_ARCH="android-arm";;
32 + "aarch64") X_ARCH="android-arm64";;
33 + "i686") X_ARCH="android-x86";;
34 + "x86_64") X_ARCH="android-x86_64";;
35 + *) X_ARCH="android-${arch}";;
36 +esac
37 +
38 +cd $WORKDIR
39 +rm -rf $OPENSSL_SRC_DIR
40 +tar -xzf $OPENSSL_FILE_PATH -C $WORKDIR
41 +cd $OPENSSL_SRC_DIR
42 +
43 +CC=clang ANDROID_NDK=$TOOLCHAIN \
44 + ./Configure ${X_ARCH} \
45 + no-shared no-tests \
46 + --with-zlib-include=${PREFIX}/include \
47 + --with-zlib-lib=${PREFIX}/lib \
48 + --prefix=${PREFIX} \
49 + --openssldir=${PREFIX} \
50 + -D__ANDROID_API__=$API
51 +make -j$THREADS
52 +make -j$THREADS install_sw
53 +
54 +done
55 +
scripts/docker/build_sodium.sh new
+30
@@ -0,0 +1,30 @@
1 +#!/bin/bash
2 +
3 +. ./config.sh
4 +SODIUM_SRC_DIR=${WORKDIR}/libsodium
5 +SODIUM_BRANCH=1.0.16
6 +
7 +for arch in "aarch" "aarch64" "i686" "x86_64"
8 +do
9 +
10 +PREFIX=${WORKDIR}/prefix_${arch}
11 +PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}"
12 +
13 +case $arch in
14 + "aarch" ) TARGET="arm";;
15 + "i686" ) TARGET="x86";;
16 + * ) TARGET="${arch}";;
17 +esac
18 +
19 +HOST="${TARGET}-linux-android"
20 +cd $WORKDIR
21 +rm -rf $SODIUM_SRC_DIR
22 +git clone https://github.com/jedisct1/libsodium.git $SODIUM_SRC_DIR -b $SODIUM_BRANCH
23 +cd $SODIUM_SRC_DIR
24 +./autogen.sh
25 +CC=clang CXX=clang++ ./configure --prefix=${PREFIX} --host=${HOST} --enable-static --disable-shared
26 +make -j$THREADS
27 +make install
28 +
29 +done
30 +
scripts/docker/build_unbound.sh new
+65
@@ -0,0 +1,65 @@
1 +#!/bin/bash
2 +
3 +. ./config.sh
4 +
5 +EXPAT_VERSION=R_2_4_8
6 +EXPAT_HASH="3bab6c09bbe8bf42d84b81563ddbcf4cca4be838"
7 +EXPAT_SRC_DIR=$WORKDIR/libexpat
8 +
9 +for arch in "aarch" "aarch64" "i686" "x86_64"
10 +do
11 +PREFIX=$WORKDIR/prefix_${arch}
12 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64
13 +PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}"
14 +
15 +cd $WORKDIR
16 +rm -rf $EXPAT_SRC_DIR
17 +git clone https://github.com/libexpat/libexpat.git -b ${EXPAT_VERSION} ${EXPAT_SRC_DIR}
18 +cd $EXPAT_SRC_DIR
19 +test `git rev-parse HEAD` = ${EXPAT_HASH} || exit 1
20 +cd $EXPAT_SRC_DIR/expat
21 +
22 +case $arch in
23 + "aarch") HOST="arm-linux-androideabi";;
24 + "i686") HOST="x86-linux-android";;
25 + *) HOST="${arch}-linux-android";;
26 +esac
27 +
28 +./buildconf.sh
29 +CC=clang CXX=clang++ ./configure --enable-static --disable-shared --prefix=${PREFIX} --host=${HOST}
30 +make -j$THREADS
31 +make -j$THREADS install
32 +done
33 +
34 +UNBOUND_VERSION=release-1.16.2
35 +UNBOUND_HASH="cbed768b8ff9bfcf11089a5f1699b7e5707f1ea5"
36 +UNBOUND_SRC_DIR=$WORKDIR/unbound-1.16.2
37 +
38 +for arch in "aarch" "aarch64" "i686" "x86_64"
39 +do
40 +PREFIX=$WORKDIR/prefix_${arch}
41 +TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64
42 +
43 +case $arch in
44 + "aarch") TOOLCHAIN_BIN_PATH=${TOOLCHAIN_BASE_DIR}_${arch}/arm-linux-androideabi/bin;;
45 + *) TOOLCHAIN_BIN_PATH=${TOOLCHAIN_BASE_DIR}_${arch}/${arch}-linux-android/bin;;
46 +esac
47 +
48 +PATH="${TOOLCHAIN_BIN_PATH}:${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}"
49 +echo $PATH
50 +cd $WORKDIR
51 +rm -rf $UNBOUND_SRC_DIR
52 +git clone https://github.com/NLnetLabs/unbound.git -b ${UNBOUND_VERSION} ${UNBOUND_SRC_DIR}
53 +cd $UNBOUND_SRC_DIR
54 +test `git rev-parse HEAD` = ${UNBOUND_HASH} || exit 1
55 +
56 +case $arch in
57 + "aarch") HOST="arm-linux-androideabi";;
58 + "i686") HOST="x86-linux-android";;
59 + *) HOST="${arch}-linux-android";;
60 +esac
61 +
62 +CC=clang CXX=clang++ ./configure --prefix=${PREFIX} --host=${HOST} --enable-static --disable-shared --disable-flto --with-ssl=${PREFIX} --with-libexpat=${PREFIX}
63 +make -j$THREADS
64 +make -j$THREADS install
65 +done
scripts/docker/build_zmq.sh new
+32
@@ -0,0 +1,32 @@
1 +#!/bin/bash
2 +
3 +. ./config.sh
4 +ZMQ_SRC_DIR=$WORKDIR/libzmq
5 +ZMQ_BRANCH=v4.3.3
6 +ZMQ_COMMIT_HASH=04f5bbedee58c538934374dc45182d8fc5926fa3
7 +
8 +for arch in "aarch" "aarch64" "i686" "x86_64"
9 +do
10 +
11 +PREFIX=$WORKDIR/prefix_${arch}
12 +PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}"
13 +
14 +case $arch in
15 + "aarch" ) TARGET="arm";;
16 + "i686" ) TARGET="x86";;
17 + * ) TARGET="${arch}";;
18 +esac
19 +
20 +
21 +HOST="${TARGET}-linux-android"
22 +cd $WORKDIR
23 +rm -rf $ZMQ_SRC_DIR
24 +git clone https://github.com/zeromq/libzmq.git ${ZMQ_SRC_DIR} -b ${ZMQ_BRANCH}
25 +cd $ZMQ_SRC_DIR
26 +git checkout ${ZMQ_COMMIT_HASH}
27 +./autogen.sh
28 +CC=clang CXX=clang++ ./configure --prefix=${PREFIX} --host=${HOST} --enable-static --disable-shared
29 +make -j$THREADS
30 +make install
31 +
32 +done
scripts/docker/config.sh new
+11
@@ -0,0 +1,11 @@
1 +#!/bin/bash
2 +
3 +export API=21
4 +export WORKDIR=/opt/android
5 +export ANDROID_NDK_ZIP=${WORKDIR}/android-ndk-r17c.zip
6 +export ANDROID_NDK_ROOT=${WORKDIR}/android-ndk-r17c
7 +export ANDROID_NDK_HOME=$ANDROID_NDK_ROOT
8 +export TOOLCHAIN_DIR="${WORKDIR}/toolchain"
9 +export TOOLCHAIN_BASE_DIR=$TOOLCHAIN_DIR
10 +export ORIGINAL_PATH=$PATH
11 +export THREADS=4
scripts/docker/copy_haven_deps.sh new
+45
@@ -0,0 +1,45 @@
1 +#!/bin/bash
2 +
3 +WORKDIR=/opt/android
4 +CW_DIR=${WORKDIR}/cake_wallet
5 +CW_EXRTERNAL_DIR=${CW_DIR}/cw_shared_external/ios/External/android
6 +CW_HAVEN_EXTERNAL_DIR=${CW_DIR}/cw_haven/ios/External/android
7 +CW_MONERO_EXTERNAL_DIR=${CW_DIR}/cw_monero/ios/External/android
8 +for arch in "aarch" "aarch64" "i686" "x86_64"
9 +do
10 +
11 +PREFIX=${WORKDIR}/prefix_${arch}
12 +ABI=""
13 +
14 +case $arch in
15 + "aarch" )
16 + ABI="armeabi-v7a";;
17 + "aarch64" )
18 + ABI="arm64-v8a";;
19 + "i686" )
20 + ABI="x86";;
21 + "x86_64" )
22 + ABI="x86_64";;
23 +esac
24 +
25 +LIB_DIR=${CW_EXRTERNAL_DIR}/${ABI}/lib
26 +INCLUDE_DIR=${CW_EXRTERNAL_DIR}/${ABI}/include
27 +LIBANBOUND_PATH=${PREFIX}/lib/libunbound.a
28 +
29 +mkdir -p $LIB_DIR
30 +mkdir -p $INCLUDE_DIR
31 +
32 +cp -r ${PREFIX}/lib/* $LIB_DIR
33 +cp -r ${PREFIX}/include/* $INCLUDE_DIR
34 +
35 +if [ -f "$LIBANBOUND_PATH" ]; then
36 + cp $LIBANBOUND_PATH ${LIB_DIR}/monero
37 +fi
38 +
39 +done
40 +
41 +mkdir -p ${CW_HAVEN_EXTERNAL_DIR}/include
42 +mkdir -p ${CW_MONERO_EXTERNAL_DIR}/include
43 +
44 +cp $CW_EXRTERNAL_DIR/x86/include/monero/wallet2_api.h ${CW_MONERO_EXTERNAL_DIR}/include
45 +cp $CW_EXRTERNAL_DIR/x86/include/haven/wallet2_api.h ${CW_HAVEN_EXTERNAL_DIR}/include
scripts/docker/copy_monero_deps.sh new
+37
@@ -0,0 +1,37 @@
1 +#!/bin/bash
2 +
3 +WORKDIR=/opt/android
4 +CW_EXRTERNAL_DIR=${WORKDIR}/output/android
5 +
6 +for arch in "aarch" "aarch64" "i686" "x86_64"
7 +do
8 +
9 +PREFIX=${WORKDIR}/prefix_${arch}
10 +ABI=""
11 +
12 +case $arch in
13 + "aarch" )
14 + ABI="armeabi-v7a";;
15 + "aarch64" )
16 + ABI="arm64-v8a";;
17 + "i686" )
18 + ABI="x86";;
19 + "x86_64" )
20 + ABI="x86_64";;
21 +esac
22 +
23 +LIB_DIR=${CW_EXRTERNAL_DIR}/${ABI}/lib
24 +INCLUDE_DIR=${CW_EXRTERNAL_DIR}/${ABI}/include
25 +LIBANBOUND_PATH=${PREFIX}/lib/libunbound.a
26 +
27 +mkdir -p $LIB_DIR
28 +mkdir -p $INCLUDE_DIR
29 +
30 +cp -r ${PREFIX}/lib/* $LIB_DIR
31 +cp -r ${PREFIX}/include/* $INCLUDE_DIR
32 +
33 +if [ -f "$LIBANBOUND_PATH" ]; then
34 + cp $LIBANBOUND_PATH ${LIB_DIR}/monero
35 +fi
36 +
37 +done
scripts/docker/docker-compose.yml new
+9
@@ -0,0 +1,9 @@
1 +version: '3.6'
2 +
3 +services:
4 + build_deps:
5 + image: build_monero_deps
6 + environment:
7 + MONERO_BRANCH: release-v0.18.2.2-android
8 + volumes:
9 + - ./output:/opt/android/output
scripts/docker/entrypoint.sh new
+4
@@ -0,0 +1,4 @@
1 +#!/bin/bash
2 +
3 +./build_monero.sh
4 +./copy_monero_deps.sh
scripts/docker/finish_boost.sh new
+9
@@ -0,0 +1,9 @@
1 +#!/bin/bash
2 +
3 +ARCH=$1
4 +PREFIX=$2
5 +BOOST_SRC_DIR=$3
6 +
7 +cd $BOOST_SRC_DIR
8 +
9 +./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --with-locale --build-dir=android --stagedir=android toolset=clang threading=multi threadapi=pthread target-os=android -sICONV_PATH=${PREFIX} -j$THREADS install
scripts/docker/init_boost.sh new
+22
@@ -0,0 +1,22 @@
1 +#!/bin/bash
2 +
3 +ARCH=$1
4 +PREFIX=$2
5 +BOOST_SRC_DIR=$3
6 +BOOST_FILENAME=$4
7 +BOOST_VERSION=$5
8 +BOOST_FILE_PATH=$WORKDIR/$BOOST_FILENAME
9 +BOOST_SHA256="59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722"
10 +
11 +if [ ! -e "$BOOST_FILE_PATH" ]; then
12 + curl -L http://downloads.sourceforge.net/project/boost/boost/${BOOST_VERSION}/${BOOST_FILENAME} > $BOOST_FILE_PATH
13 +fi
14 +
15 +echo $BOOST_SHA256 $BOOST_FILE_PATH | sha256sum -c - || exit 1
16 +
17 +cd $WORKDIR
18 +rm -rf $BOOST_SRC_DIR
19 +rm -rf $PREFIX/include/boost
20 +tar -xvf $BOOST_FILE_PATH -C $WORKDIR
21 +cd $BOOST_SRC_DIR
22 +./bootstrap.sh --prefix=${PREFIX}
scripts/docker/install_ndk.sh new
+18
@@ -0,0 +1,18 @@
1 +#!/bin/bash
2 +
3 +. ./config.sh
4 +TOOLCHAIN_DIR=${WORKDIR}/toolchain
5 +TOOLCHAIN_A32_DIR=${TOOLCHAIN_DIR}_aarch
6 +TOOLCHAIN_A64_DIR=${TOOLCHAIN_DIR}_aarch64
7 +TOOLCHAIN_x86_DIR=${TOOLCHAIN_DIR}_i686
8 +TOOLCHAIN_x86_64_DIR=${TOOLCHAIN_DIR}_x86_64
9 +ANDROID_NDK_SHA256="3f541adbd0330a9205ba12697f6d04ec90752c53d6b622101a2a8a856e816589"
10 +
11 +curl https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip -o ${ANDROID_NDK_ZIP}
12 +echo $ANDROID_NDK_SHA256 $ANDROID_NDK_ZIP | sha256sum -c || exit 1
13 +unzip $ANDROID_NDK_ZIP -d $WORKDIR
14 +
15 +${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch arm64 --api $API --install-dir ${TOOLCHAIN_A64_DIR} --stl=libc++
16 +${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch arm --api $API --install-dir ${TOOLCHAIN_A32_DIR} --stl=libc++
17 +${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch x86 --api $API --install-dir ${TOOLCHAIN_x86_DIR} --stl=libc++
18 +${ANDROID_NDK_ROOT}/build/tools/make_standalone_toolchain.py --arch x86_64 --api $API --install-dir ${TOOLCHAIN_x86_64_DIR} --stl=libc++
scripts/ios/build_monero.sh
+1 -1
@@ -59,4 +59,4 @@ cp -r ./lib/* $DEST_LIB_DIR
59 cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR
60 popd
61
62 -done
\ No newline at end of file
62 +done
tool/append_translation.dart
+66 -66
@@ -1,66 +1,66 @@
1 -// import 'dart:convert';
2 -// import 'dart:io';
3 -//
4 -// import 'package:translator/translator.dart';
5 -//
6 -// const defaultLang = "en";
7 -// const langs = [
8 -// "ar", "bg", "cs", "de", "en", "es", "fr", "ha", "hi", "hr", "id", "it",
9 -// "ja", "ko", "my", "nl", "pl", "pt", "ru", "th", "tr", "uk", "ur", "yo",
10 -// "zh-cn" // zh, but Google Translate uses zh-cn for Chinese (Simplified)
11 -// ];
12 -// final translator = GoogleTranslator();
13 -//
14 -// void main(List<String> args) async {
15 -// if (args.length != 2) {
16 -// throw Exception(
17 -// 'Insufficient arguments!\n\nTry to run `./append_translation.dart greetings "Hello World!"`');
18 -// }
19 -//
20 -// final name = args.first;
21 -// final text = args.last;
22 -//
23 -// print('Appending "$name": "$text"');
24 -//
25 -// for (var lang in langs) {
26 -// final fileName = getFileName(lang);
27 -// final translation = await getTranslation(text, lang);
28 -//
29 -// appendArbFile(fileName, name, translation);
30 -// }
31 -// }
32 -//
33 -// void appendArbFile(String fileName, String name, String text) {
34 -// final file = File(fileName);
35 -// final inputContent = file.readAsStringSync();
36 -// final arbObj = json.decode(inputContent) as Map<String, dynamic>;
37 -//
38 -// if (arbObj.containsKey(name)) {
39 -// print("String $name already exists in $fileName!");
40 -// return;
41 -// }
42 -//
43 -// arbObj.addAll({name: text});
44 -//
45 -// final outputContent = json
46 -// .encode(arbObj)
47 -// .replaceAll('","', '",\n "')
48 -// .replaceAll('{"', '{\n "')
49 -// .replaceAll('"}', '"\n}')
50 -// .replaceAll('":"', '": "');
51 -//
52 -// file.writeAsStringSync(outputContent);
53 -// }
54 -//
55 -//
56 -// Future<String> getTranslation(String text, String lang) async {
57 -// if (lang == defaultLang) return text;
58 -// return (await translator.translate(text, from: defaultLang, to: lang)).text;
59 -// }
60 -//
61 -// String getFileName(String lang) {
62 -// final shortLang = lang
63 -// .split("-")
64 -// .first;
65 -// return "./res/values/strings_$shortLang.arb";
66 -// }
1 +import 'dart:convert';
2 +import 'dart:io';
3 +
4 +import 'package:translator/translator.dart';
5 +
6 +const defaultLang = "en";
7 +const langs = [
8 + "ar", "bg", "cs", "de", "en", "es", "fr", "ha", "hi", "hr", "id", "it",
9 + "ja", "ko", "my", "nl", "pl", "pt", "ru", "th", "tr", "uk", "ur", "yo",
10 + "zh-cn" // zh, but Google Translate uses zh-cn for Chinese (Simplified)
11 +];
12 +final translator = GoogleTranslator();
13 +
14 +void main(List<String> args) async {
15 + if (args.length != 2) {
16 + throw Exception(
17 + 'Insufficient arguments!\n\nTry to run `./append_translation.dart greetings "Hello World!"`');
18 + }
19 +
20 + final name = args.first;
21 + final text = args.last;
22 +
23 + print('Appending "$name": "$text"');
24 +
25 + for (var lang in langs) {
26 + final fileName = getFileName(lang);
27 + final translation = await getTranslation(text, lang);
28 +
29 + appendArbFile(fileName, name, translation);
30 + }
31 +}
32 +
33 +void appendArbFile(String fileName, String name, String text) {
34 + final file = File(fileName);
35 + final inputContent = file.readAsStringSync();
36 + final arbObj = json.decode(inputContent) as Map<String, dynamic>;
37 +
38 + if (arbObj.containsKey(name)) {
39 + print("String $name already exists in $fileName!");
40 + return;
41 + }
42 +
43 + arbObj.addAll({name: text});
44 +
45 + final outputContent = json
46 + .encode(arbObj)
47 + .replaceAll('","', '",\n "')
48 + .replaceAll('{"', '{\n "')
49 + .replaceAll('"}', '"\n}')
50 + .replaceAll('":"', '": "');
51 +
52 + file.writeAsStringSync(outputContent);
53 +}
54 +
55 +
56 +Future<String> getTranslation(String text, String lang) async {
57 + if (lang == defaultLang) return text;
58 + return (await translator.translate(text, from: defaultLang, to: lang)).text;
59 +}
60 +
61 +String getFileName(String lang) {
62 + final shortLang = lang
63 + .split("-")
64 + .first;
65 + return "./res/values/strings_$shortLang.arb";
66 +}
tool/configure.dart
+9 -20
@@ -25,6 +25,7 @@ Future<void> main(List<String> args) async {
25 Future<void> generateBitcoin(bool hasImplementation) async {
26 final outputFile = File(bitcoinOutputPath);
27 const bitcoinCommonHeaders = """
28 +import 'package:cake_wallet/entities/unspent_transaction_output.dart';
29 import 'package:cw_core/wallet_credentials.dart';
30 import 'package:cw_core/wallet_info.dart';
31 import 'package:cw_core/transaction_priority.dart';
@@ -48,24 +49,6 @@ import 'package:cw_bitcoin/litecoin_wallet_service.dart';
49 """;
50 const bitcoinCwPart = "part 'cw_bitcoin.dart';";
51 const bitcoinContent = """
51 -class Unspent {
52 - Unspent(this.address, this.hash, this.value, this.vout)
53 - : isSending = true,
54 - isFrozen = false,
55 - note = '';
56 -
57 - final String address;
58 - final String hash;
59 - final int value;
60 - final int vout;
61 -
62 - bool isSending;
63 - bool isFrozen;
64 - String note;
65 -
66 - bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');
67 -}
68 -
52 abstract class Bitcoin {
53 TransactionPriority getMediumTransactionPriority();
54
@@ -122,6 +105,9 @@ abstract class Bitcoin {
105 Future<void> generateMonero(bool hasImplementation) async {
106 final outputFile = File(moneroOutputPath);
107 const moneroCommonHeaders = """
108 +import 'package:cake_wallet/entities/unspent_transaction_output.dart';
109 +import 'package:cw_core/unspent_coins_info.dart';
110 +import 'package:cw_monero/monero_unspent.dart';
111 import 'package:mobx/mobx.dart';
112 import 'package:flutter/foundation.dart';
113 import 'package:cw_core/wallet_credentials.dart';
@@ -231,6 +217,9 @@ abstract class Monero {
217 TransactionPriority deserializeMoneroTransactionPriority({required int raw});
218 List<TransactionPriority> getTransactionPriorities();
219 List<String> getMoneroWordList(String language);
220 +
221 + List<Unspent> getUnspents(Object wallet);
222 + void updateUnspents(Object wallet);
223
224 WalletCredentials createMoneroRestoreWalletFromKeysCredentials({
225 required String name,
@@ -252,7 +241,7 @@ abstract class Monero {
241 void setCurrentAccount(Object wallet, int id, String label, String? balance);
242 void onStartup();
243 int getTransactionInfoAccountId(TransactionInfo tx);
255 - WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource);
244 + WalletService createMoneroWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
245 Map<String, String> pendingTransactionInfo(Object transaction);
246 }
247
@@ -650,4 +639,4 @@ Future<void> generateWalletTypes({required bool hasMonero, required bool hasBitc
639
640 outputContent += '];\n';
641 await walletTypesFile.writeAsString(outputContent);
653 -}
\ No newline at end of file
642 +}