v4.23.0 release candidate (#1974)

* v4.23.0 release candidate * - Fix restoring zano from QR - Fix Zano confirmations count - Fix birdpay - Fix balance display * Fix Zano assets showing amount before they are added * - handle fetching token data while the API is busy - potential fix for duplicate transactions * fix receive confirmations, maybe * revert onChangeWallet cleanup * Fix confirmations not updating * improve zano wallet opening, fix CI commands and messages on slack (#1979) Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Cache wallet when creating/restoring as well * - hardcode Trocador Maximum limit for Zano temporarily - Configure Cake Zano node to use SSL * reformatting [skip ci] * revert to non-ssl * update build numbers [skip ci] * disable zano for desktop [skip ci] --------- Co-authored-by: cyan <cyjan@mrcyjanek.net>

Omar Hatem committed Jan 28, 2025 at 23:53 UTC 141a7ebfcafa2550ae867616db8c7b8b0f7b0b75
42 files changed +472 -306
.github/workflows/pr_test_build_android.yml
+11 -2
@@ -34,10 +34,19 @@ jobs:
34 - name: Fix github actions messing up $HOME...
35 run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
36 - uses: actions/checkout@v4
37 + with:
38 + ref: ${{ github.event.pull_request.head.sha }}
39 - name: configure git
40 run: |
41 + git config --global --add safe.directory '*'
42 git config --global user.email "ci@cakewallet.com"
43 git config --global user.name "CakeWallet CI"
44 + - name: Get the full commit message
45 + run: |
46 + FULL_MESSAGE="$(git log -1 --pretty=%B)"
47 + echo "message<<EOF" >> $GITHUB_ENV
48 + echo "$FULL_MESSAGE" >> $GITHUB_ENV
49 + echo "EOF" >> $GITHUB_ENV
50 - name: Add secrets
51 run: |
52 touch lib/.secrets.g.dart
@@ -274,14 +283,14 @@ jobs:
283 echo "APK_FILE=$apk_file" >> $GITHUB_ENV
284
285 - name: Upload artifact to slack
277 - if: ${{ !contains(github.event.head_commit.message, 'skip slack') }}
286 + if: ${{ !contains(env.message, 'skip slack') }}
287 continue-on-error: true
288 uses: adrey/slack-file-upload-action@1.0.5
289 with:
290 token: ${{ secrets.SLACK_APP_TOKEN }}
291 path: ${{ env.APK_FILE }}
292 channel: ${{ secrets.SLACK_APK_CHANNEL }}
284 - initial_comment: ${{ github.event.head_commit.message }}
293 + initial_comment: ${{ env.message }}
294
295 - name: cleanup
296 run: rm -rf build/app/outputs/flutter-apk/test-apk/
.github/workflows/pr_test_build_linux.yml
+14 -5
@@ -30,10 +30,19 @@ jobs:
30 - name: Fix github actions messing up $HOME...
31 run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
32 - uses: actions/checkout@v4
33 + with:
34 + ref: ${{ github.event.pull_request.head.sha }}
35 - name: configure git
36 run: |
37 + git config --global --add safe.directory '*'
38 git config --global user.email "ci@cakewallet.com"
39 git config --global user.name "CakeWallet CI"
40 + - name: Get the full commit message
41 + run: |
42 + FULL_MESSAGE="$(git log -1 --pretty=%B)"
43 + echo "message<<EOF" >> $GITHUB_ENV
44 + echo "$FULL_MESSAGE" >> $GITHUB_ENV
45 + echo "EOF" >> $GITHUB_ENV
46 - name: Add secrets
47 run: |
48 touch lib/.secrets.g.dart
@@ -231,7 +240,7 @@ jobs:
240 name: cakewallet_linux
241
242 - name: Prepare virtual desktop
234 - if: ${{ contains(github.event.head_commit.message, 'run tests') }}
243 + if: ${{ contains(env.message, 'run tests') }}
244 run: |
245 nohup Xvfb :99 -screen 0 720x1280x16 &
246 echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV
@@ -247,28 +256,28 @@ jobs:
256 # isn't much in those wallets anyway, we still wouldn't like to leak it to anyone who is able to access github.
257
258 - name: Test [confirm_seeds_flow_test]
250 - if: ${{ contains(github.event.head_commit.message, 'run tests') }}
259 + if: ${{ contains(env.message, 'run tests') }}
260 timeout-minutes: 20
261 run: |
262 xmessage -timeout 30 "confirm_seeds_flow_test" &
263 rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
264 exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/confirm_seeds_flow_test.dart
265 - name: Test [create_wallet_flow_test]
257 - if: ${{ contains(github.event.head_commit.message, 'run tests') }}
266 + if: ${{ contains(env.message, 'run tests') }}
267 timeout-minutes: 20
268 run: |
269 xmessage -timeout 30 "create_wallet_flow_test" &
270 rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
271 exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/create_wallet_flow_test.dart
272 - name: Test [exchange_flow_test]
264 - if: ${{ contains(github.event.head_commit.message, 'run tests') }}
273 + if: ${{ contains(env.message, 'run tests') }}
274 timeout-minutes: 20
275 run: |
276 xmessage -timeout 30 "exchange_flow_test" &
277 rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
278 exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/exchange_flow_test.dart
279 - name: Test [restore_wallet_through_seeds_flow_test]
271 - if: ${{ contains(github.event.head_commit.message, 'run tests') }}
280 + if: ${{ contains(env.message, 'run tests') }}
281 timeout-minutes: 20
282 run: |
283 xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" &
assets/text/Monerocom_Release_Notes.txt
+2
@@ -1,2 +1,4 @@
1 +Monero restore with passphrase support
2 +Add Chainflip Exchange provider
3 UI enhancements
4 Bug fixes
\ No newline at end of file
assets/text/Release_Notes.txt
+3 -2
@@ -1,4 +1,5 @@
1 -Bitcoin and Litecoin enhancements
2 -Solana and Nano fixes/improvements
1 +Add Zano Wallet
2 +Monero/Wownero/Zano restore with passphrase support
3 +Add Chainflip Exchange provider
4 UI enhancements
5 Bug fixes
\ No newline at end of file
assets/zano_node_list.yml
+3
@@ -1,4 +1,7 @@
1 -
2 uri: 37.27.100.59:10500
3 + useSSL: false
4 +-
5 + uri: zano.cakewallet.com:11211
6 is_default: true
7 useSSL: false
\ No newline at end of file
cw_core/lib/node.dart
+38 -19
@@ -102,10 +102,9 @@ class Node extends HiveObject with Keyable {
102 case WalletType.polygon:
103 case WalletType.solana:
104 case WalletType.tron:
105 - return Uri.parse(
106 - "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
105 case WalletType.zano:
108 - return Uri.https(uriRaw, '');
106 + return Uri.parse(
107 + "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") || path!.isEmpty ? path : "/$path"}");
108 case WalletType.none:
109 throw Exception('Unexpected type ${type.toString()} for Node uri');
110 }
@@ -177,7 +176,33 @@ class Node extends HiveObject with Keyable {
176 }
177
178 Future<bool> requestZanoNode() async {
180 - return requestMoneroNode(methodName: "getinfo");
179 + final path = '/json_rpc';
180 + final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
181 + final body = {'jsonrpc': '2.0', 'id': '0', 'method': "getinfo"};
182 +
183 + try {
184 + final authenticatingClient = HttpClient();
185 + authenticatingClient.badCertificateCallback =
186 + ((X509Certificate cert, String host, int port) => true);
187 +
188 + final http.Client client = ioc.IOClient(authenticatingClient);
189 +
190 + final jsonBody = json.encode(body);
191 +
192 + final response = await client.post(
193 + rpcUri,
194 + headers: {'Content-Type': 'application/json'},
195 + body: jsonBody,
196 + );
197 +
198 + printV("node check response: ${response.body}");
199 +
200 + final resBody = json.decode(response.body) as Map<String, dynamic>;
201 + return resBody['result']['height'] != null;
202 + } catch (e) {
203 + printV("error: $e");
204 + return false;
205 + }
206 }
207
208 Future<bool> requestMoneroNode({String methodName = 'get_info'}) async {
@@ -185,7 +210,6 @@ class Node extends HiveObject with Keyable {
210 return await requestNodeWithProxy();
211 }
212
188 -
213 final path = '/json_rpc';
214 final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
215 final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
@@ -195,7 +219,6 @@ class Node extends HiveObject with Keyable {
219 authenticatingClient.badCertificateCallback =
220 ((X509Certificate cert, String host, int port) => true);
221
198 -
222 final http.Client client = ioc.IOClient(authenticatingClient);
223
224 final jsonBody = json.encode(body);
@@ -209,13 +232,13 @@ class Node extends HiveObject with Keyable {
232 if (response.statusCode == 401) {
233 final daemonRpc = DaemonRpc(
234 rpcUri.toString(),
212 - username: login??'',
213 - password: password??'',
235 + username: login ?? '',
236 + password: password ?? '',
237 );
238 final response = await daemonRpc.call('get_info', {});
239 return !(response['offline'] as bool);
240 }
218 -
241 +
242 printV("node check response: ${response.body}");
243
244 if ((response.body.contains("400 Bad Request") // Some other generic error
@@ -298,10 +321,7 @@ class Node extends HiveObject with Keyable {
321 try {
322 final response = await http.post(
323 uri,
301 - headers: {
302 - "Content-Type": "application/json",
303 - "nano-app": "cake-wallet"
304 - },
324 + headers: {"Content-Type": "application/json", "nano-app": "cake-wallet"},
325 body: jsonEncode(
326 {
327 "action": "account_balance",
@@ -407,8 +427,7 @@ class DigestAuth {
427 }
428
429 /// Helper to format the nonce count.
410 - String _formatNonceCount(int count) =>
411 - count.toRadixString(16).padLeft(8, '0');
430 + String _formatNonceCount(int count) => count.toRadixString(16).padLeft(8, '0');
431
432 /// Compute the MD5 hash of a string.
433 String md5Hash(String input) {
@@ -424,8 +443,7 @@ class DaemonRpc {
443 DaemonRpc(this.rpcUrl, {required this.username, required this.password});
444
445 /// Perform a JSON-RPC call with Digest Authentication.
427 - Future<Map<String, dynamic>> call(
428 - String method, Map<String, dynamic> params) async {
446 + Future<Map<String, dynamic>> call(String method, Map<String, dynamic> params) async {
447 final http.Client client = http.Client();
448 final DigestAuth digestAuth = DigestAuth(username, password);
449
@@ -475,11 +493,12 @@ class DaemonRpc {
493 throw Exception('RPC call failed: ${authenticatedResponse.body}');
494 }
495
478 - final Map<String, dynamic> result = jsonDecode(authenticatedResponse.body) as Map<String, dynamic>;
496 + final Map<String, dynamic> result =
497 + jsonDecode(authenticatedResponse.body) as Map<String, dynamic>;
498 if (result['error'] != null) {
499 throw Exception('RPC Error: ${result['error']}');
500 }
501
502 return result['result'] as Map<String, dynamic>;
503 }
485 -}
\ No newline at end of file
504 +}
cw_monero/pubspec.lock
+2 -2
@@ -511,8 +511,8 @@ packages:
511 dependency: "direct main"
512 description:
513 path: "impls/monero.dart"
514 - ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
515 - resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
514 + ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
515 + resolved-ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
516 url: "https://github.com/mrcyjanek/monero_c"
517 source: git
518 version: "0.0.0"
cw_wownero/pubspec.lock
+2 -2
@@ -471,8 +471,8 @@ packages:
471 dependency: "direct main"
472 description:
473 path: "impls/monero.dart"
474 - ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
475 - resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
474 + ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
475 + resolved-ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
476 url: "https://github.com/mrcyjanek/monero_c"
477 source: git
478 version: "0.0.0"
cw_zano/lib/api/model/transfer.dart
+56 -49
@@ -1,12 +1,10 @@
1 +import 'package:cw_core/utils/print_verbose.dart';
2 import 'package:cw_core/zano_asset.dart';
3 import 'package:cw_zano/api/model/employed_entries.dart';
4 import 'package:cw_zano/api/model/subtransfer.dart';
5 import 'package:collection/collection.dart';
5 -import 'package:cw_zano/model/zano_asset.dart';
6 import 'package:cw_zano/model/zano_transaction_info.dart';
7 -import 'package:cw_zano/zano_formatter.dart';
7 import 'package:cw_zano/zano_wallet.dart';
9 -import 'package:cw_zano/zano_wallet_api.dart';
8
9 class Transfer {
10 final String comment;
@@ -49,78 +47,87 @@ class Transfer {
47 required this.unlockTime,
48 });
49
52 - factory Transfer.fromJson(Map<String, dynamic> json) => Transfer(
50 + factory Transfer.fromJson(Map<String, dynamic> json) =>
51 + Transfer(
52 comment: json['comment'] as String? ?? '',
54 - employedEntries: EmployedEntries.fromJson(json['employed_entries'] as Map<String, dynamic>? ?? {}),
53 + employedEntries: EmployedEntries.fromJson(
54 + json['employed_entries'] as Map<String, dynamic>? ?? {}),
55 fee: json['fee'] as int? ?? 0,
56 height: json['height'] as int? ?? 0,
57 isMining: json['is_mining'] as bool? ?? false,
58 isMixing: json['is_mixing'] as bool? ?? false,
59 isService: json['is_service'] as bool? ?? false,
60 paymentId: json['payment_id'] as String? ?? '',
61 - remoteAddresses: json['remote_addresses'] == null ? [] : (json['remote_addresses'] as List<dynamic>).cast<String>(),
62 - remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<dynamic>).cast<String>(),
61 + remoteAddresses: json['remote_addresses'] == null ? [] : (json['remote_addresses'] as List<
62 + dynamic>).cast<String>(),
63 + remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<
64 + dynamic>).cast<String>(),
65 showSender: json['show_sender'] as bool? ?? false,
64 - subtransfers: (json['subtransfers'] as List<dynamic>? ?? []).map((e) => Subtransfer.fromJson(e as Map<String, dynamic>)).toList(),
66 + subtransfers: (json['subtransfers'] as List<dynamic>? ?? []).map((e) =>
67 + Subtransfer.fromJson(e as Map<String, dynamic>)).toList(),
68 timestamp: json['timestamp'] as int? ?? 0,
69 transferInternalIndex: json['transfer_internal_index'] == null
70 ? 0
71 : json['transfer_internal_index'] is double
69 - ? (json['transfer_internal_index'] as double).toInt()
70 - : json['transfer_internal_index'] as int,
72 + ? (json['transfer_internal_index'] as double).toInt()
73 + : json['transfer_internal_index'] as int,
74 txBlobSize: json['tx_blob_size'] as int? ?? 0,
75 txHash: json['tx_hash'] as String? ?? '',
76 txType: json['tx_type'] as int? ?? 0,
77 unlockTime: json['unlock_time'] as int? ?? 0,
78 );
79
77 - static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers, Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) {
80 + static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers,
81 + Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) {
82 return Map.fromIterable(
79 - transfers,
80 - key: (item) => (item as Transfer).txHash,
81 - value: (transfer) {
82 - transfer as Transfer;
83 - // Simple (only one subtransfer OR two subtransfers and the second is Zano, outgoing and amount equals to fee) or complex?
84 - Subtransfer? single = transfer.subtransfers.singleOrNull;
85 - if (transfer.subtransfers.length == 2) {
86 - final zano = transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId);
87 - if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) {
88 - single = transfer.subtransfers.firstWhere((element) => element.assetId != ZanoWalletBase.zanoAssetId);
89 - }
83 + transfers,
84 + key: (item) => (item as Transfer).txHash,
85 + value: (transfer) {
86 + transfer as Transfer;
87 + // Simple (only one subtransfer OR two subtransfers and the second is Zano, outgoing and amount equals to fee) or complex?
88 + Subtransfer? single = transfer.subtransfers.singleOrNull;
89 + if (transfer.subtransfers.length == 2) {
90 + final zano = transfer.subtransfers.firstWhereOrNull((element) =>
91 + element.assetId == ZanoWalletBase.zanoAssetId);
92 + if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) {
93 + single = transfer.subtransfers.firstWhere((element) => element.assetId !=
94 + ZanoWalletBase.zanoAssetId);
95 }
91 - bool isSimple = single != null;
92 - // TODO: for complex transactions we show zano or any other transaction, will fix it later
93 - if (!isSimple) {
94 - single =
95 - transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId) ?? transfer.subtransfers.first;
96 + }
97 + bool isSimple = single != null;
98 + // TODO: for complex transactions we show zano or any other transaction, will fix it later
99 + if (!isSimple) {
100 + single =
101 + transfer.subtransfers.firstWhereOrNull((element) =>
102 + element.assetId == ZanoWalletBase.zanoAssetId) ?? transfer.subtransfers.first;
103 + }
104 + if (single.assetId != ZanoWalletBase.zanoAssetId) {
105 + final asset = zanoAssets[single.assetId];
106 + if (asset == null) {
107 + printV('unknown asset ${single.assetId}');
108 }
97 - if (single.assetId != ZanoWalletBase.zanoAssetId) {
98 - final asset = zanoAssets[single.assetId];
99 - if (asset == null) {
100 - ZanoWalletApi.error('unknown asset ${single.assetId}');
101 - }
102 - final ticker = asset == null ? '***' : asset.ticker;
103 - final decimalPoint = asset == null ? ZanoFormatter.defaultDecimalPoint : asset.decimalPoint;
104 - return ZanoTransactionInfo.fromTransfer(
105 - transfer,
106 - confirmations: currentDaemonHeight - transfer.height,
107 - isIncome: single.isIncome,
108 - assetId: single.assetId,
109 - amount: single.amount,
110 - tokenSymbol: isSimple ? ticker : '*${ticker}',
111 - decimalPoint: decimalPoint,
112 - );
113 - }
114 - final amount = single.isIncome ? single.amount : single.amount - BigInt.from(transfer.fee);
109 + final ticker = asset == null ? '***' : asset.ticker;
110 + final decimalPoint = asset == null ? 0 : asset.decimalPoint;
111 return ZanoTransactionInfo.fromTransfer(
112 transfer,
113 confirmations: currentDaemonHeight - transfer.height,
114 isIncome: single.isIncome,
115 assetId: single.assetId,
120 - amount: amount,
121 - tokenSymbol: isSimple ? 'ZANO' : '*ZANO',
116 + amount: single.amount,
117 + tokenSymbol: isSimple ? ticker : '*${ticker}',
118 + decimalPoint: decimalPoint,
119 );
123 - },
124 - );
120 + }
121 + final amount = single.isIncome ? single.amount : single.amount - BigInt.from(transfer.fee);
122 + return ZanoTransactionInfo.fromTransfer(
123 + transfer,
124 + confirmations: currentDaemonHeight - transfer.height,
125 + isIncome: single.isIncome,
126 + assetId: single.assetId,
127 + amount: amount,
128 + tokenSymbol: isSimple ? 'ZANO' : '*ZANO',
129 + );
130 + },
131 + );
132 }
133 }
cw_zano/lib/model/pending_zano_transaction.dart
+2 -2
@@ -32,10 +32,10 @@ class PendingZanoTransaction with PendingTransaction {
32 String get hex => '';
33
34 @override
35 - String get amountFormatted => '${ZanoFormatter.bigIntAmountToString(amount, decimalPoint)} $ticker';
35 + String get amountFormatted => ZanoFormatter.bigIntAmountToString(amount, decimalPoint);
36
37 @override
38 - String get feeFormatted => '${ZanoFormatter.bigIntAmountToString(fee)} ZANO';
38 + String get feeFormatted => ZanoFormatter.bigIntAmountToString(fee);
39
40 TransferResult? transferResult;
41
cw_zano/lib/model/zano_transaction_info.dart
+13 -8
@@ -23,11 +23,11 @@ class ZanoTransactionInfo extends TransactionInfo {
23
24 ZanoTransactionInfo.fromTransfer(Transfer transfer,
25 {required int confirmations,
26 - required bool isIncome,
27 - required String assetId,
28 - required BigInt amount,
29 - this.tokenSymbol = 'ZANO',
30 - this.decimalPoint = ZanoFormatter.defaultDecimalPoint})
26 + required bool isIncome,
27 + required String assetId,
28 + required BigInt amount,
29 + this.tokenSymbol = 'ZANO',
30 + this.decimalPoint = ZanoFormatter.defaultDecimalPoint})
31 : id = transfer.txHash,
32 height = transfer.height,
33 direction = isIncome ? TransactionDirection.incoming : TransactionDirection.outgoing,
@@ -36,14 +36,18 @@ class ZanoTransactionInfo extends TransactionInfo {
36 amount = amount.isValidInt ? amount.toInt() : 0,
37 fee = transfer.fee,
38 confirmations = confirmations,
39 - isPending = false,
40 - recipientAddress = transfer.remoteAddresses.isNotEmpty ? transfer.remoteAddresses.first : '' {
39 + isPending = confirmations < 10,
40 + recipientAddress = transfer.remoteAddresses.isNotEmpty
41 + ? transfer.remoteAddresses.first
42 + : '' {
43 additionalInfo = <String, dynamic>{
44 'comment': transfer.comment,
45 'assetId': assetId,
46 };
47 }
48 +
49 String get assetId => additionalInfo["assetId"] as String;
50 +
51 set assetId(String newId) => additionalInfo["assetId"] = newId;
52 final String id;
53 final int height;
@@ -61,7 +65,8 @@ class ZanoTransactionInfo extends TransactionInfo {
65 String? key;
66
67 @override
64 - String amountFormatted() => '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol';
68 + String amountFormatted() =>
69 + '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol';
70
71 @override
72 String fiatAmount() => _fiatAmount ?? '';
cw_zano/lib/zano_formatter.dart
+5 -1
@@ -1,5 +1,6 @@
1 import 'dart:math';
2
3 +import 'package:cw_core/utils/print_verbose.dart';
4 import 'package:cw_zano/zano_wallet_api.dart';
5 import 'package:decimal/decimal.dart';
6 import 'package:decimal/intl.dart';
@@ -32,6 +33,9 @@ class ZanoFormatter {
33 }
34
35 static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) {
36 + if (decimalPoint == 0) {
37 + return '0';
38 + }
39 final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
40 ..minimumFractionDigits = 1;
41 return numberFormat.format(
@@ -66,7 +70,7 @@ class ZanoFormatter {
70 } else if (d == null) {
71 return BigInt.zero;
72 } else {
69 - ZanoWalletApi.error('cannot cast value of type ${d.runtimeType} to BigInt');
73 + printV(('cannot cast value of type ${d.runtimeType} to BigInt'));
74 throw 'cannot cast value of type ${d.runtimeType} to BigInt';
75 //return BigInt.zero;
76 }
cw_zano/lib/zano_wallet.dart
+90 -79
@@ -1,4 +1,5 @@
1 import 'dart:async';
2 +import 'dart:core';
3 import 'dart:io';
4 import 'dart:math';
5
@@ -43,7 +44,7 @@ abstract class ZanoWalletBase
44 extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo>
45 with Store, ZanoWalletApi {
46 static const int _autoSaveIntervalSeconds = 30;
46 - static const int _pollIntervalMilliseconds = 2000;
47 + static const int _pollIntervalMilliseconds = 5000;
48 static const int _maxLoadAssetsRetries = 5;
49
50 @override
@@ -152,19 +153,27 @@ abstract class ZanoWalletBase
153 static Future<ZanoWallet> open(
154 {required String name, required String password, required WalletInfo walletInfo}) async {
155 final path = await pathForWallet(name: name, type: walletInfo.type);
155 - final wallet = ZanoWallet(walletInfo, password);
156 - await wallet.initWallet();
157 - final createWalletResult = await wallet.loadWallet(path, password);
158 - await wallet.initWallet();
159 - await wallet.parseCreateWalletResult(createWalletResult);
160 - await wallet.init(createWalletResult.wi.address);
161 - return wallet;
156 + if (ZanoWalletApi.openWalletCache[path] != null) {
157 + final wallet = ZanoWallet(walletInfo, password);
158 + await wallet.parseCreateWalletResult(ZanoWalletApi.openWalletCache[path]!).then((_) {
159 + unawaited(wallet.init(ZanoWalletApi.openWalletCache[path]!.wi.address));
160 + });
161 + return wallet;
162 + } else {
163 + final wallet = ZanoWallet(walletInfo, password);
164 + await wallet.initWallet();
165 + final createWalletResult = await wallet.loadWallet(path, password);
166 + await wallet.parseCreateWalletResult(createWalletResult).then((_) {
167 + unawaited(wallet.init(createWalletResult.wi.address));
168 + });
169 + return wallet;
170 + }
171 }
172
173 Future<void> parseCreateWalletResult(CreateWalletResult result) async {
174 hWallet = result.walletId;
175 seed = result.seed;
167 - ZanoWalletApi.info('setting hWallet = ${result.walletId}');
176 + printV('setting hWallet = ${result.walletId}');
177 walletAddresses.address = result.wi.address;
178 await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries);
179 for (final item in result.wi.balances) {
@@ -185,7 +194,7 @@ abstract class ZanoWalletBase
194
195 @override
196 Future<void> close({bool shouldCleanup = true}) async {
188 - closeWallet();
197 + closeWallet(null);
198 _updateSyncInfoTimer?.cancel();
199 _autoSaveTimer?.cancel();
200 }
@@ -285,7 +294,7 @@ abstract class ZanoWalletBase
294 } while (result.lastItemIndex + 1 < result.totalTransfers);
295 return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
296 } catch (e) {
288 - ZanoWalletApi.error(e.toString());
297 + printV((e.toString()));
298 return {};
299 }
300 }
@@ -332,7 +341,7 @@ abstract class ZanoWalletBase
341 await store();
342 await walletAddresses.updateAddressesInBox();
343 } catch (e) {
335 - ZanoWalletApi.error('Error while saving Zano wallet file ${e.toString()}');
344 + printV(('Error while saving Zano wallet file ${e.toString()}'));
345 }
346 }
347
@@ -349,7 +358,7 @@ abstract class ZanoWalletBase
358 retryCount++;
359 await Future.delayed(Duration(seconds: 1));
360 } else {
352 - ZanoWalletApi.error('failed to load assets after $retryCount retries');
361 + printV(('failed to load assets after $retryCount retries'));
362 break;
363 }
364 }
@@ -371,65 +380,10 @@ abstract class ZanoWalletBase
380 _lastKnownBlockHeight = 0;
381 _initialSyncHeight = 0;
382 _updateSyncInfoTimer ??=
374 - Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) async {
375 - GetWalletStatusResult walletStatus;
376 - // ignoring get wallet status exception (in case of wrong wallet id)
377 - try {
378 - walletStatus = await getWalletStatus();
379 - } on ZanoWalletException {
380 - return;
381 - }
382 - currentDaemonHeight = walletStatus.currentDaemonHeight;
383 - _updateSyncProgress(walletStatus);
384 -
385 - // we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready)
386 - if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) {
387 - final walletInfo = await getWalletInfo();
388 - seed = walletInfo.wiExtended.seed;
389 - keys = ZanoWalletKeys(
390 - privateSpendKey: walletInfo.wiExtended.spendPrivateKey,
391 - privateViewKey: walletInfo.wiExtended.viewPrivateKey,
392 - publicSpendKey: walletInfo.wiExtended.spendPublicKey,
393 - publicViewKey: walletInfo.wiExtended.viewPublicKey,
394 - );
395 - loadAssets(walletInfo.wi.balances);
396 - // matching balances and whitelists
397 - // 1. show only balances available in whitelists
398 - // 2. set whitelists available in balances as 'enabled' ('disabled' by default)
399 - for (final b in walletInfo.wi.balances) {
400 - if (b.assetId == zanoAssetId) {
401 - balance[CryptoCurrency.zano] = ZanoBalance(total: b.total, unlocked: b.unlocked);
402 - } else {
403 - final asset = zanoAssets[b.assetId];
404 - if (asset == null) {
405 - ZanoWalletApi.error('balance for an unknown asset ${b.assetInfo.assetId}');
406 - continue;
407 - }
408 - if (balance.keys.any(
409 - (element) => element is ZanoAsset && element.assetId == b.assetInfo.assetId)) {
410 - balance[balance.keys.firstWhere((element) =>
411 - element is ZanoAsset && element.assetId == b.assetInfo.assetId)] =
412 - ZanoBalance(
413 - total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
414 - } else {
415 - balance[asset] = ZanoBalance(
416 - total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
417 - }
418 - }
419 - }
420 - await updateTransactions();
421 - // removing balances for assets missing in wallet info balances
422 - balance.removeWhere(
423 - (key, _) =>
424 - key != CryptoCurrency.zano &&
425 - !walletInfo.wi.balances
426 - .any((element) => element.assetId == (key as ZanoAsset).assetId),
427 - );
428 - }
429 - });
383 + Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) => _updateSyncInfo());
384 } catch (e) {
385 syncStatus = FailedSyncStatus();
432 - ZanoWalletApi.error(e.toString());
386 + printV((e.toString()));
387 }
388 }
389
@@ -443,17 +397,13 @@ abstract class ZanoWalletBase
397 }
398 _isTransactionUpdating = true;
399 final transactions = await fetchTransactions();
446 - if (transactions.length == transactionHistory.transactions.length) {
447 - _isTransactionUpdating = false;
448 - return;
449 - }
400 transactionHistory.clear();
401 transactionHistory.addMany(transactions);
402 await transactionHistory.save();
403 _isTransactionUpdating = false;
404 } catch (e) {
405 printV("e: $e");
456 - ZanoWalletApi.error(e.toString());
406 + printV((e.toString()));
407 _isTransactionUpdating = false;
408 }
409 }
@@ -480,12 +430,12 @@ abstract class ZanoWalletBase
430 if (asset.enabled) {
431 final assetDescriptor = await addAssetsWhitelist(asset.assetId);
432 if (assetDescriptor == null) {
483 - ZanoWalletApi.error('Error adding zano asset');
433 + printV(('Error adding zano asset'));
434 }
435 } else {
436 final result = await removeAssetsWhitelist(asset.assetId);
437 if (result == false) {
488 - ZanoWalletApi.error('Error removing zano asset');
438 + printV(('Error removing zano asset'));
439 }
440 }
441 }
@@ -495,7 +445,11 @@ abstract class ZanoWalletBase
445 }
446
447 Future<ZanoAsset?> getZanoAsset(String assetId) async {
498 - return await getAssetInfo(assetId);
448 + // wallet api is not available while the wallet is syncing so only call it if it's synced
449 + if (syncStatus is SyncedSyncStatus) {
450 + return await getAssetInfo(assetId);
451 + }
452 + return null;
453 }
454
455 Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
@@ -514,7 +468,7 @@ abstract class ZanoWalletBase
468 syncStatus = SyncingSyncStatus(blocksLeft, ptc);
469 }
470 } catch (e) {
517 - ZanoWalletApi.error(e.toString());
471 + printV((e.toString()));
472 }
473 }
474
@@ -542,4 +496,61 @@ abstract class ZanoWalletBase
496 // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
497 _onNewBlock.call(syncHeight, left, ptc);
498 }
499 +
500 + void _updateSyncInfo() async {
501 + GetWalletStatusResult walletStatus;
502 + // ignoring get wallet status exception (in case of wrong wallet id)
503 + try {
504 + walletStatus = await getWalletStatus();
505 + } on ZanoWalletException {
506 + return;
507 + }
508 + currentDaemonHeight = walletStatus.currentDaemonHeight;
509 + _updateSyncProgress(walletStatus);
510 +
511 + // we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready)
512 + if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) {
513 + final walletInfo = await getWalletInfo();
514 + seed = walletInfo.wiExtended.seed;
515 + keys = ZanoWalletKeys(
516 + privateSpendKey: walletInfo.wiExtended.spendPrivateKey,
517 + privateViewKey: walletInfo.wiExtended.viewPrivateKey,
518 + publicSpendKey: walletInfo.wiExtended.spendPublicKey,
519 + publicViewKey: walletInfo.wiExtended.viewPublicKey,
520 + );
521 + loadAssets(walletInfo.wi.balances);
522 + // matching balances and whitelists
523 + // 1. show only balances available in whitelists
524 + // 2. set whitelists available in balances as 'enabled' ('disabled' by default)
525 + for (final b in walletInfo.wi.balances) {
526 + if (b.assetId == zanoAssetId) {
527 + balance[CryptoCurrency.zano] = ZanoBalance(total: b.total, unlocked: b.unlocked);
528 + } else {
529 + final asset = zanoAssets[b.assetId];
530 + if (asset == null) {
531 + printV('balance for an unknown asset ${b.assetInfo.assetId}');
532 + continue;
533 + }
534 + if (balance.keys.any(
535 + (element) => element is ZanoAsset && element.assetId == b.assetInfo.assetId)) {
536 + balance[balance.keys.firstWhere((element) =>
537 + element is ZanoAsset && element.assetId == b.assetInfo.assetId)] =
538 + ZanoBalance(
539 + total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
540 + } else {
541 + balance[asset] = ZanoBalance(
542 + total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
543 + }
544 + }
545 + }
546 + await updateTransactions();
547 + // removing balances for assets missing in wallet info balances
548 + balance.removeWhere(
549 + (key, _) =>
550 + key != CryptoCurrency.zano &&
551 + !walletInfo.wi.balances
552 + .any((element) => element.assetId == (key as ZanoAsset).assetId),
553 + );
554 + }
555 + }
556 }
cw_zano/lib/zano_wallet_addresses.dart
+2 -1
@@ -1,3 +1,4 @@
1 +import 'package:cw_core/utils/print_verbose.dart';
2 import 'package:cw_core/wallet_addresses.dart';
3 import 'package:cw_core/wallet_info.dart';
4 import 'package:cw_zano/zano_wallet_api.dart';
@@ -34,7 +35,7 @@ abstract class ZanoWalletAddressesBase extends WalletAddresses with Store {
35 addressesMap[address] = '';
36 await saveAddressesInBox();
37 } catch (e) {
37 - ZanoWalletApi.error(e.toString());
38 + printV(e.toString());
39 }
40 }
41 }
cw_zano/lib/zano_wallet_api.dart
+50 -66
@@ -1,5 +1,6 @@
1 import 'dart:convert' as convert;
2 import 'dart:ffi';
3 +import 'dart:io';
4 import 'dart:isolate';
5
6 import 'package:cw_core/transaction_priority.dart';
@@ -22,7 +23,6 @@ import 'package:cw_zano/api/model/transfer_params.dart';
23 import 'package:cw_zano/api/model/transfer_result.dart';
24 import 'package:cw_zano/zano_wallet_exceptions.dart';
25 import 'package:ffi/ffi.dart';
25 -import 'package:flutter/foundation.dart';
26 import 'package:json_bigint/json_bigint.dart';
27 import 'package:monero/zano.dart' as zano;
28 import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi;
@@ -30,8 +30,6 @@ import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi;
30 mixin ZanoWalletApi {
31 static const _maxReopenAttempts = 5;
32 static const _logInfo = false;
33 - static const _logError = true;
34 - static const _logJson = false;
33 static const int _zanoMixinValue = 10;
34
35 int _hWallet = 0;
@@ -46,16 +44,22 @@ mixin ZanoWalletApi {
44
45 void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);
46
49 - void closeWallet([int? walletToClose]) async {
50 - info('close_wallet ${walletToClose ?? hWallet}');
51 - final result = await _closeWallet(walletToClose ?? hWallet);
52 - info('close_wallet result $result');
47 + void closeWallet(int? walletToClose, {bool force = false}) async {
48 + printV('close_wallet ${walletToClose ?? hWallet}');
49 + if (Platform.isWindows || force) {
50 + final result = await _closeWallet(walletToClose ?? hWallet);
51 + printV('close_wallet result $result');
52 + openWalletCache.removeWhere((_, cwr) => cwr.walletId == (walletToClose ?? hWallet));
53 + }
54 }
55
56 + bool isInit = false;
57 +
58 Future<bool> initWallet() async {
59 // pathForWallet(name: , type: type)
60 + if (isInit) return true;
61 final result = zano.PlainWallet_init("", "", 0);
58 - printV(result);
62 + isInit = true;
63 return result == "OK";
64 }
65
@@ -67,21 +71,19 @@ mixin ZanoWalletApi {
71 Future<GetWalletInfoResult> getWalletInfo() async {
72 final json = await _getWalletInfo(hWallet);
73 final result = GetWalletInfoResult.fromJson(jsonDecode(json));
70 - _json('get_wallet_info', json);
71 - info('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances} seed: ${_shorten(result.wiExtended.seed)}');
74 + printV('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances}');
75 return result;
76 }
77
78 Future<GetWalletStatusResult> getWalletStatus() async {
79 final json = await _getWalletStatus(hWallet);
80 if (json == Consts.errorWalletWrongId) {
78 - error('wrong wallet id');
81 + printV('wrong wallet id');
82 throw ZanoWalletException('Wrong wallet id');
83 }
84 final status = GetWalletStatusResult.fromJson(jsonDecode(json));
82 - _json('get_wallet_status', json);
85 if (_logInfo)
84 - info(
86 + printV(
87 'get_wallet_status connected: ${status.isDaemonConnected} in refresh: ${status.isInLongRefresh} progress: ${status.progress} wallet state: ${status.walletState} sync: ${status.currentWalletHeight}/${status.currentDaemonHeight} ${(status.currentWalletHeight/status.currentDaemonHeight*100).toStringAsFixed(2)}%');
88 return status;
89 }
@@ -96,7 +98,7 @@ mixin ZanoWalletApi {
98 jsonDecode(invokeResult);
99 } catch (e) {
100 if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
99 - error('exception in parsing json in invokeMethod: $invokeResult');
101 + printV('exception in parsing json in invokeMethod: $invokeResult');
102 rethrow;
103 }
104 return invokeResult;
@@ -105,7 +107,6 @@ mixin ZanoWalletApi {
107 Future<List<ZanoAsset>> getAssetsWhitelist() async {
108 try {
109 final json = await invokeMethod('assets_whitelist_get', '{}');
108 - _json('assets_whitelist_get', json);
110 final map = jsonDecode(json) as Map<String, dynamic>?;
111 _checkForErrors(map);
112 List<ZanoAsset> assets(String type, bool isGlobalWhitelist) =>
@@ -117,12 +118,12 @@ mixin ZanoWalletApi {
118 final globalWhitelist = assets('global_whitelist', true);
119 final ownAssets = assets('own_assets', false);
120 if (_logInfo)
120 - info('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); '
121 + printV('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); '
122 'global whitelist: ${globalWhitelist.length} ($globalWhitelist); '
123 'own assets: ${ownAssets.length} ($ownAssets)');
124 return [...globalWhitelist, ...localWhitelist, ...ownAssets];
125 } catch (e) {
125 - error('assets_whitelist_get $e');
126 + printV('assets_whitelist_get $e');
127 return [];
128 // rethrow;
129 }
@@ -131,19 +132,18 @@ mixin ZanoWalletApi {
132 Future<ZanoAsset?> addAssetsWhitelist(String assetId) async {
133 try {
134 final json = await invokeMethod('assets_whitelist_add', AssetIdParams(assetId: assetId));
134 - _json('assets_whitelist_add $assetId', json);
135 final map = jsonDecode(json) as Map<String, dynamic>?;
136 _checkForErrors(map);
137 if (map!['result']!['status']! == 'OK') {
138 final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
139 - info('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
139 + printV('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
140 return assetDescriptor;
141 } else {
142 - info('assets_whitelist_add status ${map['result']!['status']!}');
142 + printV('assets_whitelist_add status ${map['result']!['status']!}');
143 return null;
144 }
145 } catch (e) {
146 - error('assets_whitelist_add $e');
146 + printV('assets_whitelist_add $e');
147 return null;
148 }
149 }
@@ -151,13 +151,12 @@ mixin ZanoWalletApi {
151 Future<bool> removeAssetsWhitelist(String assetId) async {
152 try {
153 final json = await invokeMethod('assets_whitelist_remove', AssetIdParams(assetId: assetId));
154 - _json('assets_whitelist_remove $assetId', json);
154 final map = jsonDecode(json) as Map<String, dynamic>?;
155 _checkForErrors(map);
157 - info('assets_whitelist_remove status ${map!['result']!['status']!}');
156 + printV('assets_whitelist_remove status ${map!['result']!['status']!}');
157 return (map['result']!['status']! == 'OK');
158 } catch (e) {
160 - error('assets_whitelist_remove $e');
159 + printV('assets_whitelist_remove $e');
160 return false;
161 }
162 }
@@ -173,21 +172,20 @@ mixin ZanoWalletApi {
172 final methodName = 'get_asset_info';
173 final params = AssetIdParams(assetId: assetId);
174 final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}');
176 - _json('$methodName $assetId', result?.body ?? '');
175 if (result == null) {
178 - error('get_asset_info empty result');
176 + printV('get_asset_info empty result');
177 return null;
178 }
179 final map = jsonDecode(result.body) as Map<String, dynamic>?;
180 if (map!['error'] != null) {
183 - info('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}');
181 + printV('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}');
182 return null;
183 } else if (map['result']!['status']! == 'OK') {
184 final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
187 - info('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
185 + printV('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
186 return assetDescriptor;
187 } else {
190 - info('get_asset_info $assetId status ${map['result']!['status']!}');
188 + printV('get_asset_info $assetId status ${map['result']!['status']!}');
189 return null;
190 }
191 }
@@ -199,33 +197,32 @@ mixin ZanoWalletApi {
197 _checkForErrors(map);
198 return StoreResult.fromJson(map!['result'] as Map<String, dynamic>);
199 } catch (e) {
202 - error('store $e');
200 + printV('store $e');
201 return null;
202 }
203 }
204
205 Future<GetRecentTxsAndInfoResult> getRecentTxsAndInfo({required int offset, required int count}) async {
208 - info('get_recent_txs_and_info $offset $count');
206 + printV('get_recent_txs_and_info $offset $count');
207 try {
208 final json = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: offset, count: count));
211 - _json('get_recent_txs_and_info', json);
209 final map = jsonDecode(json) as Map<String, dynamic>?;
210 _checkForErrors(map);
211 final lastItemIndex = map?['result']?['last_item_index'] as int?;
212 final totalTransfers = map?['result']?['total_transfers'] as int?;
213 final transfers = map?['result']?['transfers'] as List<dynamic>?;
214 if (transfers == null || lastItemIndex == null || totalTransfers == null) {
218 - error('get_recent_txs_and_info empty transfers');
215 + printV('get_recent_txs_and_info empty transfers');
216 return GetRecentTxsAndInfoResult.empty();
217 }
221 - info('get_recent_txs_and_info transfers.length: ${transfers.length}');
218 + printV('get_recent_txs_and_info transfers.length: ${transfers.length}');
219 return GetRecentTxsAndInfoResult(
220 transfers: transfers.map((e) => Transfer.fromJson(e as Map<String, dynamic>)).toList(),
221 lastItemIndex: lastItemIndex,
222 totalTransfers: totalTransfers,
223 );
224 } catch (e) {
228 - error('get_recent_txs_and_info $e');
225 + printV('get_recent_txs_and_info $e');
226 return GetRecentTxsAndInfoResult.empty();
227 }
228 }
@@ -237,9 +234,8 @@ mixin ZanoWalletApi {
234 String _shorten(String s) => s.length > 10 ? '${s.substring(0, 4)}...${s.substring(s.length - 4)}' : s;
235
236 Future<CreateWalletResult> createWallet(String path, String password) async {
240 - info('create_wallet path $path password ${_shorten(password)}');
237 + printV('create_wallet path $path password ${_shorten(password)}');
238 final json = zano.PlainWallet_generate(path, password);
242 - _json('create_wallet', json);
239 final map = jsonDecode(json) as Map<String, dynamic>?;
240 if (map?['error'] != null) {
241 final code = map!['error']?['code'] ?? '';
@@ -250,14 +246,14 @@ mixin ZanoWalletApi {
246 throw ZanoWalletException('Error creating wallet file, empty response');
247 }
248 final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
253 - info('create_wallet ${result.name} ${result.seed}');
249 + openWalletCache[path] = result;
250 + printV('create_wallet ${result.name} ${result.seed}');
251 return result;
252 }
253
254 Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async {
258 - info('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}');
255 + printV('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}');
256 final json = zano.PlainWallet_restore(seed, path, password, passphrase??'');
260 - _json('restore_wallet', json);
257 final map = jsonDecode(json) as Map<String, dynamic>?;
258 if (map?['error'] != null) {
259 final code = map!['error']!['code'] ?? '';
@@ -273,28 +269,29 @@ mixin ZanoWalletApi {
269 throw RestoreFromSeedsException('Error restoring wallet, empty response');
270 }
271 final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
276 - info('restore_wallet ${result.name} ${result.wi.address}');
272 + openWalletCache[path] = result;
273 + printV('restore_wallet ${result.name} ${result.wi.address}');
274 return result;
275 }
276
277 Future<CreateWalletResult>loadWallet(String path, String password, [int attempt = 0]) async {
281 - info('load_wallet1 path $path password ${_shorten(password)}');
278 + printV('load_wallet1 path $path password ${_shorten(password)}');
279 final String json;
280 try {
281 json = zano.PlainWallet_open(path, password);
282 } catch (e) {
286 - error('error in loadingWallet $e');
283 + printV('error in loadingWallet $e');
284 rethrow;
285 }
289 - info('load_wallet2: $json');
286 + // printV('load_wallet2: $json');
287 final map = jsonDecode(json) as Map<String, dynamic>?;
288 if (map?['error'] != null) {
289 final code = map?['error']!['code'] ?? '';
290 final message = map?['error']!['message'] ?? '';
291 if (code == Consts.errorAlreadyExists && attempt <= _maxReopenAttempts) {
292 // already connected to this wallet. closing and trying to reopen
296 - info('already connected. closing and reopen wallet (attempt $attempt)');
297 - closeWallet(attempt);
293 + printV('already connected. closing and reopen wallet (attempt $attempt)');
294 + closeWallet(attempt, force: true);
295 await Future.delayed(const Duration(milliseconds: 500));
296 return await loadWallet(path, password, attempt + 1);
297 }
@@ -304,10 +301,13 @@ mixin ZanoWalletApi {
301 throw ZanoWalletException('Error loading wallet, empty response');
302 }
303 final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
307 - info('load_wallet3 ${result.name} ${result.wi.address}');
304 + printV('load_wallet3 ${result.name} ${result.wi.address}');
305 + openWalletCache[path] = result;
306 return result;
307 }
308
309 + static Map<String, CreateWalletResult> openWalletCache = {};
310 +
311 Future<TransferResult> transfer(List<Destination> destinations, BigInt fee, String comment) async {
312 final params = TransferParams(
313 destinations: destinations,
@@ -319,24 +319,23 @@ mixin ZanoWalletApi {
319 hideReceiver: true,
320 );
321 final json = await invokeMethod('transfer', params);
322 - _json('transfer', json);
322 final map = jsonDecode(json);
323 final resultMap = map as Map<String, dynamic>?;
324 if (resultMap != null) {
325 final transferResultMap = resultMap['result'] as Map<String, dynamic>?;
326 if (transferResultMap != null) {
327 final transferResult = TransferResult.fromJson(transferResultMap);
329 - info('transfer success hash ${transferResult.txHash}');
328 + printV('transfer success hash ${transferResult.txHash}');
329 return transferResult;
330 } else {
331 final errorCode = resultMap['error']?['code'];
332 final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? '';
333 final message = resultMap['error']?['message'] as String? ?? '';
335 - error('transfer error $code $message');
334 + printV('transfer error $code $message');
335 throw TransferException('Transfer error, $message ($code)');
336 }
337 }
339 - error('transfer error empty result');
338 + printV('transfer error empty result');
339 throw TransferException('Transfer error, empty result');
340 }
341
@@ -358,21 +357,6 @@ mixin ZanoWalletApi {
357 }
358 }
359
361 - /*Future<void> _writeLog(String method, String logMessage) async {
362 - final dir = await getDownloadsDirectory();
363 - final logFile = File('${dir!.path}/$method.txt');
364 - final date = DateTime.now();
365 - String twoDigits(int value) => value.toString().padLeft(2, '0');
366 - String removeCRandLF(String input) => input.replaceAll(RegExp('\r|\n'), '');
367 - await logFile.writeAsString('${twoDigits(date.hour)}:${twoDigits(date.minute)}:${twoDigits(date.second)} ${removeCRandLF(logMessage)}\n',
368 - mode: FileMode.append);
369 - }*/
370 -
371 - static void info(String s) => _logInfo ? debugPrint('[info] $s') : null;
372 - static void error(String s) => _logError ? debugPrint('[error] $s') : null;
373 - static void printWrapped(String text) => RegExp('.{1,800}').allMatches(text).map((m) => m.group(0)).forEach(print);
374 - static void _json(String methodName, String json) => _logJson ? printWrapped('$methodName $json') : null;
375 -
360 }
361
362 Future<String> callSyncMethod(String methodName, int hWallet, String params) async {
cw_zano/lib/zano_wallet_service.dart
+2 -1
@@ -2,6 +2,7 @@ import 'dart:io';
2
3 import 'package:collection/collection.dart';
4 import 'package:cw_core/pathForWallet.dart';
5 +import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:cw_core/wallet_base.dart';
7 import 'package:cw_core/wallet_credentials.dart';
8 import 'package:cw_core/wallet_info.dart';
@@ -55,7 +56,7 @@ class ZanoWalletService extends WalletService<ZanoNewWalletCredentials,
56
57 @override
58 Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async {
58 - ZanoWalletApi.info('zanowallet service create isTestnet $isTestnet');
59 + printV('zanowallet service create isTestnet $isTestnet');
60 return await ZanoWalletBase.create(credentials: credentials);
61 }
62
lib/core/address_validator.dart
+2 -2
@@ -135,7 +135,7 @@ class AddressValidator extends TextValidator {
135 case CryptoCurrency.btcln:
136 pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
137 case CryptoCurrency.zano:
138 - pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
138 + pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
139 default:
140 return '';
141 }
@@ -316,7 +316,7 @@ class AddressValidator extends TextValidator {
316 case CryptoCurrency.trx:
317 pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
318 case CryptoCurrency.zano:
319 - pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
319 + pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
320 default:
321 if (type.tag == CryptoCurrency.eth.title) {
322 pattern = '0x[0-9a-zA-Z]{42}';
lib/di.dart
+1 -1
@@ -495,7 +495,7 @@ Future<void> setup({
495 settingsStore: getIt.get<SettingsStore>(),
496 fiatConvertationStore: getIt.get<FiatConversionStore>()));
497
498 - getIt.registerLazySingleton(() => DashboardViewModel(
498 + getIt.registerFactory(() => DashboardViewModel(
499 balanceViewModel: getIt.get<BalanceViewModel>(),
500 appStore: getIt.get<AppStore>(),
501 tradesStore: getIt.get<TradesStore>(),
lib/entities/default_settings_migration.dart
+1 -1
@@ -44,7 +44,7 @@ const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
44 const tronDefaultNodeUri = 'api.trongrid.io';
45 const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
46 const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
47 -const zanoDefaultNodeUri = '37.27.100.59:10500';
47 +const zanoDefaultNodeUri = 'zano.cakewallet.com:11211';
48 const moneroWorldNodeUri = '.moneroworld.com';
49
50 Future<void> defaultSettingsMigration(
lib/entities/parse_address_from_domain.dart
+6 -6
@@ -141,7 +141,7 @@ class AddressResolver {
141 if (currency == CryptoCurrency.zano && settingsStore.lookupsZanoAlias) {
142 final formattedName = text.substring(1);
143 final zanoAddress = await ZanoAlias.fetchZanoAliasAddress(formattedName);
144 - if (zanoAddress != null) {
144 + if (zanoAddress != null && zanoAddress.isNotEmpty) {
145 return ParsedAddress.zanoAddress(
146 address: zanoAddress,
147 name: text,
@@ -154,7 +154,7 @@ class AddressResolver {
154 final addressFromBio = extractAddressByType(
155 raw: twitterUser.description,
156 type: CryptoCurrency.fromString(ticker, walletCurrency: wallet.currency));
157 - if (addressFromBio != null) {
157 + if (addressFromBio != null && addressFromBio.isNotEmpty) {
158 return ParsedAddress.fetchTwitterAddress(
159 address: addressFromBio,
160 name: text,
@@ -192,7 +192,7 @@ class AddressResolver {
192 if (mastodonUser != null) {
193 String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
194
195 - if (addressFromBio != null) {
195 + if (addressFromBio != null && addressFromBio.isNotEmpty) {
196 return ParsedAddress.fetchMastodonAddress(
197 address: addressFromBio,
198 name: text,
@@ -207,7 +207,7 @@ class AddressResolver {
207 String? addressFromPinnedPost =
208 extractAddressByType(raw: userPinnedPostsText, type: currency);
209
210 - if (addressFromPinnedPost != null) {
210 + if (addressFromPinnedPost != null && addressFromPinnedPost.isNotEmpty) {
211 return ParsedAddress.fetchMastodonAddress(
212 address: addressFromPinnedPost,
213 name: text,
@@ -248,7 +248,7 @@ class AddressResolver {
248 }
249
250 final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text);
251 - if (thorChainAddress != null) {
251 + if (thorChainAddress != null && thorChainAddress.isNotEmpty) {
252 String? address =
253 thorChainAddress[ticker] ?? (ticker == 'RUNE' ? thorChainAddress['THOR'] : null);
254 if (address != null) {
@@ -301,7 +301,7 @@ class AddressResolver {
301
302 if (nostrUserData != null) {
303 String? addressFromBio = extractAddressByType(raw: nostrUserData.about, type: currency);
304 - if (addressFromBio != null) {
304 + if (addressFromBio != null && addressFromBio.isNotEmpty) {
305 return ParsedAddress.nostrAddress(
306 address: addressFromBio,
307 name: text,
lib/entities/zano_alias.dart
+1 -1
@@ -6,7 +6,7 @@ import 'package:http/http.dart' as http;
6 class ZanoAlias {
7 static Future<String?> fetchZanoAliasAddress(String alias) async {
8 try {
9 - final uri = Uri.parse("http://37.27.100.59:10500/json_rpc");
9 + final uri = Uri.parse("http://zano.cakewallet.com:11211/json_rpc");
10 final response = await http.post(
11 uri,
12 body: json.encode({
lib/exchange/provider/trocador_exchange_provider.dart
+3 -2
@@ -107,8 +107,9 @@ class TrocadorExchangeProvider extends ExchangeProvider {
107 final coinJson = responseJSON.first as Map<String, dynamic>;
108
109 return Limits(
110 - min: coinJson['minimum'] as double,
111 - max: coinJson['maximum'] as double,
110 + min: coinJson['minimum'] as double?,
111 + // TODO: remove hardcoded value and call `api/new_rate` when Trocador adds min and max to it
112 + max: from == CryptoCurrency.zano ? 2600 : coinJson['maximum'] as double?,
113 );
114 }
115
lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+1 -1
@@ -173,7 +173,7 @@ class CryptoBalanceWidget extends StatelessWidget {
173 frozenFiatBalance: balance.fiatFrozenBalance,
174 currency: balance.asset,
175 hasAdditionalBalance:
176 - dashboardViewModel.balanceViewModel.hasAdditionalBalance,
176 + dashboardViewModel.balanceViewModel.hasAdditionalBalance(balance.asset),
177 hasSecondAdditionalBalance:
178 dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance,
179 hasSecondAvailableBalance:
lib/src/screens/dashboard/pages/transactions_page.dart
-1
@@ -118,7 +118,6 @@ class TransactionsPage extends StatelessWidget {
118 dashboardViewModel.balanceViewModel.isFiatDisabled
119 ? ''
120 : item.formattedFiatAmount,
121 - isPending: transaction.isPending,
121 title:
122 item.formattedTitle + item.formattedStatus + transactionType,
123 tags: tags,
lib/src/screens/dashboard/widgets/transaction_raw.dart
-2
@@ -10,7 +10,6 @@ class TransactionRow extends StatelessWidget {
10 required this.formattedDate,
11 required this.formattedAmount,
12 required this.formattedFiatAmount,
13 - required this.isPending,
13 required this.tags,
14 required this.title,
15 required this.onTap,
@@ -22,7 +21,6 @@ class TransactionRow extends StatelessWidget {
21 final String formattedDate;
22 final String formattedAmount;
23 final String formattedFiatAmount;
25 - final bool isPending;
24 final String title;
25 final List<String> tags;
26
lib/src/screens/wallet_keys/wallet_keys_page.dart
+3 -3
@@ -175,7 +175,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
175 dataToCopy: isLegacySeed
176 ? widget.walletKeysViewModel.legacySeed
177 : widget.walletKeysViewModel.seed,
178 - onShowQR: () async => _showQR(context, false),
178 + onShowQR: () async => _showQR(context),
179 ),
180 ],
181 );
@@ -315,8 +315,8 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
315 showBar<void>(context, S.of(context).copied_key_to_clipboard(title));
316 }
317
318 - Future<void> _showQR(BuildContext context, bool isLegacySeed) async {
319 - final url = await widget.walletKeysViewModel.getUrl(isLegacySeed);
318 + Future<void> _showQR(BuildContext context) async {
319 + final url = await widget.walletKeysViewModel.getUrl(false);
320
321 BrightnessUtil.changeBrightnessForFunction(() async {
322 await Navigator.pushNamed(
lib/store/app_store.dart
-2
@@ -3,7 +3,6 @@ import 'package:cake_wallet/di.dart';
3 import 'package:cake_wallet/entities/preferences_key.dart';
4 import 'package:cake_wallet/reactions/wallet_connect.dart';
5 import 'package:cake_wallet/utils/exception_handler.dart';
6 -import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
6 import 'package:cw_core/transaction_info.dart';
7 import 'package:cw_core/wallet_type.dart';
8 import 'package:mobx/mobx.dart';
@@ -55,6 +54,5 @@ abstract class AppStoreBase with Store {
54 getIt
55 .get<SharedPreferences>()
56 .setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
58 - getIt.get<DashboardViewModel>().onWalletChange(wallet);
57 }
58 }
lib/view_model/dashboard/balance_view_model.dart
+8 -11
@@ -197,15 +197,14 @@ abstract class BalanceViewModelBase with Store {
197 }
198 }
199
200 - @computed
201 - String get additionalBalance {
202 - final walletBalance = _walletBalance;
200 + String additionalBalance(CryptoCurrency cryptoCurrency) {
201 + final balance = _currencyBalance(cryptoCurrency);
202
203 if (displayMode == BalanceDisplayMode.hiddenBalance) {
204 return '0.0';
205 }
206
208 - return walletBalance.formattedAdditionalBalance;
207 + return balance.formattedAdditionalBalance;
208 }
209
210 @computed
@@ -217,7 +216,7 @@ abstract class BalanceViewModelBase with Store {
216 key,
217 BalanceRecord(
218 availableBalance: '●●●●●●',
220 - additionalBalance: additionalBalance,
219 + additionalBalance: additionalBalance(key),
220 frozenBalance: '',
221 secondAvailableBalance: '●●●●●●',
222 secondAdditionalBalance: '●●●●●●',
@@ -287,10 +286,9 @@ abstract class BalanceViewModelBase with Store {
286 @observable
287 bool mwebEnabled = false;
288
290 - @computed
291 - bool get hasAdditionalBalance {
289 + bool hasAdditionalBalance(CryptoCurrency currency) {
290 bool isWalletTypeActivated = _hasAdditionalBalanceForWalletType(wallet.type);
293 - bool isNotZeroAmount = additionalBalance != "0.0";
291 + bool isNotZeroAmount = additionalBalance(currency) != "0.0";
292
293 return isWalletTypeActivated && isNotZeroAmount;
294 }
@@ -384,9 +382,8 @@ abstract class BalanceViewModelBase with Store {
382 return balance;
383 }
384
387 - @computed
388 - Balance get _walletBalance {
389 - final balance = wallet.balance[wallet.currency];
385 + Balance _currencyBalance(CryptoCurrency cryptoCurrency) {
386 + final balance = wallet.balance[cryptoCurrency];
387
388 if (balance == null) {
389 throw Exception('No balance for ${wallet.currency}');
lib/view_model/dashboard/dashboard_view_model.dart
+115 -5
@@ -157,18 +157,128 @@ abstract class DashboardViewModelBase with Store {
157 type = appStore.wallet!.type,
158 transactions = ObservableList<TransactionListItem>(),
159 wallet = appStore.wallet! {
160 + name = wallet.name;
161 + type = wallet.type;
162 isShowFirstYatIntroduction = false;
163 isShowSecondYatIntroduction = false;
164 isShowThirdYatIntroduction = false;
165 updateActions();
166
165 - onWalletChange(wallet);
167 + final _wallet = wallet;
168 +
169 + if (_wallet.type == WalletType.monero) {
170 + subname = monero!.getCurrentAccount(_wallet).label;
171 +
172 + _onMoneroAccountChangeReaction = reaction(
173 + (_) => monero!.getMoneroWalletDetails(wallet).account,
174 + (Account account) => _onMoneroAccountChange(_wallet));
175 +
176 + _onMoneroBalanceChangeReaction = reaction(
177 + (_) => monero!.getMoneroWalletDetails(wallet).balance,
178 + (MoneroBalance balance) => _onMoneroTransactionsUpdate(_wallet));
179 +
180 + final _accountTransactions = _wallet.transactionHistory.transactions.values
181 + .where((tx) =>
182 + monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
183 + .toList();
184 +
185 + final sortedTransactions = [..._accountTransactions];
186 + sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
187 +
188 + transactions = ObservableList.of(
189 + sortedTransactions.map(
190 + (transaction) => TransactionListItem(
191 + transaction: transaction,
192 + balanceViewModel: balanceViewModel,
193 + settingsStore: appStore.settingsStore,
194 + key: ValueKey('monero_transaction_history_item_${transaction.id}_key'),
195 + ),
196 + ),
197 + );
198 + } else if (_wallet.type == WalletType.wownero) {
199 + subname = wow.wownero!.getCurrentAccount(_wallet).label;
200 +
201 + _onMoneroAccountChangeReaction = reaction(
202 + (_) => wow.wownero!.getWowneroWalletDetails(wallet).account,
203 + (wow.Account account) => _onMoneroAccountChange(_wallet));
204 +
205 + _onMoneroBalanceChangeReaction = reaction(
206 + (_) => wow.wownero!.getWowneroWalletDetails(wallet).balance,
207 + (wow.WowneroBalance balance) => _onMoneroTransactionsUpdate(_wallet));
208 +
209 + final _accountTransactions = _wallet.transactionHistory.transactions.values
210 + .where((tx) =>
211 + wow.wownero!.getTransactionInfoAccountId(tx) ==
212 + wow.wownero!.getCurrentAccount(wallet).id)
213 + .toList();
214 +
215 + final sortedTransactions = [..._accountTransactions];
216 + sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
217 +
218 + transactions = ObservableList.of(
219 + sortedTransactions.map(
220 + (transaction) => TransactionListItem(
221 + transaction: transaction,
222 + balanceViewModel: balanceViewModel,
223 + settingsStore: appStore.settingsStore,
224 + key: ValueKey('wownero_transaction_history_item_${transaction.id}_key'),
225 + ),
226 + ),
227 + );
228 + } else {
229 + final sortedTransactions = [...wallet.transactionHistory.transactions.values];
230 + sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
231 +
232 + transactions = ObservableList.of(
233 + sortedTransactions.map(
234 + (transaction) => TransactionListItem(
235 + transaction: transaction,
236 + balanceViewModel: balanceViewModel,
237 + settingsStore: appStore.settingsStore,
238 + key: ValueKey('${_wallet.type.name}_transaction_history_item_${transaction.id}_key'),
239 + ),
240 + ),
241 + );
242 + }
243
244 // TODO: nano sub-account generation is disabled:
245 // if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
246 // subname = nano!.getCurrentAccount(_wallet).label;
247 // }
248
249 + reaction((_) => appStore.wallet, (wallet) {
250 + _onWalletChange(wallet);
251 + _checkMweb();
252 + });
253 +
254 + connectMapToListWithTransform(
255 + appStore.wallet!.transactionHistory.transactions,
256 + transactions,
257 + (TransactionInfo? transaction) => TransactionListItem(
258 + transaction: transaction!,
259 + balanceViewModel: balanceViewModel,
260 + settingsStore: appStore.settingsStore,
261 + key: ValueKey(
262 + '${_wallet.type.name}_transaction_history_item_${transaction.id}_key',
263 + ),
264 + ), filter: (TransactionInfo? transaction) {
265 + if (transaction == null) {
266 + return false;
267 + }
268 +
269 + final wallet = _wallet;
270 + if (wallet.type == WalletType.monero) {
271 + return monero!.getTransactionInfoAccountId(transaction) ==
272 + monero!.getCurrentAccount(wallet).id;
273 + }
274 + if (wallet.type == WalletType.wownero) {
275 + return wow.wownero!.getTransactionInfoAccountId(transaction) ==
276 + wow.wownero!.getCurrentAccount(wallet).id;
277 + }
278 +
279 + return true;
280 + });
281 +
282 if (hasSilentPayments) {
283 silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
284
@@ -482,11 +592,11 @@ abstract class DashboardViewModelBase with Store {
592 }
593
594 @action
485 - void onWalletChange(
595 + void _onWalletChange(
596 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
487 - if (wallet == null) return;
488 -
489 - _checkMweb();
597 + if (wallet == null) {
598 + return;
599 + }
600
601 this.wallet = wallet;
602 type = wallet.type;
lib/view_model/dashboard/transaction_list_item.dart
+5
@@ -105,6 +105,7 @@ class TransactionListItem extends ActionListItem with Keyable {
105 WalletType.haven,
106 WalletType.wownero,
107 WalletType.litecoin,
108 + WalletType.zano,
109 ].contains(balanceViewModel.wallet.type)) {
110 return formattedPendingStatus;
111 }
@@ -214,6 +215,10 @@ class TransactionListItem extends ActionListItem with Keyable {
215 break;
216 case WalletType.zano:
217 final asset = zano!.assetOfTransaction(balanceViewModel.wallet, transaction);
218 + if (asset == null) {
219 + amount = "0.00";
220 + break;
221 + }
222 final price = balanceViewModel.fiatConvertationStore.prices[asset];
223 amount = calculateFiatAmountRaw(
224 cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
lib/view_model/restore/wallet_restore_from_qr_code.dart
+6 -5
@@ -76,9 +76,7 @@ class WalletRestoreFromQRCode {
76 RegExp _getPattern(int wordCount) =>
77 RegExp(r'(?<=\W|^)((?:\w+\s+){' + (wordCount - 1).toString() + r'}\w+)(?=\W|$)');
78
79 - List<int> patternCounts = walletType == WalletType.monero || walletType == WalletType.wownero
80 - ? [25, 16, 14, 13]
81 - : [24, 18, 12];
79 + final List<int> patternCounts = [12, 13, 14, 16, 18, 24, 25, 26];
80
81 for (final count in patternCounts) {
82 final pattern = _getPattern(count);
@@ -122,7 +120,9 @@ class WalletRestoreFromQRCode {
120 queryParameters['seed'] = _extractSeedPhraseFromUrl(code, walletType!);
121 }
122 if (queryParameters['address'] == null) {
125 - queryParameters['address'] = _extractAddressFromUrl(code, walletType!);
123 + try {
124 + queryParameters['address'] = _extractAddressFromUrl(code, walletType!);
125 + } catch (_) {}
126 }
127
128 Map<String, dynamic> credentials = {'type': walletType, ...queryParameters, 'raw_qr': code};
@@ -223,7 +223,8 @@ class WalletRestoreFromQRCode {
223
224 if (type == WalletType.monero) {
225 final codeParsed = json.decode(credentials['raw_qr'].toString());
226 - if (codeParsed["version"] != 0) throw UnimplementedError("Found view-only restore with unsupported version");
226 + if (codeParsed["version"] != 0)
227 + throw UnimplementedError("Found view-only restore with unsupported version");
228 if (codeParsed["primaryAddress"] == null ||
229 codeParsed["privateViewKey"] == null ||
230 codeParsed["restoreHeight"] == null) {
lib/view_model/wallet_keys_view_model.dart
-2
@@ -3,10 +3,8 @@ import 'package:cake_wallet/haven/haven.dart';
3 import 'package:cake_wallet/monero/monero.dart';
4 import 'package:cake_wallet/reactions/wallet_connect.dart';
5 import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
6 -import 'package:cake_wallet/src/widgets/text_info_box.dart';
6 import 'package:cake_wallet/store/app_store.dart';
7 import 'package:cake_wallet/wownero/wownero.dart';
9 -import 'package:cake_wallet/zano/zano.dart';
8 import 'package:cw_core/transaction_direction.dart';
9 import 'package:cw_core/transaction_info.dart';
10 import 'package:cw_core/wallet_base.dart';
lib/view_model/wallet_new_vm.dart
+6 -1
@@ -73,11 +73,16 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
73 ? advancedPrivacySettingsViewModel.seedPhraseLength.value
74 : 24;
75 case WalletType.nano:
76 + case WalletType.banano:
77 return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39
78 ? advancedPrivacySettingsViewModel.seedPhraseLength.value
79 : 24;
79 - default:
80 + case WalletType.none:
81 return 24;
82 + case WalletType.haven:
83 + return 25;
84 + case WalletType.zano:
85 + return 26;
86 }
87 }
88
lib/view_model/wallet_restore_view_model.dart
-2
@@ -72,8 +72,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
72 }
73
74 static const moneroSeedMnemonicLength = 25;
75 - static const electrumSeedMnemonicLength = 24;
76 - static const electrumShortSeedMnemonicLength = 12;
75
76 late List<WalletRestoreMode> availableModes;
77 final bool hasSeedLanguageSelector;
lib/zano/cw_zano.dart
+2 -2
@@ -114,14 +114,14 @@ class CWZano extends Zano {
114 }
115
116 @override
117 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
117 + CryptoCurrency? assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
118 transaction as ZanoTransactionInfo;
119 if (transaction.tokenSymbol == CryptoCurrency.zano.title) {
120 return CryptoCurrency.zano;
121 }
122 wallet as ZanoWallet;
123 final asset = wallet.zanoAssets.values.firstWhereOrNull((element) => element?.ticker == transaction.tokenSymbol);
124 - return asset ?? CryptoCurrency.zano;
124 + return asset;
125 }
126
127 String getZanoAssetAddress(CryptoCurrency asset) => (asset as ZanoAsset).assetId;
scripts/android/app_env.sh
+4 -4
@@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
15 APP_ANDROID_TYPE=$1
16
17 MONERO_COM_NAME="Monero.com"
18 -MONERO_COM_VERSION="1.19.1"
19 -MONERO_COM_BUILD_NUMBER=110
18 +MONERO_COM_VERSION="1.20.0"
19 +MONERO_COM_BUILD_NUMBER=111
20 MONERO_COM_BUNDLE_ID="com.monero.app"
21 MONERO_COM_PACKAGE="com.monero.app"
22 MONERO_COM_SCHEME="monero.com"
23
24 CAKEWALLET_NAME="Cake Wallet"
25 -CAKEWALLET_VERSION="4.22.1"
26 -CAKEWALLET_BUILD_NUMBER=242
25 +CAKEWALLET_VERSION="4.23.0"
26 +CAKEWALLET_BUILD_NUMBER=244
27 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
28 CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
29 CAKEWALLET_SCHEME="cakewallet"
scripts/ios/app_env.sh
+4 -4
@@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
13 APP_IOS_TYPE=$1
14
15 MONERO_COM_NAME="Monero.com"
16 -MONERO_COM_VERSION="1.19.1"
17 -MONERO_COM_BUILD_NUMBER=107
16 +MONERO_COM_VERSION="1.20.0"
17 +MONERO_COM_BUILD_NUMBER=108
18 MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
19
20 CAKEWALLET_NAME="Cake Wallet"
21 -CAKEWALLET_VERSION="4.22.1"
22 -CAKEWALLET_BUILD_NUMBER=289
21 +CAKEWALLET_VERSION="4.23.0"
22 +CAKEWALLET_BUILD_NUMBER=291
23 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
24
25 HAVEN_NAME="Haven"
scripts/linux/app_env.sh
+2 -2
@@ -14,8 +14,8 @@ if [ -n "$1" ]; then
14 fi
15
16 CAKEWALLET_NAME="Cake Wallet"
17 -CAKEWALLET_VERSION="1.12.1"
18 -CAKEWALLET_BUILD_NUMBER=43
17 +CAKEWALLET_VERSION="1.13.0"
18 +CAKEWALLET_BUILD_NUMBER=44
19
20 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then
21 echo "Wrong app type."
scripts/macos/app_config.sh
+1 -1
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
36 $MONERO_COM)
37 CONFIG_ARGS="--monero";;
38 $CAKEWALLET)
39 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano";; #--haven
39 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; #--haven
40 esac
41
42 cp -rf pubspec_description.yaml pubspec.yaml
scripts/macos/app_env.sh
+4 -4
@@ -16,13 +16,13 @@ if [ -n "$1" ]; then
16 fi
17
18 MONERO_COM_NAME="Monero.com"
19 -MONERO_COM_VERSION="1.9.1"
20 -MONERO_COM_BUILD_NUMBER=40
19 +MONERO_COM_VERSION="1.10.0"
20 +MONERO_COM_BUILD_NUMBER=41
21 MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
22
23 CAKEWALLET_NAME="Cake Wallet"
24 -CAKEWALLET_VERSION="1.15.1"
25 -CAKEWALLET_BUILD_NUMBER=101
24 +CAKEWALLET_VERSION="1.16.0"
25 +CAKEWALLET_BUILD_NUMBER=102
26 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
27
28 if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
scripts/windows/build_exe_installer.iss
+1 -1
@@ -1,5 +1,5 @@
1 #define MyAppName "Cake Wallet"
2 -#define MyAppVersion "0.3.1"
2 +#define MyAppVersion "0.4.0"
3 #define MyAppPublisher "Cake Labs LLC"
4 #define MyAppURL "https://cakewallet.com/"
5 #define MyAppExeName "CakeWallet.exe"
tool/configure.dart
+1 -1
@@ -1447,7 +1447,7 @@ abstract class Zano {
1447 double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});
1448 int formatterParseAmount({required String amount, required CryptoCurrency currency});
1449 WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource);
1450 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx);
1450 + CryptoCurrency? assetOfTransaction(WalletBase wallet, TransactionInfo tx);
1451 List<ZanoAsset> getZanoAssets(WalletBase wallet);
1452 String getZanoAssetAddress(CryptoCurrency asset);
1453 Future<void> changeZanoAssetAvailability(WalletBase wallet, CryptoCurrency token);