mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
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>
This commit is contained in:
parent
aef90e7192
commit
141a7ebfca
42 changed files with 472 additions and 306 deletions
13
.github/workflows/pr_test_build_android.yml
vendored
13
.github/workflows/pr_test_build_android.yml
vendored
|
@ -34,10 +34,19 @@ jobs:
|
||||||
- name: Fix github actions messing up $HOME...
|
- name: Fix github actions messing up $HOME...
|
||||||
run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
|
run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: configure git
|
- name: configure git
|
||||||
run: |
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
git config --global user.email "ci@cakewallet.com"
|
git config --global user.email "ci@cakewallet.com"
|
||||||
git config --global user.name "CakeWallet CI"
|
git config --global user.name "CakeWallet CI"
|
||||||
|
- name: Get the full commit message
|
||||||
|
run: |
|
||||||
|
FULL_MESSAGE="$(git log -1 --pretty=%B)"
|
||||||
|
echo "message<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$FULL_MESSAGE" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
- name: Add secrets
|
- name: Add secrets
|
||||||
run: |
|
run: |
|
||||||
touch lib/.secrets.g.dart
|
touch lib/.secrets.g.dart
|
||||||
|
@ -274,14 +283,14 @@ jobs:
|
||||||
echo "APK_FILE=$apk_file" >> $GITHUB_ENV
|
echo "APK_FILE=$apk_file" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload artifact to slack
|
- name: Upload artifact to slack
|
||||||
if: ${{ !contains(github.event.head_commit.message, 'skip slack') }}
|
if: ${{ !contains(env.message, 'skip slack') }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: adrey/slack-file-upload-action@1.0.5
|
uses: adrey/slack-file-upload-action@1.0.5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.SLACK_APP_TOKEN }}
|
token: ${{ secrets.SLACK_APP_TOKEN }}
|
||||||
path: ${{ env.APK_FILE }}
|
path: ${{ env.APK_FILE }}
|
||||||
channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
||||||
initial_comment: ${{ github.event.head_commit.message }}
|
initial_comment: ${{ env.message }}
|
||||||
|
|
||||||
- name: cleanup
|
- name: cleanup
|
||||||
run: rm -rf build/app/outputs/flutter-apk/test-apk/
|
run: rm -rf build/app/outputs/flutter-apk/test-apk/
|
||||||
|
|
19
.github/workflows/pr_test_build_linux.yml
vendored
19
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -30,10 +30,19 @@ jobs:
|
||||||
- name: Fix github actions messing up $HOME...
|
- name: Fix github actions messing up $HOME...
|
||||||
run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
|
run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: configure git
|
- name: configure git
|
||||||
run: |
|
run: |
|
||||||
|
git config --global --add safe.directory '*'
|
||||||
git config --global user.email "ci@cakewallet.com"
|
git config --global user.email "ci@cakewallet.com"
|
||||||
git config --global user.name "CakeWallet CI"
|
git config --global user.name "CakeWallet CI"
|
||||||
|
- name: Get the full commit message
|
||||||
|
run: |
|
||||||
|
FULL_MESSAGE="$(git log -1 --pretty=%B)"
|
||||||
|
echo "message<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$FULL_MESSAGE" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
- name: Add secrets
|
- name: Add secrets
|
||||||
run: |
|
run: |
|
||||||
touch lib/.secrets.g.dart
|
touch lib/.secrets.g.dart
|
||||||
|
@ -231,7 +240,7 @@ jobs:
|
||||||
name: cakewallet_linux
|
name: cakewallet_linux
|
||||||
|
|
||||||
- name: Prepare virtual desktop
|
- name: Prepare virtual desktop
|
||||||
if: ${{ contains(github.event.head_commit.message, 'run tests') }}
|
if: ${{ contains(env.message, 'run tests') }}
|
||||||
run: |
|
run: |
|
||||||
nohup Xvfb :99 -screen 0 720x1280x16 &
|
nohup Xvfb :99 -screen 0 720x1280x16 &
|
||||||
echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV
|
echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV
|
||||||
|
@ -247,28 +256,28 @@ jobs:
|
||||||
# isn't much in those wallets anyway, we still wouldn't like to leak it to anyone who is able to access github.
|
# isn't much in those wallets anyway, we still wouldn't like to leak it to anyone who is able to access github.
|
||||||
|
|
||||||
- name: Test [confirm_seeds_flow_test]
|
- name: Test [confirm_seeds_flow_test]
|
||||||
if: ${{ contains(github.event.head_commit.message, 'run tests') }}
|
if: ${{ contains(env.message, 'run tests') }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
run: |
|
run: |
|
||||||
xmessage -timeout 30 "confirm_seeds_flow_test" &
|
xmessage -timeout 30 "confirm_seeds_flow_test" &
|
||||||
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
|
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
|
||||||
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/confirm_seeds_flow_test.dart
|
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/confirm_seeds_flow_test.dart
|
||||||
- name: Test [create_wallet_flow_test]
|
- name: Test [create_wallet_flow_test]
|
||||||
if: ${{ contains(github.event.head_commit.message, 'run tests') }}
|
if: ${{ contains(env.message, 'run tests') }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
run: |
|
run: |
|
||||||
xmessage -timeout 30 "create_wallet_flow_test" &
|
xmessage -timeout 30 "create_wallet_flow_test" &
|
||||||
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
|
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
|
||||||
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/create_wallet_flow_test.dart
|
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/create_wallet_flow_test.dart
|
||||||
- name: Test [exchange_flow_test]
|
- name: Test [exchange_flow_test]
|
||||||
if: ${{ contains(github.event.head_commit.message, 'run tests') }}
|
if: ${{ contains(env.message, 'run tests') }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
run: |
|
run: |
|
||||||
xmessage -timeout 30 "exchange_flow_test" &
|
xmessage -timeout 30 "exchange_flow_test" &
|
||||||
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
|
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
|
||||||
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/exchange_flow_test.dart
|
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/exchange_flow_test.dart
|
||||||
- name: Test [restore_wallet_through_seeds_flow_test]
|
- name: Test [restore_wallet_through_seeds_flow_test]
|
||||||
if: ${{ contains(github.event.head_commit.message, 'run tests') }}
|
if: ${{ contains(env.message, 'run tests') }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
run: |
|
run: |
|
||||||
xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" &
|
xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" &
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
|
Monero restore with passphrase support
|
||||||
|
Add Chainflip Exchange provider
|
||||||
UI enhancements
|
UI enhancements
|
||||||
Bug fixes
|
Bug fixes
|
|
@ -1,4 +1,5 @@
|
||||||
Bitcoin and Litecoin enhancements
|
Add Zano Wallet
|
||||||
Solana and Nano fixes/improvements
|
Monero/Wownero/Zano restore with passphrase support
|
||||||
|
Add Chainflip Exchange provider
|
||||||
UI enhancements
|
UI enhancements
|
||||||
Bug fixes
|
Bug fixes
|
|
@ -1,4 +1,7 @@
|
||||||
-
|
-
|
||||||
uri: 37.27.100.59:10500
|
uri: 37.27.100.59:10500
|
||||||
|
useSSL: false
|
||||||
|
-
|
||||||
|
uri: zano.cakewallet.com:11211
|
||||||
is_default: true
|
is_default: true
|
||||||
useSSL: false
|
useSSL: false
|
|
@ -102,10 +102,9 @@ class Node extends HiveObject with Keyable {
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
case WalletType.solana:
|
case WalletType.solana:
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return Uri.parse(
|
|
||||||
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
|
|
||||||
case WalletType.zano:
|
case WalletType.zano:
|
||||||
return Uri.https(uriRaw, '');
|
return Uri.parse(
|
||||||
|
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") || path!.isEmpty ? path : "/$path"}");
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||||
}
|
}
|
||||||
|
@ -177,7 +176,33 @@ class Node extends HiveObject with Keyable {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> requestZanoNode() async {
|
Future<bool> requestZanoNode() async {
|
||||||
return requestMoneroNode(methodName: "getinfo");
|
final path = '/json_rpc';
|
||||||
|
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
|
||||||
|
final body = {'jsonrpc': '2.0', 'id': '0', 'method': "getinfo"};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final authenticatingClient = HttpClient();
|
||||||
|
authenticatingClient.badCertificateCallback =
|
||||||
|
((X509Certificate cert, String host, int port) => true);
|
||||||
|
|
||||||
|
final http.Client client = ioc.IOClient(authenticatingClient);
|
||||||
|
|
||||||
|
final jsonBody = json.encode(body);
|
||||||
|
|
||||||
|
final response = await client.post(
|
||||||
|
rpcUri,
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonBody,
|
||||||
|
);
|
||||||
|
|
||||||
|
printV("node check response: ${response.body}");
|
||||||
|
|
||||||
|
final resBody = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
return resBody['result']['height'] != null;
|
||||||
|
} catch (e) {
|
||||||
|
printV("error: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> requestMoneroNode({String methodName = 'get_info'}) async {
|
Future<bool> requestMoneroNode({String methodName = 'get_info'}) async {
|
||||||
|
@ -185,7 +210,6 @@ class Node extends HiveObject with Keyable {
|
||||||
return await requestNodeWithProxy();
|
return await requestNodeWithProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final path = '/json_rpc';
|
final path = '/json_rpc';
|
||||||
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
|
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
|
||||||
final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
|
final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
|
||||||
|
@ -195,7 +219,6 @@ class Node extends HiveObject with Keyable {
|
||||||
authenticatingClient.badCertificateCallback =
|
authenticatingClient.badCertificateCallback =
|
||||||
((X509Certificate cert, String host, int port) => true);
|
((X509Certificate cert, String host, int port) => true);
|
||||||
|
|
||||||
|
|
||||||
final http.Client client = ioc.IOClient(authenticatingClient);
|
final http.Client client = ioc.IOClient(authenticatingClient);
|
||||||
|
|
||||||
final jsonBody = json.encode(body);
|
final jsonBody = json.encode(body);
|
||||||
|
@ -209,8 +232,8 @@ class Node extends HiveObject with Keyable {
|
||||||
if (response.statusCode == 401) {
|
if (response.statusCode == 401) {
|
||||||
final daemonRpc = DaemonRpc(
|
final daemonRpc = DaemonRpc(
|
||||||
rpcUri.toString(),
|
rpcUri.toString(),
|
||||||
username: login??'',
|
username: login ?? '',
|
||||||
password: password??'',
|
password: password ?? '',
|
||||||
);
|
);
|
||||||
final response = await daemonRpc.call('get_info', {});
|
final response = await daemonRpc.call('get_info', {});
|
||||||
return !(response['offline'] as bool);
|
return !(response['offline'] as bool);
|
||||||
|
@ -298,10 +321,7 @@ class Node extends HiveObject with Keyable {
|
||||||
try {
|
try {
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
uri,
|
uri,
|
||||||
headers: {
|
headers: {"Content-Type": "application/json", "nano-app": "cake-wallet"},
|
||||||
"Content-Type": "application/json",
|
|
||||||
"nano-app": "cake-wallet"
|
|
||||||
},
|
|
||||||
body: jsonEncode(
|
body: jsonEncode(
|
||||||
{
|
{
|
||||||
"action": "account_balance",
|
"action": "account_balance",
|
||||||
|
@ -407,8 +427,7 @@ class DigestAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to format the nonce count.
|
/// Helper to format the nonce count.
|
||||||
String _formatNonceCount(int count) =>
|
String _formatNonceCount(int count) => count.toRadixString(16).padLeft(8, '0');
|
||||||
count.toRadixString(16).padLeft(8, '0');
|
|
||||||
|
|
||||||
/// Compute the MD5 hash of a string.
|
/// Compute the MD5 hash of a string.
|
||||||
String md5Hash(String input) {
|
String md5Hash(String input) {
|
||||||
|
@ -424,8 +443,7 @@ class DaemonRpc {
|
||||||
DaemonRpc(this.rpcUrl, {required this.username, required this.password});
|
DaemonRpc(this.rpcUrl, {required this.username, required this.password});
|
||||||
|
|
||||||
/// Perform a JSON-RPC call with Digest Authentication.
|
/// Perform a JSON-RPC call with Digest Authentication.
|
||||||
Future<Map<String, dynamic>> call(
|
Future<Map<String, dynamic>> call(String method, Map<String, dynamic> params) async {
|
||||||
String method, Map<String, dynamic> params) async {
|
|
||||||
final http.Client client = http.Client();
|
final http.Client client = http.Client();
|
||||||
final DigestAuth digestAuth = DigestAuth(username, password);
|
final DigestAuth digestAuth = DigestAuth(username, password);
|
||||||
|
|
||||||
|
@ -475,7 +493,8 @@ class DaemonRpc {
|
||||||
throw Exception('RPC call failed: ${authenticatedResponse.body}');
|
throw Exception('RPC call failed: ${authenticatedResponse.body}');
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, dynamic> result = jsonDecode(authenticatedResponse.body) as Map<String, dynamic>;
|
final Map<String, dynamic> result =
|
||||||
|
jsonDecode(authenticatedResponse.body) as Map<String, dynamic>;
|
||||||
if (result['error'] != null) {
|
if (result['error'] != null) {
|
||||||
throw Exception('RPC Error: ${result['error']}');
|
throw Exception('RPC Error: ${result['error']}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -511,8 +511,8 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "impls/monero.dart"
|
path: "impls/monero.dart"
|
||||||
ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
|
ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
|
||||||
resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
|
resolved-ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
|
||||||
url: "https://github.com/mrcyjanek/monero_c"
|
url: "https://github.com/mrcyjanek/monero_c"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
|
|
@ -471,8 +471,8 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "impls/monero.dart"
|
path: "impls/monero.dart"
|
||||||
ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
|
ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
|
||||||
resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
|
resolved-ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
|
||||||
url: "https://github.com/mrcyjanek/monero_c"
|
url: "https://github.com/mrcyjanek/monero_c"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:cw_core/zano_asset.dart';
|
import 'package:cw_core/zano_asset.dart';
|
||||||
import 'package:cw_zano/api/model/employed_entries.dart';
|
import 'package:cw_zano/api/model/employed_entries.dart';
|
||||||
import 'package:cw_zano/api/model/subtransfer.dart';
|
import 'package:cw_zano/api/model/subtransfer.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cw_zano/model/zano_asset.dart';
|
|
||||||
import 'package:cw_zano/model/zano_transaction_info.dart';
|
import 'package:cw_zano/model/zano_transaction_info.dart';
|
||||||
import 'package:cw_zano/zano_formatter.dart';
|
|
||||||
import 'package:cw_zano/zano_wallet.dart';
|
import 'package:cw_zano/zano_wallet.dart';
|
||||||
import 'package:cw_zano/zano_wallet_api.dart';
|
|
||||||
|
|
||||||
class Transfer {
|
class Transfer {
|
||||||
final String comment;
|
final String comment;
|
||||||
|
@ -49,19 +47,24 @@ class Transfer {
|
||||||
required this.unlockTime,
|
required this.unlockTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Transfer.fromJson(Map<String, dynamic> json) => Transfer(
|
factory Transfer.fromJson(Map<String, dynamic> json) =>
|
||||||
|
Transfer(
|
||||||
comment: json['comment'] as String? ?? '',
|
comment: json['comment'] as String? ?? '',
|
||||||
employedEntries: EmployedEntries.fromJson(json['employed_entries'] as Map<String, dynamic>? ?? {}),
|
employedEntries: EmployedEntries.fromJson(
|
||||||
|
json['employed_entries'] as Map<String, dynamic>? ?? {}),
|
||||||
fee: json['fee'] as int? ?? 0,
|
fee: json['fee'] as int? ?? 0,
|
||||||
height: json['height'] as int? ?? 0,
|
height: json['height'] as int? ?? 0,
|
||||||
isMining: json['is_mining'] as bool? ?? false,
|
isMining: json['is_mining'] as bool? ?? false,
|
||||||
isMixing: json['is_mixing'] as bool? ?? false,
|
isMixing: json['is_mixing'] as bool? ?? false,
|
||||||
isService: json['is_service'] as bool? ?? false,
|
isService: json['is_service'] as bool? ?? false,
|
||||||
paymentId: json['payment_id'] as String? ?? '',
|
paymentId: json['payment_id'] as String? ?? '',
|
||||||
remoteAddresses: json['remote_addresses'] == null ? [] : (json['remote_addresses'] as List<dynamic>).cast<String>(),
|
remoteAddresses: json['remote_addresses'] == null ? [] : (json['remote_addresses'] as List<
|
||||||
remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<dynamic>).cast<String>(),
|
dynamic>).cast<String>(),
|
||||||
|
remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<
|
||||||
|
dynamic>).cast<String>(),
|
||||||
showSender: json['show_sender'] as bool? ?? false,
|
showSender: json['show_sender'] as bool? ?? false,
|
||||||
subtransfers: (json['subtransfers'] as List<dynamic>? ?? []).map((e) => Subtransfer.fromJson(e as Map<String, dynamic>)).toList(),
|
subtransfers: (json['subtransfers'] as List<dynamic>? ?? []).map((e) =>
|
||||||
|
Subtransfer.fromJson(e as Map<String, dynamic>)).toList(),
|
||||||
timestamp: json['timestamp'] as int? ?? 0,
|
timestamp: json['timestamp'] as int? ?? 0,
|
||||||
transferInternalIndex: json['transfer_internal_index'] == null
|
transferInternalIndex: json['transfer_internal_index'] == null
|
||||||
? 0
|
? 0
|
||||||
|
@ -74,7 +77,8 @@ class Transfer {
|
||||||
unlockTime: json['unlock_time'] as int? ?? 0,
|
unlockTime: json['unlock_time'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers, Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) {
|
static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers,
|
||||||
|
Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) {
|
||||||
return Map.fromIterable(
|
return Map.fromIterable(
|
||||||
transfers,
|
transfers,
|
||||||
key: (item) => (item as Transfer).txHash,
|
key: (item) => (item as Transfer).txHash,
|
||||||
|
@ -83,24 +87,27 @@ class Transfer {
|
||||||
// Simple (only one subtransfer OR two subtransfers and the second is Zano, outgoing and amount equals to fee) or complex?
|
// Simple (only one subtransfer OR two subtransfers and the second is Zano, outgoing and amount equals to fee) or complex?
|
||||||
Subtransfer? single = transfer.subtransfers.singleOrNull;
|
Subtransfer? single = transfer.subtransfers.singleOrNull;
|
||||||
if (transfer.subtransfers.length == 2) {
|
if (transfer.subtransfers.length == 2) {
|
||||||
final zano = transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId);
|
final zano = transfer.subtransfers.firstWhereOrNull((element) =>
|
||||||
|
element.assetId == ZanoWalletBase.zanoAssetId);
|
||||||
if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) {
|
if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) {
|
||||||
single = transfer.subtransfers.firstWhere((element) => element.assetId != ZanoWalletBase.zanoAssetId);
|
single = transfer.subtransfers.firstWhere((element) => element.assetId !=
|
||||||
|
ZanoWalletBase.zanoAssetId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool isSimple = single != null;
|
bool isSimple = single != null;
|
||||||
// TODO: for complex transactions we show zano or any other transaction, will fix it later
|
// TODO: for complex transactions we show zano or any other transaction, will fix it later
|
||||||
if (!isSimple) {
|
if (!isSimple) {
|
||||||
single =
|
single =
|
||||||
transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId) ?? transfer.subtransfers.first;
|
transfer.subtransfers.firstWhereOrNull((element) =>
|
||||||
|
element.assetId == ZanoWalletBase.zanoAssetId) ?? transfer.subtransfers.first;
|
||||||
}
|
}
|
||||||
if (single.assetId != ZanoWalletBase.zanoAssetId) {
|
if (single.assetId != ZanoWalletBase.zanoAssetId) {
|
||||||
final asset = zanoAssets[single.assetId];
|
final asset = zanoAssets[single.assetId];
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
ZanoWalletApi.error('unknown asset ${single.assetId}');
|
printV('unknown asset ${single.assetId}');
|
||||||
}
|
}
|
||||||
final ticker = asset == null ? '***' : asset.ticker;
|
final ticker = asset == null ? '***' : asset.ticker;
|
||||||
final decimalPoint = asset == null ? ZanoFormatter.defaultDecimalPoint : asset.decimalPoint;
|
final decimalPoint = asset == null ? 0 : asset.decimalPoint;
|
||||||
return ZanoTransactionInfo.fromTransfer(
|
return ZanoTransactionInfo.fromTransfer(
|
||||||
transfer,
|
transfer,
|
||||||
confirmations: currentDaemonHeight - transfer.height,
|
confirmations: currentDaemonHeight - transfer.height,
|
||||||
|
|
|
@ -32,10 +32,10 @@ class PendingZanoTransaction with PendingTransaction {
|
||||||
String get hex => '';
|
String get hex => '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get amountFormatted => '${ZanoFormatter.bigIntAmountToString(amount, decimalPoint)} $ticker';
|
String get amountFormatted => ZanoFormatter.bigIntAmountToString(amount, decimalPoint);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get feeFormatted => '${ZanoFormatter.bigIntAmountToString(fee)} ZANO';
|
String get feeFormatted => ZanoFormatter.bigIntAmountToString(fee);
|
||||||
|
|
||||||
TransferResult? transferResult;
|
TransferResult? transferResult;
|
||||||
|
|
||||||
|
|
|
@ -36,14 +36,18 @@ class ZanoTransactionInfo extends TransactionInfo {
|
||||||
amount = amount.isValidInt ? amount.toInt() : 0,
|
amount = amount.isValidInt ? amount.toInt() : 0,
|
||||||
fee = transfer.fee,
|
fee = transfer.fee,
|
||||||
confirmations = confirmations,
|
confirmations = confirmations,
|
||||||
isPending = false,
|
isPending = confirmations < 10,
|
||||||
recipientAddress = transfer.remoteAddresses.isNotEmpty ? transfer.remoteAddresses.first : '' {
|
recipientAddress = transfer.remoteAddresses.isNotEmpty
|
||||||
|
? transfer.remoteAddresses.first
|
||||||
|
: '' {
|
||||||
additionalInfo = <String, dynamic>{
|
additionalInfo = <String, dynamic>{
|
||||||
'comment': transfer.comment,
|
'comment': transfer.comment,
|
||||||
'assetId': assetId,
|
'assetId': assetId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
String get assetId => additionalInfo["assetId"] as String;
|
String get assetId => additionalInfo["assetId"] as String;
|
||||||
|
|
||||||
set assetId(String newId) => additionalInfo["assetId"] = newId;
|
set assetId(String newId) => additionalInfo["assetId"] = newId;
|
||||||
final String id;
|
final String id;
|
||||||
final int height;
|
final int height;
|
||||||
|
@ -61,7 +65,8 @@ class ZanoTransactionInfo extends TransactionInfo {
|
||||||
String? key;
|
String? key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String amountFormatted() => '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol';
|
String amountFormatted() =>
|
||||||
|
'${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String fiatAmount() => _fiatAmount ?? '';
|
String fiatAmount() => _fiatAmount ?? '';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:cw_zano/zano_wallet_api.dart';
|
import 'package:cw_zano/zano_wallet_api.dart';
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
import 'package:decimal/intl.dart';
|
import 'package:decimal/intl.dart';
|
||||||
|
@ -32,6 +33,9 @@ class ZanoFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) {
|
static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) {
|
||||||
|
if (decimalPoint == 0) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
|
final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
|
||||||
..minimumFractionDigits = 1;
|
..minimumFractionDigits = 1;
|
||||||
return numberFormat.format(
|
return numberFormat.format(
|
||||||
|
@ -66,7 +70,7 @@ class ZanoFormatter {
|
||||||
} else if (d == null) {
|
} else if (d == null) {
|
||||||
return BigInt.zero;
|
return BigInt.zero;
|
||||||
} else {
|
} else {
|
||||||
ZanoWalletApi.error('cannot cast value of type ${d.runtimeType} to BigInt');
|
printV(('cannot cast value of type ${d.runtimeType} to BigInt'));
|
||||||
throw 'cannot cast value of type ${d.runtimeType} to BigInt';
|
throw 'cannot cast value of type ${d.runtimeType} to BigInt';
|
||||||
//return BigInt.zero;
|
//return BigInt.zero;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:core';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ abstract class ZanoWalletBase
|
||||||
extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo>
|
extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo>
|
||||||
with Store, ZanoWalletApi {
|
with Store, ZanoWalletApi {
|
||||||
static const int _autoSaveIntervalSeconds = 30;
|
static const int _autoSaveIntervalSeconds = 30;
|
||||||
static const int _pollIntervalMilliseconds = 2000;
|
static const int _pollIntervalMilliseconds = 5000;
|
||||||
static const int _maxLoadAssetsRetries = 5;
|
static const int _maxLoadAssetsRetries = 5;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -152,19 +153,27 @@ abstract class ZanoWalletBase
|
||||||
static Future<ZanoWallet> open(
|
static Future<ZanoWallet> open(
|
||||||
{required String name, required String password, required WalletInfo walletInfo}) async {
|
{required String name, required String password, required WalletInfo walletInfo}) async {
|
||||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
if (ZanoWalletApi.openWalletCache[path] != null) {
|
||||||
|
final wallet = ZanoWallet(walletInfo, password);
|
||||||
|
await wallet.parseCreateWalletResult(ZanoWalletApi.openWalletCache[path]!).then((_) {
|
||||||
|
unawaited(wallet.init(ZanoWalletApi.openWalletCache[path]!.wi.address));
|
||||||
|
});
|
||||||
|
return wallet;
|
||||||
|
} else {
|
||||||
final wallet = ZanoWallet(walletInfo, password);
|
final wallet = ZanoWallet(walletInfo, password);
|
||||||
await wallet.initWallet();
|
await wallet.initWallet();
|
||||||
final createWalletResult = await wallet.loadWallet(path, password);
|
final createWalletResult = await wallet.loadWallet(path, password);
|
||||||
await wallet.initWallet();
|
await wallet.parseCreateWalletResult(createWalletResult).then((_) {
|
||||||
await wallet.parseCreateWalletResult(createWalletResult);
|
unawaited(wallet.init(createWalletResult.wi.address));
|
||||||
await wallet.init(createWalletResult.wi.address);
|
});
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> parseCreateWalletResult(CreateWalletResult result) async {
|
Future<void> parseCreateWalletResult(CreateWalletResult result) async {
|
||||||
hWallet = result.walletId;
|
hWallet = result.walletId;
|
||||||
seed = result.seed;
|
seed = result.seed;
|
||||||
ZanoWalletApi.info('setting hWallet = ${result.walletId}');
|
printV('setting hWallet = ${result.walletId}');
|
||||||
walletAddresses.address = result.wi.address;
|
walletAddresses.address = result.wi.address;
|
||||||
await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries);
|
await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries);
|
||||||
for (final item in result.wi.balances) {
|
for (final item in result.wi.balances) {
|
||||||
|
@ -185,7 +194,7 @@ abstract class ZanoWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close({bool shouldCleanup = true}) async {
|
Future<void> close({bool shouldCleanup = true}) async {
|
||||||
closeWallet();
|
closeWallet(null);
|
||||||
_updateSyncInfoTimer?.cancel();
|
_updateSyncInfoTimer?.cancel();
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
@ -285,7 +294,7 @@ abstract class ZanoWalletBase
|
||||||
} while (result.lastItemIndex + 1 < result.totalTransfers);
|
} while (result.lastItemIndex + 1 < result.totalTransfers);
|
||||||
return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
|
return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ZanoWalletApi.error(e.toString());
|
printV((e.toString()));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +341,7 @@ abstract class ZanoWalletBase
|
||||||
await store();
|
await store();
|
||||||
await walletAddresses.updateAddressesInBox();
|
await walletAddresses.updateAddressesInBox();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ZanoWalletApi.error('Error while saving Zano wallet file ${e.toString()}');
|
printV(('Error while saving Zano wallet file ${e.toString()}'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +358,7 @@ abstract class ZanoWalletBase
|
||||||
retryCount++;
|
retryCount++;
|
||||||
await Future.delayed(Duration(seconds: 1));
|
await Future.delayed(Duration(seconds: 1));
|
||||||
} else {
|
} else {
|
||||||
ZanoWalletApi.error('failed to load assets after $retryCount retries');
|
printV(('failed to load assets after $retryCount retries'));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -371,7 +380,124 @@ abstract class ZanoWalletBase
|
||||||
_lastKnownBlockHeight = 0;
|
_lastKnownBlockHeight = 0;
|
||||||
_initialSyncHeight = 0;
|
_initialSyncHeight = 0;
|
||||||
_updateSyncInfoTimer ??=
|
_updateSyncInfoTimer ??=
|
||||||
Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) async {
|
Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) => _updateSyncInfo());
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
printV((e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void>? updateBalance() => null;
|
||||||
|
|
||||||
|
Future<void> updateTransactions() async {
|
||||||
|
try {
|
||||||
|
if (_isTransactionUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isTransactionUpdating = true;
|
||||||
|
final transactions = await fetchTransactions();
|
||||||
|
transactionHistory.clear();
|
||||||
|
transactionHistory.addMany(transactions);
|
||||||
|
await transactionHistory.save();
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
printV("e: $e");
|
||||||
|
printV((e.toString()));
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<CryptoCurrency> addZanoAssetById(String assetId) async {
|
||||||
|
if (zanoAssets.containsKey(assetId)) {
|
||||||
|
throw ZanoWalletException('zano asset with id $assetId already added');
|
||||||
|
}
|
||||||
|
final assetDescriptor = await addAssetsWhitelist(assetId);
|
||||||
|
if (assetDescriptor == null) {
|
||||||
|
throw ZanoWalletException("there's no zano asset with id $assetId");
|
||||||
|
}
|
||||||
|
final asset = ZanoAsset.copyWith(
|
||||||
|
assetDescriptor,
|
||||||
|
assetId: assetId,
|
||||||
|
enabled: true,
|
||||||
|
);
|
||||||
|
zanoAssets[asset.assetId] = asset;
|
||||||
|
balance[asset] = ZanoBalance.empty(decimalPoint: asset.decimalPoint);
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> changeZanoAssetAvailability(ZanoAsset asset) async {
|
||||||
|
if (asset.enabled) {
|
||||||
|
final assetDescriptor = await addAssetsWhitelist(asset.assetId);
|
||||||
|
if (assetDescriptor == null) {
|
||||||
|
printV(('Error adding zano asset'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final result = await removeAssetsWhitelist(asset.assetId);
|
||||||
|
if (result == false) {
|
||||||
|
printV(('Error removing zano asset'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteZanoAsset(ZanoAsset asset) async {
|
||||||
|
final _ = await removeAssetsWhitelist(asset.assetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ZanoAsset?> getZanoAsset(String assetId) async {
|
||||||
|
// wallet api is not available while the wallet is syncing so only call it if it's synced
|
||||||
|
if (syncStatus is SyncedSyncStatus) {
|
||||||
|
return await getAssetInfo(assetId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
|
||||||
|
|
||||||
|
void _onNewBlock(int height, int blocksLeft, double ptc) async {
|
||||||
|
try {
|
||||||
|
if (blocksLeft < 1000) {
|
||||||
|
await _askForUpdateTransactionHistory();
|
||||||
|
syncStatus = SyncedSyncStatus();
|
||||||
|
|
||||||
|
if (!_hasSyncAfterStartup) {
|
||||||
|
_hasSyncAfterStartup = true;
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syncStatus = SyncingSyncStatus(blocksLeft, ptc);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
printV((e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSyncProgress(GetWalletStatusResult walletStatus) {
|
||||||
|
final syncHeight = walletStatus.currentWalletHeight;
|
||||||
|
if (_initialSyncHeight <= 0) {
|
||||||
|
_initialSyncHeight = syncHeight;
|
||||||
|
}
|
||||||
|
final bchHeight = walletStatus.currentDaemonHeight;
|
||||||
|
|
||||||
|
if (_lastKnownBlockHeight == syncHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastKnownBlockHeight = syncHeight;
|
||||||
|
final track = bchHeight - _initialSyncHeight;
|
||||||
|
final diff = track - (bchHeight - syncHeight);
|
||||||
|
final ptc = diff <= 0 ? 0.0 : diff / track;
|
||||||
|
final left = bchHeight - syncHeight;
|
||||||
|
|
||||||
|
if (syncHeight < 0 || left < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
|
||||||
|
_onNewBlock.call(syncHeight, left, ptc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSyncInfo() async {
|
||||||
GetWalletStatusResult walletStatus;
|
GetWalletStatusResult walletStatus;
|
||||||
// ignoring get wallet status exception (in case of wrong wallet id)
|
// ignoring get wallet status exception (in case of wrong wallet id)
|
||||||
try {
|
try {
|
||||||
|
@ -402,7 +528,7 @@ abstract class ZanoWalletBase
|
||||||
} else {
|
} else {
|
||||||
final asset = zanoAssets[b.assetId];
|
final asset = zanoAssets[b.assetId];
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
ZanoWalletApi.error('balance for an unknown asset ${b.assetInfo.assetId}');
|
printV('balance for an unknown asset ${b.assetInfo.assetId}');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (balance.keys.any(
|
if (balance.keys.any(
|
||||||
|
@ -426,120 +552,5 @@ abstract class ZanoWalletBase
|
||||||
.any((element) => element.assetId == (key as ZanoAsset).assetId),
|
.any((element) => element.assetId == (key as ZanoAsset).assetId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
syncStatus = FailedSyncStatus();
|
|
||||||
ZanoWalletApi.error(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void>? updateBalance() => null;
|
|
||||||
|
|
||||||
Future<void> updateTransactions() async {
|
|
||||||
try {
|
|
||||||
if (_isTransactionUpdating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_isTransactionUpdating = true;
|
|
||||||
final transactions = await fetchTransactions();
|
|
||||||
if (transactions.length == transactionHistory.transactions.length) {
|
|
||||||
_isTransactionUpdating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
transactionHistory.clear();
|
|
||||||
transactionHistory.addMany(transactions);
|
|
||||||
await transactionHistory.save();
|
|
||||||
_isTransactionUpdating = false;
|
|
||||||
} catch (e) {
|
|
||||||
printV("e: $e");
|
|
||||||
ZanoWalletApi.error(e.toString());
|
|
||||||
_isTransactionUpdating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<CryptoCurrency> addZanoAssetById(String assetId) async {
|
|
||||||
if (zanoAssets.containsKey(assetId)) {
|
|
||||||
throw ZanoWalletException('zano asset with id $assetId already added');
|
|
||||||
}
|
|
||||||
final assetDescriptor = await addAssetsWhitelist(assetId);
|
|
||||||
if (assetDescriptor == null) {
|
|
||||||
throw ZanoWalletException("there's no zano asset with id $assetId");
|
|
||||||
}
|
|
||||||
final asset = ZanoAsset.copyWith(
|
|
||||||
assetDescriptor,
|
|
||||||
assetId: assetId,
|
|
||||||
enabled: true,
|
|
||||||
);
|
|
||||||
zanoAssets[asset.assetId] = asset;
|
|
||||||
balance[asset] = ZanoBalance.empty(decimalPoint: asset.decimalPoint);
|
|
||||||
return asset;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> changeZanoAssetAvailability(ZanoAsset asset) async {
|
|
||||||
if (asset.enabled) {
|
|
||||||
final assetDescriptor = await addAssetsWhitelist(asset.assetId);
|
|
||||||
if (assetDescriptor == null) {
|
|
||||||
ZanoWalletApi.error('Error adding zano asset');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final result = await removeAssetsWhitelist(asset.assetId);
|
|
||||||
if (result == false) {
|
|
||||||
ZanoWalletApi.error('Error removing zano asset');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteZanoAsset(ZanoAsset asset) async {
|
|
||||||
final _ = await removeAssetsWhitelist(asset.assetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ZanoAsset?> getZanoAsset(String assetId) async {
|
|
||||||
return await getAssetInfo(assetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
|
|
||||||
|
|
||||||
void _onNewBlock(int height, int blocksLeft, double ptc) async {
|
|
||||||
try {
|
|
||||||
if (blocksLeft < 1000) {
|
|
||||||
await _askForUpdateTransactionHistory();
|
|
||||||
syncStatus = SyncedSyncStatus();
|
|
||||||
|
|
||||||
if (!_hasSyncAfterStartup) {
|
|
||||||
_hasSyncAfterStartup = true;
|
|
||||||
await save();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
syncStatus = SyncingSyncStatus(blocksLeft, ptc);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
ZanoWalletApi.error(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateSyncProgress(GetWalletStatusResult walletStatus) {
|
|
||||||
final syncHeight = walletStatus.currentWalletHeight;
|
|
||||||
if (_initialSyncHeight <= 0) {
|
|
||||||
_initialSyncHeight = syncHeight;
|
|
||||||
}
|
|
||||||
final bchHeight = walletStatus.currentDaemonHeight;
|
|
||||||
|
|
||||||
if (_lastKnownBlockHeight == syncHeight) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastKnownBlockHeight = syncHeight;
|
|
||||||
final track = bchHeight - _initialSyncHeight;
|
|
||||||
final diff = track - (bchHeight - syncHeight);
|
|
||||||
final ptc = diff <= 0 ? 0.0 : diff / track;
|
|
||||||
final left = bchHeight - syncHeight;
|
|
||||||
|
|
||||||
if (syncHeight < 0 || left < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
|
|
||||||
_onNewBlock.call(syncHeight, left, ptc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_zano/zano_wallet_api.dart';
|
import 'package:cw_zano/zano_wallet_api.dart';
|
||||||
|
@ -34,7 +35,7 @@ abstract class ZanoWalletAddressesBase extends WalletAddresses with Store {
|
||||||
addressesMap[address] = '';
|
addressesMap[address] = '';
|
||||||
await saveAddressesInBox();
|
await saveAddressesInBox();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ZanoWalletApi.error(e.toString());
|
printV(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert' as convert;
|
import 'dart:convert' as convert;
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
@ -22,7 +23,6 @@ import 'package:cw_zano/api/model/transfer_params.dart';
|
||||||
import 'package:cw_zano/api/model/transfer_result.dart';
|
import 'package:cw_zano/api/model/transfer_result.dart';
|
||||||
import 'package:cw_zano/zano_wallet_exceptions.dart';
|
import 'package:cw_zano/zano_wallet_exceptions.dart';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:json_bigint/json_bigint.dart';
|
import 'package:json_bigint/json_bigint.dart';
|
||||||
import 'package:monero/zano.dart' as zano;
|
import 'package:monero/zano.dart' as zano;
|
||||||
import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi;
|
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;
|
||||||
mixin ZanoWalletApi {
|
mixin ZanoWalletApi {
|
||||||
static const _maxReopenAttempts = 5;
|
static const _maxReopenAttempts = 5;
|
||||||
static const _logInfo = false;
|
static const _logInfo = false;
|
||||||
static const _logError = true;
|
|
||||||
static const _logJson = false;
|
|
||||||
static const int _zanoMixinValue = 10;
|
static const int _zanoMixinValue = 10;
|
||||||
|
|
||||||
int _hWallet = 0;
|
int _hWallet = 0;
|
||||||
|
@ -46,16 +44,22 @@ mixin ZanoWalletApi {
|
||||||
|
|
||||||
void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);
|
void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);
|
||||||
|
|
||||||
void closeWallet([int? walletToClose]) async {
|
void closeWallet(int? walletToClose, {bool force = false}) async {
|
||||||
info('close_wallet ${walletToClose ?? hWallet}');
|
printV('close_wallet ${walletToClose ?? hWallet}');
|
||||||
|
if (Platform.isWindows || force) {
|
||||||
final result = await _closeWallet(walletToClose ?? hWallet);
|
final result = await _closeWallet(walletToClose ?? hWallet);
|
||||||
info('close_wallet result $result');
|
printV('close_wallet result $result');
|
||||||
|
openWalletCache.removeWhere((_, cwr) => cwr.walletId == (walletToClose ?? hWallet));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInit = false;
|
||||||
|
|
||||||
Future<bool> initWallet() async {
|
Future<bool> initWallet() async {
|
||||||
// pathForWallet(name: , type: type)
|
// pathForWallet(name: , type: type)
|
||||||
|
if (isInit) return true;
|
||||||
final result = zano.PlainWallet_init("", "", 0);
|
final result = zano.PlainWallet_init("", "", 0);
|
||||||
printV(result);
|
isInit = true;
|
||||||
return result == "OK";
|
return result == "OK";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,21 +71,19 @@ mixin ZanoWalletApi {
|
||||||
Future<GetWalletInfoResult> getWalletInfo() async {
|
Future<GetWalletInfoResult> getWalletInfo() async {
|
||||||
final json = await _getWalletInfo(hWallet);
|
final json = await _getWalletInfo(hWallet);
|
||||||
final result = GetWalletInfoResult.fromJson(jsonDecode(json));
|
final result = GetWalletInfoResult.fromJson(jsonDecode(json));
|
||||||
_json('get_wallet_info', json);
|
printV('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances}');
|
||||||
info('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances} seed: ${_shorten(result.wiExtended.seed)}');
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GetWalletStatusResult> getWalletStatus() async {
|
Future<GetWalletStatusResult> getWalletStatus() async {
|
||||||
final json = await _getWalletStatus(hWallet);
|
final json = await _getWalletStatus(hWallet);
|
||||||
if (json == Consts.errorWalletWrongId) {
|
if (json == Consts.errorWalletWrongId) {
|
||||||
error('wrong wallet id');
|
printV('wrong wallet id');
|
||||||
throw ZanoWalletException('Wrong wallet id');
|
throw ZanoWalletException('Wrong wallet id');
|
||||||
}
|
}
|
||||||
final status = GetWalletStatusResult.fromJson(jsonDecode(json));
|
final status = GetWalletStatusResult.fromJson(jsonDecode(json));
|
||||||
_json('get_wallet_status', json);
|
|
||||||
if (_logInfo)
|
if (_logInfo)
|
||||||
info(
|
printV(
|
||||||
'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)}%');
|
'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)}%');
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +98,7 @@ mixin ZanoWalletApi {
|
||||||
jsonDecode(invokeResult);
|
jsonDecode(invokeResult);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
|
if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
|
||||||
error('exception in parsing json in invokeMethod: $invokeResult');
|
printV('exception in parsing json in invokeMethod: $invokeResult');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
return invokeResult;
|
return invokeResult;
|
||||||
|
@ -105,7 +107,6 @@ mixin ZanoWalletApi {
|
||||||
Future<List<ZanoAsset>> getAssetsWhitelist() async {
|
Future<List<ZanoAsset>> getAssetsWhitelist() async {
|
||||||
try {
|
try {
|
||||||
final json = await invokeMethod('assets_whitelist_get', '{}');
|
final json = await invokeMethod('assets_whitelist_get', '{}');
|
||||||
_json('assets_whitelist_get', json);
|
|
||||||
final map = jsonDecode(json) as Map<String, dynamic>?;
|
final map = jsonDecode(json) as Map<String, dynamic>?;
|
||||||
_checkForErrors(map);
|
_checkForErrors(map);
|
||||||
List<ZanoAsset> assets(String type, bool isGlobalWhitelist) =>
|
List<ZanoAsset> assets(String type, bool isGlobalWhitelist) =>
|
||||||
|
@ -117,12 +118,12 @@ mixin ZanoWalletApi {
|
||||||
final globalWhitelist = assets('global_whitelist', true);
|
final globalWhitelist = assets('global_whitelist', true);
|
||||||
final ownAssets = assets('own_assets', false);
|
final ownAssets = assets('own_assets', false);
|
||||||
if (_logInfo)
|
if (_logInfo)
|
||||||
info('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); '
|
printV('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); '
|
||||||
'global whitelist: ${globalWhitelist.length} ($globalWhitelist); '
|
'global whitelist: ${globalWhitelist.length} ($globalWhitelist); '
|
||||||
'own assets: ${ownAssets.length} ($ownAssets)');
|
'own assets: ${ownAssets.length} ($ownAssets)');
|
||||||
return [...globalWhitelist, ...localWhitelist, ...ownAssets];
|
return [...globalWhitelist, ...localWhitelist, ...ownAssets];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error('assets_whitelist_get $e');
|
printV('assets_whitelist_get $e');
|
||||||
return [];
|
return [];
|
||||||
// rethrow;
|
// rethrow;
|
||||||
}
|
}
|
||||||
|
@ -131,19 +132,18 @@ mixin ZanoWalletApi {
|
||||||
Future<ZanoAsset?> addAssetsWhitelist(String assetId) async {
|
Future<ZanoAsset?> addAssetsWhitelist(String assetId) async {
|
||||||
try {
|
try {
|
||||||
final json = await invokeMethod('assets_whitelist_add', AssetIdParams(assetId: assetId));
|
final json = await invokeMethod('assets_whitelist_add', AssetIdParams(assetId: assetId));
|
||||||
_json('assets_whitelist_add $assetId', json);
|
|
||||||
final map = jsonDecode(json) as Map<String, dynamic>?;
|
final map = jsonDecode(json) as Map<String, dynamic>?;
|
||||||
_checkForErrors(map);
|
_checkForErrors(map);
|
||||||
if (map!['result']!['status']! == 'OK') {
|
if (map!['result']!['status']! == 'OK') {
|
||||||
final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
|
final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
|
||||||
info('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
|
printV('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
|
||||||
return assetDescriptor;
|
return assetDescriptor;
|
||||||
} else {
|
} else {
|
||||||
info('assets_whitelist_add status ${map['result']!['status']!}');
|
printV('assets_whitelist_add status ${map['result']!['status']!}');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error('assets_whitelist_add $e');
|
printV('assets_whitelist_add $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,13 +151,12 @@ mixin ZanoWalletApi {
|
||||||
Future<bool> removeAssetsWhitelist(String assetId) async {
|
Future<bool> removeAssetsWhitelist(String assetId) async {
|
||||||
try {
|
try {
|
||||||
final json = await invokeMethod('assets_whitelist_remove', AssetIdParams(assetId: assetId));
|
final json = await invokeMethod('assets_whitelist_remove', AssetIdParams(assetId: assetId));
|
||||||
_json('assets_whitelist_remove $assetId', json);
|
|
||||||
final map = jsonDecode(json) as Map<String, dynamic>?;
|
final map = jsonDecode(json) as Map<String, dynamic>?;
|
||||||
_checkForErrors(map);
|
_checkForErrors(map);
|
||||||
info('assets_whitelist_remove status ${map!['result']!['status']!}');
|
printV('assets_whitelist_remove status ${map!['result']!['status']!}');
|
||||||
return (map['result']!['status']! == 'OK');
|
return (map['result']!['status']! == 'OK');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error('assets_whitelist_remove $e');
|
printV('assets_whitelist_remove $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,21 +172,20 @@ mixin ZanoWalletApi {
|
||||||
final methodName = 'get_asset_info';
|
final methodName = 'get_asset_info';
|
||||||
final params = AssetIdParams(assetId: assetId);
|
final params = AssetIdParams(assetId: assetId);
|
||||||
final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}');
|
final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}');
|
||||||
_json('$methodName $assetId', result?.body ?? '');
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
error('get_asset_info empty result');
|
printV('get_asset_info empty result');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final map = jsonDecode(result.body) as Map<String, dynamic>?;
|
final map = jsonDecode(result.body) as Map<String, dynamic>?;
|
||||||
if (map!['error'] != null) {
|
if (map!['error'] != null) {
|
||||||
info('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}');
|
printV('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}');
|
||||||
return null;
|
return null;
|
||||||
} else if (map['result']!['status']! == 'OK') {
|
} else if (map['result']!['status']! == 'OK') {
|
||||||
final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
|
final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
|
||||||
info('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
|
printV('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
|
||||||
return assetDescriptor;
|
return assetDescriptor;
|
||||||
} else {
|
} else {
|
||||||
info('get_asset_info $assetId status ${map['result']!['status']!}');
|
printV('get_asset_info $assetId status ${map['result']!['status']!}');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,33 +197,32 @@ mixin ZanoWalletApi {
|
||||||
_checkForErrors(map);
|
_checkForErrors(map);
|
||||||
return StoreResult.fromJson(map!['result'] as Map<String, dynamic>);
|
return StoreResult.fromJson(map!['result'] as Map<String, dynamic>);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error('store $e');
|
printV('store $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<GetRecentTxsAndInfoResult> getRecentTxsAndInfo({required int offset, required int count}) async {
|
Future<GetRecentTxsAndInfoResult> getRecentTxsAndInfo({required int offset, required int count}) async {
|
||||||
info('get_recent_txs_and_info $offset $count');
|
printV('get_recent_txs_and_info $offset $count');
|
||||||
try {
|
try {
|
||||||
final json = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: offset, count: count));
|
final json = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: offset, count: count));
|
||||||
_json('get_recent_txs_and_info', json);
|
|
||||||
final map = jsonDecode(json) as Map<String, dynamic>?;
|
final map = jsonDecode(json) as Map<String, dynamic>?;
|
||||||
_checkForErrors(map);
|
_checkForErrors(map);
|
||||||
final lastItemIndex = map?['result']?['last_item_index'] as int?;
|
final lastItemIndex = map?['result']?['last_item_index'] as int?;
|
||||||
final totalTransfers = map?['result']?['total_transfers'] as int?;
|
final totalTransfers = map?['result']?['total_transfers'] as int?;
|
||||||
final transfers = map?['result']?['transfers'] as List<dynamic>?;
|
final transfers = map?['result']?['transfers'] as List<dynamic>?;
|
||||||
if (transfers == null || lastItemIndex == null || totalTransfers == null) {
|
if (transfers == null || lastItemIndex == null || totalTransfers == null) {
|
||||||
error('get_recent_txs_and_info empty transfers');
|
printV('get_recent_txs_and_info empty transfers');
|
||||||
return GetRecentTxsAndInfoResult.empty();
|
return GetRecentTxsAndInfoResult.empty();
|
||||||
}
|
}
|
||||||
info('get_recent_txs_and_info transfers.length: ${transfers.length}');
|
printV('get_recent_txs_and_info transfers.length: ${transfers.length}');
|
||||||
return GetRecentTxsAndInfoResult(
|
return GetRecentTxsAndInfoResult(
|
||||||
transfers: transfers.map((e) => Transfer.fromJson(e as Map<String, dynamic>)).toList(),
|
transfers: transfers.map((e) => Transfer.fromJson(e as Map<String, dynamic>)).toList(),
|
||||||
lastItemIndex: lastItemIndex,
|
lastItemIndex: lastItemIndex,
|
||||||
totalTransfers: totalTransfers,
|
totalTransfers: totalTransfers,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error('get_recent_txs_and_info $e');
|
printV('get_recent_txs_and_info $e');
|
||||||
return GetRecentTxsAndInfoResult.empty();
|
return GetRecentTxsAndInfoResult.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,9 +234,8 @@ mixin ZanoWalletApi {
|
||||||
String _shorten(String s) => s.length > 10 ? '${s.substring(0, 4)}...${s.substring(s.length - 4)}' : s;
|
String _shorten(String s) => s.length > 10 ? '${s.substring(0, 4)}...${s.substring(s.length - 4)}' : s;
|
||||||
|
|
||||||
Future<CreateWalletResult> createWallet(String path, String password) async {
|
Future<CreateWalletResult> createWallet(String path, String password) async {
|
||||||
info('create_wallet path $path password ${_shorten(password)}');
|
printV('create_wallet path $path password ${_shorten(password)}');
|
||||||
final json = zano.PlainWallet_generate(path, password);
|
final json = zano.PlainWallet_generate(path, password);
|
||||||
_json('create_wallet', json);
|
|
||||||
final map = jsonDecode(json) as Map<String, dynamic>?;
|
final map = jsonDecode(json) as Map<String, dynamic>?;
|
||||||
if (map?['error'] != null) {
|
if (map?['error'] != null) {
|
||||||
final code = map!['error']?['code'] ?? '';
|
final code = map!['error']?['code'] ?? '';
|
||||||
|
@ -250,14 +246,14 @@ mixin ZanoWalletApi {
|
||||||
throw ZanoWalletException('Error creating wallet file, empty response');
|
throw ZanoWalletException('Error creating wallet file, empty response');
|
||||||
}
|
}
|
||||||
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
|
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
|
||||||
info('create_wallet ${result.name} ${result.seed}');
|
openWalletCache[path] = result;
|
||||||
|
printV('create_wallet ${result.name} ${result.seed}');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async {
|
Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async {
|
||||||
info('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}');
|
printV('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}');
|
||||||
final json = zano.PlainWallet_restore(seed, path, password, passphrase??'');
|
final json = zano.PlainWallet_restore(seed, path, password, passphrase??'');
|
||||||
_json('restore_wallet', json);
|
|
||||||
final map = jsonDecode(json) as Map<String, dynamic>?;
|
final map = jsonDecode(json) as Map<String, dynamic>?;
|
||||||
if (map?['error'] != null) {
|
if (map?['error'] != null) {
|
||||||
final code = map!['error']!['code'] ?? '';
|
final code = map!['error']!['code'] ?? '';
|
||||||
|
@ -273,28 +269,29 @@ mixin ZanoWalletApi {
|
||||||
throw RestoreFromSeedsException('Error restoring wallet, empty response');
|
throw RestoreFromSeedsException('Error restoring wallet, empty response');
|
||||||
}
|
}
|
||||||
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
|
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
|
||||||
info('restore_wallet ${result.name} ${result.wi.address}');
|
openWalletCache[path] = result;
|
||||||
|
printV('restore_wallet ${result.name} ${result.wi.address}');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CreateWalletResult>loadWallet(String path, String password, [int attempt = 0]) async {
|
Future<CreateWalletResult>loadWallet(String path, String password, [int attempt = 0]) async {
|
||||||
info('load_wallet1 path $path password ${_shorten(password)}');
|
printV('load_wallet1 path $path password ${_shorten(password)}');
|
||||||
final String json;
|
final String json;
|
||||||
try {
|
try {
|
||||||
json = zano.PlainWallet_open(path, password);
|
json = zano.PlainWallet_open(path, password);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error('error in loadingWallet $e');
|
printV('error in loadingWallet $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
info('load_wallet2: $json');
|
// printV('load_wallet2: $json');
|
||||||
final map = jsonDecode(json) as Map<String, dynamic>?;
|
final map = jsonDecode(json) as Map<String, dynamic>?;
|
||||||
if (map?['error'] != null) {
|
if (map?['error'] != null) {
|
||||||
final code = map?['error']!['code'] ?? '';
|
final code = map?['error']!['code'] ?? '';
|
||||||
final message = map?['error']!['message'] ?? '';
|
final message = map?['error']!['message'] ?? '';
|
||||||
if (code == Consts.errorAlreadyExists && attempt <= _maxReopenAttempts) {
|
if (code == Consts.errorAlreadyExists && attempt <= _maxReopenAttempts) {
|
||||||
// already connected to this wallet. closing and trying to reopen
|
// already connected to this wallet. closing and trying to reopen
|
||||||
info('already connected. closing and reopen wallet (attempt $attempt)');
|
printV('already connected. closing and reopen wallet (attempt $attempt)');
|
||||||
closeWallet(attempt);
|
closeWallet(attempt, force: true);
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
return await loadWallet(path, password, attempt + 1);
|
return await loadWallet(path, password, attempt + 1);
|
||||||
}
|
}
|
||||||
|
@ -304,10 +301,13 @@ mixin ZanoWalletApi {
|
||||||
throw ZanoWalletException('Error loading wallet, empty response');
|
throw ZanoWalletException('Error loading wallet, empty response');
|
||||||
}
|
}
|
||||||
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
|
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
|
||||||
info('load_wallet3 ${result.name} ${result.wi.address}');
|
printV('load_wallet3 ${result.name} ${result.wi.address}');
|
||||||
|
openWalletCache[path] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Map<String, CreateWalletResult> openWalletCache = {};
|
||||||
|
|
||||||
Future<TransferResult> transfer(List<Destination> destinations, BigInt fee, String comment) async {
|
Future<TransferResult> transfer(List<Destination> destinations, BigInt fee, String comment) async {
|
||||||
final params = TransferParams(
|
final params = TransferParams(
|
||||||
destinations: destinations,
|
destinations: destinations,
|
||||||
|
@ -319,24 +319,23 @@ mixin ZanoWalletApi {
|
||||||
hideReceiver: true,
|
hideReceiver: true,
|
||||||
);
|
);
|
||||||
final json = await invokeMethod('transfer', params);
|
final json = await invokeMethod('transfer', params);
|
||||||
_json('transfer', json);
|
|
||||||
final map = jsonDecode(json);
|
final map = jsonDecode(json);
|
||||||
final resultMap = map as Map<String, dynamic>?;
|
final resultMap = map as Map<String, dynamic>?;
|
||||||
if (resultMap != null) {
|
if (resultMap != null) {
|
||||||
final transferResultMap = resultMap['result'] as Map<String, dynamic>?;
|
final transferResultMap = resultMap['result'] as Map<String, dynamic>?;
|
||||||
if (transferResultMap != null) {
|
if (transferResultMap != null) {
|
||||||
final transferResult = TransferResult.fromJson(transferResultMap);
|
final transferResult = TransferResult.fromJson(transferResultMap);
|
||||||
info('transfer success hash ${transferResult.txHash}');
|
printV('transfer success hash ${transferResult.txHash}');
|
||||||
return transferResult;
|
return transferResult;
|
||||||
} else {
|
} else {
|
||||||
final errorCode = resultMap['error']?['code'];
|
final errorCode = resultMap['error']?['code'];
|
||||||
final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? '';
|
final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? '';
|
||||||
final message = resultMap['error']?['message'] as String? ?? '';
|
final message = resultMap['error']?['message'] as String? ?? '';
|
||||||
error('transfer error $code $message');
|
printV('transfer error $code $message');
|
||||||
throw TransferException('Transfer error, $message ($code)');
|
throw TransferException('Transfer error, $message ($code)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
error('transfer error empty result');
|
printV('transfer error empty result');
|
||||||
throw TransferException('Transfer error, empty result');
|
throw TransferException('Transfer error, empty result');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,21 +357,6 @@ mixin ZanoWalletApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Future<void> _writeLog(String method, String logMessage) async {
|
|
||||||
final dir = await getDownloadsDirectory();
|
|
||||||
final logFile = File('${dir!.path}/$method.txt');
|
|
||||||
final date = DateTime.now();
|
|
||||||
String twoDigits(int value) => value.toString().padLeft(2, '0');
|
|
||||||
String removeCRandLF(String input) => input.replaceAll(RegExp('\r|\n'), '');
|
|
||||||
await logFile.writeAsString('${twoDigits(date.hour)}:${twoDigits(date.minute)}:${twoDigits(date.second)} ${removeCRandLF(logMessage)}\n',
|
|
||||||
mode: FileMode.append);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
static void info(String s) => _logInfo ? debugPrint('[info] $s') : null;
|
|
||||||
static void error(String s) => _logError ? debugPrint('[error] $s') : null;
|
|
||||||
static void printWrapped(String text) => RegExp('.{1,800}').allMatches(text).map((m) => m.group(0)).forEach(print);
|
|
||||||
static void _json(String methodName, String json) => _logJson ? printWrapped('$methodName $json') : null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> callSyncMethod(String methodName, int hWallet, String params) async {
|
Future<String> callSyncMethod(String methodName, int hWallet, String params) async {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_credentials.dart';
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -55,7 +56,7 @@ class ZanoWalletService extends WalletService<ZanoNewWalletCredentials,
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async {
|
Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
ZanoWalletApi.info('zanowallet service create isTestnet $isTestnet');
|
printV('zanowallet service create isTestnet $isTestnet');
|
||||||
return await ZanoWalletBase.create(credentials: credentials);
|
return await ZanoWalletBase.create(credentials: credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.btcln:
|
case CryptoCurrency.btcln:
|
||||||
pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
|
pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
|
||||||
case CryptoCurrency.zano:
|
case CryptoCurrency.zano:
|
||||||
pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
|
pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -316,7 +316,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.trx:
|
case CryptoCurrency.trx:
|
||||||
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
|
||||||
case CryptoCurrency.zano:
|
case CryptoCurrency.zano:
|
||||||
pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
|
pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
|
||||||
default:
|
default:
|
||||||
if (type.tag == CryptoCurrency.eth.title) {
|
if (type.tag == CryptoCurrency.eth.title) {
|
||||||
pattern = '0x[0-9a-zA-Z]{42}';
|
pattern = '0x[0-9a-zA-Z]{42}';
|
||||||
|
|
|
@ -495,7 +495,7 @@ Future<void> setup({
|
||||||
settingsStore: getIt.get<SettingsStore>(),
|
settingsStore: getIt.get<SettingsStore>(),
|
||||||
fiatConvertationStore: getIt.get<FiatConversionStore>()));
|
fiatConvertationStore: getIt.get<FiatConversionStore>()));
|
||||||
|
|
||||||
getIt.registerLazySingleton(() => DashboardViewModel(
|
getIt.registerFactory(() => DashboardViewModel(
|
||||||
balanceViewModel: getIt.get<BalanceViewModel>(),
|
balanceViewModel: getIt.get<BalanceViewModel>(),
|
||||||
appStore: getIt.get<AppStore>(),
|
appStore: getIt.get<AppStore>(),
|
||||||
tradesStore: getIt.get<TradesStore>(),
|
tradesStore: getIt.get<TradesStore>(),
|
||||||
|
|
|
@ -44,7 +44,7 @@ const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
|
||||||
const tronDefaultNodeUri = 'api.trongrid.io';
|
const tronDefaultNodeUri = 'api.trongrid.io';
|
||||||
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
|
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
|
||||||
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
|
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
|
||||||
const zanoDefaultNodeUri = '37.27.100.59:10500';
|
const zanoDefaultNodeUri = 'zano.cakewallet.com:11211';
|
||||||
const moneroWorldNodeUri = '.moneroworld.com';
|
const moneroWorldNodeUri = '.moneroworld.com';
|
||||||
|
|
||||||
Future<void> defaultSettingsMigration(
|
Future<void> defaultSettingsMigration(
|
||||||
|
|
|
@ -141,7 +141,7 @@ class AddressResolver {
|
||||||
if (currency == CryptoCurrency.zano && settingsStore.lookupsZanoAlias) {
|
if (currency == CryptoCurrency.zano && settingsStore.lookupsZanoAlias) {
|
||||||
final formattedName = text.substring(1);
|
final formattedName = text.substring(1);
|
||||||
final zanoAddress = await ZanoAlias.fetchZanoAliasAddress(formattedName);
|
final zanoAddress = await ZanoAlias.fetchZanoAliasAddress(formattedName);
|
||||||
if (zanoAddress != null) {
|
if (zanoAddress != null && zanoAddress.isNotEmpty) {
|
||||||
return ParsedAddress.zanoAddress(
|
return ParsedAddress.zanoAddress(
|
||||||
address: zanoAddress,
|
address: zanoAddress,
|
||||||
name: text,
|
name: text,
|
||||||
|
@ -154,7 +154,7 @@ class AddressResolver {
|
||||||
final addressFromBio = extractAddressByType(
|
final addressFromBio = extractAddressByType(
|
||||||
raw: twitterUser.description,
|
raw: twitterUser.description,
|
||||||
type: CryptoCurrency.fromString(ticker, walletCurrency: wallet.currency));
|
type: CryptoCurrency.fromString(ticker, walletCurrency: wallet.currency));
|
||||||
if (addressFromBio != null) {
|
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||||
return ParsedAddress.fetchTwitterAddress(
|
return ParsedAddress.fetchTwitterAddress(
|
||||||
address: addressFromBio,
|
address: addressFromBio,
|
||||||
name: text,
|
name: text,
|
||||||
|
@ -192,7 +192,7 @@ class AddressResolver {
|
||||||
if (mastodonUser != null) {
|
if (mastodonUser != null) {
|
||||||
String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
|
String? addressFromBio = extractAddressByType(raw: mastodonUser.note, type: currency);
|
||||||
|
|
||||||
if (addressFromBio != null) {
|
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||||
return ParsedAddress.fetchMastodonAddress(
|
return ParsedAddress.fetchMastodonAddress(
|
||||||
address: addressFromBio,
|
address: addressFromBio,
|
||||||
name: text,
|
name: text,
|
||||||
|
@ -207,7 +207,7 @@ class AddressResolver {
|
||||||
String? addressFromPinnedPost =
|
String? addressFromPinnedPost =
|
||||||
extractAddressByType(raw: userPinnedPostsText, type: currency);
|
extractAddressByType(raw: userPinnedPostsText, type: currency);
|
||||||
|
|
||||||
if (addressFromPinnedPost != null) {
|
if (addressFromPinnedPost != null && addressFromPinnedPost.isNotEmpty) {
|
||||||
return ParsedAddress.fetchMastodonAddress(
|
return ParsedAddress.fetchMastodonAddress(
|
||||||
address: addressFromPinnedPost,
|
address: addressFromPinnedPost,
|
||||||
name: text,
|
name: text,
|
||||||
|
@ -248,7 +248,7 @@ class AddressResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text);
|
final thorChainAddress = await ThorChainExchangeProvider.lookupAddressByName(text);
|
||||||
if (thorChainAddress != null) {
|
if (thorChainAddress != null && thorChainAddress.isNotEmpty) {
|
||||||
String? address =
|
String? address =
|
||||||
thorChainAddress[ticker] ?? (ticker == 'RUNE' ? thorChainAddress['THOR'] : null);
|
thorChainAddress[ticker] ?? (ticker == 'RUNE' ? thorChainAddress['THOR'] : null);
|
||||||
if (address != null) {
|
if (address != null) {
|
||||||
|
@ -301,7 +301,7 @@ class AddressResolver {
|
||||||
|
|
||||||
if (nostrUserData != null) {
|
if (nostrUserData != null) {
|
||||||
String? addressFromBio = extractAddressByType(raw: nostrUserData.about, type: currency);
|
String? addressFromBio = extractAddressByType(raw: nostrUserData.about, type: currency);
|
||||||
if (addressFromBio != null) {
|
if (addressFromBio != null && addressFromBio.isNotEmpty) {
|
||||||
return ParsedAddress.nostrAddress(
|
return ParsedAddress.nostrAddress(
|
||||||
address: addressFromBio,
|
address: addressFromBio,
|
||||||
name: text,
|
name: text,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:http/http.dart' as http;
|
||||||
class ZanoAlias {
|
class ZanoAlias {
|
||||||
static Future<String?> fetchZanoAliasAddress(String alias) async {
|
static Future<String?> fetchZanoAliasAddress(String alias) async {
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse("http://37.27.100.59:10500/json_rpc");
|
final uri = Uri.parse("http://zano.cakewallet.com:11211/json_rpc");
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
uri,
|
uri,
|
||||||
body: json.encode({
|
body: json.encode({
|
||||||
|
|
|
@ -107,8 +107,9 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||||
final coinJson = responseJSON.first as Map<String, dynamic>;
|
final coinJson = responseJSON.first as Map<String, dynamic>;
|
||||||
|
|
||||||
return Limits(
|
return Limits(
|
||||||
min: coinJson['minimum'] as double,
|
min: coinJson['minimum'] as double?,
|
||||||
max: coinJson['maximum'] as double,
|
// TODO: remove hardcoded value and call `api/new_rate` when Trocador adds min and max to it
|
||||||
|
max: from == CryptoCurrency.zano ? 2600 : coinJson['maximum'] as double?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,7 +173,7 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||||
currency: balance.asset,
|
currency: balance.asset,
|
||||||
hasAdditionalBalance:
|
hasAdditionalBalance:
|
||||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
dashboardViewModel.balanceViewModel.hasAdditionalBalance(balance.asset),
|
||||||
hasSecondAdditionalBalance:
|
hasSecondAdditionalBalance:
|
||||||
dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance,
|
dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance,
|
||||||
hasSecondAvailableBalance:
|
hasSecondAvailableBalance:
|
||||||
|
|
|
@ -118,7 +118,6 @@ class TransactionsPage extends StatelessWidget {
|
||||||
dashboardViewModel.balanceViewModel.isFiatDisabled
|
dashboardViewModel.balanceViewModel.isFiatDisabled
|
||||||
? ''
|
? ''
|
||||||
: item.formattedFiatAmount,
|
: item.formattedFiatAmount,
|
||||||
isPending: transaction.isPending,
|
|
||||||
title:
|
title:
|
||||||
item.formattedTitle + item.formattedStatus + transactionType,
|
item.formattedTitle + item.formattedStatus + transactionType,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
|
|
|
@ -10,7 +10,6 @@ class TransactionRow extends StatelessWidget {
|
||||||
required this.formattedDate,
|
required this.formattedDate,
|
||||||
required this.formattedAmount,
|
required this.formattedAmount,
|
||||||
required this.formattedFiatAmount,
|
required this.formattedFiatAmount,
|
||||||
required this.isPending,
|
|
||||||
required this.tags,
|
required this.tags,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
@ -22,7 +21,6 @@ class TransactionRow extends StatelessWidget {
|
||||||
final String formattedDate;
|
final String formattedDate;
|
||||||
final String formattedAmount;
|
final String formattedAmount;
|
||||||
final String formattedFiatAmount;
|
final String formattedFiatAmount;
|
||||||
final bool isPending;
|
|
||||||
final String title;
|
final String title;
|
||||||
final List<String> tags;
|
final List<String> tags;
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
||||||
dataToCopy: isLegacySeed
|
dataToCopy: isLegacySeed
|
||||||
? widget.walletKeysViewModel.legacySeed
|
? widget.walletKeysViewModel.legacySeed
|
||||||
: widget.walletKeysViewModel.seed,
|
: widget.walletKeysViewModel.seed,
|
||||||
onShowQR: () async => _showQR(context, false),
|
onShowQR: () async => _showQR(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -315,8 +315,8 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
||||||
showBar<void>(context, S.of(context).copied_key_to_clipboard(title));
|
showBar<void>(context, S.of(context).copied_key_to_clipboard(title));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showQR(BuildContext context, bool isLegacySeed) async {
|
Future<void> _showQR(BuildContext context) async {
|
||||||
final url = await widget.walletKeysViewModel.getUrl(isLegacySeed);
|
final url = await widget.walletKeysViewModel.getUrl(false);
|
||||||
|
|
||||||
BrightnessUtil.changeBrightnessForFunction(() async {
|
BrightnessUtil.changeBrightnessForFunction(() async {
|
||||||
await Navigator.pushNamed(
|
await Navigator.pushNamed(
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -55,6 +54,5 @@ abstract class AppStoreBase with Store {
|
||||||
getIt
|
getIt
|
||||||
.get<SharedPreferences>()
|
.get<SharedPreferences>()
|
||||||
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||||
getIt.get<DashboardViewModel>().onWalletChange(wallet);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,15 +197,14 @@ abstract class BalanceViewModelBase with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
String additionalBalance(CryptoCurrency cryptoCurrency) {
|
||||||
String get additionalBalance {
|
final balance = _currencyBalance(cryptoCurrency);
|
||||||
final walletBalance = _walletBalance;
|
|
||||||
|
|
||||||
if (displayMode == BalanceDisplayMode.hiddenBalance) {
|
if (displayMode == BalanceDisplayMode.hiddenBalance) {
|
||||||
return '0.0';
|
return '0.0';
|
||||||
}
|
}
|
||||||
|
|
||||||
return walletBalance.formattedAdditionalBalance;
|
return balance.formattedAdditionalBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
|
@ -217,7 +216,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
key,
|
key,
|
||||||
BalanceRecord(
|
BalanceRecord(
|
||||||
availableBalance: '●●●●●●',
|
availableBalance: '●●●●●●',
|
||||||
additionalBalance: additionalBalance,
|
additionalBalance: additionalBalance(key),
|
||||||
frozenBalance: '',
|
frozenBalance: '',
|
||||||
secondAvailableBalance: '●●●●●●',
|
secondAvailableBalance: '●●●●●●',
|
||||||
secondAdditionalBalance: '●●●●●●',
|
secondAdditionalBalance: '●●●●●●',
|
||||||
|
@ -287,10 +286,9 @@ abstract class BalanceViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool mwebEnabled = false;
|
bool mwebEnabled = false;
|
||||||
|
|
||||||
@computed
|
bool hasAdditionalBalance(CryptoCurrency currency) {
|
||||||
bool get hasAdditionalBalance {
|
|
||||||
bool isWalletTypeActivated = _hasAdditionalBalanceForWalletType(wallet.type);
|
bool isWalletTypeActivated = _hasAdditionalBalanceForWalletType(wallet.type);
|
||||||
bool isNotZeroAmount = additionalBalance != "0.0";
|
bool isNotZeroAmount = additionalBalance(currency) != "0.0";
|
||||||
|
|
||||||
return isWalletTypeActivated && isNotZeroAmount;
|
return isWalletTypeActivated && isNotZeroAmount;
|
||||||
}
|
}
|
||||||
|
@ -384,9 +382,8 @@ abstract class BalanceViewModelBase with Store {
|
||||||
return balance;
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
Balance _currencyBalance(CryptoCurrency cryptoCurrency) {
|
||||||
Balance get _walletBalance {
|
final balance = wallet.balance[cryptoCurrency];
|
||||||
final balance = wallet.balance[wallet.currency];
|
|
||||||
|
|
||||||
if (balance == null) {
|
if (balance == null) {
|
||||||
throw Exception('No balance for ${wallet.currency}');
|
throw Exception('No balance for ${wallet.currency}');
|
||||||
|
|
|
@ -157,18 +157,128 @@ abstract class DashboardViewModelBase with Store {
|
||||||
type = appStore.wallet!.type,
|
type = appStore.wallet!.type,
|
||||||
transactions = ObservableList<TransactionListItem>(),
|
transactions = ObservableList<TransactionListItem>(),
|
||||||
wallet = appStore.wallet! {
|
wallet = appStore.wallet! {
|
||||||
|
name = wallet.name;
|
||||||
|
type = wallet.type;
|
||||||
isShowFirstYatIntroduction = false;
|
isShowFirstYatIntroduction = false;
|
||||||
isShowSecondYatIntroduction = false;
|
isShowSecondYatIntroduction = false;
|
||||||
isShowThirdYatIntroduction = false;
|
isShowThirdYatIntroduction = false;
|
||||||
updateActions();
|
updateActions();
|
||||||
|
|
||||||
onWalletChange(wallet);
|
final _wallet = wallet;
|
||||||
|
|
||||||
|
if (_wallet.type == WalletType.monero) {
|
||||||
|
subname = monero!.getCurrentAccount(_wallet).label;
|
||||||
|
|
||||||
|
_onMoneroAccountChangeReaction = reaction(
|
||||||
|
(_) => monero!.getMoneroWalletDetails(wallet).account,
|
||||||
|
(Account account) => _onMoneroAccountChange(_wallet));
|
||||||
|
|
||||||
|
_onMoneroBalanceChangeReaction = reaction(
|
||||||
|
(_) => monero!.getMoneroWalletDetails(wallet).balance,
|
||||||
|
(MoneroBalance balance) => _onMoneroTransactionsUpdate(_wallet));
|
||||||
|
|
||||||
|
final _accountTransactions = _wallet.transactionHistory.transactions.values
|
||||||
|
.where((tx) =>
|
||||||
|
monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final sortedTransactions = [..._accountTransactions];
|
||||||
|
sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
|
||||||
|
|
||||||
|
transactions = ObservableList.of(
|
||||||
|
sortedTransactions.map(
|
||||||
|
(transaction) => TransactionListItem(
|
||||||
|
transaction: transaction,
|
||||||
|
balanceViewModel: balanceViewModel,
|
||||||
|
settingsStore: appStore.settingsStore,
|
||||||
|
key: ValueKey('monero_transaction_history_item_${transaction.id}_key'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (_wallet.type == WalletType.wownero) {
|
||||||
|
subname = wow.wownero!.getCurrentAccount(_wallet).label;
|
||||||
|
|
||||||
|
_onMoneroAccountChangeReaction = reaction(
|
||||||
|
(_) => wow.wownero!.getWowneroWalletDetails(wallet).account,
|
||||||
|
(wow.Account account) => _onMoneroAccountChange(_wallet));
|
||||||
|
|
||||||
|
_onMoneroBalanceChangeReaction = reaction(
|
||||||
|
(_) => wow.wownero!.getWowneroWalletDetails(wallet).balance,
|
||||||
|
(wow.WowneroBalance balance) => _onMoneroTransactionsUpdate(_wallet));
|
||||||
|
|
||||||
|
final _accountTransactions = _wallet.transactionHistory.transactions.values
|
||||||
|
.where((tx) =>
|
||||||
|
wow.wownero!.getTransactionInfoAccountId(tx) ==
|
||||||
|
wow.wownero!.getCurrentAccount(wallet).id)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final sortedTransactions = [..._accountTransactions];
|
||||||
|
sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
|
||||||
|
|
||||||
|
transactions = ObservableList.of(
|
||||||
|
sortedTransactions.map(
|
||||||
|
(transaction) => TransactionListItem(
|
||||||
|
transaction: transaction,
|
||||||
|
balanceViewModel: balanceViewModel,
|
||||||
|
settingsStore: appStore.settingsStore,
|
||||||
|
key: ValueKey('wownero_transaction_history_item_${transaction.id}_key'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final sortedTransactions = [...wallet.transactionHistory.transactions.values];
|
||||||
|
sortedTransactions.sort((a, b) => a.date.compareTo(b.date));
|
||||||
|
|
||||||
|
transactions = ObservableList.of(
|
||||||
|
sortedTransactions.map(
|
||||||
|
(transaction) => TransactionListItem(
|
||||||
|
transaction: transaction,
|
||||||
|
balanceViewModel: balanceViewModel,
|
||||||
|
settingsStore: appStore.settingsStore,
|
||||||
|
key: ValueKey('${_wallet.type.name}_transaction_history_item_${transaction.id}_key'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: nano sub-account generation is disabled:
|
// TODO: nano sub-account generation is disabled:
|
||||||
// if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
|
// if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
|
||||||
// subname = nano!.getCurrentAccount(_wallet).label;
|
// subname = nano!.getCurrentAccount(_wallet).label;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
reaction((_) => appStore.wallet, (wallet) {
|
||||||
|
_onWalletChange(wallet);
|
||||||
|
_checkMweb();
|
||||||
|
});
|
||||||
|
|
||||||
|
connectMapToListWithTransform(
|
||||||
|
appStore.wallet!.transactionHistory.transactions,
|
||||||
|
transactions,
|
||||||
|
(TransactionInfo? transaction) => TransactionListItem(
|
||||||
|
transaction: transaction!,
|
||||||
|
balanceViewModel: balanceViewModel,
|
||||||
|
settingsStore: appStore.settingsStore,
|
||||||
|
key: ValueKey(
|
||||||
|
'${_wallet.type.name}_transaction_history_item_${transaction.id}_key',
|
||||||
|
),
|
||||||
|
), filter: (TransactionInfo? transaction) {
|
||||||
|
if (transaction == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final wallet = _wallet;
|
||||||
|
if (wallet.type == WalletType.monero) {
|
||||||
|
return monero!.getTransactionInfoAccountId(transaction) ==
|
||||||
|
monero!.getCurrentAccount(wallet).id;
|
||||||
|
}
|
||||||
|
if (wallet.type == WalletType.wownero) {
|
||||||
|
return wow.wownero!.getTransactionInfoAccountId(transaction) ==
|
||||||
|
wow.wownero!.getCurrentAccount(wallet).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
if (hasSilentPayments) {
|
if (hasSilentPayments) {
|
||||||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||||
|
|
||||||
|
@ -482,11 +592,11 @@ abstract class DashboardViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void onWalletChange(
|
void _onWalletChange(
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
|
||||||
if (wallet == null) return;
|
if (wallet == null) {
|
||||||
|
return;
|
||||||
_checkMweb();
|
}
|
||||||
|
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
type = wallet.type;
|
type = wallet.type;
|
||||||
|
|
|
@ -105,6 +105,7 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||||
WalletType.haven,
|
WalletType.haven,
|
||||||
WalletType.wownero,
|
WalletType.wownero,
|
||||||
WalletType.litecoin,
|
WalletType.litecoin,
|
||||||
|
WalletType.zano,
|
||||||
].contains(balanceViewModel.wallet.type)) {
|
].contains(balanceViewModel.wallet.type)) {
|
||||||
return formattedPendingStatus;
|
return formattedPendingStatus;
|
||||||
}
|
}
|
||||||
|
@ -214,6 +215,10 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||||
break;
|
break;
|
||||||
case WalletType.zano:
|
case WalletType.zano:
|
||||||
final asset = zano!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
final asset = zano!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||||
|
if (asset == null) {
|
||||||
|
amount = "0.00";
|
||||||
|
break;
|
||||||
|
}
|
||||||
final price = balanceViewModel.fiatConvertationStore.prices[asset];
|
final price = balanceViewModel.fiatConvertationStore.prices[asset];
|
||||||
amount = calculateFiatAmountRaw(
|
amount = calculateFiatAmountRaw(
|
||||||
cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
|
cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
|
||||||
|
|
|
@ -76,9 +76,7 @@ class WalletRestoreFromQRCode {
|
||||||
RegExp _getPattern(int wordCount) =>
|
RegExp _getPattern(int wordCount) =>
|
||||||
RegExp(r'(?<=\W|^)((?:\w+\s+){' + (wordCount - 1).toString() + r'}\w+)(?=\W|$)');
|
RegExp(r'(?<=\W|^)((?:\w+\s+){' + (wordCount - 1).toString() + r'}\w+)(?=\W|$)');
|
||||||
|
|
||||||
List<int> patternCounts = walletType == WalletType.monero || walletType == WalletType.wownero
|
final List<int> patternCounts = [12, 13, 14, 16, 18, 24, 25, 26];
|
||||||
? [25, 16, 14, 13]
|
|
||||||
: [24, 18, 12];
|
|
||||||
|
|
||||||
for (final count in patternCounts) {
|
for (final count in patternCounts) {
|
||||||
final pattern = _getPattern(count);
|
final pattern = _getPattern(count);
|
||||||
|
@ -122,7 +120,9 @@ class WalletRestoreFromQRCode {
|
||||||
queryParameters['seed'] = _extractSeedPhraseFromUrl(code, walletType!);
|
queryParameters['seed'] = _extractSeedPhraseFromUrl(code, walletType!);
|
||||||
}
|
}
|
||||||
if (queryParameters['address'] == null) {
|
if (queryParameters['address'] == null) {
|
||||||
|
try {
|
||||||
queryParameters['address'] = _extractAddressFromUrl(code, walletType!);
|
queryParameters['address'] = _extractAddressFromUrl(code, walletType!);
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> credentials = {'type': walletType, ...queryParameters, 'raw_qr': code};
|
Map<String, dynamic> credentials = {'type': walletType, ...queryParameters, 'raw_qr': code};
|
||||||
|
@ -223,7 +223,8 @@ class WalletRestoreFromQRCode {
|
||||||
|
|
||||||
if (type == WalletType.monero) {
|
if (type == WalletType.monero) {
|
||||||
final codeParsed = json.decode(credentials['raw_qr'].toString());
|
final codeParsed = json.decode(credentials['raw_qr'].toString());
|
||||||
if (codeParsed["version"] != 0) throw UnimplementedError("Found view-only restore with unsupported version");
|
if (codeParsed["version"] != 0)
|
||||||
|
throw UnimplementedError("Found view-only restore with unsupported version");
|
||||||
if (codeParsed["primaryAddress"] == null ||
|
if (codeParsed["primaryAddress"] == null ||
|
||||||
codeParsed["privateViewKey"] == null ||
|
codeParsed["privateViewKey"] == null ||
|
||||||
codeParsed["restoreHeight"] == null) {
|
codeParsed["restoreHeight"] == null) {
|
||||||
|
|
|
@ -3,10 +3,8 @@ import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||||
import 'package:cake_wallet/src/widgets/text_info_box.dart';
|
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/wownero/wownero.dart';
|
import 'package:cake_wallet/wownero/wownero.dart';
|
||||||
import 'package:cake_wallet/zano/zano.dart';
|
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
|
|
@ -73,11 +73,16 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||||
? advancedPrivacySettingsViewModel.seedPhraseLength.value
|
? advancedPrivacySettingsViewModel.seedPhraseLength.value
|
||||||
: 24;
|
: 24;
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
|
case WalletType.banano:
|
||||||
return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39
|
return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39
|
||||||
? advancedPrivacySettingsViewModel.seedPhraseLength.value
|
? advancedPrivacySettingsViewModel.seedPhraseLength.value
|
||||||
: 24;
|
: 24;
|
||||||
default:
|
case WalletType.none:
|
||||||
return 24;
|
return 24;
|
||||||
|
case WalletType.haven:
|
||||||
|
return 25;
|
||||||
|
case WalletType.zano:
|
||||||
|
return 26;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
static const moneroSeedMnemonicLength = 25;
|
static const moneroSeedMnemonicLength = 25;
|
||||||
static const electrumSeedMnemonicLength = 24;
|
|
||||||
static const electrumShortSeedMnemonicLength = 12;
|
|
||||||
|
|
||||||
late List<WalletRestoreMode> availableModes;
|
late List<WalletRestoreMode> availableModes;
|
||||||
final bool hasSeedLanguageSelector;
|
final bool hasSeedLanguageSelector;
|
||||||
|
|
|
@ -114,14 +114,14 @@ class CWZano extends Zano {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
CryptoCurrency? assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
||||||
transaction as ZanoTransactionInfo;
|
transaction as ZanoTransactionInfo;
|
||||||
if (transaction.tokenSymbol == CryptoCurrency.zano.title) {
|
if (transaction.tokenSymbol == CryptoCurrency.zano.title) {
|
||||||
return CryptoCurrency.zano;
|
return CryptoCurrency.zano;
|
||||||
}
|
}
|
||||||
wallet as ZanoWallet;
|
wallet as ZanoWallet;
|
||||||
final asset = wallet.zanoAssets.values.firstWhereOrNull((element) => element?.ticker == transaction.tokenSymbol);
|
final asset = wallet.zanoAssets.values.firstWhereOrNull((element) => element?.ticker == transaction.tokenSymbol);
|
||||||
return asset ?? CryptoCurrency.zano;
|
return asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getZanoAssetAddress(CryptoCurrency asset) => (asset as ZanoAsset).assetId;
|
String getZanoAssetAddress(CryptoCurrency asset) => (asset as ZanoAsset).assetId;
|
||||||
|
|
|
@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
|
||||||
APP_ANDROID_TYPE=$1
|
APP_ANDROID_TYPE=$1
|
||||||
|
|
||||||
MONERO_COM_NAME="Monero.com"
|
MONERO_COM_NAME="Monero.com"
|
||||||
MONERO_COM_VERSION="1.19.1"
|
MONERO_COM_VERSION="1.20.0"
|
||||||
MONERO_COM_BUILD_NUMBER=110
|
MONERO_COM_BUILD_NUMBER=111
|
||||||
MONERO_COM_BUNDLE_ID="com.monero.app"
|
MONERO_COM_BUNDLE_ID="com.monero.app"
|
||||||
MONERO_COM_PACKAGE="com.monero.app"
|
MONERO_COM_PACKAGE="com.monero.app"
|
||||||
MONERO_COM_SCHEME="monero.com"
|
MONERO_COM_SCHEME="monero.com"
|
||||||
|
|
||||||
CAKEWALLET_NAME="Cake Wallet"
|
CAKEWALLET_NAME="Cake Wallet"
|
||||||
CAKEWALLET_VERSION="4.22.1"
|
CAKEWALLET_VERSION="4.23.0"
|
||||||
CAKEWALLET_BUILD_NUMBER=242
|
CAKEWALLET_BUILD_NUMBER=244
|
||||||
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
|
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
|
||||||
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
|
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
|
||||||
CAKEWALLET_SCHEME="cakewallet"
|
CAKEWALLET_SCHEME="cakewallet"
|
||||||
|
|
|
@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
|
||||||
APP_IOS_TYPE=$1
|
APP_IOS_TYPE=$1
|
||||||
|
|
||||||
MONERO_COM_NAME="Monero.com"
|
MONERO_COM_NAME="Monero.com"
|
||||||
MONERO_COM_VERSION="1.19.1"
|
MONERO_COM_VERSION="1.20.0"
|
||||||
MONERO_COM_BUILD_NUMBER=107
|
MONERO_COM_BUILD_NUMBER=108
|
||||||
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
||||||
|
|
||||||
CAKEWALLET_NAME="Cake Wallet"
|
CAKEWALLET_NAME="Cake Wallet"
|
||||||
CAKEWALLET_VERSION="4.22.1"
|
CAKEWALLET_VERSION="4.23.0"
|
||||||
CAKEWALLET_BUILD_NUMBER=289
|
CAKEWALLET_BUILD_NUMBER=291
|
||||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||||
|
|
||||||
HAVEN_NAME="Haven"
|
HAVEN_NAME="Haven"
|
||||||
|
|
|
@ -14,8 +14,8 @@ if [ -n "$1" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CAKEWALLET_NAME="Cake Wallet"
|
CAKEWALLET_NAME="Cake Wallet"
|
||||||
CAKEWALLET_VERSION="1.12.1"
|
CAKEWALLET_VERSION="1.13.0"
|
||||||
CAKEWALLET_BUILD_NUMBER=43
|
CAKEWALLET_BUILD_NUMBER=44
|
||||||
|
|
||||||
if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then
|
if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then
|
||||||
echo "Wrong app type."
|
echo "Wrong app type."
|
||||||
|
|
|
@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
|
||||||
$MONERO_COM)
|
$MONERO_COM)
|
||||||
CONFIG_ARGS="--monero";;
|
CONFIG_ARGS="--monero";;
|
||||||
$CAKEWALLET)
|
$CAKEWALLET)
|
||||||
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano";; #--haven
|
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; #--haven
|
||||||
esac
|
esac
|
||||||
|
|
||||||
cp -rf pubspec_description.yaml pubspec.yaml
|
cp -rf pubspec_description.yaml pubspec.yaml
|
||||||
|
|
|
@ -16,13 +16,13 @@ if [ -n "$1" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MONERO_COM_NAME="Monero.com"
|
MONERO_COM_NAME="Monero.com"
|
||||||
MONERO_COM_VERSION="1.9.1"
|
MONERO_COM_VERSION="1.10.0"
|
||||||
MONERO_COM_BUILD_NUMBER=40
|
MONERO_COM_BUILD_NUMBER=41
|
||||||
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
||||||
|
|
||||||
CAKEWALLET_NAME="Cake Wallet"
|
CAKEWALLET_NAME="Cake Wallet"
|
||||||
CAKEWALLET_VERSION="1.15.1"
|
CAKEWALLET_VERSION="1.16.0"
|
||||||
CAKEWALLET_BUILD_NUMBER=101
|
CAKEWALLET_BUILD_NUMBER=102
|
||||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||||
|
|
||||||
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
|
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#define MyAppName "Cake Wallet"
|
#define MyAppName "Cake Wallet"
|
||||||
#define MyAppVersion "0.3.1"
|
#define MyAppVersion "0.4.0"
|
||||||
#define MyAppPublisher "Cake Labs LLC"
|
#define MyAppPublisher "Cake Labs LLC"
|
||||||
#define MyAppURL "https://cakewallet.com/"
|
#define MyAppURL "https://cakewallet.com/"
|
||||||
#define MyAppExeName "CakeWallet.exe"
|
#define MyAppExeName "CakeWallet.exe"
|
||||||
|
|
|
@ -1447,7 +1447,7 @@ abstract class Zano {
|
||||||
double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});
|
double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});
|
||||||
int formatterParseAmount({required String amount, required CryptoCurrency currency});
|
int formatterParseAmount({required String amount, required CryptoCurrency currency});
|
||||||
WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource);
|
WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource);
|
||||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx);
|
CryptoCurrency? assetOfTransaction(WalletBase wallet, TransactionInfo tx);
|
||||||
List<ZanoAsset> getZanoAssets(WalletBase wallet);
|
List<ZanoAsset> getZanoAssets(WalletBase wallet);
|
||||||
String getZanoAssetAddress(CryptoCurrency asset);
|
String getZanoAssetAddress(CryptoCurrency asset);
|
||||||
Future<void> changeZanoAssetAvailability(WalletBase wallet, CryptoCurrency token);
|
Future<void> changeZanoAssetAvailability(WalletBase wallet, CryptoCurrency token);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue