CW-438 add nano (#1015)

* Fix web3dart versioning issue

* Add primary receive address extracted from private key

* Implement open wallet functionality

* Implement restore wallet from seed functionality

* Fixate web3dart version as higher versions cause some issues

* Add Initial Transaction priorities for eth
Add estimated gas price

* Rename priority value to tip

* Re-order wallet types

* Change ethereum node
Fix connection issues

* Fix estimating gas for priority

* Add case for ethereum to fetch it's seeds

* Add case for ethereum to request node

* Fix Exchange screen initial pairs

* Add initial send transaction flow

* Add missing configure for ethereum class

* Add Eth address initial setup

* Fix Private key for Ethereum wallets

* Change sign/send transaction flow

* - Fix Conflicts with main
- Remove unused function from Haven configure.dart

* Add build command for ethereum package

* Add missing Node list file to pubspec

* - Fix balance display
- Fix parsing of Ethereum amount
- Add more Ethereum Nodes [skip ci]

* - Fix extracting Ethereum Private key from seeds
- Integrate signing/sending transaction with the send view model

* - Update and Fix Conflicts with main

* Add Balances for ERC20 tokens

* Fix conflicts with main

* Add erc20 abi json

* Add send erc20 tokens initial function

* add missing getHeightByDate in Haven [skip ci]

* Allow contacts and wallets from the same tag

* Add Shiba Inu icon

* Add send ERC-20 tokens initial flow

* Add missing import in generated file

* Add initial approach for transaction sending for ERC-20 tokens

* Refactor signing/sending transactions

* Add initial flow for transactions subscription

* Refactor signing/sending transactions

* Add home settings icon

* Fix conflicts with main

* Initial flow for home settings

* Add logic flow for adding erc20 tokens

* Fix initial UI

* Finalize UI for Tokens

* Integrate UI with Ethereum flow

* Add "Enable/Disable" feature for ERC20 tokens

* Add initial Erc20 tokens

* Add Sorting and Pin Native Token features

* Fix price sorting

* Sort tokens list as well when Sort criteria changes

* - Improve sorting balances flow
- Add initial add token from search bar flow

* Fix Accounts Popup UI

* Fix Pin native token

* Fix Enabling/Disabling tokens
Fix sorting by fiat once app is opened
Improve token availability mechanism

* Fix deleting token
Fix renaming tokens

* Fix issue with search

* Add more tokens

* - Fix scroll issue
- Add ERC20 tokens placeholder image in picker

* - Separate and organize default erc20 tokens
- Fix scrolling
- Add token placeholder images in picker
- Sort disabled tokens alphabetically

* Change BNB token initial availability [skip ci]

* Fix Conflicts with main

* Fix Conflicts with main

* Add Verse ERC20 token to the initial tokens list

* Add rename wallet to Ethereum

* Integrate EtherScan API for fetching address transactions
Generate Ethereum specific secrets in Ethereum package

* Adjust transactions fiat price for ERC20 tokens

* Free Up GitHub Actions Ubuntu Runner Disk Space

* Free Up GitHub Actions Ubuntu Runner Disk space (trial 2)

* Fix Transaction Fee display

* Save transaction history

* Enhance loading time for erc20 tokens transactions

* Minor Fixes and Enhancements

* Fix sending erc20
fix block explorer issue

* Fix int overflow

* Fix transaction amount conversions

* Minor: `slow` -> `Slow` [skip-ci]

* initial changes

* more base config stuff

* config changes

* successfully builds!

* save

* successfully add nano wallet

* save

* seed generation

* receive screen + node screen working

* tx history working and fiat fixes

* balance working

* derivation updates

* nano-unfinished

* sends working

* remove fees from send screen, send and receive transactions working

* fixes + auto receive incoming txs

* fix for scanning QR codes

* save

* update translations

* fixes

* more fixes

* more strings

* small fix

* fix github actions workflow

* potential fix

* potential fix

* ci/cd fix

* change rep working

* seed generation fixes

* fixes

* save

* change rep screen functional

* save

* banano changes

* fixes, start adding ui for PoW

* pow node changes

* update translations

* fix

* account changing barely working

* save

* disable account generation

* small fix

* save

* UI work

* save

* fixes after merge main

* fixes

* remove monero stuff, work on derivation ui

* lots of fixes + finish up seed derivation

* last minute fixes

* node related fixes

* more fixes

* small fix

* more fixes

* fixes

* pretty big refactor for pow, still some bugs

* finally works!

* get transactions after send

* fix

* merge conflict fixes

* save

* fix pow node showing up twice

* done

* initial changes

* small fix

* more merge fixes

* fixes

* more fixes

* fix

* save

* fix manage pow nodes setting appearing on other wallets

* fix contact bug

* fixes

* fiat fixes

* save

* save

* save

* save

* updates

* cleanup

* restore fix

* fixes

* remove deprecated alert

* fix

* small fix

* remove outdated warning

* electrum restore fixes

* fixes

* fixes

* fix

* derivation fixes

* nano fixes pt.1

* nano fixes pt.2

* bip39 fixes

* pownode refactor

* nodes pages fixes

* observer fix

* ssl fix

* remove old references

* remove unused imports

* code cleanup

* small fix

* small potential fix

* save

* undo all bitcoin related changes

* remove dead code

* review fixes

* more fixes

* fix

* fix

* review fix

* small fix

* nano derivation and nanoutil fixes

* exchange nano fix

* nano review fixes pt.1

* nano fixes pt.2

* nano fixes pt.3

* remove old imports + stop using dynamic in di

* nanoutil fixes

* add nano.dart to gitignore, configure fixes

* review fixes, getnanowalletservice removed

* fix settings screen, add changeRep to configure.dart, other minor fixes

* remove manage_pow_nodes_page, key derivation edge case handled

* remove old refs

* more small fixes

* Generic Enhancements/Minor fixes

* review fixes

* hopefully final fixes

* review fixes

* node connection fixes

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
Co-authored-by: fossephate <fosse@book.local>
This commit is contained in:
Matthew Fosse 2023-10-04 21:09:07 -04:00 committed by GitHub
parent 6233422643
commit 4c60b178be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
134 changed files with 8171 additions and 1055 deletions

View file

@ -92,6 +92,7 @@ jobs:
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Add secrets - name: Add secrets

1
.gitignore vendored
View file

@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
lib/monero/monero.dart lib/monero/monero.dart
lib/haven/haven.dart lib/haven/haven.dart
lib/ethereum/ethereum.dart lib/ethereum/ethereum.dart
lib/nano/nano.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png

View file

@ -0,0 +1,6 @@
-
uri: rpc.nano.to
useSSL: true
is_default: true
-
uri: node.perish.co:9076

View file

@ -0,0 +1,9 @@
-
uri: rpc.nano.to
useSSL: true
is_default: true
-
uri: workers.perish.co
-
uri: worker.nanoriver.cc
useSSL: true

1
configure_cake_wallet_android.sh Normal file → Executable file
View file

@ -7,4 +7,5 @@ cd cw_monero && flutter pub get && flutter packages pub run build_runner build -
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -90,6 +90,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.zrx, CryptoCurrency.zrx,
CryptoCurrency.dydx, CryptoCurrency.dydx,
CryptoCurrency.steth, CryptoCurrency.steth,
CryptoCurrency.banano,
]; ];
static const havenCurrencies = [ static const havenCurrencies = [
@ -119,7 +120,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const eos = CryptoCurrency(title: 'EOS', fullName: 'EOS', raw: 7, name: 'eos', iconPath: 'assets/images/eos_icon.png'); static const eos = CryptoCurrency(title: 'EOS', fullName: 'EOS', raw: 7, name: 'eos', iconPath: 'assets/images/eos_icon.png');
static const eth = CryptoCurrency(title: 'ETH', fullName: 'Ethereum', raw: 8, name: 'eth', iconPath: 'assets/images/eth_icon.png'); static const eth = CryptoCurrency(title: 'ETH', fullName: 'Ethereum', raw: 8, name: 'eth', iconPath: 'assets/images/eth_icon.png');
static const ltc = CryptoCurrency(title: 'LTC', fullName: 'Litecoin', raw: 9, name: 'ltc', iconPath: 'assets/images/litecoin-ltc_icon.png'); static const ltc = CryptoCurrency(title: 'LTC', fullName: 'Litecoin', raw: 9, name: 'ltc', iconPath: 'assets/images/litecoin-ltc_icon.png');
static const nano = CryptoCurrency(title: 'NANO', raw: 10, name: 'nano', iconPath: 'assets/images/nano.png'); static const nano = CryptoCurrency(title: 'XNO', raw: 10, fullName: 'Nano', name: 'xno', iconPath: 'assets/images/nano_icon.png');
static const trx = CryptoCurrency(title: 'TRX', fullName: 'TRON', raw: 11, name: 'trx', iconPath: 'assets/images/trx_icon.png'); static const trx = CryptoCurrency(title: 'TRX', fullName: 'TRON', raw: 11, name: 'trx', iconPath: 'assets/images/trx_icon.png');
static const usdt = CryptoCurrency(title: 'USDT', tag: 'OMNI', fullName: 'USDT Tether', raw: 12, name: 'usdt', iconPath: 'assets/images/usdt_icon.png'); static const usdt = CryptoCurrency(title: 'USDT', tag: 'OMNI', fullName: 'USDT Tether', raw: 12, name: 'usdt', iconPath: 'assets/images/usdt_icon.png');
static const usdterc20 = CryptoCurrency(title: 'USDT', tag: 'ETH', fullName: 'USDT Tether', raw: 13, name: 'usdterc20', iconPath: 'assets/images/usdterc20_icon.png'); static const usdterc20 = CryptoCurrency(title: 'USDT', tag: 'ETH', fullName: 'USDT Tether', raw: 13, name: 'usdterc20', iconPath: 'assets/images/usdterc20_icon.png');
@ -198,6 +199,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const zrx = CryptoCurrency(title: 'ZRX', tag: 'ETH', fullName: '0x Protocol', raw: 83, name: 'zrx', iconPath: 'assets/images/zrx_icon.png'); static const zrx = CryptoCurrency(title: 'ZRX', tag: 'ETH', fullName: '0x Protocol', raw: 83, name: 'zrx', iconPath: 'assets/images/zrx_icon.png');
static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png'); static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png');
static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png'); static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png');
static const banano = CryptoCurrency(title: 'BAN', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png');
static final Map<int, CryptoCurrency> _rawCurrencyMap = static final Map<int, CryptoCurrency> _rawCurrencyMap =

View file

@ -13,6 +13,10 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.xhv; return CryptoCurrency.xhv;
case WalletType.ethereum: case WalletType.ethereum:
return CryptoCurrency.eth; return CryptoCurrency.eth;
case WalletType.nano:
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
default: default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
} }

View file

@ -11,3 +11,6 @@ const UNSPENT_COINS_INFO_TYPE_ID = 9;
const ANONPAY_INVOICE_INFO_TYPE_ID = 10; const ANONPAY_INVOICE_INFO_TYPE_ID = 10;
const ADDRESS_INFO_TYPE_ID = 11; const ADDRESS_INFO_TYPE_ID = 11;
const ERC20_TOKEN_TYPE_ID = 12; const ERC20_TOKEN_TYPE_ID = 12;
const NANO_ACCOUNT_TYPE_ID = 13;
const POW_NODE_TYPE_ID = 14;
const DERIVATION_TYPE_TYPE_ID = 15;

View file

@ -0,0 +1,23 @@
import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart';
part 'nano_account.g.dart';
@HiveType(typeId: NanoAccount.typeId)
class NanoAccount extends HiveObject {
NanoAccount({required this.label, required this.id, this.balance, this.isSelected = false});
static const typeId = NANO_ACCOUNT_TYPE_ID;
@HiveField(0)
String label;
@HiveField(1)
final int id;
@HiveField(2)
bool isSelected;
@HiveField(3)
String? balance;
}

View file

@ -0,0 +1,23 @@
class AccountInfoResponse {
String frontier;
int confirmationHeight;
String balance;
String representative;
String? address;
AccountInfoResponse({
required this.frontier,
required this.balance,
required this.representative,
required this.confirmationHeight,
});
factory AccountInfoResponse.fromJson(Map<String, dynamic> json) {
return AccountInfoResponse(
frontier: json['frontier'] as String,
representative: json['representative'] as String,
balance: json['balance'] as String,
confirmationHeight: int.parse(json['confirmation_height'] as String),
);
}
}

View file

@ -9,19 +9,19 @@ import 'package:http/io_client.dart' as ioc;
part 'node.g.dart'; part 'node.g.dart';
Uri createUriFromElectrumAddress(String address) => Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
Uri.tryParse('tcp://$address')!;
@HiveType(typeId: Node.typeId) @HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable { class Node extends HiveObject with Keyable {
Node( Node({
{this.login, this.login,
this.password, this.password,
this.useSSL, this.useSSL,
this.trusted = false, this.trusted = false,
this.socksProxyAddress, this.socksProxyAddress,
String? uri, String? uri,
WalletType? type,}) { WalletType? type,
}) {
if (uri != null) { if (uri != null) {
uriRaw = uri; uriRaw = uri;
} }
@ -78,6 +78,13 @@ class Node extends HiveObject with Keyable {
return Uri.http(uriRaw, ''); return Uri.http(uriRaw, '');
case WalletType.ethereum: case WalletType.ethereum:
return Uri.https(uriRaw, ''); return Uri.https(uriRaw, '');
case WalletType.nano:
case WalletType.banano:
if (isSSL) {
return Uri.https(uriRaw, '');
} else {
return Uri.http(uriRaw, '');
}
default: default:
throw Exception('Unexpected type ${type.toString()} for Node uri'); throw Exception('Unexpected type ${type.toString()} for Node uri');
} }
@ -86,13 +93,13 @@ class Node extends HiveObject with Keyable {
@override @override
bool operator ==(other) => bool operator ==(other) =>
other is Node && other is Node &&
(other.uriRaw == uriRaw && (other.uriRaw == uriRaw &&
other.login == login && other.login == login &&
other.password == password && other.password == password &&
other.typeRaw == typeRaw && other.typeRaw == typeRaw &&
other.useSSL == useSSL && other.useSSL == useSSL &&
other.trusted == trusted && other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress); other.socksProxyAddress == socksProxyAddress);
@override @override
int get hashCode => int get hashCode =>
@ -120,7 +127,9 @@ class Node extends HiveObject with Keyable {
try { try {
switch (type) { switch (type) {
case WalletType.monero: case WalletType.monero:
return useSocksProxy ? requestNodeWithProxy(socksProxyAddress ?? '') : requestMoneroNode(); return useSocksProxy
? requestNodeWithProxy(socksProxyAddress ?? '')
: requestMoneroNode();
case WalletType.bitcoin: case WalletType.bitcoin:
return requestElectrumServer(); return requestElectrumServer();
case WalletType.litecoin: case WalletType.litecoin:
@ -129,6 +138,9 @@ class Node extends HiveObject with Keyable {
return requestMoneroNode(); return requestMoneroNode();
case WalletType.ethereum: case WalletType.ethereum:
return requestElectrumServer(); return requestElectrumServer();
case WalletType.nano:
case WalletType.banano:
return requestNanoNode();
default: default:
return false; return false;
} }
@ -141,27 +153,23 @@ class Node extends HiveObject with Keyable {
final path = '/json_rpc'; final path = '/json_rpc';
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
final realm = 'monero-rpc'; final realm = 'monero-rpc';
final body = { final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'};
'jsonrpc': '2.0',
'id': '0',
'method': 'get_info'
};
try { try {
final authenticatingClient = HttpClient(); final authenticatingClient = HttpClient();
authenticatingClient.addCredentials( authenticatingClient.addCredentials(
rpcUri, rpcUri,
realm, realm,
HttpClientDigestCredentials(login ?? '', password ?? ''), HttpClientDigestCredentials(login ?? '', password ?? ''),
); );
final http.Client client = ioc.IOClient(authenticatingClient); final http.Client client = ioc.IOClient(authenticatingClient);
final response = await client.post( final response = await client.post(
rpcUri, rpcUri,
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: json.encode(body), body: json.encode(body),
); );
client.close(); client.close();
@ -173,8 +181,24 @@ class Node extends HiveObject with Keyable {
} }
} }
Future<bool> requestNodeWithProxy(String proxy) async { Future<bool> requestNanoNode() async {
http.Response response = await http.post(
uri,
headers: {'Content-type': 'application/json'},
body: json.encode(
{
"action": "block_count",
},
),
);
if (response.statusCode == 200) {
return true;
} else {
return false;
}
}
Future<bool> requestNodeWithProxy(String proxy) async {
if (proxy.isEmpty || !proxy.contains(':')) { if (proxy.isEmpty || !proxy.contains(':')) {
return false; return false;
} }

View file

@ -13,9 +13,7 @@ import 'package:cw_core/sync_status.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
abstract class WalletBase< abstract class WalletBase<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
BalanceType extends Balance,
HistoryType extends TransactionHistoryBase,
TransactionType extends TransactionInfo> { TransactionType extends TransactionInfo> {
WalletBase(this.walletInfo); WalletBase(this.walletInfo);
@ -58,6 +56,9 @@ abstract class WalletBase<
Future<void> connectToNode({required Node node}); Future<void> connectToNode({required Node node});
// there is a default definition here because only coins with a pow node (nano based) need to override this
Future<void> connectToPowNode({required Node node}) async {}
Future<void> startSync(); Future<void> startSync();
Future<PendingTransaction> createTransaction(Object credentials); Future<PendingTransaction> createTransaction(Object credentials);

View file

@ -5,10 +5,15 @@ abstract class WalletCredentials {
required this.name, required this.name,
this.height, this.height,
this.walletInfo, this.walletInfo,
this.password}); this.password,
this.derivationType,
this.derivationPath,
});
final String name; final String name;
final int? height; final int? height;
String? password; String? password;
DerivationType? derivationType;
String? derivationPath;
WalletInfo? walletInfo; WalletInfo? walletInfo;
} }

View file

@ -6,29 +6,92 @@ import 'package:hive/hive.dart';
part 'wallet_info.g.dart'; part 'wallet_info.g.dart';
@HiveType(typeId: DERIVATION_TYPE_TYPE_ID)
enum DerivationType {
@HiveField(0)
unknown,
@HiveField(1)
def, // default is a reserved word
@HiveField(2)
nano,
@HiveField(3)
bip39,
@HiveField(4)
electrum1,
@HiveField(5)
electrum2,
}
class DerivationInfo {
DerivationInfo({
required this.derivationType,
this.derivationPath,
this.balance = "",
this.address = "",
this.height = 0,
this.script_type,
this.description,
});
String balance;
String address;
int height;
final DerivationType derivationType;
final String? derivationPath;
final String? script_type;
final String? description;
}
@HiveType(typeId: WalletInfo.typeId) @HiveType(typeId: WalletInfo.typeId)
class WalletInfo extends HiveObject { class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight, WalletInfo(
this.timestamp, this.dirPath, this.path, this.address, this.yatEid, this.id,
this.yatLastUsedAddressRaw, this.showIntroCakePayCard) this.name,
this.type,
this.isRecovery,
this.restoreHeight,
this.timestamp,
this.dirPath,
this.path,
this.address,
this.yatEid,
this.yatLastUsedAddressRaw,
this.showIntroCakePayCard,
this.derivationType,
this.derivationPath)
: _yatLastUsedAddressController = StreamController<String>.broadcast(); : _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external( factory WalletInfo.external({
{required String id, required String id,
required String name, required String name,
required WalletType type, required WalletType type,
required bool isRecovery, required bool isRecovery,
required int restoreHeight, required int restoreHeight,
required DateTime date, required DateTime date,
required String dirPath, required String dirPath,
required String path, required String path,
required String address, required String address,
bool? showIntroCakePayCard, bool? showIntroCakePayCard,
String yatEid ='', String yatEid = '',
String yatLastUsedAddressRaw = ''}) { String yatLastUsedAddressRaw = '',
return WalletInfo(id, name, type, isRecovery, restoreHeight, DerivationType? derivationType,
date.millisecondsSinceEpoch, dirPath, path, address, String? derivationPath,
yatEid, yatLastUsedAddressRaw, showIntroCakePayCard); }) {
return WalletInfo(
id,
name,
type,
isRecovery,
restoreHeight,
date.millisecondsSinceEpoch,
dirPath,
path,
address,
yatEid,
yatLastUsedAddressRaw,
showIntroCakePayCard,
derivationType,
derivationPath);
} }
static const typeId = WALLET_INFO_TYPE_ID; static const typeId = WALLET_INFO_TYPE_ID;
@ -79,6 +142,12 @@ class WalletInfo extends HiveObject {
@HiveField(15) @HiveField(15)
List<String>? usedAddresses; List<String>? usedAddresses;
@HiveField(16)
DerivationType? derivationType;
@HiveField(17)
String? derivationPath;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) { set yatLastUsedAddress(String address) {
@ -89,7 +158,7 @@ class WalletInfo extends HiveObject {
String get yatEmojiId => yatEid ?? ''; String get yatEmojiId => yatEid ?? '';
bool get isShowIntroCakePayCard { bool get isShowIntroCakePayCard {
if(showIntroCakePayCard == null) { if (showIntroCakePayCard == null) {
return type != WalletType.haven; return type != WalletType.haven;
} }
return showIntroCakePayCard!; return showIntroCakePayCard!;

View file

@ -1,9 +1,11 @@
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
abstract class WalletService<N extends WalletCredentials, abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
RFS extends WalletCredentials, RFK extends WalletCredentials> { RFK extends WalletCredentials> {
WalletType getType(); WalletType getType();
Future<WalletBase> create(N credentials); Future<WalletBase> create(N credentials);

View file

@ -10,6 +10,8 @@ const walletTypes = [
WalletType.litecoin, WalletType.litecoin,
WalletType.haven, WalletType.haven,
WalletType.ethereum, WalletType.ethereum,
WalletType.nano,
WalletType.banano,
]; ];
@HiveType(typeId: WALLET_TYPE_TYPE_ID) @HiveType(typeId: WALLET_TYPE_TYPE_ID)
@ -31,6 +33,12 @@ enum WalletType {
@HiveField(5) @HiveField(5)
ethereum, ethereum,
@HiveField(6)
nano,
@HiveField(7)
banano,
} }
int serializeToInt(WalletType type) { int serializeToInt(WalletType type) {
@ -45,6 +53,10 @@ int serializeToInt(WalletType type) {
return 3; return 3;
case WalletType.ethereum: case WalletType.ethereum:
return 4; return 4;
case WalletType.nano:
return 5;
case WalletType.banano:
return 6;
default: default:
return -1; return -1;
} }
@ -62,6 +74,10 @@ WalletType deserializeFromInt(int raw) {
return WalletType.haven; return WalletType.haven;
case 4: case 4:
return WalletType.ethereum; return WalletType.ethereum;
case 5:
return WalletType.nano;
case 6:
return WalletType.banano;
default: default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
} }
@ -79,6 +95,10 @@ String walletTypeToString(WalletType type) {
return 'Haven'; return 'Haven';
case WalletType.ethereum: case WalletType.ethereum:
return 'Ethereum'; return 'Ethereum';
case WalletType.nano:
return 'Nano';
case WalletType.banano:
return 'Banano';
default: default:
return ''; return '';
} }
@ -96,6 +116,10 @@ String walletTypeToDisplayName(WalletType type) {
return 'Haven (XHV)'; return 'Haven (XHV)';
case WalletType.ethereum: case WalletType.ethereum:
return 'Ethereum (ETH)'; return 'Ethereum (ETH)';
case WalletType.nano:
return 'Nano (XNO)';
case WalletType.banano:
return 'Banano (BAN)';
default: default:
return ''; return '';
} }
@ -113,6 +137,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
return CryptoCurrency.xhv; return CryptoCurrency.xhv;
case WalletType.ethereum: case WalletType.ethereum:
return CryptoCurrency.eth; return CryptoCurrency.eth;
case WalletType.nano:
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
default: default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
} }

View file

@ -234,7 +234,6 @@ extern "C"
} }
void setUnlocked(bool unlocked); void setUnlocked(bool unlocked);
}; };
Monero::Coins *m_coins; Monero::Coins *m_coins;

View file

@ -0,0 +1,20 @@
import 'package:cw_core/balance.dart';
import 'package:cw_nano/nano_util.dart';
class BananoBalance extends Balance {
final BigInt currentBalance;
final BigInt receivableBalance;
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
}
@override
String get formattedAvailableBalance {
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano);
}
@override
String get formattedAdditionalBalance {
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano);
}
}

7
cw_nano/lib/cw_nano.dart Normal file
View file

@ -0,0 +1,7 @@
library cw_nano;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}

39
cw_nano/lib/file.dart Normal file
View file

@ -0,0 +1,39 @@
import 'dart:io';
import 'package:cw_core/key.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
Future<void> write(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<void> writeData(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<String> read({required String path, required String password}) async {
final file = File(path);
if (!file.existsSync()) {
file.createSync();
}
final encrypted = file.readAsStringSync();
return decode(password: password, data: encrypted);
}

View file

@ -0,0 +1,69 @@
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/nano_account.dart';
import 'package:mobx/mobx.dart';
import 'package:hive/hive.dart';
part 'nano_account_list.g.dart';
class NanoAccountList = NanoAccountListBase with _$NanoAccountList;
abstract class NanoAccountListBase with Store {
NanoAccountListBase(this.address)
: accounts = ObservableList<NanoAccount>(),
_isRefreshing = false,
_isUpdating = false {
refresh();
}
@observable
ObservableList<NanoAccount> accounts;
bool _isRefreshing;
bool _isUpdating;
String address;
Future<void> update(String? address) async {
if (_isUpdating) {
return;
}
try {
_isUpdating = true;
final accounts = await getAll(address: address ?? this.address);
if (accounts.isNotEmpty) {
this.accounts.clear();
this.accounts.addAll(accounts);
}
_isUpdating = false;
} catch (e) {
_isUpdating = false;
rethrow;
}
}
Future<List<NanoAccount>> getAll({String? address}) async {
final box = await CakeHive.openBox<NanoAccount>(address ?? this.address);
// get all accounts in box:
return box.values.toList();
}
Future<void> addAccount({required String label}) async {
final box = await CakeHive.openBox<NanoAccount>(address);
final account = NanoAccount(id: box.length, label: label, balance: "0.00", isSelected: false);
await box.add(account);
await account.save();
}
Future<void> setLabelAccount({required int accountIndex, required String label}) async {
final box = await CakeHive.openBox<NanoAccount>(address);
final account = box.getAt(accountIndex);
account!.label = label;
await account.save();
}
void refresh() {}
}

View file

@ -0,0 +1,34 @@
import 'package:cw_core/balance.dart';
import 'package:cw_nano/nano_util.dart';
BigInt stringAmountToBigInt(String amount) {
return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano));
}
class NanoBalance extends Balance {
final BigInt currentBalance;
final BigInt receivableBalance;
late String formattedCurrentBalance;
late String formattedReceivableBalance;
NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
this.formattedCurrentBalance = "";
this.formattedReceivableBalance = "";
}
NanoBalance.fromString(
{required this.formattedCurrentBalance, required this.formattedReceivableBalance})
: currentBalance = stringAmountToBigInt(formattedCurrentBalance),
receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
super(0, 0);
@override
String get formattedAvailableBalance {
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano);
}
@override
String get formattedAdditionalBalance {
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano);
}
}

View file

@ -0,0 +1,424 @@
import 'dart:async';
import 'dart:convert';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_transaction_model.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:http/http.dart' as http;
import 'package:nanodart/nanodart.dart';
import 'package:cw_core/node.dart';
class NanoClient {
static const String DEFAULT_REPRESENTATIVE =
"nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579";
Node? _node;
Node? _powNode;
bool connect(Node node) {
try {
_node = node;
return true;
} catch (e) {
return false;
}
}
bool connectPow(Node node) {
try {
_powNode = node;
return true;
} catch (e) {
return false;
}
}
Future<NanoBalance> getBalance(String address) async {
final response = await http.post(
_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode(
{
"action": "account_balance",
"account": address,
},
),
);
final data = await jsonDecode(response.body);
final String currentBalance = data["balance"] as String;
final String receivableBalance = data["receivable"] as String;
final BigInt cur = BigInt.parse(currentBalance);
final BigInt rec = BigInt.parse(receivableBalance);
return NanoBalance(currentBalance: cur, receivableBalance: rec);
}
Future<AccountInfoResponse?> getAccountInfo(String address) async {
try {
final response = await http.post(
_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode(
{
"action": "account_info",
"representative": "true",
"account": address,
},
),
);
final data = await jsonDecode(response.body);
return AccountInfoResponse.fromJson(data as Map<String, dynamic>);
} catch (e) {
print("error while getting account info");
return null;
}
}
Future<String> changeRep({
required String privateKey,
required String repAddress,
required String ourAddress,
}) async {
try {
AccountInfoResponse? accountInfo = await getAccountInfo(ourAddress);
if (accountInfo == null) {
throw Exception("error while getting account info");
}
// construct the change block:
Map<String, String> changeBlock = {
"type": "state",
"account": ourAddress,
"previous": accountInfo.frontier,
"representative": repAddress,
"balance": accountInfo.balance,
"link": "0000000000000000000000000000000000000000000000000000000000000000",
"link_as_account": "nano_1111111111111111111111111111111111111111111111111111hifc8npp",
};
// sign the change block:
final String hash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
changeBlock["account"]!,
changeBlock["previous"]!,
changeBlock["representative"]!,
BigInt.parse(changeBlock["balance"]!),
changeBlock["link"]!,
);
final String signature = NanoSignatures.signBlock(hash, privateKey);
// get PoW for the send block:
final String work = await requestWork(accountInfo.frontier);
changeBlock["signature"] = signature;
changeBlock["work"] = work;
return await processBlock(changeBlock, "change");
} catch (e) {
throw Exception("error while changing representative");
}
}
Future<String> requestWork(String hash) async {
final response = await http.post(
_powNode!.uri,
headers: {'Content-type': 'application/json'},
body: json.encode(
{
"action": "work_generate",
"hash": hash,
},
),
);
if (response.statusCode == 200) {
final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
return decoded["work"] as String;
} else {
throw Exception("Received work error ${response.body}");
}
}
Future<String> send({
required String privateKey,
required String amountRaw,
required String destinationAddress,
}) async {
final Map<String, String> sendBlock = await constructSendBlock(
privateKey: privateKey,
amountRaw: amountRaw,
destinationAddress: destinationAddress,
);
return await processBlock(sendBlock, "send");
}
Future<String> processBlock(Map<String, String> block, String subtype) async {
final headers = {"Content-Type": "application/json"};
final processBody = jsonEncode({
"action": "process",
"json_block": "true",
"subtype": subtype,
"block": block,
});
final processResponse = await http.post(
_node!.uri,
headers: headers,
body: processBody,
);
final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
// return the hash of the transaction:
return decoded["hash"].toString();
}
Future<Map<String, String>> constructSendBlock({
required String privateKey,
required String amountRaw,
required String destinationAddress,
BigInt? balanceAfterTx,
String? previousHash,
}) async {
try {
// our address:
final String publicAddress = NanoUtil.privateKeyToAddress(privateKey);
// first get the current account balance:
if (balanceAfterTx == null) {
final BigInt currentBalance = (await getBalance(publicAddress)).currentBalance;
final BigInt txAmount = BigInt.parse(amountRaw);
balanceAfterTx = currentBalance - txAmount;
}
// get the account info (we need the frontier and representative):
AccountInfoResponse? infoResponse = await getAccountInfo(publicAddress);
if (infoResponse == null) {
throw Exception(
"error while getting account info! (we probably don't have an open account yet)");
}
String frontier = infoResponse.frontier;
// override if provided:
if (previousHash != null) {
frontier = previousHash;
}
final String representative = infoResponse.representative;
// link = destination address:
final String link = NanoAccounts.extractPublicKey(destinationAddress);
final String linkAsAccount = destinationAddress;
// construct the send block:
Map<String, String> sendBlock = {
"type": "state",
"account": publicAddress,
"previous": frontier,
"representative": representative,
"balance": balanceAfterTx.toString(),
"link": link,
};
// sign the send block:
final String hash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
sendBlock["account"]!,
sendBlock["previous"]!,
sendBlock["representative"]!,
BigInt.parse(sendBlock["balance"]!),
sendBlock["link"]!,
);
final String signature = NanoSignatures.signBlock(hash, privateKey);
// get PoW for the send block:
final String work = await requestWork(frontier);
sendBlock["link_as_account"] = linkAsAccount;
sendBlock["signature"] = signature;
sendBlock["work"] = work;
// ready to post send block:
return sendBlock;
} catch (e) {
print(e);
rethrow;
}
}
Future<void> receiveBlock({
required String blockHash,
required String source,
required String amountRaw,
required String destinationAddress,
required String privateKey,
}) async {
bool openBlock = false;
final headers = {
"Content-Type": "application/json",
};
// first check if the account is open:
// get the account info (we need the frontier and representative):
AccountInfoResponse? infoData = await getAccountInfo(destinationAddress);
String? frontier;
String? representative;
if (infoData == null) {
// account is not open yet, we need to create an open block:
openBlock = true;
// we don't have a representative set yet:
representative = DEFAULT_REPRESENTATIVE;
// we don't have a frontier yet:
frontier = "0000000000000000000000000000000000000000000000000000000000000000";
} else {
frontier = infoData.frontier;
representative = infoData.representative;
}
// first get the account balance:
final BigInt currentBalance = (await getBalance(destinationAddress)).currentBalance;
final BigInt txAmount = BigInt.parse(amountRaw);
final BigInt balanceAfterTx = currentBalance + txAmount;
// link = send block hash:
final String link = blockHash;
// this "linkAsAccount" is meaningless:
final String linkAsAccount = NanoAccounts.createAccount(NanoAccountType.NANO, blockHash);
// construct the receive block:
Map<String, String> receiveBlock = {
"type": "state",
"account": destinationAddress,
"previous": frontier,
"representative": representative,
"balance": balanceAfterTx.toString(),
"link": link,
"link_as_account": linkAsAccount,
};
// sign the receive block:
final String hash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
receiveBlock["account"]!,
receiveBlock["previous"]!,
receiveBlock["representative"]!,
BigInt.parse(receiveBlock["balance"]!),
receiveBlock["link"]!,
);
final String signature = NanoSignatures.signBlock(hash, privateKey);
// get PoW for the receive block:
String? work;
if (openBlock) {
work = await requestWork(NanoAccounts.extractPublicKey(destinationAddress));
} else {
work = await requestWork(frontier);
}
receiveBlock["link_as_account"] = linkAsAccount;
receiveBlock["signature"] = signature;
receiveBlock["work"] = work;
// process the receive block:
final processBody = jsonEncode({
"action": "process",
"json_block": "true",
"subtype": "receive",
"block": receiveBlock,
});
final processResponse = await http.post(
_node!.uri,
headers: headers,
body: processBody,
);
final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
}
// returns the number of blocks received:
Future<int> confirmAllReceivable({
required String destinationAddress,
required String privateKey,
}) async {
final receivableResponse = await http.post(_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"action": "receivable",
"account": destinationAddress,
"count": "-1",
"source": true,
}));
final receivableData = await jsonDecode(receivableResponse.body);
if (receivableData["blocks"] == "" || receivableData["blocks"] == null) {
return 0;
}
dynamic blocks;
if (receivableData["blocks"] is List<dynamic>) {
var listBlocks = receivableData["blocks"] as List<dynamic>;
if (listBlocks.isEmpty) {
return 0;
}
blocks = {for (var block in listBlocks) block['hash']: block};
} else {
blocks = receivableData["blocks"] as Map<String, dynamic>;
}
blocks = blocks as Map<String, dynamic>;
// confirm all receivable blocks:
for (final blockHash in blocks.keys) {
final block = blocks[blockHash];
final String amountRaw = block["amount"] as String;
final String source = block["source"] as String;
await receiveBlock(
blockHash: blockHash,
source: source,
amountRaw: amountRaw,
privateKey: privateKey,
destinationAddress: destinationAddress,
);
// a bit of a hack:
await Future<void>.delayed(const Duration(seconds: 2));
}
return blocks.keys.length;
}
void stop() {}
Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
try {
final response = await http.post(_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"action": "account_history",
"account": address,
"count": "250", // TODO: pick a number
// "raw": true,
}));
final data = await jsonDecode(response.body);
final transactions = data["history"] is List ? data["history"] as List<dynamic> : [];
// Map the transactions list to NanoTransactionModel using the factory
// reversed so that the DateTime is correct when local_timestamp is absent
return transactions.reversed
.map<NanoTransactionModel>((transaction) => NanoTransactionModel.fromJson(transaction))
.toList();
} catch (e) {
print(e);
return [];
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
import 'package:cw_core/output_info.dart';
class NanoTransactionCredentials {
NanoTransactionCredentials(this.outputs);
final List<OutputInfo> outputs;
}

View file

@ -0,0 +1,72 @@
import 'dart:convert';
import 'dart:core';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_nano/file.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_nano/nano_transaction_info.dart';
part 'nano_transaction_history.g.dart';
const transactionsHistoryFileName = 'transactions.json';
class NanoTransactionHistory = NanoTransactionHistoryBase with _$NanoTransactionHistory;
abstract class NanoTransactionHistoryBase
extends TransactionHistoryBase<NanoTransactionInfo> with Store {
NanoTransactionHistoryBase({required this.walletInfo, required String password})
: _password = password {
transactions = ObservableMap<String, NanoTransactionInfo>();
}
final WalletInfo walletInfo;
String _password;
Future<void> init() async => await _load();
@override
Future<void> save() async {
try {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final data = json.encode({'transactions': transactions});
await writeData(path: path, password: _password, data: data);
} catch (e) {
print('Error while save nano transaction history: ${e.toString()}');
}
}
@override
void addOne(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction;
@override
void addMany(Map<String, NanoTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
Future<Map<String, dynamic>> _read() async {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, dynamic>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = NanoTransactionInfo.fromJson(val);
_update(tx);
}
});
} catch (e) {
print(e);
}
}
void _update(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction;
}

View file

@ -0,0 +1,70 @@
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_nano/nano_util.dart';
class NanoTransactionInfo extends TransactionInfo {
NanoTransactionInfo({
required this.id,
required this.height,
required this.amountRaw,
this.tokenSymbol = "XNO",
required this.direction,
required this.confirmed,
required this.date,
required this.confirmations,
}) : this.amount = amountRaw.toInt();
final String id;
final int height;
final int amount;
final BigInt amountRaw;
final TransactionDirection direction;
final DateTime date;
final bool confirmed;
final int confirmations;
final String tokenSymbol;
String? _fiatAmount;
bool get isPending => !this.confirmed;
@override
String amountFormatted() {
final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano);
final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano);
return "$acc$amt $tokenSymbol";
}
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() => "0 XNO";
factory NanoTransactionInfo.fromJson(Map<String, dynamic> data) {
return NanoTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
amountRaw: BigInt.parse(data['amountRaw'] as String),
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
confirmed: data['confirmed'] as bool,
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'height': height,
'amountRaw': amountRaw.toString(),
'direction': direction.index,
'date': date.millisecondsSinceEpoch,
'confirmed': confirmed,
'confirmations': confirmations,
'tokenSymbol': tokenSymbol,
};
}

View file

@ -0,0 +1,39 @@
class NanoTransactionModel {
final DateTime? date;
final String hash;
final bool confirmed;
final String account;
final BigInt amount;
final int height;
final String type;
NanoTransactionModel({
this.date,
required this.hash,
required this.height,
required this.amount,
required this.confirmed,
required this.type,
required this.account,
});
factory NanoTransactionModel.fromJson(dynamic json) {
DateTime? localTimestamp;
try {
localTimestamp = DateTime.fromMillisecondsSinceEpoch(
int.parse(json["local_timestamp"] as String) * 1000);
} catch (e) {
localTimestamp = DateTime.now();
}
return NanoTransactionModel(
date: localTimestamp,
hash: json["hash"] as String,
height: int.parse(json["height"] as String),
type: json["type"] as String,
amount: BigInt.parse(json["amount"] as String),
account: json["account"] as String,
confirmed: (json["confirmed"] as String) == "true",
);
}
}

193
cw_nano/lib/nano_util.dart Normal file
View file

@ -0,0 +1,193 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import "package:ed25519_hd_key/ed25519_hd_key.dart";
import 'package:libcrypto/libcrypto.dart';
import 'package:nanodart/nanodart.dart';
import 'package:decimal/decimal.dart';
class NanoUtil {
// standard:
static String seedToPrivate(String seed, int index) {
return NanoKeys.seedToPrivate(seed, index);
}
static String seedToAddress(String seed, int index) {
return NanoAccounts.createAccount(
NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
}
static String seedToMnemonic(String seed) {
return NanoMnemomics.seedToMnemonic(seed).join(" ");
}
static Future<String> mnemonicToSeed(String mnemonic) async {
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
}
static String privateKeyToPublic(String privateKey) {
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
return NanoKeys.createPublicKey(privateKey);
}
static String addressToPublicKey(String publicAddress) {
return NanoAccounts.extractPublicKey(publicAddress);
}
// universal:
static String privateKeyToAddress(String privateKey) {
return NanoAccounts.createAccount(NanoAccountType.NANO, privateKeyToPublic(privateKey));
}
static String publicKeyToAddress(String publicKey) {
return NanoAccounts.createAccount(NanoAccountType.NANO, publicKey);
}
// standard + hd:
static bool isValidSeed(String seed) {
// Ensure seed is 64 or 128 characters long
if (seed == null || (seed.length != 64 && seed.length != 128)) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return NanoHelpers.isHexString(seed);
}
// // hd:
static Future<String> hdMnemonicListToSeed(List<String> words) async {
// if (words.length != 24) {
// throw Exception('Expected a 24-word list, got a ${words.length} list');
// }
final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
final String seed = await hasher.sha512(words.join(' '), salt);
return seed;
}
static Future<String> hdSeedToPrivate(String seed, int index) async {
List<int> seedBytes = hex.decode(seed);
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
return hex.encode(data.key);
}
static Future<String> hdSeedToAddress(String seed, int index) async {
return NanoAccounts.createAccount(
NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
}
static Future<String> uniSeedToAddress(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToAddress(seed, index));
} else if (type == "hd") {
return hdSeedToAddress(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
static Future<String> uniSeedToPrivate(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToPrivate(seed, index));
} else if (type == "hd") {
return hdSeedToPrivate(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
static bool isValidBip39Seed(String seed) {
// Ensure seed is 128 characters long
if (seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return NanoHelpers.isHexString(seed);
}
// number util:
static const int maxDecimalDigits = 6; // Max digits after decimal
static BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
static BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
static BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
static BigInt rawPerXMR = BigInt.parse("1000000000000");
static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
// static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
/// Convert raw to ban and return as BigDecimal
///
/// @param raw 100000000000000000000000000000
/// @return Decimal value 1.000000000000000000000000000000
///
static Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
rawPerCur ??= rawPerNano;
final Decimal amount = Decimal.parse(raw.toString());
final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
return result;
}
static String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
Decimal bigger = input.shift(digits);
bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
bigger = bigger.shift(-digits);
return bigger.toString();
}
/// Return raw as a NANO amount.
///
/// @param raw 100000000000000000000000000000
/// @returns 1
///
static String getRawAsUsableString(String? raw, BigInt rawPerCur) {
final String res =
truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
return "0";
}
if (!res.contains(".")) {
return res;
}
final String numAmount = res.split(".")[0];
String decAmount = res.split(".")[1];
// truncate:
if (decAmount.length > maxDecimalDigits) {
decAmount = decAmount.substring(0, maxDecimalDigits);
// remove trailing zeros:
decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
if (decAmount.isEmpty) {
return numAmount;
}
}
return "$numAmount.$decAmount";
}
static String getRawAccuracy(String? raw, BigInt rawPerCur) {
final String rawString = getRawAsUsableString(raw, rawPerCur);
final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
if (raw == null || raw.isEmpty || raw == "0") {
return "";
}
if (rawString != rawDecimalString) {
return "~";
}
return "";
}
/// Return readable string amount as raw string
/// @param amount 1.01
/// @returns 101000000000000000000000000000
///
static String getAmountAsRaw(String amount, BigInt rawPerCur) {
final Decimal asDecimal = Decimal.parse(amount);
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
return (asDecimal * rawDecimal).toString();
}
}

View file

@ -0,0 +1,437 @@
import 'dart:convert';
import 'dart:io';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/nano_account_info_response.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_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_nano/file.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_transaction_credentials.dart';
import 'package:cw_nano/nano_transaction_history.dart';
import 'package:cw_nano/nano_transaction_info.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:cw_nano/nano_wallet_keys.dart';
import 'package:cw_nano/pending_nano_transaction.dart';
import 'package:mobx/mobx.dart';
import 'dart:async';
import 'package:cw_nano/nano_wallet_addresses.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:nanodart/nanodart.dart';
import 'package:bip39/bip39.dart' as bip39;
part 'nano_wallet.g.dart';
class NanoWallet = NanoWalletBase with _$NanoWallet;
abstract class NanoWalletBase
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store {
NanoWalletBase({
required WalletInfo walletInfo,
required String mnemonic,
required String password,
NanoBalance? initialBalance,
}) : syncStatus = NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_derivationType = walletInfo.derivationType!,
_isTransactionUpdating = false,
_client = NanoClient(),
walletAddresses = NanoWalletAddresses(walletInfo),
balance = ObservableMap<CryptoCurrency, NanoBalance>.of({
CryptoCurrency.nano: initialBalance ??
NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero)
}),
super(walletInfo) {
this.walletInfo = walletInfo;
transactionHistory = NanoTransactionHistory(walletInfo: walletInfo, password: password);
if (!CakeHive.isAdapterRegistered(NanoAccount.typeId)) {
CakeHive.registerAdapter(NanoAccountAdapter());
}
}
final String _mnemonic;
final String _password;
final DerivationType _derivationType;
String? _privateKey;
String? _publicAddress;
String? _seedKey;
String? _representativeAddress;
Timer? _receiveTimer;
late final NanoClient _client;
bool _isTransactionUpdating;
@override
NanoWalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
late ObservableMap<CryptoCurrency, NanoBalance> balance;
// initialize the different forms of private / public key we'll need:
Future<void> init() async {
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
// our "mnemonic" is actually a seedkey:
if (!_mnemonic.contains(' ')) {
_seedKey = _mnemonic;
}
if (_seedKey == null) {
if (_derivationType == DerivationType.nano) {
_seedKey = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
} else {
_seedKey = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
}
}
_privateKey = await NanoUtil.uniSeedToPrivate(_seedKey!, 0, type);
_publicAddress = await NanoUtil.uniSeedToAddress(_seedKey!, 0, type);
this.walletInfo.address = _publicAddress!;
await walletAddresses.init();
await transactionHistory.init();
await save();
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
return 0; // always 0 :)
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@override
void close() {
_client.stop();
}
@action
@override
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
final isConnected = _client.connect(node);
if (!isConnected) {
throw Exception("Nano Node connection failed");
}
try {
await _updateBalance();
await _updateRep();
await _receiveAll();
} catch (e) {
print(e);
}
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
}
}
@override
Future<void> connectToPowNode({required Node node}) async {
_client.connectPow(node);
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
credentials = credentials as NanoTransactionCredentials;
BigInt runningAmount = BigInt.zero;
await _updateBalance();
BigInt runningBalance = balance[currency]?.currentBalance ?? BigInt.zero;
final List<Map<String, String>> blocks = [];
String? previousHash;
for (var txOut in credentials.outputs) {
late BigInt amt;
if (txOut.sendAll) {
amt = balance[currency]?.currentBalance ?? BigInt.zero;
} else {
amt = BigInt.tryParse(
NanoUtil.getAmountAsRaw(txOut.cryptoAmount ?? "0", NanoUtil.rawPerNano)) ??
BigInt.zero;
}
if (balance[currency]?.currentBalance != null && amt > balance[currency]!.currentBalance) {
throw Exception("Trying to send more than entire balance!");
}
runningBalance = runningBalance - amt;
final block = await _client.constructSendBlock(
amountRaw: amt.toString(),
destinationAddress: txOut.extractedAddress ?? txOut.address,
privateKey: _privateKey!,
balanceAfterTx: runningBalance,
previousHash: previousHash,
);
previousHash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
block["account"]!,
block["previous"]!,
block["representative"]!,
BigInt.parse(block["balance"]!),
block["link"]!,
);
blocks.add(block);
runningAmount += amt;
}
try {
if (runningAmount > balance[currency]!.currentBalance || runningBalance < BigInt.zero) {
throw Exception(("Trying to send more than entire balance!"));
}
} catch (e) {
rethrow;
}
return PendingNanoTransaction(
amount: runningAmount,
id: "",
nanoClient: _client,
blocks: blocks,
);
}
Future<void> _receiveAll() async {
await _updateBalance();
int blocksReceived = await this._client.confirmAllReceivable(
destinationAddress: _publicAddress!,
privateKey: _privateKey!,
);
if (blocksReceived > 0) {
await Future<void>.delayed(Duration(seconds: 3));
_updateBalance();
updateTransactions();
}
}
Future<void> updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (_) {
_isTransactionUpdating = false;
}
}
@override
Future<Map<String, NanoTransactionInfo>> fetchTransactions() async {
String address = _publicAddress!;
final transactions = await _client.fetchTransactions(address);
final Map<String, NanoTransactionInfo> result = {};
for (var transactionModel in transactions) {
result[transactionModel.hash] = NanoTransactionInfo(
id: transactionModel.hash,
amountRaw: transactionModel.amount,
height: transactionModel.height,
direction: transactionModel.type == "send"
? TransactionDirection.outgoing
: TransactionDirection.incoming,
confirmed: transactionModel.confirmed,
date: transactionModel.date ?? DateTime.now(),
confirmations: transactionModel.confirmed ? 1 : 0,
);
}
return result;
}
@override
NanoWalletKeys get keys {
return NanoWalletKeys(seedKey: _seedKey!);
}
@override
String? get privateKey => _seedKey!;
@override
Future<void> rescan({required int height}) async {
updateTransactions();
_updateBalance();
return;
}
@override
Future<void> save() async {
await walletAddresses.updateAddressesInBox();
final path = await makePath();
await write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
}
@override
String get seed => _mnemonic;
String get representative => _representativeAddress ?? "";
@action
@override
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await _updateBalance();
await updateTransactions();
_receiveTimer?.cancel();
_receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async {
// get our balance:
await _updateBalance();
// if we have anything to receive, process it:
if (balance[currency]!.receivableBalance > BigInt.zero) {
await _receiveAll();
}
});
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
rethrow;
}
}
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'seedKey': _seedKey,
'mnemonic': _mnemonic,
'currentBalance': balance[currency]?.currentBalance.toString() ?? "0",
'receivableBalance': balance[currency]?.receivableBalance.toString() ?? "0",
'derivationType': _derivationType.toString()
});
static Future<NanoWallet> open({
required String name,
required String password,
required WalletInfo walletInfo,
}) async {
final path = await pathForWallet(name: name, type: walletInfo.type);
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
final balance = NanoBalance.fromString(
formattedCurrentBalance: data['currentBalance'] as String? ?? "0",
formattedReceivableBalance: data['receivableBalance'] as String? ?? "0");
DerivationType derivationType = DerivationType.bip39;
if (data['derivationType'] == "DerivationType.nano") {
derivationType = DerivationType.nano;
}
walletInfo.derivationType = derivationType;
return NanoWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
initialBalance: balance,
);
// init() should always be run after this!
}
Future<void> _updateBalance() async {
try {
balance[currency] = await _client.getBalance(_publicAddress!);
} catch (e) {
print("Failed to get balance $e");
}
await save();
}
Future<void> _updateRep() async {
try {
AccountInfoResponse accountInfo = (await _client.getAccountInfo(_publicAddress!))!;
_representativeAddress = accountInfo.representative;
} catch (e) {
// account not found:
_representativeAddress = NanoClient.DEFAULT_REPRESENTATIVE;
throw Exception("Failed to get representative address $e");
}
}
Future<void> regenerateAddress() async {
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
_privateKey =
await NanoUtil.uniSeedToPrivate(_seedKey!, this.walletAddresses.account!.id, type);
_publicAddress =
await NanoUtil.uniSeedToAddress(_seedKey!, this.walletAddresses.account!.id, type);
this.walletInfo.address = _publicAddress!;
this.walletAddresses.address = _publicAddress!;
}
Future<void> changeRep(String address) async {
try {
final String hash = await _client.changeRep(
privateKey: _privateKey!,
repAddress: address,
ourAddress: _publicAddress!,
);
if (hash.isNotEmpty) {
_representativeAddress = address;
}
} catch (e) {
throw Exception("Failed to change representative address $e");
}
}
Future<void>? updateBalance() async => await _updateBalance();
@override
Future<void> renameWalletFiles(String newWalletName) async {
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
final currentWalletFile = File(currentWalletPath);
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
// Copies current wallet files into new wallet name's dir and files
if (currentWalletFile.existsSync()) {
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
await currentWalletFile.copy(newWalletPath);
}
if (currentTransactionsFile.existsSync()) {
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
}
// Delete old name's dir and files
await Directory(currentDirPath).delete(recursive: true);
}
}

View file

@ -0,0 +1,50 @@
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_nano/nano_account_list.dart';
import 'package:mobx/mobx.dart';
part 'nano_wallet_addresses.g.dart';
class NanoWalletAddresses = NanoWalletAddressesBase with _$NanoWalletAddresses;
abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
NanoWalletAddressesBase(WalletInfo walletInfo)
: accountList = NanoAccountList(walletInfo.address),
address = '',
super(walletInfo);
@override
@observable
String address;
@observable
NanoAccount? account;
NanoAccountList accountList;
@override
Future<void> init() async {
var box = await CakeHive.openBox<NanoAccount>(walletInfo.address);
try {
box.getAt(0);
} catch (e) {
box.add(NanoAccount(id: 0, label: "Primary Account", balance: "0.00"));
}
await accountList.update(walletInfo.address);
account = accountList.accounts.first;
address = walletInfo.address;
}
@override
Future<void> updateAddressesInBox() async {
try {
addressesMap.clear();
addressesMap[address] = '';
await saveAddressesInBox();
} catch (e) {
print(e.toString());
}
}
}

View file

@ -0,0 +1,41 @@
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class NanoNewWalletCredentials extends WalletCredentials {
NanoNewWalletCredentials({required String name, String? password})
: super(name: name, password: password);
}
class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
NanoRestoreWalletFromSeedCredentials({
required String name,
required this.mnemonic,
int height = 0,
String? password,
DerivationType? derivationType,
}) : super(
name: name,
password: password,
height: height,
derivationType: derivationType,
);
final String mnemonic;
}
class NanoWalletLoadingException implements Exception {
@override
String toString() => 'Failure to load the wallet.';
}
class NanoRestoreWalletFromKeysCredentials extends WalletCredentials {
NanoRestoreWalletFromKeysCredentials({
required String name,
required String password,
required this.seedKey,
this.derivationType,
}) : super(name: name, password: password);
final String seedKey;
final DerivationType? derivationType;
}

View file

@ -0,0 +1,5 @@
class NanoWalletKeys {
const NanoWalletKeys({required this.seedKey});
final String seedKey;
}

View file

@ -0,0 +1,163 @@
import 'dart:io';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_base.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_nano/nano_mnemonic.dart' as nm;
import 'package:cw_nano/nano_util.dart';
import 'package:cw_nano/nano_wallet.dart';
import 'package:cw_nano/nano_wallet_creation_credentials.dart';
import 'package:hive/hive.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:nanodart/nanodart.dart';
class NanoWalletService extends WalletService<NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
NanoWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
static bool walletFilesExist(String path) =>
!File(path).existsSync() && !File('$path.keys').existsSync();
@override
WalletType getType() => WalletType.nano;
@override
Future<WalletBase> create(NanoNewWalletCredentials credentials) async {
// nano standard:
DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoUtil.seedToMnemonic(seedKey);
credentials.walletInfo!.derivationType = derivationType;
final wallet = NanoWallet(
walletInfo: credentials.walletInfo!,
mnemonic: mnemonic,
password: credentials.password!,
);
wallet.init();
return wallet;
}
@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()));
String randomWords =
(List<String>.from(nm.NanoMnemomics.WORDLIST)..shuffle()).take(24).join(' ');
final currentWallet =
NanoWallet(walletInfo: currentWalletInfo, password: password, mnemonic: randomWords);
await currentWallet.renameWalletFiles(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async {
if (credentials.seedKey.contains(' ')) {
throw Exception("Invalid key!");
} else {
if (credentials.seedKey.length != 64 && credentials.seedKey.length != 128) {
throw Exception("Invalid key length!");
}
}
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;
String? mnemonic;
// we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
if (credentials.seedKey.length == 64) {
try {
mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey);
} catch (e) {
throw Exception("Wasn't a valid nano style seed!");
}
}
final wallet = await NanoWallet(
password: credentials.password!,
mnemonic: mnemonic ?? credentials.seedKey,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
await wallet.save();
return wallet;
}
@override
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async {
if (credentials.mnemonic.contains(' ')) {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw nm.NanoMnemonicIsIncorrectException();
}
if (!NanoMnemomics.validateMnemonic(credentials.mnemonic.split(' '))) {
throw nm.NanoMnemonicIsIncorrectException();
}
} else {
if (credentials.mnemonic.length != 64 && credentials.mnemonic.length != 128) {
throw Exception("Invalid seed length");
}
}
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;
final wallet = await NanoWallet(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
await wallet.save();
return wallet;
}
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<NanoWallet> openWallet(String name, String password) async {
final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await NanoWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init();
await wallet.save();
return wallet;
}
}

View file

@ -0,0 +1,40 @@
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_util.dart';
class PendingNanoTransaction with PendingTransaction {
PendingNanoTransaction({
required this.nanoClient,
required this.amount,
required this.id,
required this.blocks,
});
final NanoClient nanoClient;
final BigInt amount;
final String id;
final List<Map<String, String>> blocks;
String hex = "unused";
@override
String get amountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
return amt;
}
String get accurateAmountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano);
return "$acc$amt";
}
@override
String get feeFormatted => "0";
@override
Future<void> commit() async {
for (var block in blocks) {
await nanoClient.processBlock(block, "send");
}
}
}

756
cw_nano/pubspec.lock Normal file
View file

@ -0,0 +1,756 @@
# 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: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.4.2"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0
url: "https://pub.dev"
source: hosted
version: "1.4.1"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bip32:
dependency: "direct main"
description:
name: bip32
sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
bip39:
dependency: "direct main"
description:
name: bip39
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
url: "https://pub.dev"
source: hosted
version: "1.0.6"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bs58check:
dependency: transitive
description:
name: bs58check
sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
url: "https://pub.dev"
source: hosted
version: "1.0.2"
build:
dependency: transitive
description:
name: build
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
url: "https://pub.dev"
source: hosted
version: "2.3.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: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
build_resolvers:
dependency: transitive
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: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
url: "https://pub.dev"
source: hosted
version: "2.3.3"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
url: "https://pub.dev"
source: hosted
version: "7.2.7+1"
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: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
url: "https://pub.dev"
source: hosted
version: "8.6.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: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
url: "https://pub.dev"
source: hosted
version: "4.5.0"
collection:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev"
source: hosted
version: "1.17.1"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
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"
ed25519_hd_key:
dependency: "direct main"
description:
name: ed25519_hd_key
sha256: "326608234e986ea826a5db4cf4cd6826058d860875a3fff7926c0725fe1a604d"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
encrypt:
dependency: transitive
description:
name: encrypt
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
url: "https://pub.dev"
source: hosted
version: "5.0.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev"
source: hosted
version: "2.0.2"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
fixnum_nanodart:
dependency: transitive
description:
name: fixnum_nanodart
sha256: "4b0132d11ecddc0d2ca64b6d7dee6726db432ed02cac1349d7532a08be5c54fc"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_mobx:
dependency: transitive
description:
name: flutter_mobx
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
url: "https://pub.dev"
source: hosted
version: "2.0.6+5"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
url: "https://pub.dev"
source: hosted
version: "3.2.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: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.3.1"
hex:
dependency: "direct main"
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
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: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
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: transitive
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
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: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.8.1"
libcrypto:
dependency: "direct main"
description:
name: libcrypto
sha256: "18a97db8d88147b0b60d2755f29b5e4944181c4c1a9f52bd1ecbea1b0a5aab03"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mobx:
dependency: "direct main"
description:
name: mobx
sha256: "0afcf88b3ee9d6819890bf16c11a727fc8c62cf736fda8e5d3b9b4eace4e62ea"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
mobx_codegen:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
url: "https://pub.dev"
source: hosted
version: "2.3.0"
nanodart:
dependency: "direct main"
description:
name: nanodart
sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
url: "https://pub.dev"
source: hosted
version: "2.0.15"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
url: "https://pub.dev"
source: hosted
version: "2.0.27"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
url: "https://pub.dev"
source: hosted
version: "2.1.11"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev"
source: hosted
version: "2.0.6"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
pinenacl:
dependency: transitive
description:
name: pinenacl
sha256: e5fb0bce1717b7f136f35ee98b5c02b3e6383211f8a77ca882fa7812232a07b9
url: "https://pub.dev"
source: hosted
version: "0.3.4"
platform:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev"
source: hosted
version: "1.2.3"
rational:
dependency: transitive
description:
name: rational
sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf
url: "https://pub.dev"
source: hosted
version: "2.2.2"
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: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
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: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
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: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
url: "https://pub.dev"
source: hosted
version: "0.5.1"
timing:
dependency: transitive
description:
name: timing
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
win32:
dependency: transitive
description:
name: win32
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
url: "https://pub.dev"
source: hosted
version: "4.1.4"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
url: "https://pub.dev"
source: hosted
version: "1.0.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
flutter: ">=3.3.0"

69
cw_nano/pubspec.yaml Normal file
View file

@ -0,0 +1,69 @@
name: cw_nano
description: A new Flutter package project.
version: 0.0.1
publish_to: none
author: Cake Wallet
homepage: https://cakewallet.com
environment:
sdk: '>=2.18.2 <3.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
mobx: ^2.0.7+4
bip39: ^1.0.6
bip32: ^2.0.0
nanodart: ^2.0.0
decimal: ^2.3.3
libcrypto: ^0.2.2
ed25519_hd_key: ^2.2.0
hex: ^0.2.0
http: ^1.1.0
cw_core:
path: ../cw_core
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
# 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 packages.
flutter:
# To add assets to your 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 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

View file

@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_nano/cw_nano.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}

View file

@ -27,6 +27,8 @@ class OnRamperBuyProvider {
return "LTC_LITECOIN"; return "LTC_LITECOIN";
case CryptoCurrency.xmr: case CryptoCurrency.xmr:
return "XMR_MONERO"; return "XMR_MONERO";
case CryptoCurrency.nano:
return "XNO_NANO";
default: default:
return _wallet.currency.title; return _wallet.currency.title;
} }

View file

@ -28,6 +28,8 @@ class AddressValidator extends TextValidator {
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$'; return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
case CryptoCurrency.nano: case CryptoCurrency.nano:
return '[0-9a-zA-Z_]'; return '[0-9a-zA-Z_]';
case CryptoCurrency.banano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.usdc: case CryptoCurrency.usdc:
case CryptoCurrency.usdcpoly: case CryptoCurrency.usdcpoly:
case CryptoCurrency.ape: case CryptoCurrency.ape:
@ -177,6 +179,8 @@ class AddressValidator extends TextValidator {
return [34, 43, 63]; return [34, 43, 63];
case CryptoCurrency.nano: case CryptoCurrency.nano:
return [64, 65]; return [64, 65];
case CryptoCurrency.banano:
return [64, 65];
case CryptoCurrency.sc: case CryptoCurrency.sc:
return [76]; return [76];
case CryptoCurrency.sol: case CryptoCurrency.sol:

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/utils/language_list.dart'; import 'package:cake_wallet/utils/language_list.dart';
class SeedValidator extends Validator<MnemonicItem> { class SeedValidator extends Validator<MnemonicItem> {
@ -28,6 +29,9 @@ class SeedValidator extends Validator<MnemonicItem> {
return haven!.getMoneroWordList(language); return haven!.getMoneroWordList(language);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.getEthereumWordList(language); return ethereum!.getEthereumWordList(language);
case WalletType.nano:
case WalletType.banano:
return nano!.getNanoWordList(language);
default: default:
return []; return [];
} }

View file

@ -39,15 +39,11 @@ class WalletCreationService {
bool exists(String name) { bool exists(String name) {
final walletName = name.toLowerCase(); final walletName = name.toLowerCase();
return walletInfoSource return walletInfoSource.values.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
.values
.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
} }
bool typeExists(WalletType type) { bool typeExists(WalletType type) {
return walletInfoSource return walletInfoSource.values.any((walletInfo) => walletInfo.type == type);
.values
.any((walletInfo) => walletInfo.type == type);
} }
void checkIfExists(String name) { void checkIfExists(String name) {
@ -60,15 +56,12 @@ class WalletCreationService {
checkIfExists(credentials.name); checkIfExists(credentials.name);
final password = generateWalletPassword(); final password = generateWalletPassword();
credentials.password = password; credentials.password = password;
await keyService.saveWalletPassword( await keyService.saveWalletPassword(password: password, walletName: credentials.name);
password: password, walletName: credentials.name); final wallet = await _service!.create(credentials);
final wallet = await _service!.create(credentials);
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
await sharedPreferences await sharedPreferences.setBool(
.setBool( PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
} }
return wallet; return wallet;
@ -78,15 +71,12 @@ class WalletCreationService {
checkIfExists(credentials.name); checkIfExists(credentials.name);
final password = generateWalletPassword(); final password = generateWalletPassword();
credentials.password = password; credentials.password = password;
await keyService.saveWalletPassword( await keyService.saveWalletPassword(password: password, walletName: credentials.name);
password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromKeys(credentials); final wallet = await _service!.restoreFromKeys(credentials);
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
await sharedPreferences await sharedPreferences.setBool(
.setBool( PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
} }
return wallet; return wallet;
@ -96,15 +86,12 @@ class WalletCreationService {
checkIfExists(credentials.name); checkIfExists(credentials.name);
final password = generateWalletPassword(); final password = generateWalletPassword();
credentials.password = password; credentials.password = password;
await keyService.saveWalletPassword( await keyService.saveWalletPassword(password: password, walletName: credentials.name);
password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromSeed(credentials); final wallet = await _service!.restoreFromSeed(credentials);
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
await sharedPreferences await sharedPreferences.setBool(
.setBool( PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
} }
return wallet; return wallet;

View file

@ -13,6 +13,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart';
@ -26,8 +27,13 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart'; import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart'; import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart'; import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
@ -73,6 +79,9 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
@ -83,7 +92,9 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/backup_service.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
@ -206,6 +217,7 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/nano/nano.dart' as nanoNano;
import 'core/totp_request_details.dart'; import 'core/totp_request_details.dart';
@ -214,6 +226,7 @@ final getIt = GetIt.instance;
var _isSetupFinished = false; var _isSetupFinished = false;
late Box<WalletInfo> _walletInfoSource; late Box<WalletInfo> _walletInfoSource;
late Box<Node> _nodeSource; late Box<Node> _nodeSource;
late Box<Node> _powNodeSource;
late Box<Contact> _contactSource; late Box<Contact> _contactSource;
late Box<Trade> _tradesSource; late Box<Trade> _tradesSource;
late Box<Template> _templates; late Box<Template> _templates;
@ -226,6 +239,7 @@ late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
Future<void> setup({ Future<void> setup({
required Box<WalletInfo> walletInfoSource, required Box<WalletInfo> walletInfoSource,
required Box<Node> nodeSource, required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required Box<Contact> contactSource, required Box<Contact> contactSource,
required Box<Trade> tradesSource, required Box<Trade> tradesSource,
required Box<Template> templates, required Box<Template> templates,
@ -237,6 +251,7 @@ Future<void> setup({
}) async { }) async {
_walletInfoSource = walletInfoSource; _walletInfoSource = walletInfoSource;
_nodeSource = nodeSource; _nodeSource = nodeSource;
_powNodeSource = powNodeSource;
_contactSource = contactSource; _contactSource = contactSource;
_tradesSource = tradesSource; _tradesSource = tradesSource;
_templates = templates; _templates = templates;
@ -259,6 +274,7 @@ Future<void> setup({
final settingsStore = await SettingsStoreBase.load( final settingsStore = await SettingsStoreBase.load(
nodeSource: _nodeSource, nodeSource: _nodeSource,
powNodeSource: _powNodeSource,
isBitcoinBuyEnabled: isBitcoinBuyEnabled, isBitcoinBuyEnabled: isBitcoinBuyEnabled,
// Enforce darkTheme on platforms other than mobile till the design for other themes is completed // Enforce darkTheme on platforms other than mobile till the design for other themes is completed
initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile
@ -271,6 +287,7 @@ Future<void> setup({
} }
getIt.registerFactory<Box<Node>>(() => _nodeSource); getIt.registerFactory<Box<Node>>(() => _nodeSource);
getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage()); getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
getIt.registerSingleton(AuthenticationStore()); getIt.registerSingleton(AuthenticationStore());
@ -612,20 +629,30 @@ Future<void> setup({
editingWallet: editingWallet); editingWallet: editingWallet);
}); });
getIt.registerFactory(() { getIt.registerFactory<NanoAccountListViewModel>(() {
final wallet = getIt.get<AppStore>().wallet!; final wallet = getIt.get<AppStore>().wallet!;
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
return NanoAccountListViewModel(wallet);
}
throw Exception(
'Unexpected wallet type: ${wallet.type} for generate Nano/Banano AccountListViewModel');
});
getIt.registerFactory<MoneroAccountListViewModel>(() {
final wallet = getIt.get<AppStore>().wallet!;
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) { if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
return MoneroAccountListViewModel(wallet); return MoneroAccountListViewModel(wallet);
} }
throw Exception( throw Exception(
'Unexpected wallet type: ${wallet.type} for generate MoneroAccountListViewModel'); 'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel');
}); });
getIt.registerFactory( getIt.registerFactory(
() => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>())); () => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
getIt.registerFactory(
() => NanoAccountListPage(accountListViewModel: getIt.get<NanoAccountListViewModel>()));
/*getIt.registerFactory(() { /*getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet; final wallet = getIt.get<AppStore>().wallet;
@ -653,6 +680,18 @@ Future<void> setup({
moneroAccountCreationViewModel: moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account))); getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactoryParam<NanoAccountEditOrCreateViewModel, NanoAccount?, void>(
(NanoAccount? account, _) =>
NanoAccountEditOrCreateViewModel(nano!.getAccountList(getIt.get<AppStore>().wallet!),
// banano?.getAccountList(getIt.get<AppStore>().wallet!),
wallet: getIt.get<AppStore>().wallet!,
accountListItem: account));
getIt.registerFactoryParam<NanoAccountEditOrCreatePage, NanoAccount?, void>(
(NanoAccount? account, _) => NanoAccountEditOrCreatePage(
nanoAccountCreationViewModel:
getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactory(() { getIt.registerFactory(() {
return DisplaySettingsViewModel(getIt.get<SettingsStore>()); return DisplaySettingsViewModel(getIt.get<SettingsStore>());
}); });
@ -696,6 +735,11 @@ Future<void> setup({
return NodeListViewModel(_nodeSource, appStore); return NodeListViewModel(_nodeSource, appStore);
}); });
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
return PowNodeListViewModel(_powNodeSource, appStore);
});
getIt.registerFactory( getIt.registerFactory(
() => ConnectionSyncPage(getIt.get<DashboardViewModel>(), getIt.get<Web3WalletService>()), () => ConnectionSyncPage(getIt.get<DashboardViewModel>(), getIt.get<Web3WalletService>()),
); );
@ -709,13 +753,23 @@ Future<void> setup({
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>())); getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>((WalletType? type, _) => getIt.registerFactory(() => NanoChangeRepPage(getIt.get<AppStore>().wallet!));
NodeCreateOrEditViewModel(
_nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>())); getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, bool?>(
(WalletType? type, bool? isPow) => NodeCreateOrEditViewModel(
(isPow ?? false) ? _powNodeSource : _nodeSource,
type ?? getIt.get<AppStore>().wallet!.type,
getIt.get<SettingsStore>()));
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>( getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage( (Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(), nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
editingNode: editingNode,
isSelected: isSelected));
getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: true),
editingNode: editingNode, editingNode: editingNode,
isSelected: isSelected)); isSelected: isSelected));
@ -771,6 +825,8 @@ Future<void> setup({
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource); return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumWalletService(_walletInfoSource); return ethereum!.createEthereumWalletService(_walletInfoSource);
case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource);
default: default:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
} }
@ -798,6 +854,15 @@ Future<void> setup({
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>( getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
(type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type))); (type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>(
(credentials, _) =>
WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
param1: credentials,
)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>( getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) { (TransactionInfo transactionInfo, _) {
final wallet = getIt.get<AppStore>().wallet!; final wallet = getIt.get<AppStore>().wallet!;
@ -911,8 +976,8 @@ Future<void> setup({
getIt.registerFactory(() => YatService()); getIt.registerFactory(() => YatService());
getIt.registerFactory(() => AddressResolver( getIt.registerFactory(() =>
yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!)); AddressResolver(yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>( getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData)); (QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
@ -1077,7 +1142,12 @@ Future<void> setup({
), ),
); );
getIt.registerFactory<ManageNodesPage>(() => ManageNodesPage(getIt.get<NodeListViewModel>())); getIt.registerFactoryParam<ManageNodesPage, bool, void>((bool isPow, _) {
if (isPow) {
return ManageNodesPage(isPow, powNodeListViewModel: getIt.get<PowNodeListViewModel>());
}
return ManageNodesPage(isPow, nodeListViewModel: getIt.get<NodeListViewModel>());
});
_isSetupFinished = true; _isSetupFinished = true;
} }

View file

@ -26,12 +26,15 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
Future<void> defaultSettingsMigration( Future<void> defaultSettingsMigration(
{required int version, {required int version,
required SharedPreferences sharedPreferences, required SharedPreferences sharedPreferences,
required FlutterSecureStorage secureStorage, required FlutterSecureStorage secureStorage,
required Box<Node> nodes, required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource, required Box<WalletInfo> walletInfoSource,
required Box<Trade> tradeSource, required Box<Trade> tradeSource,
required Box<Contact> contactSource}) async { required Box<Contact> contactSource}) async {
@ -40,41 +43,36 @@ Future<void> defaultSettingsMigration(
} }
// check current nodes for nullability regardless of the version // check current nodes for nullability regardless of the version
await checkCurrentNodes(nodes, sharedPreferences); await checkCurrentNodes(nodes, powNodes, sharedPreferences);
final isNewInstall =
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
await _validateWalletInfoBoxData(walletInfoSource); await _validateWalletInfoBoxData(walletInfoSource);
final isNewInstall = sharedPreferences await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall);
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
await sharedPreferences.setBool( final currentVersion =
PreferencesKey.isNewInstall, isNewInstall); sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0;
final currentVersion = sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ??
0;
if (currentVersion >= version) { if (currentVersion >= version) {
return; return;
} }
final migrationVersionsLength = version - currentVersion; final migrationVersionsLength = version - currentVersion;
final migrationVersions = List<int>.generate( final migrationVersions =
migrationVersionsLength, (i) => currentVersion + (i + 1)); List<int>.generate(migrationVersionsLength, (i) => currentVersion + (i + 1));
await Future.forEach(migrationVersions, (int version) async { await Future.forEach(migrationVersions, (int version) async {
try { try {
switch (version) { switch (version) {
case 1: case 1:
await sharedPreferences.setString( await sharedPreferences.setString(
PreferencesKey.currentFiatCurrencyKey, PreferencesKey.currentFiatCurrencyKey, FiatCurrency.usd.toString());
FiatCurrency.usd.toString()); await sharedPreferences.setInt(PreferencesKey.currentTransactionPriorityKeyLegacy,
await sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKeyLegacy,
monero!.getDefaultTransactionPriority().raw); monero!.getDefaultTransactionPriority().raw);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey, PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw);
BalanceDisplayMode.availableBalance.raw);
await sharedPreferences.setBool('save_recipient_address', true); await sharedPreferences.setBool('save_recipient_address', true);
await resetToDefault(nodes); await resetToDefault(nodes);
await changeMoneroCurrentNodeToDefault( await changeMoneroCurrentNodeToDefault(
@ -83,14 +81,12 @@ Future<void> defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault( await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault( await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 2: case 2:
await replaceNodesMigration(nodes: nodes); await replaceNodesMigration(nodes: nodes);
await replaceDefaultNode( await replaceDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 3: case 3:
@ -124,7 +120,7 @@ Future<void> defaultSettingsMigration(
break; break;
case 12: case 12:
await checkCurrentNodes(nodes, sharedPreferences); await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break; break;
case 13: case 13:
@ -135,14 +131,13 @@ Future<void> defaultSettingsMigration(
await addLitecoinElectrumServerList(nodes: nodes); await addLitecoinElectrumServerList(nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault( await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences); await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break; break;
case 16: case 16:
await addHavenNodeList(nodes: nodes); await addHavenNodeList(nodes: nodes);
await changeHavenCurrentNodeToDefault( await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
sharedPreferences: sharedPreferences, nodes: nodes); await checkCurrentNodes(nodes, powNodes, sharedPreferences);
await checkCurrentNodes(nodes, sharedPreferences);
break; break;
case 17: case 17:
@ -164,6 +159,13 @@ Future<void> defaultSettingsMigration(
await changeEthereumCurrentNodeToDefault( await changeEthereumCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 22:
await addNanoNodeList(nodes: nodes);
await addNanoPowNodeList(nodes: nodes);
await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await changeNanoCurrentPowNodeToDefault(
sharedPreferences: sharedPreferences, nodes: powNodes);
break;
default: default:
break; break;
@ -176,8 +178,7 @@ Future<void> defaultSettingsMigration(
} }
}); });
await sharedPreferences.setInt( await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
PreferencesKey.currentDefaultSettingsMigrationVersion, version);
} }
Future<void> _validateWalletInfoBoxData(Box<WalletInfo> walletInfoSource) async { Future<void> _validateWalletInfoBoxData(Box<WalletInfo> walletInfoSource) async {
@ -247,8 +248,8 @@ Future<void> validateBitcoinSavedTransactionPriority(SharedPreferences sharedPre
final int? savedBitcoinPriority = final int? savedBitcoinPriority =
sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority); sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority);
if (!bitcoin!.getTransactionPriorities().any((element) => element.raw == savedBitcoinPriority)) { if (!bitcoin!.getTransactionPriorities().any((element) => element.raw == savedBitcoinPriority)) {
await sharedPreferences.setInt( await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
PreferencesKey.bitcoinTransactionPriority, bitcoin!.getMediumTransactionPriority().serialize()); bitcoin!.getMediumTransactionPriority().serialize());
} }
} }
@ -265,10 +266,9 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
final replaceNodes = <String, Node>{ final replaceNodes = <String, Node>{
'eu-node.cakewallet.io:18081': 'eu-node.cakewallet.io:18081':
Node(uri: 'xmr-node-eu.cakewallet.com:18081', type: WalletType.monero), Node(uri: 'xmr-node-eu.cakewallet.com:18081', type: WalletType.monero),
'node.cakewallet.io:18081': Node( 'node.cakewallet.io:18081':
uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero), Node(uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
'node.xmr.ru:13666': 'node.xmr.ru:13666': Node(uri: 'node.monero.net:18081', type: WalletType.monero)
Node(uri: 'node.monero.net:18081', type: WalletType.monero)
}; };
nodes.values.forEach((Node node) async { nodes.values.forEach((Node node) async {
@ -284,8 +284,7 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
} }
Future<void> changeMoneroCurrentNodeToDefault( Future<void> changeMoneroCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
required Box<Node> nodes}) async {
final node = getMoneroDefaultNode(nodes: nodes); final node = getMoneroDefaultNode(nodes: nodes);
final nodeId = node.key as int? ?? 0; // 0 - England final nodeId = node.key as int? ?? 0; // 0 - England
@ -293,27 +292,35 @@ Future<void> changeMoneroCurrentNodeToDefault(
} }
Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) { Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull( return nodes.values
(Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri) .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri) ??
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin); nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
} }
Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) { Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull( return nodes.values
(Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin); nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
} }
Node? getHavenDefaultNode({required Box<Node> nodes}) { Node? getHavenDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull( return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == havenDefaultNodeUri) ??
(Node node) => node.uriRaw == havenDefaultNodeUri) nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
} }
Node? getEthereumDefaultNode({required Box<Node> nodes}) { Node? getEthereumDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull( return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == ethereumDefaultNodeUri) ??
(Node node) => node.uriRaw == ethereumDefaultNodeUri) nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum); }
Node? getNanoDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
}
Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
} }
Node getMoneroDefaultNode({required Box<Node> nodes}) { Node getMoneroDefaultNode({required Box<Node> nodes}) {
@ -329,16 +336,14 @@ Node getMoneroDefaultNode({required Box<Node> nodes}) {
} }
try { try {
return nodes.values return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
.firstWhere((Node node) => node.uriRaw == nodeUri); } catch (_) {
} catch(_) {
return nodes.values.first; return nodes.values.first;
} }
} }
Future<void> changeBitcoinCurrentElectrumServerToDefault( Future<void> changeBitcoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
required Box<Node> nodes}) async {
final server = getBitcoinDefaultElectrumServer(nodes: nodes); final server = getBitcoinDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int? ?? 0; final serverId = server?.key as int? ?? 0;
@ -346,8 +351,7 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
} }
Future<void> changeLitecoinCurrentElectrumServerToDefault( Future<void> changeLitecoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
required Box<Node> nodes}) async {
final server = getLitecoinDefaultElectrumServer(nodes: nodes); final server = getLitecoinDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int? ?? 0; final serverId = server?.key as int? ?? 0;
@ -355,8 +359,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
} }
Future<void> changeHavenCurrentNodeToDefault( Future<void> changeHavenCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
required Box<Node> nodes}) async {
final node = getHavenDefaultNode(nodes: nodes); final node = getHavenDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0; final nodeId = node?.key as int? ?? 0;
@ -364,25 +367,21 @@ Future<void> changeHavenCurrentNodeToDefault(
} }
Future<void> replaceDefaultNode( Future<void> replaceDefaultNode(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
required Box<Node> nodes}) async {
const nodesForReplace = <String>[ const nodesForReplace = <String>[
'xmr-node-uk.cakewallet.com:18081', 'xmr-node-uk.cakewallet.com:18081',
'eu-node.cakewallet.io:18081', 'eu-node.cakewallet.io:18081',
'node.cakewallet.io:18081' 'node.cakewallet.io:18081'
]; ];
final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentNode = final currentNode = nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId); final needToReplace = currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
final needToReplace =
currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
if (!needToReplace) { if (!needToReplace) {
return; return;
} }
await changeMoneroCurrentNodeToDefault( await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
sharedPreferences: sharedPreferences, nodes: nodes);
} }
Future<void> updateNodeTypes({required Box<Node> nodes}) async { Future<void> updateNodeTypes({required Box<Node> nodes}) async {
@ -421,14 +420,11 @@ Future<void> addHavenNodeList({required Box<Node> nodes}) async {
} }
} }
Future<void> addAddressesForMoneroWallets( Future<void> addAddressesForMoneroWallets(Box<WalletInfo> walletInfoSource) async {
Box<WalletInfo> walletInfoSource) async { final moneroWalletsInfo = walletInfoSource.values.where((info) => info.type == WalletType.monero);
final moneroWalletsInfo =
walletInfoSource.values.where((info) => info.type == WalletType.monero);
moneroWalletsInfo.forEach((info) async { moneroWalletsInfo.forEach((info) async {
try { try {
final walletPath = final walletPath = await pathForWallet(name: info.name, type: WalletType.monero);
await pathForWallet(name: info.name, type: WalletType.monero);
final addressFilePath = '$walletPath.address.txt'; final addressFilePath = '$walletPath.address.txt';
final addressFile = File(addressFilePath); final addressFile = File(addressFilePath);
@ -449,8 +445,7 @@ Future<void> updateDisplayModes(SharedPreferences sharedPreferences) async {
final currentBalanceDisplayMode = final currentBalanceDisplayMode =
sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey) ?? -1; sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey) ?? -1;
final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2; final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2;
await sharedPreferences.setInt( await sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
} }
Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async { Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
@ -464,10 +459,9 @@ Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
await secureStorage.write(key: key, value: password); await secureStorage.write(key: key, value: password);
} }
Future<void> changeTransactionPriorityAndFeeRateKeys( Future<void> changeTransactionPriorityAndFeeRateKeys(SharedPreferences sharedPreferences) async {
SharedPreferences sharedPreferences) async { final legacyTransactionPriority =
final legacyTransactionPriority = sharedPreferences sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.moneroTransactionPriority, legacyTransactionPriority); PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority, await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
@ -477,10 +471,8 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
Future<void> changeDefaultMoneroNode( Future<void> changeDefaultMoneroNode(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async { Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com'; const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
final currentMoneroNodeId = final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final currentMoneroNode =
nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode = final needToReplaceCurrentMoneroNode =
currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern); currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
@ -491,78 +483,87 @@ Future<void> changeDefaultMoneroNode(
} }
}); });
final newCakeWalletNode = final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode); await nodeSource.add(newCakeWalletNode);
if (needToReplaceCurrentMoneroNode) { if (needToReplaceCurrentMoneroNode) {
await sharedPreferences.setInt( await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
} }
} }
Future<void> checkCurrentNodes( Future<void> checkCurrentNodes(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async { Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId = final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentBitcoinElectrumSeverId = final currentBitcoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentLitecoinElectrumSeverId = sharedPreferences final currentLitecoinElectrumSeverId =
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
.getInt(PreferencesKey.currentHavenNodeIdKey); final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentEthereumNodeId = sharedPreferences final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
.getInt(PreferencesKey.currentEthereumNodeIdKey); final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentMoneroNode = nodeSource.values.firstWhereOrNull( final currentMoneroNode =
(node) => node.key == currentMoneroNodeId); nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull( final currentBitcoinElectrumServer =
(node) => node.key == currentBitcoinElectrumSeverId); nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull( final currentLitecoinElectrumServer =
(node) => node.key == currentLitecoinElectrumSeverId); nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull( final currentHavenNodeServer =
(node) => node.key == currentHavenNodeId); nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull( final currentEthereumNodeServer =
(node) => node.key == currentEthereumNodeId); nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentNanoPowNodeServer =
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
if (currentMoneroNode == null) { if (currentMoneroNode == null) {
final newCakeWalletNode = final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode); await nodeSource.add(newCakeWalletNode);
await sharedPreferences.setInt( await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
} }
if (currentBitcoinElectrumServer == null) { if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum = final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletElectrum); await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
cakeWalletElectrum.key as int);
} }
if (currentLitecoinElectrumServer == null) { if (currentLitecoinElectrumServer == null) {
final cakeWalletElectrum = final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
await nodeSource.add(cakeWalletElectrum); await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentLitecoinElectrumSererIdKey, PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
cakeWalletElectrum.key as int);
} }
if (currentHavenNodeServer == null) { if (currentHavenNodeServer == null) {
final node = Node(uri: havenDefaultNodeUri, type: WalletType.haven); final node = Node(uri: havenDefaultNodeUri, type: WalletType.haven);
await nodeSource.add(node); await nodeSource.add(node);
await sharedPreferences.setInt( await sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
PreferencesKey.currentHavenNodeIdKey, node.key as int);
} }
if (currentEthereumNodeServer == null) { if (currentEthereumNodeServer == null) {
final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum); final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
await nodeSource.add(node); await nodeSource.add(node);
await sharedPreferences.setInt( await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
PreferencesKey.currentEthereumNodeIdKey, node.key as int); }
if (currentNanoNodeServer == null) {
final node = Node(uri: nanoDefaultNodeUri, useSSL: true, type: WalletType.nano);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
}
if (currentNanoPowNodeServer == null) {
Node? node = powNodeSource.values
.firstWhereOrNull((node) => node.uri.toString() == nanoDefaultPowNodeUri);
if (node == null) {
node = Node(uri: nanoDefaultPowNodeUri, useSSL: true, type: WalletType.nano);
await powNodeSource.add(node);
}
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
} }
} }
@ -570,31 +571,27 @@ Future<void> resetBitcoinElectrumServer(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async { Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentElectrumSeverId = final currentElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final oldElectrumServer = nodeSource.values.firstWhereOrNull( final oldElectrumServer = nodeSource.values
(node) => node.uri.toString().contains('electrumx.cakewallet.com')); .firstWhereOrNull((node) => node.uri.toString().contains('electrumx.cakewallet.com'));
var cakeWalletNode = nodeSource.values.firstWhereOrNull( var cakeWalletNode = nodeSource.values
(node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri); .firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
if (cakeWalletNode == null) { if (cakeWalletNode == null) {
cakeWalletNode = cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletNode); await nodeSource.add(cakeWalletNode);
} }
if (currentElectrumSeverId == oldElectrumServer?.key) { if (currentElectrumSeverId == oldElectrumServer?.key) {
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int);
cakeWalletNode.key as int);
} }
await oldElectrumServer?.delete(); await oldElectrumServer?.delete();
} }
Future<void> changeDefaultHavenNode( Future<void> changeDefaultHavenNode(Box<Node> nodeSource) async {
Box<Node> nodeSource) async {
const previousHavenDefaultNodeUri = 'vault.havenprotocol.org:443'; const previousHavenDefaultNodeUri = 'vault.havenprotocol.org:443';
final havenNodes = nodeSource.values.where( final havenNodes = nodeSource.values.where((node) => node.uriRaw == previousHavenDefaultNodeUri);
(node) => node.uriRaw == previousHavenDefaultNodeUri);
havenNodes.forEach((node) async { havenNodes.forEach((node) async {
node.uriRaw = havenDefaultNodeUri; node.uriRaw = havenDefaultNodeUri;
await node.save(); await node.save();
@ -607,8 +604,8 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
return; return;
} }
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey,
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw); isExchangeDisabled ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
await sharedPreferences.remove(PreferencesKey.disableExchangeKey); await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
} }
@ -623,10 +620,42 @@ Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
} }
Future<void> changeEthereumCurrentNodeToDefault( Future<void> changeEthereumCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
required Box<Node> nodes}) async {
final node = getEthereumDefaultNode(nodes: nodes); final node = getEthereumDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0; final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId); await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
} }
Future<void> addNanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoPowNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changeNanoCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getNanoDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, nodeId);
}
Future<void> changeNanoCurrentPowNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getNanoDefaultPowNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
}

View file

@ -52,6 +52,8 @@ class MainActions {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.nano:
case WalletType.banano:
switch (defaultBuyProvider) { switch (defaultBuyProvider) {
case BuyProviderType.AskEachTime: case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy); Navigator.pushNamed(context, Routes.buy);
@ -73,7 +75,7 @@ class MainActions {
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertWithOneAction( return AlertWithOneAction(
alertTitle: S.of(context).buy, alertTitle: S.of(context).buy,
alertContent: S.of(context).buy_alert_content, alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok, buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()); buttonAction: () => Navigator.of(context).pop());
}); });
@ -143,7 +145,7 @@ class MainActions {
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertWithOneAction( return AlertWithOneAction(
alertTitle: S.of(context).sell, alertTitle: S.of(context).sell,
alertContent: S.of(context).sell_alert_content, alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok, buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()); buttonAction: () => Navigator.of(context).pop());
}, },

View file

@ -21,13 +21,12 @@ Future<List<Node>> loadDefaultNodes() async {
} }
Future<List<Node>> loadBitcoinElectrumServerList() async { Future<List<Node>> loadBitcoinElectrumServerList() async {
final serverListRaw = final serverListRaw = await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList; final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[]; final serverList = <Node>[];
for (final raw in loadedServerList) { for (final raw in loadedServerList) {
if (raw is Map) { if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw)); final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.bitcoin; node.type = WalletType.bitcoin;
serverList.add(node); serverList.add(node);
@ -38,8 +37,7 @@ Future<List<Node>> loadBitcoinElectrumServerList() async {
} }
Future<List<Node>> loadLitecoinElectrumServerList() async { Future<List<Node>> loadLitecoinElectrumServerList() async {
final serverListRaw = final serverListRaw = await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList; final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[]; final serverList = <Node>[];
@ -86,17 +84,60 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
return nodes; return nodes;
} }
Future<List<Node>> loadDefaultNanoNodes() async {
final nodesRaw = await rootBundle.loadString('assets/nano_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.nano;
nodes.add(node);
}
}
return nodes;
}
Future<List<Node>> loadDefaultNanoPowNodes() async {
final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml');
final loadedPowNodes = loadYaml(powNodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedPowNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.nano;
nodes.add(node);
}
}
return nodes;
}
Future resetToDefault(Box<Node> nodeSource) async { Future resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes(); final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final litecoinElectrumServerList = await loadLitecoinElectrumServerList(); final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final havenNodes = await loadDefaultHavenNodes(); final havenNodes = await loadDefaultHavenNodes();
final nodes = final ethereumNodes = await loadDefaultEthereumNodes();
moneroNodes + final nanoNodes = await loadDefaultNanoNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList + bitcoinElectrumServerList +
litecoinElectrumServerList + litecoinElectrumServerList +
havenNodes; havenNodes +
ethereumNodes +
nanoNodes;
await nodeSource.clear(); await nodeSource.clear();
await nodeSource.addAll(nodes); await nodeSource.addAll(nodes);
} }
Future resetPowToDefault(Box<Node> powNodeSource) async {
final nanoPowNodes = await loadDefaultNanoPowNodes();
final nodes = nanoPowNodes;
await powNodeSource.clear();
await powNodeSource.addAll(nodes);
}

View file

@ -6,6 +6,10 @@ class PreferencesKey {
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc'; static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentHavenNodeIdKey = 'current_node_id_xhv'; static const currentHavenNodeIdKey = 'current_node_id_xhv';
static const currentEthereumNodeIdKey = 'current_node_id_eth'; static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentNanoNodeIdKey = 'current_node_id_nano';
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
static const currentBananoNodeIdKey = 'current_node_id_banano';
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
static const currentFiatCurrencyKey = 'current_fiat_currency'; static const currentFiatCurrencyKey = 'current_fiat_currency';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode';

View file

@ -17,8 +17,11 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return haven!.getTransactionPriorities(); return haven!.getTransactionPriorities();
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.getTransactionPriorities(); return ethereum!.getTransactionPriorities();
// no such thing for nano/banano:
case WalletType.nano:
case WalletType.banano:
return [];
default: default:
return []; return [];
} }
} }

View file

@ -97,6 +97,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(WalletInfoAdapter()); CakeHive.registerAdapter(WalletInfoAdapter());
} }
if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(DerivationTypeAdapter());
}
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) { if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(WalletTypeAdapter()); CakeHive.registerAdapter(WalletTypeAdapter());
} }
@ -128,6 +132,7 @@ Future<void> initializeAppConfigs() async {
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey); final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.boxName); final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
final nodes = await CakeHive.openBox<Node>(Node.boxName); final nodes = await CakeHive.openBox<Node>(Node.boxName);
final powNodes = await CakeHive.openBox<Node>(Node.boxName + "pow");// must be different from Node.boxName
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>( final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName, TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey); encryptionKey: transactionDescriptionsBoxKey);
@ -142,6 +147,7 @@ Future<void> initializeAppConfigs() async {
await initialSetup( await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(), sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes, nodes: nodes,
powNodes: powNodes,
walletInfoSource: walletInfoSource, walletInfoSource: walletInfoSource,
contactSource: contacts, contactSource: contacts,
tradesSource: trades, tradesSource: trades,
@ -153,12 +159,13 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21); initialMigrationVersion: 22);
} }
Future<void> initialSetup( Future<void> initialSetup(
{required SharedPreferences sharedPreferences, {required SharedPreferences sharedPreferences,
required Box<Node> nodes, required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource, required Box<WalletInfo> walletInfoSource,
required Box<Contact> contactSource, required Box<Contact> contactSource,
required Box<Trade> tradesSource, required Box<Trade> tradesSource,
@ -179,10 +186,12 @@ Future<void> initialSetup(
walletInfoSource: walletInfoSource, walletInfoSource: walletInfoSource,
contactSource: contactSource, contactSource: contactSource,
tradeSource: tradesSource, tradeSource: tradesSource,
nodes: nodes); nodes: nodes,
powNodes: powNodes);
await setup( await setup(
walletInfoSource: walletInfoSource, walletInfoSource: walletInfoSource,
nodeSource: nodes, nodeSource: nodes,
powNodeSource: powNodes,
contactSource: contactSource, contactSource: contactSource,
tradesSource: tradesSource, tradesSource: tradesSource,
templates: templates, templates: templates,

499
lib/nano/cw_nano.dart Normal file
View file

@ -0,0 +1,499 @@
part of 'nano.dart';
class CWNanoAccountList extends NanoAccountList {
CWNanoAccountList(this._wallet);
final Object _wallet;
@override
@computed
ObservableList<NanoAccount> get accounts {
final nanoWallet = _wallet as NanoWallet;
final accounts = nanoWallet.walletAddresses.accountList.accounts
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
return ObservableList<NanoAccount>.of(accounts);
}
@override
void update(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.update(null);
}
@override
void refresh(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.refresh();
}
@override
Future<List<NanoAccount>> getAll(Object wallet) async {
final nanoWallet = wallet as NanoWallet;
return (await nanoWallet.walletAddresses.accountList.getAll())
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
}
@override
Future<void> addAccount(Object wallet, {required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList.addAccount(label: label);
}
@override
Future<void> setLabelAccount(Object wallet,
{required int accountIndex, required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList
.setLabelAccount(accountIndex: accountIndex, label: label);
}
}
class CWNano extends Nano {
@override
NanoAccountList getAccountList(Object wallet) {
return CWNanoAccountList(wallet);
}
@override
Account getCurrentAccount(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final acc = nanoWallet.walletAddresses.account;
return Account(id: acc!.id, label: acc.label, balance: acc.balance);
}
@override
void setCurrentAccount(Object wallet, int id, String label, String? balance) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.account = NanoAccount(id: id, label: label, balance: balance);
nanoWallet.regenerateAddress();
}
@override
List<String> getNanoWordList(String language) {
return NanoMnemomics.WORDLIST;
}
@override
WalletService createNanoWalletService(Box<WalletInfo> walletInfoSource) {
return NanoWalletService(walletInfoSource);
}
@override
Map<String, String> getKeys(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final keys = nanoWallet.keys;
return <String, String>{
"seedKey": keys.seedKey,
};
}
@override
WalletCredentials createNanoNewWalletCredentials({
required String name,
String? password,
}) =>
NanoNewWalletCredentials(
name: name,
password: password,
);
@override
WalletCredentials createNanoRestoreWalletFromSeedCredentials({
required String name,
required String password,
required String mnemonic,
DerivationType? derivationType,
}) {
if (derivationType == null) {
// figure out the derivation type as best we can, otherwise set it to "unknown"
if (mnemonic.split(" ").length == 12) {
derivationType = DerivationType.bip39;
} else {
derivationType = DerivationType.unknown;
}
}
return NanoRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
derivationType: derivationType,
);
}
@override
WalletCredentials createNanoRestoreWalletFromKeysCredentials({
required String name,
required String password,
required String seedKey,
DerivationType? derivationType,
}) {
if (derivationType == null) {
// figure out the derivation type as best we can, otherwise set it to "unknown"
if (seedKey.length == 64) {
derivationType = DerivationType.nano;
} else {
derivationType = DerivationType.unknown;
}
}
return NanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: seedKey,
derivationType: derivationType,
);
}
@override
Object createNanoTransactionCredentials(List<Output> outputs) {
return NanoTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
))
.toList(),
);
}
@override
Future<void> changeRep(Object wallet, String address) async {
return (wallet as NanoWallet).changeRep(address);
}
@override
Future<void> updateTransactions(Object wallet) async {
return (wallet as NanoWallet).updateTransactions();
}
@override
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
return (transactionInfo as NanoTransactionInfo).amountRaw;
}
}
class CWNanoUtil extends NanoUtil {
// standard:
@override
String seedToPrivate(String seed, int index) {
return ND.NanoKeys.seedToPrivate(seed, index);
}
@override
String seedToAddress(String seed, int index) {
return ND.NanoAccounts.createAccount(
ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
}
@override
String seedToMnemonic(String seed) {
return NanoMnemomics.seedToMnemonic(seed).join(" ");
}
@override
Future<String> mnemonicToSeed(String mnemonic) async {
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
}
@override
String privateKeyToPublic(String privateKey) {
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
return ND.NanoKeys.createPublicKey(privateKey);
}
@override
String addressToPublicKey(String publicAddress) {
return ND.NanoAccounts.extractPublicKey(publicAddress);
}
// universal:
@override
String privateKeyToAddress(String privateKey) {
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, privateKeyToPublic(privateKey));
}
@override
String publicKeyToAddress(String publicKey) {
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, publicKey);
}
// standard + hd:
@override
bool isValidSeed(String seed) {
// Ensure seed is 64 or 128 characters long
if (seed.length != 64 && seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return ND.NanoHelpers.isHexString(seed);
}
// hd:
@override
Future<String> hdMnemonicListToSeed(List<String> words) async {
// if (words.length != 24) {
// throw Exception('Expected a 24-word list, got a ${words.length} list');
// }
final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
final String seed = await hasher.sha512(words.join(' '), salt);
return seed;
}
@override
Future<String> hdSeedToPrivate(String seed, int index) async {
List<int> seedBytes = hex.decode(seed);
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
return hex.encode(data.key);
}
@override
Future<String> hdSeedToAddress(String seed, int index) async {
return ND.NanoAccounts.createAccount(
ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
}
@override
Future<String> uniSeedToAddress(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToAddress(seed, index));
} else if (type == "hd") {
return hdSeedToAddress(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override
Future<String> uniSeedToPrivate(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToPrivate(seed, index));
} else if (type == "hd") {
return hdSeedToPrivate(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override
bool isValidBip39Seed(String seed) {
// Ensure seed is 128 characters long
if (seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return ND.NanoHelpers.isHexString(seed);
}
// number util:
static const int maxDecimalDigits = 6; // Max digits after decimal
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
BigInt rawPerXMR = BigInt.parse("1000000000000");
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
// static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
/// Convert raw to ban and return as BigDecimal
///
/// @param raw 100000000000000000000000000000
/// @return Decimal value 1.000000000000000000000000000000
///
@override
Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
rawPerCur ??= rawPerNano;
final Decimal amount = Decimal.parse(raw.toString());
final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
return result;
}
@override
String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
Decimal bigger = input.shift(digits);
bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
bigger = bigger.shift(-digits);
return bigger.toString();
}
/// Return raw as a NANO amount.
///
/// @param raw 100000000000000000000000000000
/// @returns 1
///
@override
String getRawAsUsableString(String? raw, BigInt rawPerCur) {
final String res =
truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
return "0";
}
if (!res.contains(".")) {
return res;
}
final String numAmount = res.split(".")[0];
String decAmount = res.split(".")[1];
// truncate:
if (decAmount.length > maxDecimalDigits) {
decAmount = decAmount.substring(0, maxDecimalDigits);
// remove trailing zeros:
decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
if (decAmount.isEmpty) {
return numAmount;
}
}
return "$numAmount.$decAmount";
}
@override
String getRawAccuracy(String? raw, BigInt rawPerCur) {
final String rawString = getRawAsUsableString(raw, rawPerCur);
final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
if (raw == null || raw.isEmpty || raw == "0") {
return "";
}
if (rawString != rawDecimalString) {
return "~";
}
return "";
}
/// Return readable string amount as raw string
/// @param amount 1.01
/// @returns 101000000000000000000000000000
///
@override
String getAmountAsRaw(String amount, BigInt rawPerCur) {
final Decimal asDecimal = Decimal.parse(amount);
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
return (asDecimal * rawDecimal).toString();
}
@override
Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
DerivationType derivationType, {
String? seedKey,
String? mnemonic,
required Node node,
}) async {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
late String publicAddress;
if (seedKey != null) {
if (derivationType == DerivationType.bip39) {
publicAddress = await hdSeedToAddress(seedKey, 0);
} else if (derivationType == DerivationType.nano) {
publicAddress = await seedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.bip39) {
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await hdSeedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.nano) {
if (mnemonic != null) {
seedKey = await mnemonicToSeed(mnemonic);
publicAddress = await seedToAddress(seedKey, 0);
}
}
AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress);
if (accountInfo == null) {
accountInfo = AccountInfoResponse(frontier: "", balance: "0", representative: "", confirmationHeight: 0);
}
accountInfo.address = publicAddress;
return accountInfo;
}
@override
Future<List<DerivationType>> compareDerivationMethods({
String? mnemonic,
String? privateKey,
required Node node,
}) async {
String? seedKey = privateKey;
if (mnemonic?.split(' ').length == 12) {
return [DerivationType.bip39];
}
if (seedKey?.length == 128) {
return [DerivationType.bip39];
} else if (seedKey?.length == 64) {
return [DerivationType.nano];
}
late String publicAddressStandard;
late String publicAddressBip39;
try {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
seedKey = await mnemonicToSeed(mnemonic);
publicAddressStandard = await seedToAddress(seedKey, 0);
} else if (seedKey != null) {
try {
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
} catch (e) {
return [DerivationType.nano];
}
try {
publicAddressStandard = await seedToAddress(seedKey, 0);
} catch (e) {
return [DerivationType.bip39];
}
}
// check if account has a history:
AccountInfoResponse? bip39Info;
AccountInfoResponse? standardInfo;
try {
bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
} catch (e) {
bip39Info = null;
}
try {
standardInfo = await nanoClient.getAccountInfo(publicAddressStandard);
} catch (e) {
standardInfo = null;
}
// one of these is *probably* null:
if (bip39Info == null && standardInfo != null) {
return [DerivationType.nano];
} else if (standardInfo == null && bip39Info != null) {
return [DerivationType.bip39];
}
// we don't know for sure:
return [DerivationType.nano, DerivationType.bip39];
} catch (e) {
return [DerivationType.unknown];
}
}
}

View file

@ -13,4 +13,11 @@ void startOnCurrentNodeChangeReaction(AppStore appStore) {
print(e.toString()); print(e.toString());
} }
}); });
appStore.settingsStore.powNodes.observe((change) async {
try {
await appStore.wallet!.connectToPowNode(node: change.newValue!);
} catch (e) {
print(e.toString());
}
});
} }

View file

@ -58,16 +58,23 @@ void startCurrentWalletChangeReaction(
} }
final node = settingsStore.getCurrentNode(wallet.type); final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore); startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
startCheckConnectionReaction(wallet, settingsStore); startCheckConnectionReaction(wallet, settingsStore);
await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name); await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
await getIt await getIt
.get<SharedPreferences>() .get<SharedPreferences>()
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type)); .setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
_setAutoGenerateSubaddressStatus(wallet, settingsStore); _setAutoGenerateSubaddressStatus(wallet, settingsStore);
} }
await wallet.connectToNode(node: node); await wallet.connectToNode(node: node);
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
final powNode = settingsStore.getCurrentPowNode(wallet.type);
await wallet.connectToPowNode(node: powNode);
}
if (wallet.type == WalletType.haven) { if (wallet.type == WalletType.haven) {
await updateHavenRate(fiatConversionStore); await updateHavenRate(fiatConversionStore);
@ -101,7 +108,7 @@ void startCurrentWalletChangeReaction(
if (wallet.type == WalletType.ethereum) { if (wallet.type == WalletType.ethereum) {
final currencies = final currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
for (final currency in currencies) { for (final currency in currencies) {
() async { () async {

View file

@ -12,11 +12,15 @@ import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/webview_page.dart'; import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart'; import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart'; import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart'; import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart'; import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@ -51,6 +55,8 @@ import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dar
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
@ -111,36 +117,34 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWalletFromWelcome: case Routes.newWalletFromWelcome:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>( builder: (_) =>
param1: (PinCodeState<PinCodeWidget> context, dynamic _) { getIt.get<SetupPinCodePage>(param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (availableWalletTypes.length == 1) { if (availableWalletTypes.length == 1) {
Navigator.of(context.context).pushNamed(Routes.newWallet, arguments: availableWalletTypes.first); Navigator.of(context.context)
} else { .pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
Navigator.of(context.context).pushNamed(Routes.newWalletType); } else {
} Navigator.of(context.context).pushNamed(Routes.newWalletType);
}), }
}),
fullscreenDialog: true); fullscreenDialog: true);
case Routes.newWalletType: case Routes.newWalletType:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>( builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) => param1: (BuildContext context, WalletType type) =>
Navigator.of(context) Navigator.of(context).pushNamed(Routes.newWallet, arguments: type)));
.pushNamed(Routes.newWallet, arguments: type)));
case Routes.newWallet: case Routes.newWallet:
final type = settings.arguments as WalletType; final type = settings.arguments as WalletType;
final walletNewVM = getIt.get<WalletNewVM>(param1: type); final walletNewVM = getIt.get<WalletNewVM>(param1: type);
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => NewWalletPage(walletNewVM));
builder: (_) => NewWalletPage(walletNewVM));
case Routes.setupPin: case Routes.setupPin:
Function(PinCodeState<PinCodeWidget>, String)? callback; Function(PinCodeState<PinCodeWidget>, String)? callback;
if (settings.arguments is Function(PinCodeState<PinCodeWidget>, String)) { if (settings.arguments is Function(PinCodeState<PinCodeWidget>, String)) {
callback = callback = settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
} }
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
@ -150,8 +154,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>( builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) => param1: (BuildContext context, WalletType type) =>
Navigator.of(context) Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
.pushNamed(Routes.restoreWallet, arguments: type),
param2: false)); param2: false));
case Routes.restoreOptions: case Routes.restoreOptions:
@ -166,66 +169,62 @@ Route<dynamic> createRoute(RouteSettings settings) {
if (isNewInstall) { if (isNewInstall) {
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>( builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) { param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (isSingleCoin) { if (isSingleCoin) {
return Navigator.of(context.context) return Navigator.of(context.context)
.pushNamed(Routes.restoreWallet, arguments: availableWalletTypes.first); .pushNamed(Routes.restoreWallet, arguments: availableWalletTypes.first);
} }
return Navigator.pushNamed( return Navigator.pushNamed(context.context, Routes.restoreWalletType);
context.context, Routes.restoreWalletType);
}), }),
fullscreenDialog: true); fullscreenDialog: true);
} else if (isSingleCoin) { } else if (isSingleCoin) {
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>( builder: (_) => getIt.get<WalletRestorePage>(param1: availableWalletTypes.first));
param1: availableWalletTypes.first
));
} else { } else {
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>( builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) => param1: (BuildContext context, WalletType type) =>
Navigator.of(context) Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
.pushNamed(Routes.restoreWallet, arguments: type),
param2: false)); param2: false));
} }
case Routes.seed: case Routes.seed:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => builder: (_) => getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
case Routes.restoreWallet: case Routes.restoreWallet:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>( builder: (_) => getIt.get<WalletRestorePage>(param1: settings.arguments as WalletType));
param1: settings.arguments as WalletType));
case Routes.restoreWalletChooseDerivation:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
param1: settings.arguments as List<DerivationInfo>));
case Routes.sweepingWalletPage: case Routes.sweepingWalletPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
builder: (_) => getIt.get<SweepingWalletPage>());
case Routes.dashboard: case Routes.dashboard:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
settings: settings, settings: settings, builder: (_) => getIt.get<DashboardPage>());
builder: (_) => getIt.get<DashboardPage>());
case Routes.send: case Routes.send:
final initialPaymentRequest = settings.arguments as PaymentRequest?; final initialPaymentRequest = settings.arguments as PaymentRequest?;
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SendPage>( fullscreenDialog: true,
param1: initialPaymentRequest, builder: (_) => getIt.get<SendPage>(
)); param1: initialPaymentRequest,
));
case Routes.sendTemplate: case Routes.sendTemplate:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
builder: (_) => getIt.get<SendTemplatePage>());
case Routes.receive: case Routes.receive:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<ReceivePage>());
builder: (_) => getIt.get<ReceivePage>());
case Routes.addressPage: case Routes.addressPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
@ -234,20 +233,21 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.transactionDetails: case Routes.transactionDetails:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => getIt.get<TransactionDetailsPage>( builder: (_) =>
param1: settings.arguments as TransactionInfo)); getIt.get<TransactionDetailsPage>(param1: settings.arguments as TransactionInfo));
case Routes.newSubaddress: case Routes.newSubaddress:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
case Routes.disclaimer: case Routes.disclaimer:
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage()); return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
case Routes.readDisclaimer: case Routes.readDisclaimer:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
builder: (_) => DisclaimerPage(isReadOnly: true));
case Routes.changeRep:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
case Routes.seedLanguage: case Routes.seedLanguage:
final args = settings.arguments as List<dynamic>; final args = settings.arguments as List<dynamic>;
@ -256,8 +256,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(builder: (_) { return CupertinoPageRoute<void>(builder: (_) {
return SeedLanguage( return SeedLanguage(
onConfirm: (context, lang) => Navigator.of(context) onConfirm: (context, lang) =>
.popAndPushNamed(redirectRoute, arguments: [type, lang])); Navigator.of(context).popAndPushNamed(redirectRoute, arguments: [type, lang]));
}); });
case Routes.walletList: case Routes.walletList:
@ -267,15 +267,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletEdit: case Routes.walletEdit:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => getIt.get<WalletEditPage>( builder: (_) => getIt.get<WalletEditPage>(param1: settings.arguments as List<dynamic>));
param1: settings.arguments as List<dynamic>));
case Routes.auth: case Routes.auth:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => getIt.get<AuthPage>( builder: (_) => getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished, param1: settings.arguments as OnAuthenticationFinished, param2: true));
param2: true));
case Routes.totpAuthCodePage: case Routes.totpAuthCodePage:
final args = settings.arguments as TotpAuthArgumentsModel; final args = settings.arguments as TotpAuthArgumentsModel;
@ -290,9 +288,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (context) => WillPopScope( builder: (context) => WillPopScope(
child: getIt.get<AuthPage>(instanceName: 'login'), child: getIt.get<AuthPage>(instanceName: 'login'),
onWillPop: () async => onWillPop: () async =>
// FIX-ME: Additional check does it works correctly // FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ?? (await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
false), false),
), ),
fullscreenDialog: true); fullscreenDialog: true);
@ -302,53 +300,54 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => WillPopScope( builder: (_) => WillPopScope(
child: getIt.get<AuthPage>( child: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished, param1: settings.arguments as OnAuthenticationFinished, param2: false),
param2: false),
onWillPop: () async => false)); onWillPop: () async => false));
case Routes.connectionSync: case Routes.connectionSync:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
builder: (_) => getIt.get<ConnectionSyncPage>());
case Routes.securityBackupPage: case Routes.securityBackupPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<SecurityBackupPage>());
builder: (_) => getIt.get<SecurityBackupPage>());
case Routes.privacyPage: case Routes.privacyPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<PrivacyPage>());
builder: (_) => getIt.get<PrivacyPage>());
case Routes.displaySettingsPage: case Routes.displaySettingsPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<DisplaySettingsPage>());
builder: (_) => getIt.get<DisplaySettingsPage>());
case Routes.otherSettingsPage: case Routes.otherSettingsPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<OtherSettingsPage>());
builder: (_) => getIt.get<OtherSettingsPage>());
case Routes.newNode: case Routes.newNode:
final args = settings.arguments as Map<String, dynamic>?; final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NodeCreateOrEditPage>( builder: (_) => getIt.get<NodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?, param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
param2: args?['isSelected'] as bool?));
case Routes.newPowNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<PowNodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
case Routes.accountCreation: case Routes.accountCreation:
return CupertinoPageRoute<String>( return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>( builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem?)); param1: settings.arguments as AccountListItem?));
case Routes.nanoAccountCreation:
return CupertinoPageRoute<String>(
builder: (_) =>
getIt.get<NanoAccountEditOrCreatePage>(param1: settings.arguments as NanoAccount?));
case Routes.addressBook: case Routes.addressBook:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<ContactListPage>());
builder: (_) => getIt.get<ContactListPage>());
case Routes.pickerAddressBook: case Routes.pickerAddressBook:
final selectedCurrency = settings.arguments as CryptoCurrency?; final selectedCurrency = settings.arguments as CryptoCurrency?;
@ -357,31 +356,26 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.addressBookAddContact: case Routes.addressBookAddContact:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ContactPage>( builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
param1: settings.arguments as ContactRecord?));
case Routes.showKeys: case Routes.showKeys:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletKeysPage>(), fullscreenDialog: true); builder: (_) => getIt.get<WalletKeysPage>(), fullscreenDialog: true);
case Routes.exchangeTrade: case Routes.exchangeTrade:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTradePage>());
builder: (_) => getIt.get<ExchangeTradePage>());
case Routes.exchangeConfirm: case Routes.exchangeConfirm:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeConfirmPage>());
builder: (_) => getIt.get<ExchangeConfirmPage>());
case Routes.tradeDetails: case Routes.tradeDetails:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => builder: (_) => getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.orderDetails: case Routes.orderDetails:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
case Routes.buy: case Routes.buy:
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>()); return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>());
@ -390,18 +384,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
final args = settings.arguments as List; final args = settings.arguments as List;
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
builder: (_) =>
getIt.get<BuyWebViewPage>(param1: args));
case Routes.exchange: case Routes.exchange:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
builder: (_) => getIt.get<ExchangePage>());
case Routes.exchangeTemplate: case Routes.exchangeTemplate:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
builder: (_) => getIt.get<ExchangeTemplatePage>());
case Routes.rescan: case Routes.rescan:
return MaterialPageRoute<void>(builder: (_) => getIt.get<RescanPage>()); return MaterialPageRoute<void>(builder: (_) => getIt.get<RescanPage>());
@ -411,51 +401,41 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.preSeed: case Routes.preSeed:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
case Routes.backup: case Routes.backup:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>()); fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
case Routes.editBackupPassword: case Routes.editBackupPassword:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<EditBackupPasswordPage>());
builder: (_) => getIt.get<EditBackupPasswordPage>());
case Routes.restoreFromBackup: case Routes.restoreFromBackup:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<RestoreFromBackupPage>());
builder: (_) => getIt.get<RestoreFromBackupPage>());
case Routes.support: case Routes.support:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportPage>());
builder: (_) => getIt.get<SupportPage>());
case Routes.supportLiveChat: case Routes.supportLiveChat:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportChatPage>());
builder: (_) => getIt.get<SupportChatPage>());
case Routes.supportOtherLinks: case Routes.supportOtherLinks:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
builder: (_) => getIt.get<SupportOtherLinksPage>());
case Routes.unspentCoinsList: case Routes.unspentCoinsList:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>());
builder: (_) => getIt.get<UnspentCoinsListPage>());
case Routes.unspentCoinsDetails: case Routes.unspentCoinsDetails:
final args = settings.arguments as List; final args = settings.arguments as List;
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => builder: (_) => getIt.get<UnspentCoinsDetailsPage>(param1: args));
getIt.get<UnspentCoinsDetailsPage>(
param1: args));
case Routes.fullscreenQR: case Routes.fullscreenQR:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => builder: (_) => getIt.get<FullscreenQRPage>(
getIt.get<FullscreenQRPage>(
param1: settings.arguments as QrViewData, param1: settings.arguments as QrViewData,
)); ));
@ -466,26 +446,27 @@ Route<dynamic> createRoute(RouteSettings settings) {
); );
case Routes.ioniaLoginPage: case Routes.ioniaLoginPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>()); return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaLoginPage>());
case Routes.ioniaCreateAccountPage: case Routes.ioniaCreateAccountPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>()); return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCreateAccountPage>());
case Routes.ioniaManageCardsPage: case Routes.ioniaManageCardsPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
builder: (_) => getIt.get<IoniaManageCardsPage>());
case Routes.ioniaBuyGiftCardPage: case Routes.ioniaBuyGiftCardPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args)); return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
case Routes.ioniaBuyGiftCardDetailPage: case Routes.ioniaBuyGiftCardDetailPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args)); return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
case Routes.ioniaVerifyIoniaOtpPage: case Routes.ioniaVerifyIoniaOtpPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaVerifyIoniaOtp>(param1: args)); return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaVerifyIoniaOtp>(param1: args));
case Routes.ioniaDebitCardPage: case Routes.ioniaDebitCardPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>()); return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
@ -501,57 +482,60 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.ioniaCustomTipPage: case Routes.ioniaCustomTipPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaCustomTipPage>(param1: args)); return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomTipPage>(param1: args));
case Routes.ioniaGiftCardDetailPage: case Routes.ioniaGiftCardDetailPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first)); return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
case Routes.ioniaCustomRedeemPage: case Routes.ioniaCustomRedeemPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args)); return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
case Routes.ioniaMoreOptionsPage: case Routes.ioniaMoreOptionsPage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args)); return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
case Routes.ioniaPaymentStatusPage: case Routes.ioniaPaymentStatusPage:
final args = settings.arguments as List; final args = settings.arguments as List;
final paymentInfo = args.first as IoniaAnyPayPaymentInfo; final paymentInfo = args.first as IoniaAnyPayPaymentInfo;
final commitedInfo = args[1] as AnyPayPaymentCommittedInfo; final commitedInfo = args[1] as AnyPayPaymentCommittedInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaPaymentStatusPage>( return CupertinoPageRoute<void>(
param1: paymentInfo, builder: (_) =>
param2: commitedInfo)); getIt.get<IoniaPaymentStatusPage>(param1: paymentInfo, param2: commitedInfo));
case Routes.webViewPage: case Routes.webViewPage:
final args = settings.arguments as List; final args = settings.arguments as List;
final title = args.first as String; final title = args.first as String;
final url = args[1] as Uri; final url = args[1] as Uri;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<WebViewPage>( return CupertinoPageRoute<void>(
param1: title, builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
param2: url));
case Routes.advancedPrivacySettings: case Routes.advancedPrivacySettings:
final type = settings.arguments as WalletType; final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
builder: (_) => AdvancedPrivacySettingsPage( builder: (_) => AdvancedPrivacySettingsPage(
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type), getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type), getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
)); ));
case Routes.anonPayInvoicePage: case Routes.anonPayInvoicePage:
final args = settings.arguments as List; final args = settings.arguments as List;
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage: case Routes.anonPayReceivePage:
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase; final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData)); return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
case Routes.anonPayDetailsPage: case Routes.anonPayDetailsPage:
final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo; final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData)); return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
case Routes.desktop_actions: case Routes.desktop_actions:
return PageRouteBuilder( return PageRouteBuilder(
@ -560,12 +544,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
); );
case Routes.desktop_settings_page: case Routes.desktop_settings_page:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(builder: (_) => DesktopSettingsPage());
builder: (_) => DesktopSettingsPage());
case Routes.empty_no_route: case Routes.empty_no_route:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(builder: (_) => SizedBox.shrink());
builder: (_) => SizedBox.shrink());
case Routes.transactionsPage: case Routes.transactionsPage:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
@ -602,12 +584,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
); );
case Routes.manageNodes: case Routes.manageNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>()); return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: false));
case Routes.managePowNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: true));
default: default:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => Scaffold( builder: (_) => Scaffold(
body: Center( body: Center(child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
} }
} }

View file

@ -6,6 +6,7 @@ class Routes {
static const seed = '/seed'; static const seed = '/seed';
static const restoreOptions = '/restore_options'; static const restoreOptions = '/restore_options';
static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys'; static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys';
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
static const dashboard = '/dashboard'; static const dashboard = '/dashboard';
static const send = '/send'; static const send = '/send';
static const transactionDetails = '/transaction_info'; static const transactionDetails = '/transaction_info';
@ -14,13 +15,16 @@ class Routes {
static const walletEdit = '/walletEdit'; static const walletEdit = '/walletEdit';
static const disclaimer = '/disclaimer'; static const disclaimer = '/disclaimer';
static const readDisclaimer = '/read_disclaimer'; static const readDisclaimer = '/read_disclaimer';
static const changeRep = '/change_representative';
static const seedLanguage = '/seed_language'; static const seedLanguage = '/seed_language';
static const walletList = '/view_model.wallet_list'; static const walletList = '/view_model.wallet_list';
static const auth = '/auth'; static const auth = '/auth';
static const newNode = '/new_node_list'; static const newNode = '/new_node_list';
static const newPowNode = '/new_pow_node_list';
static const login = '/login'; static const login = '/login';
static const splash = '/splash'; static const splash = '/splash';
static const accountCreation = '/account_new'; static const accountCreation = '/account_new';
static const nanoAccountCreation = '/nano_account_new';
static const addressBook = '/address_book'; static const addressBook = '/address_book';
static const pickerAddressBook = '/picker_address_book'; static const pickerAddressBook = '/picker_address_book';
static const addressBookAddContact = '/address_book_add_contact'; static const addressBookAddContact = '/address_book_add_contact';
@ -92,4 +96,6 @@ class Routes {
static const homeSettings = '/home_settings'; static const homeSettings = '/home_settings';
static const editToken = '/edit_token'; static const editToken = '/edit_token';
static const manageNodes = '/manage_nodes'; static const manageNodes = '/manage_nodes';
static const managePowNodes = '/manage_pow_nodes';
} }

View file

@ -295,29 +295,6 @@ class _DashboardPageView extends BasePage {
); );
_isEffectsInstalled = true; _isEffectsInstalled = true;
autorun(
(_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_description,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
},
);
_showReleaseNotesPopup(context); _showReleaseNotesPopup(context);
var needToPresentYat = false; var needToPresentYat = false;

View file

@ -78,23 +78,6 @@ class DesktopDashboardPage extends StatelessWidget {
} }
_isEffectsInstalled = true; _isEffectsInstalled = true;
autorun((_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_description,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop());
});
});
var needToPresentYat = false; var needToPresentYat = false;
var isInactive = false; var isInactive = false;

View file

@ -33,6 +33,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
Image _newWalletImage(BuildContext context) => Image.asset( Image _newWalletImage(BuildContext context) => Image.asset(
@ -141,6 +143,10 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return havenIcon; return havenIcon;
case WalletType.ethereum: case WalletType.ethereum:
return ethereumIcon; return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default: default:
return nonWalletTypeIcon; return nonWalletTypeIcon;
} }

View file

@ -120,31 +120,6 @@ class AddressPage extends BasePage {
Widget body(BuildContext context) { Widget body(BuildContext context) {
_setEffects(context); _setEffects(context);
autorun((_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet ||
!dashboardViewModel.settingsStore.shouldShowReceiveWarning) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
leftButtonText: S.of(context).understand,
actionLeftButton: () => Navigator.of(context).pop(),
rightButtonText: S.of(context).do_not_show_me,
actionRightButton: () {
dashboardViewModel.settingsStore.setShouldShowReceiveWarning(false);
Navigator.of(context).pop();
});
});
}
});
return KeyboardActions( return KeyboardActions(
autoScroll: false, autoScroll: false,
disableScroll: true, disableScroll: true,

View file

@ -18,18 +18,21 @@ class MenuWidget extends StatefulWidget {
class MenuWidgetState extends State<MenuWidget> { class MenuWidgetState extends State<MenuWidget> {
MenuWidgetState() MenuWidgetState()
: this.menuWidth = 0, : this.menuWidth = 0,
this.screenWidth = 0, this.screenWidth = 0,
this.screenHeight = 0, this.screenHeight = 0,
this.headerHeight = 120, this.headerHeight = 120,
this.tileHeight = 60, this.tileHeight = 60,
this.fromTopEdge = 50, this.fromTopEdge = 50,
this.fromBottomEdge = 25, this.fromBottomEdge = 25,
this.moneroIcon = Image.asset('assets/images/monero_menu.png'), this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'), this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'), this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
this.havenIcon = Image.asset('assets/images/haven_menu.png'), this.havenIcon = Image.asset('assets/images/haven_menu.png'),
this.ethereumIcon = Image.asset('assets/images/eth_icon.png'); this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
this.bananoIcon = Image.asset('assets/images/nano_icon.png');
final largeScreen = 731; final largeScreen = 731;
@ -47,6 +50,9 @@ class MenuWidgetState extends State<MenuWidget> {
Image litecoinIcon; Image litecoinIcon;
Image havenIcon; Image havenIcon;
Image ethereumIcon; Image ethereumIcon;
Image nanoIcon;
Image bananoIcon;
@override @override
void initState() { void initState() {
@ -206,6 +212,10 @@ class MenuWidgetState extends State<MenuWidget> {
return havenIcon; return havenIcon;
case WalletType.ethereum: case WalletType.ethereum:
return ethereumIcon; return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default: default:
throw Exception('No icon for ${type.toString()}'); throw Exception('No icon for ${type.toString()}');
} }

View file

@ -0,0 +1,103 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_nano/nano_wallet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class NanoChangeRepPage extends BasePage {
NanoChangeRepPage(WalletBase wallet)
: _wallet = wallet,
_addressController = TextEditingController() {
_addressController.text = (wallet as NanoWallet).representative;
}
final TextEditingController _addressController;
final WalletBase _wallet;
@override
String get title => S.current.change_rep;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _addressController,
hintText: S.of(context).node_address,
validator: AddressValidator(type: CryptoCurrency.nano),
),
)
],
),
],
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_rep,
alertContent: S.of(context).change_rep_message,
rightButtonText: S.of(context).change,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
try {
await nano!.changeRep(_wallet, _addressController.text);
Navigator.of(context).pop();
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
throw e;
}
}
},
text: S.of(context).change,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
)),
],
)),
));
}
}

View file

@ -0,0 +1,66 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/monero_account_label_validator.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class NanoAccountEditOrCreatePage extends BasePage {
NanoAccountEditOrCreatePage({required this.nanoAccountCreationViewModel})
: _formKey = GlobalKey<FormState>(),
_textController = TextEditingController() {
_textController.addListener(() => nanoAccountCreationViewModel.label = _textController.text);
_textController.text = nanoAccountCreationViewModel.label;
}
final NanoAccountEditOrCreateViewModel nanoAccountCreationViewModel;
@override
String get title => S.current.account;
final GlobalKey<FormState> _formKey;
final TextEditingController _textController;
@override
Widget body(BuildContext context) => Form(
key: _formKey,
child: Container(
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _textController,
hintText: S.of(context).account,
validator: MoneroLabelValidator(),
))),
Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await nanoAccountCreationViewModel.save();
Navigator.of(context).pop(_textController.text);
},
text: nanoAccountCreationViewModel.isEdit
? S.of(context).rename
: S.of(context).add,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: nanoAccountCreationViewModel.state is IsExecutingState,
isDisabled: nanoAccountCreationViewModel.label?.isEmpty ?? true,
))
],
),
),
);
}

View file

@ -0,0 +1,92 @@
import 'package:cake_wallet/src/widgets/picker_inner_wrapper_widget.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/monero_accounts/widgets/account_tile.dart';
class NanoAccountListPage extends StatelessWidget {
NanoAccountListPage({required this.accountListViewModel});
final NanoAccountListViewModel accountListViewModel;
final ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
double itemHeight = 80;
double buttonHeight = 62;
return Observer(builder: (_) {
final accounts = accountListViewModel.accounts;
return PickerInnerWrapperWidget(
title: S.of(context).choose_account,
itemsHeight: (itemHeight * accounts.length) + buttonHeight,
children: [
Expanded(
child: Scrollbar(
controller: controller,
child: ListView.separated(
padding: EdgeInsets.zero,
controller: controller,
separatorBuilder: (context, index) => const VerticalSectionDivider(),
itemCount: accounts.length,
itemBuilder: (context, index) {
final account = accounts[index];
return AccountTile(
isCurrent: account.isSelected,
accountName: account.label,
accountBalance: account.balance ?? '0.00',
currency: accountListViewModel.currency.toString(),
onTap: () {
if (account.isSelected) {
return;
}
accountListViewModel.select(account);
Navigator.of(context).pop();
},
onEdit: () async => await Navigator.of(context)
.pushNamed(Routes.nanoAccountCreation, arguments: account));
},
),
)),
GestureDetector(
onTap: () async => await Navigator.of(context).pushNamed(Routes.nanoAccountCreation),
child: Container(
height: buttonHeight,
color: Theme.of(context).cardColor,
padding: EdgeInsets.symmetric(horizontal: 24),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.add,
color: Colors.white,
),
Padding(
padding: EdgeInsets.only(left: 5),
child: Text(
S.of(context).create_new_account,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Colors.white,
decoration: TextDecoration.none,
),
),
)
],
),
),
),
)
],
);
});
}
}

View file

@ -0,0 +1,96 @@
import 'package:cake_wallet/themes/extensions/account_list_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/generated/i18n.dart';
class AccountTile extends StatelessWidget {
AccountTile(
{required this.isCurrent,
required this.accountName,
this.accountBalance,
required this.currency,
required this.onTap,
required this.onEdit});
final bool isCurrent;
final String accountName;
final String? accountBalance;
final String currency;
final Function() onTap;
final Function() onEdit;
@override
Widget build(BuildContext context) {
final color = isCurrent
? Theme.of(context).extension<AccountListTheme>()!.currentAccountBackgroundColor
: Theme.of(context).extension<AccountListTheme>()!.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context).extension<AccountListTheme>()!.currentAccountTextColor
: Theme.of(context).extension<AccountListTheme>()!.tilesTextColor;
final Widget cell = GestureDetector(
onTap: onTap,
child: Container(
height: 77,
width: double.infinity,
padding: EdgeInsets.only(left: 24, right: 24),
color: color,
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceBetween,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(
child: Text(
accountName,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: textColor,
decoration: TextDecoration.none,
),
),
),
if (accountBalance != null)
Container(
child: Text(
'${accountBalance.toString()} $currency',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).textTheme.headlineMedium!.color!,
decoration: TextDecoration.none,
),
),
),
],
),
),
);
// return cell;
return Slidable(
key: Key(accountName),
child: cell,
endActionPane: _actionPane(context)
);
}
ActionPane _actionPane(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
children: [
SlidableAction(
onPressed: (_) => onEdit.call(),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
icon: Icons.edit,
label: S.of(context).edit,
),
],
);
}

View file

@ -0,0 +1,196 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class PowNodeCreateOrEditPage extends BasePage {
PowNodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
reaction((_) => nodeCreateOrEditViewModel.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => nodeCreateOrEditViewModel.port, (String port) {
if (port != _portController.text) {
_portController.text = port;
}
});
if (nodeCreateOrEditViewModel.hasAuthCredentials) {
reaction((_) => nodeCreateOrEditViewModel.login, (String login) {
if (login != _loginController.text) {
_loginController.text = login;
}
});
reaction((_) => nodeCreateOrEditViewModel.password, (String password) {
if (password != _passwordController.text) {
_passwordController.text = password;
}
});
}
_addressController.addListener(
() => nodeCreateOrEditViewModel.address = _addressController.text);
_portController.addListener(
() => nodeCreateOrEditViewModel.port = _portController.text);
_loginController.addListener(
() => nodeCreateOrEditViewModel.login = _loginController.text);
_passwordController.addListener(
() => nodeCreateOrEditViewModel.password = _passwordController.text);
}
final GlobalKey<FormState> _formKey;
final TextEditingController _addressController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
@override
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
@override
Widget trailing(BuildContext context) => IconButton(
onPressed: () async {
await nodeCreateOrEditViewModel.scanQRCodeForNewNode();
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
icon: Image.asset(
'assets/images/qr_code_icon.png',
),
);
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
final Node? editingNode;
final bool? isSelected;
@override
Widget body(BuildContext context) {
reaction((_) => nodeCreateOrEditViewModel.connectionState,
(ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) =>
AlertWithOneAction(
alertTitle: S.of(context).new_node_testing,
alertContent: state.payload as bool
? S.of(context).node_connection_successful
: S.of(context).node_connection_failed,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()));
});
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
});
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: NodeForm(
formKey: _formKey,
nodeViewModel: nodeCreateOrEditViewModel,
editingNode: editingNode,
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle:
S.of(context).remove_node,
alertContent: S
.of(context)
.remove_node_message,
rightButtonText:
S.of(context).remove,
leftButtonText:
S.of(context).cancel,
actionRightButton: () =>
Navigator.pop(context, true),
actionLeftButton: () =>
Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
await editingNode!.delete();
Navigator.of(context).pop();
}
},
text: S.of(context).delete,
isDisabled: !nodeCreateOrEditViewModel.isReady ||
(isSelected ?? false),
color: Palette.red,
textColor: Colors.white),
)),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: PrimaryButton(
onPressed: () async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
await nodeCreateOrEditViewModel.save(
editingNode: editingNode, saveAsCurrent: isSelected ?? false);
Navigator.of(context).pop();
},
text: S.of(context).save,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isDisabled: (!nodeCreateOrEditViewModel.isReady)||
(nodeCreateOrEditViewModel
.connectionState is IsExecutingState),
),
)),
],
)),
));
}
}

View file

@ -11,10 +11,12 @@ class NodeListRow extends StandardListRow {
{required String title, {required String title,
required this.node, required this.node,
required void Function(BuildContext context) onTap, required void Function(BuildContext context) onTap,
required bool isSelected}) required bool isSelected,
required this.isPow})
: super(title: title, onTap: onTap, isSelected: isSelected); : super(title: title, onTap: onTap, isSelected: isSelected);
final Node node; final Node node;
final bool isPow;
@override @override
Widget buildLeading(BuildContext context) { Widget buildLeading(BuildContext context) {
@ -33,7 +35,7 @@ class NodeListRow extends StandardListRow {
@override @override
Widget buildTrailing(BuildContext context) { Widget buildTrailing(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.newNode, onTap: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode,
arguments: {'editingNode': node, 'isSelected': isSelected}), arguments: {'editingNode': node, 'isSelected': isSelected}),
child: Container( child: Container(
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),

View file

@ -1,7 +1,9 @@
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/share_util.dart';
@ -22,7 +24,6 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
class ReceivePage extends BasePage { class ReceivePage extends BasePage {
ReceivePage({required this.addressListViewModel}) ReceivePage({required this.addressListViewModel})
@ -99,7 +100,9 @@ class ReceivePage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
return (addressListViewModel.type == WalletType.monero || return (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven) addressListViewModel.type == WalletType.haven ||
addressListViewModel.type == WalletType.nano ||
addressListViewModel.type == WalletType.banano)
? KeyboardActions( ? KeyboardActions(
config: KeyboardActionsConfig( config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS, keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
@ -137,9 +140,18 @@ class ReceivePage extends BasePage {
if (item is WalletAccountListHeader) { if (item is WalletAccountListHeader) {
cell = HeaderTile( cell = HeaderTile(
onTap: () async => await showPopUp<void>( onTap: () async {
context: context, if (addressListViewModel.type == WalletType.monero ||
builder: (_) => getIt.get<MoneroAccountListPage>()), addressListViewModel.type == WalletType.haven) {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>());
} else {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<NanoAccountListPage>());
}
},
title: S.of(context).accounts, title: S.of(context).accounts,
icon: Icon( icon: Icon(
Icons.arrow_forward_ios, Icons.arrow_forward_ios,

View file

@ -0,0 +1,129 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class WalletRestoreChooseDerivationPage extends BasePage {
WalletRestoreChooseDerivationPage(this.walletRestoreChooseDerivationViewModel) {}
@override
Widget middle(BuildContext context) => Observer(
builder: (_) => Text(
S.current.choose_derivation,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: titleColor(context)),
));
final WalletRestoreChooseDerivationViewModel walletRestoreChooseDerivationViewModel;
DerivationType derivationType = DerivationType.unknown;
@override
Widget body(BuildContext context) {
return Observer(
builder: (_) => FutureBuilder<List<DerivationInfo>>(
future: walletRestoreChooseDerivationViewModel.derivations,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('Error! No derivations available!'));
} else {
return ListView.separated(
shrinkWrap: true,
separatorBuilder: (_, __) => SizedBox(),
itemCount: snapshot.data!.length,
itemBuilder: (__, index) {
final derivation = snapshot.data![index];
return Card(
margin: const EdgeInsets.all(8),
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
child: InkWell(
borderRadius: BorderRadius.circular(15),
onTap: () async {
Navigator.pop(context, derivation);
},
child: ListTile(
contentPadding: EdgeInsets.all(16),
title: Center(
child: Text(
"${derivation.description ?? derivation.derivationType.toString().split('.').last}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 18,
fontWeight: FontWeight.w800,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (derivation.derivationPath != null)
Text(
derivation.derivationPath!,
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
Text(
derivation.address,
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
Text(
"${S.current.confirmed}: ${derivation.balance}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
Text(
"${S.current.transactions}: ${derivation.height}",
style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
],
),
),
),
);
},
);
}
},
),
);
}
}

View file

@ -13,6 +13,7 @@ import 'package:flutter/services.dart';
class WalletRestoreFromKeysFrom extends StatefulWidget { class WalletRestoreFromKeysFrom extends StatefulWidget {
WalletRestoreFromKeysFrom({ WalletRestoreFromKeysFrom({
required this.walletRestoreViewModel, required this.walletRestoreViewModel,
required this.onPrivateKeyChange,
required this.displayPrivateKeyField, required this.displayPrivateKeyField,
required this.onHeightOrDateEntered, required this.onHeightOrDateEntered,
Key? key, Key? key,
@ -20,6 +21,7 @@ class WalletRestoreFromKeysFrom extends StatefulWidget {
final Function(bool) onHeightOrDateEntered; final Function(bool) onHeightOrDateEntered;
final WalletRestoreViewModel walletRestoreViewModel; final WalletRestoreViewModel walletRestoreViewModel;
final void Function(String)? onPrivateKeyChange;
final bool displayPrivateKeyField; final bool displayPrivateKeyField;
@override @override
@ -54,6 +56,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
if (privateKeyController.text.isNotEmpty) { if (privateKeyController.text.isNotEmpty) {
widget.onHeightOrDateEntered(true); widget.onHeightOrDateEntered(true);
} }
widget.onPrivateKeyChange?.call(privateKeyController.text);
}); });
} }
@ -62,8 +65,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
nameController.dispose(); nameController.dispose();
addressController.dispose(); addressController.dispose();
viewKeyController.dispose(); viewKeyController.dispose();
spendKeyController.dispose();
privateKeyController.dispose(); privateKeyController.dispose();
spendKeyController.dispose();
super.dispose(); super.dispose();
} }

View file

@ -1,6 +1,12 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:keyboard_actions/keyboard_actions.dart';
@ -69,6 +75,12 @@ class WalletRestorePage extends BasePage {
_pages.add(WalletRestoreFromKeysFrom( _pages.add(WalletRestoreFromKeysFrom(
key: walletRestoreFromKeysFormKey, key: walletRestoreFromKeysFormKey,
walletRestoreViewModel: walletRestoreViewModel, walletRestoreViewModel: walletRestoreViewModel,
onPrivateKeyChange: (String seed) {
if (walletRestoreViewModel.type == WalletType.nano ||
walletRestoreViewModel.type == WalletType.banano) {
walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
}
},
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey, displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
break; break;
@ -97,6 +109,8 @@ class WalletRestorePage extends BasePage {
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey; final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey; final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey;
final FocusNode _blockHeightFocusNode; final FocusNode _blockHeightFocusNode;
DerivationType derivationType = DerivationType.unknown;
String? derivationPath = null;
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
@ -185,7 +199,9 @@ class WalletRestorePage extends BasePage {
child: Observer( child: Observer(
builder: (context) { builder: (context) {
return LoadingPrimaryButton( return LoadingPrimaryButton(
onPressed: _confirmForm, onPressed: () async {
await _confirmForm(context);
},
text: S.of(context).restore_recover, text: S.of(context).restore_recover,
color: Theme.of(context) color: Theme.of(context)
.extension<WalletListTheme>()! .extension<WalletListTheme>()!
@ -217,18 +233,34 @@ class WalletRestorePage extends BasePage {
return false; return false;
} }
if ((walletRestoreViewModel.type == WalletType.bitcoin || if ((walletRestoreViewModel.type == WalletType.litecoin) &&
walletRestoreViewModel.type == WalletType.litecoin) &&
(seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength && (seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength &&
seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) { seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) {
return false; return false;
} }
// bip39:
const validSeedLengths = [12, 18, 24];
if (walletRestoreViewModel.type == WalletType.bitcoin &&
!(validSeedLengths.contains(seedWords.length))) {
return false;
}
final words = final words =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet(); walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
return seedWords.toSet().difference(words).toSet().isEmpty; return seedWords.toSet().difference(words).toSet().isEmpty;
} }
bool _isValidSeedKey() {
final seedKey = walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
if (seedKey.length != 64 && seedKey.length != 128) {
return false;
}
return true;
}
Map<String, dynamic> _credentials() { Map<String, dynamic> _credentials() {
final credentials = <String, dynamic>{}; final credentials = <String, dynamic>{};
@ -243,10 +275,12 @@ class WalletRestorePage extends BasePage {
credentials['name'] = credentials['name'] =
walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text; walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
} else { } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
if (walletRestoreViewModel.hasRestoreFromPrivateKey) { if (walletRestoreViewModel.hasRestoreFromPrivateKey) {
credentials['private_key'] = credentials['private_key'] =
walletRestoreFromKeysFormKey.currentState!.privateKeyController.text; walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} else { } else {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
@ -254,31 +288,81 @@ class WalletRestorePage extends BasePage {
walletRestoreFromKeysFormKey.currentState!.spendKeyController.text; walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
credentials['height'] = credentials['height'] =
walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height; walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} }
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} }
credentials['derivationType'] = this.derivationType;
credentials['derivationPath'] = this.derivationPath;
credentials['walletType'] = walletRestoreViewModel.type;
return credentials; return credentials;
} }
void _confirmForm() { Future<List<DerivationInfo>> getDerivationInfo(dynamic credentials) async {
var list = <DerivationInfo>[];
var walletType = credentials["walletType"] as WalletType;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (walletType) {
case WalletType.nano:
String? mnemonic = credentials['seed'] as String?;
String? seedKey = credentials['private_key'] as String?;
AccountInfoResponse? bip39Info = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.bip39,
mnemonic: mnemonic,
seedKey: seedKey,
node: node);
AccountInfoResponse? standardInfo = await nanoUtil!.getInfoFromSeedOrMnemonic(
DerivationType.nano,
mnemonic: mnemonic,
seedKey: seedKey,
node: node,
);
if (standardInfo?.balance != null) {
list.add(DerivationInfo(
derivationType: DerivationType.nano,
balance: nanoUtil!.getRawAsUsableString(standardInfo!.balance, nanoUtil!.rawPerNano),
address: standardInfo.address!,
height: standardInfo.confirmationHeight,
));
}
if (bip39Info?.balance != null) {
list.add(DerivationInfo(
derivationType: DerivationType.bip39,
balance: nanoUtil!.getRawAsUsableString(bip39Info!.balance, nanoUtil!.rawPerNano),
address: bip39Info.address!,
height: bip39Info.confirmationHeight,
));
}
break;
default:
break;
}
return list;
}
Future<void> _confirmForm(BuildContext context) async {
// Dismissing all visible keyboard to provide context for navigation // Dismissing all visible keyboard to provide context for navigation
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
final formContext = walletRestoreViewModel.mode == WalletRestoreMode.seed
? walletRestoreFromSeedFormKey.currentContext
: walletRestoreFromKeysFormKey.currentContext;
final formKey = walletRestoreViewModel.mode == WalletRestoreMode.seed late BuildContext? formContext;
? walletRestoreFromSeedFormKey.currentState!.formKey late GlobalKey<FormState>? formKey;
: walletRestoreFromKeysFormKey.currentState!.formKey; late String name;
if (walletRestoreViewModel.mode == WalletRestoreMode.seed) {
formContext = walletRestoreFromSeedFormKey.currentContext;
formKey = walletRestoreFromSeedFormKey.currentState!.formKey;
name = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text;
} else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
formContext = walletRestoreFromKeysFormKey.currentContext;
formKey = walletRestoreFromKeysFormKey.currentState!.formKey;
name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
}
final name = walletRestoreViewModel.mode == WalletRestoreMode.seed if (!formKey!.currentState!.validate()) {
? walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text
: walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
if (!formKey.currentState!.validate()) {
return; return;
} }
@ -287,6 +371,54 @@ class WalletRestorePage extends BasePage {
return; return;
} }
walletRestoreViewModel.state = IsExecutingState();
List<DerivationType> derivationTypes =
await walletRestoreViewModel.getDerivationTypes(_credentials());
if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 1) {
// push screen to choose the derivation type:
List<DerivationInfo> derivations = await getDerivationInfo(_credentials());
int derivationsWithHistory = 0;
int derivationWithHistoryIndex = 0;
for (int i = 0; i < derivations.length; i++) {
if (derivations[i].height > 0) {
derivationsWithHistory++;
derivationWithHistoryIndex = i;
}
}
DerivationInfo? derivationInfo;
if (derivationsWithHistory > 1) {
derivationInfo = await Navigator.of(context).pushNamed(Routes.restoreWalletChooseDerivation,
arguments: derivations) as DerivationInfo?;
} else if (derivationsWithHistory == 1) {
derivationInfo = derivations[derivationWithHistoryIndex];
} else if (derivationsWithHistory == 0) {
// default derivation:
derivationInfo = DerivationInfo(
derivationType: derivationTypes[0],
derivationPath: "m/0'/1",
height: 0,
);
}
if (derivationInfo == null) {
walletRestoreViewModel.state = InitialExecutionState();
return;
}
this.derivationType = derivationInfo.derivationType;
this.derivationPath = derivationInfo.derivationPath;
} else {
// electrum derivation:
this.derivationType = derivationTypes[0];
this.derivationPath = "m/0'/1";
}
walletRestoreViewModel.state = InitialExecutionState();
walletRestoreViewModel.create(options: _credentials()); walletRestoreViewModel.create(options: _credentials());
} }

View file

@ -8,26 +8,26 @@ import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
class ConfirmSendingAlert extends BaseAlertDialog { class ConfirmSendingAlert extends BaseAlertDialog {
ConfirmSendingAlert({ ConfirmSendingAlert(
required this.alertTitle, {required this.alertTitle,
this.paymentId, this.paymentId,
this.paymentIdValue, this.paymentIdValue,
required this.amount, required this.amount,
required this.amountValue, required this.amountValue,
required this.fiatAmountValue, required this.fiatAmountValue,
required this.fee, required this.fee,
required this.feeValue, required this.feeValue,
required this.feeFiatAmount, required this.feeFiatAmount,
required this.outputs, required this.outputs,
required this.leftButtonText, required this.leftButtonText,
required this.rightButtonText, required this.rightButtonText,
required this.actionLeftButton, required this.actionLeftButton,
required this.actionRightButton, required this.actionRightButton,
this.alertBarrierDismissible = true, this.alertBarrierDismissible = true,
this.alertLeftActionButtonTextColor, this.alertLeftActionButtonTextColor,
this.alertRightActionButtonTextColor, this.alertRightActionButtonTextColor,
this.alertLeftActionButtonColor, this.alertLeftActionButtonColor,
this.alertRightActionButtonColor}); this.alertRightActionButtonColor});
final String alertTitle; final String alertTitle;
final String? paymentId; final String? paymentId;
@ -92,21 +92,20 @@ class ConfirmSendingAlert extends BaseAlertDialog {
fee: fee, fee: fee,
feeValue: feeValue, feeValue: feeValue,
feeFiatAmount: feeFiatAmount, feeFiatAmount: feeFiatAmount,
outputs: outputs outputs: outputs);
);
} }
class ConfirmSendingAlertContent extends StatefulWidget { class ConfirmSendingAlertContent extends StatefulWidget {
ConfirmSendingAlertContent({ ConfirmSendingAlertContent(
this.paymentId, {this.paymentId,
this.paymentIdValue, this.paymentIdValue,
required this.amount, required this.amount,
required this.amountValue, required this.amountValue,
required this.fiatAmountValue, required this.fiatAmountValue,
required this.fee, required this.fee,
required this.feeValue, required this.feeValue,
required this.feeFiatAmount, required this.feeFiatAmount,
required this.outputs}); required this.outputs});
final String? paymentId; final String? paymentId;
final String? paymentIdValue; final String? paymentIdValue;
@ -120,29 +119,28 @@ class ConfirmSendingAlertContent extends StatefulWidget {
@override @override
ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState( ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState(
paymentId: paymentId, paymentId: paymentId,
paymentIdValue: paymentIdValue, paymentIdValue: paymentIdValue,
amount: amount, amount: amount,
amountValue: amountValue, amountValue: amountValue,
fiatAmountValue: fiatAmountValue, fiatAmountValue: fiatAmountValue,
fee: fee, fee: fee,
feeValue: feeValue, feeValue: feeValue,
feeFiatAmount: feeFiatAmount, feeFiatAmount: feeFiatAmount,
outputs: outputs outputs: outputs);
);
} }
class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> { class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> {
ConfirmSendingAlertContentState({ ConfirmSendingAlertContentState(
this.paymentId, {this.paymentId,
this.paymentIdValue, this.paymentIdValue,
required this.amount, required this.amount,
required this.amountValue, required this.amountValue,
required this.fiatAmountValue, required this.fiatAmountValue,
required this.fee, required this.fee,
required this.feeValue, required this.feeValue,
required this.feeFiatAmount, required this.feeFiatAmount,
required this.outputs}) required this.outputs})
: recipientTitle = '' { : recipientTitle = '' {
recipientTitle = outputs.length > 1 recipientTitle = outputs.length > 1
? S.current.transaction_details_recipient_address ? S.current.transaction_details_recipient_address
@ -170,8 +168,9 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
Widget build(BuildContext context) { Widget build(BuildContext context) {
controller.addListener(() { controller.addListener(() {
fromTop = controller.hasClients fromTop = controller.hasClients
? (controller.offset / controller.position.maxScrollExtent * ? (controller.offset /
(backgroundHeight - thumbHeight)) controller.position.maxScrollExtent *
(backgroundHeight - thumbHeight))
: 0; : 0;
setState(() {}); setState(() {});
}); });
@ -182,94 +181,92 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
}); });
}); });
return Stack( return Stack(alignment: Alignment.center, clipBehavior: Clip.none, children: [
alignment: Alignment.center, Container(
clipBehavior: Clip.none, height: 200,
children: [ child: SingleChildScrollView(
Container( controller: controller,
height: 200, child: Column(
child: SingleChildScrollView( children: <Widget>[
controller: controller, if (paymentIdValue != null && paymentId != null)
child: Column( Padding(
children: <Widget>[ padding: EdgeInsets.only(bottom: 32),
if (paymentIdValue != null && paymentId != null) child: Row(
Padding( mainAxisSize: MainAxisSize.max,
padding: EdgeInsets.only(bottom: 32), mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Row( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max, children: <Widget>[
mainAxisAlignment: MainAxisAlignment.spaceBetween, Text(
crossAxisAlignment: CrossAxisAlignment.start, paymentId!,
children: <Widget>[ style: TextStyle(
Text( fontSize: 16,
paymentId!, fontWeight: FontWeight.normal,
style: TextStyle( fontFamily: 'Lato',
fontSize: 16, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontWeight: FontWeight.normal, decoration: TextDecoration.none,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
), ),
Column( ),
crossAxisAlignment: CrossAxisAlignment.end, Column(
children: [ crossAxisAlignment: CrossAxisAlignment.end,
Text( children: [
paymentIdValue!, Text(
style: TextStyle( paymentIdValue!,
fontSize: 18, style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 18,
fontFamily: 'Lato', fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, fontFamily: 'Lato',
decoration: TextDecoration.none, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
), decoration: TextDecoration.none,
), ),
], ),
) ],
], )
],
),
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
amount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
), ),
), ),
Row( Column(
mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
crossAxisAlignment: CrossAxisAlignment.start, Text(
children: <Widget>[ amountValue,
Text( style: TextStyle(
amount, fontSize: 18,
style: TextStyle( fontWeight: FontWeight.w600,
fontSize: 16, fontFamily: 'Lato',
fontWeight: FontWeight.normal, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontFamily: 'Lato', decoration: TextDecoration.none,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
amountValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
), ),
Text( ),
fiatAmountValue, Text(
style: TextStyle( fiatAmountValue,
fontSize: 12, style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 12,
fontFamily: 'Lato', fontWeight: FontWeight.w600,
color: PaletteDark.pigeonBlue, fontFamily: 'Lato',
decoration: TextDecoration.none, color: PaletteDark.pigeonBlue,
), decoration: TextDecoration.none,
) ),
], )
) ],
], )
), ],
),
if (feeValue.isNotEmpty && feeValue != "0")
Padding( Padding(
padding: EdgeInsets.only(top: 16), padding: EdgeInsets.only(top: 16),
child: Row( child: Row(
@ -313,103 +310,97 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
], ],
) )
], ],
) )),
), Padding(
Padding( padding: EdgeInsets.only(top: 16),
padding: EdgeInsets.only(top: 16), child: Column(
child: Column( children: [
children: [ Text(
Text( '$recipientTitle:',
'$recipientTitle:', style: TextStyle(
style: TextStyle( fontSize: 16,
fontSize: 16, fontWeight: FontWeight.normal,
fontWeight: FontWeight.normal, fontFamily: 'Lato',
fontFamily: 'Lato', color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, decoration: TextDecoration.none,
decoration: TextDecoration.none,
),
), ),
outputs.length > 1 ),
? ListView.builder( outputs.length > 1
padding: EdgeInsets.only(top: 0), ? ListView.builder(
shrinkWrap: true, padding: EdgeInsets.only(top: 0),
physics: NeverScrollableScrollPhysics(), shrinkWrap: true,
itemCount: outputs.length, physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemCount: outputs.length,
final item = outputs[index]; itemBuilder: (context, index) {
final _address = item.isParsedAddress final item = outputs[index];
? item.extractedAddress final _address =
: item.address; item.isParsedAddress ? item.extractedAddress : item.address;
final _amount = final _amount = item.cryptoAmount.replaceAll(',', '.');
item.cryptoAmount.replaceAll(',', '.');
return Column( return Column(
children: [ children: [
if (item.isParsedAddress) Padding( if (item.isParsedAddress)
padding: EdgeInsets.only(top: 8), Padding(
child: Text( padding: EdgeInsets.only(top: 8),
item.parsedAddress.name, child: Text(
textAlign: TextAlign.center, item.parsedAddress.name,
style: TextStyle( textAlign: TextAlign.center,
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
_address,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
),
Padding(
padding: EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
_amount,
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontFamily: 'Lato', fontFamily: 'Lato',
color: PaletteDark.pigeonBlue, color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
) )),
], Padding(
) padding: EdgeInsets.only(top: 8),
) child: Text(
], _address,
); style: TextStyle(
}) fontSize: 10,
: Column( fontWeight: FontWeight.w600,
children: [ fontFamily: 'Lato',
if (outputs.first.isParsedAddress) Padding( color: PaletteDark.pigeonBlue,
padding: EdgeInsets.only(top: 8), decoration: TextDecoration.none,
child: Text( ),
outputs.first.parsedAddress.name, )),
textAlign: TextAlign.center, Padding(
style: TextStyle( padding: EdgeInsets.only(top: 8),
fontSize: 14, child: Row(
fontWeight: FontWeight.w600, mainAxisSize: MainAxisSize.max,
fontFamily: 'Lato', mainAxisAlignment: MainAxisAlignment.end,
color: PaletteDark.pigeonBlue, children: [
decoration: TextDecoration.none, Text(
), _amount,
) style: TextStyle(
), fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
))
],
);
})
: Column(children: [
if (outputs.first.isParsedAddress)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
outputs.first.parsedAddress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)),
Padding( Padding(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
child: Text( child: Text(
@ -423,24 +414,19 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
color: PaletteDark.pigeonBlue, color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none, decoration: TextDecoration.none,
), ),
) )),
), ])
] ],
) ),
], )
), ],
) ))),
], if (showScrollbar)
) CakeScrollbar(
) backgroundHeight: backgroundHeight,
), thumbHeight: thumbHeight,
if (showScrollbar) CakeScrollbar( fromTop: fromTop,
backgroundHeight: backgroundHeight, rightOffset: -15)
thumbHeight: thumbHeight, ]);
fromTop: fromTop,
rightOffset: -15
)
]
);
} }
} }

View file

@ -167,8 +167,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
AddressTextFieldOption.qrCode, AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook AddressTextFieldOption.addressBook
], ],
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, buttonColor:
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintStyle: TextStyle( hintStyle: TextStyle(
@ -196,7 +198,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
child: BaseTextFormField( child: BaseTextFormField(
controller: extractedAddressController, controller: extractedAddressController,
readOnly: true, readOnly: true,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, borderColor: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldBorderColor,
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
validator: sendViewModel.addressValidator)), validator: sendViewModel.addressValidator)),
@ -251,7 +255,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
child: Container( child: Container(
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonColor,
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(6), Radius.circular(6),
)), )),
@ -263,7 +269,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor), color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor),
), ),
), ),
), ),
@ -304,7 +312,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.white), color: Colors.white),
placeholderTextStyle: TextStyle( placeholderTextStyle: TextStyle(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 14), fontSize: 14),
validator: output.sendAll validator: output.sendAll
@ -322,7 +332,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
onTap: () async => output.setSendAll(), onTap: () async => output.setSendAll(),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonColor,
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(6), Radius.circular(6),
), ),
@ -334,7 +346,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor, color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor,
), ),
), ),
), ),
@ -364,7 +378,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor), color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor),
), ),
), ),
Text( Text(
@ -372,7 +388,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor), color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor),
) )
], ],
), ),
@ -403,11 +421,13 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
), ),
), ),
hintText: '0.00', hintText: '0.00',
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
placeholderTextStyle: TextStyle( placeholderTextStyle: TextStyle(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, color:
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 14), fontSize: 14),
), ),
@ -418,7 +438,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
controller: noteController, controller: noteController,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
maxLines: null, maxLines: null,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintText: S.of(context).note_optional, hintText: S.of(context).note_optional,
@ -429,73 +450,76 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor), Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
), ),
), ),
Observer( if (sendViewModel.hasFees)
builder: (_) => GestureDetector( Observer(
onTap: () => _setTransactionPriority(context), builder: (_) => GestureDetector(
child: Container( onTap: () => _setTransactionPriority(context),
padding: EdgeInsets.only(top: 24), child: Container(
child: Row( padding: EdgeInsets.only(top: 24),
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ crossAxisAlignment: CrossAxisAlignment.start,
Text( children: <Widget>[
S.of(context).send_estimated_fee, Text(
style: TextStyle( S.of(context).send_estimated_fee,
fontSize: 12, style: TextStyle(
fontWeight: FontWeight.w500, fontSize: 12,
color: Colors.white), fontWeight: FontWeight.w500,
), color: Colors.white),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
output.estimatedFee.toString() +
' ' +
sendViewModel.selectedCryptoCurrency.toString(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: sendViewModel.isFiatDisabled
? const SizedBox(height: 14)
: Text(
output.estimatedFeeFiatAmount +
' ' +
sendViewModel.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
), ),
) Container(
], child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
output.estimatedFee.toString() +
' ' +
sendViewModel.selectedCryptoCurrency.toString(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: sendViewModel.isFiatDisabled
? const SizedBox(height: 14)
: Text(
output.estimatedFeeFiatAmount +
' ' +
sendViewModel.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor,
),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
),
)
],
),
), ),
), ),
), ),
),
if (sendViewModel.hasCoinControl) if (sendViewModel.hasCoinControl)
Padding( Padding(
padding: EdgeInsets.only(top: 6), padding: EdgeInsets.only(top: 6),

View file

@ -70,6 +70,21 @@ class ConnectionSyncPage extends BasePage {
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes), handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
), ),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Observer(
builder: (context) {
if (!dashboardViewModel.hasPowNodes) return const SizedBox();
return Column(
children: [
SettingsCellWithArrow(
title: S.current.manage_pow_nodes,
handler: (context) => Navigator.of(context).pushNamed(Routes.managePowNodes),
),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
);
},
),
if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[ if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
WalletConnectTile( WalletConnectTile(
onTap: () async { onTap: () async {

View file

@ -6,13 +6,17 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
class ManageNodesPage extends BasePage { class ManageNodesPage extends BasePage {
ManageNodesPage(this.nodeListViewModel); ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel})
: assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null));
final NodeListViewModel nodeListViewModel; final NodeListViewModel? nodeListViewModel;
final PowNodeListViewModel? powNodeListViewModel;
final bool isPow;
@override @override
String get title => S.current.manage_nodes; String get title => S.current.manage_nodes;
@ -34,7 +38,8 @@ class ManageNodesPage extends BasePage {
SizedBox(height: 20), SizedBox(height: 20),
Observer( Observer(
builder: (BuildContext context) { builder: (BuildContext context) {
int itemsCount = nodeListViewModel.nodes.length; int itemsCount =
nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
return Flexible( return Flexible(
child: SectionStandardList( child: SectionStandardList(
sectionCount: 1, sectionCount: 1,
@ -43,12 +48,19 @@ class ManageNodesPage extends BasePage {
itemBuilder: (_, index) { itemBuilder: (_, index) {
return Observer( return Observer(
builder: (context) { builder: (context) {
final node = nodeListViewModel.nodes[index]; final node =
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex; nodeListViewModel?.nodes[index] ?? powNodeListViewModel!.nodes[index];
late bool isSelected;
if (isPow) {
isSelected = node.keyIndex == powNodeListViewModel!.currentNode.keyIndex;
} else {
isSelected = node.keyIndex == nodeListViewModel!.currentNode.keyIndex;
}
final nodeListRow = NodeListRow( final nodeListRow = NodeListRow(
title: node.uriRaw, title: node.uriRaw,
node: node, node: node,
isSelected: isSelected, isSelected: isSelected,
isPow: false,
onTap: (_) async { onTap: (_) async {
if (isSelected) { if (isSelected) {
return; return;
@ -59,12 +71,17 @@ class ManageNodesPage extends BasePage {
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertWithTwoActions( return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title, alertTitle: S.of(context).change_current_node_title,
alertContent: nodeListViewModel.getAlertContent(node.uriRaw), alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ??
powNodeListViewModel!.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel, leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change, rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(), actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async { actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node); if (isPow) {
await powNodeListViewModel!.setAsCurrent(node);
} else {
await nodeListViewModel!.setAsCurrent(node);
}
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
); );

View file

@ -28,13 +28,20 @@ class OtherSettingsPage extends BasePage {
padding: EdgeInsets.only(top: 10), padding: EdgeInsets.only(top: 10),
child: Column( child: Column(
children: [ children: [
SettingsPickerCell( if (!_otherSettingsViewModel.changeRepresentativeEnabled)
title: S.current.settings_fee_priority, SettingsPickerCell(
items: priorityForWalletType(_otherSettingsViewModel.walletType), title: S.current.settings_fee_priority,
displayItem: _otherSettingsViewModel.getDisplayPriority, items: priorityForWalletType(_otherSettingsViewModel.walletType),
selectedItem: _otherSettingsViewModel.transactionPriority, displayItem: _otherSettingsViewModel.getDisplayPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, selectedItem: _otherSettingsViewModel.transactionPriority,
), onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,
handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.changeRep),
),
SettingsPickerCell( SettingsPickerCell(
title: S.current.default_buy_provider, title: S.current.default_buy_provider,
items: BuyProviderType.values, items: BuyProviderType.values,
@ -50,7 +57,7 @@ class OtherSettingsPage extends BasePage {
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Spacer(), Spacer(),
SettingsVersionCell( SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion)) title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
], ],
), ),
); );

View file

@ -48,6 +48,7 @@ class WalletListBodyState extends State<WalletListBody> {
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final scrollController = ScrollController(); final scrollController = ScrollController();
final double tileHeight = 60; final double tileHeight = 60;
Flushbar<void>? _progressBar; Flushbar<void>? _progressBar;
@ -242,6 +243,8 @@ class WalletListBodyState extends State<WalletListBody> {
return havenIcon; return havenIcon;
case WalletType.ethereum: case WalletType.ethereum:
return ethereumIcon; return ethereumIcon;
case WalletType.nano:
return nanoIcon;
default: default:
return nonWalletTypeIcon; return nonWalletTypeIcon;
} }

View file

@ -36,7 +36,7 @@ class WelcomePage extends BasePage {
return S.of(context).haven_app_wallet_text; return S.of(context).haven_app_wallet_text;
} }
return S.of(context).first_wallet_text; return S.of(context).new_first_wallet_text;
} }
@override @override

View file

@ -64,6 +64,7 @@ abstract class SettingsStoreBase with Store {
required this.appVersion, required this.appVersion,
required this.deviceName, required this.deviceName,
required Map<WalletType, Node> nodes, required Map<WalletType, Node> nodes,
required Map<WalletType, Node> powNodes,
required this.shouldShowYatPopup, required this.shouldShowYatPopup,
required this.isBitcoinBuyEnabled, required this.isBitcoinBuyEnabled,
required this.actionlistDisplayMode, required this.actionlistDisplayMode,
@ -86,6 +87,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialLitecoinTransactionPriority, TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority}) TransactionPriority? initialEthereumTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes), : nodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, Node>.of(powNodes),
_sharedPreferences = sharedPreferences, _sharedPreferences = sharedPreferences,
_backgroundTasks = backgroundTasks, _backgroundTasks = backgroundTasks,
fiatCurrency = initialFiatCurrency, fiatCurrency = initialFiatCurrency,
@ -347,6 +349,12 @@ abstract class SettingsStoreBase with Store {
_saveCurrentNode(change.newValue!, change.key!); _saveCurrentNode(change.newValue!, change.key!);
} }
}); });
this.powNodes.observe((change) {
if (change.newValue != null && change.key != null) {
_saveCurrentPowNode(change.newValue!, change.key!);
}
});
} }
static const defaultPinLength = 4; static const defaultPinLength = 4;
@ -473,6 +481,7 @@ abstract class SettingsStoreBase with Store {
final BackgroundTasks _backgroundTasks; final BackgroundTasks _backgroundTasks;
ObservableMap<WalletType, Node> nodes; ObservableMap<WalletType, Node> nodes;
ObservableMap<WalletType, Node> powNodes;
Node getCurrentNode(WalletType walletType) { Node getCurrentNode(WalletType walletType) {
final node = nodes[walletType]; final node = nodes[walletType];
@ -484,6 +493,16 @@ abstract class SettingsStoreBase with Store {
return node; return node;
} }
Node getCurrentPowNode(WalletType walletType) {
final node = powNodes[walletType];
if (node == null) {
throw Exception('No pow node found for wallet type: ${walletType.toString()}');
}
return node;
}
bool isBitcoinBuyEnabled; bool isBitcoinBuyEnabled;
bool get shouldShowReceiveWarning => bool get shouldShowReceiveWarning =>
@ -494,6 +513,7 @@ abstract class SettingsStoreBase with Store {
static Future<SettingsStore> load( static Future<SettingsStore> load(
{required Box<Node> nodeSource, {required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required bool isBitcoinBuyEnabled, required bool isBitcoinBuyEnabled,
FiatCurrency initialFiatCurrency = FiatCurrency.usd, FiatCurrency initialFiatCurrency = FiatCurrency.usd,
BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance, BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
@ -611,11 +631,15 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final moneroNode = nodeSource.get(nodeId); final moneroNode = nodeSource.get(nodeId);
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId); final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId); final ethereumNode = nodeSource.get(ethereumNodeId);
final nanoNode = nodeSource.get(nanoNodeId);
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? ''; final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
@ -626,6 +650,7 @@ abstract class SettingsStoreBase with Store {
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
: defaultAutoGenerateSubaddressStatus; : defaultAutoGenerateSubaddressStatus;
final nodes = <WalletType, Node>{}; final nodes = <WalletType, Node>{};
final powNodes = <WalletType, Node>{};
if (moneroNode != null) { if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode; nodes[WalletType.monero] = moneroNode;
@ -647,6 +672,13 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode; nodes[WalletType.ethereum] = ethereumNode;
} }
if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode;
}
if (nanoPowNode != null) {
powNodes[WalletType.nano] = nanoPowNode;
}
final savedSyncMode = SyncMode.all.firstWhere((element) { final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
}); });
@ -656,6 +688,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences: sharedPreferences, sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes, nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version, appVersion: packageInfo.version,
deviceName: deviceName, deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled, isBitcoinBuyEnabled: isBitcoinBuyEnabled,
@ -818,11 +851,14 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final moneroNode = nodeSource.get(nodeId); final moneroNode = nodeSource.get(nodeId);
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId); final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId); final ethereumNode = nodeSource.get(ethereumNodeId);
final nanoNode = nodeSource.get(nanoNodeId);
if (moneroNode != null) { if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode; nodes[WalletType.monero] = moneroNode;
@ -843,6 +879,10 @@ abstract class SettingsStoreBase with Store {
if (ethereumNode != null) { if (ethereumNode != null) {
nodes[WalletType.ethereum] = ethereumNode; nodes[WalletType.ethereum] = ethereumNode;
} }
if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode;
}
} }
Future<void> _saveCurrentNode(Node node, WalletType walletType) async { Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
@ -864,6 +904,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.ethereum: case WalletType.ethereum:
await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int); await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
break; break;
case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
break;
default: default:
break; break;
} }
@ -871,6 +914,18 @@ abstract class SettingsStoreBase with Store {
nodes[walletType] = node; nodes[walletType] = node;
} }
Future<void> _saveCurrentPowNode(Node node, WalletType walletType) async {
switch (walletType) {
case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
break;
default:
break;
}
powNodes[walletType] = node;
}
static Future<String?> _getDeviceName() async { static Future<String?> _getDeviceName() async {
String? deviceName = ''; String? deviceName = '';
final deviceInfoPlugin = DeviceInfoPlugin(); final deviceInfoPlugin = DeviceInfoPlugin();

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/nano/nano.dart';
class PaymentRequest { class PaymentRequest {
PaymentRequest(this.address, this.amount, this.note, this.scheme); PaymentRequest(this.address, this.amount, this.note, this.scheme);
@ -10,11 +12,18 @@ class PaymentRequest {
if (uri != null) { if (uri != null) {
address = uri.path; address = uri.path;
amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? ""; amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
note = uri.queryParameters['tx_description'] note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
?? uri.queryParameters['message'] ?? "";
scheme = uri.scheme; scheme = uri.scheme;
} }
if (nano != null) {
if (address.contains("nano")) {
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano);
} else if (address.contains("ban")) {
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano);
}
}
return PaymentRequest(address, amount, note, scheme); return PaymentRequest(address, amount, note, scheme);
} }

View file

@ -15,12 +15,11 @@ import 'package:collection/collection.dart';
part 'contact_list_view_model.g.dart'; part 'contact_list_view_model.g.dart';
class ContactListViewModel = ContactListViewModelBase class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
with _$ContactListViewModel;
abstract class ContactListViewModelBase with Store { abstract class ContactListViewModelBase with Store {
ContactListViewModelBase(this.contactSource, this.walletInfoSource, ContactListViewModelBase(
this._currency, this.settingsStore) this.contactSource, this.walletInfoSource, this._currency, this.settingsStore)
: contacts = ObservableList<ContactRecord>(), : contacts = ObservableList<ContactRecord>(),
walletContacts = [], walletContacts = [],
isAutoGenerateEnabled = isAutoGenerateEnabled =
@ -48,6 +47,12 @@ abstract class ContactListViewModelBase with Store {
walletTypeToCryptoCurrency(info.type), walletTypeToCryptoCurrency(info.type),
)); ));
}); });
} else if (info.address != null) {
walletContacts.add(WalletContact(
info.address,
info.name,
walletTypeToCryptoCurrency(info.type),
));
} }
}); });
@ -78,14 +83,12 @@ abstract class ContactListViewModelBase with Store {
Future<void> delete(ContactRecord contact) async => contact.original.delete(); Future<void> delete(ContactRecord contact) async => contact.original.delete();
@computed @computed
List<ContactRecord> get contactsToShow => contacts List<ContactRecord> get contactsToShow =>
.where((element) => _isValidForCurrency(element)) contacts.where((element) => _isValidForCurrency(element)).toList();
.toList();
@computed @computed
List<WalletContact> get walletContactsToShow => walletContacts List<WalletContact> get walletContactsToShow =>
.where((element) => _isValidForCurrency(element)) walletContacts.where((element) => _isValidForCurrency(element)).toList();
.toList();
bool _isValidForCurrency(ContactBase element) { bool _isValidForCurrency(ContactBase element) {
return _currency == null || element.type == _currency || element.type.title == _currency!.tag; return _currency == null || element.type == _currency || element.type.title == _currency!.tag;

View file

@ -47,74 +47,71 @@ abstract class DashboardViewModelBase with Store {
required this.yatStore, required this.yatStore,
required this.ordersStore, required this.ordersStore,
required this.anonpayTransactionsStore}) required this.anonpayTransactionsStore})
: isOutdatedElectrumWallet = false, : hasSellAction = false,
hasSellAction = false, hasBuyAction = false,
hasBuyAction = false, hasExchangeAction = false,
hasExchangeAction = false, isShowFirstYatIntroduction = false,
isShowFirstYatIntroduction = false, isShowSecondYatIntroduction = false,
isShowSecondYatIntroduction = false, isShowThirdYatIntroduction = false,
isShowThirdYatIntroduction = false, filterItems = {
filterItems = { S.current.transactions: [
S.current.transactions: [ FilterItem(
FilterItem( value: () => transactionFilterStore.displayAll,
value: () => transactionFilterStore.displayAll, caption: S.current.all_transactions,
caption: S.current.all_transactions, onChanged: transactionFilterStore.toggleAll),
onChanged: transactionFilterStore.toggleAll), FilterItem(
FilterItem( value: () => transactionFilterStore.displayIncoming,
value: () => transactionFilterStore.displayIncoming, caption: S.current.incoming,
caption: S.current.incoming, onChanged:transactionFilterStore.toggleIncoming),
onChanged: transactionFilterStore.toggleIncoming), FilterItem(
FilterItem( value: () => transactionFilterStore.displayOutgoing,
value: () => transactionFilterStore.displayOutgoing, caption: S.current.outgoing,
caption: S.current.outgoing, onChanged: transactionFilterStore.toggleOutgoing),
onChanged: transactionFilterStore.toggleOutgoing), // FilterItem(
// FilterItem( // value: () => false,
// value: () => false, // caption: S.current.transactions_by_date,
// caption: S.current.transactions_by_date, // onChanged: null),
// onChanged: null), ],
], S.current.trades: [
S.current.trades: [ FilterItem(
FilterItem( value: () => tradeFilterStore.displayAllTrades,
value: () => tradeFilterStore.displayAllTrades, caption: S.current.all_trades,
caption: S.current.all_trades, onChanged: () => tradeFilterStore
onChanged: () => .toggleDisplayExchange(ExchangeProviderDescription.all)),
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)), FilterItem(
FilterItem( value: () => tradeFilterStore.displayChangeNow,
value: () => tradeFilterStore.displayChangeNow, caption: ExchangeProviderDescription.changeNow.title,
caption: ExchangeProviderDescription.changeNow.title, onChanged: () => tradeFilterStore
onChanged: () => .toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)), FilterItem(
FilterItem( value: () => tradeFilterStore.displaySideShift,
value: () => tradeFilterStore.displaySideShift, caption: ExchangeProviderDescription.sideShift.title,
caption: ExchangeProviderDescription.sideShift.title, onChanged: () => tradeFilterStore
onChanged: () => .toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)), FilterItem(
FilterItem( value: () => tradeFilterStore.displaySimpleSwap,
value: () => tradeFilterStore.displaySimpleSwap, caption: ExchangeProviderDescription.simpleSwap.title,
caption: ExchangeProviderDescription.simpleSwap.title, onChanged: () => tradeFilterStore
onChanged: () => .toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)), FilterItem(
FilterItem( value: () => tradeFilterStore.displayTrocador,
value: () => tradeFilterStore.displayTrocador, caption: ExchangeProviderDescription.trocador.title,
caption: ExchangeProviderDescription.trocador.title, onChanged: () => tradeFilterStore
onChanged: () => .toggleDisplayExchange(ExchangeProviderDescription.trocador)),
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)), FilterItem(
FilterItem( value: () => tradeFilterStore.displayExolix,
value: () => tradeFilterStore.displayExolix, caption: ExchangeProviderDescription.exolix.title,
caption: ExchangeProviderDescription.exolix.title, onChanged: () => tradeFilterStore
onChanged: () => .toggleDisplayExchange(ExchangeProviderDescription.exolix)),
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)), ]
] },
}, subname = '',
subname = '', name = appStore.wallet!.name,
name = appStore.wallet!.name, type = appStore.wallet!.type,
type = appStore.wallet!.type, transactions = ObservableList<TransactionListItem>(),
transactions = ObservableList<TransactionListItem>(), wallet = appStore.wallet! {
wallet = appStore.wallet! {
name = wallet.name; name = wallet.name;
type = wallet.type; type = wallet.type;
isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
isShowFirstYatIntroduction = false; isShowFirstYatIntroduction = false;
isShowSecondYatIntroduction = false; isShowSecondYatIntroduction = false;
isShowThirdYatIntroduction = false; isShowThirdYatIntroduction = false;
@ -151,6 +148,11 @@ abstract class DashboardViewModelBase with Store {
settingsStore: appStore.settingsStore))); settingsStore: appStore.settingsStore)));
} }
// TODO: nano sub-account generation is disabled:
// if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
// subname = nano!.getCurrentAccount(_wallet).label;
// }
reaction((_) => appStore.wallet, _onWalletChange); reaction((_) => appStore.wallet, _onWalletChange);
connectMapToListWithTransform( connectMapToListWithTransform(
@ -315,12 +317,16 @@ abstract class DashboardViewModelBase with Store {
ReactionDisposer? _onMoneroBalanceChangeReaction; ReactionDisposer? _onMoneroBalanceChangeReaction;
@observable @computed
bool isOutdatedElectrumWallet; bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
Future<void> reconnect() async { Future<void> reconnect() async {
final node = appStore.settingsStore.getCurrentNode(wallet.type); final node = appStore.settingsStore.getCurrentNode(wallet.type);
await wallet.connectToNode(node: node); await wallet.connectToNode(node: node);
if (hasPowNodes) {
final powNode = settingsStore.getCurrentPowNode(wallet.type);
await wallet.connectToPowNode(node: powNode);
}
} }
@action @action
@ -333,8 +339,6 @@ abstract class DashboardViewModelBase with Store {
this.wallet = wallet; this.wallet = wallet;
type = wallet.type; type = wallet.type;
name = wallet.name; name = wallet.name;
isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
updateActions(); updateActions();
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
@ -16,9 +17,7 @@ import 'package:cw_core/wallet_type.dart';
class TransactionListItem extends ActionListItem with Keyable { class TransactionListItem extends ActionListItem with Keyable {
TransactionListItem( TransactionListItem(
{required this.transaction, {required this.transaction, required this.balanceViewModel, required this.settingsStore});
required this.balanceViewModel,
required this.settingsStore});
final TransactionInfo transaction; final TransactionInfo transaction;
final BalanceViewModel balanceViewModel; final BalanceViewModel balanceViewModel;
@ -34,10 +33,9 @@ class TransactionListItem extends ActionListItem with Keyable {
dynamic get keyIndex => transaction.id; dynamic get keyIndex => transaction.id;
String get formattedCryptoAmount { String get formattedCryptoAmount {
return displayMode == BalanceDisplayMode.hiddenBalance return displayMode == BalanceDisplayMode.hiddenBalance ? '---' : transaction.amountFormatted();
? '---'
: transaction.amountFormatted();
} }
String get formattedTitle { String get formattedTitle {
if (transaction.direction == TransactionDirection.incoming) { if (transaction.direction == TransactionDirection.incoming) {
return S.current.received; return S.current.received;
@ -57,33 +55,47 @@ class TransactionListItem extends ActionListItem with Keyable {
if (transaction.direction == TransactionDirection.incoming) { if (transaction.direction == TransactionDirection.incoming) {
if (balanceViewModel.wallet.type == WalletType.monero || if (balanceViewModel.wallet.type == WalletType.monero ||
balanceViewModel.wallet.type == WalletType.haven) { balanceViewModel.wallet.type == WalletType.haven) {
return formattedPendingStatus; return formattedPendingStatus;
}
} }
return transaction.isPending ? S.current.pending : '';
} }
return transaction.isPending ? S.current.pending : '';
}
String get formattedFiatAmount { String get formattedFiatAmount {
var amount = ''; var amount = '';
switch(balanceViewModel.wallet.type) { switch (balanceViewModel.wallet.type) {
case WalletType.monero: case WalletType.monero:
amount = calculateFiatAmountRaw( amount = calculateFiatAmountRaw(
cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount), cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price); price: price);
break; break;
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
amount = calculateFiatAmountRaw( amount = calculateFiatAmountRaw(
cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount), cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
price: price); price: price);
break; break;
case WalletType.haven: case WalletType.haven:
final asset = haven!.assetOfTransaction(transaction); final asset = haven!.assetOfTransaction(transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset]; final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw( amount = calculateFiatAmountRaw(
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount), cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price); price: price);
break;
case WalletType.ethereum:
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.nano:
amount = calculateFiatAmountRaw(
cryptoAmount: nanoUtil!
.getRawAsDecimal(nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)
.toDouble(),
price: price);
break; break;
case WalletType.ethereum: case WalletType.ethereum:
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction); final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);

View file

@ -86,13 +86,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
super(appStore: appStore) { super(appStore: appStore) {
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly; _useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
_setProviders(); _setProviders();
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano]; const excludeDepositCurrencies = [CryptoCurrency.btt];
const excludeReceiveCurrencies = [ const excludeReceiveCurrencies = [
CryptoCurrency.xlm, CryptoCurrency.xlm,
CryptoCurrency.xrp, CryptoCurrency.xrp,
CryptoCurrency.bnb, CryptoCurrency.bnb,
CryptoCurrency.btt, CryptoCurrency.btt
CryptoCurrency.nano
]; ];
_initialPairBasedOnWallet(); _initialPairBasedOnWallet();
@ -703,6 +702,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.eth; depositCurrency = CryptoCurrency.eth;
receiveCurrency = CryptoCurrency.xmr; receiveCurrency = CryptoCurrency.xmr;
break; break;
case WalletType.nano:
depositCurrency = CryptoCurrency.nano;
receiveCurrency = CryptoCurrency.xmr;
break;
default: default:
break; break;
} }

View file

@ -0,0 +1,58 @@
import 'package:cake_wallet/nano/nano.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/execution_state.dart';
part 'nano_account_edit_or_create_view_model.g.dart';
class NanoAccountEditOrCreateViewModel = NanoAccountEditOrCreateViewModelBase
with _$NanoAccountEditOrCreateViewModel;
abstract class NanoAccountEditOrCreateViewModelBase with Store {
NanoAccountEditOrCreateViewModelBase(this._nanoAccountList,
{required WalletBase wallet, NanoAccount? accountListItem})
: state = InitialExecutionState(),
isEdit = accountListItem != null,
label = accountListItem?.label ?? '',
_accountListItem = accountListItem,
_wallet = wallet;
final bool isEdit;
@observable
ExecutionState state;
@observable
String label;
final NanoAccountList _nanoAccountList;
final NanoAccount? _accountListItem;
final WalletBase _wallet;
Future<void> save() async {
if (_wallet.type == WalletType.nano) {
await saveNano();
}
}
Future<void> saveNano() async {
try {
state = IsExecutingState();
if (_accountListItem != null) {
await _nanoAccountList.setLabelAccount(_wallet,
accountIndex: _accountListItem!.id, label: label);
} else {
await _nanoAccountList.addAccount(_wallet, label: label);
}
await _wallet.save();
state = ExecutedSuccessfullyState();
} catch (e) {
state = FailureState(e.toString());
}
}
}

View file

@ -0,0 +1,54 @@
import 'package:cake_wallet/nano/nano.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/nano_account.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
part 'nano_account_list_view_model.g.dart';
class NanoAccountListViewModel = NanoAccountListViewModelBase with _$NanoAccountListViewModel;
abstract class NanoAccountListViewModelBase with Store {
NanoAccountListViewModelBase(this._wallet) : scrollOffsetFromTop = 0;
@observable
double scrollOffsetFromTop;
@action
void setScrollOffsetFromTop(double scrollOffsetFromTop) {
this.scrollOffsetFromTop = scrollOffsetFromTop;
}
CryptoCurrency get currency => _wallet.currency;
@computed
List<NanoAccount> get accounts {
if (_wallet.type == WalletType.nano) {
return nano!
.getAccountList(_wallet)
.accounts
.map((acc) => NanoAccount(
label: acc.label,
id: acc.id,
isSelected: acc.id == nano?.getCurrentAccount(_wallet).id,
))
.toList();
}
throw Exception('Unexpected wallet type: ${_wallet.type}');
}
final WalletBase _wallet;
void select(NanoAccount item) {
if (_wallet.type == WalletType.nano) {
nano!.setCurrentAccount(
_wallet,
item.id,
item.label,
item.balance,
);
}
}
}

View file

@ -66,6 +66,9 @@ abstract class NodeListViewModelBase with Store {
case WalletType.ethereum: case WalletType.ethereum:
node = getEthereumDefaultNode(nodes: _nodeSource)!; node = getEthereumDefaultNode(nodes: _nodeSource)!;
break; break;
case WalletType.nano:
node = getNanoDefaultNode(nodes: _nodeSource)!;
break;
default: default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}'); throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
} }

View file

@ -0,0 +1,79 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/entities/node_list.dart';
import 'package:cake_wallet/entities/default_settings_migration.dart';
import 'package:cw_core/wallet_type.dart';
part 'pow_node_list_view_model.g.dart';
class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewModel;
abstract class PowNodeListViewModelBase with Store {
PowNodeListViewModelBase(this._nodeSource, this._appStore)
: nodes = ObservableList<Node>(),
settingsStore = _appStore.settingsStore {
_bindNodes();
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
_bindNodes();
});
}
@computed
Node get currentNode {
final node = settingsStore.powNodes[_appStore.wallet!.type];
if (node == null) {
throw Exception('No node for wallet type: ${_appStore.wallet!.type}');
}
return node;
}
String getAlertContent(String uri) =>
S.current.change_current_node(uri) +
'${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}';
final ObservableList<Node> nodes;
final SettingsStore settingsStore;
final Box<Node> _nodeSource;
final AppStore _appStore;
Future<void> reset() async {
await resetPowToDefault(_nodeSource);
Node node;
switch (_appStore.wallet!.type) {
case WalletType.nano:
node = getNanoDefaultPowNode(nodes: _nodeSource)!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
}
await setAsCurrent(node);
}
@action
Future<void> delete(Node node) async => node.delete();
Future<void> setAsCurrent(Node node) async =>
settingsStore.powNodes[_appStore.wallet!.type] = node;
@action
void _bindNodes() {
nodes.clear();
_nodeSource.bindToList(
nodes,
filter: (val) => val.type == _appStore.wallet!.type,
initialFire: true,
);
}
}

View file

@ -1,10 +1,13 @@
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/output.dart';
@ -16,7 +19,6 @@ import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cw_core/sync_status.dart'; import 'package:cw_core/sync_status.dart';
@ -62,7 +64,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
final priority = _settingsStore.priority[wallet.type]; final priority = _settingsStore.priority[wallet.type];
final priorities = priorityForWalletType(wallet.type); final priorities = priorityForWalletType(wallet.type);
if (!priorityForWalletType(wallet.type).contains(priority)) { if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[wallet.type] = priorities.first; _settingsStore.priority[wallet.type] = priorities.first;
} }
@ -99,15 +101,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed @computed
String get pendingTransactionFiatAmount { String get pendingTransactionFiatAmount {
if (pendingTransaction == null) {
return '0.00';
}
try { try {
if (pendingTransaction != null) { final fiat = calculateFiatAmount(
final fiat = calculateFiatAmount( price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
price: _fiatConversationStore.prices[selectedCryptoCurrency]!, cryptoAmount: pendingTransaction!.amountFormatted);
cryptoAmount: pendingTransaction!.amountFormatted); return fiat;
return fiat;
} else {
return '0.00';
}
} catch (_) { } catch (_) {
return '0.00'; return '0.00';
} }
@ -191,6 +193,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
bool get isElectrumWallet => bool get isElectrumWallet =>
wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin; wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin;
@computed
bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
@observable @observable
CryptoCurrency selectedCryptoCurrency; CryptoCurrency selectedCryptoCurrency;
@ -316,6 +321,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = TransactionCommitting(); state = TransactionCommitting();
await pendingTransaction!.commit(); await pendingTransaction!.commit();
if (walletType == WalletType.nano) {
nano!.updateTransactions(wallet);
}
if (pendingTransaction!.id.isNotEmpty) { if (pendingTransaction!.id.isNotEmpty) {
_settingsStore.shouldSaveRecipientAddress _settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription( ? await transactionDescriptionBox.add(TransactionDescription(
@ -380,6 +389,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return ethereum!.createEthereumTransactionCredentials(outputs, return ethereum!.createEthereumTransactionCredentials(outputs,
priority: priority, currency: selectedCryptoCurrency); priority: priority, currency: selectedCryptoCurrency);
case WalletType.nano:
return nano!.createNanoTransactionCredentials(
outputs,
);
default: default:
throw Exception('Unexpected wallet type: ${wallet.type}'); throw Exception('Unexpected wallet type: ${wallet.type}');
} }

View file

@ -25,7 +25,7 @@ abstract class OtherSettingsViewModelBase with Store {
final priority = _settingsStore.priority[_wallet.type]; final priority = _settingsStore.priority[_wallet.type];
final priorities = priorityForWalletType(_wallet.type); final priorities = priorityForWalletType(_wallet.type);
if (!priorities.contains(priority)) { if (!priorities.contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[_wallet.type] = priorities.first; _settingsStore.priority[_wallet.type] = priorities.first;
} }
} }
@ -50,6 +50,14 @@ abstract class OtherSettingsViewModelBase with Store {
} }
@computed @computed
bool get changeRepresentativeEnabled {
if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
return true;
}
return false;
}
BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; } BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; }
String getDisplayPriority(dynamic priority) { String getDisplayPriority(dynamic priority) {

View file

@ -47,6 +47,9 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.ethereum: case WalletType.ethereum:
_addEthereumListItems(tx, dateFormat); _addEthereumListItems(tx, dateFormat);
break; break;
case WalletType.nano:
_addNanoListItems(tx, dateFormat);
break;
default: default:
break; break;
} }
@ -116,6 +119,10 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://explorer.havenprotocol.org/search?value=${txId}'; return 'https://explorer.havenprotocol.org/search?value=${txId}';
case WalletType.ethereum: case WalletType.ethereum:
return 'https://etherscan.io/tx/${txId}'; return 'https://etherscan.io/tx/${txId}';
case WalletType.nano:
return 'https://nanolooker.com/block/${txId}';
case WalletType.banano:
return 'https://bananolooker.com/block/${txId}';
default: default:
return ''; return '';
} }
@ -133,6 +140,10 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'explorer.havenprotocol.org'; return S.current.view_transaction_on + 'explorer.havenprotocol.org';
case WalletType.ethereum: case WalletType.ethereum:
return S.current.view_transaction_on + 'etherscan.io'; return S.current.view_transaction_on + 'etherscan.io';
case WalletType.nano:
return S.current.view_transaction_on + 'nanolooker.com';
case WalletType.banano:
return S.current.view_transaction_on + 'bananolooker.com';
default: default:
return ''; return '';
} }
@ -221,4 +232,18 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items); items.addAll(_items);
} }
void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.confirmations, value: (tx.confirmations > 0).toString()),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
];
items.addAll(_items);
}
} }

View file

@ -107,6 +107,22 @@ class EthereumURI extends PaymentURI {
} }
} }
class NanoURI extends PaymentURI {
NanoURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = 'nano:' + address;
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({ WalletAddressListViewModelBase({
required AppStore appStore, required AppStore appStore,
@ -176,6 +192,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return EthereumURI(amount: amount, address: address.address); return EthereumURI(amount: amount, address: address.address);
} }
if (wallet.type == WalletType.nano) {
return NanoURI(amount: amount, address: address.address);
}
throw Exception('Unexpected type: ${type.toString()}'); throw Exception('Unexpected type: ${type.toString()}');
} }
@ -252,7 +272,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
} }
@computed @computed
bool get hasAddressList => wallet.type == WalletType.monero || wallet.type == WalletType.haven; bool get hasAddressList =>
wallet.type == WalletType.monero ||
wallet.type == WalletType.haven;/* ||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano;*/// TODO: nano accounts are disabled for now
@computed @computed
bool get showElectrumAddressDisclaimer => bool get showElectrumAddressDisclaimer =>
@ -269,11 +293,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void _init() { void _init() {
_baseItems = []; _baseItems = [];
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) { if (wallet.type == WalletType.monero ||
wallet.type == WalletType.haven /*||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano*/) {
_baseItems.add(WalletAccountListHeader()); _baseItems.add(WalletAccountListHeader());
} }
_baseItems.add(WalletAddressListHeader()); if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
_baseItems.add(WalletAddressListHeader());
}
} }
@action @action

View file

@ -35,18 +35,16 @@ abstract class WalletCreationVMBase with Store {
final Box<WalletInfo> _walletInfoSource; final Box<WalletInfo> _walletInfoSource;
final AppStore _appStore; final AppStore _appStore;
bool nameExists(String name) bool nameExists(String name) => walletCreationService.exists(name);
=> walletCreationService.exists(name);
bool typeExists(WalletType type) bool typeExists(WalletType type) => walletCreationService.typeExists(type);
=> walletCreationService.typeExists(type);
Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async { Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async {
final type = restoreWallet?.type ?? this.type; final type = restoreWallet?.type ?? this.type;
try { try {
state = IsExecutingState(); state = IsExecutingState();
if (name.isEmpty) { if (name.isEmpty) {
name = await generateName(); name = await generateName();
} }
walletCreationService.checkIfExists(name); walletCreationService.checkIfExists(name);
@ -55,17 +53,21 @@ abstract class WalletCreationVMBase with Store {
final credentials = restoreWallet != null final credentials = restoreWallet != null
? getCredentialsFromRestoredWallet(options, restoreWallet) ? getCredentialsFromRestoredWallet(options, restoreWallet)
: getCredentials(options); : getCredentials(options);
final walletInfo = WalletInfo.external( final walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, type), id: WalletBase.idFor(name, type),
name: name, name: name,
type: type, type: type,
isRecovery: isRecovery, isRecovery: isRecovery,
restoreHeight: credentials.height ?? 0, restoreHeight: credentials.height ?? 0,
date: DateTime.now(), date: DateTime.now(),
path: path, path: path,
dirPath: dirPath, dirPath: dirPath,
address: '', address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven); showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationPath: credentials.derivationPath,
derivationType: credentials.derivationType,
);
credentials.walletInfo = walletInfo; credentials.walletInfo = walletInfo;
final wallet = restoreWallet != null final wallet = restoreWallet != null
? await processFromRestoredWallet(credentials, restoreWallet) ? await processFromRestoredWallet(credentials, restoreWallet)
@ -80,15 +82,16 @@ abstract class WalletCreationVMBase with Store {
state = FailureState(e.toString()); state = FailureState(e.toString());
} }
} }
WalletCredentials getCredentials(dynamic options) =>
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError();
WalletCredentials getCredentialsFromRestoredWallet(
dynamic options, RestoredWallet restoreWallet) =>
throw UnimplementedError(); throw UnimplementedError();
Future<WalletBase> process(WalletCredentials credentials) => Future<WalletBase> processFromRestoredWallet(
throw UnimplementedError(); WalletCredentials credentials, RestoredWallet restoreWallet) =>
WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) =>
throw UnimplementedError();
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) =>
throw UnimplementedError(); throw UnimplementedError();
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
@ -104,6 +105,22 @@ abstract class WalletKeysViewModelBase with Store {
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
]); ]);
} }
if (_appStore.wallet!.type == WalletType.nano || _appStore.wallet!.type == WalletType.banano) {
// we don't necessarily have the seed phrase for nano / banano:
if (_appStore.wallet!.seed != null) {
items.addAll([
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
]);
}
// we always have the hex version of the seed:
items.addAll([
if (_appStore.wallet!.privateKey != null)
StandartListItem(title: S.current.spend_key_private, value: _appStore.wallet!.privateKey!),
]);
}
} }
Future<int?> _currentHeight() async { Future<int?> _currentHeight() async {
@ -128,6 +145,10 @@ abstract class WalletKeysViewModelBase with Store {
return 'haven-wallet'; return 'haven-wallet';
case WalletType.ethereum: case WalletType.ethereum:
return 'ethereum-wallet'; return 'ethereum-wallet';
case WalletType.nano:
return 'nano-wallet';
case WalletType.banano:
return 'banano-wallet';
default: default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}'); throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
} }

View file

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart';
@ -45,6 +46,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
name: name, language: options as String); name: name, language: options as String);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumNewWalletCredentials(name: name); return ethereum!.createEthereumNewWalletCredentials(name: name);
case WalletType.nano:
return nano!.createNanoNewWalletCredentials(name: name);
default: default:
throw Exception('Unexpected type: ${type.toString()}');; throw Exception('Unexpected type: ${type.toString()}');;
} }

View file

@ -0,0 +1,23 @@
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
part 'wallet_restore_choose_derivation_view_model.g.dart';
class WalletRestoreChooseDerivationViewModel = WalletRestoreChooseDerivationViewModelBase
with _$WalletRestoreChooseDerivationViewModel;
abstract class WalletRestoreChooseDerivationViewModelBase with Store {
WalletRestoreChooseDerivationViewModelBase({required this.derivationInfos})
: mode = WalletRestoreMode.seed {}
@observable
List<DerivationInfo> derivationInfos;
Future<List<DerivationInfo>> get derivations async {
return derivationInfos;
}
@observable
WalletRestoreMode mode;
}

View file

@ -1,4 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -16,25 +18,34 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart';
part 'wallet_restore_view_model.g.dart'; part 'wallet_restore_view_model.g.dart';
class WalletRestoreViewModel = WalletRestoreViewModelBase class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel;
with _$WalletRestoreViewModel;
abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource, Box<WalletInfo> walletInfoSource,
{required WalletType type}) {required WalletType type})
: availableModes = : hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
(type == WalletType.monero || type == WalletType.haven || type == WalletType.ethereum)
? WalletRestoreMode.values
: [WalletRestoreMode.seed],
hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasRestoreFromPrivateKey = type == WalletType.ethereum, hasRestoreFromPrivateKey =
type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano,
isButtonEnabled = false, isButtonEnabled = false,
mode = WalletRestoreMode.seed, mode = WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) { super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
isButtonEnabled = switch (type) {
!hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector; case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
availableModes = WalletRestoreMode.values;
break;
case WalletType.nano:
case WalletType.banano:
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
break;
default:
availableModes = [WalletRestoreMode.seed];
break;
}
isButtonEnabled = !hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector;
walletCreationService.changeWalletType(type: type); walletCreationService.changeWalletType(type: type);
} }
@ -42,7 +53,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
static const electrumSeedMnemonicLength = 24; static const electrumSeedMnemonicLength = 24;
static const electrumShortSeedMnemonicLength = 12; static const electrumShortSeedMnemonicLength = 12;
final List<WalletRestoreMode> availableModes; late List<WalletRestoreMode> availableModes;
final bool hasSeedLanguageSelector; final bool hasSeedLanguageSelector;
final bool hasBlockchainHeightLanguageSelector; final bool hasBlockchainHeightLanguageSelector;
final bool hasRestoreFromPrivateKey; final bool hasRestoreFromPrivateKey;
@ -58,38 +69,37 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final password = generateWalletPassword(); final password = generateWalletPassword();
final height = options['height'] as int? ?? 0; final height = options['height'] as int? ?? 0;
name = options['name'] as String; name = options['name'] as String;
DerivationType? derivationType = options["derivationType"] as DerivationType?;
String? derivationPath = options["derivationPath"] as String?;
if (mode == WalletRestoreMode.seed) { if (mode == WalletRestoreMode.seed) {
final seed = options['seed'] as String; final seed = options['seed'] as String;
switch (type) { switch (type) {
case WalletType.monero: case WalletType.monero:
return monero!.createMoneroRestoreWalletFromSeedCredentials( return monero!.createMoneroRestoreWalletFromSeedCredentials(
name: name, name: name, height: height, mnemonic: seed, password: password);
height: height,
mnemonic: seed,
password: password);
case WalletType.bitcoin: case WalletType.bitcoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, name: name,
mnemonic: seed, mnemonic: seed,
password: password); password: password,
);
case WalletType.litecoin: case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, name: name, mnemonic: seed, password: password);
mnemonic: seed,
password: password);
case WalletType.haven: case WalletType.haven:
return haven!.createHavenRestoreWalletFromSeedCredentials( return haven!.createHavenRestoreWalletFromSeedCredentials(
name: name, name: name, height: height, mnemonic: seed, password: password);
height: height,
mnemonic: seed,
password: password);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials( return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name, name: name, mnemonic: seed, password: password);
mnemonic: seed, case WalletType.nano:
password: password); return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
derivationType: derivationType,
);
default: default:
break; break;
} }
@ -100,40 +110,73 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final spendKey = options['spendKey'] as String?; final spendKey = options['spendKey'] as String?;
final address = options['address'] as String?; final address = options['address'] as String?;
if (type == WalletType.monero) { switch (type) {
return monero!.createMoneroRestoreWalletFromKeysCredentials( case WalletType.monero:
return monero!.createMoneroRestoreWalletFromKeysCredentials(
name: name, name: name,
height: height, height: height,
spendKey: spendKey!, spendKey: spendKey!,
viewKey: viewKey!, viewKey: viewKey!,
address: address!, address: address!,
password: password, password: password,
language: 'English'); language: 'English',
} );
if (type == WalletType.haven) { case WalletType.haven:
return haven!.createHavenRestoreWalletFromKeysCredentials( return haven!.createHavenRestoreWalletFromKeysCredentials(
name: name, name: name,
height: height, height: height,
spendKey: spendKey!, spendKey: spendKey!,
viewKey: viewKey!, viewKey: viewKey!,
address: address!, address: address!,
password: password, password: password,
language: 'English'); language: 'English',
} );
if (type == WalletType.ethereum) { case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromPrivateKey( return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name, name: name,
privateKey: options['private_key'] as String, privateKey: options['private_key'] as String,
password: password, password: password,
); );
case WalletType.nano:
return nano!.createNanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: options['private_key'] as String,
derivationType: options["derivationType"] as DerivationType,
);
default:
break;
} }
} }
throw Exception('Unexpected type: ${type.toString()}'); throw Exception('Unexpected type: ${type.toString()}');
} }
Future<List<DerivationType>> getDerivationTypes(dynamic options) async {
final seedKey = options['private_key'] as String?;
final mnemonic = options['seed'] as String?;
WalletType walletType = options['walletType'] as WalletType;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (type) {
case WalletType.nano:
return nanoUtil!.compareDerivationMethods(
mnemonic: mnemonic,
privateKey: seedKey,
node: node,
);
default:
break;
}
// throw Exception('Unexpected type: ${type.toString()}');
return [DerivationType.def];
}
@override @override
Future<WalletBase> process(WalletCredentials credentials) async { Future<WalletBase> process(WalletCredentials credentials) async {
if (mode == WalletRestoreMode.keys) { if (mode == WalletRestoreMode.keys) {

1
model_generator.sh Normal file → Executable file
View file

@ -3,4 +3,5 @@ cd cw_monero && flutter pub get && flutter packages pub run build_runner build -
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -129,6 +129,8 @@ flutter:
- assets/bitcoin_electrum_server_list.yml - assets/bitcoin_electrum_server_list.yml
- assets/litecoin_electrum_server_list.yml - assets/litecoin_electrum_server_list.yml
- assets/ethereum_server_list.yml - assets/ethereum_server_list.yml
- assets/nano_node_list.yml
- assets/nano_pow_node_list.yml
- assets/text/ - assets/text/
- assets/faq/ - assets/faq/
- assets/animation/ - assets/animation/

View file

@ -673,14 +673,20 @@
"matrix_green_dark_theme": "موضوع ماتريكس الأخضر الداكن", "matrix_green_dark_theme": "موضوع ماتريكس الأخضر الداكن",
"monero_light_theme": " ضوء مونيرو", "monero_light_theme": " ضوء مونيرو",
"etherscan_history": "Etherscan تاريخ", "etherscan_history": "Etherscan تاريخ",
"manage_nodes": "ﺪﻘﻌﻟﺍ ﺓﺭﺍﺩﺇ",
"template_name": "اسم القالب", "template_name": "اسم القالب",
"change_rep": "ﺏﻭﺪﻨﻣ ﺮﻴﻴﻐﺗ",
"change_rep_message": "؟ﻦﻴﻠﺜﻤﻤﻟﺍ ﺮﻴﻴﻐﺗ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ",
"manage_nodes": "ﺪﻘﻌﻟﺍ ﺓﺭﺍﺩﺇ",
"unsupported_asset": ".ﻡﻮﻋﺪﻣ ﻞﺻﺃ ﻉﻮﻧ ﻦﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .ﻞﺻﻷﺍ ﺍﺬﻬﻟ ءﺍﺮﺟﻹﺍ ﺍﺬﻫ ﻢﻋﺪﻧ ﻻ ﻦﺤﻧ",
"manage_pow_nodes": "PoW ﻁﺎﻘﻧ ﺓﺭﺍﺩﺇ",
"support_title_live_chat": "الدعم المباشر", "support_title_live_chat": "الدعم المباشر",
"support_description_live_chat": "حرة وسريعة! ممثلو الدعم المدربين متاحون للمساعدة", "support_description_live_chat": "حرة وسريعة! ممثلو الدعم المدربين متاحون للمساعدة",
"support_title_guides": "أدلة محفظة كعكة", "support_title_guides": "أدلة محفظة كعكة",
"support_description_guides": "توثيق ودعم القضايا المشتركة", "support_description_guides": "توثيق ودعم القضايا المشتركة",
"support_title_other_links": "روابط دعم أخرى", "support_title_other_links": "روابط دعم أخرى",
"support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى", "support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى",
"choose_derivation": "اختر اشتقاق المحفظة",
"new_first_wallet_text": "حافظ بسهولة على أمان العملة المشفرة",
"select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ", "select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
"auto_generate_subaddresses": "تلقائي توليد subddresses", "auto_generate_subaddresses": "تلقائي توليد subddresses",
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ", "save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ",

View file

@ -670,14 +670,20 @@
"matrix_green_dark_theme": "Зелена тъмна тема Matrix", "matrix_green_dark_theme": "Зелена тъмна тема Matrix",
"monero_light_theme": "Лека тема Monero", "monero_light_theme": "Лека тема Monero",
"etherscan_history": "История на Etherscan", "etherscan_history": "История на Etherscan",
"manage_nodes": "Управление на възли",
"template_name": "Име на шаблон", "template_name": "Име на шаблон",
"change_rep": "Смяна на представител",
"change_rep_message": "Сигурни ли сте, че искате да смените представителите?",
"manage_nodes": "Управление на възли",
"unsupported_asset": "Не поддържаме това действие за този актив. Моля, създайте или преминете към портфейл от поддържан тип актив.",
"manage_pow_nodes": "Управление на PoW възли",
"support_title_live_chat": "Подкрепа на живо", "support_title_live_chat": "Подкрепа на живо",
"support_description_live_chat": "Безплатно и бързо! Обучени представители на подкрепата са на разположение за подпомагане", "support_description_live_chat": "Безплатно и бързо! Обучени представители на подкрепата са на разположение за подпомагане",
"support_title_guides": "Ръководства за портфейл за торта", "support_title_guides": "Ръководства за портфейл за торта",
"support_description_guides": "Документация и подкрепа за общи проблеми", "support_description_guides": "Документация и подкрепа за общи проблеми",
"support_title_other_links": "Други връзки за поддръжка", "support_title_other_links": "Други връзки за поддръжка",
"support_description_other_links": "Присъединете се към нашите общности или се свържете с нас нашите партньори чрез други методи", "support_description_other_links": "Присъединете се към нашите общности или се свържете с нас нашите партньори чрез други методи",
"choose_derivation": "Изберете производно на портфейла",
"new_first_wallet_text": "Лесно пазете криптовалутата си в безопасност",
"select_destination": "Моля, изберете дестинация за архивния файл.", "select_destination": "Моля, изберете дестинация за архивния файл.",
"save_to_downloads": "Запазване в Изтегляния", "save_to_downloads": "Запазване в Изтегляния",
"select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.", "select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.",

View file

@ -672,12 +672,18 @@
"manage_nodes": "Spravovat uzly", "manage_nodes": "Spravovat uzly",
"etherscan_history": "Historie Etherscanu", "etherscan_history": "Historie Etherscanu",
"template_name": "Název šablony", "template_name": "Název šablony",
"change_rep": "Změna zástupce",
"change_rep_message": "Jste si jisti, že chcete změnit zástupce?",
"unsupported_asset": "Tuto akci u tohoto díla nepodporujeme. Vytvořte nebo přepněte na peněženku podporovaného typu aktiv.",
"manage_pow_nodes": "Správa uzlů PoW",
"support_title_live_chat": "Živá podpora", "support_title_live_chat": "Živá podpora",
"support_description_live_chat": "Zdarma a rychle! K dispozici jsou zástupci vyškolených podpůrných podpory", "support_description_live_chat": "Zdarma a rychle! K dispozici jsou zástupci vyškolených podpůrných podpory",
"support_title_guides": "Průvodce peněženkami dortu", "support_title_guides": "Průvodce peněženkami dortu",
"support_description_guides": "Dokumentace a podpora běžných otázek", "support_description_guides": "Dokumentace a podpora běžných otázek",
"support_title_other_links": "Další odkazy na podporu", "support_title_other_links": "Další odkazy na podporu",
"support_description_other_links": "Připojte se k našim komunitám nebo se k nám oslovte další metody", "support_description_other_links": "Připojte se k našim komunitám nebo se k nám oslovte další metody",
"choose_derivation": "Vyberte derivaci peněženky",
"new_first_wallet_text": "Snadno udržujte svou kryptoměnu v bezpečí",
"select_destination": "Vyberte cíl pro záložní soubor.", "select_destination": "Vyberte cíl pro záložní soubor.",
"save_to_downloads": "Uložit do Stažených souborů", "save_to_downloads": "Uložit do Stažených souborů",
"select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.", "select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.",

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