mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
commit
93f0cda444
10 changed files with 269 additions and 151 deletions
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue