From 7a95c20645e00b395b0dbe943208aba2c16d3118 Mon Sep 17 00:00:00 2001 From: fossephate Date: Wed, 24 May 2023 11:56:44 -0400 Subject: [PATCH 1/2] nano sending + minor sats constants refactor --- lib/db/isar/main_db.dart | 102 ++----- lib/models/paymint/transactions_model.dart | 8 +- .../wallet_view/sub_widgets/desktop_send.dart | 6 +- .../coins/bitcoin/bitcoin_wallet.dart | 2 +- lib/services/coins/firo/firo_wallet.dart | 14 +- .../coins/litecoin/litecoin_wallet.dart | 2 +- .../coins/namecoin/namecoin_wallet.dart | 2 +- lib/services/coins/nano/nano_wallet.dart | 257 ++++++++++++++---- .../coins/particl/particl_wallet.dart | 12 +- lib/utilities/constants.dart | 20 +- 10 files changed, 274 insertions(+), 151 deletions(-) diff --git a/lib/db/isar/main_db.dart b/lib/db/isar/main_db.dart index b9841de02..5729e1329 100644 --- a/lib/db/isar/main_db.dart +++ b/lib/db/isar/main_db.dart @@ -50,7 +50,7 @@ class MainDB { } // contact entries - List getContactEntries(){ + List getContactEntries() { return isar.contactEntrys.where().findAllSync(); } @@ -66,11 +66,7 @@ class MainDB { } Future isContactEntryExists({required String id}) async { - return isar.contactEntrys - .where() - .customIdEqualTo(id) - .count() - .then((value) => value > 0); + return isar.contactEntrys.where().customIdEqualTo(id).count().then((value) => value > 0); } ContactEntry? getContactEntry({required String id}) { @@ -90,14 +86,10 @@ class MainDB { // tx block explorers TransactionBlockExplorer? getTransactionBlockExplorer({required Coin coin}) { - return isar.transactionBlockExplorers - .where() - .tickerEqualTo(coin.ticker) - .findFirstSync(); + return isar.transactionBlockExplorers.where().tickerEqualTo(coin.ticker).findFirstSync(); } - Future putTransactionBlockExplorer( - TransactionBlockExplorer explorer) async { + Future putTransactionBlockExplorer(TransactionBlockExplorer explorer) async { try { return await isar.writeTxn(() async { return await isar.transactionBlockExplorers.put(explorer); @@ -108,8 +100,7 @@ class MainDB { } // addresses - QueryBuilder getAddresses( - String walletId) => + QueryBuilder getAddresses(String walletId) => isar.addresses.where().walletIdEqualTo(walletId); Future putAddress(Address address) async { @@ -137,8 +128,7 @@ class MainDB { List ids = []; await isar.writeTxn(() async { for (final address in addresses) { - final storedAddress = await isar.addresses - .getByValueWalletId(address.value, address.walletId); + final storedAddress = await isar.addresses.getByValueWalletId(address.value, address.walletId); int id; if (storedAddress == null) { @@ -178,14 +168,12 @@ class MainDB { return id; }); } catch (e) { - throw MainDBException( - "failed updateAddress: from=$oldAddress to=$newAddress", e); + throw MainDBException("failed updateAddress: from=$oldAddress to=$newAddress", e); } } // transactions - QueryBuilder getTransactions( - String walletId) => + QueryBuilder getTransactions(String walletId) => isar.transactions.where().walletIdEqualTo(walletId); Future putTransaction(Transaction transaction) async { @@ -220,8 +208,7 @@ class MainDB { } // utxos - QueryBuilder getUTXOs(String walletId) => - isar.utxos.where().walletIdEqualTo(walletId); + QueryBuilder getUTXOs(String walletId) => isar.utxos.where().walletIdEqualTo(walletId); Future putUTXO(UTXO utxo) => isar.writeTxn(() async { await isar.utxos.put(utxo); @@ -236,10 +223,8 @@ class MainDB { final set = utxos.toSet(); for (final utxo in utxos) { // check if utxo exists in db and update accordingly - final storedUtxo = await isar.utxos - .where() - .txidWalletIdVoutEqualTo(utxo.txid, utxo.walletId, utxo.vout) - .findFirst(); + final storedUtxo = + await isar.utxos.where().txidWalletIdVoutEqualTo(utxo.txid, utxo.walletId, utxo.vout).findFirst(); if (storedUtxo != null) { // update @@ -269,22 +254,18 @@ class MainDB { } // transaction notes - QueryBuilder - getTransactionNotes(String walletId) => - isar.transactionNotes.where().walletIdEqualTo(walletId); + QueryBuilder getTransactionNotes(String walletId) => + isar.transactionNotes.where().walletIdEqualTo(walletId); - Future putTransactionNote(TransactionNote transactionNote) => - isar.writeTxn(() async { + Future putTransactionNote(TransactionNote transactionNote) => isar.writeTxn(() async { await isar.transactionNotes.put(transactionNote); }); - Future putTransactionNotes(List transactionNotes) => - isar.writeTxn(() async { + Future putTransactionNotes(List transactionNotes) => isar.writeTxn(() async { await isar.transactionNotes.putAll(transactionNotes); }); - Future getTransactionNote( - String walletId, String txid) async { + Future getTransactionNote(String walletId, String txid) async { return isar.transactionNotes.getByTxidWalletId( txid, walletId, @@ -295,17 +276,14 @@ class MainDB { required Id id, bool fireImmediately = false, }) { - return isar.transactionNotes - .watchObject(id, fireImmediately: fireImmediately); + return isar.transactionNotes.watchObject(id, fireImmediately: fireImmediately); } // address labels - QueryBuilder getAddressLabels( - String walletId) => + QueryBuilder getAddressLabels(String walletId) => isar.addressLabels.where().walletIdEqualTo(walletId); - Future putAddressLabel(AddressLabel addressLabel) => - isar.writeTxn(() async { + Future putAddressLabel(AddressLabel addressLabel) => isar.writeTxn(() async { return await isar.addressLabels.put(addressLabel); }); @@ -313,13 +291,11 @@ class MainDB { return isar.addressLabels.putSync(addressLabel); }); - Future putAddressLabels(List addressLabels) => - isar.writeTxn(() async { + Future putAddressLabels(List addressLabels) => isar.writeTxn(() async { await isar.addressLabels.putAll(addressLabels); }); - Future getAddressLabel( - String walletId, String addressString) async { + Future getAddressLabel(String walletId, String addressString) async { return isar.addressLabels.getByAddressStringWalletId( addressString, walletId, @@ -361,31 +337,19 @@ class MainDB { // transactions for (int i = 0; i < transactionCount; i += paginateLimit) { - final txnIds = await getTransactions(walletId) - .offset(i) - .limit(paginateLimit) - .idProperty() - .findAll(); + final txnIds = await getTransactions(walletId).offset(i).limit(paginateLimit).idProperty().findAll(); await isar.transactions.deleteAll(txnIds); } // addresses for (int i = 0; i < addressCount; i += paginateLimit) { - final addressIds = await getAddresses(walletId) - .offset(i) - .limit(paginateLimit) - .idProperty() - .findAll(); + final addressIds = await getAddresses(walletId).offset(i).limit(paginateLimit).idProperty().findAll(); await isar.addresses.deleteAll(addressIds); } // utxos for (int i = 0; i < utxoCount; i += paginateLimit) { - final utxoIds = await getUTXOs(walletId) - .offset(i) - .limit(paginateLimit) - .idProperty() - .findAll(); + final utxoIds = await getUTXOs(walletId).offset(i).limit(paginateLimit).idProperty().findAll(); await isar.utxos.deleteAll(utxoIds); } }); @@ -396,11 +360,7 @@ class MainDB { await isar.writeTxn(() async { const paginateLimit = 50; for (int i = 0; i < addressLabelCount; i += paginateLimit) { - final labelIds = await getAddressLabels(walletId) - .offset(i) - .limit(paginateLimit) - .idProperty() - .findAll(); + final labelIds = await getAddressLabels(walletId).offset(i).limit(paginateLimit).idProperty().findAll(); await isar.addressLabels.deleteAll(labelIds); } }); @@ -411,11 +371,7 @@ class MainDB { await isar.writeTxn(() async { const paginateLimit = 50; for (int i = 0; i < noteCount; i += paginateLimit) { - final labelIds = await getTransactionNotes(walletId) - .offset(i) - .limit(paginateLimit) - .idProperty() - .findAll(); + final labelIds = await getTransactionNotes(walletId).offset(i).limit(paginateLimit).idProperty().findAll(); await isar.transactionNotes.deleteAll(labelIds); } }); @@ -465,8 +421,7 @@ class MainDB { // eth contracts - QueryBuilder getEthContracts() => - isar.ethContracts.where(); + QueryBuilder getEthContracts() => isar.ethContracts.where(); Future getEthContract(String contractAddress) => isar.ethContracts.where().addressEqualTo(contractAddress).findFirst(); @@ -478,8 +433,7 @@ class MainDB { return await isar.ethContracts.put(contract); }); - Future putEthContracts(List contracts) => - isar.writeTxn(() async { + Future putEthContracts(List contracts) => isar.writeTxn(() async { await isar.ethContracts.putAll(contracts); }); } diff --git a/lib/models/paymint/transactions_model.dart b/lib/models/paymint/transactions_model.dart index cee48b8eb..14449decd 100644 --- a/lib/models/paymint/transactions_model.dart +++ b/lib/models/paymint/transactions_model.dart @@ -223,7 +223,7 @@ class Transaction { txType: json['txType'] as String, amount: (Decimal.parse(json["amount"].toString()) * Decimal.fromInt(Constants.satsPerCoin(Coin - .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure + .firo).toInt())) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt(), aliens: [], @@ -231,7 +231,7 @@ class Transaction { worthAtBlockTimestamp: json['worthAtBlockTimestamp'] as String? ?? "0", fees: (Decimal.parse(json["fees"].toString()) * Decimal.fromInt(Constants.satsPerCoin(Coin - .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure + .firo).toInt())) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt(), inputSize: json['inputSize'] as int? ?? 0, @@ -397,7 +397,7 @@ class Output { value: (Decimal.parse( (json["value"] ?? 0).toString()) * Decimal.fromInt(Constants.satsPerCoin(Coin - .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure + .firo).toInt())) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt(), ); @@ -410,7 +410,7 @@ class Output { scriptpubkeyAddress: "", value: (Decimal.parse(0.toString()) * Decimal.fromInt(Constants.satsPerCoin(Coin - .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure + .firo).toInt())) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt()); } diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 19854a017..e16c976c3 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -1412,7 +1412,7 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 20, ), - if (coin != Coin.epicCash) + if (!([Coin.nano, Coin.epicCash].contains(coin))) Text( "Transaction fee (${coin == Coin.ethereum ? "max" : "estimated"})", style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -1422,11 +1422,11 @@ class _DesktopSendState extends ConsumerState { ), textAlign: TextAlign.left, ), - if (coin != Coin.epicCash) + if (!([Coin.nano, Coin.epicCash].contains(coin))) const SizedBox( height: 10, ), - if (coin != Coin.epicCash) + if (!([Coin.nano, Coin.epicCash].contains(coin))) DesktopFeeDropDown( walletId: walletId, ), diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index 5dee0e430..cf765aaf3 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -278,7 +278,7 @@ class BitcoinWallet extends CoinServiceAPI Future get maxFee async { final fee = (await fees).fast as String; final satsFee = - Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt()); return satsFee.floor().toBigInt().toInt(); } diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 57e916e2e..e038be7ab 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -2338,7 +2338,7 @@ class FiroWallet extends CoinServiceAPI Future _fetchMaxFee() async { final balance = availablePrivateBalance(); int spendAmount = - (balance.decimal * Decimal.fromInt(Constants.satsPerCoin(coin))) + (balance.decimal * Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); int fee = await estimateJoinSplitFee(spendAmount); @@ -3549,7 +3549,7 @@ class FiroWallet extends CoinServiceAPI if (nFees != null) { nFeesUsed = true; fees = (Decimal.parse(nFees.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); } @@ -3573,14 +3573,14 @@ class FiroWallet extends CoinServiceAPI if (value != null) { outAmount += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); if (address != null) { if (changeAddresses.contains(address)) { inputAmtSentFromWallet -= (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); } else { @@ -3597,7 +3597,7 @@ class FiroWallet extends CoinServiceAPI final nFees = input["nFees"]; if (nFees != null) { fees += (Decimal.parse(nFees.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); } @@ -3612,7 +3612,7 @@ class FiroWallet extends CoinServiceAPI if (allAddresses.where((e) => e.value == address).isNotEmpty) { outputAmtAddressedToWallet += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); outAddress = address; @@ -4833,7 +4833,7 @@ class FiroWallet extends CoinServiceAPI ) async { var lelantusEntry = await _getLelantusEntry(); final balance = availablePrivateBalance().decimal; - int spendAmount = (balance * Decimal.fromInt(Constants.satsPerCoin(coin))) + int spendAmount = (balance * Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); if (spendAmount == 0 || lelantusEntry.isEmpty) { diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 5e1e8064c..6f94f08f5 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -223,7 +223,7 @@ class LitecoinWallet extends CoinServiceAPI Future get maxFee async { final fee = (await fees).fast as String; final satsFee = - Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt()); return satsFee.floor().toBigInt().toInt(); } diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 19c5a823b..b2ac8e20c 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -215,7 +215,7 @@ class NamecoinWallet extends CoinServiceAPI Future get maxFee async { final fee = (await fees).fast as String; final satsFee = - Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt()); return satsFee.floor().toBigInt().toInt(); } diff --git a/lib/services/coins/nano/nano_wallet.dart b/lib/services/coins/nano/nano_wallet.dart index 38a4c5a98..58de81577 100644 --- a/lib/services/coins/nano/nano_wallet.dart +++ b/lib/services/coins/nano/nano_wallet.dart @@ -14,6 +14,8 @@ import 'package:stackwallet/services/mixins/wallet_cache.dart'; import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/log_level_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; import '../../../db/isar/main_db.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; @@ -52,12 +54,11 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI @override Future get mnemonicPassphrase => _secureStore.read( - key: '${_walletId}_mnemonicPassphrase', - ); + key: '${_walletId}_mnemonicPassphrase', + ); @override - Future get mnemonicString => - _secureStore.read(key: '${_walletId}_mnemonic'); + Future get mnemonicString => _secureStore.read(key: '${_walletId}_mnemonic'); Future getSeedFromMnemonic() async { var mnemonic = await mnemonicString; @@ -73,7 +74,8 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI Future getAddressFromMnemonic() async { var mnemonic = await mnemonicString; var seed = NanoMnemomics.mnemonicListToSeed(mnemonic!.split(' ')); - var address = NanoAccounts.createAccount(NanoAccountType.NANO, NanoKeys.createPublicKey(NanoKeys.seedToPrivate(seed, 0))); + var address = + NanoAccounts.createAccount(NanoAccountType.NANO, NanoKeys.createPublicKey(NanoKeys.seedToPrivate(seed, 0))); return address; } @@ -128,10 +130,143 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI Balance get balance => _balance ??= getCachedBalance(); Balance? _balance; + Future requestWork(String url, String hash) async { + return http + .post( + Uri.parse(url), + headers: {'Content-type': 'application/json'}, + body: json.encode( + { + "action": "work_generate", + "hash": hash, + }, + ), + ) + .then((http.Response response) { + if (response.statusCode == 200) { + final Map decoded = json.decode(response.body) as Map; + if (decoded.containsKey("error")) { + throw Exception("Received error ${decoded["error"]}"); + } + return decoded["work"] as String?; + } else { + throw Exception("Received error ${response.statusCode}"); + } + }); + } + @override - Future confirmSend({required Map txData}) { - // TODO: implement confirmSend - throw UnimplementedError(); + Future confirmSend({required Map txData}) async { + + try { + // first get the account balance: + final balanceBody = jsonEncode({ + "action": "account_balance", + "account": await getAddressFromMnemonic(), + }); + final headers = { + "Content-Type": "application/json", + }; + final balanceResponse = await http.post( + Uri.parse(getCurrentNode().host), + headers: headers, + body: balanceBody, + ); + + final balanceData = jsonDecode(balanceResponse.body); + final BigInt currentBalance = BigInt.parse(balanceData["balance"].toString()); + final BigInt txAmount = txData["recipientAmt"].raw as BigInt; + final BigInt balanceAfterTx = currentBalance - txAmount; + + // get the account info (we need the frontier and representative): + final infoBody = jsonEncode({ + "action": "account_info", + "representative": "true", + "account": await getAddressFromMnemonic(), + }); + final infoResponse = await http.post( + Uri.parse(getCurrentNode().host), + headers: headers, + body: infoBody, + ); + + final String frontier = jsonDecode(infoResponse.body)["frontier"].toString(); + final String representative = jsonDecode(infoResponse.body)["representative"].toString(); + // our address: + final String publicAddress = await getAddressFromMnemonic(); + // link = destination address: + final String link = NanoAccounts.extractPublicKey(txData["address"].toString()); + final String linkAsAccount = txData["address"].toString(); + + // construct the send block: + final Map sendBlock = { + "type": "state", + "account": publicAddress, + "previous": frontier, + "representative": representative, + "balance": balanceAfterTx.toString(), + "link": link, + }; + + // sign the send block: + final String hash = NanoBlocks.computeStateHash( + NanoAccountType.NANO, + sendBlock["account"]!, + sendBlock["previous"]!, + sendBlock["representative"]!, + BigInt.parse(sendBlock["balance"]!), + sendBlock["link"]!, + ); + final String privateKey = await getPrivateKeyFromMnemonic(); + final String signature = NanoSignatures.signBlock(hash, privateKey); + + // get PoW for the send block: + final String? work = await requestWork("https://rpc.nano.to", frontier); + if (work == null) { + throw Exception("Failed to get PoW for send block"); + } + + // process the send block: + final Map finalSendBlock = { + "type": "state", + "account": publicAddress, + "previous": frontier, + "representative": representative, + "balance": balanceAfterTx.toString(), + "link": link, + "link_as_account": linkAsAccount, + "signature": signature, + "work": work, + }; + + final processBody = jsonEncode({ + "action": "process", + "json_block": "true", + "subtype": "send", + "block": finalSendBlock, + }); + final processResponse = await http.post( + Uri.parse(getCurrentNode().host), + headers: headers, + body: processBody, + ); + + final Map decoded = json.decode(processResponse.body) as Map; + if (decoded.containsKey("error")) { + throw Exception("Received error ${decoded["error"]}"); + } + + print(jsonDecode(processBody)); + print(jsonDecode(processResponse.body)); + + throw Exception("Received error ${decoded["error"]}"); + + // return the hash of the transaction: + return decoded["hash"].toString(); + } catch (e, s) { + Logging.instance.log("Error sending transaction $e - $s", level: LogLevel.Error); + rethrow; + } } @override @@ -139,8 +274,8 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI @override Future estimateFeeFor(Amount amount, int feeRate) { - // TODO: implement estimateFeeFor - throw UnimplementedError(); + // fees are always 0 :) + return Future.value(Amount(rawValue: BigInt.from(0), fractionDigits: 7)); } @override @@ -163,10 +298,15 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI final response = await http.post(Uri.parse(getCurrentNode().host), headers: headers, body: body); final data = jsonDecode(response.body); _balance = Balance( - total: Amount(rawValue: (BigInt.parse(data["balance"].toString()) + BigInt.parse(data["receivable"].toString())) ~/ BigInt.from(10).pow(23), fractionDigits: 7), - spendable: Amount(rawValue: BigInt.parse(data["balance"].toString()) ~/ BigInt.from(10).pow(23), fractionDigits: 7), + total: Amount( + rawValue: (BigInt.parse(data["balance"].toString()) /*+ BigInt.parse(data["receivable"].toString())*/) ~/ + BigInt.from(10).pow(23), + fractionDigits: 7), + spendable: + Amount(rawValue: BigInt.parse(data["balance"].toString()) ~/ BigInt.from(10).pow(23), fractionDigits: 7), blockedTotal: Amount(rawValue: BigInt.parse("0"), fractionDigits: 30), - pendingSpendable: Amount(rawValue: BigInt.parse(data["receivable"].toString()) ~/ BigInt.from(10).pow(23), fractionDigits: 7), + pendingSpendable: + Amount(rawValue: BigInt.parse(data["receivable"].toString()) ~/ BigInt.from(10).pow(23), fractionDigits: 7), ); await updateCachedBalance(_balance!); } @@ -177,7 +317,13 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI Future updateTransactions() async { await confirmAllReceivable(); - final response = await http.post(Uri.parse(getCurrentNode().host), headers: {"Content-Type": "application/json"}, body: jsonEncode({"action": "account_history", "account": await getAddressFromMnemonic(), "count": "-1"})); + final response = await http.post(Uri.parse(getCurrentNode().host), + headers: {"Content-Type": "application/json"}, + body: jsonEncode({ + "action": "account_history", + "account": await getAddressFromMnemonic(), + "count": "-1", + })); final data = await jsonDecode(response.body); final transactions = data["history"] as List; if (transactions.isEmpty) { @@ -205,7 +351,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI subType: TransactionSubType.none, amount: intAmount, amountString: strAmount, - fee: 0, // TODO: Use real fee? + fee: 0, height: int.parse(tx["height"].toString()), isCancelled: false, isLelantus: false, @@ -213,8 +359,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI otherData: "", inputs: [], outputs: [], - nonce: 0 - ); + nonce: 0); transactionList.add(transaction); } await db.putTransactions(transactionList); @@ -247,8 +392,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI @override Future initializeNew() async { if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { - throw Exception( - "Attempted to overwrite mnemonic on generate new wallet!"); + throw Exception("Attempted to overwrite mnemonic on generate new wallet!"); } await _prefs.init(); @@ -256,8 +400,8 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI String seed = NanoSeeds.generateSeed(); final mnemonic = NanoMnemomics.seedToMnemonic(seed); await _secureStore.write( - key: '${_walletId}_mnemonic', - value: mnemonic.join(' '), + key: '${_walletId}_mnemonic', + value: mnemonic.join(' '), ); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', @@ -279,10 +423,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI await db.putAddress(address); - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false) - ]); + await Future.wait([updateCachedId(walletId), updateCachedIsFavorite(false)]); } @override @@ -296,8 +437,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI bool refreshMutex = false; @override - // TODO: implement maxFee - Future get maxFee => throw UnimplementedError(); + Future get maxFee => Future.value(0); @override Future> get mnemonic => _getMnemonicList(); @@ -312,26 +452,54 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI } @override - Future> prepareSend({required String address, required Amount amount, Map? args}) { - // TODO: implement prepareSend - throw UnimplementedError(); + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { + try { + int satAmount = amount.raw.toInt(); + int realfee = 0; + + if (balance.spendable == amount) { + satAmount = balance.spendable.raw.toInt() - realfee; + } + + Map txData = { + "fee": realfee, + "addresss": address, + "recipientAmt": Amount( + rawValue: BigInt.from(satAmount), + fractionDigits: coin.decimals, + ), + }; + + Logging.instance.log("prepare send: $txData", level: LogLevel.Info); + return txData; + } catch (e, s) { + Logging.instance.log("Error getting fees $e - $s", level: LogLevel.Error); + rethrow; + } } @override - Future recoverFromMnemonic({required String mnemonic, String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height}) async { + Future recoverFromMnemonic( + {required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height}) async { try { - if ((await mnemonicString) != null || - (await this.mnemonicPassphrase) != null) { + if ((await mnemonicString) != null || (await this.mnemonicPassphrase) != null) { throw Exception("Attempted to overwrite mnemonic on restore!"); } - - await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonic.trim()); + + await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic.trim()); await _secureStore.write( key: '${_walletId}_mnemonicPassphrase', value: mnemonicPassphrase ?? "", ); - + String seed = NanoMnemomics.mnemonicListToSeed(mnemonic.split(" ")); String privateKey = NanoKeys.seedToPrivate(seed, 0); String publicKey = NanoKeys.createPublicKey(privateKey); @@ -349,10 +517,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI await db.putAddress(address); - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false) - ]); + await Future.wait([updateCachedId(walletId), updateCachedIsFavorite(false)]); } catch (e) { rethrow; } @@ -370,11 +535,10 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI NodeModel getCurrentNode() { return _xnoNode ?? - NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? + NodeService(secureStorageInterface: _secureStore).getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); } - + @override Future testNetworkConnection() { http.get(Uri.parse("${getCurrentNode().host}?action=version")).then((response) { @@ -390,8 +554,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI @override Future updateNode(bool shouldRefresh) async { - _xnoNode = NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? + _xnoNode = NodeService(secureStorageInterface: _secureStore).getPrimaryNodeFor(coin: coin) ?? DefaultNodes.getNodeFor(coin); if (shouldRefresh) { @@ -413,4 +576,4 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI bool validateAddress(String address) { return NanoAccounts.isValid(NanoAccountType.NANO, address); } -} \ No newline at end of file +} diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index f16d8a5f2..c62ea4d84 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -210,7 +210,7 @@ class ParticlWallet extends CoinServiceAPI Future get maxFee async { final fee = (await fees).fast as String; final satsFee = - Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin).toInt()); return satsFee.floor().toBigInt().toInt(); } @@ -2191,7 +2191,7 @@ class ParticlWallet extends CoinServiceAPI if (prevOut == out["n"]) { inputAmtSentFromWallet += (Decimal.parse(out["value"]!.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); } @@ -2210,7 +2210,7 @@ class ParticlWallet extends CoinServiceAPI output["scriptPubKey"]!["addresses"][0] as String; final value = output["value"]!; final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); totalOutput += _value; @@ -2245,7 +2245,7 @@ class ParticlWallet extends CoinServiceAPI level: LogLevel.Info); final ctFee = output["ct_fee"]!; final feeValue = (Decimal.parse(ctFee.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); Logging.instance.log( @@ -2280,7 +2280,7 @@ class ParticlWallet extends CoinServiceAPI output["scriptPubKey"]?["addresses"]?[0] as String?; if (address != null) { final value = (Decimal.parse((output["value"] ?? 0).toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); totalOut += value; @@ -2306,7 +2306,7 @@ class ParticlWallet extends CoinServiceAPI for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { totalIn += (Decimal.parse((out["value"] ?? 0).toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) + Decimal.fromInt(Constants.satsPerCoin(coin).toInt())) .toBigInt() .toInt(); } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 1b564dacc..151c47408 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -25,11 +25,13 @@ abstract class Constants { // static bool enableBuy = enableExchange; // // true; // true for development, - static const int _satsPerCoinEthereum = 1000000000000000000; - static const int _satsPerCoinMonero = 1000000000000; - static const int _satsPerCoinWownero = 100000000000; - static const int _satsPerCoin = 100000000; + static final BigInt _satsPerCoinEthereum = BigInt.from(1000000000000000000); + static final BigInt _satsPerCoinMonero = BigInt.from(1000000000000); + static final BigInt _satsPerCoinWownero = BigInt.from(100000000000); + static final BigInt _satsPerCoinNano = BigInt.parse("1000000000000000000000000000000"); + static final BigInt _satsPerCoin = BigInt.from(100000000); static const int _decimalPlaces = 8; + static const int _decimalPlacesNano = 6; static const int _decimalPlacesWownero = 11; static const int _decimalPlacesMonero = 12; static const int _decimalPlacesEthereum = 18; @@ -46,7 +48,7 @@ abstract class Constants { static const int rescanV1 = 1; - static int satsPerCoin(Coin coin) { + static BigInt satsPerCoin(Coin coin) { switch (coin) { case Coin.bitcoin: case Coin.litecoin: @@ -61,9 +63,11 @@ abstract class Constants { case Coin.epicCash: case Coin.namecoin: case Coin.particl: - case Coin.nano: // TODO: Check this: https://nano.org/en/faq#what-are-the-units-of-nano return _satsPerCoin; + case Coin.nano: + return _satsPerCoinNano; + case Coin.wownero: return _satsPerCoinWownero; @@ -90,9 +94,11 @@ abstract class Constants { case Coin.epicCash: case Coin.namecoin: case Coin.particl: - case Coin.nano: return _decimalPlaces; + case Coin.nano: + return _decimalPlacesNano; + case Coin.wownero: return _decimalPlacesWownero; From 03d69d44ceeb2b37ee011b52d5d5ea07945ca03a Mon Sep 17 00:00:00 2001 From: fossephate Date: Wed, 24 May 2023 12:12:28 -0400 Subject: [PATCH 2/2] small cleanup --- lib/services/coins/nano/nano_wallet.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/services/coins/nano/nano_wallet.dart b/lib/services/coins/nano/nano_wallet.dart index 58de81577..2b7b67c15 100644 --- a/lib/services/coins/nano/nano_wallet.dart +++ b/lib/services/coins/nano/nano_wallet.dart @@ -256,11 +256,6 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI throw Exception("Received error ${decoded["error"]}"); } - print(jsonDecode(processBody)); - print(jsonDecode(processResponse.body)); - - throw Exception("Received error ${decoded["error"]}"); - // return the hash of the transaction: return decoded["hash"].toString(); } catch (e, s) {