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_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_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
- name: Add secrets

1
.gitignore vendored
View file

@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
lib/monero/monero.dart
lib/haven/haven.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_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_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_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

View file

@ -2297,4 +2297,4 @@ final englishWordlist = <String>[
'zero',
'zone',
'zoo'
];
];

View file

@ -89,4 +89,4 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
}
}
}

View file

@ -20,4 +20,4 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
: super(name: name, password: password, walletInfo: walletInfo);
final String wif;
}
}

View file

@ -100,4 +100,4 @@ class BitcoinWalletService extends WalletService<
await wallet.init();
return wallet;
}
}
}

View file

@ -56,4 +56,4 @@ class ElectrumWallletSnapshot {
regularAddressIndex: regularAddressIndex,
changeAddressIndex: changeAddressIndex);
}
}
}

View file

@ -90,6 +90,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.zrx,
CryptoCurrency.dydx,
CryptoCurrency.steth,
CryptoCurrency.banano,
];
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 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 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 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');
@ -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 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 banano = CryptoCurrency(title: 'BAN', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png');
static final Map<int, CryptoCurrency> _rawCurrencyMap =

View file

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

View file

@ -1,4 +1,4 @@
const CONTACT_TYPE_ID = 0;
const CONTACT_TYPE_ID = 0;
const NODE_TYPE_ID = 1;
const TRANSACTION_TYPE_ID = 2;
const TRADE_TYPE_ID = 3;
@ -11,3 +11,6 @@ const UNSPENT_COINS_INFO_TYPE_ID = 9;
const ANONPAY_INVOICE_INFO_TYPE_ID = 10;
const ADDRESS_INFO_TYPE_ID = 11;
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';
Uri createUriFromElectrumAddress(String address) =>
Uri.tryParse('tcp://$address')!;
Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
@HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable {
Node(
{this.login,
this.password,
this.useSSL,
this.trusted = false,
this.socksProxyAddress,
String? uri,
WalletType? type,}) {
Node({
this.login,
this.password,
this.useSSL,
this.trusted = false,
this.socksProxyAddress,
String? uri,
WalletType? type,
}) {
if (uri != null) {
uriRaw = uri;
}
@ -78,6 +78,13 @@ class Node extends HiveObject with Keyable {
return Uri.http(uriRaw, '');
case WalletType.ethereum:
return Uri.https(uriRaw, '');
case WalletType.nano:
case WalletType.banano:
if (isSSL) {
return Uri.https(uriRaw, '');
} else {
return Uri.http(uriRaw, '');
}
default:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -86,13 +93,13 @@ class Node extends HiveObject with Keyable {
@override
bool operator ==(other) =>
other is Node &&
(other.uriRaw == uriRaw &&
other.login == login &&
other.password == password &&
other.typeRaw == typeRaw &&
other.useSSL == useSSL &&
other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress);
(other.uriRaw == uriRaw &&
other.login == login &&
other.password == password &&
other.typeRaw == typeRaw &&
other.useSSL == useSSL &&
other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress);
@override
int get hashCode =>
@ -120,7 +127,9 @@ class Node extends HiveObject with Keyable {
try {
switch (type) {
case WalletType.monero:
return useSocksProxy ? requestNodeWithProxy(socksProxyAddress ?? '') : requestMoneroNode();
return useSocksProxy
? requestNodeWithProxy(socksProxyAddress ?? '')
: requestMoneroNode();
case WalletType.bitcoin:
return requestElectrumServer();
case WalletType.litecoin:
@ -129,6 +138,9 @@ class Node extends HiveObject with Keyable {
return requestMoneroNode();
case WalletType.ethereum:
return requestElectrumServer();
case WalletType.nano:
case WalletType.banano:
return requestNanoNode();
default:
return false;
}
@ -141,27 +153,23 @@ class Node extends HiveObject with Keyable {
final path = '/json_rpc';
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
final realm = 'monero-rpc';
final body = {
'jsonrpc': '2.0',
'id': '0',
'method': 'get_info'
};
final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'};
try {
final authenticatingClient = HttpClient();
authenticatingClient.addCredentials(
rpcUri,
realm,
HttpClientDigestCredentials(login ?? '', password ?? ''),
rpcUri,
realm,
HttpClientDigestCredentials(login ?? '', password ?? ''),
);
final http.Client client = ioc.IOClient(authenticatingClient);
final response = await client.post(
rpcUri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
rpcUri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
);
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(':')) {
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/wallet_type.dart';
abstract class WalletBase<
BalanceType extends Balance,
HistoryType extends TransactionHistoryBase,
abstract class WalletBase<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
TransactionType extends TransactionInfo> {
WalletBase(this.walletInfo);
@ -58,6 +56,9 @@ abstract class WalletBase<
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<PendingTransaction> createTransaction(Object credentials);

View file

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

View file

@ -6,29 +6,92 @@ import 'package:hive/hive.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)
class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
this.timestamp, this.dirPath, this.path, this.address, this.yatEid,
this.yatLastUsedAddressRaw, this.showIntroCakePayCard)
WalletInfo(
this.id,
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();
factory WalletInfo.external(
{required String id,
required String name,
required WalletType type,
required bool isRecovery,
required int restoreHeight,
required DateTime date,
required String dirPath,
required String path,
required String address,
bool? showIntroCakePayCard,
String yatEid ='',
String yatLastUsedAddressRaw = ''}) {
return WalletInfo(id, name, type, isRecovery, restoreHeight,
date.millisecondsSinceEpoch, dirPath, path, address,
yatEid, yatLastUsedAddressRaw, showIntroCakePayCard);
factory WalletInfo.external({
required String id,
required String name,
required WalletType type,
required bool isRecovery,
required int restoreHeight,
required DateTime date,
required String dirPath,
required String path,
required String address,
bool? showIntroCakePayCard,
String yatEid = '',
String yatLastUsedAddressRaw = '',
DerivationType? derivationType,
String? derivationPath,
}) {
return WalletInfo(
id,
name,
type,
isRecovery,
restoreHeight,
date.millisecondsSinceEpoch,
dirPath,
path,
address,
yatEid,
yatLastUsedAddressRaw,
showIntroCakePayCard,
derivationType,
derivationPath);
}
static const typeId = WALLET_INFO_TYPE_ID;
@ -79,6 +142,12 @@ class WalletInfo extends HiveObject {
@HiveField(15)
List<String>? usedAddresses;
@HiveField(16)
DerivationType? derivationType;
@HiveField(17)
String? derivationPath;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) {
@ -89,7 +158,7 @@ class WalletInfo extends HiveObject {
String get yatEmojiId => yatEid ?? '';
bool get isShowIntroCakePayCard {
if(showIntroCakePayCard == null) {
if (showIntroCakePayCard == null) {
return type != WalletType.haven;
}
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_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletService<N extends WalletCredentials,
RFS extends WalletCredentials, RFK extends WalletCredentials> {
abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
RFK extends WalletCredentials> {
WalletType getType();
Future<WalletBase> create(N credentials);

View file

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

View file

@ -234,7 +234,6 @@ extern "C"
}
void setUnlocked(bool unlocked);
};
Monero::Coins *m_coins;
@ -568,7 +567,7 @@ extern "C"
_preferred_inputs.insert(std::string(*preferred_inputs));
preferred_inputs++;
}
auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
std::string _payment_id;
Monero::PendingTransaction *transaction;

View file

@ -57,7 +57,7 @@ class MoneroWalletService extends WalletService<
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
static bool walletFilesExist(String path) =>
!File(path).existsSync() && !File('$path.keys').existsSync();

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

@ -161,4 +161,4 @@ class CWBitcoin extends Bitcoin {
@override
TransactionPriority getLitecoinTransactionPrioritySlow()
=> LitecoinTransactionPriority.slow;
}
}

View file

@ -27,6 +27,8 @@ class OnRamperBuyProvider {
return "LTC_LITECOIN";
case CryptoCurrency.xmr:
return "XMR_MONERO";
case CryptoCurrency.nano:
return "XNO_NANO";
default:
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}\$';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.banano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.usdc:
case CryptoCurrency.usdcpoly:
case CryptoCurrency.ape:
@ -177,6 +179,8 @@ class AddressValidator extends TextValidator {
return [34, 43, 63];
case CryptoCurrency.nano:
return [64, 65];
case CryptoCurrency.banano:
return [64, 65];
case CryptoCurrency.sc:
return [76];
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:cw_core/wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/utils/language_list.dart';
class SeedValidator extends Validator<MnemonicItem> {
@ -28,6 +29,9 @@ class SeedValidator extends Validator<MnemonicItem> {
return haven!.getMoneroWordList(language);
case WalletType.ethereum:
return ethereum!.getEthereumWordList(language);
case WalletType.nano:
case WalletType.banano:
return nano!.getNanoWordList(language);
default:
return [];
}

View file

@ -39,15 +39,11 @@ class WalletCreationService {
bool exists(String name) {
final walletName = name.toLowerCase();
return walletInfoSource
.values
.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
return walletInfoSource.values.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
}
bool typeExists(WalletType type) {
return walletInfoSource
.values
.any((walletInfo) => walletInfo.type == type);
return walletInfoSource.values.any((walletInfo) => walletInfo.type == type);
}
void checkIfExists(String name) {
@ -60,15 +56,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;
@ -78,15 +71,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromKeys(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;
@ -96,15 +86,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromSeed(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
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/receive_page_option.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_gift_card.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/home_settings_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_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/manage_nodes_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_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/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/restore/restore_from_qr_vm.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_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_restore_choose_derivation_view_model.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:cake_wallet/core/backup_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:cw_core/crypto_currency.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';
@ -214,6 +226,7 @@ final getIt = GetIt.instance;
var _isSetupFinished = false;
late Box<WalletInfo> _walletInfoSource;
late Box<Node> _nodeSource;
late Box<Node> _powNodeSource;
late Box<Contact> _contactSource;
late Box<Trade> _tradesSource;
late Box<Template> _templates;
@ -226,6 +239,7 @@ late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
Future<void> setup({
required Box<WalletInfo> walletInfoSource,
required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
required Box<Template> templates,
@ -237,6 +251,7 @@ Future<void> setup({
}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_powNodeSource = powNodeSource;
_contactSource = contactSource;
_tradesSource = tradesSource;
_templates = templates;
@ -259,6 +274,7 @@ Future<void> setup({
final settingsStore = await SettingsStoreBase.load(
nodeSource: _nodeSource,
powNodeSource: _powNodeSource,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
// Enforce darkTheme on platforms other than mobile till the design for other themes is completed
initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile
@ -271,6 +287,7 @@ Future<void> setup({
}
getIt.registerFactory<Box<Node>>(() => _nodeSource);
getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
getIt.registerSingleton(AuthenticationStore());
@ -401,7 +418,7 @@ Future<void> setup({
}
if (appStore.wallet != null) {
authStore.allowed();
if (appStore.wallet!.type == WalletType.ethereum) {
getIt.get<Web3WalletService>().init();
}
@ -449,7 +466,7 @@ Future<void> setup({
}, instanceName: 'login');
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
final appStore = getIt.get<AppStore>();
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl(appStore.wallet!));
@ -612,20 +629,30 @@ Future<void> setup({
editingWallet: editingWallet);
});
getIt.registerFactory(() {
getIt.registerFactory<NanoAccountListViewModel>(() {
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) {
return MoneroAccountListViewModel(wallet);
}
throw Exception(
'Unexpected wallet type: ${wallet.type} for generate MoneroAccountListViewModel');
'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel');
});
getIt.registerFactory(
() => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
getIt.registerFactory(
() => NanoAccountListPage(accountListViewModel: getIt.get<NanoAccountListViewModel>()));
/*getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
@ -653,6 +680,18 @@ Future<void> setup({
moneroAccountCreationViewModel:
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(() {
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
});
@ -696,6 +735,11 @@ Future<void> setup({
return NodeListViewModel(_nodeSource, appStore);
});
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
return PowNodeListViewModel(_powNodeSource, appStore);
});
getIt.registerFactory(
() => ConnectionSyncPage(getIt.get<DashboardViewModel>(), getIt.get<Web3WalletService>()),
);
@ -709,13 +753,23 @@ Future<void> setup({
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>((WalletType? type, _) =>
NodeCreateOrEditViewModel(
_nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
getIt.registerFactory(() => NanoChangeRepPage(getIt.get<AppStore>().wallet!));
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?>(
(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,
isSelected: isSelected));
@ -771,6 +825,8 @@ Future<void> setup({
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.ethereum:
return ethereum!.createEthereumWalletService(_walletInfoSource);
case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource);
default:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
}
@ -798,6 +854,15 @@ Future<void> setup({
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
(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>(
(TransactionInfo transactionInfo, _) {
final wallet = getIt.get<AppStore>().wallet!;
@ -911,8 +976,8 @@ Future<void> setup({
getIt.registerFactory(() => YatService());
getIt.registerFactory(() => AddressResolver(
yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory(() =>
AddressResolver(yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
(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;
}

View file

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

View file

@ -21,13 +21,12 @@ Future<List<Node>> loadDefaultNodes() async {
}
Future<List<Node>> loadBitcoinElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final serverListRaw = await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[];
for (final raw in loadedServerList) {
if (raw is Map) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.bitcoin;
serverList.add(node);
@ -38,8 +37,7 @@ Future<List<Node>> loadBitcoinElectrumServerList() async {
}
Future<List<Node>> loadLitecoinElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final serverListRaw = await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[];
@ -66,7 +64,7 @@ Future<List<Node>> loadDefaultHavenNodes() async {
nodes.add(node);
}
}
return nodes;
}
@ -86,17 +84,60 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
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 {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final havenNodes = await loadDefaultHavenNodes();
final nodes =
moneroNodes +
final ethereumNodes = await loadDefaultEthereumNodes();
final nanoNodes = await loadDefaultNanoNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList +
litecoinElectrumServerList +
havenNodes;
havenNodes +
ethereumNodes +
nanoNodes;
await nodeSource.clear();
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 currentHavenNodeIdKey = 'current_node_id_xhv';
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 currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';

View file

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

View file

@ -97,6 +97,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(DerivationTypeAdapter());
}
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(WalletTypeAdapter());
}
@ -128,6 +132,7 @@ Future<void> initializeAppConfigs() async {
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.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>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
@ -142,6 +147,7 @@ Future<void> initializeAppConfigs() async {
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
powNodes: powNodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
@ -153,12 +159,13 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21);
}
initialMigrationVersion: 22);
}
Future<void> initialSetup(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
@ -179,10 +186,12 @@ Future<void> initialSetup(
walletInfoSource: walletInfoSource,
contactSource: contactSource,
tradeSource: tradesSource,
nodes: nodes);
nodes: nodes,
powNodes: powNodes);
await setup(
walletInfoSource: walletInfoSource,
nodeSource: nodes,
powNodeSource: powNodes,
contactSource: contactSource,
tradesSource: tradesSource,
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());
}
});
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);
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
startCheckConnectionReaction(wallet, settingsStore);
await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
await getIt
.get<SharedPreferences>()
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
if (wallet.type == WalletType.monero) {
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
}
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) {
await updateHavenRate(fiatConversionStore);
@ -101,7 +108,7 @@ void startCurrentWalletChangeReaction(
if (wallet.type == WalletType.ethereum) {
final currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
for (final currency in currencies) {
() 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/dashboard/edit_token_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/receive/anonpay_invoice_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/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/display_settings_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/advanced_privacy_settings_view_model.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/material.dart';
import 'package:cake_wallet/routes.dart';
@ -111,36 +117,34 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWalletFromWelcome:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (availableWalletTypes.length == 1) {
Navigator.of(context.context).pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
} else {
Navigator.of(context.context).pushNamed(Routes.newWalletType);
}
}),
builder: (_) =>
getIt.get<SetupPinCodePage>(param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (availableWalletTypes.length == 1) {
Navigator.of(context.context)
.pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
} else {
Navigator.of(context.context).pushNamed(Routes.newWalletType);
}
}),
fullscreenDialog: true);
case Routes.newWalletType:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.newWallet, arguments: type)));
Navigator.of(context).pushNamed(Routes.newWallet, arguments: type)));
case Routes.newWallet:
final type = settings.arguments as WalletType;
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
return CupertinoPageRoute<void>(
builder: (_) => NewWalletPage(walletNewVM));
return CupertinoPageRoute<void>(builder: (_) => NewWalletPage(walletNewVM));
case Routes.setupPin:
Function(PinCodeState<PinCodeWidget>, String)? callback;
if (settings.arguments is Function(PinCodeState<PinCodeWidget>, String)) {
callback =
settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
callback = settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
}
return CupertinoPageRoute<void>(
@ -150,8 +154,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.restoreWallet, arguments: type),
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
param2: false));
case Routes.restoreOptions:
@ -166,66 +169,62 @@ Route<dynamic> createRoute(RouteSettings settings) {
if (isNewInstall) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (isSingleCoin) {
return Navigator.of(context.context)
.pushNamed(Routes.restoreWallet, arguments: availableWalletTypes.first);
}
return Navigator.pushNamed(
context.context, Routes.restoreWalletType);
return Navigator.pushNamed(context.context, Routes.restoreWalletType);
}),
fullscreenDialog: true);
} else if (isSingleCoin) {
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(
param1: availableWalletTypes.first
));
builder: (_) => getIt.get<WalletRestorePage>(param1: availableWalletTypes.first));
} else {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.restoreWallet, arguments: type),
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
param2: false));
}
case Routes.seed:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
builder: (_) => getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
case Routes.restoreWallet:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(
param1: settings.arguments as WalletType));
builder: (_) => getIt.get<WalletRestorePage>(param1: settings.arguments as WalletType));
case Routes.restoreWalletChooseDerivation:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
param1: settings.arguments as List<DerivationInfo>));
case Routes.sweepingWalletPage:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SweepingWalletPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
case Routes.dashboard:
return CupertinoPageRoute<void>(
settings: settings,
builder: (_) => getIt.get<DashboardPage>());
settings: settings, builder: (_) => getIt.get<DashboardPage>());
case Routes.send:
final initialPaymentRequest = settings.arguments as PaymentRequest?;
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
fullscreenDialog: true,
builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
case Routes.sendTemplate:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SendTemplatePage>());
fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
case Routes.receive:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ReceivePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ReceivePage>());
case Routes.addressPage:
return CupertinoPageRoute<void>(
@ -234,20 +233,21 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.transactionDetails:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<TransactionDetailsPage>(
param1: settings.arguments as TransactionInfo));
builder: (_) =>
getIt.get<TransactionDetailsPage>(param1: settings.arguments as TransactionInfo));
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
builder: (_) =>
getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
case Routes.disclaimer:
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
case Routes.readDisclaimer:
return CupertinoPageRoute<void>(
builder: (_) => DisclaimerPage(isReadOnly: true));
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
case Routes.changeRep:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
case Routes.seedLanguage:
final args = settings.arguments as List<dynamic>;
@ -256,8 +256,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(builder: (_) {
return SeedLanguage(
onConfirm: (context, lang) => Navigator.of(context)
.popAndPushNamed(redirectRoute, arguments: [type, lang]));
onConfirm: (context, lang) =>
Navigator.of(context).popAndPushNamed(redirectRoute, arguments: [type, lang]));
});
case Routes.walletList:
@ -267,15 +267,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletEdit:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<WalletEditPage>(
param1: settings.arguments as List<dynamic>));
builder: (_) => getIt.get<WalletEditPage>(param1: settings.arguments as List<dynamic>));
case Routes.auth:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: true));
param1: settings.arguments as OnAuthenticationFinished, param2: true));
case Routes.totpAuthCodePage:
final args = settings.arguments as TotpAuthArgumentsModel;
@ -290,9 +288,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (context) => WillPopScope(
child: getIt.get<AuthPage>(instanceName: 'login'),
onWillPop: () async =>
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
onWillPop: () async =>
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
false),
),
fullscreenDialog: true);
@ -302,53 +300,54 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true,
builder: (_) => WillPopScope(
child: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: false),
param1: settings.arguments as OnAuthenticationFinished, param2: false),
onWillPop: () async => false));
case Routes.connectionSync:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ConnectionSyncPage>());
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
case Routes.securityBackupPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SecurityBackupPage>());
fullscreenDialog: true, builder: (_) => getIt.get<SecurityBackupPage>());
case Routes.privacyPage:
case Routes.privacyPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<PrivacyPage>());
fullscreenDialog: true, builder: (_) => getIt.get<PrivacyPage>());
case Routes.displaySettingsPage:
case Routes.displaySettingsPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<DisplaySettingsPage>());
fullscreenDialog: true, builder: (_) => getIt.get<DisplaySettingsPage>());
case Routes.otherSettingsPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<OtherSettingsPage>());
fullscreenDialog: true, builder: (_) => getIt.get<OtherSettingsPage>());
case Routes.newNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?,
param2: args?['isSelected'] as bool?));
param1: args?['editingNode'] as Node?, 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:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem?));
case Routes.nanoAccountCreation:
return CupertinoPageRoute<String>(
builder: (_) =>
getIt.get<NanoAccountEditOrCreatePage>(param1: settings.arguments as NanoAccount?));
case Routes.addressBook:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ContactListPage>());
fullscreenDialog: true, builder: (_) => getIt.get<ContactListPage>());
case Routes.pickerAddressBook:
final selectedCurrency = settings.arguments as CryptoCurrency?;
@ -357,31 +356,26 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.addressBookAddContact:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ContactPage>(
param1: settings.arguments as ContactRecord?));
builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
case Routes.showKeys:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletKeysPage>(), fullscreenDialog: true);
case Routes.exchangeTrade:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangeTradePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTradePage>());
case Routes.exchangeConfirm:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<ExchangeConfirmPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeConfirmPage>());
case Routes.tradeDetails:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
builder: (_) => getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.orderDetails:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
case Routes.buy:
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>());
@ -390,18 +384,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
final args = settings.arguments as List;
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<BuyWebViewPage>(param1: args));
fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
case Routes.exchange:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ExchangePage>());
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
case Routes.exchangeTemplate:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangeTemplatePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
case Routes.rescan:
return MaterialPageRoute<void>(builder: (_) => getIt.get<RescanPage>());
@ -411,51 +401,41 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.preSeed:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
case Routes.backup:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
case Routes.editBackupPassword:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<EditBackupPasswordPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<EditBackupPasswordPage>());
case Routes.restoreFromBackup:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<RestoreFromBackupPage>());
fullscreenDialog: true, builder: (_) => getIt.get<RestoreFromBackupPage>());
case Routes.support:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SupportPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportPage>());
case Routes.supportLiveChat:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SupportChatPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportChatPage>());
case Routes.supportOtherLinks:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SupportOtherLinksPage>());
fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
case Routes.unspentCoinsList:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<UnspentCoinsListPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>());
case Routes.unspentCoinsDetails:
final args = settings.arguments as List;
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<UnspentCoinsDetailsPage>(
param1: args));
builder: (_) => getIt.get<UnspentCoinsDetailsPage>(param1: args));
case Routes.fullscreenQR:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<FullscreenQRPage>(
builder: (_) => getIt.get<FullscreenQRPage>(
param1: settings.arguments as QrViewData,
));
@ -466,26 +446,27 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.ioniaLoginPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaLoginPage>());
case Routes.ioniaCreateAccountPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCreateAccountPage>());
case Routes.ioniaManageCardsPage:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaManageCardsPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
case Routes.ioniaBuyGiftCardPage:
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:
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:
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:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
@ -501,57 +482,60 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.ioniaCustomTipPage:
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:
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:
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:
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:
final args = settings.arguments as List;
final paymentInfo = args.first as IoniaAnyPayPaymentInfo;
final commitedInfo = args[1] as AnyPayPaymentCommittedInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaPaymentStatusPage>(
param1: paymentInfo,
param2: commitedInfo));
return CupertinoPageRoute<void>(
builder: (_) =>
getIt.get<IoniaPaymentStatusPage>(param1: paymentInfo, param2: commitedInfo));
case Routes.webViewPage:
final args = settings.arguments as List;
final title = args.first as String;
final url = args[1] as Uri;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<WebViewPage>(
param1: title,
param2: url));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
case Routes.advancedPrivacySettings:
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => AdvancedPrivacySettingsPage(
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type),
));
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
));
case Routes.anonPayInvoicePage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage:
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
case Routes.anonPayDetailsPage:
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:
return PageRouteBuilder(
@ -560,12 +544,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.desktop_settings_page:
return CupertinoPageRoute<void>(
builder: (_) => DesktopSettingsPage());
return CupertinoPageRoute<void>(builder: (_) => DesktopSettingsPage());
case Routes.empty_no_route:
return MaterialPageRoute<void>(
builder: (_) => SizedBox.shrink());
return MaterialPageRoute<void>(builder: (_) => SizedBox.shrink());
case Routes.transactionsPage:
return CupertinoPageRoute<void>(
@ -602,12 +584,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
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:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(
body: Center(
child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
body: Center(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 restoreOptions = '/restore_options';
static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys';
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
static const dashboard = '/dashboard';
static const send = '/send';
static const transactionDetails = '/transaction_info';
@ -14,13 +15,16 @@ class Routes {
static const walletEdit = '/walletEdit';
static const disclaimer = '/disclaimer';
static const readDisclaimer = '/read_disclaimer';
static const changeRep = '/change_representative';
static const seedLanguage = '/seed_language';
static const walletList = '/view_model.wallet_list';
static const auth = '/auth';
static const newNode = '/new_node_list';
static const newPowNode = '/new_pow_node_list';
static const login = '/login';
static const splash = '/splash';
static const accountCreation = '/account_new';
static const nanoAccountCreation = '/nano_account_new';
static const addressBook = '/address_book';
static const pickerAddressBook = '/picker_address_book';
static const addressBookAddContact = '/address_book_add_contact';
@ -92,4 +96,6 @@ class Routes {
static const homeSettings = '/home_settings';
static const editToken = '/edit_token';
static const manageNodes = '/manage_nodes';
static const managePowNodes = '/manage_pow_nodes';
}

View file

@ -295,29 +295,6 @@ class _DashboardPageView extends BasePage {
);
_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);
var needToPresentYat = false;

View file

@ -78,23 +78,6 @@ class DesktopDashboardPage extends StatelessWidget {
}
_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 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 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 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);
Image _newWalletImage(BuildContext context) => Image.asset(
@ -141,6 +143,10 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default:
return nonWalletTypeIcon;
}

View file

@ -120,31 +120,6 @@ class AddressPage extends BasePage {
Widget body(BuildContext 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(
autoScroll: false,
disableScroll: true,

View file

@ -18,18 +18,21 @@ class MenuWidget extends StatefulWidget {
class MenuWidgetState extends State<MenuWidget> {
MenuWidgetState()
: this.menuWidth = 0,
this.screenWidth = 0,
this.screenHeight = 0,
this.headerHeight = 120,
this.tileHeight = 60,
this.fromTopEdge = 50,
this.fromBottomEdge = 25,
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
: this.menuWidth = 0,
this.screenWidth = 0,
this.screenHeight = 0,
this.headerHeight = 120,
this.tileHeight = 60,
this.fromTopEdge = 50,
this.fromBottomEdge = 25,
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
this.havenIcon = Image.asset('assets/images/haven_menu.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;
@ -47,6 +50,9 @@ class MenuWidgetState extends State<MenuWidget> {
Image litecoinIcon;
Image havenIcon;
Image ethereumIcon;
Image nanoIcon;
Image bananoIcon;
@override
void initState() {
@ -206,6 +212,10 @@ class MenuWidgetState extends State<MenuWidget> {
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default:
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 this.node,
required void Function(BuildContext context) onTap,
required bool isSelected})
required bool isSelected,
required this.isPow})
: super(title: title, onTap: onTap, isSelected: isSelected);
final Node node;
final bool isPow;
@override
Widget buildLeading(BuildContext context) {
@ -33,7 +35,7 @@ class NodeListRow extends StandardListRow {
@override
Widget buildTrailing(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.newNode,
onTap: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode,
arguments: {'editingNode': node, 'isSelected': isSelected}),
child: Container(
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/receive_page_theme.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/themes/theme_base.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/src/screens/receive/widgets/qr_widget.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
class ReceivePage extends BasePage {
ReceivePage({required this.addressListViewModel})
@ -99,7 +100,9 @@ class ReceivePage extends BasePage {
@override
Widget body(BuildContext context) {
return (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven)
addressListViewModel.type == WalletType.haven ||
addressListViewModel.type == WalletType.nano ||
addressListViewModel.type == WalletType.banano)
? KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
@ -137,9 +140,18 @@ class ReceivePage extends BasePage {
if (item is WalletAccountListHeader) {
cell = HeaderTile(
onTap: () async => await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>()),
onTap: () async {
if (addressListViewModel.type == WalletType.monero ||
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,
icon: Icon(
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 {
WalletRestoreFromKeysFrom({
required this.walletRestoreViewModel,
required this.onPrivateKeyChange,
required this.displayPrivateKeyField,
required this.onHeightOrDateEntered,
Key? key,
@ -20,6 +21,7 @@ class WalletRestoreFromKeysFrom extends StatefulWidget {
final Function(bool) onHeightOrDateEntered;
final WalletRestoreViewModel walletRestoreViewModel;
final void Function(String)? onPrivateKeyChange;
final bool displayPrivateKeyField;
@override
@ -54,6 +56,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
if (privateKeyController.text.isNotEmpty) {
widget.onHeightOrDateEntered(true);
}
widget.onPrivateKeyChange?.call(privateKeyController.text);
});
}
@ -62,8 +65,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
nameController.dispose();
addressController.dispose();
viewKeyController.dispose();
spendKeyController.dispose();
privateKeyController.dispose();
spendKeyController.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/src/widgets/keyboard_done_button.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:flutter/material.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
@ -69,6 +75,12 @@ class WalletRestorePage extends BasePage {
_pages.add(WalletRestoreFromKeysFrom(
key: walletRestoreFromKeysFormKey,
walletRestoreViewModel: walletRestoreViewModel,
onPrivateKeyChange: (String seed) {
if (walletRestoreViewModel.type == WalletType.nano ||
walletRestoreViewModel.type == WalletType.banano) {
walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
}
},
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
break;
@ -97,6 +109,8 @@ class WalletRestorePage extends BasePage {
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey;
final FocusNode _blockHeightFocusNode;
DerivationType derivationType = DerivationType.unknown;
String? derivationPath = null;
@override
Widget body(BuildContext context) {
@ -185,7 +199,9 @@ class WalletRestorePage extends BasePage {
child: Observer(
builder: (context) {
return LoadingPrimaryButton(
onPressed: _confirmForm,
onPressed: () async {
await _confirmForm(context);
},
text: S.of(context).restore_recover,
color: Theme.of(context)
.extension<WalletListTheme>()!
@ -217,18 +233,34 @@ class WalletRestorePage extends BasePage {
return false;
}
if ((walletRestoreViewModel.type == WalletType.bitcoin ||
walletRestoreViewModel.type == WalletType.litecoin) &&
if ((walletRestoreViewModel.type == WalletType.litecoin) &&
(seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength &&
seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) {
return false;
}
// bip39:
const validSeedLengths = [12, 18, 24];
if (walletRestoreViewModel.type == WalletType.bitcoin &&
!(validSeedLengths.contains(seedWords.length))) {
return false;
}
final words =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
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() {
final credentials = <String, dynamic>{};
@ -243,10 +275,12 @@ class WalletRestorePage extends BasePage {
credentials['name'] =
walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
} else {
} else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
if (walletRestoreViewModel.hasRestoreFromPrivateKey) {
credentials['private_key'] =
walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} else {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
@ -254,31 +288,81 @@ class WalletRestorePage extends BasePage {
walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
credentials['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;
}
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
FocusManager.instance.primaryFocus?.unfocus();
final formContext = walletRestoreViewModel.mode == WalletRestoreMode.seed
? walletRestoreFromSeedFormKey.currentContext
: walletRestoreFromKeysFormKey.currentContext;
final formKey = walletRestoreViewModel.mode == WalletRestoreMode.seed
? walletRestoreFromSeedFormKey.currentState!.formKey
: walletRestoreFromKeysFormKey.currentState!.formKey;
late BuildContext? formContext;
late GlobalKey<FormState>? 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
? walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text
: walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
if (!formKey.currentState!.validate()) {
if (!formKey!.currentState!.validate()) {
return;
}
@ -287,6 +371,54 @@ class WalletRestorePage extends BasePage {
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());
}

View file

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

View file

@ -167,8 +167,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
buttonColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintStyle: TextStyle(
@ -196,7 +198,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
child: BaseTextFormField(
controller: extractedAddressController,
readOnly: true,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
borderColor: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
validator: sendViewModel.addressValidator)),
@ -251,7 +255,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
child: Container(
height: 32,
decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonColor,
borderRadius: BorderRadius.all(
Radius.circular(6),
)),
@ -263,7 +269,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle(
fontSize: 12,
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,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: output.sendAll
@ -322,7 +332,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
onTap: () async => output.setSendAll(),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonColor,
borderRadius: BorderRadius.all(
Radius.circular(6),
),
@ -334,7 +346,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle(
fontSize: 12,
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(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor),
),
),
Text(
@ -372,7 +388,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle(
fontSize: 12,
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',
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
color:
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
fontWeight: FontWeight.w500,
fontSize: 14),
),
@ -418,7 +438,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
controller: noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintText: S.of(context).note_optional,
@ -429,73 +450,76 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
),
),
Observer(
builder: (_) => GestureDetector(
onTap: () => _setTransactionPriority(context),
child: Container(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).send_estimated_fee,
style: TextStyle(
fontSize: 12,
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,
),
)
],
if (sendViewModel.hasFees)
Observer(
builder: (_) => GestureDetector(
onTap: () => _setTransactionPriority(context),
child: Container(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).send_estimated_fee,
style: TextStyle(
fontSize: 12,
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,
),
)
],
),
)
],
),
),
),
),
),
if (sendViewModel.hasCoinControl)
Padding(
padding: EdgeInsets.only(top: 6),

View file

@ -70,6 +70,21 @@ class ConnectionSyncPage extends BasePage {
handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
),
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) ...[
WalletConnectTile(
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/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/pow_node_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
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
String get title => S.current.manage_nodes;
@ -34,7 +38,8 @@ class ManageNodesPage extends BasePage {
SizedBox(height: 20),
Observer(
builder: (BuildContext context) {
int itemsCount = nodeListViewModel.nodes.length;
int itemsCount =
nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
return Flexible(
child: SectionStandardList(
sectionCount: 1,
@ -43,12 +48,19 @@ class ManageNodesPage extends BasePage {
itemBuilder: (_, index) {
return Observer(
builder: (context) {
final node = nodeListViewModel.nodes[index];
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final node =
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(
title: node.uriRaw,
node: node,
isSelected: isSelected,
isPow: false,
onTap: (_) async {
if (isSelected) {
return;
@ -59,12 +71,17 @@ class ManageNodesPage extends BasePage {
builder: (BuildContext context) {
return AlertWithTwoActions(
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,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node);
if (isPow) {
await powNodeListViewModel!.setAsCurrent(node);
} else {
await nodeListViewModel!.setAsCurrent(node);
}
Navigator.of(context).pop();
},
);

View file

@ -28,13 +28,20 @@ class OtherSettingsPage extends BasePage {
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
if (!_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
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(
title: S.current.default_buy_provider,
items: BuyProviderType.values,
@ -50,7 +57,7 @@ class OtherSettingsPage extends BasePage {
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Spacer(),
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 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 nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final scrollController = ScrollController();
final double tileHeight = 60;
Flushbar<void>? _progressBar;
@ -242,6 +243,8 @@ class WalletListBodyState extends State<WalletListBody> {
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
default:
return nonWalletTypeIcon;
}

View file

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

View file

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

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/nano/nano.dart';
class PaymentRequest {
PaymentRequest(this.address, this.amount, this.note, this.scheme);
@ -10,11 +12,18 @@ class PaymentRequest {
if (uri != null) {
address = uri.path;
amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
note = uri.queryParameters['tx_description']
?? uri.queryParameters['message'] ?? "";
note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
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);
}
@ -22,4 +31,4 @@ class PaymentRequest {
final String amount;
final String note;
final String scheme;
}
}

View file

@ -15,12 +15,11 @@ import 'package:collection/collection.dart';
part 'contact_list_view_model.g.dart';
class ContactListViewModel = ContactListViewModelBase
with _$ContactListViewModel;
class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
abstract class ContactListViewModelBase with Store {
ContactListViewModelBase(this.contactSource, this.walletInfoSource,
this._currency, this.settingsStore)
ContactListViewModelBase(
this.contactSource, this.walletInfoSource, this._currency, this.settingsStore)
: contacts = ObservableList<ContactRecord>(),
walletContacts = [],
isAutoGenerateEnabled =
@ -48,6 +47,12 @@ abstract class ContactListViewModelBase with Store {
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();
@computed
List<ContactRecord> get contactsToShow => contacts
.where((element) => _isValidForCurrency(element))
.toList();
List<ContactRecord> get contactsToShow =>
contacts.where((element) => _isValidForCurrency(element)).toList();
@computed
List<WalletContact> get walletContactsToShow => walletContacts
.where((element) => _isValidForCurrency(element))
.toList();
List<WalletContact> get walletContactsToShow =>
walletContacts.where((element) => _isValidForCurrency(element)).toList();
bool _isValidForCurrency(ContactBase element) {
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.ordersStore,
required this.anonpayTransactionsStore})
: isOutdatedElectrumWallet = false,
hasSellAction = false,
hasBuyAction = false,
hasExchangeAction = false,
isShowFirstYatIntroduction = false,
isShowSecondYatIntroduction = false,
isShowThirdYatIntroduction = false,
filterItems = {
S.current.transactions: [
FilterItem(
value: () => transactionFilterStore.displayAll,
caption: S.current.all_transactions,
onChanged: transactionFilterStore.toggleAll),
FilterItem(
value: () => transactionFilterStore.displayIncoming,
caption: S.current.incoming,
onChanged: transactionFilterStore.toggleIncoming),
FilterItem(
value: () => transactionFilterStore.displayOutgoing,
caption: S.current.outgoing,
onChanged: transactionFilterStore.toggleOutgoing),
// FilterItem(
// value: () => false,
// caption: S.current.transactions_by_date,
// onChanged: null),
],
S.current.trades: [
FilterItem(
value: () => tradeFilterStore.displayAllTrades,
caption: S.current.all_trades,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)),
FilterItem(
value: () => tradeFilterStore.displayChangeNow,
caption: ExchangeProviderDescription.changeNow.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
FilterItem(
value: () => tradeFilterStore.displaySideShift,
caption: ExchangeProviderDescription.sideShift.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
FilterItem(
value: () => tradeFilterStore.displaySimpleSwap,
caption: ExchangeProviderDescription.simpleSwap.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
FilterItem(
value: () => tradeFilterStore.displayTrocador,
caption: ExchangeProviderDescription.trocador.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
FilterItem(
value: () => tradeFilterStore.displayExolix,
caption: ExchangeProviderDescription.exolix.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
]
},
subname = '',
name = appStore.wallet!.name,
type = appStore.wallet!.type,
transactions = ObservableList<TransactionListItem>(),
wallet = appStore.wallet! {
: hasSellAction = false,
hasBuyAction = false,
hasExchangeAction = false,
isShowFirstYatIntroduction = false,
isShowSecondYatIntroduction = false,
isShowThirdYatIntroduction = false,
filterItems = {
S.current.transactions: [
FilterItem(
value: () => transactionFilterStore.displayAll,
caption: S.current.all_transactions,
onChanged: transactionFilterStore.toggleAll),
FilterItem(
value: () => transactionFilterStore.displayIncoming,
caption: S.current.incoming,
onChanged:transactionFilterStore.toggleIncoming),
FilterItem(
value: () => transactionFilterStore.displayOutgoing,
caption: S.current.outgoing,
onChanged: transactionFilterStore.toggleOutgoing),
// FilterItem(
// value: () => false,
// caption: S.current.transactions_by_date,
// onChanged: null),
],
S.current.trades: [
FilterItem(
value: () => tradeFilterStore.displayAllTrades,
caption: S.current.all_trades,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.all)),
FilterItem(
value: () => tradeFilterStore.displayChangeNow,
caption: ExchangeProviderDescription.changeNow.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
FilterItem(
value: () => tradeFilterStore.displaySideShift,
caption: ExchangeProviderDescription.sideShift.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
FilterItem(
value: () => tradeFilterStore.displaySimpleSwap,
caption: ExchangeProviderDescription.simpleSwap.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
FilterItem(
value: () => tradeFilterStore.displayTrocador,
caption: ExchangeProviderDescription.trocador.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
FilterItem(
value: () => tradeFilterStore.displayExolix,
caption: ExchangeProviderDescription.exolix.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
]
},
subname = '',
name = appStore.wallet!.name,
type = appStore.wallet!.type,
transactions = ObservableList<TransactionListItem>(),
wallet = appStore.wallet! {
name = wallet.name;
type = wallet.type;
isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
isShowFirstYatIntroduction = false;
isShowSecondYatIntroduction = false;
isShowThirdYatIntroduction = false;
@ -151,6 +148,11 @@ abstract class DashboardViewModelBase with Store {
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);
connectMapToListWithTransform(
@ -315,12 +317,16 @@ abstract class DashboardViewModelBase with Store {
ReactionDisposer? _onMoneroBalanceChangeReaction;
@observable
bool isOutdatedElectrumWallet;
@computed
bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
Future<void> reconnect() async {
final node = appStore.settingsStore.getCurrentNode(wallet.type);
await wallet.connectToNode(node: node);
if (hasPowNodes) {
final powNode = settingsStore.getCurrentPowNode(wallet.type);
await wallet.connectToPowNode(node: powNode);
}
}
@action
@ -333,8 +339,6 @@ abstract class DashboardViewModelBase with Store {
this.wallet = wallet;
type = wallet.type;
name = wallet.name;
isOutdatedElectrumWallet =
wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
updateActions();
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/ethereum/ethereum.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_info.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 {
TransactionListItem(
{required this.transaction,
required this.balanceViewModel,
required this.settingsStore});
{required this.transaction, required this.balanceViewModel, required this.settingsStore});
final TransactionInfo transaction;
final BalanceViewModel balanceViewModel;
@ -34,10 +33,9 @@ class TransactionListItem extends ActionListItem with Keyable {
dynamic get keyIndex => transaction.id;
String get formattedCryptoAmount {
return displayMode == BalanceDisplayMode.hiddenBalance
? '---'
: transaction.amountFormatted();
return displayMode == BalanceDisplayMode.hiddenBalance ? '---' : transaction.amountFormatted();
}
String get formattedTitle {
if (transaction.direction == TransactionDirection.incoming) {
return S.current.received;
@ -57,33 +55,47 @@ class TransactionListItem extends ActionListItem with Keyable {
if (transaction.direction == TransactionDirection.incoming) {
if (balanceViewModel.wallet.type == WalletType.monero ||
balanceViewModel.wallet.type == WalletType.haven) {
return formattedPendingStatus;
}
return formattedPendingStatus;
}
return transaction.isPending ? S.current.pending : '';
}
return transaction.isPending ? S.current.pending : '';
}
String get formattedFiatAmount {
var amount = '';
switch(balanceViewModel.wallet.type) {
switch (balanceViewModel.wallet.type) {
case WalletType.monero:
amount = calculateFiatAmountRaw(
cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price);
cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.bitcoin:
case WalletType.litecoin:
amount = calculateFiatAmountRaw(
cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
price: price);
cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.haven:
final asset = haven!.assetOfTransaction(transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
price: price);
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
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;
case WalletType.ethereum:
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);

View file

@ -86,13 +86,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
super(appStore: appStore) {
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
_setProviders();
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
const excludeDepositCurrencies = [CryptoCurrency.btt];
const excludeReceiveCurrencies = [
CryptoCurrency.xlm,
CryptoCurrency.xrp,
CryptoCurrency.bnb,
CryptoCurrency.btt,
CryptoCurrency.nano
CryptoCurrency.btt
];
_initialPairBasedOnWallet();
@ -703,6 +702,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.eth;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.nano:
depositCurrency = CryptoCurrency.nano;
receiveCurrency = CryptoCurrency.xmr;
break;
default:
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:
node = getEthereumDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.nano:
node = getNanoDefaultNode(nodes: _nodeSource)!;
break;
default:
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/entities/contact_record.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.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/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:cw_core/transaction_priority.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:cw_core/pending_transaction.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/monero/monero.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 priorities = priorityForWalletType(wallet.type);
if (!priorityForWalletType(wallet.type).contains(priority)) {
if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[wallet.type] = priorities.first;
}
@ -99,15 +101,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed
String get pendingTransactionFiatAmount {
if (pendingTransaction == null) {
return '0.00';
}
try {
if (pendingTransaction != null) {
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
cryptoAmount: pendingTransaction!.amountFormatted);
return fiat;
} else {
return '0.00';
}
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
cryptoAmount: pendingTransaction!.amountFormatted);
return fiat;
} catch (_) {
return '0.00';
}
@ -191,6 +193,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
bool get isElectrumWallet =>
wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin;
@computed
bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
@observable
CryptoCurrency selectedCryptoCurrency;
@ -316,6 +321,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = TransactionCommitting();
await pendingTransaction!.commit();
if (walletType == WalletType.nano) {
nano!.updateTransactions(wallet);
}
if (pendingTransaction!.id.isNotEmpty) {
_settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription(
@ -380,6 +389,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return ethereum!.createEthereumTransactionCredentials(outputs,
priority: priority, currency: selectedCryptoCurrency);
case WalletType.nano:
return nano!.createNanoTransactionCredentials(
outputs,
);
default:
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 priorities = priorityForWalletType(_wallet.type);
if (!priorities.contains(priority)) {
if (!priorities.contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[_wallet.type] = priorities.first;
}
}
@ -50,6 +50,14 @@ abstract class OtherSettingsViewModelBase with Store {
}
@computed
bool get changeRepresentativeEnabled {
if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
return true;
}
return false;
}
BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; }
String getDisplayPriority(dynamic priority) {

View file

@ -47,6 +47,9 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.ethereum:
_addEthereumListItems(tx, dateFormat);
break;
case WalletType.nano:
_addNanoListItems(tx, dateFormat);
break;
default:
break;
}
@ -116,6 +119,10 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://explorer.havenprotocol.org/search?value=${txId}';
case WalletType.ethereum:
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:
return '';
}
@ -133,6 +140,10 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'explorer.havenprotocol.org';
case WalletType.ethereum:
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:
return '';
}
@ -221,4 +232,18 @@ abstract class TransactionDetailsViewModelBase with Store {
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 {
WalletAddressListViewModelBase({
required AppStore appStore,
@ -176,6 +192,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
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()}');
}
@ -252,7 +272,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
@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
bool get showElectrumAddressDisclaimer =>
@ -269,11 +293,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void _init() {
_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(WalletAddressListHeader());
if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
_baseItems.add(WalletAddressListHeader());
}
}
@action

View file

@ -35,18 +35,16 @@ abstract class WalletCreationVMBase with Store {
final Box<WalletInfo> _walletInfoSource;
final AppStore _appStore;
bool nameExists(String name)
=> walletCreationService.exists(name);
bool nameExists(String name) => walletCreationService.exists(name);
bool typeExists(WalletType type)
=> walletCreationService.typeExists(type);
bool typeExists(WalletType type) => walletCreationService.typeExists(type);
Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async {
final type = restoreWallet?.type ?? this.type;
try {
state = IsExecutingState();
if (name.isEmpty) {
name = await generateName();
name = await generateName();
}
walletCreationService.checkIfExists(name);
@ -55,17 +53,21 @@ abstract class WalletCreationVMBase with Store {
final credentials = restoreWallet != null
? getCredentialsFromRestoredWallet(options, restoreWallet)
: getCredentials(options);
final walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, type),
name: name,
type: type,
isRecovery: isRecovery,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: path,
dirPath: dirPath,
address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven);
id: WalletBase.idFor(name, type),
name: name,
type: type,
isRecovery: isRecovery,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: path,
dirPath: dirPath,
address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationPath: credentials.derivationPath,
derivationType: credentials.derivationType,
);
credentials.walletInfo = walletInfo;
final wallet = restoreWallet != null
? await processFromRestoredWallet(credentials, restoreWallet)
@ -80,15 +82,16 @@ abstract class WalletCreationVMBase with Store {
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();
Future<WalletBase> process(WalletCredentials credentials) =>
throw UnimplementedError();
WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) =>
throw UnimplementedError();
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) =>
Future<WalletBase> processFromRestoredWallet(
WalletCredentials credentials, RestoredWallet restoreWallet) =>
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:cw_core/transaction_direction.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!),
]);
}
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 {
@ -128,6 +145,10 @@ abstract class WalletKeysViewModelBase with Store {
return 'haven-wallet';
case WalletType.ethereum:
return 'ethereum-wallet';
case WalletType.nano:
return 'nano-wallet';
case WalletType.banano:
return 'banano-wallet';
default:
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:mobx/mobx.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:cw_core/wallet_base.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);
case WalletType.ethereum:
return ethereum!.createEthereumNewWalletCredentials(name: name);
case WalletType.nano:
return nano!.createNanoNewWalletCredentials(name: name);
default:
throw Exception('Unexpected type: ${type.toString()}');;
}

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