mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-04-17 19:51:58 +00:00
commit
02fec3d581
33 changed files with 817 additions and 1013 deletions
android
crypto_plugins
docs
lib/wallets/wallet
pubspec.lockscripts
|
@ -1,3 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
|
|
|
@ -18,7 +18,7 @@ pluginManagement {
|
|||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.6.0' apply false
|
||||
id "com.android.application" version '8.7.0' apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 8830be2ba661828d743be12df6f33d560448ed6a
|
||||
Subproject commit 238455f2b3fe39564cc617ed0ea45f22971aa644
|
|
@ -1 +1 @@
|
|||
Subproject commit 89f05ee42754f80da036ddcf31c7b1e1dc3b4a83
|
||||
Subproject commit 30bc004bd7cadd635e943d29033a8d261cbc1eda
|
|
@ -72,8 +72,8 @@ Install [Rust](https://www.rust-lang.org/tools/install) via [rustup.rs](https://
|
|||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
rustup install 1.85.1
|
||||
rustup default 1.85.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
```
|
||||
|
||||
|
@ -209,12 +209,12 @@ brew install brotli cairo coreutils gdbm gettext glib gmp libevent libidn2 libng
|
|||
```
|
||||
<!-- TODO: determine which of the above list are not needed at all. -->
|
||||
|
||||
Download and install [Rust](https://www.rust-lang.org/tools/install). [Rustup](https://rustup.rs/) is recommended for Rust setup. Use `rustc` to confirm successful installation. Install toolchains 1.67.1 and 1.72.0 and `cbindgen` and `cargo-lipo` too. You will also have to add the platform target(s) `aarch64-apple-ios` and/or `aarch64-apple-darwin`. You can use the command(s):
|
||||
Download and install [Rust](https://www.rust-lang.org/tools/install). [Rustup](https://rustup.rs/) is recommended for Rust setup. Use `rustc` to confirm successful installation. Install toolchain 1.85.1 and `cbindgen` and `cargo-lipo` too. You will also have to add the platform target(s) `aarch64-apple-ios` and/or `aarch64-apple-darwin`. You can use the command(s):
|
||||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.bashrc
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
rustup install 1.85.1
|
||||
rustup default 1.85.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
cargo install cbindgen cargo-lipo
|
||||
rustup target add aarch64-apple-ios aarch64-apple-darwin
|
||||
|
@ -306,8 +306,8 @@ Run `flutter doctor` in PowerShell to confirm its installation.
|
|||
### Rust
|
||||
Install [Rust](https://www.rust-lang.org/tools/install) on the Windows host (not in WSL2). Download the installer from [rustup.rs](https://rustup.rs), make sure it works on the commandline (you may need to open a new terminal), and install the following versions:
|
||||
```
|
||||
rustup install 1.67.1 1.71.0 1.72.0 1.73.0
|
||||
rustup default 1.67.1
|
||||
rustup install 1.85.1
|
||||
rustup default 1.85.1
|
||||
cargo install cargo-ndk --version 2.12.7 --locked
|
||||
```
|
||||
|
||||
|
|
|
@ -41,11 +41,7 @@ String nameSaltKeyBuilder(String txid, String walletId, int txPos) {
|
|||
}
|
||||
|
||||
String encodeNameSaltData(String name, String salt, String value) =>
|
||||
jsonEncode({
|
||||
"name": name,
|
||||
"salt": salt,
|
||||
"value": value,
|
||||
});
|
||||
jsonEncode({"name": name, "salt": salt, "value": value});
|
||||
|
||||
({String salt, String name, String value}) decodeNameSaltData(String value) {
|
||||
try {
|
||||
|
@ -76,25 +72,26 @@ 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;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// ===========================================================================
|
||||
|
||||
@override
|
||||
Future<({String? blockedReason, bool blocked, String? utxoLabel})>
|
||||
checkBlockUTXO(
|
||||
checkBlockUTXO(
|
||||
Map<String, dynamic> jsonUTXO,
|
||||
String? scriptPubKeyHex,
|
||||
Map<String, dynamic> jsonTX,
|
||||
|
@ -108,9 +105,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<UTXO> parseUTXO({
|
||||
required Map<String, dynamic> jsonUTXO,
|
||||
}) async {
|
||||
Future<UTXO> parseUTXO({required Map<String, dynamic> jsonUTXO}) async {
|
||||
final txn = await electrumXCachedClient.getTransaction(
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
|
@ -136,7 +131,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
if (output["n"] == vout) {
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
|
||||
// check for nameOp
|
||||
if (output["scriptPubKey"]?["nameOp"] != null) {
|
||||
|
@ -145,19 +140,15 @@ 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),
|
||||
});
|
||||
final nameOp = OpNameData(
|
||||
rawNameOP,
|
||||
jsonUTXO["height"] as int,
|
||||
);
|
||||
Logging.instance.i(
|
||||
"nameOp:\n$nameOp",
|
||||
);
|
||||
final nameOp = OpNameData(rawNameOP, jsonUTXO["height"] as int);
|
||||
Logging.instance.i("nameOp:\n$nameOp");
|
||||
|
||||
switch (nameOp.op) {
|
||||
case OpName.nameNew:
|
||||
|
@ -193,7 +184,8 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
name: label ?? "",
|
||||
isBlocked: shouldBlock,
|
||||
blockedReason: blockReason,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ??
|
||||
isCoinbase:
|
||||
txn["is_coinbase"] as bool? ??
|
||||
txn["is-coinbase"] as bool? ??
|
||||
txn["iscoinbase"] as bool? ??
|
||||
isCoinbase,
|
||||
|
@ -231,30 +223,34 @@ 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};
|
||||
|
||||
// Fetch history from ElectrumX.
|
||||
final List<Map<String, dynamic>> allTxHashes =
|
||||
await fetchHistory(allAddressesSet);
|
||||
final List<Map<String, dynamic>> allTxHashes = await fetchHistory(
|
||||
allAddressesSet,
|
||||
);
|
||||
|
||||
// Only parse new txs (not in db yet).
|
||||
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 ||
|
||||
|
@ -267,8 +263,9 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
);
|
||||
|
||||
// Only tx to list once.
|
||||
if (allTransactions
|
||||
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
||||
if (allTransactions.indexWhere(
|
||||
(e) => e["txid"] == tx["txid"] as String,
|
||||
) ==
|
||||
-1) {
|
||||
tx["height"] = txHash["height"];
|
||||
allTransactions.add(tx);
|
||||
|
@ -418,7 +415,8 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
txid: txData["txid"] as String,
|
||||
height: txData["height"] as int?,
|
||||
version: txData["version"] as int,
|
||||
timestamp: txData["blocktime"] as int? ??
|
||||
timestamp:
|
||||
txData["blocktime"] as int? ??
|
||||
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||
inputs: List.unmodifiable(inputs),
|
||||
outputs: List.unmodifiable(outputs),
|
||||
|
@ -456,10 +454,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
final data = decodeNameSaltData(encoded);
|
||||
|
||||
if (data.name == name) {
|
||||
return (
|
||||
data: null,
|
||||
nameState: NameState.unavailable,
|
||||
);
|
||||
return (data: null, nameState: NameState.unavailable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -485,9 +480,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
opNameData = OpNameData.fromTx(txMap, txHeight);
|
||||
final isExpired = opNameData.expired(await chainHeight);
|
||||
|
||||
Logging.instance.i(
|
||||
"Name $opNameData \nis expired = $isExpired",
|
||||
);
|
||||
Logging.instance.i("Name $opNameData \nis expired = $isExpired");
|
||||
available = isExpired;
|
||||
} catch (_) {
|
||||
available = false; // probably
|
||||
|
@ -508,23 +501,22 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
/// Must be called in refresh() AFTER the wallet's UTXOs have been updated!
|
||||
Future<void> checkAutoRegisterNameNewOutputs() async {
|
||||
Logging.instance.t(
|
||||
"$walletId checkAutoRegisterNameNewOutputs()",
|
||||
);
|
||||
Logging.instance.t("$walletId checkAutoRegisterNameNewOutputs()");
|
||||
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})"
|
||||
|
@ -539,9 +531,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
for (final utxo in utxos) {
|
||||
final nameOp = getOpNameDataFrom(utxo);
|
||||
if (nameOp != null) {
|
||||
Logging.instance.t(
|
||||
"Found OpName: $nameOp\n\nIN UTXO: $utxo",
|
||||
);
|
||||
Logging.instance.t("Found OpName: $nameOp\n\nIN UTXO: $utxo");
|
||||
|
||||
if (nameOp.op == OpName.nameNew) {
|
||||
// at this point we should have an unspent UTXO that is at least
|
||||
|
@ -646,14 +636,10 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
switch (txData.opNameState!.type) {
|
||||
case OpName.nameNew:
|
||||
assert(
|
||||
nameAmount.raw == BigInt.from(kNameNewAmountSats),
|
||||
);
|
||||
assert(nameAmount.raw == BigInt.from(kNameNewAmountSats));
|
||||
break;
|
||||
case OpName.nameFirstUpdate || OpName.nameUpdate:
|
||||
assert(
|
||||
nameAmount.raw == BigInt.from(kNameAmountSats),
|
||||
);
|
||||
assert(nameAmount.raw == BigInt.from(kNameAmountSats));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -671,9 +657,10 @@ 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 < utxoSigningData.length; i++) {
|
||||
|
@ -683,10 +670,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
txid.toUint8ListFromHex.reversed.toList(),
|
||||
);
|
||||
|
||||
final prevOutpoint = coinlib.OutPoint(
|
||||
hash,
|
||||
utxoSigningData[i].utxo.vout,
|
||||
);
|
||||
final prevOutpoint = coinlib.OutPoint(hash, utxoSigningData[i].utxo.vout);
|
||||
|
||||
final prevOutput = coinlib.Output.fromAddress(
|
||||
BigInt.from(utxoSigningData[i].utxo.value),
|
||||
|
@ -746,9 +730,10 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
txid: utxoSigningData[i].utxo.txid,
|
||||
vout: utxoSigningData[i].utxo.vout,
|
||||
),
|
||||
addresses: utxoSigningData[i].utxo.address == null
|
||||
? []
|
||||
: [utxoSigningData[i].utxo.address!],
|
||||
addresses:
|
||||
utxoSigningData[i].utxo.address == null
|
||||
? []
|
||||
: [utxoSigningData[i].utxo.address!],
|
||||
valueStringSats: utxoSigningData[i].utxo.value.toString(),
|
||||
witness: null,
|
||||
innerRedeemScriptAsm: null,
|
||||
|
@ -803,10 +788,9 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: "000000",
|
||||
valueStringSats: txData.recipients![i].amount.raw.toString(),
|
||||
addresses: [
|
||||
txData.recipients![i].address.toString(),
|
||||
],
|
||||
walletOwns: (await mainDB.isar.addresses
|
||||
addresses: [txData.recipients![i].address.toString()],
|
||||
walletOwns:
|
||||
(await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
|
@ -822,22 +806,33 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
// Sign the transaction accordingly
|
||||
for (int i = 0; i < utxoSigningData.length; i++) {
|
||||
final value = BigInt.from(utxoSigningData[i].utxo.value);
|
||||
coinlib.ECPrivateKey key = utxoSigningData[i].keyPair!.privateKey;
|
||||
final key = utxoSigningData[i].keyPair!.privateKey;
|
||||
|
||||
if (clTx.inputs[i] is coinlib.TaprootKeyInput) {
|
||||
final taproot = coinlib.Taproot(
|
||||
internalKey: utxoSigningData[i].keyPair!.publicKey,
|
||||
);
|
||||
|
||||
key = taproot.tweakPrivateKey(key);
|
||||
clTx = clTx.signTaproot(
|
||||
inputN: i,
|
||||
key: taproot.tweakPrivateKey(key),
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else if (clTx.inputs[i] is coinlib.LegacyWitnessInput) {
|
||||
clTx = clTx.signLegacyWitness(inputN: i, key: key, value: value);
|
||||
} else if (clTx.inputs[i] is coinlib.LegacyInput) {
|
||||
clTx = clTx.signLegacy(inputN: i, key: key);
|
||||
} else if (clTx.inputs[i] is coinlib.TaprootSingleScriptSigInput) {
|
||||
clTx = clTx.signTaprootSingleScriptSig(
|
||||
inputN: i,
|
||||
key: key,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unable to sign input of type ${clTx.inputs[i].runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
clTx = clTx.sign(
|
||||
inputN: i,
|
||||
value: value,
|
||||
key: key,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
|
@ -879,17 +874,13 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
);
|
||||
}
|
||||
|
||||
Future<TxData> prepareNameSend({
|
||||
required TxData txData,
|
||||
}) async {
|
||||
Future<TxData> prepareNameSend({required TxData txData}) async {
|
||||
try {
|
||||
if (txData.amount == null) {
|
||||
throw Exception("No recipients in attempted transaction!");
|
||||
}
|
||||
|
||||
Logging.instance.t(
|
||||
"prepareNameSend called with TxData:\n\n$txData",
|
||||
);
|
||||
Logging.instance.t("prepareNameSend called with TxData:\n\n$txData");
|
||||
|
||||
final feeRateType = txData.feeRateType;
|
||||
final customSatsPerVByte = txData.satsPerVByte;
|
||||
|
@ -944,20 +935,17 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
final result = await coinSelectionName(
|
||||
txData: txData.copyWith(
|
||||
feeRateAmount: rate,
|
||||
),
|
||||
txData: txData.copyWith(feeRateAmount: rate),
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: coinControl,
|
||||
);
|
||||
|
||||
Logging.instance.d(
|
||||
"prepare send: $result",
|
||||
);
|
||||
Logging.instance.d("prepare send: $result");
|
||||
if (result.fee!.raw.toInt() < result.vSize!) {
|
||||
throw Exception(
|
||||
"Error in fee calculation: Transaction fee (${result.fee!.raw.toInt()}) cannot "
|
||||
"be less than vSize (${result.vSize})");
|
||||
"Error in fee calculation: Transaction fee (${result.fee!.raw.toInt()}) cannot "
|
||||
"be less than vSize (${result.vSize})",
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1028,19 +1016,20 @@ 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) {
|
||||
|
@ -1050,8 +1039,9 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
} else {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort(
|
||||
(a, b) => (b.blockTime ?? currentChainHeight)
|
||||
.compareTo((a.blockTime ?? currentChainHeight)),
|
||||
(a, b) => (b.blockTime ?? currentChainHeight).compareTo(
|
||||
(a.blockTime ?? currentChainHeight),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1061,8 +1051,10 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
spendableOutputs.insert(0, txData.opNameState!.output!);
|
||||
}
|
||||
|
||||
final spendableSatoshiValue =
|
||||
spendableOutputs.fold(BigInt.zero, (p, e) => p + BigInt.from(e.value));
|
||||
final spendableSatoshiValue = spendableOutputs.fold(
|
||||
BigInt.zero,
|
||||
(p, e) => p + BigInt.from(e.value),
|
||||
);
|
||||
|
||||
if (spendableSatoshiValue < satoshiAmountToSend) {
|
||||
throw Exception("Insufficient balance");
|
||||
|
@ -1082,21 +1074,24 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
final List<UTXO> utxoObjectsToUse = [];
|
||||
|
||||
if (!coinControl) {
|
||||
for (int i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++
|
||||
) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += BigInt.from(spendableOutputs[i].value);
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++
|
||||
) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed +=
|
||||
BigInt.from(spendableOutputs[inputsBeingConsumed].value);
|
||||
satoshisBeingUsed += BigInt.from(
|
||||
spendableOutputs[inputsBeingConsumed].value,
|
||||
);
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
|
@ -1120,17 +1115,17 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
final int vSizeForOneOutput;
|
||||
try {
|
||||
vSizeForOneOutput = (await _createNameTx(
|
||||
utxoSigningData: utxoSigningData,
|
||||
isForFeeCalcPurposesOnly: true,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress],
|
||||
[satoshisBeingUsed],
|
||||
),
|
||||
),
|
||||
))
|
||||
.vSize!;
|
||||
vSizeForOneOutput =
|
||||
(await _createNameTx(
|
||||
utxoSigningData: utxoSigningData,
|
||||
isForFeeCalcPurposesOnly: true,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress],
|
||||
[satoshisBeingUsed],
|
||||
),
|
||||
),
|
||||
)).vSize!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.e("vSizeForOneOutput: $e", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
|
@ -1141,23 +1136,20 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
BigInt maxBI(BigInt a, BigInt b) => a > b ? a : b;
|
||||
|
||||
try {
|
||||
vSizeForTwoOutPuts = (await _createNameTx(
|
||||
utxoSigningData: utxoSigningData,
|
||||
isForFeeCalcPurposesOnly: true,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress, (await getCurrentChangeAddress())!.value],
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
maxBI(
|
||||
BigInt.zero,
|
||||
satoshisBeingUsed - satoshiAmountToSend,
|
||||
vSizeForTwoOutPuts =
|
||||
(await _createNameTx(
|
||||
utxoSigningData: utxoSigningData,
|
||||
isForFeeCalcPurposesOnly: true,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress, (await getCurrentChangeAddress())!.value],
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
maxBI(BigInt.zero, satoshisBeingUsed - satoshiAmountToSend),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
.vSize!;
|
||||
),
|
||||
)).vSize!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.e("vSizeForTwoOutPuts: $e", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
|
@ -1168,18 +1160,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(
|
||||
|
@ -1250,12 +1242,14 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
recipientsArray.add(newChangeAddress);
|
||||
recipientsAmtArray.add(changeOutputSize);
|
||||
|
||||
Logging.instance.d('2 outputs in tx'
|
||||
'\nInput size: $satoshisBeingUsed'
|
||||
'\nRecipient output size: $satoshiAmountToSend'
|
||||
'\nChange Output Size: $changeOutputSize'
|
||||
'\nDifference (fee being paid): $feeBeingPaid sats'
|
||||
'\nEstimated fee: $feeForTwoOutputs');
|
||||
Logging.instance.d(
|
||||
'2 outputs in tx'
|
||||
'\nInput size: $satoshisBeingUsed'
|
||||
'\nRecipient output size: $satoshiAmountToSend'
|
||||
'\nChange Output Size: $changeOutputSize'
|
||||
'\nDifference (fee being paid): $feeBeingPaid sats'
|
||||
'\nEstimated fee: $feeForTwoOutputs',
|
||||
);
|
||||
|
||||
TxData txnData = await _createNameTx(
|
||||
utxoSigningData: utxoSigningData,
|
||||
|
@ -1305,9 +1299,7 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
} else {
|
||||
// Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize
|
||||
// is smaller than or equal to cryptoCurrency.dustLimit. Revert to single output transaction.
|
||||
Logging.instance.d(
|
||||
'Reverting to 1 output in tx',
|
||||
);
|
||||
Logging.instance.d('Reverting to 1 output in tx');
|
||||
|
||||
return await _singleOutputTxn();
|
||||
}
|
||||
|
@ -1365,7 +1357,4 @@ class NamecoinWallet<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
}
|
||||
|
||||
enum NameState {
|
||||
available,
|
||||
unavailable;
|
||||
}
|
||||
enum NameState { available, unavailable }
|
||||
|
|
|
@ -36,7 +36,8 @@ import 'rbf_interface.dart';
|
|||
import 'view_only_option_interface.dart';
|
||||
|
||||
mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
||||
on Bip39HDWallet<T> implements ViewOnlyOptionInterface<T> {
|
||||
on Bip39HDWallet<T>
|
||||
implements ViewOnlyOptionInterface<T> {
|
||||
late ElectrumXClient electrumXClient;
|
||||
late CachedElectrumXClient electrumXCachedClient;
|
||||
|
||||
|
@ -54,9 +55,10 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
try {
|
||||
_serverVersion ??= _parseServerVersion(
|
||||
(await electrumXClient
|
||||
.getServerFeatures()
|
||||
.timeout(const Duration(seconds: 2)))["server_version"] as String,
|
||||
(await electrumXClient.getServerFeatures().timeout(
|
||||
const Duration(seconds: 2),
|
||||
))["server_version"]
|
||||
as String,
|
||||
);
|
||||
} catch (_) {
|
||||
// ignore failure as it doesn't matter
|
||||
|
@ -74,32 +76,28 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
Future<List<({String address, Amount amount, bool isChange})>>
|
||||
helperRecipientsConvert(
|
||||
List<String> addrs,
|
||||
List<BigInt> satValues,
|
||||
) async {
|
||||
helperRecipientsConvert(List<String> addrs, List<BigInt> satValues) async {
|
||||
final List<({String address, Amount amount, bool isChange})> results = [];
|
||||
|
||||
for (int i = 0; i < addrs.length; i++) {
|
||||
results.add(
|
||||
(
|
||||
address: addrs[i],
|
||||
amount: Amount(
|
||||
rawValue: satValues[i],
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
isChange: (await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.change)
|
||||
.and()
|
||||
.valueEqualTo(addrs[i])
|
||||
.valueProperty()
|
||||
.findFirst()) !=
|
||||
null
|
||||
results.add((
|
||||
address: addrs[i],
|
||||
amount: Amount(
|
||||
rawValue: satValues[i],
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
);
|
||||
isChange:
|
||||
(await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.change)
|
||||
.and()
|
||||
.valueEqualTo(addrs[i])
|
||||
.valueProperty()
|
||||
.findFirst()) !=
|
||||
null,
|
||||
));
|
||||
}
|
||||
|
||||
return results;
|
||||
|
@ -133,21 +131,24 @@ mixin ElectrumXInterface<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 spendableSatoshiValue =
|
||||
spendableOutputs.fold(BigInt.zero, (p, e) => p + BigInt.from(e.value));
|
||||
final spendableOutputs =
|
||||
availableOutputs
|
||||
.where(
|
||||
(e) =>
|
||||
!e.isBlocked &&
|
||||
(e.used != true) &&
|
||||
(canCPFP ||
|
||||
e.isConfirmed(
|
||||
currentChainHeight,
|
||||
cryptoCurrency.minConfirms,
|
||||
cryptoCurrency.minCoinbaseConfirms,
|
||||
)),
|
||||
)
|
||||
.toList();
|
||||
final spendableSatoshiValue = spendableOutputs.fold(
|
||||
BigInt.zero,
|
||||
(p, e) => p + BigInt.from(e.value),
|
||||
);
|
||||
|
||||
if (spendableSatoshiValue < satoshiAmountToSend) {
|
||||
throw Exception("Insufficient balance");
|
||||
|
@ -165,45 +166,41 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
} else {
|
||||
// sort spendable by age (oldest first)
|
||||
spendableOutputs.sort(
|
||||
(a, b) => (b.blockTime ?? currentChainHeight)
|
||||
.compareTo((a.blockTime ?? currentChainHeight)),
|
||||
(a, b) => (b.blockTime ?? currentChainHeight).compareTo(
|
||||
(a.blockTime ?? currentChainHeight),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Logging.instance.d(
|
||||
"spendableOutputs.length: ${spendableOutputs.length}",
|
||||
);
|
||||
Logging.instance.d(
|
||||
"availableOutputs.length: ${availableOutputs.length}",
|
||||
);
|
||||
Logging.instance.d("spendableOutputs.length: ${spendableOutputs.length}");
|
||||
Logging.instance.d("availableOutputs.length: ${availableOutputs.length}");
|
||||
Logging.instance.d("spendableOutputs: $spendableOutputs");
|
||||
Logging.instance.d(
|
||||
"spendableSatoshiValue: $spendableSatoshiValue",
|
||||
);
|
||||
Logging.instance.d(
|
||||
"satoshiAmountToSend: $satoshiAmountToSend",
|
||||
);
|
||||
Logging.instance.d("spendableSatoshiValue: $spendableSatoshiValue");
|
||||
Logging.instance.d("satoshiAmountToSend: $satoshiAmountToSend");
|
||||
|
||||
BigInt satoshisBeingUsed = BigInt.zero;
|
||||
int inputsBeingConsumed = 0;
|
||||
final List<UTXO> utxoObjectsToUse = [];
|
||||
|
||||
if (!coinControl) {
|
||||
for (var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend &&
|
||||
i < spendableOutputs.length;
|
||||
i++) {
|
||||
for (
|
||||
var i = 0;
|
||||
satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length;
|
||||
i++
|
||||
) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += BigInt.from(spendableOutputs[i].value);
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
for (int i = 0;
|
||||
i < additionalOutputs &&
|
||||
inputsBeingConsumed < spendableOutputs.length;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length;
|
||||
i++
|
||||
) {
|
||||
utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]);
|
||||
satoshisBeingUsed +=
|
||||
BigInt.from(spendableOutputs[inputsBeingConsumed].value);
|
||||
satoshisBeingUsed += BigInt.from(
|
||||
spendableOutputs[inputsBeingConsumed].value,
|
||||
);
|
||||
inputsBeingConsumed += 1;
|
||||
}
|
||||
} else {
|
||||
|
@ -213,9 +210,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
Logging.instance.d("satoshisBeingUsed: $satoshisBeingUsed");
|
||||
Logging.instance.d(
|
||||
"inputsBeingConsumed: $inputsBeingConsumed",
|
||||
);
|
||||
Logging.instance.d("inputsBeingConsumed: $inputsBeingConsumed");
|
||||
Logging.instance.d('utxoObjectsToUse: $utxoObjectsToUse');
|
||||
|
||||
// numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray
|
||||
|
@ -245,16 +240,16 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
final int vSizeForOneOutput;
|
||||
try {
|
||||
vSizeForOneOutput = (await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress],
|
||||
[satoshisBeingUsed - BigInt.one],
|
||||
),
|
||||
),
|
||||
))
|
||||
.vSize!;
|
||||
vSizeForOneOutput =
|
||||
(await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress],
|
||||
[satoshisBeingUsed - BigInt.one],
|
||||
),
|
||||
),
|
||||
)).vSize!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.e("vSizeForOneOutput: $e", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
|
@ -265,22 +260,22 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
BigInt maxBI(BigInt a, BigInt b) => a > b ? a : b;
|
||||
|
||||
try {
|
||||
vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress, (await getCurrentChangeAddress())!.value],
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
maxBI(
|
||||
BigInt.zero,
|
||||
satoshisBeingUsed - (satoshiAmountToSend + BigInt.one),
|
||||
vSizeForTwoOutPuts =
|
||||
(await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress, (await getCurrentChangeAddress())!.value],
|
||||
[
|
||||
satoshiAmountToSend,
|
||||
maxBI(
|
||||
BigInt.zero,
|
||||
satoshisBeingUsed - (satoshiAmountToSend + BigInt.one),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
.vSize!;
|
||||
),
|
||||
)).vSize!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.e("vSizeForTwoOutPuts: $e", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
|
@ -291,42 +286,30 @@ mixin ElectrumXInterface<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(
|
||||
"feeForTwoOutputs: $feeForTwoOutputs",
|
||||
);
|
||||
Logging.instance.d(
|
||||
"feeForOneOutput: $feeForOneOutput",
|
||||
);
|
||||
Logging.instance.d("feeForTwoOutputs: $feeForTwoOutputs");
|
||||
Logging.instance.d("feeForOneOutput: $feeForOneOutput");
|
||||
|
||||
final difference = satoshisBeingUsed - satoshiAmountToSend;
|
||||
|
||||
Future<TxData> singleOutputTxn() async {
|
||||
Logging.instance.d(
|
||||
'Input size: $satoshisBeingUsed',
|
||||
);
|
||||
Logging.instance.d(
|
||||
'Recipient output size: $satoshiAmountToSend',
|
||||
);
|
||||
Logging.instance.d(
|
||||
'Fee being paid: $difference sats',
|
||||
);
|
||||
Logging.instance.d(
|
||||
'Estimated fee: $feeForOneOutput',
|
||||
);
|
||||
Logging.instance.d('Input size: $satoshisBeingUsed');
|
||||
Logging.instance.d('Recipient output size: $satoshiAmountToSend');
|
||||
Logging.instance.d('Fee being paid: $difference sats');
|
||||
Logging.instance.d('Estimated fee: $feeForOneOutput');
|
||||
final txnData = await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
txData: txData.copyWith(
|
||||
|
@ -406,9 +389,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
recipientsAmtArray.removeLast();
|
||||
recipientsAmtArray.add(changeOutputSize);
|
||||
|
||||
Logging.instance.d(
|
||||
'Adjusted Input size: $satoshisBeingUsed',
|
||||
);
|
||||
Logging.instance.d('Adjusted Input size: $satoshisBeingUsed');
|
||||
Logging.instance.d(
|
||||
'Adjusted Recipient output size: $satoshiAmountToSend',
|
||||
);
|
||||
|
@ -418,9 +399,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
Logging.instance.d(
|
||||
'Adjusted Difference (fee being paid): $feeBeingPaid sats',
|
||||
);
|
||||
Logging.instance.d(
|
||||
'Adjusted Estimated fee: $feeForTwoOutputs',
|
||||
);
|
||||
Logging.instance.d('Adjusted Estimated fee: $feeForTwoOutputs');
|
||||
|
||||
txnData = await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
|
@ -443,9 +422,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
} else {
|
||||
// Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize
|
||||
// is smaller than or equal to cryptoCurrency.dustLimit. Revert to single output transaction.
|
||||
Logging.instance.d(
|
||||
'Reverting to 1 output in tx',
|
||||
);
|
||||
Logging.instance.d('Reverting to 1 output in tx');
|
||||
|
||||
return await singleOutputTxn();
|
||||
}
|
||||
|
@ -466,36 +443,28 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}) async {
|
||||
Logging.instance.d("Attempting to send all $cryptoCurrency");
|
||||
if (txData.recipients!.length != 1) {
|
||||
throw Exception(
|
||||
"Send all to more than one recipient not yet supported",
|
||||
);
|
||||
throw Exception("Send all to more than one recipient not yet supported");
|
||||
}
|
||||
|
||||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress],
|
||||
[satoshisBeingUsed - BigInt.one],
|
||||
),
|
||||
),
|
||||
))
|
||||
.vSize!;
|
||||
final int vSizeForOneOutput =
|
||||
(await buildTransaction(
|
||||
utxoSigningData: utxoSigningData,
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress],
|
||||
[satoshisBeingUsed - BigInt.one],
|
||||
),
|
||||
),
|
||||
)).vSize!;
|
||||
BigInt feeForOneOutput = BigInt.from(
|
||||
satsPerVByte != null
|
||||
? (satsPerVByte * vSizeForOneOutput)
|
||||
: estimateTxFee(
|
||||
vSize: vSizeForOneOutput,
|
||||
feeRatePerKB: feeRatePerKB,
|
||||
),
|
||||
: estimateTxFee(vSize: vSizeForOneOutput, feeRatePerKB: feeRatePerKB),
|
||||
);
|
||||
|
||||
if (satsPerVByte == null) {
|
||||
final roughEstimate = roughFeeEstimate(
|
||||
utxoSigningData.length,
|
||||
1,
|
||||
feeRatePerKB,
|
||||
).raw;
|
||||
final roughEstimate =
|
||||
roughFeeEstimate(utxoSigningData.length, 1, feeRatePerKB).raw;
|
||||
if (feeForOneOutput < roughEstimate) {
|
||||
feeForOneOutput = roughEstimate;
|
||||
}
|
||||
|
@ -511,10 +480,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
final data = await buildTransaction(
|
||||
txData: txData.copyWith(
|
||||
recipients: await helperRecipientsConvert(
|
||||
[recipientAddress],
|
||||
[amount],
|
||||
),
|
||||
recipients: await helperRecipientsConvert([recipientAddress], [amount]),
|
||||
),
|
||||
utxoSigningData: utxoSigningData,
|
||||
);
|
||||
|
@ -528,23 +494,19 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
);
|
||||
}
|
||||
|
||||
Future<List<SigningData>> fetchBuildTxData(
|
||||
List<UTXO> utxosToUse,
|
||||
) async {
|
||||
Future<List<SigningData>> fetchBuildTxData(List<UTXO> utxosToUse) async {
|
||||
// return data
|
||||
final List<SigningData> signingData = [];
|
||||
|
||||
try {
|
||||
// Populating the addresses to check
|
||||
for (var i = 0; i < utxosToUse.length; i++) {
|
||||
final derivePathType =
|
||||
cryptoCurrency.addressType(address: utxosToUse[i].address!);
|
||||
final derivePathType = cryptoCurrency.addressType(
|
||||
address: utxosToUse[i].address!,
|
||||
);
|
||||
|
||||
signingData.add(
|
||||
SigningData(
|
||||
derivePathType: derivePathType,
|
||||
utxo: utxosToUse[i],
|
||||
),
|
||||
SigningData(derivePathType: derivePathType, utxo: utxosToUse[i]),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -564,9 +526,9 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
final privateKey = await (this as PaynymInterface)
|
||||
.getPrivateKeyForPaynymReceivingAddress(
|
||||
paymentCodeString: code!,
|
||||
index: address.derivationIndex,
|
||||
);
|
||||
paymentCodeString: code!,
|
||||
index: address.derivationIndex,
|
||||
);
|
||||
|
||||
keys = coinlib.HDPrivateKey.fromKeyAndChainCode(
|
||||
coinlib.ECPrivateKey.fromHex(privateKey.toHex),
|
||||
|
@ -594,11 +556,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
return signingData;
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"fetchBuildTxData() threw",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
Logging.instance.e("fetchBuildTxData() threw", error: e, stackTrace: s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -623,9 +581,10 @@ mixin ElectrumXInterface<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 (var i = 0; i < utxoSigningData.length; i++) {
|
||||
|
@ -635,10 +594,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
txid.toUint8ListFromHex.reversed.toList(),
|
||||
);
|
||||
|
||||
final prevOutpoint = coinlib.OutPoint(
|
||||
hash,
|
||||
utxoSigningData[i].utxo.vout,
|
||||
);
|
||||
final prevOutpoint = coinlib.OutPoint(hash, utxoSigningData[i].utxo.vout);
|
||||
|
||||
final prevOutput = coinlib.Output.fromAddress(
|
||||
BigInt.from(utxoSigningData[i].utxo.value),
|
||||
|
@ -699,9 +655,10 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
txid: utxoSigningData[i].utxo.txid,
|
||||
vout: utxoSigningData[i].utxo.vout,
|
||||
),
|
||||
addresses: utxoSigningData[i].utxo.address == null
|
||||
? []
|
||||
: [utxoSigningData[i].utxo.address!],
|
||||
addresses:
|
||||
utxoSigningData[i].utxo.address == null
|
||||
? []
|
||||
: [utxoSigningData[i].utxo.address!],
|
||||
valueStringSats: utxoSigningData[i].utxo.value.toString(),
|
||||
witness: null,
|
||||
innerRedeemScriptAsm: null,
|
||||
|
@ -741,10 +698,9 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: "000000",
|
||||
valueStringSats: txData.recipients![i].amount.raw.toString(),
|
||||
addresses: [
|
||||
txData.recipients![i].address.toString(),
|
||||
],
|
||||
walletOwns: (await mainDB.isar.addresses
|
||||
addresses: [txData.recipients![i].address.toString()],
|
||||
walletOwns:
|
||||
(await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
|
@ -760,22 +716,33 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
// Sign the transaction accordingly
|
||||
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||
final value = BigInt.from(utxoSigningData[i].utxo.value);
|
||||
coinlib.ECPrivateKey key = utxoSigningData[i].keyPair!.privateKey;
|
||||
final key = utxoSigningData[i].keyPair!.privateKey;
|
||||
|
||||
if (clTx.inputs[i] is coinlib.TaprootKeyInput) {
|
||||
final taproot = coinlib.Taproot(
|
||||
internalKey: utxoSigningData[i].keyPair!.publicKey,
|
||||
);
|
||||
|
||||
key = taproot.tweakPrivateKey(key);
|
||||
clTx = clTx.signTaproot(
|
||||
inputN: i,
|
||||
key: taproot.tweakPrivateKey(key),
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else if (clTx.inputs[i] is coinlib.LegacyWitnessInput) {
|
||||
clTx = clTx.signLegacyWitness(inputN: i, key: key, value: value);
|
||||
} else if (clTx.inputs[i] is coinlib.LegacyInput) {
|
||||
clTx = clTx.signLegacy(inputN: i, key: key);
|
||||
} else if (clTx.inputs[i] is coinlib.TaprootSingleScriptSigInput) {
|
||||
clTx = clTx.signTaprootSingleScriptSig(
|
||||
inputN: i,
|
||||
key: key,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unable to sign input of type ${clTx.inputs[i].runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
clTx = clTx.sign(
|
||||
inputN: i,
|
||||
value: value,
|
||||
key: key,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
|
@ -839,8 +806,9 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
Future<int> fetchTxCount({required String addressScriptHash}) async {
|
||||
final transactions =
|
||||
await electrumXClient.getHistory(scripthash: addressScriptHash);
|
||||
final transactions = await electrumXClient.getHistory(
|
||||
scripthash: addressScriptHash,
|
||||
);
|
||||
return transactions.length;
|
||||
}
|
||||
|
||||
|
@ -885,20 +853,21 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
Future<void> updateElectrumX() async {
|
||||
final failovers = nodeService
|
||||
.failoverNodesFor(currency: cryptoCurrency)
|
||||
.map(
|
||||
(e) => ElectrumXNode(
|
||||
address: e.host,
|
||||
port: e.port,
|
||||
name: e.name,
|
||||
id: e.id,
|
||||
useSSL: e.useSSL,
|
||||
torEnabled: e.torEnabled,
|
||||
clearnetEnabled: e.clearnetEnabled,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
final failovers =
|
||||
nodeService
|
||||
.failoverNodesFor(currency: cryptoCurrency)
|
||||
.map(
|
||||
(e) => ElectrumXNode(
|
||||
address: e.host,
|
||||
port: e.port,
|
||||
name: e.name,
|
||||
id: e.id,
|
||||
useSSL: e.useSSL,
|
||||
torEnabled: e.torEnabled,
|
||||
clearnetEnabled: e.clearnetEnabled,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
final newNode = await _getCurrentElectrumXNode();
|
||||
try {
|
||||
|
@ -937,9 +906,11 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
int gapCounter = 0;
|
||||
int highestIndexWithHistory = 0;
|
||||
|
||||
for (int index = 0;
|
||||
gapCounter < cryptoCurrency.maxUnusedAddressGap;
|
||||
index += txCountBatchSize) {
|
||||
for (
|
||||
int index = 0;
|
||||
gapCounter < cryptoCurrency.maxUnusedAddressGap;
|
||||
index += txCountBatchSize
|
||||
) {
|
||||
Logging.instance.d(
|
||||
"index: $index, \t GapCounter $chain ${type.name}: $gapCounter",
|
||||
);
|
||||
|
@ -985,9 +956,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
addressArray.add(address);
|
||||
|
||||
txCountCallArgs.add(
|
||||
addressString,
|
||||
);
|
||||
txCountCallArgs.add(addressString);
|
||||
}
|
||||
|
||||
// get address tx counts
|
||||
|
@ -1115,8 +1084,9 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
for (int i = 0; i < batches.length; i++) {
|
||||
final response =
|
||||
await electrumXClient.getBatchHistory(args: batches[i]!);
|
||||
final response = await electrumXClient.getBatchHistory(
|
||||
args: batches[i]!,
|
||||
);
|
||||
for (int j = 0; j < response.length; j++) {
|
||||
final entry = response[j];
|
||||
for (int k = 0; k < entry.length; k++) {
|
||||
|
@ -1149,15 +1119,16 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
return allTxHashes;
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.e("$runtimeType._fetchHistory: ", error: e, stackTrace: s);
|
||||
Logging.instance.e(
|
||||
"$runtimeType._fetchHistory: ",
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<UTXO> parseUTXO({
|
||||
required Map<String, dynamic> jsonUTXO,
|
||||
}) async {
|
||||
Future<UTXO> parseUTXO({required Map<String, dynamic> jsonUTXO}) async {
|
||||
final txn = await electrumXCachedClient.getTransaction(
|
||||
txHash: jsonUTXO["tx_hash"] as String,
|
||||
verbose: true,
|
||||
|
@ -1179,7 +1150,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
scriptPubKey = output["scriptPubKey"]?["hex"] as String?;
|
||||
utxoOwnerAddress =
|
||||
output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1198,7 +1169,8 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
name: checkBlockResult.utxoLabel ?? "",
|
||||
isBlocked: checkBlockResult.blocked,
|
||||
blockedReason: checkBlockResult.blockedReason,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ??
|
||||
isCoinbase:
|
||||
txn["is_coinbase"] as bool? ??
|
||||
txn["is-coinbase"] as bool? ??
|
||||
txn["iscoinbase"] as bool? ??
|
||||
isCoinbase,
|
||||
|
@ -1216,10 +1188,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
@override
|
||||
Future<void> updateChainHeight() async {
|
||||
final height = await fetchChainHeight();
|
||||
await info.updateCachedChainHeight(
|
||||
newHeight: height,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1252,23 +1221,24 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
numberOfBlocksFast: f,
|
||||
numberOfBlocksAverage: m,
|
||||
numberOfBlocksSlow: s,
|
||||
fast: Amount.fromDecimal(
|
||||
fast,
|
||||
fractionDigits: info.coin.fractionDigits,
|
||||
).raw.toInt(),
|
||||
medium: Amount.fromDecimal(
|
||||
medium,
|
||||
fractionDigits: info.coin.fractionDigits,
|
||||
).raw.toInt(),
|
||||
slow: Amount.fromDecimal(
|
||||
slow,
|
||||
fractionDigits: info.coin.fractionDigits,
|
||||
).raw.toInt(),
|
||||
fast:
|
||||
Amount.fromDecimal(
|
||||
fast,
|
||||
fractionDigits: info.coin.fractionDigits,
|
||||
).raw.toInt(),
|
||||
medium:
|
||||
Amount.fromDecimal(
|
||||
medium,
|
||||
fractionDigits: info.coin.fractionDigits,
|
||||
).raw.toInt(),
|
||||
slow:
|
||||
Amount.fromDecimal(
|
||||
slow,
|
||||
fractionDigits: info.coin.fractionDigits,
|
||||
).raw.toInt(),
|
||||
);
|
||||
|
||||
Logging.instance.d(
|
||||
"fetched fees: $feeObject",
|
||||
);
|
||||
Logging.instance.d("fetched fees: $feeObject");
|
||||
_cachedFees = feeObject;
|
||||
return _cachedFees!;
|
||||
} catch (e, s) {
|
||||
|
@ -1383,9 +1353,10 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.e("Exception rethrown from _checkReceivingAddressForTransactions"
|
||||
"($cryptoCurrency): $e\n$s");
|
||||
Logging.instance.e(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions"
|
||||
"($cryptoCurrency): $e\n$s",
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1434,9 +1405,10 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
await checkChangeAddressForTransactions();
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.e("Exception rethrown from _checkChangeAddressForTransactions"
|
||||
"($cryptoCurrency): $e\n$s");
|
||||
Logging.instance.e(
|
||||
"Exception rethrown from _checkChangeAddressForTransactions"
|
||||
"($cryptoCurrency): $e\n$s",
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -1472,47 +1444,25 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
// receiving addresses
|
||||
Logging.instance.i(
|
||||
"checking receiving addresses...",
|
||||
);
|
||||
Logging.instance.i("checking receiving addresses...");
|
||||
|
||||
final canBatch = await serverCanBatch;
|
||||
|
||||
for (final type in cryptoCurrency.supportedDerivationPathTypes) {
|
||||
receiveFutures.add(
|
||||
canBatch
|
||||
? checkGapsBatched(
|
||||
txCountBatchSize,
|
||||
root,
|
||||
type,
|
||||
receiveChain,
|
||||
)
|
||||
: checkGapsLinearly(
|
||||
root,
|
||||
type,
|
||||
receiveChain,
|
||||
),
|
||||
? checkGapsBatched(txCountBatchSize, root, type, receiveChain)
|
||||
: checkGapsLinearly(root, type, receiveChain),
|
||||
);
|
||||
}
|
||||
|
||||
// change addresses
|
||||
Logging.instance.d(
|
||||
"checking change addresses...",
|
||||
);
|
||||
Logging.instance.d("checking change addresses...");
|
||||
for (final type in cryptoCurrency.supportedDerivationPathTypes) {
|
||||
changeFutures.add(
|
||||
canBatch
|
||||
? checkGapsBatched(
|
||||
txCountBatchSize,
|
||||
root,
|
||||
type,
|
||||
changeChain,
|
||||
)
|
||||
: checkGapsLinearly(
|
||||
root,
|
||||
type,
|
||||
changeChain,
|
||||
),
|
||||
? checkGapsBatched(txCountBatchSize, root, type, changeChain)
|
||||
: checkGapsLinearly(root, type, changeChain),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1574,13 +1524,15 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
final notificationAddress =
|
||||
await (this as PaynymInterface).getMyNotificationAddress();
|
||||
|
||||
await (this as BitcoinWallet)
|
||||
.updateTransactions(overrideAddresses: [notificationAddress]);
|
||||
await (this as BitcoinWallet).updateTransactions(
|
||||
overrideAddresses: [notificationAddress],
|
||||
);
|
||||
|
||||
// get own payment code
|
||||
// isSegwit does not matter here at all
|
||||
final myCode =
|
||||
await (this as PaynymInterface).getPaymentCode(isSegwit: false);
|
||||
final myCode = await (this as PaynymInterface).getPaymentCode(
|
||||
isSegwit: false,
|
||||
);
|
||||
|
||||
try {
|
||||
final Set<String> codesToCheck = {};
|
||||
|
@ -1648,8 +1600,9 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
for (int i = 0; i < batchArgs.length; i++) {
|
||||
final response =
|
||||
await electrumXClient.getBatchUTXOs(args: batchArgs[i]!);
|
||||
final response = await electrumXClient.getBatchUTXOs(
|
||||
args: batchArgs[i]!,
|
||||
);
|
||||
for (final entry in response) {
|
||||
if (entry.isNotEmpty) {
|
||||
fetchedUtxoList.add(entry);
|
||||
|
@ -1673,9 +1626,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
final utxo = await parseUTXO(
|
||||
jsonUTXO: fetchedUtxoList[i][j],
|
||||
);
|
||||
final utxo = await parseUTXO(jsonUTXO: fetchedUtxoList[i][j]);
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
|
@ -1695,16 +1646,12 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
@override
|
||||
Future<TxData> confirmSend({required TxData txData}) async {
|
||||
try {
|
||||
Logging.instance.d(
|
||||
"confirmSend txData: $txData",
|
||||
);
|
||||
Logging.instance.d("confirmSend txData: $txData");
|
||||
|
||||
final txHash = await electrumXClient.broadcastTransaction(
|
||||
rawTx: txData.raw!,
|
||||
);
|
||||
Logging.instance.d(
|
||||
"Sent txHash: $txHash",
|
||||
);
|
||||
Logging.instance.d("Sent txHash: $txHash");
|
||||
|
||||
txData = txData.copyWith(
|
||||
usedUTXOs:
|
||||
|
@ -1742,7 +1689,8 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
final bool coinControl = utxos != null;
|
||||
|
||||
final isSendAllCoinControlUtxos = coinControl &&
|
||||
final isSendAllCoinControlUtxos =
|
||||
coinControl &&
|
||||
txData.amount!.raw ==
|
||||
utxos
|
||||
.map((e) => e.value)
|
||||
|
@ -1811,22 +1759,19 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
final result = await coinSelection(
|
||||
txData: txData.copyWith(
|
||||
feeRateAmount: rate,
|
||||
),
|
||||
txData: txData.copyWith(feeRateAmount: rate),
|
||||
isSendAll: isSendAll,
|
||||
utxos: utxos?.toList(),
|
||||
coinControl: coinControl,
|
||||
isSendAllCoinControlUtxos: isSendAllCoinControlUtxos,
|
||||
);
|
||||
|
||||
Logging.instance.d(
|
||||
"prepare send: $result",
|
||||
);
|
||||
Logging.instance.d("prepare send: $result");
|
||||
if (result.fee!.raw.toInt() < result.vSize!) {
|
||||
throw Exception(
|
||||
"Error in fee calculation: Transaction fee (${result.fee!.raw.toInt()}) cannot "
|
||||
"be less than vSize (${result.vSize})");
|
||||
"Error in fee calculation: Transaction fee (${result.fee!.raw.toInt()}) cannot "
|
||||
"be less than vSize (${result.vSize})",
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1862,16 +1807,15 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
Future<void> _initializeServerVersionAndCheckGenesisHash() async {
|
||||
try {
|
||||
final features = await electrumXClient
|
||||
.getServerFeatures()
|
||||
.timeout(const Duration(seconds: 5));
|
||||
|
||||
Logging.instance.d(
|
||||
"features: $features",
|
||||
final features = await electrumXClient.getServerFeatures().timeout(
|
||||
const Duration(seconds: 5),
|
||||
);
|
||||
|
||||
_serverVersion =
|
||||
_parseServerVersion(features["server_version"] as String);
|
||||
Logging.instance.d("features: $features");
|
||||
|
||||
_serverVersion = _parseServerVersion(
|
||||
features["server_version"] as String,
|
||||
);
|
||||
|
||||
if (cryptoCurrency.genesisHash != features['genesis_hash']) {
|
||||
throw Exception("Genesis hash does not match!");
|
||||
|
@ -1896,7 +1840,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
/// Certain coins need to check if the utxo should be marked
|
||||
/// as blocked as well as give a reason.
|
||||
Future<({String? blockedReason, bool blocked, String? utxoLabel})>
|
||||
checkBlockUTXO(
|
||||
checkBlockUTXO(
|
||||
Map<String, dynamic> jsonUTXO,
|
||||
String? scriptPubKeyHex,
|
||||
Map<String, dynamic> jsonTX,
|
||||
|
@ -1948,9 +1892,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
} catch (_) {}
|
||||
|
||||
Logging.instance.d(
|
||||
"${info.name} _parseServerVersion($version) => $result",
|
||||
);
|
||||
Logging.instance.d("${info.name} _parseServerVersion($version) => $result");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -2003,9 +1945,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
if (root != null) {
|
||||
// receiving addresses
|
||||
Logging.instance.i(
|
||||
"checking receiving addresses...",
|
||||
);
|
||||
Logging.instance.i("checking receiving addresses...");
|
||||
|
||||
final canBatch = await serverCanBatch;
|
||||
|
||||
|
@ -2021,24 +1961,18 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
receiveFutures.add(
|
||||
canBatch
|
||||
? checkGapsBatched(
|
||||
txCountBatchSize,
|
||||
root,
|
||||
type,
|
||||
receiveChain,
|
||||
)
|
||||
: checkGapsLinearly(
|
||||
root,
|
||||
type,
|
||||
receiveChain,
|
||||
),
|
||||
txCountBatchSize,
|
||||
root,
|
||||
type,
|
||||
receiveChain,
|
||||
)
|
||||
: checkGapsLinearly(root, type, receiveChain),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// change addresses
|
||||
Logging.instance.d(
|
||||
"checking change addresses...",
|
||||
);
|
||||
Logging.instance.d("checking change addresses...");
|
||||
for (final type in cryptoCurrency.supportedDerivationPathTypes) {
|
||||
final path = cryptoCurrency.constructDerivePath(
|
||||
derivePathType: type,
|
||||
|
@ -2051,16 +1985,12 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
changeFutures.add(
|
||||
canBatch
|
||||
? checkGapsBatched(
|
||||
txCountBatchSize,
|
||||
root,
|
||||
type,
|
||||
changeChain,
|
||||
)
|
||||
: checkGapsLinearly(
|
||||
root,
|
||||
type,
|
||||
changeChain,
|
||||
),
|
||||
txCountBatchSize,
|
||||
root,
|
||||
type,
|
||||
changeChain,
|
||||
)
|
||||
: checkGapsLinearly(root, type, changeChain),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,34 +42,28 @@ String _notificationDerivationPath({required bool testnet}) =>
|
|||
String _receivingPaynymAddressDerivationPath(
|
||||
int index, {
|
||||
required bool testnet,
|
||||
}) =>
|
||||
"${_basePaynymDerivePath(testnet: testnet)}/$index/0";
|
||||
String _sendPaynymAddressDerivationPath(
|
||||
int index, {
|
||||
required bool testnet,
|
||||
}) =>
|
||||
}) => "${_basePaynymDerivePath(testnet: testnet)}/$index/0";
|
||||
String _sendPaynymAddressDerivationPath(int index, {required bool testnet}) =>
|
||||
"${_basePaynymDerivePath(testnet: testnet)}/0/$index";
|
||||
|
||||
mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
||||
on Bip39HDWallet<T>, ElectrumXInterface<T> {
|
||||
btc_dart.NetworkType get networkType => btc_dart.NetworkType(
|
||||
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
|
||||
bech32: cryptoCurrency.networkParams.bech32Hrp,
|
||||
bip32: btc_dart.Bip32Type(
|
||||
public: cryptoCurrency.networkParams.pubHDPrefix,
|
||||
private: cryptoCurrency.networkParams.privHDPrefix,
|
||||
),
|
||||
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
|
||||
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
|
||||
wif: cryptoCurrency.networkParams.wifPrefix,
|
||||
);
|
||||
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
|
||||
bech32: cryptoCurrency.networkParams.bech32Hrp,
|
||||
bip32: btc_dart.Bip32Type(
|
||||
public: cryptoCurrency.networkParams.pubHDPrefix,
|
||||
private: cryptoCurrency.networkParams.privHDPrefix,
|
||||
),
|
||||
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
|
||||
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
|
||||
wif: cryptoCurrency.networkParams.wifPrefix,
|
||||
);
|
||||
|
||||
Future<bip32.BIP32> getBip47BaseNode() async {
|
||||
final root = await _getRootNode();
|
||||
final node = root.derivePath(
|
||||
_basePaynymDerivePath(
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
_basePaynymDerivePath(testnet: info.coin.network.isTestNet),
|
||||
);
|
||||
return node;
|
||||
}
|
||||
|
@ -101,25 +95,29 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}) async {
|
||||
final keys = await lookupKey(sender.toString());
|
||||
|
||||
final address = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymReceive)
|
||||
.and()
|
||||
.group((q) {
|
||||
if (isSegwit) {
|
||||
return q
|
||||
.typeEqualTo(AddressType.p2sh)
|
||||
.or()
|
||||
.typeEqualTo(AddressType.p2wpkh);
|
||||
} else {
|
||||
return q.typeEqualTo(AddressType.p2pkh);
|
||||
}
|
||||
})
|
||||
.and()
|
||||
.anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e))
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst();
|
||||
final address =
|
||||
await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymReceive)
|
||||
.and()
|
||||
.group((q) {
|
||||
if (isSegwit) {
|
||||
return q
|
||||
.typeEqualTo(AddressType.p2sh)
|
||||
.or()
|
||||
.typeEqualTo(AddressType.p2wpkh);
|
||||
} else {
|
||||
return q.typeEqualTo(AddressType.p2pkh);
|
||||
}
|
||||
})
|
||||
.and()
|
||||
.anyOf<String, Address>(
|
||||
keys,
|
||||
(q, String e) => q.otherDataEqualTo(e),
|
||||
)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst();
|
||||
|
||||
if (address == null) {
|
||||
final generatedAddress = await _generatePaynymReceivingAddress(
|
||||
|
@ -128,11 +126,12 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
generateSegwitAddress: isSegwit,
|
||||
);
|
||||
|
||||
final existing = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(generatedAddress.value)
|
||||
.findFirst();
|
||||
final existing =
|
||||
await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(generatedAddress.value)
|
||||
.findFirst();
|
||||
|
||||
if (existing == null) {
|
||||
// Add that new address
|
||||
|
@ -142,10 +141,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
await mainDB.updateAddress(existing, generatedAddress);
|
||||
}
|
||||
|
||||
return currentReceivingPaynymAddress(
|
||||
isSegwit: isSegwit,
|
||||
sender: sender,
|
||||
);
|
||||
return currentReceivingPaynymAddress(isSegwit: isSegwit, sender: sender);
|
||||
} else {
|
||||
return address;
|
||||
}
|
||||
|
@ -158,9 +154,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}) async {
|
||||
final root = await _getRootNode();
|
||||
final node = root.derivePath(
|
||||
_basePaynymDerivePath(
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
_basePaynymDerivePath(testnet: info.coin.network.isTestNet),
|
||||
);
|
||||
|
||||
final paymentAddress = PaymentAddress(
|
||||
|
@ -170,20 +164,22 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
index: 0,
|
||||
);
|
||||
|
||||
final addressString = generateSegwitAddress
|
||||
? paymentAddress.getReceiveAddressP2WPKH()
|
||||
: paymentAddress.getReceiveAddressP2PKH();
|
||||
final addressString =
|
||||
generateSegwitAddress
|
||||
? paymentAddress.getReceiveAddressP2WPKH()
|
||||
: paymentAddress.getReceiveAddressP2PKH();
|
||||
|
||||
final address = Address(
|
||||
walletId: walletId,
|
||||
value: addressString,
|
||||
publicKey: [],
|
||||
derivationIndex: index,
|
||||
derivationPath: DerivationPath()
|
||||
..value = _receivingPaynymAddressDerivationPath(
|
||||
index,
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
derivationPath:
|
||||
DerivationPath()
|
||||
..value = _receivingPaynymAddressDerivationPath(
|
||||
index,
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
type: generateSegwitAddress ? AddressType.p2wpkh : AddressType.p2pkh,
|
||||
subType: AddressSubType.paynymReceive,
|
||||
otherData: await storeCode(sender.toString()),
|
||||
|
@ -207,20 +203,22 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
index: index,
|
||||
);
|
||||
|
||||
final addressString = generateSegwitAddress
|
||||
? paymentAddress.getSendAddressP2WPKH()
|
||||
: paymentAddress.getSendAddressP2PKH();
|
||||
final addressString =
|
||||
generateSegwitAddress
|
||||
? paymentAddress.getSendAddressP2WPKH()
|
||||
: paymentAddress.getSendAddressP2PKH();
|
||||
|
||||
final address = Address(
|
||||
walletId: walletId,
|
||||
value: addressString,
|
||||
publicKey: [],
|
||||
derivationIndex: index,
|
||||
derivationPath: DerivationPath()
|
||||
..value = _sendPaynymAddressDerivationPath(
|
||||
index,
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
derivationPath:
|
||||
DerivationPath()
|
||||
..value = _sendPaynymAddressDerivationPath(
|
||||
index,
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
type: AddressType.nonWallet,
|
||||
subType: AddressSubType.paynymSend,
|
||||
otherData: await storeCode(other.toString()),
|
||||
|
@ -251,11 +249,12 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
generateSegwitAddress: isSegwit,
|
||||
);
|
||||
|
||||
final existing = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(nextAddress.value)
|
||||
.findFirst();
|
||||
final existing =
|
||||
await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(nextAddress.value)
|
||||
.findFirst();
|
||||
|
||||
if (existing == null) {
|
||||
// Add that new address
|
||||
|
@ -312,26 +311,18 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
Future<bip32.BIP32> deriveNotificationBip32Node() async {
|
||||
final root = await _getRootNode();
|
||||
final node = root
|
||||
.derivePath(
|
||||
_basePaynymDerivePath(
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
)
|
||||
.derivePath(_basePaynymDerivePath(testnet: info.coin.network.isTestNet))
|
||||
.derive(0);
|
||||
return node;
|
||||
}
|
||||
|
||||
/// fetch or generate this wallet's bip47 payment code
|
||||
Future<PaymentCode> getPaymentCode({
|
||||
required bool isSegwit,
|
||||
}) async {
|
||||
Future<PaymentCode> getPaymentCode({required bool isSegwit}) async {
|
||||
final node = await _getRootNode();
|
||||
|
||||
final paymentCode = PaymentCode.fromBip32Node(
|
||||
node.derivePath(
|
||||
_basePaynymDerivePath(
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
_basePaynymDerivePath(testnet: info.coin.network.isTestNet),
|
||||
),
|
||||
networkType: networkType,
|
||||
shouldSetSegwitBit: isSegwit,
|
||||
|
@ -351,8 +342,9 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}
|
||||
|
||||
Future<String> signStringWithNotificationKey(String data) async {
|
||||
final bytes =
|
||||
await signWithNotificationKey(Uint8List.fromList(utf8.encode(data)));
|
||||
final bytes = await signWithNotificationKey(
|
||||
Uint8List.fromList(utf8.encode(data)),
|
||||
);
|
||||
return Format.uint8listToString(bytes);
|
||||
}
|
||||
|
||||
|
@ -411,15 +403,19 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
|
||||
for (int i = startIndex; i < maxCount; i++) {
|
||||
final keys = await lookupKey(pCode.toString());
|
||||
final address = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymSend)
|
||||
.and()
|
||||
.anyOf<String, Address>(keys, (q, String e) => q.otherDataEqualTo(e))
|
||||
.and()
|
||||
.derivationIndexEqualTo(i)
|
||||
.findFirst();
|
||||
final address =
|
||||
await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymSend)
|
||||
.and()
|
||||
.anyOf<String, Address>(
|
||||
keys,
|
||||
(q, String e) => q.otherDataEqualTo(e),
|
||||
)
|
||||
.and()
|
||||
.derivationIndexEqualTo(i)
|
||||
.findFirst();
|
||||
|
||||
if (address != null) {
|
||||
final count = await fetchTxCount(
|
||||
|
@ -506,21 +502,26 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
int outputsBeingUsed = 0;
|
||||
final List<UTXO> utxoObjectsToUse = [];
|
||||
|
||||
for (int i = 0;
|
||||
satoshisBeingUsed < amountToSend.raw && i < spendableOutputs.length;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
satoshisBeingUsed < amountToSend.raw && i < spendableOutputs.length;
|
||||
i++
|
||||
) {
|
||||
utxoObjectsToUse.add(spendableOutputs[i]);
|
||||
satoshisBeingUsed += BigInt.from(spendableOutputs[i].value);
|
||||
outputsBeingUsed += 1;
|
||||
}
|
||||
|
||||
// add additional outputs if required
|
||||
for (int i = 0;
|
||||
i < additionalOutputs && outputsBeingUsed < spendableOutputs.length;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
i < additionalOutputs && outputsBeingUsed < spendableOutputs.length;
|
||||
i++
|
||||
) {
|
||||
utxoObjectsToUse.add(spendableOutputs[outputsBeingUsed]);
|
||||
satoshisBeingUsed +=
|
||||
BigInt.from(spendableOutputs[outputsBeingUsed].value);
|
||||
satoshisBeingUsed += BigInt.from(
|
||||
spendableOutputs[outputsBeingUsed].value,
|
||||
);
|
||||
outputsBeingUsed += 1;
|
||||
}
|
||||
|
||||
|
@ -534,8 +535,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
change: BigInt.zero,
|
||||
// override amount to get around absurd fees error
|
||||
overrideAmountForTesting: satoshisBeingUsed,
|
||||
))
|
||||
.item2,
|
||||
)).item2,
|
||||
);
|
||||
|
||||
final vSizeForWithChange = BigInt.from(
|
||||
|
@ -543,8 +543,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
targetPaymentCodeString: targetPaymentCodeString,
|
||||
utxoSigningData: utxoSigningData,
|
||||
change: satoshisBeingUsed - amountToSend.raw,
|
||||
))
|
||||
.item2,
|
||||
)).item2,
|
||||
);
|
||||
|
||||
// Assume 2 outputs, for recipient and payment code script
|
||||
|
@ -836,10 +835,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
clTx = clTx.addOutput(output);
|
||||
|
||||
clTx = clTx.addOutput(
|
||||
coinlib.Output.fromScriptBytes(
|
||||
BigInt.zero,
|
||||
opReturnScript,
|
||||
),
|
||||
coinlib.Output.fromScriptBytes(BigInt.zero, opReturnScript),
|
||||
);
|
||||
|
||||
// TODO: add possible change output and mark output as dangerous
|
||||
|
@ -859,32 +855,64 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
clTx = clTx.addOutput(output);
|
||||
}
|
||||
|
||||
clTx = clTx.sign(
|
||||
inputN: 0,
|
||||
value: BigInt.from(utxo.value),
|
||||
key: myKeyPair.privateKey,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
if (clTx.inputs[0] is coinlib.TaprootKeyInput) {
|
||||
final taproot = coinlib.Taproot(internalKey: myKeyPair.publicKey);
|
||||
|
||||
clTx = clTx.signTaproot(
|
||||
inputN: 0,
|
||||
key: taproot.tweakPrivateKey(myKeyPair.privateKey),
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else if (clTx.inputs[0] is coinlib.LegacyWitnessInput) {
|
||||
clTx = clTx.signLegacyWitness(
|
||||
inputN: 0,
|
||||
key: myKeyPair.privateKey,
|
||||
value: BigInt.from(utxo.value),
|
||||
);
|
||||
} else if (clTx.inputs[0] is coinlib.LegacyInput) {
|
||||
clTx = clTx.signLegacy(inputN: 0, key: myKeyPair.privateKey);
|
||||
} else if (clTx.inputs[0] is coinlib.TaprootSingleScriptSigInput) {
|
||||
clTx = clTx.signTaprootSingleScriptSig(
|
||||
inputN: 0,
|
||||
key: myKeyPair.privateKey,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unable to sign input of type ${clTx.inputs[0].runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
// sign rest of possible inputs
|
||||
for (int i = 1; i < utxoSigningData.length; i++) {
|
||||
final value = BigInt.from(utxoSigningData[i].utxo.value);
|
||||
coinlib.ECPrivateKey key = utxoSigningData[i].keyPair!.privateKey;
|
||||
final key = utxoSigningData[i].keyPair!.privateKey;
|
||||
|
||||
if (clTx.inputs[i] is coinlib.TaprootKeyInput) {
|
||||
final taproot = coinlib.Taproot(
|
||||
internalKey: utxoSigningData[i].keyPair!.publicKey,
|
||||
);
|
||||
|
||||
key = taproot.tweakPrivateKey(key);
|
||||
clTx = clTx.signTaproot(
|
||||
inputN: i,
|
||||
key: taproot.tweakPrivateKey(key),
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else if (clTx.inputs[i] is coinlib.LegacyWitnessInput) {
|
||||
clTx = clTx.signLegacyWitness(inputN: i, key: key, value: value);
|
||||
} else if (clTx.inputs[i] is coinlib.LegacyInput) {
|
||||
clTx = clTx.signLegacy(inputN: i, key: key);
|
||||
} else if (clTx.inputs[i] is coinlib.TaprootSingleScriptSigInput) {
|
||||
clTx = clTx.signTaprootSingleScriptSig(
|
||||
inputN: i,
|
||||
key: key,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unable to sign input of type ${clTx.inputs[i].runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
clTx = clTx.sign(
|
||||
inputN: i,
|
||||
value: value,
|
||||
key: key,
|
||||
prevOuts: prevOuts,
|
||||
);
|
||||
}
|
||||
|
||||
return Tuple2(clTx.toHex(), clTx.vSize());
|
||||
|
@ -894,13 +922,12 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}
|
||||
}
|
||||
|
||||
Future<TxData> broadcastNotificationTx({
|
||||
required TxData txData,
|
||||
}) async {
|
||||
Future<TxData> broadcastNotificationTx({required TxData txData}) async {
|
||||
try {
|
||||
Logging.instance.d("confirmNotificationTx txData: $txData");
|
||||
final txHash =
|
||||
await electrumXClient.broadcastTransaction(rawTx: txData.raw!);
|
||||
final txHash = await electrumXClient.broadcastTransaction(
|
||||
rawTx: txData.raw!,
|
||||
);
|
||||
Logging.instance.d("Sent txHash: $txHash");
|
||||
|
||||
try {
|
||||
|
@ -913,10 +940,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
);
|
||||
}
|
||||
|
||||
return txData.copyWith(
|
||||
txid: txHash,
|
||||
txHash: txHash,
|
||||
);
|
||||
return txData.copyWith(txid: txHash, txHash: txHash);
|
||||
} catch (e, s) {
|
||||
Logging.instance.e(
|
||||
"Exception rethrown from confirmSend(): ",
|
||||
|
@ -964,12 +988,13 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
|
||||
final myNotificationAddress = await getMyNotificationAddress();
|
||||
|
||||
final txns = await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.findAll();
|
||||
final txns =
|
||||
await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.findAll();
|
||||
|
||||
for (final tx in txns) {
|
||||
switch (tx.type) {
|
||||
|
@ -978,9 +1003,7 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
for (final outputAddress in output.addresses) {
|
||||
if (outputAddress == myNotificationAddress.value) {
|
||||
final unBlindedPaymentCode =
|
||||
await unBlindedPaymentCodeFromTransaction(
|
||||
transaction: tx,
|
||||
);
|
||||
await unBlindedPaymentCodeFromTransaction(transaction: tx);
|
||||
|
||||
if (unBlindedPaymentCode != null &&
|
||||
paymentCodeString == unBlindedPaymentCode.toString()) {
|
||||
|
@ -990,8 +1013,8 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
|
||||
final unBlindedPaymentCodeBad =
|
||||
await unBlindedPaymentCodeFromTransactionBad(
|
||||
transaction: tx,
|
||||
);
|
||||
transaction: tx,
|
||||
);
|
||||
|
||||
if (unBlindedPaymentCodeBad != null &&
|
||||
paymentCodeString == unBlindedPaymentCodeBad.toString()) {
|
||||
|
@ -1005,14 +1028,15 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
case TransactionType.outgoing:
|
||||
for (final output in tx.outputs) {
|
||||
for (final outputAddress in output.addresses) {
|
||||
final address = await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.valueEqualTo(outputAddress)
|
||||
.findFirst();
|
||||
final address =
|
||||
await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.valueEqualTo(outputAddress)
|
||||
.findFirst();
|
||||
|
||||
if (address?.otherData != null) {
|
||||
final code = await paymentCodeStringByKey(address!.otherData!);
|
||||
|
@ -1055,8 +1079,9 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
required TransactionV2 transaction,
|
||||
}) async {
|
||||
try {
|
||||
final blindedCodeBytes =
|
||||
Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction);
|
||||
final blindedCodeBytes = Bip47Utils.getBlindedPaymentCodeBytesFrom(
|
||||
transaction,
|
||||
);
|
||||
|
||||
// transaction does not contain a payment code
|
||||
if (blindedCodeBytes == null) {
|
||||
|
@ -1113,8 +1138,9 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
required TransactionV2 transaction,
|
||||
}) async {
|
||||
try {
|
||||
final blindedCodeBytes =
|
||||
Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction);
|
||||
final blindedCodeBytes = Bip47Utils.getBlindedPaymentCodeBytesFrom(
|
||||
transaction,
|
||||
);
|
||||
|
||||
// transaction does not contain a payment code
|
||||
if (blindedCodeBytes == null) {
|
||||
|
@ -1168,13 +1194,14 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}
|
||||
|
||||
Future<List<PaymentCode>>
|
||||
getAllPaymentCodesFromNotificationTransactions() async {
|
||||
final txns = await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.findAll();
|
||||
getAllPaymentCodesFromNotificationTransactions() async {
|
||||
final txns =
|
||||
await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.findAll();
|
||||
|
||||
final List<PaymentCode> codes = [];
|
||||
|
||||
|
@ -1182,20 +1209,23 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
// tx is sent so we can check the address's otherData for the code String
|
||||
if (tx.type == TransactionType.outgoing) {
|
||||
for (final output in tx.outputs) {
|
||||
for (final outputAddress
|
||||
in output.addresses.where((e) => e.isNotEmpty)) {
|
||||
final address = await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.valueEqualTo(outputAddress)
|
||||
.findFirst();
|
||||
for (final outputAddress in output.addresses.where(
|
||||
(e) => e.isNotEmpty,
|
||||
)) {
|
||||
final address =
|
||||
await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.valueEqualTo(outputAddress)
|
||||
.findFirst();
|
||||
|
||||
if (address?.otherData != null) {
|
||||
final codeString =
|
||||
await paymentCodeStringByKey(address!.otherData!);
|
||||
final codeString = await paymentCodeStringByKey(
|
||||
address!.otherData!,
|
||||
);
|
||||
if (codeString != null &&
|
||||
codes.where((e) => e.toString() == codeString).isEmpty) {
|
||||
codes.add(
|
||||
|
@ -1236,14 +1266,15 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
Future<void> checkForNotificationTransactionsTo(
|
||||
Set<String> otherCodeStrings,
|
||||
) async {
|
||||
final sentNotificationTransactions = await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.and()
|
||||
.typeEqualTo(TransactionType.outgoing)
|
||||
.findAll();
|
||||
final sentNotificationTransactions =
|
||||
await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.bip47Notification)
|
||||
.and()
|
||||
.typeEqualTo(TransactionType.outgoing)
|
||||
.findAll();
|
||||
|
||||
final List<PaymentCode> codes = [];
|
||||
for (final codeString in otherCodeStrings) {
|
||||
|
@ -1260,8 +1291,10 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
final notificationAddress = code.notificationAddressP2PKH();
|
||||
|
||||
if (outputAddress == notificationAddress) {
|
||||
Address? storedAddress =
|
||||
await mainDB.getAddress(walletId, outputAddress);
|
||||
Address? storedAddress = await mainDB.getAddress(
|
||||
walletId,
|
||||
outputAddress,
|
||||
);
|
||||
if (storedAddress == null) {
|
||||
// most likely not mine
|
||||
storedAddress = Address(
|
||||
|
@ -1343,10 +1376,12 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
int outgoingGapCounter = 0;
|
||||
|
||||
// non segwit receiving
|
||||
for (int i = 0;
|
||||
i < maxNumberOfIndexesToCheck &&
|
||||
receivingGapCounter < maxUnusedAddressGap;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
i < maxNumberOfIndexesToCheck &&
|
||||
receivingGapCounter < maxUnusedAddressGap;
|
||||
i++
|
||||
) {
|
||||
if (receivingGapCounter < maxUnusedAddressGap) {
|
||||
final address = await _generatePaynymReceivingAddress(
|
||||
sender: other,
|
||||
|
@ -1371,10 +1406,11 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}
|
||||
|
||||
// non segwit sends
|
||||
for (int i = 0;
|
||||
i < maxNumberOfIndexesToCheck &&
|
||||
outgoingGapCounter < maxUnusedAddressGap;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
i < maxNumberOfIndexesToCheck && outgoingGapCounter < maxUnusedAddressGap;
|
||||
i++
|
||||
) {
|
||||
if (outgoingGapCounter < maxUnusedAddressGap) {
|
||||
final address = await _generatePaynymSendAddress(
|
||||
other: other,
|
||||
|
@ -1403,10 +1439,12 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
int receivingGapCounterSegwit = 0;
|
||||
int outgoingGapCounterSegwit = 0;
|
||||
// segwit receiving
|
||||
for (int i = 0;
|
||||
i < maxNumberOfIndexesToCheck &&
|
||||
receivingGapCounterSegwit < maxUnusedAddressGap;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
i < maxNumberOfIndexesToCheck &&
|
||||
receivingGapCounterSegwit < maxUnusedAddressGap;
|
||||
i++
|
||||
) {
|
||||
if (receivingGapCounterSegwit < maxUnusedAddressGap) {
|
||||
final address = await _generatePaynymReceivingAddress(
|
||||
sender: other,
|
||||
|
@ -1431,10 +1469,12 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}
|
||||
|
||||
// segwit sends
|
||||
for (int i = 0;
|
||||
i < maxNumberOfIndexesToCheck &&
|
||||
outgoingGapCounterSegwit < maxUnusedAddressGap;
|
||||
i++) {
|
||||
for (
|
||||
int i = 0;
|
||||
i < maxNumberOfIndexesToCheck &&
|
||||
outgoingGapCounterSegwit < maxUnusedAddressGap;
|
||||
i++
|
||||
) {
|
||||
if (outgoingGapCounterSegwit < maxUnusedAddressGap) {
|
||||
final address = await _generatePaynymSendAddress(
|
||||
other: other,
|
||||
|
@ -1463,25 +1503,24 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}
|
||||
|
||||
Future<Address> getMyNotificationAddress() async {
|
||||
final storedAddress = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.typeEqualTo(AddressType.p2pkh)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.findFirst();
|
||||
final storedAddress =
|
||||
await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.typeEqualTo(AddressType.p2pkh)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.findFirst();
|
||||
|
||||
if (storedAddress != null) {
|
||||
return storedAddress;
|
||||
} else {
|
||||
final root = await _getRootNode();
|
||||
final node = root.derivePath(
|
||||
_basePaynymDerivePath(
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
_basePaynymDerivePath(testnet: info.coin.network.isTestNet),
|
||||
);
|
||||
final paymentCode = PaymentCode.fromBip32Node(
|
||||
node,
|
||||
|
@ -1493,23 +1532,19 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
pubkey: paymentCode.notificationPublicKey(),
|
||||
);
|
||||
|
||||
final addressString = btc_dart
|
||||
.P2PKH(
|
||||
data: data,
|
||||
network: networkType,
|
||||
)
|
||||
.data
|
||||
.address!;
|
||||
final addressString =
|
||||
btc_dart.P2PKH(data: data, network: networkType).data.address!;
|
||||
|
||||
Address address = Address(
|
||||
walletId: walletId,
|
||||
value: addressString,
|
||||
publicKey: paymentCode.getPubKey(),
|
||||
derivationIndex: 0,
|
||||
derivationPath: DerivationPath()
|
||||
..value = _notificationDerivationPath(
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
derivationPath:
|
||||
DerivationPath()
|
||||
..value = _notificationDerivationPath(
|
||||
testnet: info.coin.network.isTestNet,
|
||||
),
|
||||
type: AddressType.p2pkh,
|
||||
subType: AddressSubType.paynymNotification,
|
||||
otherData: await storeCode(paymentCode.toString()),
|
||||
|
@ -1520,16 +1555,17 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
// beginning to see if there already was notification address. This would
|
||||
// lead to a Unique Index violation error
|
||||
await mainDB.isar.writeTxn(() async {
|
||||
final storedAddress = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.typeEqualTo(AddressType.p2pkh)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.findFirst();
|
||||
final storedAddress =
|
||||
await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification)
|
||||
.and()
|
||||
.typeEqualTo(AddressType.p2pkh)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.findFirst();
|
||||
|
||||
if (storedAddress == null) {
|
||||
await mainDB.isar.addresses.put(address);
|
||||
|
@ -1607,45 +1643,43 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
overrideAddresses ?? await fetchAddressesForElectrumXScan();
|
||||
|
||||
// Separate receiving and change addresses.
|
||||
final Set<String> receivingAddresses = allAddressesOld
|
||||
.where(
|
||||
(e) =>
|
||||
e.subType == AddressSubType.receiving ||
|
||||
e.subType == AddressSubType.paynymNotification ||
|
||||
e.subType == AddressSubType.paynymReceive,
|
||||
)
|
||||
.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 ||
|
||||
e.subType == AddressSubType.paynymNotification ||
|
||||
e.subType == AddressSubType.paynymReceive,
|
||||
)
|
||||
.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};
|
||||
|
||||
// Fetch history from ElectrumX.
|
||||
final List<Map<String, dynamic>> allTxHashes =
|
||||
await fetchHistory(allAddressesSet);
|
||||
|
||||
final unconfirmedTxs = await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.heightIsNull()
|
||||
.or()
|
||||
.heightEqualTo(0)
|
||||
.txidProperty()
|
||||
.findAll();
|
||||
|
||||
allTxHashes.addAll(
|
||||
unconfirmedTxs.map(
|
||||
(e) => {
|
||||
"tx_hash": e,
|
||||
},
|
||||
),
|
||||
final List<Map<String, dynamic>> allTxHashes = await fetchHistory(
|
||||
allAddressesSet,
|
||||
);
|
||||
|
||||
final unconfirmedTxs =
|
||||
await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.heightIsNull()
|
||||
.or()
|
||||
.heightEqualTo(0)
|
||||
.txidProperty()
|
||||
.findAll();
|
||||
|
||||
allTxHashes.addAll(unconfirmedTxs.map((e) => {"tx_hash": e}));
|
||||
|
||||
// Only parse new txs (not in db yet).
|
||||
final List<Map<String, dynamic>> allTransactions = [];
|
||||
for (final txHash in allTxHashes) {
|
||||
|
@ -1670,16 +1704,17 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
} catch (e) {
|
||||
// tx no longer exists then delete from local db
|
||||
if (e.toString().contains(
|
||||
"JSON-RPC error 2: daemon error: DaemonError({'code': -5, "
|
||||
"'message': 'No such mempool or blockchain transaction",
|
||||
)) {
|
||||
"JSON-RPC error 2: daemon error: DaemonError({'code': -5, "
|
||||
"'message': 'No such mempool or blockchain transaction",
|
||||
)) {
|
||||
await mainDB.isar.writeTxn(
|
||||
() async => await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.txidEqualTo(txid)
|
||||
.deleteFirst(),
|
||||
() async =>
|
||||
await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.txidEqualTo(txid)
|
||||
.deleteFirst(),
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
|
@ -1802,8 +1837,9 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
TransactionSubType subType = TransactionSubType.none;
|
||||
if (outputs.length > 1 && inputs.isNotEmpty) {
|
||||
for (int i = 0; i < outputs.length; i++) {
|
||||
final List<String>? scriptChunks =
|
||||
outputs[i].scriptPubKeyAsm?.split(" ");
|
||||
final List<String>? scriptChunks = outputs[i].scriptPubKeyAsm?.split(
|
||||
" ",
|
||||
);
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.toUint8ListFromHex;
|
||||
|
@ -1856,7 +1892,8 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
txid: txData["txid"] as String,
|
||||
height: txData["height"] as int?,
|
||||
version: txData["version"] as int,
|
||||
timestamp: txData["blocktime"] as int? ??
|
||||
timestamp:
|
||||
txData["blocktime"] as int? ??
|
||||
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||
inputs: List.unmodifiable(inputs),
|
||||
outputs: List.unmodifiable(outputs),
|
||||
|
@ -1872,12 +1909,8 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<
|
||||
({
|
||||
String? blockedReason,
|
||||
bool blocked,
|
||||
String? utxoLabel,
|
||||
})> checkBlockUTXO(
|
||||
Future<({String? blockedReason, bool blocked, String? utxoLabel})>
|
||||
checkBlockUTXO(
|
||||
Map<String, dynamic> jsonUTXO,
|
||||
String? scriptPubKeyHex,
|
||||
Map<String, dynamic>? jsonTX,
|
||||
|
@ -1905,7 +1938,8 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
blocked = true;
|
||||
blockedReason = "Incoming paynym notification transaction.";
|
||||
} else {
|
||||
blockedReason = "Paynym notification change output. Incautious "
|
||||
blockedReason =
|
||||
"Paynym notification change output. Incautious "
|
||||
"handling of change outputs from notification transactions "
|
||||
"may cause unintended loss of privacy.";
|
||||
utxoLabel = blockedReason;
|
||||
|
@ -1920,23 +1954,21 @@ mixin PaynymInterface<T extends PaynymCurrencyInterface>
|
|||
return (
|
||||
blockedReason: blockedReason,
|
||||
blocked: blocked,
|
||||
utxoLabel: utxoLabel
|
||||
utxoLabel: utxoLabel,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FilterOperation? get transactionFilterOperation => FilterGroup.not(
|
||||
const FilterGroup.and(
|
||||
[
|
||||
FilterCondition.equalTo(
|
||||
property: r"subType",
|
||||
value: TransactionSubType.bip47Notification,
|
||||
),
|
||||
FilterCondition.equalTo(
|
||||
property: r"type",
|
||||
value: TransactionType.incoming,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
const FilterGroup.and([
|
||||
FilterCondition.equalTo(
|
||||
property: r"subType",
|
||||
value: TransactionSubType.bip47Notification,
|
||||
),
|
||||
FilterCondition.equalTo(
|
||||
property: r"type",
|
||||
value: TransactionType.incoming,
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
|
28
pubspec.lock
28
pubspec.lock
|
@ -352,21 +352,19 @@ packages:
|
|||
coinlib:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
path: coinlib
|
||||
ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739"
|
||||
resolved-ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739"
|
||||
url: "https://github.com/julian-CStack/coinlib.git"
|
||||
source: git
|
||||
version: "2.2.0"
|
||||
name: coinlib
|
||||
sha256: f99c090ca300b6c9b5414dc100f7f36f49a5a2af31d477b3ce04a605c5f1103c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
coinlib_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: coinlib_flutter
|
||||
ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739"
|
||||
resolved-ref: "0acacfd17eacf72135c693a7b862bd9b7cc56739"
|
||||
url: "https://github.com/julian-CStack/coinlib.git"
|
||||
source: git
|
||||
version: "2.2.0"
|
||||
name: coinlib_flutter
|
||||
sha256: "185c622986d12d2ccda98f151ce047360464dd7a6cbb6877781a9816d14bb8c4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -832,8 +830,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: ca0c72cecc40fc0bfbafc0d26af675d973ab516b
|
||||
resolved-ref: ca0c72cecc40fc0bfbafc0d26af675d973ab516b
|
||||
ref: "33e1034911b842c57bdf6ddaa825cbf635a0c9db"
|
||||
resolved-ref: "33e1034911b842c57bdf6ddaa825cbf635a0c9db"
|
||||
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
|
||||
source: git
|
||||
version: "0.0.2"
|
||||
|
@ -1247,7 +1245,7 @@ packages:
|
|||
path: "crypto_plugins/flutter_liblelantus"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.2"
|
||||
version: "0.0.3"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -2,19 +2,13 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
mkdir -p build
|
||||
. ./config.sh
|
||||
./install_ndk.sh
|
||||
|
||||
PLUGINS_DIR=../../crypto_plugins
|
||||
|
||||
(cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh )
|
||||
|
||||
wait
|
||||
|
|
|
@ -2,19 +2,13 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
mkdir -p build
|
||||
. ./config.sh
|
||||
./install_ndk.sh
|
||||
|
||||
PLUGINS_DIR=../../crypto_plugins
|
||||
|
||||
(cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh )
|
||||
|
||||
wait
|
||||
|
|
|
@ -4,19 +4,13 @@ set -x -e
|
|||
|
||||
# todo: revisit following at some point
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
mkdir -p build
|
||||
. ./config.sh
|
||||
./install_ndk.sh
|
||||
|
||||
PLUGINS_DIR=../../crypto_plugins
|
||||
|
||||
(cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh )
|
||||
|
||||
wait
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
export WORKDIR="$(pwd)/"build
|
||||
export ANDROID_NDK_ZIP=${WORKDIR}/android-ndk-r20b.zip
|
||||
export TOOLCHAIN_DIR="${WORKDIR}/toolchain"
|
||||
# Change this Value to a lower number if you run out of memory while compiling
|
||||
export OVERRIDE_THREADS="$(nproc)"
|
|
@ -1,21 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
mkdir -p build
|
||||
. ./config.sh
|
||||
ANDROID_NDK_SHA256="8381c440fe61fcbb01e209211ac01b519cd6adf51ab1c2281d5daad6ca4c8c8c"
|
||||
|
||||
if [ ! -e "$ANDROID_NDK_ZIP" ]; then
|
||||
curl https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip -o "${ANDROID_NDK_ZIP}"
|
||||
fi
|
||||
echo "${ANDROID_NDK_SHA256}" "${ANDROID_NDK_ZIP}" | sha256sum -c || exit 1
|
||||
|
||||
|
||||
PLUGINS_DIR=../../crypto_plugins
|
||||
|
||||
mkdir -p "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/build
|
||||
mkdir -p "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android/build
|
||||
mkdir -p "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android/build
|
||||
|
||||
cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/build/
|
||||
cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android/build/
|
||||
cp "${ANDROID_NDK_ZIP}" "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android/build/
|
|
@ -15,7 +15,7 @@ android {
|
|||
namespace "com.place.holder"
|
||||
compileSdk flutter.compileSdkVersion
|
||||
// ndkVersion flutter.ndkVersion
|
||||
ndkVersion = "26.1.10909125"
|
||||
ndkVersion = "28.0.13004108"
|
||||
|
||||
packagingOptions {
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
|
@ -90,6 +90,7 @@ android {
|
|||
task.doFirst {
|
||||
println "The compileSdkVersion is $flutter.compileSdkVersion"
|
||||
println "The targetSdkVersion is $flutter.targetSdkVersion"
|
||||
println "The ndkVersion is $ndkVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.place.holder">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.place.holder">
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission
|
||||
android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<uses-permission
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.place.holder">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
|
|
@ -38,7 +38,7 @@ dependencies:
|
|||
flutter_libsparkmobile:
|
||||
git:
|
||||
url: https://github.com/cypherstack/flutter_libsparkmobile.git
|
||||
ref: ca0c72cecc40fc0bfbafc0d26af675d973ab516b
|
||||
ref: 33e1034911b842c57bdf6ddaa825cbf635a0c9db
|
||||
|
||||
# cs_monero compat (unpublished)
|
||||
compat:
|
||||
|
@ -176,12 +176,7 @@ dependencies:
|
|||
convert: ^3.1.1
|
||||
flutter_hooks: ^0.20.3
|
||||
meta: ^1.9.1
|
||||
# coinlib_flutter: ^2.1.0-rc.1
|
||||
coinlib_flutter:
|
||||
git:
|
||||
url: https://github.com/julian-CStack/coinlib.git
|
||||
ref: 0acacfd17eacf72135c693a7b862bd9b7cc56739
|
||||
path: coinlib_flutter
|
||||
coinlib_flutter: ^3.0.0
|
||||
electrum_adapter:
|
||||
git:
|
||||
url: https://github.com/cypherstack/electrum_adapter.git
|
||||
|
@ -261,18 +256,9 @@ dependency_overrides:
|
|||
# needed for dart 3.5+ (at least for now)
|
||||
win32: ^5.5.4
|
||||
|
||||
# coin lib git for testing while waiting for publishing
|
||||
coinlib:
|
||||
git:
|
||||
url: https://github.com/julian-CStack/coinlib.git
|
||||
ref: 0acacfd17eacf72135c693a7b862bd9b7cc56739
|
||||
path: coinlib
|
||||
|
||||
coinlib_flutter:
|
||||
git:
|
||||
url: https://github.com/julian-CStack/coinlib.git
|
||||
ref: 0acacfd17eacf72135c693a7b862bd9b7cc56739
|
||||
path: coinlib_flutter
|
||||
# namecoin names lib needs to be updated
|
||||
coinlib: ^3.0.0
|
||||
coinlib_flutter: ^3.0.0
|
||||
|
||||
bip47:
|
||||
git:
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
# ensure ios rust triples are there
|
||||
rustup target add aarch64-apple-ios
|
||||
rustup target add x86_64-apple-ios
|
||||
|
@ -16,7 +12,6 @@ rustup target add x86_64-apple-ios
|
|||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh )
|
||||
|
||||
wait
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
# ensure ios rust triples are there
|
||||
rustup target add aarch64-apple-ios
|
||||
rustup target add x86_64-apple-ios
|
||||
|
@ -16,7 +12,6 @@ rustup target add x86_64-apple-ios
|
|||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh )
|
||||
|
||||
wait
|
||||
|
|
|
@ -4,10 +4,6 @@ set -x -e
|
|||
|
||||
# todo: revisit following at some point
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
# ensure ios rust triples are there
|
||||
rustup target add aarch64-apple-ios
|
||||
rustup target add x86_64-apple-ios
|
||||
|
@ -18,7 +14,6 @@ rustup target add x86_64-apple-ios
|
|||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh )
|
||||
|
||||
wait
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
# for arm
|
||||
# flutter-elinux clean
|
||||
# flutter-elinux pub get
|
||||
|
@ -14,7 +10,6 @@ mkdir -p build
|
|||
./build_secure_storage_deps.sh
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh )
|
||||
|
||||
./build_secp256k1.sh
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
# for arm
|
||||
# flutter-elinux clean
|
||||
# flutter-elinux pub get
|
||||
|
@ -14,7 +10,6 @@ mkdir -p build
|
|||
./build_secure_storage_deps.sh
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh )
|
||||
|
||||
./build_secp256k1.sh
|
||||
|
|
|
@ -4,9 +4,6 @@ set -x -e
|
|||
|
||||
# todo: revisit following at some point
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
# for arm
|
||||
# flutter-elinux clean
|
||||
|
@ -16,7 +13,6 @@ mkdir -p build
|
|||
./build_secure_storage_deps.sh &
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh )
|
||||
|
||||
./build_secp256k1.sh
|
||||
|
|
|
@ -2,17 +2,10 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh )
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
||||
# set rust (back) to a more recent stable release to allow stack wallet to build tor
|
||||
set_rust_to_1720
|
||||
|
|
|
@ -2,17 +2,9 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh )
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
||||
# set rust (back) to a more recent stable release to allow stack wallet to build tor
|
||||
set_rust_to_1720
|
||||
|
|
|
@ -4,17 +4,10 @@ set -x -e
|
|||
|
||||
# todo: revisit following at some point
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh )
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
||||
# set rust (back) to a more recent stable release to allow stack wallet to build tor
|
||||
set_rust_to_1720
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set_rust_to_1671() {
|
||||
if rustup toolchain list | grep -q "1.67.1"; then
|
||||
rustup default 1.67.1
|
||||
else
|
||||
echo "Rust version 1.67.1 is not installed. Please install it using 'rustup install 1.67.1'." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
set_rust_to_1720() {
|
||||
if rustup toolchain list | grep -q "1.72.0"; then
|
||||
rustup default 1.72.0
|
||||
else
|
||||
echo "Rust version 1.72.0 is not installed. Please install it using 'rustup install 1.72.0'." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
|
@ -2,14 +2,9 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
mkdir -p build
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh )
|
||||
|
||||
./build_secp256k1_wsl.sh
|
||||
|
|
|
@ -2,14 +2,9 @@
|
|||
|
||||
set -x -e
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
mkdir -p build
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh )
|
||||
|
||||
./build_secp256k1_wsl.sh
|
||||
|
|
|
@ -4,14 +4,9 @@ set -x -e
|
|||
|
||||
# todo: revisit following at some point
|
||||
|
||||
# libepiccash requires old rust
|
||||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
mkdir -p build
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh )
|
||||
|
||||
./build_secp256k1_wsl.sh
|
||||
|
|
Loading…
Reference in a new issue