Merge pull request #551 from fossephate/add-nano

Nano Sends
This commit is contained in:
julian-CStack 2023-05-24 10:34:24 -06:00 committed by GitHub
commit 93f0cda444
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 269 additions and 151 deletions

View file

@ -50,7 +50,7 @@ class MainDB {
}
// contact entries
List<ContactEntry> getContactEntries(){
List<ContactEntry> getContactEntries() {
return isar.contactEntrys.where().findAllSync();
}
@ -66,11 +66,7 @@ class MainDB {
}
Future<bool> 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<int> putTransactionBlockExplorer(
TransactionBlockExplorer explorer) async {
Future<int> putTransactionBlockExplorer(TransactionBlockExplorer explorer) async {
try {
return await isar.writeTxn(() async {
return await isar.transactionBlockExplorers.put(explorer);
@ -108,8 +100,7 @@ class MainDB {
}
// addresses
QueryBuilder<Address, Address, QAfterWhereClause> getAddresses(
String walletId) =>
QueryBuilder<Address, Address, QAfterWhereClause> getAddresses(String walletId) =>
isar.addresses.where().walletIdEqualTo(walletId);
Future<int> putAddress(Address address) async {
@ -137,8 +128,7 @@ class MainDB {
List<int> 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<Transaction, Transaction, QAfterWhereClause> getTransactions(
String walletId) =>
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(String walletId) =>
isar.transactions.where().walletIdEqualTo(walletId);
Future<int> putTransaction(Transaction transaction) async {
@ -220,8 +208,7 @@ class MainDB {
}
// utxos
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) =>
isar.utxos.where().walletIdEqualTo(walletId);
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) => isar.utxos.where().walletIdEqualTo(walletId);
Future<void> 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<TransactionNote, TransactionNote, QAfterWhereClause>
getTransactionNotes(String walletId) =>
isar.transactionNotes.where().walletIdEqualTo(walletId);
QueryBuilder<TransactionNote, TransactionNote, QAfterWhereClause> getTransactionNotes(String walletId) =>
isar.transactionNotes.where().walletIdEqualTo(walletId);
Future<void> putTransactionNote(TransactionNote transactionNote) =>
isar.writeTxn(() async {
Future<void> putTransactionNote(TransactionNote transactionNote) => isar.writeTxn(() async {
await isar.transactionNotes.put(transactionNote);
});
Future<void> putTransactionNotes(List<TransactionNote> transactionNotes) =>
isar.writeTxn(() async {
Future<void> putTransactionNotes(List<TransactionNote> transactionNotes) => isar.writeTxn(() async {
await isar.transactionNotes.putAll(transactionNotes);
});
Future<TransactionNote?> getTransactionNote(
String walletId, String txid) async {
Future<TransactionNote?> 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<AddressLabel, AddressLabel, QAfterWhereClause> getAddressLabels(
String walletId) =>
QueryBuilder<AddressLabel, AddressLabel, QAfterWhereClause> getAddressLabels(String walletId) =>
isar.addressLabels.where().walletIdEqualTo(walletId);
Future<int> putAddressLabel(AddressLabel addressLabel) =>
isar.writeTxn(() async {
Future<int> putAddressLabel(AddressLabel addressLabel) => isar.writeTxn(() async {
return await isar.addressLabels.put(addressLabel);
});
@ -313,13 +291,11 @@ class MainDB {
return isar.addressLabels.putSync(addressLabel);
});
Future<void> putAddressLabels(List<AddressLabel> addressLabels) =>
isar.writeTxn(() async {
Future<void> putAddressLabels(List<AddressLabel> addressLabels) => isar.writeTxn(() async {
await isar.addressLabels.putAll(addressLabels);
});
Future<AddressLabel?> getAddressLabel(
String walletId, String addressString) async {
Future<AddressLabel?> 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<EthContract, EthContract, QWhere> getEthContracts() =>
isar.ethContracts.where();
QueryBuilder<EthContract, EthContract, QWhere> getEthContracts() => isar.ethContracts.where();
Future<EthContract?> getEthContract(String contractAddress) =>
isar.ethContracts.where().addressEqualTo(contractAddress).findFirst();
@ -478,8 +433,7 @@ class MainDB {
return await isar.ethContracts.put(contract);
});
Future<void> putEthContracts(List<EthContract> contracts) =>
isar.writeTxn(() async {
Future<void> putEthContracts(List<EthContract> contracts) => isar.writeTxn(() async {
await isar.ethContracts.putAll(contracts);
});
}

View file

@ -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());
}

View file

@ -1412,7 +1412,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
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<DesktopSend> {
),
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,
),

View file

@ -278,7 +278,7 @@ class BitcoinWallet extends CoinServiceAPI
Future<int> 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();
}

View file

@ -2338,7 +2338,7 @@ class FiroWallet extends CoinServiceAPI
Future<int> _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) {

View file

@ -223,7 +223,7 @@ class LitecoinWallet extends CoinServiceAPI
Future<int> 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();
}

View file

@ -215,7 +215,7 @@ class NamecoinWallet extends CoinServiceAPI
Future<int> 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();
}

View file

@ -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<String?> get mnemonicPassphrase => _secureStore.read(
key: '${_walletId}_mnemonicPassphrase',
);
key: '${_walletId}_mnemonicPassphrase',
);
@override
Future<String?> get mnemonicString =>
_secureStore.read(key: '${_walletId}_mnemonic');
Future<String?> get mnemonicString => _secureStore.read(key: '${_walletId}_mnemonic');
Future<String> getSeedFromMnemonic() async {
var mnemonic = await mnemonicString;
@ -73,7 +74,8 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
Future<String> 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,138 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
Balance get balance => _balance ??= getCachedBalance();
Balance? _balance;
Future<String?> 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<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
return decoded["work"] as String?;
} else {
throw Exception("Received error ${response.statusCode}");
}
});
}
@override
Future<String> confirmSend({required Map<String, dynamic> txData}) {
// TODO: implement confirmSend
throw UnimplementedError();
Future<String> confirmSend({required Map<String, dynamic> 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<String, String> sendBlock = {
"type": "state",
"account": publicAddress,
"previous": frontier,
"representative": representative,
"balance": balanceAfterTx.toString(),
"link": link,
};
// sign the send block:
final String hash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
sendBlock["account"]!,
sendBlock["previous"]!,
sendBlock["representative"]!,
BigInt.parse(sendBlock["balance"]!),
sendBlock["link"]!,
);
final String 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<String, String> 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<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
// return the hash of the transaction:
return decoded["hash"].toString();
} catch (e, s) {
Logging.instance.log("Error sending transaction $e - $s", level: LogLevel.Error);
rethrow;
}
}
@override
@ -139,8 +269,8 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
@override
Future<Amount> 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 +293,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 +312,13 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
Future<void> 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<dynamic>;
if (transactions.isEmpty) {
@ -205,7 +346,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 +354,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 +387,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
@override
Future<void> 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 +395,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 +418,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 +432,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
bool refreshMutex = false;
@override
// TODO: implement maxFee
Future<int> get maxFee => throw UnimplementedError();
Future<int> get maxFee => Future.value(0);
@override
Future<List<String>> get mnemonic => _getMnemonicList();
@ -312,26 +447,54 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
}
@override
Future<Map<String, dynamic>> prepareSend({required String address, required Amount amount, Map<String, dynamic>? args}) {
// TODO: implement prepareSend
throw UnimplementedError();
Future<Map<String, dynamic>> prepareSend({
required String address,
required Amount amount,
Map<String, dynamic>? args,
}) async {
try {
int satAmount = amount.raw.toInt();
int realfee = 0;
if (balance.spendable == amount) {
satAmount = balance.spendable.raw.toInt() - realfee;
}
Map<String, dynamic> 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<void> recoverFromMnemonic({required String mnemonic, String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height}) async {
Future<void> 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 +512,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 +530,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<bool> testNetworkConnection() {
http.get(Uri.parse("${getCurrentNode().host}?action=version")).then((response) {
@ -390,8 +549,7 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
@override
Future<void> 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 +571,4 @@ class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlI
bool validateAddress(String address) {
return NanoAccounts.isValid(NanoAccountType.NANO, address);
}
}
}

View file

@ -210,7 +210,7 @@ class ParticlWallet extends CoinServiceAPI
Future<int> 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();
}

View file

@ -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;