What changed, and why it matters
This commit changes how transaction fees are estimated across many cryptocurrency wallets in the Stack Wallet app. The old formula used regular math with decimal numbers, which could lose precision or round incorrectly on very large fee values. The new formula uses exact integer (BigInt) arithmetic. The change is likely a bug-fix or hardening improvement rather than an active vulnerability, but it touches fee logic for many coins so it has broad reach.
Review the new integer fee formula against each coin's expected fee semantics to ensure truncation (`~/`) does not underpay fees or break minimum-relay-fee requirements. Confirm that `feeRatePerKB * vSize` cannot overflow the platform's BigInt implementation. Consider adding unit tests covering edge-case fee rates and vSize values.
Security signals we found
Fee-estimation arithmetic changed from double-based to BigInt-based across 11 wallet implementations
Potential precision/rounding issue in previous fee calculation when feeRatePerKB is very large
No explicit security disclosure, CVE, or researcher attribution in commit or supplied references
No input validation changes or network/authorization changes observed
Evidence from the diff
The core functional change replaces vSize * (feeRatePerKB.toInt() / 1000).ceil() with (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt() in 11 wallet implementations. The old code converts a BigInt fee rate to int, divides by 1000 as a double, then multiplies by vSize and ceils. The new code performs the multiplication in BigInt space and uses integer truncation division (~/). This avoids floating-point rounding and possible overflow/precision issues when feeRatePerKB is large. The rest of the diff is formatting/whitespace changes (line wrapping) and has no functional effect.
Changed components
lib/wallets/wallet/impl/bitcoin_wallet.dartlib/wallets/wallet/impl/bitcoincash_wallet.dartlib/wallets/wallet/impl/dash_wallet.dartlib/wallets/wallet/impl/dogecoin_wallet.dartlib/wallets/wallet/impl/ecash_wallet.dartlib/wallets/wallet/impl/fact0rn_wallet.dartlib/wallets/wallet/impl/firo_wallet.dartlib/wallets/wallet/impl/litecoin_wallet.dartlib/wallets/wallet/impl/namecoin_wallet.dartlib/wallets/wallet/impl/particl_wallet.dartlib/wallets/wallet/impl/peercoin_wallet.dartInspect captured patch +342 / −387
diff --git a/lib/wallets/wallet/impl/bitcoin_wallet.dart b/lib/wallets/wallet/impl/bitcoin_wallet.dart
index 6361ec1..dc1cf5d 100644
--- a/lib/wallets/wallet/impl/bitcoin_wallet.dart
+++ b/lib/wallets/wallet/impl/bitcoin_wallet.dart
@@ -37,18 +37,17 @@ class BitcoinWallet<T extends PaynymCurrencyInterface> extends Bip39HDWallet<T>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -71,7 +70,7 @@ class BitcoinWallet<T extends PaynymCurrencyInterface> extends Bip39HDWallet<T>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
//
diff --git a/lib/wallets/wallet/impl/bitcoincash_wallet.dart b/lib/wallets/wallet/impl/bitcoincash_wallet.dart
index 5edcbf9..4191052 100644
--- a/lib/wallets/wallet/impl/bitcoincash_wallet.dart
+++ b/lib/wallets/wallet/impl/bitcoincash_wallet.dart
@@ -67,20 +67,19 @@ class BitcoincashWallet<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .typeEqualTo(AddressType.nonWallet)
- .and()
- .group(
- (q) => q
- .subTypeEqualTo(AddressSubType.receiving)
- .or()
- .subTypeEqualTo(AddressSubType.change),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .typeEqualTo(AddressType.nonWallet)
+ .and()
+ .group(
+ (q) => q
+ .subTypeEqualTo(AddressSubType.receiving)
+ .or()
+ .subTypeEqualTo(AddressSubType.change),
+ )
+ .findAll();
return allAddresses;
}
@@ -103,17 +102,15 @@ class BitcoincashWallet<T extends ElectrumXCurrencyInterface>
final List<Address> allAddressesOld =
await fetchAddressesForElectrumXScan();
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => convertAddressString(e.value))
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => convertAddressString(e.value))
+ .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => convertAddressString(e.value))
- .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => convertAddressString(e.value))
+ .toSet();
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -389,7 +386,7 @@ class BitcoincashWallet<T extends ElectrumXCurrencyInterface>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
@override
diff --git a/lib/wallets/wallet/impl/dash_wallet.dart b/lib/wallets/wallet/impl/dash_wallet.dart
index 9d39bd2..a00faf7 100644
--- a/lib/wallets/wallet/impl/dash_wallet.dart
+++ b/lib/wallets/wallet/impl/dash_wallet.dart
@@ -36,18 +36,17 @@ class DashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -60,16 +59,14 @@ class DashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
await fetchAddressesForElectrumXScan();
// Separate receiving and change addresses.
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => e.value)
- .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => e.value)
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => e.value)
+ .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => e.value)
+ .toSet();
// Remove duplicates.
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -83,11 +80,10 @@ class DashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
// Check for duplicates by searching for tx by tx_hash in db.
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
@@ -319,6 +315,6 @@ class DashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
}
diff --git a/lib/wallets/wallet/impl/dogecoin_wallet.dart b/lib/wallets/wallet/impl/dogecoin_wallet.dart
index 01a1eed..444b0ba 100644
--- a/lib/wallets/wallet/impl/dogecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/dogecoin_wallet.dart
@@ -38,18 +38,17 @@ class DogecoinWallet<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -62,16 +61,14 @@ class DogecoinWallet<T extends ElectrumXCurrencyInterface>
await fetchAddressesForElectrumXScan();
// Separate receiving and change addresses.
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => e.value)
- .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => e.value)
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => e.value)
+ .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => e.value)
+ .toSet();
// Remove duplicates.
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -85,11 +82,10 @@ class DogecoinWallet<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
// Check for duplicates by searching for tx by tx_hash in db.
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
@@ -323,6 +319,6 @@ class DogecoinWallet<T extends ElectrumXCurrencyInterface>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
}
diff --git a/lib/wallets/wallet/impl/ecash_wallet.dart b/lib/wallets/wallet/impl/ecash_wallet.dart
index 4a72b2b..9e83afb 100644
--- a/lib/wallets/wallet/impl/ecash_wallet.dart
+++ b/lib/wallets/wallet/impl/ecash_wallet.dart
@@ -55,16 +55,15 @@ class EcashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .typeEqualTo(AddressType.nonWallet)
- .and()
- .not()
- .subTypeEqualTo(AddressSubType.nonWallet)
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .typeEqualTo(AddressType.nonWallet)
+ .and()
+ .not()
+ .subTypeEqualTo(AddressSubType.nonWallet)
+ .findAll();
return allAddresses;
}
@@ -87,17 +86,15 @@ class EcashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
final List<Address> allAddressesOld =
await fetchAddressesForElectrumXScan();
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => convertAddressString(e.value))
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => convertAddressString(e.value))
+ .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => convertAddressString(e.value))
- .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => convertAddressString(e.value))
+ .toSet();
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -108,11 +105,10 @@ class EcashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
@@ -360,7 +356,7 @@ class EcashWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
@override
diff --git a/lib/wallets/wallet/impl/fact0rn_wallet.dart b/lib/wallets/wallet/impl/fact0rn_wallet.dart
index 0f6a93d..3ddd053 100644
--- a/lib/wallets/wallet/impl/fact0rn_wallet.dart
+++ b/lib/wallets/wallet/impl/fact0rn_wallet.dart
@@ -35,18 +35,17 @@ class Fact0rnWallet<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -59,16 +58,14 @@ class Fact0rnWallet<T extends ElectrumXCurrencyInterface>
await fetchAddressesForElectrumXScan();
// Separate receiving and change addresses.
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => e.value)
- .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => e.value)
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => e.value)
+ .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => e.value)
+ .toSet();
// Remove duplicates.
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -82,11 +79,10 @@ class Fact0rnWallet<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
// Check for duplicates by searching for tx by tx_hash in db.
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
@@ -321,6 +317,6 @@ class Fact0rnWallet<T extends ElectrumXCurrencyInterface>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
}
diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart
index 8c69f79..bd2b3f7 100644
--- a/lib/wallets/wallet/impl/firo_wallet.dart
+++ b/lib/wallets/wallet/impl/firo_wallet.dart
@@ -813,6 +813,6 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
}
diff --git a/lib/wallets/wallet/impl/litecoin_wallet.dart b/lib/wallets/wallet/impl/litecoin_wallet.dart
index db497a9..c9fa52a 100644
--- a/lib/wallets/wallet/impl/litecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/litecoin_wallet.dart
@@ -49,20 +49,19 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.mweb)
- .or()
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.mweb)
+ .or()
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -75,16 +74,14 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
await fetchAddressesForElectrumXScan();
// Separate receiving and change addresses.
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => e.value)
- .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => e.value)
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => e.value)
+ .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => e.value)
+ .toSet();
// Remove duplicates.
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -102,11 +99,10 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
// Check for duplicates by searching for tx by tx_hash in db.
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
@@ -239,10 +235,9 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
final db = Drift.get(walletId);
- final mwebUtxo =
- await (db.select(
- db.mwebUtxos,
- )..where((e) => e.outputId.equals(outputId))).getSingleOrNull();
+ final mwebUtxo = await (db.select(
+ db.mwebUtxos,
+ )..where((e) => e.outputId.equals(outputId))).getSingleOrNull();
final output = OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: "mweb",
@@ -283,13 +278,12 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
// Check for special Litecoin outputs like ordinals.
if (outputs.isNotEmpty) {
// may not catch every case but it is much quicker
- final hasOrdinal =
- await mainDB.isar.ordinals
- .where()
- .filter()
- .walletIdEqualTo(walletId)
- .utxoTXIDEqualTo(txData["txid"] as String)
- .isNotEmpty();
+ final hasOrdinal = await mainDB.isar.ordinals
+ .where()
+ .filter()
+ .walletIdEqualTo(walletId)
+ .utxoTXIDEqualTo(txData["txid"] as String)
+ .isNotEmpty();
if (hasOrdinal) {
subType = TransactionSubType.ordinal;
} else {
@@ -384,7 +378,7 @@ class LitecoinWallet<T extends ElectrumXCurrencyInterface>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
//
diff --git a/lib/wallets/wallet/impl/namecoin_wallet.dart b/lib/wallets/wallet/impl/namecoin_wallet.dart
index a6dd6f7..10ea40c 100644
--- a/lib/wallets/wallet/impl/namecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/namecoin_wallet.dart
@@ -72,18 +72,17 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -140,9 +139,8 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
blockReason = "Contains name";
try {
- final rawNameOP =
- (output["scriptPubKey"]["nameOp"] as Map)
- .cast<String, dynamic>();
+ final rawNameOP = (output["scriptPubKey"]["nameOp"] as Map)
+ .cast<String, dynamic>();
otherDataString = jsonEncode({
UTXOOtherDataKeys.nameOpData: jsonEncode(rawNameOP),
@@ -201,7 +199,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
// TODO: Check if this is the correct formula for namecoin.
@@ -227,16 +225,14 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
await fetchAddressesForElectrumXScan();
// Separate receiving and change addresses.
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => e.value)
- .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => e.value)
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => e.value)
+ .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => e.value)
+ .toSet();
// Remove duplicates.
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -250,11 +246,10 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
// Check for duplicates by searching for tx by tx_hash in db.
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
@@ -442,8 +437,11 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
) async {
// first check own utxos. Should only need to check NAME NEW here.
// NAME UPDATE and NAME FIRST UPDATE will appear readable from electrumx
- final utxos =
- await mainDB.getUTXOs(walletId).filter().otherDataIsNotNull().findAll();
+ final utxos = await mainDB
+ .getUTXOs(walletId)
+ .filter()
+ .otherDataIsNotNull()
+ .findAll();
for (final utxo in utxos) {
final nameOp = getOpNameDataFrom(utxo);
if (nameOp?.op == OpName.nameNew) {
@@ -509,18 +507,17 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
try {
final currentHeight = await chainHeight;
// not ideal filtering
- final utxos =
- await mainDB
- .getUTXOs(walletId)
- .filter()
- .otherDataIsNotNull()
- .and()
- .blockHeightIsNotNull()
- .and()
- .blockHeightGreaterThan(0)
- .and()
- .blockHeightLessThan(currentHeight - kNameWaitBlocks)
- .findAll();
+ final utxos = await mainDB
+ .getUTXOs(walletId)
+ .filter()
+ .otherDataIsNotNull()
+ .and()
+ .blockHeightIsNotNull()
+ .and()
+ .blockHeightGreaterThan(0)
+ .and()
+ .blockHeightLessThan(currentHeight - kNameWaitBlocks)
+ .findAll();
Logging.instance.t(
"_unknownNameNewOutputs(count=${_unknownNameNewOutputs.length})"
@@ -572,8 +569,9 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
data.salt,
);
- String noteName =
- data.name.startsWith("d/") ? data.name.substring(2) : data.name;
+ String noteName = data.name.startsWith("d/")
+ ? data.name.substring(2)
+ : data.name;
if (!noteName.endsWith(".bit")) {
noteName += ".bit";
}
@@ -638,8 +636,10 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
assert(txData.recipients!.where((e) => !e.isChange).length == 1);
if (!isForFeeCalcPurposesOnly) {
- final nameAmount =
- txData.recipients!.where((e) => !e.isChange).first.amount;
+ final nameAmount = txData.recipients!
+ .where((e) => !e.isChange)
+ .first
+ .amount;
switch (txData.opNameState!.type) {
case OpName.nameNew:
@@ -664,10 +664,9 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
);
// TODO: [prio=high]: check this opt in rbf
- final sequence =
- this is RbfInterface && (this as RbfInterface).flagOptInRBF
- ? 0xffffffff - 10
- : 0xffffffff - 1;
+ final sequence = this is RbfInterface && (this as RbfInterface).flagOptInRBF
+ ? 0xffffffff - 10
+ : 0xffffffff - 1;
// Add transaction inputs
for (int i = 0; i < inputsWithKeys.length; i++) {
@@ -737,10 +736,9 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
txid: inputsWithKeys[i].utxo.txid,
vout: inputsWithKeys[i].utxo.vout,
),
- addresses:
- inputsWithKeys[i].utxo.address == null
- ? []
- : [inputsWithKeys[i].utxo.address!],
+ addresses: inputsWithKeys[i].utxo.address == null
+ ? []
+ : [inputsWithKeys[i].utxo.address!],
valueStringSats: inputsWithKeys[i].utxo.value.toString(),
witness: null,
innerRedeemScriptAsm: null,
@@ -872,9 +870,9 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
version: clTx.version,
type:
tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e) &&
- txData.paynymAccountLite == null
- ? TransactionType.sentToSelf
- : TransactionType.outgoing,
+ txData.paynymAccountLite == null
+ ? TransactionType.sentToSelf
+ : TransactionType.outgoing,
subType: TransactionSubType.none,
otherData: null,
),
@@ -1023,20 +1021,19 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
final canCPFP = this is CpfpInterface && coinControl;
- final spendableOutputs =
- availableOutputs
- .where(
- (e) =>
- !e.isBlocked &&
- (e.used != true) &&
- (canCPFP ||
- e.isConfirmed(
- currentChainHeight,
- cryptoCurrency.minConfirms,
- cryptoCurrency.minCoinbaseConfirms,
- )),
- )
- .toList();
+ final spendableOutputs = availableOutputs
+ .where(
+ (e) =>
+ !e.isBlocked &&
+ (e.used != true) &&
+ (canCPFP ||
+ e.isConfirmed(
+ currentChainHeight,
+ cryptoCurrency.minConfirms,
+ cryptoCurrency.minCoinbaseConfirms,
+ )),
+ )
+ .toList();
if (coinControl) {
if (spendableOutputs.length < availableOutputs.length) {
@@ -1118,24 +1115,22 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
final List<BigInt> recipientsAmtArray = [satoshiAmountToSend];
// gather required signing data
- final inputsWithKeys =
- (await addSigningKeys(
- utxoObjectsToUse.map((e) => StandardInput(e)).toList(),
- )).whereType<StandardInput>().toList();
+ final inputsWithKeys = (await addSigningKeys(
+ utxoObjectsToUse.map((e) => StandardInput(e)).toList(),
+ )).whereType<StandardInput>().toList();
final int vSizeForOneOutput;
try {
- vSizeForOneOutput =
- (await _createNameTx(
- inputsWithKeys: inputsWithKeys,
- isForFeeCalcPurposesOnly: true,
- txData: txData.copyWith(
- recipients: await helperRecipientsConvert(
- [recipientAddress],
- [satoshisBeingUsed],
- ),
- ),
- )).vSize!;
+ vSizeForOneOutput = (await _createNameTx(
+ inputsWithKeys: inputsWithKeys,
+ isForFeeCalcPurposesOnly: true,
+ txData: txData.copyWith(
+ recipients: await helperRecipientsConvert(
+ [recipientAddress],
+ [satoshisBeingUsed],
+ ),
+ ),
+ )).vSize!;
} catch (e, s) {
Logging.instance.e("vSizeForOneOutput: $e", error: e, stackTrace: s);
rethrow;
@@ -1146,20 +1141,19 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
BigInt maxBI(BigInt a, BigInt b) => a > b ? a : b;
try {
- vSizeForTwoOutPuts =
- (await _createNameTx(
- inputsWithKeys: inputsWithKeys,
- isForFeeCalcPurposesOnly: true,
- txData: txData.copyWith(
- recipients: await helperRecipientsConvert(
- [recipientAddress, (await getCurrentChangeAddress())!.value],
- [
- satoshiAmountToSend,
- maxBI(BigInt.zero, satoshisBeingUsed - satoshiAmountToSend),
- ],
- ),
- ),
- )).vSize!;
+ vSizeForTwoOutPuts = (await _createNameTx(
+ inputsWithKeys: inputsWithKeys,
+ isForFeeCalcPurposesOnly: true,
+ txData: txData.copyWith(
+ recipients: await helperRecipientsConvert(
+ [recipientAddress, (await getCurrentChangeAddress())!.value],
+ [
+ satoshiAmountToSend,
+ maxBI(BigInt.zero, satoshisBeingUsed - satoshiAmountToSend),
+ ],
+ ),
+ ),
+ )).vSize!;
} catch (e, s) {
Logging.instance.e("vSizeForTwoOutPuts: $e", error: e, stackTrace: s);
rethrow;
@@ -1170,18 +1164,18 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
satsPerVByte != null
? (satsPerVByte * vSizeForOneOutput)
: estimateTxFee(
- vSize: vSizeForOneOutput,
- feeRatePerKB: selectedTxFeeRate,
- ),
+ vSize: vSizeForOneOutput,
+ feeRatePerKB: selectedTxFeeRate,
+ ),
);
// Assume 2 outputs, one for recipient and one for change
final feeForTwoOutputs = BigInt.from(
satsPerVByte != null
? (satsPerVByte * vSizeForTwoOutPuts)
: estimateTxFee(
- vSize: vSizeForTwoOutPuts,
- feeRatePerKB: selectedTxFeeRate,
- ),
+ vSize: vSizeForTwoOutPuts,
+ feeRatePerKB: selectedTxFeeRate,
+ ),
);
Logging.instance.d(
diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart
index eb9fb60..6f2b976 100644
--- a/lib/wallets/wallet/impl/particl_wallet.dart
+++ b/lib/wallets/wallet/impl/particl_wallet.dart
@@ -45,18 +45,17 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -115,7 +114,7 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
@override
@@ -140,16 +139,14 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
await fetchAddressesForElectrumXScan();
// Separate receiving and change addresses.
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => e.value)
- .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => e.value)
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => e.value)
+ .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => e.value)
+ .toSet();
// Remove duplicates.
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -163,11 +160,10 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
// Check for duplicates by searching for tx by tx_hash in db.
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
@@ -382,31 +378,28 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
switch (sd.derivePathType) {
case DerivePathType.bip44:
- data =
- bitcoindart
- .P2PKH(
- data: bitcoindart.PaymentData(pubkey: pubKey),
- network: convertedNetwork,
- )
- .data;
+ data = bitcoindart
+ .P2PKH(
+ data: bitcoindart.PaymentData(pubkey: pubKey),
+ network: convertedNetwork,
+ )
+ .data;
break;
case DerivePathType.bip49:
- final p2wpkh =
- bitcoindart
- .P2WPKH(
- data: bitcoindart.PaymentData(pubkey: pubKey),
- network: convertedNetwork,
- )
- .data;
+ final p2wpkh = bitcoindart
+ .P2WPKH(
+ data: bitcoindart.PaymentData(pubkey: pubKey),
+ network: convertedNetwork,
+ )
+ .data;
redeem = p2wpkh.output;
- data =
- bitcoindart
- .P2SH(
- data: bitcoindart.PaymentData(redeem: p2wpkh),
- network: convertedNetwork,
- )
- .data;
+ data = bitcoindart
+ .P2SH(
+ data: bitcoindart.PaymentData(redeem: p2wpkh),
+ network: convertedNetwork,
+ )
+ .data;
break;
case DerivePathType.bip84:
@@ -414,13 +407,12 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
// prevOut: coinlib.OutPoint.fromHex(sd.utxo.txid, sd.utxo.vout),
// publicKey: keys.publicKey,
// );
- data =
- bitcoindart
- .P2WPKH(
- data: bitcoindart.PaymentData(pubkey: pubKey),
- network: convertedNetwork,
- )
- .data;
+ data = bitcoindart
+ .P2WPKH(
+ data: bitcoindart.PaymentData(pubkey: pubKey),
+ network: convertedNetwork,
+ )
+ .data;
break;
case DerivePathType.bip86:
@@ -469,10 +461,9 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
txid: insAndKeys[i].utxo.txid,
vout: insAndKeys[i].utxo.vout,
),
- addresses:
- insAndKeys[i].utxo.address == null
- ? []
- : [insAndKeys[i].utxo.address!],
+ addresses: insAndKeys[i].utxo.address == null
+ ? []
+ : [insAndKeys[i].utxo.address!],
valueStringSats: insAndKeys[i].utxo.value.toString(),
witness: null,
innerRedeemScriptAsm: null,
diff --git a/lib/wallets/wallet/impl/peercoin_wallet.dart b/lib/wallets/wallet/impl/peercoin_wallet.dart
index 8046f0d..bcdb36c 100644
--- a/lib/wallets/wallet/impl/peercoin_wallet.dart
+++ b/lib/wallets/wallet/impl/peercoin_wallet.dart
@@ -37,18 +37,17 @@ class PeercoinWallet<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -74,7 +73,7 @@ class PeercoinWallet<T extends ElectrumXCurrencyInterface>
/// we can just pretend vSize is size for peercoin
@override
int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) {
- return vSize * (feeRatePerKB.toInt() / 1000).ceil();
+ return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt();
}
// ===========================================================================
@@ -98,16 +97,14 @@ class PeercoinWallet<T extends ElectrumXCurrencyInterface>
await fetchAddressesForElectrumXScan();
// Separate receiving and change addresses.
- final Set<String> receivingAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.receiving)
- .map((e) => e.value)
- .toSet();
- final Set<String> changeAddresses =
- allAddressesOld
- .where((e) => e.subType == AddressSubType.change)
- .map((e) => e.value)
- .toSet();
+ final Set<String> receivingAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.receiving)
+ .map((e) => e.value)
+ .toSet();
+ final Set<String> changeAddresses = allAddressesOld
+ .where((e) => e.subType == AddressSubType.change)
+ .map((e) => e.value)
+ .toSet();
// Remove duplicates.
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
@@ -121,11 +118,10 @@ class PeercoinWallet<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
// Check for duplicates by searching for tx by tx_hash in db.
- final storedTx =
- await mainDB.isar.transactionV2s
- .where()
- .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
- .findFirst();
+ final storedTx = await mainDB.isar.transactionV2s
+ .where()
+ .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
+ .findFirst();
if (storedTx == null ||
storedTx.height == null ||
Why this scored 38/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.