From d30de4dc3f68f1f018f0093eeb739314f8178459 Mon Sep 17 00:00:00 2001 From: fosse Date: Thu, 27 Jul 2023 13:02:10 -0400 Subject: [PATCH] tx history working and fiat fixes --- cw_core/lib/crypto_currency.dart | 2 +- cw_nano/lib/nano_balance.dart | 23 ++- cw_nano/lib/nano_client.dart | 145 ++++++++++++++++++ cw_nano/lib/nano_transaction_history.dart | 59 ++++++- cw_nano/lib/nano_transaction_info.dart | 107 ++++++------- cw_nano/lib/nano_transaction_model.dart | 39 +++++ cw_nano/lib/nano_util.dart | 77 ++++++++++ cw_nano/lib/nano_wallet.dart | 91 ++++++++--- cw_nano/pubspec.lock | 16 ++ cw_nano/pubspec.yaml | 1 + lib/entities/priority_for_wallet_type.dart | 6 +- .../dashboard/widgets/menu_widget.dart | 4 +- .../dashboard/transaction_list_item.dart | 40 +++-- .../wallet_address_list_view_model.dart | 1 - 14 files changed, 492 insertions(+), 119 deletions(-) create mode 100644 cw_nano/lib/nano_client.dart create mode 100644 cw_nano/lib/nano_transaction_model.dart diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 45fb53c60..a73e2c107 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -120,7 +120,7 @@ class CryptoCurrency extends EnumerableItem with Serializable 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, name: 'nano', iconPath: 'assets/images/nano.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'); diff --git a/cw_nano/lib/nano_balance.dart b/cw_nano/lib/nano_balance.dart index b540cb618..13ef1b4d3 100644 --- a/cw_nano/lib/nano_balance.dart +++ b/cw_nano/lib/nano_balance.dart @@ -6,6 +6,10 @@ String rawToFormattedAmount(BigInt amount, Currency currency) { return ""; } +BigInt stringAmountToBigInt(String amount) { + return BigInt.zero; +} + class NanoBalance extends Balance { final BigInt currentBalance; final BigInt receivableBalance; @@ -17,16 +21,19 @@ class NanoBalance extends Balance { this.formattedReceivableBalance = ""; } - // NanoBalance.fromString( - // {required this.formattedCurrentBalance, required this.formattedReceivableBalance}) - // : currentBalance = moneroParseAmount(amount: formattedCurrentBalance), - // receivableBalance = moneroParseAmount(amount: formattedReceivableBalance), - // super(moneroParseAmount(amount: formattedReceivableBalance), - // moneroParseAmount(amount: formattedCurrentBalance)); + NanoBalance.fromString( + {required this.formattedCurrentBalance, required this.formattedReceivableBalance}) + : currentBalance = stringAmountToBigInt(formattedCurrentBalance), + receivableBalance = stringAmountToBigInt(formattedReceivableBalance), + super(0, 0); @override - String get formattedAvailableBalance => "0"; + String get formattedAvailableBalance { + return "0"; + } @override - String get formattedAdditionalBalance => "0"; + String get formattedAdditionalBalance { + return "0"; + } } diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart new file mode 100644 index 000000000..4455880d9 --- /dev/null +++ b/cw_nano/lib/nano_client.dart @@ -0,0 +1,145 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; + +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_nano/nano_balance.dart'; +import 'package:cw_nano/nano_transaction_model.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:web3dart/contracts/erc20.dart'; +import 'package:cw_core/node.dart'; + +class NanoClient { + final _httpClient = Client(); + StreamSubscription? subscription; + Node? _node; + + bool connect(Node node) { + try { + _node = node; + return true; + } catch (e) { + return false; + } + } + + void setListeners(EthereumAddress userAddress, Function(FilterEvent) onNewTransaction) async {} + + Future getBalance(String address) async { + return NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero); + } + + // Future signTransaction({ + // required EthPrivateKey privateKey, + // required String toAddress, + // required String amount, + // required int gas, + // required EthereumTransactionPriority priority, + // required CryptoCurrency currency, + // required int exponent, + // String? contractAddress, + // }) async { + // assert(currency == CryptoCurrency.eth || contractAddress != null); + + // bool _isEthereum = currency == CryptoCurrency.eth; + + // final price = await _client!.getGasPrice(); + + // final Transaction transaction = Transaction( + // from: privateKey.address, + // to: EthereumAddress.fromHex(toAddress), + // maxGas: gas, + // gasPrice: price, + // value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), + // ); + + // final signedTransaction = await _client!.signTransaction(privateKey, transaction); + + // final BigInt estimatedGas; + // final Function _sendTransaction; + + // if (_isEthereum) { + // estimatedGas = BigInt.from(21000); + // _sendTransaction = () async => await sendTransaction(signedTransaction); + // } else { + // estimatedGas = BigInt.from(50000); + + // final erc20 = Erc20( + // client: _client!, + // address: EthereumAddress.fromHex(contractAddress!), + // ); + + // _sendTransaction = () async { + // await erc20.transfer( + // EthereumAddress.fromHex(toAddress), + // BigInt.parse(amount), + // credentials: privateKey, + // ); + // }; + // } + + // return PendingEthereumTransaction( + // signedTransaction: signedTransaction, + // amount: amount, + // fee: estimatedGas * price.getInWei, + // sendTransaction: _sendTransaction, + // exponent: exponent, + // ); + // } + + Future getTransactionDetails(String transactionHash) async { + throw UnimplementedError(); + } + + void stop() { + subscription?.cancel(); + _httpClient?.close(); + } + + Future> fetchTransactions(String address) async { + try { + final response = await _httpClient.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 : []; + + // 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( + (transaction) => NanoTransactionModel.fromJson(transaction as Map)) + .toList(); + } catch (e) { + print(e); + return []; + } + } + +// Future _getDecimalPlacesForContract(DeployedContract contract) async { +// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json"); +// final contractAbi = ContractAbi.fromJson(abi, "ERC20"); +// +// final contract = DeployedContract( +// contractAbi, +// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!), +// ); +// final decimalsFunction = contract.function('decimals'); +// final decimals = await _client!.call( +// contract: contract, +// function: decimalsFunction, +// params: [], +// ); +// +// int exponent = int.parse(decimals.first.toString()); +// return exponent; +// } +} diff --git a/cw_nano/lib/nano_transaction_history.dart b/cw_nano/lib/nano_transaction_history.dart index 7b904f320..b201d6d13 100644 --- a/cw_nano/lib/nano_transaction_history.dart +++ b/cw_nano/lib/nano_transaction_history.dart @@ -1,28 +1,73 @@ +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:flutter/material.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; +class NanoTransactionHistory = NanoTransactionHistoryBase with _$NanoTransactionHistory; abstract class NanoTransactionHistoryBase extends TransactionHistoryBase with Store { - NanoTransactionHistoryBase() { + NanoTransactionHistoryBase({required this.walletInfo, required String password}) + : _password = password { transactions = ObservableMap(); } - @override - Future save() async {} + final WalletInfo walletInfo; + String _password; + + Future init() async => await _load(); @override - void addOne(NanoTransactionInfo transaction) => - transactions[transaction.id] = transaction; + Future 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 transactions) => this.transactions.addAll(transactions); + Future> _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; + } + + Future _load() async { + try { + final content = await _read(); + final txs = content['transactions'] as Map? ?? {}; + + txs.entries.forEach((entry) { + final val = entry.value; + + if (val is Map) { + final tx = NanoTransactionInfo.fromJson(val); + _update(tx); + } + }); + } catch (e) { + print(e); + } + } + + void _update(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction; } diff --git a/cw_nano/lib/nano_transaction_info.dart b/cw_nano/lib/nano_transaction_info.dart index 19db58b64..b47887c39 100644 --- a/cw_nano/lib/nano_transaction_info.dart +++ b/cw_nano/lib/nano_transaction_info.dart @@ -1,72 +1,39 @@ -import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_monero/api/structs/transaction_info_row.dart'; -import 'package:cw_core/parseBoolFromString.dart'; -import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/format_amount.dart'; -import 'package:cw_monero/api/transaction_history.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(this.id, this.height, this.direction, this.date, this.isPending, this.amount, - this.accountIndex, this.addressIndex, this.amountRaw, this.confirmations); - - // NanoTransactionInfo.fromMap(Map map) - // : id = (map['hash'] ?? '') as String, - // height = (map['height'] ?? 0) as int, - // direction = map['direction'] != null - // ? parseTransactionDirectionFromNumber(map['direction'] as String) - // : TransactionDirection.incoming, - // date = DateTime.fromMillisecondsSinceEpoch( - // (int.tryParse(map['timestamp'] as String? ?? '') ?? 0) * 1000), - // isPending = parseBoolFromString(map['isPending'] as String), - // amount = map['amount'] as int, - // accountIndex = int.parse(map['accountIndex'] as String), - // addressIndex = map['addressIndex'] as int, - // confirmations = map['confirmations'] as int, - // key = getTxKey((map['hash'] ?? '') as String), - // fee = map['fee'] as int? ?? 0 { - // additionalInfo = { - // 'key': key, - // 'accountIndex': accountIndex, - // 'addressIndex': addressIndex - // }; - // } - - // NanoTransactionInfo.fromRow(TransactionInfoRow row) - // : id = row.getHash(), - // height = row.blockHeight, - // direction = parseTransactionDirectionFromInt(row.direction), - // date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000), - // isPending = row.isPending != 0, - // amount = row.getAmount(), - // accountIndex = row.subaddrAccount, - // addressIndex = row.subaddrIndex, - // confirmations = row.confirmations, - // key = getTxKey(row.getHash()), - // fee = row.fee { - // additionalInfo = { - // 'key': key, - // 'accountIndex': accountIndex, - // 'addressIndex': addressIndex - // }; - // } + 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 TransactionDirection direction; - final DateTime date; - final int accountIndex; - final bool isPending; final int amount; final BigInt amountRaw; - final int addressIndex; + final TransactionDirection direction; + final DateTime date; + final bool confirmed; final int confirmations; - String? recipientAddress; - String? key; + final String tokenSymbol; String? _fiatAmount; + bool get isPending => !this.confirmed; + @override - String amountFormatted() => '${formatAmount(moneroAmountToString(amount: amount))} XNO'; + 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 ?? ''; @@ -75,5 +42,29 @@ class NanoTransactionInfo extends TransactionInfo { void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); @override - String feeFormatted() => '${formatAmount(moneroAmountToString(amount: 0))} XNO'; + String feeFormatted() => "0 XNO"; + + factory NanoTransactionInfo.fromJson(Map data) { + return NanoTransactionInfo( + id: data['id'] as String, + height: data['height'] as int, + amountRaw: data['amountRaw'] as BigInt, + 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 toJson() => { + 'id': id, + 'height': height, + 'amountRaw': amountRaw, + 'direction': direction.index, + 'date': date.millisecondsSinceEpoch, + 'confirmed': confirmed, + 'confirmations': confirmations, + 'tokenSymbol': tokenSymbol, + }; } diff --git a/cw_nano/lib/nano_transaction_model.dart b/cw_nano/lib/nano_transaction_model.dart new file mode 100644 index 000000000..9f7556f96 --- /dev/null +++ b/cw_nano/lib/nano_transaction_model.dart @@ -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(Map json) { + DateTime? local_timestamp; + try { + local_timestamp = + DateTime.fromMillisecondsSinceEpoch(int.parse(json["local_timeStamp"] as String) * 1000); + } catch (e) { + local_timestamp = DateTime.now(); + } + + return NanoTransactionModel( + date: local_timestamp, + 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", + ); + } +} diff --git a/cw_nano/lib/nano_util.dart b/cw_nano/lib/nano_util.dart index 7f6b09ea7..6a14bc01a 100644 --- a/cw_nano/lib/nano_util.dart +++ b/cw_nano/lib/nano_util.dart @@ -8,6 +8,7 @@ import 'package:libcrypto/libcrypto.dart'; import 'package:nanodart/nanodart.dart'; import 'package:ed25519_hd_key/ed25519_hd_key.dart'; import 'package:nanodart/nanodart.dart'; +import 'package:decimal/decimal.dart'; class NanoUtil { // standard: @@ -120,4 +121,80 @@ class NanoUtil { // 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 ""; + } } diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index b09094617..4c1c4f449 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -5,11 +5,13 @@ 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_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_nano/file.dart'; import 'package:cw_nano/nano_balance.dart'; +import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_transaction_history.dart'; import 'package:cw_nano/nano_transaction_info.dart'; import 'package:cw_nano/nano_util.dart'; @@ -40,7 +42,7 @@ abstract class NanoWalletBase _mnemonic = mnemonic, _derivationType = derivationType, _isTransactionUpdating = false, - _priorityFees = [], + _client = NanoClient(), walletAddresses = NanoWalletAddresses(walletInfo), balance = ObservableMap.of({ CryptoCurrency.nano: initialBalance ?? @@ -48,7 +50,7 @@ abstract class NanoWalletBase }), super(walletInfo) { this.walletInfo = walletInfo; - transactionHistory = NanoTransactionHistory(); + transactionHistory = NanoTransactionHistory(walletInfo: walletInfo, password: password); } final String _mnemonic; @@ -59,8 +61,7 @@ abstract class NanoWalletBase late final String _publicAddress; late final String _seedKey; - List _priorityFees; - int? _gasPrice; + late NanoClient _client; bool _isTransactionUpdating; @override @@ -84,7 +85,7 @@ abstract class NanoWalletBase this.walletInfo.address = _publicAddress; await walletAddresses.init(); - // await transactionHistory.init(); + await transactionHistory.init(); // walletAddresses.address = _privateKey.address.toString(); await save(); @@ -103,14 +104,24 @@ abstract class NanoWalletBase @override void close() { - // _client.stop(); + _client.stop(); } @action @override Future connectToNode({required Node node}) async { - print("f"); - throw UnimplementedError(); + try { + syncStatus = ConnectingSyncStatus(); + final isConnected = _client.connect(node); + if (!isConnected) { + throw Exception("Ethereum Node connection failed"); + } + // _client.setListeners(_privateKey.address, _onNewTransaction); + _updateBalance(); + syncStatus = ConnectedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } } @override @@ -120,14 +131,45 @@ abstract class NanoWalletBase } Future updateTransactions() async { - print("h"); - throw UnimplementedError(); + print("updating_transactions"); + try { + if (_isTransactionUpdating) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (_) { + _isTransactionUpdating = false; + } } @override Future> fetchTransactions() async { - print("i"); - throw UnimplementedError(); + String address = _publicAddress; + + final transactions = await _client.fetchTransactions(address); + + final Map result = {}; + + for (var transactionModel in transactions) { + result[transactionModel.hash] = NanoTransactionInfo( + id: transactionModel.hash, + amountRaw: transactionModel.amount, + height: transactionModel.height, + direction: transactionModel.account == address + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + confirmed: transactionModel.confirmed, + date: transactionModel.date ?? DateTime.now(), + confirmations: transactionModel.confirmed ? 1 : 0, + ); + } + + return result; } @override @@ -156,11 +198,15 @@ abstract class NanoWalletBase @action @override Future startSync() async { - throw UnimplementedError(); - } + try { + syncStatus = AttemptingSyncStatus(); + await _updateBalance(); + await updateTransactions(); - int feeRate(TransactionPriority priority) { - throw UnimplementedError(); + syncStatus = SyncedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } } Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); @@ -177,16 +223,16 @@ abstract class NanoWalletBase required String password, required WalletInfo walletInfo, }) async { - // TODO: finish 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.fromJSON(data['balance'] as String) ?? */ - NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero); + final balance = NanoBalance.fromString( + formattedCurrentBalance: data['balance'] as String? ?? "0", + formattedReceivableBalance: "0"); DerivationType derivationType = DerivationType.bip39; - if (data['derivationType'] == "nano") { + if (data['derivationType'] == "DerivationType.nano") { derivationType = DerivationType.nano; } @@ -203,11 +249,6 @@ abstract class NanoWalletBase await save(); } - Future getPrivateKey(String mnemonic, String password) async { - print("o"); - throw UnimplementedError(); - } - Future? updateBalance() async => await _updateBalance(); void _onNewTransaction(FilterEvent event) { diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index d581a6834..b7725fde7 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -208,6 +208,14 @@ packages: 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: @@ -586,6 +594,14 @@ packages: 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: diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml index d48cfe8b9..ada2693a3 100644 --- a/cw_nano/pubspec.yaml +++ b/cw_nano/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: 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 diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index eb9417763..5eded3a4a 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/nano/nano.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_type.dart'; @@ -17,8 +18,11 @@ List priorityForWalletType(WalletType type) { return haven!.getTransactionPriorities(); case WalletType.ethereum: return ethereum!.getTransactionPriorities(); + // we just get ethereum's here since there's no transaction priority in nano + // and so there's no point in bothering to implement it: + case WalletType.nano: + return ethereum!.getTransactionPriorities(); default: return []; } } - diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index 0fe61ec89..c9723db56 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -31,8 +31,8 @@ class MenuWidgetState extends State { 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/eth_icon.png'), - this.bananoIcon = 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; diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index ac74df89d..799c6b52b 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -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'; @@ -13,12 +14,12 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cw_core/keyable.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:cw_nano/nano_transaction_info.dart'; +import 'package:cw_nano/nano_util.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 +35,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 +57,33 @@ 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); @@ -92,6 +92,14 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction), price: price); break; + case WalletType.nano: + final nanoTransaction = transaction as NanoTransactionInfo; + amount = calculateFiatAmountRaw( + cryptoAmount: + NanoUtil.getRawAsDecimal(nanoTransaction.amountRaw.toString(), NanoUtil.rawPerNano) + .toDouble(), + price: price); + break; default: break; } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 1d95da119..8dc7e65ff 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -116,7 +116,6 @@ class NanoURI extends PaymentURI { @override String toString() { - print(address); var base = 'nano:' + address; if (amount.isNotEmpty) {