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-05 04:09:07 +03:00
committed by GitHub
co-authored by OmarHatem Justin Ehrenhofer fossephate
parent 6233422643
commit 4c60b178be
134 changed files with 8171 additions and 1055 deletions
@@ -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;
@@ -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) {
@@ -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);
@@ -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;
}
@@ -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());
}
}
}
@@ -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,
);
}
}
}
@@ -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}');
}
@@ -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,
);
}
}
+27 -14
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}');
}
@@ -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) {
@@ -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);
}
}
@@ -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
+26 -23
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();
}
@@ -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()}');
}
+3
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()}');;
}
@@ -0,0 +1,23 @@
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
part 'wallet_restore_choose_derivation_view_model.g.dart';
class WalletRestoreChooseDerivationViewModel = WalletRestoreChooseDerivationViewModelBase
with _$WalletRestoreChooseDerivationViewModel;
abstract class WalletRestoreChooseDerivationViewModelBase with Store {
WalletRestoreChooseDerivationViewModelBase({required this.derivationInfos})
: mode = WalletRestoreMode.seed {}
@observable
List<DerivationInfo> derivationInfos;
Future<List<DerivationInfo>> get derivations async {
return derivationInfos;
}
@observable
WalletRestoreMode mode;
}
+84 -41
View File
@@ -1,4 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@@ -16,25 +18,34 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart';
part 'wallet_restore_view_model.g.dart';
class WalletRestoreViewModel = WalletRestoreViewModelBase
with _$WalletRestoreViewModel;
class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel;
abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource,
{required WalletType type})
: availableModes =
(type == WalletType.monero || type == WalletType.haven || type == WalletType.ethereum)
? WalletRestoreMode.values
: [WalletRestoreMode.seed],
hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
: hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
hasRestoreFromPrivateKey = type == WalletType.ethereum,
hasRestoreFromPrivateKey =
type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano,
isButtonEnabled = false,
mode = WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
isButtonEnabled =
!hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector;
switch (type) {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
availableModes = WalletRestoreMode.values;
break;
case WalletType.nano:
case WalletType.banano:
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
break;
default:
availableModes = [WalletRestoreMode.seed];
break;
}
isButtonEnabled = !hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector;
walletCreationService.changeWalletType(type: type);
}
@@ -42,7 +53,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
static const electrumSeedMnemonicLength = 24;
static const electrumShortSeedMnemonicLength = 12;
final List<WalletRestoreMode> availableModes;
late List<WalletRestoreMode> availableModes;
final bool hasSeedLanguageSelector;
final bool hasBlockchainHeightLanguageSelector;
final bool hasRestoreFromPrivateKey;
@@ -58,38 +69,37 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final password = generateWalletPassword();
final height = options['height'] as int? ?? 0;
name = options['name'] as String;
DerivationType? derivationType = options["derivationType"] as DerivationType?;
String? derivationPath = options["derivationPath"] as String?;
if (mode == WalletRestoreMode.seed) {
final seed = options['seed'] as String;
switch (type) {
case WalletType.monero:
return monero!.createMoneroRestoreWalletFromSeedCredentials(
name: name,
height: height,
mnemonic: seed,
password: password);
name: name, height: height, mnemonic: seed, password: password);
case WalletType.bitcoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
password: password,
);
case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
name: name, mnemonic: seed, password: password);
case WalletType.haven:
return haven!.createHavenRestoreWalletFromSeedCredentials(
name: name,
height: height,
mnemonic: seed,
password: password);
name: name, height: height, mnemonic: seed, password: password);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
name: name, mnemonic: seed, password: password);
case WalletType.nano:
return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
derivationType: derivationType,
);
default:
break;
}
@@ -100,40 +110,73 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final spendKey = options['spendKey'] as String?;
final address = options['address'] as String?;
if (type == WalletType.monero) {
return monero!.createMoneroRestoreWalletFromKeysCredentials(
switch (type) {
case WalletType.monero:
return monero!.createMoneroRestoreWalletFromKeysCredentials(
name: name,
height: height,
spendKey: spendKey!,
viewKey: viewKey!,
address: address!,
password: password,
language: 'English');
}
language: 'English',
);
if (type == WalletType.haven) {
return haven!.createHavenRestoreWalletFromKeysCredentials(
case WalletType.haven:
return haven!.createHavenRestoreWalletFromKeysCredentials(
name: name,
height: height,
spendKey: spendKey!,
viewKey: viewKey!,
address: address!,
password: password,
language: 'English');
}
language: 'English',
);
if (type == WalletType.ethereum) {
return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name,
privateKey: options['private_key'] as String,
password: password,
);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name,
privateKey: options['private_key'] as String,
password: password,
);
case WalletType.nano:
return nano!.createNanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: options['private_key'] as String,
derivationType: options["derivationType"] as DerivationType,
);
default:
break;
}
}
throw Exception('Unexpected type: ${type.toString()}');
}
Future<List<DerivationType>> getDerivationTypes(dynamic options) async {
final seedKey = options['private_key'] as String?;
final mnemonic = options['seed'] as String?;
WalletType walletType = options['walletType'] as WalletType;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (type) {
case WalletType.nano:
return nanoUtil!.compareDerivationMethods(
mnemonic: mnemonic,
privateKey: seedKey,
node: node,
);
default:
break;
}
// throw Exception('Unexpected type: ${type.toString()}');
return [DerivationType.def];
}
@override
Future<WalletBase> process(WalletCredentials credentials) async {
if (mode == WalletRestoreMode.keys) {