Zano with passphrase ()

* CW-685 Add passphrase restore for xmr/wow ()

* CW-685 Add passphrase restore for xmr/wow

* add support for polyseed passphrase

* disable 14 word seed passphrase (not supported in wownero-seed)
fix: Getting grayed screen on latest passphrase build after having restored a 14-word wownero seed (+passphrase) and attempting to restore a XMR seed, legacy or otherwise.

* fix pointer when restoring depracated wownero seed

* Fix polyseed encryption

* changes from review

* remove unused code

* add passphrase back to the screen
add passphrase to qr code backup export

* fix settings leaking through currencies on seed restore

* fix monero.com builds, make passphrase a getter on WalletBase

* add support for weird polyseed

* store passphrase for weird polyseed

* show encrypted seed only when passphrase is not empty

* force set restore height

* fix build issues

* fix build errors

* fix configure script

* print -> printV

* Update lib/view_model/wallet_keys_view_model.dart [skip ci]

* Update lib/view_model/wallet_keys_view_model.dart [skip ci]

* Update tool/configure.dart [skip ci]

* Update lib/view_model/wallet_new_vm.dart

* reuse existing passphrase field

* remove unused passphrase field

* make workflow run on pullrequests only [skip ci] [skip slack]

---------

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Zano ()

* my experiments

* Inital code for Zano integration

* Added missing android log lib

* added dummy wallet & some zano implementation

* fixing api for zano

* fixed zano build script

* attempt tp fix namespace problem

* added copy script for Zano files

* changes for zano wallet

* last updates

* zano.dart test app

* wallet recovery

* added pending transfer, some cleanup of unused

* some cleanup

* send + receive qr code

* last upd

* updated build_zano.sh

* updated zano ApiCalls, removed dummy

* updated zano ApiCalls, removed dummy

* added logging for get status/get info

* restored old wallet.dart

* restoring original versions of files

* restoring original versions of files

* restored get_height_by_date.dart, removed unnecessary calls for zano get height

* restoring original versions

* added multiple destinations, send all flag; some refactoring

* logging

* removed the duplicate

* fixed syncing sync status, decimal division, safe null json parsing

* some fixes after merge

* added multibalance/asset support for zano (ui)

* adding/removing from whitelist

* transfers in different assets

* transfers for multiple destinations and send all, some refactoring

* whitelists, some refactoring

* added different digits (decimal points) for formatters, some refactoring

* open, create, restore wallet refactoring; whitelists

* whitelists

* getting and updating transaction list; restoring a wallet from QR code

* several attempts to close wallet

* some refactoring

* added seed phrase

* changed fields to BigInt, some fixes

* modified build scripts for android

* build scripts

* restored accidently removed cw_haven.dart

* inital ios integration(zano libs built)

* update in script

* latest changes

* Applied a patch for iOS build (Boost and Zano scripts)

* Removed zano.dart (script-generated) and some unnecessary files

* Revert "Removed zano.dart (script-generated) and some unnecessary files"

This reverts commit 367c86398e.

* Removed zano.dart (generated by scripts), some files restored to initial versions

* added timer library

* changed paths in build_zano.sh

* build_zano.sh

* edit_token_page.dart - removed flag skipZanoAddressValidation

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* lib/core/address_validator.dart - updated zano address regex
lib/src/screens/dashboard/edit_token_page.dart - using AddressValidator().call

* fix zano build issues on android

* remove contrib/depends to save space

* move async call to a synchronouse one

* call sync call in isolate to make it async
generate framework for iOS as well
fix UR issues

* zano changes from monero_c repo

* update monero_c hash

* fix invalid zano imports, add support for linux, speed up CI builds

* update monero_c hash

* bump monero_c commit (yes, again, I know)

* fix wallet resttore, fix hardcoded IP

* fix regex, don't throw error when opening wallet, fix tx history, fix async calls, move stuff to isolate

* fix api calls in async transaction creation

* update build scripts

* fix some build issues

* update dependencies

* fix dependencies

* update ci scripts

* Improve multithread use of zano api

* Fix build issue

* fix zano node selection, move other zano calls to separate isolate

* update moneroc hash
WIP fixes for zano

* update monero_c

* fix monero.com builds

* sync wallet after connecting

* update monero_c

* Fix windows builds

* update monero_c

* update monero_c

* unshallow submodule

* cherry pick CW-867 Wownero fixes ()

* fix wownero syntax error

* remove print statements in zano

* update zano node URL

* [PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948)

* drop env -i to fix cmake build errors on newer system

* [skip ci] Revert "[PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948)"

This reverts commit 5acb5bfe57.

* [run tests] [skip slack] Fix env in build

* Dynamically detect number of cores used to build monero_c, since it appears that zano requires more memory to link (and it reliably fails for first couple builds due to OOM on CI/VM with memory constrains).
Drop unshallowing of all modules
[run tests]

* Changes from review [run tests]

* drop zano on linux (missing symbols)
fix wownero on linux
add aarch64-linux-gnu
[run tests]

* - remove duplicate entry in addToken()
- use walletPassword in createZanoNewWalletCredentials
- remove createZanoRestoreWalletFromKeysCredentials
[run tests]

* [skip ci] update dockerfile

* fix parameter issue

---------

Co-authored-by: leo <leonid.ivanov@gmail.com>
Co-authored-by: cr.zoidberg <crypto.zoidberg@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* - Add Zano Aliases
- Enable simpleswap [skip ci]
- Fix settings migration versions

* push missing file [skip ci]

* Zano pr with CW-685 passphrase ()

* my experiments

* Inital code for Zano integration

* Added missing android log lib

* added dummy wallet & some zano implementation

* fixing api for zano

* fixed zano build script

* attempt tp fix namespace problem

* added copy script for Zano files

* changes for zano wallet

* last updates

* zano.dart test app

* wallet recovery

* added pending transfer, some cleanup of unused

* some cleanup

* send + receive qr code

* last upd

* updated build_zano.sh

* updated zano ApiCalls, removed dummy

* updated zano ApiCalls, removed dummy

* added logging for get status/get info

* restored old wallet.dart

* restoring original versions of files

* restoring original versions of files

* restored get_height_by_date.dart, removed unnecessary calls for zano get height

* restoring original versions

* added multiple destinations, send all flag; some refactoring

* logging

* removed the duplicate

* fixed syncing sync status, decimal division, safe null json parsing

* some fixes after merge

* added multibalance/asset support for zano (ui)

* adding/removing from whitelist

* transfers in different assets

* transfers for multiple destinations and send all, some refactoring

* whitelists, some refactoring

* added different digits (decimal points) for formatters, some refactoring

* open, create, restore wallet refactoring; whitelists

* whitelists

* getting and updating transaction list; restoring a wallet from QR code

* several attempts to close wallet

* some refactoring

* added seed phrase

* CW-685 Add passphrase restore for xmr/wow

* add support for polyseed passphrase

* disable 14 word seed passphrase (not supported in wownero-seed)
fix: Getting grayed screen on latest passphrase build after having restored a 14-word wownero seed (+passphrase) and attempting to restore a XMR seed, legacy or otherwise.

* fix pointer when restoring depracated wownero seed

* Fix polyseed encryption

* changed fields to BigInt, some fixes

* modified build scripts for android

* build scripts

* restored accidently removed cw_haven.dart

* inital ios integration(zano libs built)

* update in script

* latest changes

* changes from review

* remove unused code

* add passphrase back to the screen
add passphrase to qr code backup export

* fix settings leaking through currencies on seed restore

* fix monero.com builds, make passphrase a getter on WalletBase

* add support for weird polyseed

* store passphrase for weird polyseed

* show encrypted seed only when passphrase is not empty

* force set restore height

* Applied a patch for iOS build (Boost and Zano scripts)

* Removed zano.dart (script-generated) and some unnecessary files

* Revert "Removed zano.dart (script-generated) and some unnecessary files"

This reverts commit 367c86398e.

* Removed zano.dart (generated by scripts), some files restored to initial versions

* fix build issues

* fix build errors

* added timer library

* changed paths in build_zano.sh

* build_zano.sh

* edit_token_page.dart - removed flag skipZanoAddressValidation

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* lib/core/address_validator.dart - updated zano address regex
lib/src/screens/dashboard/edit_token_page.dart - using AddressValidator().call

* fix zano build issues on android

* remove contrib/depends to save space

* move async call to a synchronouse one

* call sync call in isolate to make it async
generate framework for iOS as well
fix UR issues

* zano changes from monero_c repo

* update monero_c hash

* fix invalid zano imports, add support for linux, speed up CI builds

* update monero_c hash

* bump monero_c commit (yes, again, I know)

* fix wallet resttore, fix hardcoded IP

* fix regex, don't throw error when opening wallet, fix tx history, fix async calls, move stuff to isolate

* fix api calls in async transaction creation

* fix configure script

* update build scripts

* fix some build issues

* update dependencies

* fix dependencies

* update ci scripts

* Improve multithread use of zano api

* Fix build issue

* fix zano node selection, move other zano calls to separate isolate

* update moneroc hash
WIP fixes for zano

* update monero_c

* fix monero.com builds

* sync wallet after connecting

* update monero_c

* Fix windows builds

* update monero_c

* print -> printV

* update monero_c

* unshallow submodule

* cherry pick CW-867 Wownero fixes ()

* fix wownero syntax error

* remove print statements in zano

* update zano node URL

* [PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948)

* drop env -i to fix cmake build errors on newer system

* Update lib/view_model/wallet_keys_view_model.dart [skip ci]

* Update lib/view_model/wallet_keys_view_model.dart [skip ci]

* Update tool/configure.dart [skip ci]

* Update lib/view_model/wallet_new_vm.dart

* [skip ci] Revert "[PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948)"

This reverts commit 5acb5bfe57.

* [run tests] [skip slack] Fix env in build

* Dynamically detect number of cores used to build monero_c, since it appears that zano requires more memory to link (and it reliably fails for first couple builds due to OOM on CI/VM with memory constrains).
Drop unshallowing of all modules
[run tests]

* Changes from review [run tests]

* drop zano on linux (missing symbols)
fix wownero on linux
add aarch64-linux-gnu
[run tests]

* - remove duplicate entry in addToken()
- use walletPassword in createZanoNewWalletCredentials
- remove createZanoRestoreWalletFromKeysCredentials
[run tests]

* [skip ci] update dockerfile

* reuse existing passphrase field

* add passphrase support for zano

* Drop aarch64-linux-gnu for now.

* fix passphrase display, fix gray screen

* catch errors in polyseed encryption, encrypt only polyseed, fix coin in wownero

* update monero_c
update wownero to 0.11.3.0

* Show passphrase only when non-empty, fix passphrase being displayed as view key private.

* fix NanoAccountListPage showing up instead of MoneroAccountListPage for wownero

* build zano dependencies on android

* fix parameter issue

* minor merge leftover [skip ci]

* minor cleanup [skip ci]

* fix zano alias
update eth url for ens lookup
change $MAKE_JOB_COUNT to $NPROC

* minor cleanup [skip ci]

* fix zano alias

* Disable passphrase for creation of xmr/wow/zano
minor fixes

* fix zano on iOS

* - Fix get token data
- Enable unavailable balance
- Enable confirmations count
- Adjust explorer link

---------

Co-authored-by: leo <leonid.ivanov@gmail.com>
Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
Co-authored-by: cr.zoidberg <crypto.zoidberg@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

---------

Co-authored-by: cyan <cyjan@mrcyjanek.net>
Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
Co-authored-by: leo <leonid.ivanov@gmail.com>
Co-authored-by: cr.zoidberg <crypto.zoidberg@gmail.com>
This commit is contained in:
Omar Hatem 2025-01-24 20:33:24 +02:00 committed by GitHub
parent 9cda2c99e7
commit df3a26dc15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
169 changed files with 6113 additions and 212 deletions
.github/workflows
.gitignoreanalysis_options.yaml
android/app/src/main
assets
cw_bitcoin
cw_core
cw_haven
cw_monero
cw_wownero
cw_zano
ios
lib

View file

@ -1,6 +1,6 @@
name: Cake Wallet Android
on: [push]
on: [pull_request]
defaults:
run:

View file

@ -1,6 +1,6 @@
name: Cake Wallet Linux
on: [push]
on: [pull_request]
defaults:
run:

5
.gitignore vendored
View file

@ -126,7 +126,7 @@ cw_shared_external/ios/External/
cw_haven/ios/External/
cw_haven/android/.externalNativeBuild/
cw_haven/android/.cxx/
cw_zano/ios/External/
lib/bitcoin/bitcoin.dart
lib/monero/monero.dart
lib/haven/haven.dart
@ -137,6 +137,7 @@ lib/polygon/polygon.dart
lib/solana/solana.dart
lib/tron/tron.dart
lib/wownero/wownero.dart
lib/zano/zano.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
@ -179,3 +180,5 @@ scripts/monero_c
# iOS generated framework bin
ios/MoneroWallet.framework/MoneroWallet
ios/WowneroWallet.framework/WowneroWallet
ios/ZanoWallet.framework/ZanoWallet
*_libwallet2_api_c.dylib

View file

@ -22,6 +22,7 @@ analyzer:
lib/solana/cw_solana.dart,
lib/tron/cw_tron.dart,
lib/wownero/cw_wownero.dart,
lib/zano/cw_zano.dart,
]
language:
strict-casts: true

View file

@ -89,6 +89,9 @@
<data android:scheme="wownero" />
<data android:scheme="wownero-wallet" />
<data android:scheme="wownero_wallet" />
<data android:scheme="zano" />
<data android:scheme="zano-wallet" />
<data android:scheme="zano_wallet" />
</intent-filter>
<!-- nano-gpt link scheme -->
<intent-filter android:autoVerify="true">

View file

@ -0,0 +1 @@
../../../../../../scripts/monero_c/release/zano/aarch64-linux-android_libwallet2_api_c.so

View file

@ -0,0 +1 @@
../../../../../../scripts/monero_c/release/zano/armv7a-linux-androideabi_libwallet2_api_c.so

View file

@ -0,0 +1 @@
../../../../../../scripts/monero_c/release/zano/x86_64-linux-android_libwallet2_api_c.so

BIN
assets/images/zano_icon.png Normal file

Binary file not shown.

After

(image error) Size: 1.8 KiB

View file

@ -0,0 +1,4 @@
-
uri: 37.27.100.59:10500
is_default: true
useSSL: false

View file

@ -316,6 +316,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.10"
decimal:
dependency: transitive
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: transitive
description:
@ -797,6 +805,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.2"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
rxdart:
dependency: "direct main"
description:

View file

@ -1,3 +1,5 @@
import 'package:decimal/decimal.dart';
import 'package:decimal/intl.dart';
import 'package:intl/intl.dart';
import 'package:cw_core/crypto_currency.dart';
@ -43,6 +45,8 @@ class AmountConverter {
case CryptoCurrency.xnzd:
case CryptoCurrency.xusd:
return _moneroAmountToString(amount);
case CryptoCurrency.zano:
return _moneroAmountToStringUsingDecimals(amount);
default:
return '';
}
@ -59,4 +63,10 @@ class AmountConverter {
static String _wowneroAmountToString(int amount) => _wowneroAmountFormat
.format(cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider));
static Decimal cryptoAmountToDecimal({required int amount, required int divider}) =>
(Decimal.fromInt(amount) / Decimal.fromInt(divider)).toDecimal();
static String _moneroAmountToStringUsingDecimals(int amount) => _moneroAmountFormat.format(
DecimalIntl(cryptoAmountToDecimal(amount: amount, divider: _moneroAmountDivider)));
}

View file

@ -106,6 +106,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.usdcTrc20,
CryptoCurrency.tbtc,
CryptoCurrency.wow,
CryptoCurrency.zano,
CryptoCurrency.ton,
CryptoCurrency.flip
];
@ -226,8 +227,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11);
static const ton = CryptoCurrency(title: 'TON', fullName: 'Toncoin', raw: 95, name: 'ton', iconPath: 'assets/images/ton_icon.png', decimals: 8);
static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 96, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18);
static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 96, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12);
static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 97, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18);
static final Map<int, CryptoCurrency> _rawCurrencyMap =
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {

View file

@ -30,8 +30,11 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
return CryptoCurrency.trx;
case WalletType.wownero:
return CryptoCurrency.wow;
case WalletType.zano:
return CryptoCurrency.zano;
case WalletType.none:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}
}

View file

@ -19,4 +19,5 @@ const DERIVATION_INFO_TYPE_ID = 17;
const TRON_TOKEN_TYPE_ID = 18;
const HARDWARE_WALLET_TYPE_TYPE_ID = 19;
const MWEB_UTXO_TYPE_ID = 20;
const HAVEN_SEED_STORE_TYPE_ID = 21;
const HAVEN_SEED_STORE_TYPE_ID = 21;
const ZANO_ASSET_TYPE_ID = 22;

View file

@ -4,11 +4,13 @@ class MoneroWalletKeys {
required this.privateSpendKey,
required this.privateViewKey,
required this.publicSpendKey,
required this.publicViewKey});
required this.publicViewKey,
required this.passphrase});
final String primaryAddress;
final String publicViewKey;
final String privateViewKey;
final String publicSpendKey;
final String privateSpendKey;
final String passphrase;
}

View file

@ -1,5 +1,4 @@
import 'dart:io';
import 'dart:math';
import 'package:cw_core/keyable.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'dart:convert';
@ -10,7 +9,6 @@ import 'package:cw_core/wallet_type.dart';
import 'package:http/io_client.dart' as ioc;
import 'dart:math' as math;
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart' as crypto;
import 'package:crypto/crypto.dart';
@ -105,7 +103,9 @@ class Node extends HiveObject with Keyable {
case WalletType.solana:
case WalletType.tron:
return Uri.parse(
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
case WalletType.zano:
return Uri.https(uriRaw, '');
case WalletType.none:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -166,6 +166,8 @@ class Node extends HiveObject with Keyable {
case WalletType.solana:
case WalletType.tron:
return requestElectrumServer();
case WalletType.zano:
return requestZanoNode();
case WalletType.none:
return false;
}
@ -174,7 +176,11 @@ class Node extends HiveObject with Keyable {
}
}
Future<bool> requestMoneroNode() async {
Future<bool> requestZanoNode() async {
return requestMoneroNode(methodName: "getinfo");
}
Future<bool> requestMoneroNode({String methodName = 'get_info'}) async {
if (useSocksProxy) {
return await requestNodeWithProxy();
}
@ -182,8 +188,7 @@ class Node extends HiveObject with Keyable {
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': 'get_info'};
final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
try {
final authenticatingClient = HttpClient();
@ -226,7 +231,7 @@ class Node extends HiveObject with Keyable {
final oldUseSSL = useSSL;
useSSL = true;
try {
final ret = await requestMoneroNode();
final ret = await requestMoneroNode(methodName: methodName);
if (ret == true) {
await save();
return ret;

View file

@ -16,6 +16,7 @@ const walletTypes = [
WalletType.polygon,
WalletType.solana,
WalletType.tron,
WalletType.zano,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@ -58,6 +59,10 @@ enum WalletType {
@HiveField(12)
wownero,
@HiveField(13)
zano,
}
int serializeToInt(WalletType type) {
@ -86,6 +91,8 @@ int serializeToInt(WalletType type) {
return 10;
case WalletType.wownero:
return 11;
case WalletType.zano:
return 12;
case WalletType.none:
return -1;
}
@ -117,8 +124,11 @@ WalletType deserializeFromInt(int raw) {
return WalletType.tron;
case 11:
return WalletType.wownero;
case 12:
return WalletType.zano;
default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
throw Exception(
'Unexpected token: $raw for WalletType deserializeFromInt');
}
}
@ -148,6 +158,8 @@ String walletTypeToString(WalletType type) {
return 'Tron';
case WalletType.wownero:
return 'Wownero';
case WalletType.zano:
return 'Zano';
case WalletType.none:
return '';
}
@ -179,6 +191,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Tron (TRX)';
case WalletType.wownero:
return 'Wownero (WOW)';
case WalletType.zano:
return 'Zano (ZANO)';
case WalletType.none:
return '';
}
@ -213,6 +227,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
return CryptoCurrency.trx;
case WalletType.wownero:
return CryptoCurrency.wow;
case WalletType.zano:
return CryptoCurrency.zano;
case WalletType.none:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');

117
cw_core/lib/zano_asset.dart Normal file
View file

@ -0,0 +1,117 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart';
part 'zano_asset.g.dart';
@HiveType(typeId: ZanoAsset.typeId)
class ZanoAsset extends CryptoCurrency with HiveObjectMixin {
@HiveField(0)
final String fullName;
@HiveField(1)
final String ticker;
@HiveField(2)
final String assetId;
@HiveField(3)
final int decimalPoint;
@HiveField(4, defaultValue: true)
bool _enabled;
@HiveField(5)
final String? iconPath;
// @HiveField(6)
// final String? tag;
@HiveField(6)
final String owner;
@HiveField(7)
final String metaInfo;
@HiveField(8)
final BigInt currentSupply;
@HiveField(9)
final bool hiddenSupply;
@HiveField(10)
final BigInt totalMaxSupply;
@HiveField(11)
final bool isInGlobalWhitelist;
bool get enabled => _enabled;
set enabled(bool value) => _enabled = value;
ZanoAsset({
this.fullName = '',
this.ticker = '',
required this.assetId,
this.decimalPoint = 12,
bool enabled = true,
this.iconPath,
this.owner = defaultOwner,
this.metaInfo = '',
required this.currentSupply,
this.hiddenSupply = false,
required this.totalMaxSupply,
this.isInGlobalWhitelist = false,
}) : _enabled = enabled,
super(
name: fullName,
title: ticker.toUpperCase(),
fullName: fullName,
tag: 'ZANO',
iconPath: iconPath,
decimals: decimalPoint,
);
ZanoAsset.copyWith(ZanoAsset other, {String? icon, String? assetId, bool enabled = true})
: this.fullName = other.fullName,
this.ticker = other.ticker,
this.assetId = assetId ?? other.assetId,
this.decimalPoint = other.decimalPoint,
this._enabled = enabled && other.enabled,
this.iconPath = icon,
this.currentSupply = other.currentSupply,
this.hiddenSupply = other.hiddenSupply,
this.metaInfo = other.metaInfo,
this.owner = other.owner,
this.totalMaxSupply = other.totalMaxSupply,
this.isInGlobalWhitelist = other.isInGlobalWhitelist,
super(
name: other.name,
title: other.ticker.toUpperCase(),
fullName: other.name,
tag: 'ZANO',
iconPath: icon,
decimals: other.decimalPoint,
enabled: enabled,
);
factory ZanoAsset.fromJson(Map<String, dynamic> json, {bool isInGlobalWhitelist = false}) => ZanoAsset(
assetId: json['asset_id'] as String? ?? '',
currentSupply: bigIntFromDynamic(json['current_supply']),
decimalPoint: json['decimal_point'] as int? ?? 12,
fullName: json['full_name'] as String? ?? '',
hiddenSupply: json['hidden_supply'] as bool? ?? false,
metaInfo: json['meta_info'] as String? ?? '',
owner: json['owner'] as String? ?? '',
ticker: json['ticker'] as String? ?? '',
totalMaxSupply: bigIntFromDynamic(json['total_max_supply']),
isInGlobalWhitelist: isInGlobalWhitelist,
);
static const typeId = ZANO_ASSET_TYPE_ID;
static const zanoAssetsBoxName = 'zanoAssetsBox';
static const defaultOwner = '0000000000000000000000000000000000000000000000000000000000000000';
}
BigInt bigIntFromDynamic(dynamic d) {
if (d is int) {
return BigInt.from(d);
} else if (d is BigInt) {
return d;
} else if (d == null) {
return BigInt.zero;
} else {
throw 'cannot cast value of type ${d.runtimeType} to BigInt';
//return BigInt.zero;
}
}

View file

@ -207,6 +207,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.7"
decimal:
dependency: "direct main"
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: "direct main"
description:
@ -577,6 +585,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shelf:
dependency: transitive
description:

View file

@ -19,6 +19,7 @@ dependencies:
flutter_mobx: ^2.0.6+1
intl: ^0.19.0
encrypt: ^5.0.1
decimal: ^2.3.3
cake_backup:
git:
url: https://github.com/cake-tech/cake_backup.git

View file

@ -78,7 +78,8 @@ abstract class HavenWalletBase
privateSpendKey: haven_wallet.getSecretSpendKey(),
privateViewKey: haven_wallet.getSecretViewKey(),
publicSpendKey: haven_wallet.getPublicSpendKey(),
publicViewKey: haven_wallet.getPublicViewKey());
publicViewKey: haven_wallet.getPublicViewKey(),
passphrase: "");
haven_wallet.SyncListener? _listener;
ReactionDisposer? _onAccountChangeReaction;

View file

@ -209,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.4"
decimal:
dependency: transitive
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: transitive
description:
@ -571,6 +579,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shelf:
dependency: transitive
description:

View file

@ -8,6 +8,7 @@ import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
import 'package:flutter/foundation.dart';
import 'package:monero/monero.dart' as monero;
import 'package:mutex/mutex.dart';
import 'package:polyseed/polyseed.dart';
bool debugMonero = false;
@ -36,19 +37,34 @@ String getSeed() {
// monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
final cakepolyseed =
monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed");
final cakepassphrase = getPassphrase();
final weirdPolyseed = monero.Wallet_getPolyseed(wptr!, passphrase: cakepassphrase);
if (weirdPolyseed != "") return weirdPolyseed;
if (cakepolyseed != "") {
if (cakepassphrase != "") {
try {
final lang = PolyseedLang.getByPhrase(cakepassphrase);
final coin = PolyseedCoin.POLYSEED_MONERO;
final ps = Polyseed.decode(cakepolyseed, lang, coin);
final passphrase = getPassphrase();
if (ps.isEncrypted || passphrase == "") return ps.encode(lang, coin);
ps.crypt(getPassphrase());
return ps.encode(lang, coin);
} catch (e) {
printV(e);
}
}
return cakepolyseed;
}
final polyseed = monero.Wallet_getPolyseed(wptr!, passphrase: '');
if (polyseed != "") {
return polyseed;
}
final legacy = getSeedLegacy("English");
final legacy = getSeedLegacy(null);
return legacy;
}
String getSeedLegacy(String? language) {
var legacy = monero.Wallet_seed(wptr!, seedOffset: '');
final cakepassphrase = getPassphrase();
var legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
switch (language) {
case "Chinese (Traditional)": language = "Chinese (simplified)"; break;
case "Chinese (Simplified)": language = "Chinese (simplified)"; break;
@ -58,7 +74,7 @@ String getSeedLegacy(String? language) {
}
if (monero.Wallet_status(wptr!) != 0) {
monero.Wallet_setSeedLanguage(wptr!, language: language ?? "English");
legacy = monero.Wallet_seed(wptr!, seedOffset: '');
legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
}
if (monero.Wallet_status(wptr!) != 0) {
final err = monero.Wallet_errorString(wptr!);
@ -70,6 +86,10 @@ String getSeedLegacy(String? language) {
return legacy;
}
String getPassphrase() {
return monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.passphrase");
}
Map<int, Map<int, Map<int, String>>> addressCache = {};
String getAddress({int accountIndex = 0, int addressIndex = 0}) {

View file

@ -95,9 +95,10 @@ bool isWalletExistSync({required String path}) {
void restoreWalletFromSeedSync(
{required String path,
required String password,
required String passphrase,
required String seed,
int nettype = 0,
int restoreHeight = 0}) {
int restoreHeight = 0}) async {
txhistory = null;
final newWptr = monero.WalletManager_recoveryWallet(
wmPtr,
@ -105,7 +106,7 @@ void restoreWalletFromSeedSync(
password: password,
mnemonic: seed,
restoreHeight: restoreHeight,
seedOffset: '',
seedOffset: passphrase,
networkType: 0,
);
@ -117,7 +118,13 @@ void restoreWalletFromSeedSync(
}
wptr = newWptr;
setRefreshFromBlockHeight(height: restoreHeight);
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
openedWalletsByPath[path] = wptr!;
monero.Wallet_store(wptr!);
_lastOpenedWallet = path;
}
@ -189,6 +196,48 @@ void restoreWalletFromKeysSync(
_lastOpenedWallet = path;
}
// English only, because normalization.
void restoreWalletFromPolyseedWithOffset(
{required String path,
required String password,
required String seed,
required String seedOffset,
required String language,
int nettype = 0}) {
txhistory = null;
final newWptr = monero.WalletManager_createWalletFromPolyseed(
wmPtr,
path: path,
password: password,
networkType: nettype,
mnemonic: seed,
seedOffset: seedOffset,
newWallet: true, // safe to remove
restoreHeight: 0,
kdfRounds: 1,
);
final status = monero.Wallet_status(newWptr);
if (status != 0) {
final err = monero.Wallet_errorString(newWptr);
printV("err: $err");
throw WalletRestoreFromKeysException(message: err);
}
wptr = newWptr;
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: seedOffset);
monero.Wallet_store(wptr!);
storeSync();
openedWalletsByPath[path] = wptr!;
}
void restoreWalletFromSpendKeySync(
{required String path,
required String password,
@ -348,11 +397,12 @@ void _createWallet(Map<String, dynamic> args) {
void _restoreFromSeed(Map<String, dynamic> args) {
final path = args['path'] as String;
final password = args['password'] as String;
final passphrase = args['passphrase'] as String;
final seed = args['seed'] as String;
final restoreHeight = args['restoreHeight'] as int;
restoreWalletFromSeedSync(
path: path, password: password, seed: seed, restoreHeight: restoreHeight);
path: path, password: password, passphrase: passphrase, seed: seed, restoreHeight: restoreHeight);
}
void _restoreFromKeys(Map<String, dynamic> args) {
@ -420,12 +470,14 @@ Future<void> createWallet(
Future<void> restoreFromSeed(
{required String path,
required String password,
required String passphrase,
required String seed,
int nettype = 0,
int restoreHeight = 0}) async =>
_restoreFromSeed({
'path': path,
'password': password,
'passphrase': passphrase,
'seed': seed,
'nettype': nettype,
'restoreHeight': restoreHeight

View file

@ -122,13 +122,17 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
@override
String get password => _password;
@override
String get passphrase => monero_wallet.getPassphrase();
@override
MoneroWalletKeys get keys => MoneroWalletKeys(
primaryAddress: monero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: monero_wallet.getSecretSpendKey(),
privateViewKey: monero_wallet.getSecretViewKey(),
publicSpendKey: monero_wallet.getPublicSpendKey(),
publicViewKey: monero_wallet.getPublicViewKey());
publicViewKey: monero_wallet.getPublicViewKey(),
passphrase: monero_wallet.getPassphrase());
int? get restoreHeight =>
transactionHistory.transactions.values.firstOrNull?.height;

View file

@ -11,6 +11,8 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart';
@ -42,10 +44,15 @@ class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
MoneroRestoreWalletFromSeedCredentials(
{required String name, required this.mnemonic, int height = 0, String? password})
{required String name,
required this.mnemonic,
required this.passphrase,
int height = 0,
String? password})
: super(name: name, password: password, height: height);
final String mnemonic;
final String passphrase;
}
class MoneroWalletLoadingException implements Exception {
@ -94,12 +101,14 @@ class MoneroWalletService extends WalletService<
final polyseed = Polyseed.create();
final lang = PolyseedLang.getByEnglishName(credentials.language);
if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
final heightOverride =
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
overrideHeight: heightOverride);
overrideHeight: heightOverride, passphrase: credentials.passphrase);
}
await monero_wallet_manager.createWallet(
@ -292,6 +301,7 @@ class MoneroWalletService extends WalletService<
await monero_wallet_manager.restoreFromSeed(
path: path,
password: credentials.password!,
passphrase: credentials.passphrase,
seed: credentials.mnemonic,
restoreHeight: credentials.height!);
final wallet = MoneroWallet(
@ -318,7 +328,8 @@ class MoneroWalletService extends WalletService<
Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
passphrase: credentials.passphrase);
} catch (e) {
// TODO: Implement Exception for wallet list service.
printV('MoneroWalletsManager Error: $e');
@ -326,9 +337,35 @@ class MoneroWalletService extends WalletService<
}
}
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
Future<MoneroWallet> _restoreFromPolyseed(
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO,
int? overrideHeight,
String? passphrase}) async {
if (polyseed.isEncrypted == false &&
(passphrase??'') != "") {
// Fallback to the different passphrase offset method, when a passphrase
// was provided but the polyseed is not encrypted.
monero_wallet_manager.restoreWalletFromPolyseedWithOffset(
path: path,
password: password,
seed: polyseed.encode(lang, coin),
seedOffset: passphrase??'',
language: "English");
final wallet = MoneroWallet(
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
password: password,
);
await wallet.init();
return wallet;
}
if (polyseed.isEncrypted) polyseed.crypt(passphrase ?? '');
final height = overrideHeight ??
getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
final spendKey = polyseed.generateKey(coin, 32).toHexString();

View file

@ -1 +1 @@
/Users/omarhatem/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/
/home/parallels/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/

View file

@ -225,6 +225,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.10"
decimal:
dependency: transitive
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: "direct main"
description:
@ -503,8 +511,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
ref: af5277f96073917185864d3596e82b67bee54e78
resolved-ref: af5277f96073917185864d3596e82b67bee54e78
ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"
@ -660,6 +668,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
rxdart:
dependency: transitive
description:

View file

@ -25,8 +25,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: af5277f96073917185864d3596e82b67bee54e78
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
path: impls/monero.dart
mutex: ^3.1.0
ledger_flutter_plus: ^1.4.1

View file

@ -10,7 +10,7 @@ import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dar
import 'package:ffi/ffi.dart';
import 'package:monero/wownero.dart' as wownero;
import 'package:monero/src/generated_bindings_wownero.g.dart' as wownero_gen;
import 'package:mutex/mutex.dart';
String getTxKey(String txId) {
final ret = wownero.Wallet_getTxKey(wptr!, txid: txId);
@ -18,6 +18,7 @@ String getTxKey(String txId) {
return ret;
}
final txHistoryMutex = Mutex();
wownero.TransactionHistory? txhistory;
bool isRefreshingTx = false;
@ -26,22 +27,25 @@ Future<void> refreshTransactions() async {
isRefreshingTx = true;
txhistory ??= wownero.Wallet_history(wptr!);
final ptr = txhistory!.address;
await txHistoryMutex.acquire();
await Isolate.run(() {
wownero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
});
txHistoryMutex.release();
isRefreshingTx = false;
}
int countOfTransactions() => wownero.TransactionHistory_count(txhistory!);
List<Transaction> getAllTransactions() {
Future<List<Transaction>> getAllTransactions() async {
List<Transaction> dummyTxs = [];
await txHistoryMutex.acquire();
txhistory ??= wownero.Wallet_history(wptr!);
wownero.TransactionHistory_refresh(txhistory!);
int size = countOfTransactions();
final list = List.generate(size, (index) => Transaction(txInfo: wownero.TransactionHistory_transaction(txhistory!, index: index)));
txHistoryMutex.release();
final accts = wownero.Wallet_numSubaddressAccounts(wptr!);
for (var i = 0; i < accts; i++) {
final fullBalance = wownero.Wallet_balance(wptr!, accountIndex: i);

View file

@ -7,6 +7,7 @@ import 'package:cw_wownero/api/account_list.dart';
import 'package:cw_wownero/api/exceptions/setup_wallet_exception.dart';
import 'package:monero/wownero.dart' as wownero;
import 'package:mutex/mutex.dart';
import 'package:polyseed/polyseed.dart';
int getSyncingHeight() {
// final height = wownero.WOWNERO_cw_WalletListener_height(getWlptr());
@ -32,22 +33,37 @@ bool isNewTransactionExist() {
String getFilename() => wownero.Wallet_filename(wptr!);
String getSeed() {
// wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
// monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
final cakepolyseed =
wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed");
final cakepassphrase = getPassphrase();
final weirdPolyseed = wownero.Wallet_getPolyseed(wptr!, passphrase: cakepassphrase);
if (weirdPolyseed != "") return weirdPolyseed;
if (cakepolyseed != "") {
if (cakepassphrase != "") {
try {
final lang = PolyseedLang.getByPhrase(cakepassphrase);
final coin = PolyseedCoin.POLYSEED_WOWNERO;
final ps = Polyseed.decode(cakepolyseed, lang, coin);
final passphrase = getPassphrase();
if (ps.isEncrypted || passphrase == "") return ps.encode(lang, coin);
ps.crypt(passphrase);
return ps.encode(lang, coin);
} catch (e) {
printV(e);
}
}
return cakepolyseed;
}
final polyseed = wownero.Wallet_getPolyseed(wptr!, passphrase: '');
if (polyseed != "") {
return polyseed;
}
final legacy = getSeedLegacy(null);
return legacy;
}
String getSeedLegacy(String? language) {
var legacy = wownero.Wallet_seed(wptr!, seedOffset: '');
final cakepassphrase = getPassphrase();
var legacy = wownero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
switch (language) {
case "Chinese (Traditional)": language = "Chinese (simplified)"; break;
case "Chinese (Simplified)": language = "Chinese (simplified)"; break;
@ -57,7 +73,7 @@ String getSeedLegacy(String? language) {
}
if (wownero.Wallet_status(wptr!) != 0) {
wownero.Wallet_setSeedLanguage(wptr!, language: language ?? "English");
legacy = wownero.Wallet_seed(wptr!, seedOffset: '');
legacy = wownero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
}
if (wownero.Wallet_status(wptr!) != 0) {
final err = wownero.Wallet_errorString(wptr!);
@ -70,6 +86,10 @@ String getSeedLegacy(String? language) {
}
Map<int, Map<int, Map<int, String>>> addressCache = {};
String getPassphrase() {
return wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.passphrase");
}
String getAddress({int accountIndex = 0, int addressIndex = 1}) {
while (wownero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
printV("adding subaddress");
@ -333,4 +353,4 @@ String signMessage(String message, {String address = ""}) {
bool verifyMessage(String message, String address, String signature) {
return wownero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
}
}

View file

@ -90,6 +90,7 @@ bool isWalletExistSync({required String path}) {
void restoreWalletFromSeedSync(
{required String path,
required String password,
required String passphrase,
required String seed,
int nettype = 0,
int restoreHeight = 0}) {
@ -102,10 +103,12 @@ void restoreWalletFromSeedSync(
language: seed, // I KNOW - this is supposed to be called seed
networkType: 0,
);
final oldwptr = wptr;
wptr = newWptr;
setRefreshFromBlockHeight(
height: wownero.WOWNERO_deprecated_14WordSeedHeight(seed: seed),
);
wptr = oldwptr;
} else {
txhistory = null;
newWptr = wownero.WalletManager_recoveryWallet(
@ -114,7 +117,7 @@ void restoreWalletFromSeedSync(
password: password,
mnemonic: seed,
restoreHeight: restoreHeight,
seedOffset: '',
seedOffset: passphrase,
networkType: 0,
);
}
@ -127,8 +130,13 @@ void restoreWalletFromSeedSync(
}
wptr = newWptr;
wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
openedWalletsByPath[path] = wptr!;
store();
}
void restoreWalletFromKeysSync(
@ -196,6 +204,48 @@ void restoreWalletFromKeysSync(
openedWalletsByPath[path] = wptr!;
}
// English only, because normalization.
void restoreWalletFromPolyseedWithOffset(
{required String path,
required String password,
required String seed,
required String seedOffset,
required String language,
int nettype = 0}) {
txhistory = null;
final newWptr = wownero.WalletManager_createWalletFromPolyseed(
wmPtr,
path: path,
password: password,
networkType: nettype,
mnemonic: seed,
seedOffset: seedOffset,
newWallet: true, // safe to remove
restoreHeight: 0,
kdfRounds: 1,
);
final status = wownero.Wallet_status(newWptr);
if (status != 0) {
final err = wownero.Wallet_errorString(newWptr);
printV("err: $err");
throw WalletRestoreFromKeysException(message: err);
}
wptr = newWptr;
wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: seedOffset);
storeSync();
openedWalletsByPath[path] = wptr!;
}
void restoreWalletFromSpendKeySync(
{required String path,
required String password,
@ -319,11 +369,12 @@ void _createWallet(Map<String, dynamic> args) {
void _restoreFromSeed(Map<String, dynamic> args) {
final path = args['path'] as String;
final password = args['password'] as String;
final passphrase = args['passphrase'] as String;
final seed = args['seed'] as String;
final restoreHeight = args['restoreHeight'] as int;
restoreWalletFromSeedSync(
path: path, password: password, seed: seed, restoreHeight: restoreHeight);
path: path, password: password, passphrase: passphrase, seed: seed, restoreHeight: restoreHeight);
}
void _restoreFromKeys(Map<String, dynamic> args) {
@ -391,12 +442,14 @@ Future<void> createWallet(
Future<void> restoreFromSeed(
{required String path,
required String password,
required String passphrase,
required String seed,
int nettype = 0,
int restoreHeight = 0}) async =>
_restoreFromSeed({
'path': path,
'password': password,
'passphrase': passphrase,
'seed': seed,
'nettype': nettype,
'restoreHeight': restoreHeight

View file

@ -117,6 +117,9 @@ abstract class WowneroWalletBase
String get password => _password;
@override
String get passphrase => wownero_wallet.getPassphrase();
String _password;
@override
@ -125,7 +128,8 @@ abstract class WowneroWalletBase
privateSpendKey: wownero_wallet.getSecretSpendKey(),
privateViewKey: wownero_wallet.getSecretViewKey(),
publicSpendKey: wownero_wallet.getPublicSpendKey(),
publicViewKey: wownero_wallet.getPublicViewKey());
publicViewKey: wownero_wallet.getPublicViewKey(),
passphrase: wownero_wallet.getPassphrase());
wownero_wallet.SyncListener? _listener;
ReactionDisposer? _onAccountChangeReaction;
@ -557,7 +561,7 @@ abstract class WowneroWalletBase
@override
Future<Map<String, WowneroTransactionInfo>> fetchTransactions() async {
transaction_history.refreshTransactions();
return _getAllTransactionsOfAccount(walletAddresses.account?.id)
return (await _getAllTransactionsOfAccount(walletAddresses.account?.id))
.fold<Map<String, WowneroTransactionInfo>>(<String, WowneroTransactionInfo>{},
(Map<String, WowneroTransactionInfo> acc, WowneroTransactionInfo tx) {
acc[tx.id] = tx;
@ -586,9 +590,9 @@ abstract class WowneroWalletBase
String getSubaddressLabel(int accountIndex, int addressIndex) =>
wownero_wallet.getSubaddressLabel(accountIndex, addressIndex);
List<WowneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) =>
transaction_history
.getAllTransactions()
Future<List<WowneroTransactionInfo>> _getAllTransactionsOfAccount(int? accountIndex) async =>
(await transaction_history
.getAllTransactions())
.map(
(row) => WowneroTransactionInfo(
row.hash,

View file

@ -30,10 +30,11 @@ class WowneroNewWalletCredentials extends WalletCredentials {
class WowneroRestoreWalletFromSeedCredentials extends WalletCredentials {
WowneroRestoreWalletFromSeedCredentials(
{required String name, required this.mnemonic, int height = 0, String? password})
{required String name, required this.mnemonic, required this.passphrase, int height = 0, String? password})
: super(name: name, password: password, height: height);
final String mnemonic;
final String passphrase;
}
class WowneroWalletLoadingException implements Exception {
@ -83,12 +84,14 @@ class WowneroWalletService extends WalletService<
final polyseed = Polyseed.create();
final lang = PolyseedLang.getByEnglishName(credentials.language);
if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
final heightOverride =
getWowneroHeightByDate(date: DateTime.now().subtract(Duration(days: 2)));
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
overrideHeight: heightOverride);
overrideHeight: heightOverride, passphrase: credentials.passphrase);
}
await wownero_wallet_manager.createWallet(
@ -266,6 +269,7 @@ class WowneroWalletService extends WalletService<
await wownero_wallet_manager.restoreFromSeed(
path: path,
password: credentials.password!,
passphrase: credentials.passphrase,
seed: credentials.mnemonic,
restoreHeight: credentials.height!);
final wallet = WowneroWallet(
@ -289,7 +293,7 @@ class WowneroWalletService extends WalletService<
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
path, credentials.password!, polyseed, credentials.walletInfo!, lang, passphrase: credentials.passphrase);
} catch (e) {
// TODO: Implement Exception for wallet list service.
printV('WowneroWalletsManager Error: $e');
@ -299,7 +303,32 @@ class WowneroWalletService extends WalletService<
Future<WowneroWallet> _restoreFromPolyseed(
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_WOWNERO, int? overrideHeight}) async {
{PolyseedCoin coin = PolyseedCoin.POLYSEED_WOWNERO, int? overrideHeight, String? passphrase}) async {
if (polyseed.isEncrypted == false &&
(passphrase??'') != "") {
// Fallback to the different passphrase offset method, when a passphrase
// was provided but the polyseed is not encrypted.
wownero_wallet_manager.restoreWalletFromPolyseedWithOffset(
path: path,
password: password,
seed: polyseed.encode(lang, coin),
seedOffset: passphrase??'',
language: "English");
final wallet = WowneroWallet(
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
password: password,
);
await wallet.init();
return wallet;
}
if (polyseed.isEncrypted) polyseed.crypt(passphrase ?? '');
final height = overrideHeight ??
getWowneroHeightByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
final spendKey = polyseed.generateKey(coin, 32).toHexString();

View file

@ -209,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.4"
decimal:
dependency: transitive
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: "direct main"
description:
@ -463,8 +471,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
ref: af5277f96073917185864d3596e82b67bee54e78
resolved-ref: af5277f96073917185864d3596e82b67bee54e78
ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"
@ -612,6 +620,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shelf:
dependency: transitive
description:

View file

@ -25,8 +25,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: af5277f96073917185864d3596e82b67bee54e78
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70 # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0

7
cw_zano/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/

10
cw_zano/.metadata Normal file
View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb
channel: stable
project_type: plugin

3
cw_zano/CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
cw_zano/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

15
cw_zano/README.md Normal file
View file

@ -0,0 +1,15 @@
# cw_zano
A new flutter plugin project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,6 @@
class Consts {
static const errorWrongSeed = 'WRONG_SEED';
static const errorAlreadyExists = 'ALREADY_EXISTS';
static const errorWalletWrongId = 'WALLET_WRONG_ID';
static const errorBusy = 'BUSY';
}

View file

@ -0,0 +1,9 @@
class AssetIdParams {
final String assetId;
AssetIdParams({required this.assetId});
Map<String, dynamic> toJson() => {
'asset_id': assetId,
};
}

View file

@ -0,0 +1,32 @@
import 'package:cw_core/zano_asset.dart';
import 'package:cw_zano/model/zano_asset.dart';
import 'package:cw_zano/zano_formatter.dart';
class Balance {
final ZanoAsset assetInfo;
final BigInt awaitingIn;
final BigInt awaitingOut;
final BigInt total;
final BigInt unlocked;
Balance(
{required this.assetInfo,
required this.awaitingIn,
required this.awaitingOut,
required this.total,
required this.unlocked});
String get assetId => assetInfo.assetId;
@override
String toString() => '$assetInfo: $total/$unlocked';
factory Balance.fromJson(Map<String, dynamic> json) => Balance(
assetInfo:
ZanoAsset.fromJson(json['asset_info'] as Map<String, dynamic>? ?? {}),
awaitingIn: ZanoFormatter.bigIntFromDynamic(json['awaiting_in']),
awaitingOut: ZanoFormatter.bigIntFromDynamic(json['awaiting_out']),
total: ZanoFormatter.bigIntFromDynamic(json['total']),
unlocked: ZanoFormatter.bigIntFromDynamic(json['unlocked']),
);
}

View file

@ -0,0 +1,39 @@
import 'package:cw_zano/api/model/recent_history.dart';
import 'package:cw_zano/api/model/wi.dart';
class CreateWalletResult {
final String name;
final String pass;
final RecentHistory recentHistory;
final bool recovered;
final String seed;
final int walletFileSize;
final int walletId;
final int walletLocalBcSize;
final Wi wi;
CreateWalletResult(
{required this.name,
required this.pass,
required this.recentHistory,
required this.recovered,
required this.seed,
required this.walletFileSize,
required this.walletId,
required this.walletLocalBcSize,
required this.wi});
factory CreateWalletResult.fromJson(Map<String, dynamic> json) =>
CreateWalletResult(
name: json['name'] as String? ?? '',
pass: json['pass'] as String? ?? '',
recentHistory: RecentHistory.fromJson(
json['recent_history'] as Map<String, dynamic>? ?? {}),
recovered: json['recovered'] as bool? ?? false,
seed: json['seed'] as String? ?? '',
walletFileSize: json['wallet_file_size'] as int? ?? 0,
walletId: json['wallet_id'] as int? ?? 0,
walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0,
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
);
}

View file

@ -0,0 +1,20 @@
class Destination {
final BigInt amount; // transfered as string
final String address;
final String assetId;
Destination(
{required this.amount, required this.address, required this.assetId});
factory Destination.fromJson(Map<String, dynamic> json) => Destination(
amount: BigInt.parse(json['amount'] as String? ?? '0'),
address: json['address'] as String? ?? '',
assetId: json['asset_id'] as String? ?? '',
);
Map<String, dynamic> toJson() => {
'amount': amount.toString(),
'address': address,
'asset_id': assetId,
};
}

View file

@ -0,0 +1,18 @@
import 'package:cw_zano/api/model/receive.dart';
class EmployedEntries {
final List<Receive> receive;
final List<Receive> send;
EmployedEntries({required this.receive, required this.send});
factory EmployedEntries.fromJson(Map<String, dynamic> json) =>
EmployedEntries(
receive: json['receive'] == null ? [] : (json['receive'] as List<dynamic>)
.map((e) => Receive.fromJson(e as Map<String, dynamic>))
.toList(),
send: json['spent'] == null ? [] : (json['spent'] as List<dynamic>)
.map((e) => Receive.fromJson(e as Map<String, dynamic>))
.toList(),
);
}

View file

@ -0,0 +1,16 @@
class GetAddressInfoResult {
final bool valid;
final bool auditable;
final bool paymentId;
final bool wrap;
GetAddressInfoResult(
{required this.valid, required this.auditable, required this.paymentId, required this.wrap});
factory GetAddressInfoResult.fromJson(Map<String, dynamic> json) => GetAddressInfoResult(
valid: json['valid'] as bool? ?? false,
auditable: json['auditable'] as bool? ?? false,
paymentId: json['payment_id'] as bool? ?? false,
wrap: json['wrap'] as bool? ?? false,
);
}

View file

@ -0,0 +1,14 @@
class GetRecentTxsAndInfoParams {
final int offset;
final int count;
final bool updateProvisionInfo;
GetRecentTxsAndInfoParams({required this.offset, required this.count, this.updateProvisionInfo = true});
Map<String, dynamic> toJson() => {
'offset': offset,
'count': count,
'update_provision_info': updateProvisionInfo,
'order': 'FROM_BEGIN_TO_END',
};
}

View file

@ -0,0 +1,12 @@
import 'package:cw_zano/api/model/transfer.dart';
class GetRecentTxsAndInfoResult {
final List<Transfer> transfers;
final int lastItemIndex;
final int totalTransfers;
GetRecentTxsAndInfoResult({required this.transfers, required this.lastItemIndex, required this.totalTransfers});
GetRecentTxsAndInfoResult.empty(): this.transfers = [], this.lastItemIndex = 0, this.totalTransfers = 0;
}

View file

@ -0,0 +1,14 @@
import 'package:cw_zano/api/model/wi.dart';
import 'package:cw_zano/api/model/wi_extended.dart';
class GetWalletInfoResult {
final Wi wi;
final WiExtended wiExtended;
GetWalletInfoResult({required this.wi, required this.wiExtended});
factory GetWalletInfoResult.fromJson(Map<String, dynamic> json) => GetWalletInfoResult(
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
wiExtended: WiExtended.fromJson(json['wi_extended'] as Map<String, dynamic>? ?? {}),
);
}

View file

@ -0,0 +1,26 @@
class GetWalletStatusResult {
final int currentDaemonHeight;
final int currentWalletHeight;
final bool isDaemonConnected;
final bool isInLongRefresh;
final int progress;
final int walletState;
GetWalletStatusResult(
{required this.currentDaemonHeight,
required this.currentWalletHeight,
required this.isDaemonConnected,
required this.isInLongRefresh,
required this.progress,
required this.walletState});
factory GetWalletStatusResult.fromJson(Map<String, dynamic> json) =>
GetWalletStatusResult(
currentDaemonHeight: json['current_daemon_height'] as int? ?? 0,
currentWalletHeight: json['current_wallet_height'] as int? ?? 0,
isDaemonConnected: json['is_daemon_connected'] as bool? ?? false,
isInLongRefresh: json['is_in_long_refresh'] as bool? ?? false,
progress: json['progress'] as int? ?? 0,
walletState: json['wallet_state'] as int? ?? 0,
);
}

View file

@ -0,0 +1,13 @@
import 'dart:convert';
class ProxyToDaemonParams {
final String body;
final String uri;
ProxyToDaemonParams({required this.body, required this.uri});
Map<String, dynamic> toJson() => {
'base64_body': base64Encode(utf8.encode(body)),
'uri': uri,
};
}

View file

@ -0,0 +1,13 @@
import 'dart:convert';
class ProxyToDaemonResult {
final String body;
final int responseCode;
ProxyToDaemonResult({required this.body, required this.responseCode});
factory ProxyToDaemonResult.fromJson(Map<String, dynamic> json) => ProxyToDaemonResult(
body: utf8.decode(base64Decode(json['base64_body'] as String? ?? '')),
responseCode: json['response_code'] as int? ?? 0,
);
}

View file

@ -0,0 +1,15 @@
import 'package:cw_zano/zano_formatter.dart';
class Receive {
final BigInt amount;
final String assetId;
final int index;
Receive({required this.amount, required this.assetId, required this.index});
factory Receive.fromJson(Map<String, dynamic> json) => Receive(
amount: ZanoFormatter.bigIntFromDynamic(json['amount']),
assetId: json['asset_id'] as String? ?? '',
index: json['index'] as int? ?? 0,
);
}

View file

@ -0,0 +1,20 @@
import 'package:cw_zano/api/model/transfer.dart';
class RecentHistory {
final List<Transfer>? history;
final int lastItemIndex;
final int totalHistoryItems;
RecentHistory(
{required this.history,
required this.lastItemIndex,
required this.totalHistoryItems});
factory RecentHistory.fromJson(Map<String, dynamic> json) => RecentHistory(
history: json['history'] == null ? null : (json['history'] as List<dynamic>)
.map((e) => Transfer.fromJson(e as Map<String, dynamic>))
.toList(),
lastItemIndex: json['last_item_index'] as int? ?? 0,
totalHistoryItems: json['total_history_items'] as int? ?? 0,
);
}

View file

@ -0,0 +1,9 @@
class StoreResult {
final int walletFileSize;
StoreResult({required this.walletFileSize});
factory StoreResult.fromJson(Map<String, dynamic> json) => StoreResult(
walletFileSize: json['wallet_file_size'] as int? ?? 0,
);
}

View file

@ -0,0 +1,16 @@
import 'package:cw_zano/zano_formatter.dart';
class Subtransfer {
final BigInt amount;
final String assetId;
final bool isIncome;
Subtransfer(
{required this.amount, required this.assetId, required this.isIncome});
factory Subtransfer.fromJson(Map<String, dynamic> json) => Subtransfer(
amount: ZanoFormatter.bigIntFromDynamic(json['amount']),
assetId: json['asset_id'] as String? ?? '',
isIncome: json['is_income'] as bool? ?? false,
);
}

View file

@ -0,0 +1,126 @@
import 'package:cw_core/zano_asset.dart';
import 'package:cw_zano/api/model/employed_entries.dart';
import 'package:cw_zano/api/model/subtransfer.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/zano_formatter.dart';
import 'package:cw_zano/zano_wallet.dart';
import 'package:cw_zano/zano_wallet_api.dart';
class Transfer {
final String comment;
final EmployedEntries employedEntries;
final int fee;
final int height;
final bool isMining;
final bool isMixing;
final bool isService;
final String paymentId;
final List<String> remoteAddresses;
final List<String> remoteAliases;
final bool showSender;
final List<Subtransfer> subtransfers;
final int timestamp;
final int transferInternalIndex;
final int txBlobSize;
final String txHash;
final int txType;
final int unlockTime;
Transfer({
required this.comment,
required this.employedEntries,
required this.fee,
required this.height,
required this.isMining,
required this.isMixing,
required this.isService,
required this.paymentId,
required this.remoteAddresses,
required this.remoteAliases,
required this.showSender,
required this.subtransfers,
required this.timestamp,
required this.transferInternalIndex,
required this.txBlobSize,
required this.txHash,
required this.txType,
required this.unlockTime,
});
factory Transfer.fromJson(Map<String, dynamic> json) => Transfer(
comment: json['comment'] as String? ?? '',
employedEntries: EmployedEntries.fromJson(json['employed_entries'] as Map<String, dynamic>? ?? {}),
fee: json['fee'] as int? ?? 0,
height: json['height'] as int? ?? 0,
isMining: json['is_mining'] as bool? ?? false,
isMixing: json['is_mixing'] as bool? ?? false,
isService: json['is_service'] as bool? ?? false,
paymentId: json['payment_id'] as String? ?? '',
remoteAddresses: json['remote_addresses'] == null ? [] : (json['remote_addresses'] as List<dynamic>).cast<String>(),
remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<dynamic>).cast<String>(),
showSender: json['show_sender'] as bool? ?? false,
subtransfers: (json['subtransfers'] as List<dynamic>? ?? []).map((e) => Subtransfer.fromJson(e as Map<String, dynamic>)).toList(),
timestamp: json['timestamp'] as int? ?? 0,
transferInternalIndex: json['transfer_internal_index'] == null
? 0
: json['transfer_internal_index'] is double
? (json['transfer_internal_index'] as double).toInt()
: json['transfer_internal_index'] as int,
txBlobSize: json['tx_blob_size'] as int? ?? 0,
txHash: json['tx_hash'] as String? ?? '',
txType: json['tx_type'] as int? ?? 0,
unlockTime: json['unlock_time'] as int? ?? 0,
);
static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers, Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) {
return Map.fromIterable(
transfers,
key: (item) => (item as Transfer).txHash,
value: (transfer) {
transfer as Transfer;
// 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;
if (transfer.subtransfers.length == 2) {
final zano = transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId);
if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) {
single = transfer.subtransfers.firstWhere((element) => element.assetId != ZanoWalletBase.zanoAssetId);
}
}
bool isSimple = single != null;
// TODO: for complex transactions we show zano or any other transaction, will fix it later
if (!isSimple) {
single =
transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId) ?? transfer.subtransfers.first;
}
if (single.assetId != ZanoWalletBase.zanoAssetId) {
final asset = zanoAssets[single.assetId];
if (asset == null) {
ZanoWalletApi.error('unknown asset ${single.assetId}');
}
final ticker = asset == null ? '***' : asset.ticker;
final decimalPoint = asset == null ? ZanoFormatter.defaultDecimalPoint : asset.decimalPoint;
return ZanoTransactionInfo.fromTransfer(
transfer,
confirmations: currentDaemonHeight - transfer.height,
isIncome: single.isIncome,
assetId: single.assetId,
amount: single.amount,
tokenSymbol: isSimple ? ticker : '*${ticker}',
decimalPoint: decimalPoint,
);
}
final amount = single.isIncome ? single.amount : single.amount - BigInt.from(transfer.fee);
return ZanoTransactionInfo.fromTransfer(
transfer,
confirmations: currentDaemonHeight - transfer.height,
isIncome: single.isIncome,
assetId: single.assetId,
amount: amount,
tokenSymbol: isSimple ? 'ZANO' : '*ZANO',
);
},
);
}
}

View file

@ -0,0 +1,41 @@
import 'package:cw_zano/api/model/destination.dart';
class TransferParams {
final List<Destination> destinations;
final BigInt fee;
final int mixin;
final String paymentId;
final String comment;
final bool pushPayer;
final bool hideReceiver;
TransferParams({
required this.destinations,
required this.fee,
required this.mixin,
required this.paymentId,
required this.comment,
required this.pushPayer,
required this.hideReceiver,
});
Map<String, dynamic> toJson() => {
'destinations': destinations,
'fee': fee.toInt(),
'mixin': mixin,
'payment_id': paymentId,
'comment': comment,
'push_payer': pushPayer,
'hide_receiver': hideReceiver,
};
factory TransferParams.fromJson(Map<String, dynamic> json) => TransferParams(
destinations: (json['destinations'] as List<dynamic>?)?.map((e) => Destination.fromJson(e as Map<String, dynamic>)).toList() ?? [],
fee: BigInt.from(json['fee'] as int? ?? 0),
mixin: json['mixin'] as int? ?? 0,
paymentId: json['payment_id'] as String? ?? '',
comment: json['comment'] as String? ?? '',
pushPayer: json['push_payer'] as bool? ?? false,
hideReceiver: json['hide_receiver'] as bool? ?? false,
);
}

View file

@ -0,0 +1,13 @@
class TransferResult {
final String txHash;
final int txSize;
final String txUnsignedHex;
TransferResult({required this.txHash, required this.txSize, required this.txUnsignedHex});
factory TransferResult.fromJson(Map<String, dynamic> json) => TransferResult(
txHash: json['tx_hash'] as String? ?? '',
txSize: json['tx_size'] as int? ?? 0,
txUnsignedHex: json['tx_unsigned_hex'] as String? ?? '',
);
}

View file

@ -0,0 +1,32 @@
import 'package:cw_zano/api/model/balance.dart';
class Wi {
final String address;
final List<Balance> balances;
final bool isAuditable;
final bool isWatchOnly;
final int minedTotal;
final String path;
final String viewSecKey;
Wi(
{required this.address,
required this.balances,
required this.isAuditable,
required this.isWatchOnly,
required this.minedTotal,
required this.path,
required this.viewSecKey});
factory Wi.fromJson(Map<String, dynamic> json) => Wi(
address: json['address'] as String? ?? '',
balances: (json['balances'] as List<dynamic>? ?? [])
.map((e) => Balance.fromJson(e as Map<String, dynamic>))
.toList(),
isAuditable: json['is_auditable'] as bool? ?? false,
isWatchOnly: json['is_watch_only'] as bool? ?? false,
minedTotal: json['mined_total'] as int? ?? 0,
path: json['path'] as String? ?? '',
viewSecKey: json['view_sec_key'] as String? ?? '',
);
}

View file

@ -0,0 +1,17 @@
class WiExtended {
final String seed;
final String spendPrivateKey;
final String spendPublicKey;
final String viewPrivateKey;
final String viewPublicKey;
WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey});
factory WiExtended.fromJson(Map<String, dynamic> json) => WiExtended(
seed: json['seed'] as String? ?? '',
spendPrivateKey: json['spend_private_key'] as String? ?? '',
spendPublicKey: json['spend_public_key'] as String? ?? '',
viewPrivateKey: json['view_private_key'] as String? ?? '',
viewPublicKey: json['view_public_key'] as String? ?? '',
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,52 @@
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_zano/api/model/destination.dart';
import 'package:cw_zano/api/model/transfer_result.dart';
import 'package:cw_zano/zano_formatter.dart';
import 'package:cw_zano/zano_wallet.dart';
class PendingZanoTransaction with PendingTransaction {
PendingZanoTransaction({
required this.zanoWallet,
required this.destinations,
required this.fee,
required this.comment,
required this.assetId,
required this.ticker,
this.decimalPoint = ZanoFormatter.defaultDecimalPoint,
required this.amount,
});
final ZanoWalletBase zanoWallet;
final List<Destination> destinations;
final BigInt fee;
final String comment;
final String assetId;
final String ticker;
final int decimalPoint;
final BigInt amount;
@override
String get id => transferResult?.txHash ?? '';
@override
String get hex => '';
@override
String get amountFormatted => '${ZanoFormatter.bigIntAmountToString(amount, decimalPoint)} $ticker';
@override
String get feeFormatted => '${ZanoFormatter.bigIntAmountToString(fee)} ZANO';
TransferResult? transferResult;
@override
Future<void> commit() async {
await zanoWallet.transfer(destinations, fee, comment);
zanoWallet.fetchTransactions();
}
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
}

View file

View file

@ -0,0 +1,17 @@
import 'package:cw_core/balance.dart';
import 'package:cw_zano/zano_formatter.dart';
class ZanoBalance extends Balance {
final BigInt total;
final BigInt unlocked;
final int decimalPoint;
ZanoBalance({required this.total, required this.unlocked, this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) : super(unlocked.isValidInt ? unlocked.toInt() : 0, (total - unlocked).isValidInt ? (total - unlocked).toInt() : 0);
ZanoBalance.empty({this.decimalPoint = ZanoFormatter.defaultDecimalPoint}): total = BigInt.zero, unlocked = BigInt.zero, super(0, 0);
@override
String get formattedAdditionalBalance => ZanoFormatter.bigIntAmountToString(total - unlocked, decimalPoint);
@override
String get formattedAvailableBalance => ZanoFormatter.bigIntAmountToString(unlocked, decimalPoint);
}

View file

@ -0,0 +1,8 @@
class ZanoTransactionCreationException implements Exception {
ZanoTransactionCreationException(this.message);
final String message;
@override
String toString() => message;
}

View file

@ -0,0 +1,11 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:cw_core/output_info.dart';
class ZanoTransactionCredentials {
ZanoTransactionCredentials({required this.outputs, required this.priority, required this.currency});
final List<OutputInfo> outputs;
final MoneroTransactionPriority priority;
final CryptoCurrency currency;
}

View file

@ -0,0 +1,76 @@
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_zano/api/model/transfer.dart';
import 'package:cw_zano/zano_formatter.dart';
class ZanoTransactionInfo extends TransactionInfo {
ZanoTransactionInfo({
required this.id,
required this.height,
required this.direction,
required this.date,
required this.isPending,
required this.zanoAmount,
required this.fee,
required this.confirmations,
required this.tokenSymbol,
required this.decimalPoint,
required String assetId,
}) : amount = zanoAmount.isValidInt ? zanoAmount.toInt() : 0 {
additionalInfo['assetId'] = assetId;
}
ZanoTransactionInfo.fromTransfer(Transfer transfer,
{required int confirmations,
required bool isIncome,
required String assetId,
required BigInt amount,
this.tokenSymbol = 'ZANO',
this.decimalPoint = ZanoFormatter.defaultDecimalPoint})
: id = transfer.txHash,
height = transfer.height,
direction = isIncome ? TransactionDirection.incoming : TransactionDirection.outgoing,
date = DateTime.fromMillisecondsSinceEpoch(transfer.timestamp * 1000),
zanoAmount = amount,
amount = amount.isValidInt ? amount.toInt() : 0,
fee = transfer.fee,
confirmations = confirmations,
isPending = false,
recipientAddress = transfer.remoteAddresses.isNotEmpty ? transfer.remoteAddresses.first : '' {
additionalInfo = <String, dynamic>{
'comment': transfer.comment,
'assetId': assetId,
};
}
String get assetId => additionalInfo["assetId"] as String;
set assetId(String newId) => additionalInfo["assetId"] = newId;
final String id;
final int height;
final TransactionDirection direction;
final DateTime date;
final bool isPending;
final BigInt zanoAmount;
final int amount;
final int fee;
final int confirmations;
final int decimalPoint;
late String recipientAddress;
final String tokenSymbol;
String? _fiatAmount;
String? key;
@override
String amountFormatted() => '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol';
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() => '${formatAmount(ZanoFormatter.intAmountToString(fee))} $feeCurrency';
String get feeCurrency => 'ZANO';
}

View file

@ -0,0 +1,12 @@
class ZanoWalletKeys {
const ZanoWalletKeys(
{required this.privateSpendKey,
required this.privateViewKey,
required this.publicSpendKey,
required this.publicViewKey});
final String publicViewKey;
final String privateViewKey;
final String publicSpendKey;
final String privateSpendKey;
}

View file

@ -0,0 +1,74 @@
import 'dart:math';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:decimal/decimal.dart';
import 'package:decimal/intl.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
class ZanoFormatter {
static const defaultDecimalPoint = 12;
//static final numberFormat = NumberFormat()
// ..maximumFractionDigits = defaultDecimalPoint
// ..minimumFractionDigits = 1;
static Decimal _bigIntDivision({required BigInt amount, required BigInt divider}) {
return (Decimal.fromBigInt(amount) / Decimal.fromBigInt(divider)).toDecimal();
}
static String intAmountToString(int amount, [int decimalPoint = defaultDecimalPoint]) {
final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
..minimumFractionDigits = 1;
return numberFormat.format(
DecimalIntl(
_bigIntDivision(
amount: BigInt.from(amount),
divider: BigInt.from(pow(10, decimalPoint)),
),
),
)
.replaceAll(',', '');
}
static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) {
final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
..minimumFractionDigits = 1;
return numberFormat.format(
DecimalIntl(
_bigIntDivision(
amount: amount,
divider: BigInt.from(pow(10, decimalPoint)),
),
),
)
.replaceAll(',', '');
}
static double intAmountToDouble(int amount, [int decimalPoint = defaultDecimalPoint]) => _bigIntDivision(
amount: BigInt.from(amount),
divider: BigInt.from(pow(10, decimalPoint)),
).toDouble();
static int parseAmount(String amount, [int decimalPoint = defaultDecimalPoint]) {
final resultBigInt = (Decimal.parse(amount) * Decimal.fromBigInt(BigInt.from(10).pow(decimalPoint))).toBigInt();
if (!resultBigInt.isValidInt) {
Fluttertoast.showToast(msg: 'Cannot transfer $amount. Maximum is ${intAmountToString(resultBigInt.toInt(), decimalPoint)}.');
}
return resultBigInt.toInt();
}
static BigInt bigIntFromDynamic(dynamic d) {
if (d is int) {
return BigInt.from(d);
} else if (d is BigInt) {
return d;
} else if (d == null) {
return BigInt.zero;
} else {
ZanoWalletApi.error('cannot cast value of type ${d.runtimeType} to BigInt');
throw 'cannot cast value of type ${d.runtimeType} to BigInt';
//return BigInt.zero;
}
}
}

View file

@ -0,0 +1,27 @@
import 'dart:core';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_zano/model/zano_transaction_info.dart';
part 'zano_transaction_history.g.dart';
class ZanoTransactionHistory = ZanoTransactionHistoryBase
with _$ZanoTransactionHistory;
abstract class ZanoTransactionHistoryBase
extends TransactionHistoryBase<ZanoTransactionInfo> with Store {
ZanoTransactionHistoryBase() {
transactions = ObservableMap<String, ZanoTransactionInfo>();
}
@override
Future<void> save() async {}
@override
void addOne(ZanoTransactionInfo transaction) =>
transactions[transaction.id] = transaction;
@override
void addMany(Map<String, ZanoTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
}

View file

@ -0,0 +1,17 @@
import 'dart:convert';
import 'package:monero/zano.dart' as zano;
import 'package:cw_zano/api/model/get_address_info_result.dart';
class ZanoUtils {
static bool validateAddress(String address) {
try {
final result = GetAddressInfoResult.fromJson(
jsonDecode(zano.PlainWallet_getAddressInfo(address)) as Map<String, dynamic>,
);
return result.valid;
} catch (err) {
return false;
}
}
}

View file

@ -0,0 +1,545 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/zano_asset.dart';
import 'package:cw_zano/api/model/create_wallet_result.dart';
import 'package:cw_zano/api/model/destination.dart';
import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart';
import 'package:cw_zano/api/model/get_wallet_status_result.dart';
import 'package:cw_zano/api/model/transfer.dart';
import 'package:cw_zano/model/pending_zano_transaction.dart';
import 'package:cw_zano/model/zano_balance.dart';
import 'package:cw_zano/model/zano_transaction_creation_exception.dart';
import 'package:cw_zano/model/zano_transaction_credentials.dart';
import 'package:cw_zano/model/zano_transaction_info.dart';
import 'package:cw_zano/model/zano_wallet_keys.dart';
import 'package:cw_zano/zano_formatter.dart';
import 'package:cw_zano/zano_transaction_history.dart';
import 'package:cw_zano/zano_wallet_addresses.dart';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:cw_zano/zano_wallet_exceptions.dart';
import 'package:cw_zano/zano_wallet_service.dart';
import 'package:cw_zano/api/model/balance.dart';
import 'package:mobx/mobx.dart';
part 'zano_wallet.g.dart';
class ZanoWallet = ZanoWalletBase with _$ZanoWallet;
abstract class ZanoWalletBase
extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo>
with Store, ZanoWalletApi {
static const int _autoSaveIntervalSeconds = 30;
static const int _pollIntervalMilliseconds = 2000;
static const int _maxLoadAssetsRetries = 5;
@override
void setPassword(String password) {
_password = password;
super.setPassword(password);
}
String _password;
@override
String get password => _password;
@override
Future<String> signMessage(String message, {String? address = null}) {
throw UnimplementedError();
}
@override
Future<bool> verifyMessage(String message, String signature, {String? address = null}) {
throw UnimplementedError();
}
@override
ZanoWalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
ObservableMap<CryptoCurrency, ZanoBalance> balance;
@override
String seed = '';
@override
ZanoWalletKeys keys = ZanoWalletKeys(
privateSpendKey: '', privateViewKey: '', publicSpendKey: '', publicViewKey: '');
static const String zanoAssetId =
'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a';
Map<String, ZanoAsset> zanoAssets = {};
Timer? _updateSyncInfoTimer;
int _lastKnownBlockHeight = 0;
int _initialSyncHeight = 0;
int currentDaemonHeight = 0;
bool _isTransactionUpdating;
bool _hasSyncAfterStartup;
Timer? _autoSaveTimer;
/// number of transactions in each request
static final int _txChunkSize = (pow(2, 32) - 1).toInt();
ZanoWalletBase(WalletInfo walletInfo, String password)
: balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance.empty()}),
_isTransactionUpdating = false,
_hasSyncAfterStartup = false,
walletAddresses = ZanoWalletAddresses(walletInfo),
syncStatus = NotConnectedSyncStatus(),
_password = password,
super(walletInfo) {
transactionHistory = ZanoTransactionHistory();
if (!CakeHive.isAdapterRegistered(ZanoAsset.typeId)) {
CakeHive.registerAdapter(ZanoAssetAdapter());
}
}
@override
int calculateEstimatedFee(TransactionPriority priority, [int? amount = null]) =>
getCurrentTxFee(priority);
@override
Future<void> changePassword(String password) async {
setPassword(password);
}
static Future<ZanoWallet> create({required WalletCredentials credentials}) async {
final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!);
await wallet.initWallet();
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
final createWalletResult = await wallet.createWallet(path, credentials.password!);
await wallet.initWallet();
await wallet.parseCreateWalletResult(createWalletResult);
await wallet.init(createWalletResult.wi.address);
return wallet;
}
static Future<ZanoWallet> restore(
{required ZanoRestoreWalletFromSeedCredentials credentials}) async {
final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!);
await wallet.initWallet();
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
final createWalletResult = await wallet.restoreWalletFromSeed(
path, credentials.password!, credentials.mnemonic, credentials.passphrase);
await wallet.initWallet();
await wallet.parseCreateWalletResult(createWalletResult);
await wallet.init(createWalletResult.wi.address);
return wallet;
}
static Future<ZanoWallet> open(
{required String name, required String password, required WalletInfo walletInfo}) async {
final path = await pathForWallet(name: name, type: walletInfo.type);
final wallet = ZanoWallet(walletInfo, password);
await wallet.initWallet();
final createWalletResult = await wallet.loadWallet(path, password);
await wallet.initWallet();
await wallet.parseCreateWalletResult(createWalletResult);
await wallet.init(createWalletResult.wi.address);
return wallet;
}
Future<void> parseCreateWalletResult(CreateWalletResult result) async {
hWallet = result.walletId;
seed = result.seed;
ZanoWalletApi.info('setting hWallet = ${result.walletId}');
walletAddresses.address = result.wi.address;
await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries);
for (final item in result.wi.balances) {
if (item.assetInfo.assetId == zanoAssetId) {
balance[CryptoCurrency.zano] = ZanoBalance(
total: item.total,
unlocked: item.unlocked,
);
}
}
if (result.recentHistory.history != null) {
final transfers = result.recentHistory.history!;
final transactions = Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
transactionHistory.addMany(transactions);
await transactionHistory.save();
}
}
@override
Future<void> close({bool shouldCleanup = true}) async {
closeWallet();
_updateSyncInfoTimer?.cancel();
_autoSaveTimer?.cancel();
}
@override
Future<void> connectToNode({required Node node}) async {
syncStatus = ConnectingSyncStatus();
await setupNode(node.uriRaw);
syncStatus = ConnectedSyncStatus();
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
credentials as ZanoTransactionCredentials;
final isZano = credentials.currency == CryptoCurrency.zano;
final outputs = credentials.outputs;
final hasMultiDestination = outputs.length > 1;
final unlockedBalanceZano = balance[CryptoCurrency.zano]?.unlocked ?? BigInt.zero;
final unlockedBalanceCurrency = balance[credentials.currency]?.unlocked ?? BigInt.zero;
final fee = BigInt.from(calculateEstimatedFee(credentials.priority));
late BigInt totalAmount;
void checkForEnoughBalances() {
if (isZano) {
if (totalAmount + fee > unlockedBalanceZano) {
throw ZanoTransactionCreationException(
"You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount + fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO).");
}
} else {
if (fee > unlockedBalanceZano) {
throw ZanoTransactionCreationException(
"You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO).");
}
if (totalAmount > unlockedBalanceCurrency) {
throw ZanoTransactionCreationException(
"You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount, credentials.currency.decimals)} ${credentials.currency.title}, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceCurrency, credentials.currency.decimals)} ${credentials.currency.title}).");
}
}
}
final assetId = isZano ? zanoAssetId : (credentials.currency as ZanoAsset).assetId;
late List<Destination> destinations;
if (hasMultiDestination) {
if (outputs.any((output) => output.sendAll || (output.formattedCryptoAmount ?? 0) <= 0)) {
throw ZanoTransactionCreationException("You don't have enough coins.");
}
totalAmount = outputs.fold(
BigInt.zero, (acc, value) => acc + BigInt.from(value.formattedCryptoAmount ?? 0));
checkForEnoughBalances();
destinations = outputs
.map((output) => Destination(
amount: BigInt.from(output.formattedCryptoAmount ?? 0),
address: output.isParsedAddress ? output.extractedAddress! : output.address,
assetId: assetId,
))
.toList();
} else {
final output = outputs.first;
if (output.sendAll) {
if (isZano) {
totalAmount = unlockedBalanceZano - fee;
} else {
totalAmount = unlockedBalanceCurrency;
}
} else {
totalAmount = BigInt.from(output.formattedCryptoAmount!);
}
checkForEnoughBalances();
destinations = [
Destination(
amount: totalAmount,
address: output.isParsedAddress ? output.extractedAddress! : output.address,
assetId: assetId,
)
];
}
return PendingZanoTransaction(
zanoWallet: this,
destinations: destinations,
fee: fee,
comment: outputs.first.note ?? '',
assetId: assetId,
ticker: credentials.currency.title,
decimalPoint: credentials.currency.decimals,
amount: totalAmount,
);
}
@override
Future<Map<String, ZanoTransactionInfo>> fetchTransactions() async {
try {
final transfers = <Transfer>[];
late GetRecentTxsAndInfoResult result;
do {
result = await getRecentTxsAndInfo(offset: 0, count: _txChunkSize);
// _lastTxIndex += result.transfers.length;
transfers.addAll(result.transfers);
} while (result.lastItemIndex + 1 < result.totalTransfers);
return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
} catch (e) {
ZanoWalletApi.error(e.toString());
return {};
}
}
Future<void> init(String address) async {
await walletAddresses.init();
await walletAddresses.updateAddress(address);
await updateTransactions();
_autoSaveTimer = Timer.periodic(Duration(seconds: _autoSaveIntervalSeconds), (_) async {
await save();
});
}
@override
Future<void> renameWalletFiles(String newWalletName) async {
final currentWalletPath = await pathForWallet(name: name, type: type);
final currentCacheFile = File(currentWalletPath);
final currentKeysFile = File('$currentWalletPath.keys');
final currentAddressListFile = File('$currentWalletPath.address.txt');
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
// Copies current wallet files into new wallet name's dir and files
if (currentCacheFile.existsSync()) {
await currentCacheFile.copy(newWalletPath);
}
if (currentKeysFile.existsSync()) {
await currentKeysFile.copy('$newWalletPath.keys');
}
if (currentAddressListFile.existsSync()) {
await currentAddressListFile.copy('$newWalletPath.address.txt');
}
// Delete old name's dir and files
await Directory(currentWalletPath).delete(recursive: true);
}
@override
Future<void> rescan({required int height}) => throw UnimplementedError();
@override
Future<void> save() async {
try {
await store();
await walletAddresses.updateAddressesInBox();
} catch (e) {
ZanoWalletApi.error('Error while saving Zano wallet file ${e.toString()}');
}
}
Future<void> loadAssets(List<Balance> balances, {int maxRetries = 1}) async {
List<ZanoAsset> assets = [];
int retryCount = 0;
while (retryCount < maxRetries) {
try {
assets = await getAssetsWhitelist();
break;
} on ZanoWalletBusyException {
if (retryCount < maxRetries - 1) {
retryCount++;
await Future.delayed(Duration(seconds: 1));
} else {
ZanoWalletApi.error('failed to load assets after $retryCount retries');
break;
}
}
}
zanoAssets = {};
for (final asset in assets) {
final newAsset = ZanoAsset.copyWith(
asset,
enabled: balances.any((element) => element.assetId == asset.assetId),
);
zanoAssets.putIfAbsent(asset.assetId, () => newAsset);
}
}
@override
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
_lastKnownBlockHeight = 0;
_initialSyncHeight = 0;
_updateSyncInfoTimer ??=
Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) async {
GetWalletStatusResult walletStatus;
// ignoring get wallet status exception (in case of wrong wallet id)
try {
walletStatus = await getWalletStatus();
} on ZanoWalletException {
return;
}
currentDaemonHeight = walletStatus.currentDaemonHeight;
_updateSyncProgress(walletStatus);
// we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready)
if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) {
final walletInfo = await getWalletInfo();
seed = walletInfo.wiExtended.seed;
keys = ZanoWalletKeys(
privateSpendKey: walletInfo.wiExtended.spendPrivateKey,
privateViewKey: walletInfo.wiExtended.viewPrivateKey,
publicSpendKey: walletInfo.wiExtended.spendPublicKey,
publicViewKey: walletInfo.wiExtended.viewPublicKey,
);
loadAssets(walletInfo.wi.balances);
// matching balances and whitelists
// 1. show only balances available in whitelists
// 2. set whitelists available in balances as 'enabled' ('disabled' by default)
for (final b in walletInfo.wi.balances) {
if (b.assetId == zanoAssetId) {
balance[CryptoCurrency.zano] = ZanoBalance(total: b.total, unlocked: b.unlocked);
} else {
final asset = zanoAssets[b.assetId];
if (asset == null) {
ZanoWalletApi.error('balance for an unknown asset ${b.assetInfo.assetId}');
continue;
}
if (balance.keys.any(
(element) => element is ZanoAsset && element.assetId == b.assetInfo.assetId)) {
balance[balance.keys.firstWhere((element) =>
element is ZanoAsset && element.assetId == b.assetInfo.assetId)] =
ZanoBalance(
total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
} else {
balance[asset] = ZanoBalance(
total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
}
}
}
await updateTransactions();
// removing balances for assets missing in wallet info balances
balance.removeWhere(
(key, _) =>
key != CryptoCurrency.zano &&
!walletInfo.wi.balances
.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);
}
}

View file

@ -0,0 +1,40 @@
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:mobx/mobx.dart';
part 'zano_wallet_addresses.g.dart';
class ZanoWalletAddresses = ZanoWalletAddressesBase with _$ZanoWalletAddresses;
abstract class ZanoWalletAddressesBase extends WalletAddresses with Store {
ZanoWalletAddressesBase(WalletInfo walletInfo)
: address = '',
super(walletInfo);
@override
@observable
String address;
@override
Future<void> init() async {
address = walletInfo.address;
await updateAddressesInBox();
}
Future<void> updateAddress(String address) async {
this.address = address;
await updateAddressesInBox();
}
@override
Future<void> updateAddressesInBox() async {
try {
addressesMap.clear();
addressesMap[address] = '';
await saveAddressesInBox();
} catch (e) {
ZanoWalletApi.error(e.toString());
}
}
}

View file

@ -0,0 +1,467 @@
import 'dart:convert' as convert;
import 'dart:ffi';
import 'dart:isolate';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/zano_asset.dart';
import 'package:cw_zano/api/consts.dart';
import 'package:cw_zano/api/model/asset_id_params.dart';
import 'package:cw_zano/api/model/create_wallet_result.dart';
import 'package:cw_zano/api/model/destination.dart';
import 'package:cw_zano/api/model/get_address_info_result.dart';
import 'package:cw_zano/api/model/get_recent_txs_and_info_params.dart';
import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart';
import 'package:cw_zano/api/model/get_wallet_info_result.dart';
import 'package:cw_zano/api/model/get_wallet_status_result.dart';
import 'package:cw_zano/api/model/proxy_to_daemon_params.dart';
import 'package:cw_zano/api/model/proxy_to_daemon_result.dart';
import 'package:cw_zano/api/model/store_result.dart';
import 'package:cw_zano/api/model/transfer.dart';
import 'package:cw_zano/api/model/transfer_params.dart';
import 'package:cw_zano/api/model/transfer_result.dart';
import 'package:cw_zano/zano_wallet_exceptions.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:json_bigint/json_bigint.dart';
import 'package:monero/zano.dart' as zano;
import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi;
mixin ZanoWalletApi {
static const _maxReopenAttempts = 5;
static const _logInfo = false;
static const _logError = true;
static const _logJson = false;
static const int _zanoMixinValue = 10;
int _hWallet = 0;
int get hWallet => _hWallet;
set hWallet(int value) {
_hWallet = value;
}
int getCurrentTxFee(TransactionPriority priority) => zano.PlainWallet_getCurrentTxFee(priority.raw);
void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);
void closeWallet([int? walletToClose]) async {
info('close_wallet ${walletToClose ?? hWallet}');
final result = await _closeWallet(walletToClose ?? hWallet);
info('close_wallet result $result');
}
Future<bool> initWallet() async {
// pathForWallet(name: , type: type)
final result = zano.PlainWallet_init("", "", 0);
printV(result);
return result == "OK";
}
Future<bool> setupNode(String nodeUrl) async {
await _setupNode(hWallet, nodeUrl);
return true;
}
Future<GetWalletInfoResult> getWalletInfo() async {
final json = await _getWalletInfo(hWallet);
final result = GetWalletInfoResult.fromJson(jsonDecode(json));
_json('get_wallet_info', json);
info('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances} seed: ${_shorten(result.wiExtended.seed)}');
return result;
}
Future<GetWalletStatusResult> getWalletStatus() async {
final json = await _getWalletStatus(hWallet);
if (json == Consts.errorWalletWrongId) {
error('wrong wallet id');
throw ZanoWalletException('Wrong wallet id');
}
final status = GetWalletStatusResult.fromJson(jsonDecode(json));
_json('get_wallet_status', json);
if (_logInfo)
info(
'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;
}
Future<String> invokeMethod(String methodName, Object params) async {
final request = jsonEncode({
"method": methodName,
"params": params,
});
final invokeResult = await callSyncMethod('invoke', hWallet, request);
try {
jsonDecode(invokeResult);
} catch (e) {
if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
error('exception in parsing json in invokeMethod: $invokeResult');
rethrow;
}
return invokeResult;
}
Future<List<ZanoAsset>> getAssetsWhitelist() async {
try {
final json = await invokeMethod('assets_whitelist_get', '{}');
_json('assets_whitelist_get', json);
final map = jsonDecode(json) as Map<String, dynamic>?;
_checkForErrors(map);
List<ZanoAsset> assets(String type, bool isGlobalWhitelist) =>
(map?['result']?[type] as List<dynamic>?)
?.map((e) => ZanoAsset.fromJson(e as Map<String, dynamic>, isInGlobalWhitelist: isGlobalWhitelist))
.toList() ??
[];
final localWhitelist = assets('local_whitelist', false);
final globalWhitelist = assets('global_whitelist', true);
final ownAssets = assets('own_assets', false);
if (_logInfo)
info('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); '
'global whitelist: ${globalWhitelist.length} ($globalWhitelist); '
'own assets: ${ownAssets.length} ($ownAssets)');
return [...globalWhitelist, ...localWhitelist, ...ownAssets];
} catch (e) {
error('assets_whitelist_get $e');
return [];
// rethrow;
}
}
Future<ZanoAsset?> addAssetsWhitelist(String assetId) async {
try {
final json = await invokeMethod('assets_whitelist_add', AssetIdParams(assetId: assetId));
_json('assets_whitelist_add $assetId', json);
final map = jsonDecode(json) as Map<String, dynamic>?;
_checkForErrors(map);
if (map!['result']!['status']! == 'OK') {
final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
info('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
return assetDescriptor;
} else {
info('assets_whitelist_add status ${map['result']!['status']!}');
return null;
}
} catch (e) {
error('assets_whitelist_add $e');
return null;
}
}
Future<bool> removeAssetsWhitelist(String assetId) async {
try {
final json = await invokeMethod('assets_whitelist_remove', AssetIdParams(assetId: assetId));
_json('assets_whitelist_remove $assetId', json);
final map = jsonDecode(json) as Map<String, dynamic>?;
_checkForErrors(map);
info('assets_whitelist_remove status ${map!['result']!['status']!}');
return (map['result']!['status']! == 'OK');
} catch (e) {
error('assets_whitelist_remove $e');
return false;
}
}
Future<ProxyToDaemonResult?> _proxyToDaemon(String uri, String body) async {
final json = await invokeMethod('proxy_to_daemon', ProxyToDaemonParams(body: body, uri: uri));
final map = jsonDecode(json) as Map<String, dynamic>?;
_checkForErrors(map);
return ProxyToDaemonResult.fromJson(map!['result'] as Map<String, dynamic>);
}
Future<ZanoAsset?> getAssetInfo(String assetId) async {
final methodName = 'get_asset_info';
final params = AssetIdParams(assetId: assetId);
final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}');
_json('$methodName $assetId', result?.body ?? '');
if (result == null) {
error('get_asset_info empty result');
return null;
}
final map = jsonDecode(result.body) as Map<String, dynamic>?;
if (map!['error'] != null) {
info('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}');
return null;
} else if (map['result']!['status']! == 'OK') {
final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
info('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
return assetDescriptor;
} else {
info('get_asset_info $assetId status ${map['result']!['status']!}');
return null;
}
}
Future<StoreResult?> store() async {
try {
final json = await invokeMethod('store', '{}');
final map = jsonDecode(json) as Map<String, dynamic>?;
_checkForErrors(map);
return StoreResult.fromJson(map!['result'] as Map<String, dynamic>);
} catch (e) {
error('store $e');
return null;
}
}
Future<GetRecentTxsAndInfoResult> getRecentTxsAndInfo({required int offset, required int count}) async {
info('get_recent_txs_and_info $offset $count');
try {
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>?;
_checkForErrors(map);
final lastItemIndex = map?['result']?['last_item_index'] as int?;
final totalTransfers = map?['result']?['total_transfers'] as int?;
final transfers = map?['result']?['transfers'] as List<dynamic>?;
if (transfers == null || lastItemIndex == null || totalTransfers == null) {
error('get_recent_txs_and_info empty transfers');
return GetRecentTxsAndInfoResult.empty();
}
info('get_recent_txs_and_info transfers.length: ${transfers.length}');
return GetRecentTxsAndInfoResult(
transfers: transfers.map((e) => Transfer.fromJson(e as Map<String, dynamic>)).toList(),
lastItemIndex: lastItemIndex,
totalTransfers: totalTransfers,
);
} catch (e) {
error('get_recent_txs_and_info $e');
return GetRecentTxsAndInfoResult.empty();
}
}
GetAddressInfoResult getAddressInfo(String address) => GetAddressInfoResult.fromJson(
jsonDecode(zano.PlainWallet_getAddressInfo(address)),
);
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 {
info('create_wallet path $path password ${_shorten(password)}');
final json = zano.PlainWallet_generate(path, password);
_json('create_wallet', json);
final map = jsonDecode(json) as Map<String, dynamic>?;
if (map?['error'] != null) {
final code = map!['error']?['code'] ?? '';
final message = map['error']?['message'] ?? '';
throw ZanoWalletException('Error creating wallet file, $message ($code)');
}
if (map?['result'] == null) {
throw ZanoWalletException('Error creating wallet file, empty response');
}
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
info('create_wallet ${result.name} ${result.seed}');
return result;
}
Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async {
info('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}');
final json = zano.PlainWallet_restore(seed, path, password, passphrase??'');
_json('restore_wallet', json);
final map = jsonDecode(json) as Map<String, dynamic>?;
if (map?['error'] != null) {
final code = map!['error']!['code'] ?? '';
final message = map['error']!['message'] ?? '';
if (code == Consts.errorWrongSeed) {
throw RestoreFromSeedsException('Error restoring wallet, wrong seed');
} else if (code == Consts.errorAlreadyExists) {
throw RestoreFromSeedsException('Error restoring wallet, already exists');
}
throw RestoreFromSeedsException('Error restoring wallet, $message ($code)');
}
if (map?['result'] == null) {
throw RestoreFromSeedsException('Error restoring wallet, empty response');
}
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
info('restore_wallet ${result.name} ${result.wi.address}');
return result;
}
Future<CreateWalletResult>loadWallet(String path, String password, [int attempt = 0]) async {
info('load_wallet1 path $path password ${_shorten(password)}');
final String json;
try {
json = zano.PlainWallet_open(path, password);
} catch (e) {
error('error in loadingWallet $e');
rethrow;
}
info('load_wallet2: $json');
final map = jsonDecode(json) as Map<String, dynamic>?;
if (map?['error'] != null) {
final code = map?['error']!['code'] ?? '';
final message = map?['error']!['message'] ?? '';
if (code == Consts.errorAlreadyExists && attempt <= _maxReopenAttempts) {
// already connected to this wallet. closing and trying to reopen
info('already connected. closing and reopen wallet (attempt $attempt)');
closeWallet(attempt);
await Future.delayed(const Duration(milliseconds: 500));
return await loadWallet(path, password, attempt + 1);
}
throw ZanoWalletException('Error loading wallet, $message ($code)');
}
if (map?['result'] == null) {
throw ZanoWalletException('Error loading wallet, empty response');
}
final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
info('load_wallet3 ${result.name} ${result.wi.address}');
return result;
}
Future<TransferResult> transfer(List<Destination> destinations, BigInt fee, String comment) async {
final params = TransferParams(
destinations: destinations,
fee: fee,
mixin: _zanoMixinValue,
paymentId: '',
comment: comment,
pushPayer: false,
hideReceiver: true,
);
final json = await invokeMethod('transfer', params);
_json('transfer', json);
final map = jsonDecode(json);
final resultMap = map as Map<String, dynamic>?;
if (resultMap != null) {
final transferResultMap = resultMap['result'] as Map<String, dynamic>?;
if (transferResultMap != null) {
final transferResult = TransferResult.fromJson(transferResultMap);
info('transfer success hash ${transferResult.txHash}');
return transferResult;
} else {
final errorCode = resultMap['error']?['code'];
final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? '';
final message = resultMap['error']?['message'] as String? ?? '';
error('transfer error $code $message');
throw TransferException('Transfer error, $message ($code)');
}
}
error('transfer error empty result');
throw TransferException('Transfer error, empty result');
}
void _checkForErrors(Map<String, dynamic>? map) {
if (map == null) {
throw ZanoWalletException('Empty response');
}
final result = map['result'];
if (result == null) {
throw ZanoWalletException('Empty response');
}
if (result['error'] != null) {
final code = result['error']!['code'] ?? '';
final message = result['error']!['message'] ?? '';
if (code == -1 && message == Consts.errorBusy) {
throw ZanoWalletBusyException();
}
throw ZanoWalletException('Error, $message ($code)');
}
}
/*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 {
final params_ = params.toNativeUtf8().address;
final method_name_ = methodName.toNativeUtf8().address;
final invokeResult = await Isolate.run(() async {
final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath));
final txid = lib.ZANO_PlainWallet_syncCall(
Pointer.fromAddress(method_name_).cast(),
hWallet,
Pointer.fromAddress(params_).cast()
);
try {
final strPtr = txid.cast<Utf8>();
final str = strPtr.toDartString();
lib.ZANO_free(strPtr.cast());
return str;
} catch (e) {
return "";
}
});
calloc.free(Pointer.fromAddress(method_name_));
calloc.free(Pointer.fromAddress(params_));
return invokeResult;
}
Map<String, dynamic> jsonDecode(String json) {
try {
return decodeJson(json.replaceAll("\\/", "/")) as Map<String, dynamic>;
} catch (e) {
return convert.jsonDecode(json) as Map<String, dynamic>;
}
}
String jsonEncode(Object? object) {
return convert.jsonEncode(object);
}
Future<String> _getWalletStatus(int hWallet) async {
final jsonPtr = await Isolate.run(() async {
final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath));
final status = lib.ZANO_PlainWallet_getWalletStatus(
hWallet,
);
return status.address;
});
String json = "";
try {
final strPtr = Pointer.fromAddress(jsonPtr).cast<Utf8>();
final str = strPtr.toDartString();
zano.ZANO_free(strPtr.cast());
json = str;
} catch (e) {
json = "";
}
return json;
}
Future<String> _getWalletInfo(int hWallet) async {
final jsonPtr = await Isolate.run(() async {
final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath));
final status = lib.ZANO_PlainWallet_getWalletInfo(
hWallet,
);
return status.address;
});
String json = "";
try {
final strPtr = Pointer.fromAddress(jsonPtr).cast<Utf8>();
final str = strPtr.toDartString();
zano.ZANO_free(strPtr.cast());
json = str;
} catch (e) {
json = "";
}
return json;
}
Future<String> _setupNode(int hWallet, String nodeUrl) async {
final resp = await callSyncMethod("reset_connection_url", hWallet, nodeUrl);
printV(resp);
final resp2 = await callSyncMethod("run_wallet", hWallet, "");
printV(resp2);
return "OK";
}
Future<String> _closeWallet(int hWallet) async {
final str = await Isolate.run(() async {
return zano.PlainWallet_closeWallet(hWallet);
});
printV("Closing wallet: $str");
return str;
}

View file

@ -0,0 +1,19 @@
class ZanoWalletException implements Exception {
final String message;
ZanoWalletException(this.message);
@override
String toString() => '${this.runtimeType} (message: $message)';
}
class RestoreFromSeedsException extends ZanoWalletException {
RestoreFromSeedsException(String message) : super(message);
}
class TransferException extends ZanoWalletException {
TransferException(String message): super(message);
}
class ZanoWalletBusyException extends ZanoWalletException {
ZanoWalletBusyException(): super('');
}

View file

@ -0,0 +1,123 @@
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_zano/zano_wallet.dart';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:hive/hive.dart';
import 'package:monero/zano.dart' as zano;
class ZanoNewWalletCredentials extends WalletCredentials {
ZanoNewWalletCredentials({required String name, String? password}) : super(name: name, password: password);
}
class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials {
ZanoRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required this.mnemonic})
: super(name: name, password: password, passphrase: passphrase, height: height);
final String mnemonic;
}
class ZanoRestoreWalletFromKeysCredentials extends WalletCredentials {
ZanoRestoreWalletFromKeysCredentials(
{required String name,
required String password,
required this.language,
required this.address,
required this.viewKey,
required this.spendKey,
required int height})
: super(name: name, password: password, height: height);
final String language;
final String address;
final String viewKey;
final String spendKey;
}
class ZanoWalletService extends WalletService<ZanoNewWalletCredentials,
ZanoRestoreWalletFromSeedCredentials, ZanoRestoreWalletFromKeysCredentials, ZanoNewWalletCredentials> {
ZanoWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync();
int hWallet = 0;
@override
WalletType getType() => WalletType.zano;
@override
Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async {
ZanoWalletApi.info('zanowallet service create isTestnet $isTestnet');
return await ZanoWalletBase.create(credentials: credentials);
}
@override
Future<bool> isWalletExit(String name) async {
final path = await pathForWallet(name: name, type: getType());
return zano.PlainWallet_isWalletExist(path);
}
@override
Future<ZanoWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
try {
final wallet = await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo);
saveBackup(name);
return wallet;
} catch (e) {
await restoreWalletFilesFromBackup(name);
return await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo);
}
}
@override
Future<void> remove(String wallet) async {
final path = await pathForWalletDir(name: wallet, type: getType());
final file = Directory(path);
final isExist = file.existsSync();
if (isExist) {
await file.delete(recursive: true);
}
final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(wallet, getType()));
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
final currentWallet = ZanoWallet(currentWalletInfo, password);
await currentWallet.renameWalletFiles(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<ZanoWallet> restoreFromKeys(ZanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
throw UnimplementedError();
}
@override
Future<ZanoWallet> restoreFromSeed(ZanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
return ZanoWalletBase.restore(credentials: credentials);
}
@override
Future<ZanoWallet> restoreFromHardwareWallet(ZanoNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Zano wallet from a hardware wallet is not yet supported!");
}
}

827
cw_zano/pubspec.lock Normal file
View file

@ -0,0 +1,827 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
url: "https://pub.dev"
source: hosted
version: "47.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
url: "https://pub.dev"
source: hosted
version: "4.7.0"
args:
dependency: transitive
description:
name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.6.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
url: "https://pub.dev"
source: hosted
version: "1.5.8"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
build:
dependency: transitive
description:
name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
build_config:
dependency: transitive
description:
name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
url: "https://pub.dev"
source: hosted
version: "1.1.1"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
build_resolvers:
dependency: "direct dev"
description:
name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
url: "https://pub.dev"
source: hosted
version: "2.0.10"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
url: "https://pub.dev"
source: hosted
version: "2.4.13"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
url: "https://pub.dev"
source: hosted
version: "7.2.10"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev"
source: hosted
version: "8.9.2"
cake_backup:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
url: "https://github.com/cake-tech/cake_backup.git"
source: git
version: "1.0.0+1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
url: "https://pub.dev"
source: hosted
version: "4.10.1"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
cryptography:
dependency: transitive
description:
name: cryptography
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
url: "https://pub.dev"
source: hosted
version: "2.7.0"
cupertino_icons:
dependency: transitive
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
cw_core:
dependency: "direct main"
description:
path: "../cw_core"
relative: true
source: path
version: "0.0.1"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
decimal:
dependency: "direct main"
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: transitive
description:
name: encrypt
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: "direct main"
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_mobx:
dependency: "direct main"
description:
name: flutter_mobx
sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e"
url: "https://pub.dev"
source: hosted
version: "2.2.1+1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc"
url: "https://pub.dev"
source: hosted
version: "8.2.8"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hive:
dependency: transitive
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
http:
dependency: "direct main"
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.dev"
source: hosted
version: "1.0.5"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
json_bigint:
dependency: "direct main"
description:
name: json_bigint
sha256: "9e613e731847ab2154d67160682adf104cbd9863741ec2f7abfcf6e77c70592f"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.15.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mobx:
dependency: "direct main"
description:
name: mobx
sha256: "1f01a429529ac55e5e80c0fcad62c60112fb91df3dec11a9113d71cf0c2e2c4c"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
mobx_codegen:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
url: "https://pub.dev"
source: hosted
version: "2.3.0"
monero:
dependency: "direct main"
description:
path: "impls/monero.dart"
ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
resolved-ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
url: "https://pub.dev"
source: hosted
version: "2.2.15"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
provider:
dependency: transitive
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.2"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
url: "https://pub.dev"
source: hosted
version: "1.3.0"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shelf:
dependency: transitive
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
url: "https://pub.dev"
source: hosted
version: "2.0.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
url: "https://pub.dev"
source: hosted
version: "1.2.6"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
unorm_dart:
dependency: transitive
description:
name: unorm_dart
sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.4"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"

81
cw_zano/pubspec.yaml Normal file
View file

@ -0,0 +1,81 @@
name: cw_zano
description: A new flutter plugin project.
version: 0.0.1
publish_to: none
author: Cake Wallet
homepage: https://cakewallet.com
environment:
sdk: ">=2.19.0 <3.0.0"
flutter: ">=1.20.0"
dependencies:
flutter:
sdk: flutter
ffi: ^2.0.1
http: ^1.1.0
path_provider: ^2.0.11
mobx: ^2.1.4
flutter_mobx: ^2.0.6+1
intl: ^0.19.0
decimal: ^2.3.3
cw_core:
path: ../cw_core
json_bigint: ^3.0.0
fluttertoast: ^8.2.8
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70 # monero_c hash
path: impls/monero.dart
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.7
mobx_codegen: ^2.1.1
build_resolvers: ^2.0.9
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# This section identifies this Flutter project as a plugin project.
# The 'pluginClass' and Android 'package' identifiers should not ordinarily
# be modified. They are used by the tooling to maintain consistency when
# adding or updating assets for this project.
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# To add custom fonts to your plugin package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

2
ios/.gitignore vendored
View file

@ -31,4 +31,4 @@ Runner/GeneratedPluginRegistrant.*
!default.pbxuser
!default.perspectivev3
Mwebd.xcframework
Mwebd.xcframework

View file

@ -28,6 +28,7 @@
A3D5E17CC53DF13FA740DEFA /* RedeemSwap.swift in Resources */ = {isa = PBXBuildFile; fileRef = 9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
B6C6E59403ACDE44724C12F4 /* ServiceConfig.swift in Resources */ = {isa = PBXBuildFile; fileRef = B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
CE291CFE2C15DB9A00B9F709 /* WowneroWallet.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEA883682D43BE4500278CD3 /* ZanoWallet.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CEB6D62E2D43BB78002C6DBC /* ZanoWallet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEAFE4A02C53926F009FF3AD /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C58D93382C00FAC6004BCF69 /* libresolv.tbd */; };
CFEFC24F82F78FE747DF1D22 /* LnurlPayInfo.swift in Resources */ = {isa = PBXBuildFile; fileRef = 58C22CBD8C22B9D6023D59F8 /* LnurlPayInfo.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
D0D7A0D4E13F31C4E02E235B /* ReceivePayment.swift in Resources */ = {isa = PBXBuildFile; fileRef = 91C524F800843E0A3F17E004 /* ReceivePayment.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
@ -44,6 +45,7 @@
files = (
CE291CFE2C15DB9A00B9F709 /* WowneroWallet.framework in CopyFiles */,
0C50DFB92BF3CB56002B0EB3 /* MoneroWallet.framework in CopyFiles */,
CEA883682D43BE4500278CD3 /* ZanoWallet.framework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -86,6 +88,7 @@
C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WowneroWallet.framework; sourceTree = "<group>"; };
CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Mwebd.xcframework; sourceTree = "<group>"; };
CEB6D62E2D43BB78002C6DBC /* ZanoWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZanoWallet.framework; sourceTree = "<group>"; };
D139E30AEB36740C21C00A9E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
D7CD6B6020744E8FA471915D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DCEA540E3586164FB47AD13E /* LnurlPayInvoice.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LnurlPayInvoice.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/LnurlPayInvoice.swift"; sourceTree = "<group>"; };
@ -161,6 +164,7 @@
children = (
CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */,
0C50DFB82BF3CB56002B0EB3 /* MoneroWallet.framework */,
CEB6D62E2D43BB78002C6DBC /* ZanoWallet.framework */,
0C44A7182518EF4A00B570ED /* CakeWallet */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
@ -495,6 +499,7 @@
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -641,6 +646,7 @@
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -679,6 +685,7 @@
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
../scripts/monero_c/release/monero/host-apple-ios_libwallet2_api_c.dylib
../scripts/monero_c/release/monero/aarch64-apple-ios_libwallet2_api_c.dylib

View file

@ -1 +1 @@
../scripts/monero_c/release/wownero/host-apple-ios_libwallet2_api_c.dylib
../scripts/monero_c/release/wownero/aarch64-apple-ios_libwallet2_api_c.dylib

View file

@ -1 +1 @@
../scripts/monero_c/release/zano/host-apple-ios_libwallet2_api_c.dylib
../scripts/monero_c/release/zano/aarch64-apple-ios_libwallet2_api_c.dylib

View file

@ -2,9 +2,9 @@ import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
const BEFORE_REGEX = '(^|\\s)';
const AFTER_REGEX = '(\$|\\s)';
@ -19,7 +19,9 @@ class AddressValidator extends TextValidator {
? BitcoinNetwork.mainnet
: LitecoinNetwork.mainnet,
)
: null,
: type == CryptoCurrency.zano
? zano?.validateAddress
: null,
pattern: getPattern(type),
length: getLength(type));
@ -132,6 +134,8 @@ class AddressValidator extends TextValidator {
pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}';
case CryptoCurrency.btcln:
pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
case CryptoCurrency.zano:
pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
default:
return '';
}
@ -271,6 +275,7 @@ class AddressValidator extends TextValidator {
return [64];
case CryptoCurrency.btcln:
case CryptoCurrency.kaspa:
case CryptoCurrency.zano:
default:
return null;
}
@ -310,6 +315,8 @@ class AddressValidator extends TextValidator {
pattern = '[1-9A-HJ-NP-Za-km-z]+';
case CryptoCurrency.trx:
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
case CryptoCurrency.zano:
pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
default:
if (type.tag == CryptoCurrency.eth.title) {
pattern = '0x[0-9a-zA-Z]{42}';

View file

@ -76,6 +76,8 @@ class DecimalAmountValidator extends TextValidator {
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
case CryptoCurrency.btc:
return '^([0-9]+([.\,][0-9]{1,8})?|[.\,][0-9]{1,8})\$';
case CryptoCurrency.zano:
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,18})\$';
default:
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
}

View file

@ -9,6 +9,7 @@ import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/utils/language_list.dart';
import 'package:cw_core/wallet_type.dart';
@ -21,7 +22,8 @@ class SeedValidator extends Validator<MnemonicItem> {
final String language;
final List<String> _words;
static List<String> getWordList({required WalletType type, required String language}) {
static List<String> getWordList(
{required WalletType type, required String language}) {
switch (type) {
case WalletType.bitcoin:
return getBitcoinWordList(language);
@ -46,6 +48,8 @@ class SeedValidator extends Validator<MnemonicItem> {
return tron!.getTronWordList(language);
case WalletType.wownero:
return wownero!.getWowneroWordList(language);
case WalletType.zano:
return zano!.getWordList(language);
case WalletType.none:
return [];
}

View file

@ -89,6 +89,7 @@ class WalletCreationService {
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:
case WalletType.zano:
return false;
}
}

View file

@ -245,6 +245,7 @@ import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart';
import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@ -1087,6 +1088,8 @@ Future<void> setup({
return tron!.createTronWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
case WalletType.wownero:
return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.zano:
return zano!.createZanoWalletService(_walletInfoSource);
case WalletType.none:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
}
@ -1415,4 +1418,4 @@ Future<void> setup({
getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
_isSetupFinished = true;
}
}

View file

@ -44,6 +44,7 @@ const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
const tronDefaultNodeUri = 'api.trongrid.io';
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
const zanoDefaultNodeUri = '37.27.100.59:10500';
const moneroWorldNodeUri = '.moneroworld.com';
Future<void> defaultSettingsMigration(
@ -260,7 +261,11 @@ Future<void> defaultSettingsMigration(
await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 41:
_deselectExchangeProvider(sharedPreferences, "Quantex");
_changeExchangeProviderAvailability(
sharedPreferences,
providerName: "Quantex",
enabled: false,
);
await _addSethNode(nodes, sharedPreferences);
await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
break;
@ -269,8 +274,16 @@ Future<void> defaultSettingsMigration(
break;
case 43:
_fixNodesUseSSLFlag(nodes);
_deselectExchangeProvider(sharedPreferences, "THORChain");
_deselectExchangeProvider(sharedPreferences, "SimpleSwap");
_changeExchangeProviderAvailability(
sharedPreferences,
providerName: "THORChain",
enabled: false,
);
_changeExchangeProviderAvailability(
sharedPreferences,
providerName: "SimpleSwap",
enabled: false,
);
break;
case 44:
_fixNodesUseSSLFlag(nodes);
@ -311,6 +324,7 @@ Future<void> defaultSettingsMigration(
type: WalletType.ethereum,
useSSL: true,
);
_changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
@ -373,6 +387,15 @@ Future<void> defaultSettingsMigration(
useSSL: true,
);
break;
case 47:
await addZanoNodeList(nodes: nodes);
await changeZanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
_changeExchangeProviderAvailability(
sharedPreferences,
providerName: "SimpleSwap",
enabled: true,
);
break;
default:
break;
}
@ -469,12 +492,13 @@ Future<void> updateWalletTypeNodesWithNewNode({
);
}
void _deselectExchangeProvider(SharedPreferences sharedPreferences, String providerName) {
void _changeExchangeProviderAvailability(SharedPreferences sharedPreferences,
{required String providerName, required bool enabled}) {
final Map<String, dynamic> exchangeProvidersSelection =
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
as Map<String, dynamic>;
exchangeProvidersSelection[providerName] = false;
exchangeProvidersSelection[providerName] = enabled;
sharedPreferences.setString(
PreferencesKey.exchangeProvidersSelection,
@ -703,6 +727,12 @@ Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash);
}
Node? getZanoDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == zanoDefaultNodeUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.zano);
}
Node getMoneroDefaultNode({required Box<Node> nodes}) {
var nodeUri = newCakeWalletMoneroUri;
@ -1199,6 +1229,7 @@ Future<void> checkCurrentNodes(
final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
final currentWowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final currentZanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer =
@ -1223,6 +1254,8 @@ Future<void> checkCurrentNodes(
nodeSource.values.firstWhereOrNull((node) => node.key == currentTronNodeId);
final currentWowneroNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId);
final currentZanoNode = nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
@ -1306,6 +1339,12 @@ Future<void> checkCurrentNodes(
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
}
if (currentZanoNode == null) {
final node = Node(uri: zanoDefaultNodeUri, type: WalletType.zano);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int);
}
}
Future<void> resetBitcoinElectrumServer(
@ -1381,6 +1420,15 @@ Future<void> addWowneroNodeList({required Box<Node> nodes}) async {
}
}
Future<void> addZanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultZanoNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changeWowneroCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getWowneroDefaultNode(nodes: nodes);
@ -1389,6 +1437,13 @@ Future<void> changeWowneroCurrentNodeToDefault(
await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, nodeId);
}
Future<void> changeZanoCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getZanoDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, nodeId);
}
Future<void> addNanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoNodes();
for (var node in nodeList) {

View file

@ -20,7 +20,7 @@ class EnsRecord {
}
if (_client == null) {
_client = Web3Client("https://ethereum.publicnode.com", Client());
_client = Web3Client("https://ethereum-rpc.publicnode.com", Client());
}
try {

View file

@ -200,6 +200,23 @@ Future<List<Node>> loadDefaultWowneroNodes() async {
return nodes;
}
Future<List<Node>> loadDefaultZanoNodes() async {
final nodesRaw = await rootBundle.loadString('assets/zano_node_list.yml');
final loadedNodes = loadYaml(nodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.zano;
nodes.add(node);
}
}
return nodes;
}
Future<void> resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
@ -211,6 +228,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final polygonNodes = await loadDefaultPolygonNodes();
final solanaNodes = await loadDefaultSolanaNodes();
final tronNodes = await loadDefaultTronNodes();
final zanoNodes = await loadDefaultZanoNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList +
@ -220,7 +238,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
bitcoinCashElectrumServerList +
nanoNodes +
polygonNodes +
solanaNodes + tronNodes;
solanaNodes + tronNodes + zanoNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
import 'package:cake_wallet/entities/emoji_string_extension.dart';
import 'package:cake_wallet/entities/wellknown_record.dart';
import 'package:cake_wallet/entities/zano_alias.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/mastodon/mastodon_api.dart';
import 'package:cake_wallet/nostr/nostr_api.dart';
@ -137,6 +138,16 @@ class AddressResolver {
try {
// twitter handle example: @username
if (text.startsWith('@') && !text.substring(1).contains('@')) {
if (currency == CryptoCurrency.zano && settingsStore.lookupsZanoAlias) {
final formattedName = text.substring(1);
final zanoAddress = await ZanoAlias.fetchZanoAliasAddress(formattedName);
if (zanoAddress != null) {
return ParsedAddress.zanoAddress(
address: zanoAddress,
name: text,
);
}
}
if (settingsStore.lookupsTwitter) {
final formattedName = text.substring(1);
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);

View file

@ -13,7 +13,8 @@ enum ParseFrom {
mastodon,
nostr,
thorChain,
wellKnown
wellKnown,
zanoAlias,
}
class ParsedAddress {
@ -143,6 +144,14 @@ class ParsedAddress {
);
}
factory ParsedAddress.zanoAddress({required String address, required String name}) {
return ParsedAddress(
addresses: [address],
name: name,
parseFrom: ParseFrom.zanoAlias,
);
}
factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) {
return ParsedAddress(
addresses: [address],

View file

@ -5,6 +5,7 @@ class PreferencesKey {
static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentHavenNodeIdKey = 'current_node_id_xhv';
static const currentZanoNodeIdKey = 'current_node_id_zano';
static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentPolygonNodeIdKey = 'current_node_id_matic';
static const currentNanoNodeIdKey = 'current_node_id_nano';
@ -45,6 +46,7 @@ class PreferencesKey {
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const zanoTransactionPriority = 'current_fee_priority_zano';
static const wowneroTransactionPriority = 'current_fee_priority_wownero';
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
@ -71,6 +73,7 @@ class PreferencesKey {
static const defaultNanoRep = 'default_nano_representative';
static const defaultBananoRep = 'default_banano_representative';
static const lookupsTwitter = 'looks_up_twitter';
static const lookupsZanoAlias = 'looks_up_zano_alias';
static const lookupsMastodon = 'looks_up_mastodon';
static const lookupsYatService = 'looks_up_yat';
static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain';

Some files were not shown because too many files have changed in this diff Show more