Cw 78 ethereum (#862)

* Add initial flow for ethereum

* Add initial create Eth wallet flow

* Complete Ethereum wallet creation flow

* 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 

* - 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

* 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

* 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` 

* Update build guide

* Fix fetching fiat rate taking a lot of time by only fetching enabled tokens only and making the API calls in parallel not sequential

* Update transactions on a periodic basis

* For fee, use ETH spot price, not ERC-20 spot price

* Add Etherscan History privacy option to enable/disable Etherscan API

* Show estimated fee amounts in the send screen

* fix send fiat fields parsing issue

* Fix transactions estimated fee less than actual fee

* handle balance sorting when balance is disabled
Handle empty transactions list

* Fix Delete Ethereum wallet
Fix balance < 0.01

* Fix Decimal place for Ethereum amount
Fix sending amount issue

* Change words count

* Remove balance hint and Full balance row from Ethereum wallets

* support changing the asset type in send templates

* Fix Templates for ERC tokens issues

* Fix conflicts in send templates

* Disable batch sending in Ethereum

* Fix Fee calculation with different priorities

* Fix Conflicts with main

* Add offline error to ignored exceptions

---------

Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>
This commit is contained in:
Omar Hatem
2023-08-04 20:01:49 +03:00
committed by GitHub
co-authored by Justin Ehrenhofer
parent 4120394121
commit 3ce4000dcf
137 changed files with 7164 additions and 1492 deletions
@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_info.dart';
@@ -57,11 +58,15 @@ abstract class ContactListViewModelBase with Store {
@computed
List<ContactRecord> get contactsToShow => contacts
.where((element) => _currency == null || element.type == _currency)
.where((element) => _isValidForCurrency(element))
.toList();
@computed
List<WalletContact> get walletContactsToShow => walletContacts
.where((element) => _currency == null || element.type == _currency)
.where((element) => _isValidForCurrency(element))
.toList();
bool _isValidForCurrency(ContactBase element) {
return _currency == null || element.type == _currency || element.type.title == _currency!.tag;
}
}
@@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/balance.dart';
@@ -79,6 +80,15 @@ abstract class BalanceViewModelBase with Store {
@computed
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
@computed
bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum;
@computed
SortBalanceBy get sortBalanceBy => settingsStore.sortBalanceBy;
@computed
bool get pinNativeToken => settingsStore.pinNativeTokenAtTop;
@computed
String get asset {
final typeFormatted = walletTypeToString(appStore.wallet!.type);
@@ -109,6 +119,7 @@ abstract class BalanceViewModelBase with Store {
switch(wallet.type) {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
return S.current.xmr_available_balance;
default:
return S.current.confirmed;
@@ -120,6 +131,7 @@ abstract class BalanceViewModelBase with Store {
switch(wallet.type) {
case WalletType.monero:
case WalletType.haven:
case WalletType.ethereum:
return S.current.xmr_full_balance;
default:
return S.current.unconfirmed;
@@ -262,32 +274,58 @@ abstract class BalanceViewModelBase with Store {
});
}
@computed
bool get hasAdditionalBalance => wallet.type != WalletType.ethereum;
@computed
List<BalanceRecord> get formattedBalances {
final balance = balances.values.toList();
balance.sort((BalanceRecord a, BalanceRecord b) {
if (b.asset == CryptoCurrency.xhv) {
return 1;
}
if (b.asset == CryptoCurrency.xusd) {
if (a.asset == CryptoCurrency.xhv) {
return -1;
if (wallet.currency == CryptoCurrency.xhv) {
if (b.asset == CryptoCurrency.xhv) {
return 1;
}
return 1;
if (b.asset == CryptoCurrency.xusd) {
if (a.asset == CryptoCurrency.xhv) {
return -1;
}
return 1;
}
if (b.asset == CryptoCurrency.xbtc) {
return 1;
}
if (b.asset == CryptoCurrency.xeur) {
return 1;
}
return 0;
}
if (b.asset == CryptoCurrency.xbtc) {
return 1;
if (pinNativeToken) {
if (b.asset == wallet.currency) return 1;
if (a.asset == wallet.currency) return -1;
}
if (b.asset == CryptoCurrency.xeur) {
return 1;
}
switch (sortBalanceBy) {
case SortBalanceBy.FiatBalance:
final aFiatBalance = _getFiatBalance(
price: fiatConvertationStore.prices[a.asset] ?? 0, cryptoAmount: a.availableBalance);
final bFiatBalance = _getFiatBalance(
price: fiatConvertationStore.prices[b.asset] ?? 0, cryptoAmount: b.availableBalance);
return 0;
return (double.tryParse(bFiatBalance) ?? 0)
.compareTo((double.tryParse(aFiatBalance)) ?? 0);
case SortBalanceBy.GrossBalance:
return (double.tryParse(b.availableBalance) ?? 0)
.compareTo(double.tryParse(a.availableBalance) ?? 0);
case SortBalanceBy.Alphabetical:
return a.asset.title.compareTo(b.asset.title);
}
});
return balance;
@@ -335,7 +373,7 @@ abstract class BalanceViewModelBase with Store {
}
String _getFiatBalance({required double price, String? cryptoAmount}) {
if (cryptoAmount == null || cryptoAmount.isEmpty) {
if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) {
return '0.00';
}
@@ -0,0 +1,121 @@
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:mobx/mobx.dart';
part 'home_settings_view_model.g.dart';
class HomeSettingsViewModel = HomeSettingsViewModelBase with _$HomeSettingsViewModel;
abstract class HomeSettingsViewModelBase with Store {
HomeSettingsViewModelBase(this._settingsStore, this._balanceViewModel)
: tokens = ObservableSet<Erc20Token>() {
_updateTokensList();
}
final SettingsStore _settingsStore;
final BalanceViewModel _balanceViewModel;
final ObservableSet<Erc20Token> tokens;
@observable
String searchText = '';
@computed
SortBalanceBy get sortBalanceBy => _settingsStore.sortBalanceBy;
@action
void setSortBalanceBy(SortBalanceBy value) {
_settingsStore.sortBalanceBy = value;
_updateTokensList();
}
@computed
bool get pinNativeToken => _settingsStore.pinNativeTokenAtTop;
@action
void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
Future<void> addErc20Token(Erc20Token token) async {
await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
_updateTokensList();
_updateFiatPrices(token);
}
Future<void> deleteErc20Token(Erc20Token token) async {
await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
_updateTokensList();
}
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
void _updateFiatPrices(Erc20Token token) async {
try {
_balanceViewModel.fiatConvertationStore.prices[token] =
await FiatConversionService.fetchPrice(
crypto: token,
fiat: _settingsStore.fiatCurrency,
torOnly: _settingsStore.fiatApiMode == FiatApiMode.torOnly);
} catch (_) {}
}
void changeTokenAvailability(Erc20Token token, bool value) async {
token.enabled = value;
ethereum!.addErc20Token(_balanceViewModel.wallet, token);
_refreshTokensList();
}
@action
void _updateTokensList() {
int _sortFunc(Erc20Token e1, Erc20Token e2) {
int index1 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e1);
int index2 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e2);
if (e1.enabled && !e2.enabled) {
return -1;
} else if (e2.enabled && !e1.enabled) {
return 1;
} else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically
return e1.name.compareTo(e2.name);
}
return index1.compareTo(index2);
}
tokens.clear();
tokens.addAll(ethereum!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..sort(_sortFunc));
}
@action
void _refreshTokensList() {
final _tokens = Set.of(tokens);
tokens.clear();
tokens.addAll(_tokens);
}
@action
void changeSearchText(String text) {
searchText = text;
_updateTokensList();
}
bool _matchesSearchText(Erc20Token asset) {
return searchText.isEmpty ||
asset.fullName!.toLowerCase().contains(searchText.toLowerCase()) ||
asset.title.toLowerCase().contains(searchText.toLowerCase()) ||
asset.contractAddress == searchText;
}
}
@@ -1,5 +1,6 @@
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:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@@ -84,6 +85,13 @@ class TransactionListItem extends ActionListItem with Keyable {
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;
default:
break;
}
@@ -699,6 +699,10 @@ abstract class ExchangeViewModelBase with Store {
depositCurrency = CryptoCurrency.xhv;
receiveCurrency = CryptoCurrency.btc;
break;
case WalletType.ethereum:
depositCurrency = CryptoCurrency.eth;
receiveCurrency = CryptoCurrency.xmr;
break;
default:
break;
}
@@ -63,6 +63,9 @@ abstract class NodeListViewModelBase with Store {
case WalletType.haven:
node = getHavenDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.ethereum:
node = getEthereumDefaultNode(nodes: _nodeSource)!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
}
+13 -1
View File
@@ -2,6 +2,7 @@ import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -90,6 +91,9 @@ abstract class OutputBase with Store {
case WalletType.haven:
_amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
break;
case WalletType.ethereum:
_amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
break;
default:
break;
}
@@ -123,6 +127,10 @@ abstract class OutputBase with Store {
if (_wallet.type == WalletType.haven) {
return haven!.formatterMoneroAmountToDouble(amount: fee);
}
if (_wallet.type == WalletType.ethereum) {
return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
}
} catch (e) {
print(e.toString());
}
@@ -133,8 +141,9 @@ abstract class OutputBase with Store {
@computed
String get estimatedFeeFiatAmount {
try {
final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler();
final fiat = calculateFiatAmountRaw(
price: _fiatConversationStore.prices[cryptoCurrencyHandler()]!,
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: estimatedFee);
return fiat;
} catch (_) {
@@ -228,6 +237,9 @@ abstract class OutputBase with Store {
case WalletType.haven:
maximumFractionDigits = 12;
break;
case WalletType.ethereum:
maximumFractionDigits = 12;
break;
default:
break;
}
@@ -13,8 +13,7 @@ import 'package:cake_wallet/store/settings_store.dart';
part 'send_template_view_model.g.dart';
class SendTemplateViewModel = SendTemplateViewModelBase
with _$SendTemplateViewModel;
class SendTemplateViewModel = SendTemplateViewModelBase with _$SendTemplateViewModel;
abstract class SendTemplateViewModelBase with Store {
final WalletBase _wallet;
@@ -22,8 +21,8 @@ abstract class SendTemplateViewModelBase with Store {
final SendTemplateStore _sendTemplateStore;
final FiatConversionStore _fiatConversationStore;
SendTemplateViewModelBase(this._wallet, this._settingsStore,
this._sendTemplateStore, this._fiatConversationStore)
SendTemplateViewModelBase(
this._wallet, this._settingsStore, this._sendTemplateStore, this._fiatConversationStore)
: recipients = ObservableList<TemplateViewModel>() {
addRecipient();
}
@@ -33,7 +32,6 @@ abstract class SendTemplateViewModelBase with Store {
@action
void addRecipient() {
recipients.add(TemplateViewModel(
cryptoCurrency: cryptoCurrency,
wallet: _wallet,
settingsStore: _settingsStore,
fiatConversationStore: _fiatConversationStore));
@@ -47,11 +45,13 @@ abstract class SendTemplateViewModelBase with Store {
AmountValidator get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
AddressValidator get addressValidator =>
AddressValidator(type: _wallet.currency);
AddressValidator get addressValidator => AddressValidator(type: _wallet.currency);
TemplateValidator get templateValidator => TemplateValidator();
bool get hasMultiRecipient =>
_wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum;
@computed
CryptoCurrency get cryptoCurrency => _wallet.currency;
@@ -68,6 +68,7 @@ abstract class SendTemplateViewModelBase with Store {
void addTemplate(
{required String name,
required bool isCurrencySelected,
required String cryptoCurrency,
required String address,
required String amount,
required String amountFiat,
@@ -76,7 +77,7 @@ abstract class SendTemplateViewModelBase with Store {
name: name,
isCurrencySelected: isCurrencySelected,
address: address,
cryptoCurrency: cryptoCurrency.title,
cryptoCurrency: cryptoCurrency,
fiatCurrency: fiatCurrency,
amount: amount,
amountFiat: amountFiat,
@@ -89,4 +90,7 @@ abstract class SendTemplateViewModelBase with Store {
_sendTemplateStore.remove(template: template);
updateTemplate();
}
@computed
List<CryptoCurrency> get walletCurrencies => _wallet.balance.keys.toList();
}
+37 -12
View File
@@ -3,6 +3,7 @@ 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/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/view_model/send/output.dart';
@@ -45,6 +46,7 @@ abstract class SendViewModelBase with Store {
: state = InitialExecutionState(),
currencies = _wallet.balance.keys.toList(),
selectedCryptoCurrency = _wallet.currency,
hasMultipleTokens = _wallet.type == WalletType.ethereum,
outputs = ObservableList<Output>(),
fiatFromSettings = _settingsStore.fiatCurrency {
final priority = _settingsStore.priority[_wallet.type];
@@ -105,8 +107,11 @@ abstract class SendViewModelBase with Store {
String get pendingTransactionFeeFiatAmount {
try {
if (pendingTransaction != null) {
final currency = walletType == WalletType.ethereum
? _wallet.currency
: selectedCryptoCurrency;
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
price: _fiatConversationStore.prices[currency]!,
cryptoAmount: pendingTransaction!.feeFormatted);
return fiat;
} else {
@@ -131,14 +136,14 @@ abstract class SendViewModelBase with Store {
CryptoCurrency get currency => _wallet.currency;
Validator get amountValidator =>
Validator<String> get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
Validator get allAmountValidator => AllAmountValidator();
Validator<String> get allAmountValidator => AllAmountValidator();
Validator get addressValidator => AddressValidator(type: selectedCryptoCurrency);
Validator<String> get addressValidator => AddressValidator(type: selectedCryptoCurrency);
Validator get textValidator => TextValidator();
Validator<String> get textValidator => TextValidator();
final FiatCurrency fiatFromSettings;
@@ -146,7 +151,7 @@ abstract class SendViewModelBase with Store {
PendingTransaction? pendingTransaction;
@computed
String get balance => balanceViewModel.availableBalance;
String get balance => _wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance;
@computed
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
@@ -176,10 +181,9 @@ abstract class SendViewModelBase with Store {
List<CryptoCurrency> currencies;
bool get hasMultiRecipient => _wallet.type != WalletType.haven;
bool get hasYat => outputs
.any((out) => out.isParsedAddress && out.parsedAddress.parseFrom == ParseFrom.yatRecord);
bool get hasYat => outputs.any((out) =>
out.isParsedAddress &&
out.parsedAddress.parseFrom == ParseFrom.yatRecord);
WalletType get walletType => _wallet.type;
@@ -198,6 +202,7 @@ abstract class SendViewModelBase with Store {
final ContactListViewModel contactListViewModel;
final FiatConversionStore _fiatConversationStore;
final Box<TransactionDescription> transactionDescriptionBox;
final bool hasMultipleTokens;
@computed
List<ContactRecord> get contactsToShow => contactListViewModel.contacts
@@ -351,6 +356,15 @@ abstract class SendViewModelBase with Store {
return haven!.createHavenTransactionCreationCredentials(
outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
case WalletType.ethereum:
final priority = _settingsStore.priority[_wallet.type];
if (priority == null) {
throw Exception('Priority is null for wallet type: ${_wallet.type}');
}
return ethereum!.createEthereumTransactionCredentials(
outputs, priority: priority, currency: selectedCryptoCurrency);
default:
throw Exception('Unexpected wallet type: ${_wallet.type}');
}
@@ -369,11 +383,22 @@ abstract class SendViewModelBase with Store {
}
bool _isEqualCurrency(String currency) =>
currency.toLowerCase() == _wallet.currency.title.toLowerCase();
_wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase());
@action
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
@action
void setFiatCurrency(FiatCurrency fiat) => _settingsStore.fiatCurrency = fiat;
void setFiatCurrency(FiatCurrency fiat) =>
_settingsStore.fiatCurrency = fiat;
@action
void setSelectedCryptoCurrency(String cryptoCurrency) {
try {
selectedCryptoCurrency = _wallet.balance.keys
.firstWhere((e) => cryptoCurrency.toLowerCase() == e.title.toLowerCase());
} catch (e) {
selectedCryptoCurrency = _wallet.currency;
}
}
}
+21 -13
View File
@@ -11,23 +11,20 @@ part 'template_view_model.g.dart';
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
abstract class TemplateViewModelBase with Store {
final CryptoCurrency cryptoCurrency;
final WalletBase _wallet;
final SettingsStore _settingsStore;
final FiatConversionStore _fiatConversationStore;
TemplateViewModelBase(
{required this.cryptoCurrency,
required WalletBase wallet,
required SettingsStore settingsStore,
required FiatConversionStore fiatConversationStore})
: _wallet = wallet,
TemplateViewModelBase({
required WalletBase wallet,
required SettingsStore settingsStore,
required FiatConversionStore fiatConversationStore,
}) : _wallet = wallet,
_settingsStore = settingsStore,
_fiatConversationStore = fiatConversationStore,
output = Output(wallet, settingsStore, fiatConversationStore,
() => wallet.currency) {
output = Output(
_wallet, _settingsStore, _fiatConversationStore, () => cryptoCurrency);
_currency = wallet.currency,
output = Output(wallet, settingsStore, fiatConversationStore, () => wallet.currency) {
output = Output(_wallet, _settingsStore, _fiatConversationStore, () => _currency);
}
@observable
@@ -39,6 +36,9 @@ abstract class TemplateViewModelBase with Store {
@observable
String address = '';
@observable
CryptoCurrency _currency;
@observable
bool isCurrencySelected = true;
@@ -66,8 +66,7 @@ abstract class TemplateViewModelBase with Store {
output.reset();
}
Template toTemplate(
{required String cryptoCurrency, required String fiatCurrency}) {
Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) {
return Template(
isCurrencySelectedRaw: isCurrencySelected,
nameRaw: name,
@@ -77,4 +76,13 @@ abstract class TemplateViewModelBase with Store {
amountRaw: output.cryptoAmount,
amountFiatRaw: output.fiatAmount);
}
@action
void changeSelectedCurrency(CryptoCurrency currency) {
isCurrencySelected = true;
_currency = currency;
}
@computed
CryptoCurrency get selectedCurrency => _currency;
}
@@ -1,5 +1,8 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
@@ -8,9 +11,10 @@ part 'privacy_settings_view_model.g.dart';
class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
abstract class PrivacySettingsViewModelBase with Store {
PrivacySettingsViewModelBase(this._settingsStore);
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
final SettingsStore _settingsStore;
final WalletBase _wallet;
@computed
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
@@ -30,8 +34,14 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get disableSell => _settingsStore.disableSell;
@computed
bool get useEtherscan => _settingsStore.useEtherscan;
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
@action
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
void setShouldSaveRecipientAddress(bool value) =>
_settingsStore.shouldSaveRecipientAddress = value;
@action
void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
@@ -48,4 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store {
@action
void setDisableSell(bool value) => _settingsStore.disableSell = value;
@action
void setUseEtherscan(bool value) {
_settingsStore.useEtherscan = value;
ethereum!.updateEtherscanUsageState(_wallet, value);
}
}
@@ -9,6 +9,7 @@ import 'package:cw_core/transaction_direction.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:hive/hive.dart';
import 'package:intl/src/intl/date_format.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -27,105 +28,27 @@ abstract class TransactionDetailsViewModelBase with Store {
required this.wallet,
required this.settingsStore})
: items = [],
isRecipientAddressShown = false,
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
isRecipientAddressShown = false,
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
if (wallet.type == WalletType.monero) {
final key = tx.additionalInfo['key'] as String?;
final accountIndex = tx.additionalInfo['accountIndex'] as int;
final addressIndex = tx.additionalInfo['addressIndex'] as int;
final feeFormatted = tx.feeFormatted();
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.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
if (feeFormatted != null)
StandartListItem(
title: S.current.transaction_details_fee, value: feeFormatted),
if (key?.isNotEmpty ?? false)
StandartListItem(title: S.current.transaction_key, value: key!)
];
if (tx.direction == TransactionDirection.incoming &&
accountIndex != null &&
addressIndex != null) {
try {
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
if (address?.isNotEmpty ?? false) {
isRecipientAddressShown = true;
_items.add(
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: address));
}
if (label?.isNotEmpty ?? false) {
_items.add(
StandartListItem(
title: S.current.address_label,
value: label)
);
}
} catch (e) {
print(e.toString());
}
}
items.addAll(_items);
}
if (wallet.type == WalletType.bitcoin
|| wallet.type == WalletType.litecoin) {
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.toString()),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!),
];
items.addAll(_items);
}
if (wallet.type == WalletType.haven) {
items.addAll([
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.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(
title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
]);
switch (wallet.type) {
case WalletType.monero:
_addMoneroListItems(tx, dateFormat);
break;
case WalletType.bitcoin:
case WalletType.litecoin:
_addElectrumListItems(tx, dateFormat);
break;
case WalletType.haven:
_addHavenListItems(tx, dateFormat);
break;
case WalletType.ethereum:
_addEthereumListItems(tx, dateFormat);
break;
default:
break;
}
if (showRecipientAddress && !isRecipientAddressShown) {
@@ -136,10 +59,9 @@ abstract class TransactionDetailsViewModelBase with Store {
if (recipientAddress?.isNotEmpty ?? false) {
items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: recipientAddress!));
title: S.current.transaction_details_recipient_address, value: recipientAddress!));
}
} catch(_) {
} catch (_) {
// FIX-ME: Unhandled exception
}
}
@@ -192,6 +114,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://blockchair.com/litecoin/transaction/${txId}';
case WalletType.haven:
return 'https://explorer.havenprotocol.org/search?value=${txId}';
case WalletType.ethereum:
return 'https://etherscan.io/tx/${txId}';
default:
return '';
}
@@ -207,8 +131,92 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'Blockchair.com';
case WalletType.haven:
return S.current.view_transaction_on + 'explorer.havenprotocol.org';
case WalletType.ethereum:
return S.current.view_transaction_on + 'etherscan.io';
default:
return '';
}
}
void _addMoneroListItems(TransactionInfo tx, DateFormat dateFormat) {
final key = tx.additionalInfo['key'] as String?;
final accountIndex = tx.additionalInfo['accountIndex'] as int;
final addressIndex = tx.additionalInfo['addressIndex'] as int;
final feeFormatted = tx.feeFormatted();
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.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
if (feeFormatted != null)
StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
];
if (tx.direction == TransactionDirection.incoming) {
try {
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
if (address.isNotEmpty) {
isRecipientAddressShown = true;
_items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: address,
));
}
if (label.isNotEmpty) {
_items.add(StandartListItem(title: S.current.address_label, value: label));
}
} catch (e) {
print(e.toString());
}
}
items.addAll(_items);
}
void _addElectrumListItems(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.toString()),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
];
items.addAll(_items);
}
void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
items.addAll([
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.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
]);
}
void _addEthereumListItems(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.toString()),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
];
items.addAll(_items);
}
}
@@ -1,3 +1,4 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
@@ -93,6 +94,22 @@ class LitecoinURI extends PaymentURI {
}
}
class EthereumURI extends PaymentURI {
EthereumURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = 'ethereum:' + address;
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
abstract class WalletAddressListViewModelBase with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
@@ -151,6 +168,10 @@ abstract class WalletAddressListViewModelBase with Store {
return LitecoinURI(amount: amount, address: address.address);
}
if (_wallet.type == WalletType.ethereum) {
return EthereumURI(amount: amount, address: address.address);
}
throw Exception('Unexpected type: ${type.toString()}');
}
@@ -202,6 +223,12 @@ abstract class WalletAddressListViewModelBase with Store {
addressList.addAll(bitcoinAddresses);
}
if (wallet.type == WalletType.ethereum) {
final primaryAddress = ethereum!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
return addressList;
}
@@ -226,6 +253,10 @@ abstract class WalletAddressListViewModelBase with Store {
@computed
bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven;
@computed
bool get showElectrumAddressDisclaimer =>
_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin;
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
+6 -2
View File
@@ -17,7 +17,8 @@ class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel;
abstract class WalletKeysViewModelBase with Store {
WalletKeysViewModelBase(this._appStore)
: title = _appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin
_appStore.wallet!.type == WalletType.litecoin ||
_appStore.wallet!.type == WalletType.ethereum
? S.current.wallet_seed
: S.current.wallet_keys,
_restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
@@ -89,7 +90,8 @@ abstract class WalletKeysViewModelBase with Store {
}
if (_appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin) {
_appStore.wallet!.type == WalletType.litecoin ||
_appStore.wallet!.type == WalletType.ethereum) {
items.addAll([
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed),
]);
@@ -116,6 +118,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'litecoin-wallet';
case WalletType.haven:
return 'haven-wallet';
case WalletType.ethereum:
return 'ethereum-wallet';
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
}
+3
View File
@@ -1,4 +1,5 @@
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@@ -42,6 +43,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
case WalletType.haven:
return haven!.createHavenNewWalletCredentials(
name: name, language: options as String);
case WalletType.ethereum:
return ethereum!.createEthereumNewWalletCredentials(name: name);
default:
throw Exception('Unexpected type: ${type.toString()}');;
}
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/mnemonic_length.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/app_store.dart';
@@ -85,6 +86,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
height: height,
mnemonic: seed,
password: password);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
default:
break;
}