mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
between 2-4x speed increase for restore, first refresh, and nth refresh
This commit is contained in:
parent
691c4a772d
commit
d8bc213749
4 changed files with 752 additions and 693 deletions
|
@ -399,6 +399,138 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _checkGaps(
|
||||||
|
int maxNumberOfIndexesToCheck,
|
||||||
|
int maxUnusedAddressGap,
|
||||||
|
int txCountBatchSize,
|
||||||
|
bip32.BIP32 root,
|
||||||
|
DerivePathType type,
|
||||||
|
int account) async {
|
||||||
|
List<String> addressArray = [];
|
||||||
|
int returningIndex = -1;
|
||||||
|
Map<String, Map<String, String>> derivations = {};
|
||||||
|
int gapCounter = 0;
|
||||||
|
for (int index = 0;
|
||||||
|
index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap;
|
||||||
|
index += txCountBatchSize) {
|
||||||
|
List<String> iterationsAddressArray = [];
|
||||||
|
Logging.instance.log(
|
||||||
|
"index: $index, \t GapCounter $account ${type.name}: $gapCounter",
|
||||||
|
level: LogLevel.Info);
|
||||||
|
|
||||||
|
final ID = "k_$index";
|
||||||
|
Map<String, String> txCountCallArgs = {};
|
||||||
|
final Map<String, dynamic> receivingNodes = {};
|
||||||
|
|
||||||
|
for (int j = 0; j < txCountBatchSize; j++) {
|
||||||
|
final node = await compute(
|
||||||
|
getBip32NodeFromRootWrapper,
|
||||||
|
Tuple4(
|
||||||
|
account,
|
||||||
|
index + j,
|
||||||
|
root,
|
||||||
|
type,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
String? address;
|
||||||
|
switch (type) {
|
||||||
|
case DerivePathType.bip44:
|
||||||
|
address = P2PKH(
|
||||||
|
data: PaymentData(pubkey: node.publicKey),
|
||||||
|
network: _network)
|
||||||
|
.data
|
||||||
|
.address!;
|
||||||
|
break;
|
||||||
|
case DerivePathType.bip49:
|
||||||
|
address = P2SH(
|
||||||
|
data: PaymentData(
|
||||||
|
redeem: P2WPKH(
|
||||||
|
data: PaymentData(pubkey: node.publicKey),
|
||||||
|
network: _network)
|
||||||
|
.data),
|
||||||
|
network: _network)
|
||||||
|
.data
|
||||||
|
.address!;
|
||||||
|
break;
|
||||||
|
case DerivePathType.bip84:
|
||||||
|
address = P2WPKH(
|
||||||
|
network: _network,
|
||||||
|
data: PaymentData(pubkey: node.publicKey))
|
||||||
|
.data
|
||||||
|
.address!;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Exception("No Path type $type exists");
|
||||||
|
}
|
||||||
|
receivingNodes.addAll({
|
||||||
|
"${ID}_$j": {
|
||||||
|
"node": node,
|
||||||
|
"address": address,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
txCountCallArgs.addAll({
|
||||||
|
"${ID}_$j": address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get address tx counts
|
||||||
|
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
|
||||||
|
|
||||||
|
// check and add appropriate addresses
|
||||||
|
for (int k = 0; k < txCountBatchSize; k++) {
|
||||||
|
int count = counts["${ID}_$k"]!;
|
||||||
|
if (count > 0) {
|
||||||
|
final node = receivingNodes["${ID}_$k"];
|
||||||
|
// add address to array
|
||||||
|
addressArray.add(node["address"] as String);
|
||||||
|
iterationsAddressArray.add(node["address"] as String);
|
||||||
|
// set current index
|
||||||
|
returningIndex = index + k;
|
||||||
|
// reset counter
|
||||||
|
gapCounter = 0;
|
||||||
|
// add info to derivations
|
||||||
|
derivations[node["address"] as String] = {
|
||||||
|
"pubKey": Format.uint8listToString(
|
||||||
|
(node["node"] as bip32.BIP32).publicKey),
|
||||||
|
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase counter when no tx history found
|
||||||
|
if (count == 0) {
|
||||||
|
gapCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// cache all the transactions while waiting for the current function to finish.
|
||||||
|
unawaited(getTransactionCacheEarly(iterationsAddressArray));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"addressArray": addressArray,
|
||||||
|
"index": returningIndex,
|
||||||
|
"derivations": derivations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getTransactionCacheEarly(List<String> allAddresses) async {
|
||||||
|
try {
|
||||||
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
|
await _fetchHistory(allAddresses);
|
||||||
|
for (final txHash in allTxHashes) {
|
||||||
|
try {
|
||||||
|
unawaited(cachedElectrumXClient.getTransaction(
|
||||||
|
txHash: txHash["tx_hash"] as String,
|
||||||
|
verbose: true,
|
||||||
|
coin: coin,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _recoverWalletFromBIP32SeedPhrase({
|
Future<void> _recoverWalletFromBIP32SeedPhrase({
|
||||||
required String mnemonic,
|
required String mnemonic,
|
||||||
int maxUnusedAddressGap = 20,
|
int maxUnusedAddressGap = 20,
|
||||||
|
@ -429,10 +561,6 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
int p2shChangeIndex = -1;
|
int p2shChangeIndex = -1;
|
||||||
int p2wpkhChangeIndex = -1;
|
int p2wpkhChangeIndex = -1;
|
||||||
|
|
||||||
// The gap limit will be capped at [maxUnusedAddressGap]
|
|
||||||
int receivingGapCounter = 0;
|
|
||||||
int changeGapCounter = 0;
|
|
||||||
|
|
||||||
// actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3
|
// actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3
|
||||||
const txCountBatchSize = 12;
|
const txCountBatchSize = 12;
|
||||||
|
|
||||||
|
@ -440,323 +568,71 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
// receiving addresses
|
// receiving addresses
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("checking receiving addresses...", level: LogLevel.Info);
|
.log("checking receiving addresses...", level: LogLevel.Info);
|
||||||
for (int index = 0;
|
Future resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
index < maxNumberOfIndexesToCheck &&
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
|
||||||
receivingGapCounter < maxUnusedAddressGap;
|
|
||||||
index += txCountBatchSize) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"index: $index, \t receivingGapCounter: $receivingGapCounter",
|
|
||||||
level: LogLevel.Info);
|
|
||||||
|
|
||||||
final receivingP2pkhID = "k_$index";
|
Future resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
final receivingP2shID = "s_$index";
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0);
|
||||||
final receivingP2wpkhID = "w_$index";
|
|
||||||
Map<String, String> txCountCallArgs = {};
|
|
||||||
final Map<String, dynamic> receivingNodes = {};
|
|
||||||
|
|
||||||
for (int j = 0; j < txCountBatchSize; j++) {
|
Future resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
// bip44 / P2PKH
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0);
|
||||||
final node44 = await compute(
|
|
||||||
getBip32NodeFromRootWrapper,
|
|
||||||
Tuple4(
|
|
||||||
0,
|
|
||||||
index + j,
|
|
||||||
root,
|
|
||||||
DerivePathType.bip44,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2pkhReceiveAddress = P2PKH(
|
|
||||||
data: PaymentData(pubkey: node44.publicKey),
|
|
||||||
network: _network)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
receivingNodes.addAll({
|
|
||||||
"${receivingP2pkhID}_$j": {
|
|
||||||
"node": node44,
|
|
||||||
"address": p2pkhReceiveAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
txCountCallArgs.addAll({
|
|
||||||
"${receivingP2pkhID}_$j": p2pkhReceiveAddress,
|
|
||||||
});
|
|
||||||
|
|
||||||
// bip49 / P2SH
|
|
||||||
final node49 = await compute(
|
|
||||||
getBip32NodeFromRootWrapper,
|
|
||||||
Tuple4(
|
|
||||||
0,
|
|
||||||
index + j,
|
|
||||||
root,
|
|
||||||
DerivePathType.bip49,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2shReceiveAddress = P2SH(
|
|
||||||
data: PaymentData(
|
|
||||||
redeem: P2WPKH(
|
|
||||||
data: PaymentData(pubkey: node49.publicKey),
|
|
||||||
network: _network)
|
|
||||||
.data),
|
|
||||||
network: _network)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
receivingNodes.addAll({
|
|
||||||
"${receivingP2shID}_$j": {
|
|
||||||
"node": node49,
|
|
||||||
"address": p2shReceiveAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
txCountCallArgs.addAll({
|
|
||||||
"${receivingP2shID}_$j": p2shReceiveAddress,
|
|
||||||
});
|
|
||||||
|
|
||||||
// bip84 / P2WPKH
|
|
||||||
final node84 = await compute(
|
|
||||||
getBip32NodeFromRootWrapper,
|
|
||||||
Tuple4(
|
|
||||||
0,
|
|
||||||
index + j,
|
|
||||||
root,
|
|
||||||
DerivePathType.bip84,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2wpkhReceiveAddress = P2WPKH(
|
|
||||||
network: _network,
|
|
||||||
data: PaymentData(pubkey: node84.publicKey))
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
receivingNodes.addAll({
|
|
||||||
"${receivingP2wpkhID}_$j": {
|
|
||||||
"node": node84,
|
|
||||||
"address": p2wpkhReceiveAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
txCountCallArgs.addAll({
|
|
||||||
"${receivingP2wpkhID}_$j": p2wpkhReceiveAddress,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// get address tx counts
|
|
||||||
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
|
|
||||||
|
|
||||||
// check and add appropriate addresses
|
|
||||||
for (int k = 0; k < txCountBatchSize; k++) {
|
|
||||||
int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!;
|
|
||||||
if (p2pkhTxCount > 0) {
|
|
||||||
final node = receivingNodes["${receivingP2pkhID}_$k"];
|
|
||||||
// add address to array
|
|
||||||
p2pkhReceiveAddressArray.add(node["address"] as String);
|
|
||||||
// set current index
|
|
||||||
p2pkhReceiveIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
receivingGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2pkhReceiveDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int p2shTxCount = counts["${receivingP2shID}_$k"]!;
|
|
||||||
if (p2shTxCount > 0) {
|
|
||||||
final node = receivingNodes["${receivingP2shID}_$k"];
|
|
||||||
// add address to array
|
|
||||||
p2shReceiveAddressArray.add(node["address"] as String);
|
|
||||||
// set current index
|
|
||||||
p2shReceiveIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
receivingGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2shReceiveDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int p2wpkhTxCount = counts["${receivingP2wpkhID}_$k"]!;
|
|
||||||
if (p2wpkhTxCount > 0) {
|
|
||||||
final node = receivingNodes["${receivingP2wpkhID}_$k"];
|
|
||||||
// add address to array
|
|
||||||
p2wpkhReceiveAddressArray.add(node["address"] as String);
|
|
||||||
// set current index
|
|
||||||
p2wpkhReceiveIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
receivingGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2wpkhReceiveDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// increase counter when no tx history found
|
|
||||||
if (p2wpkhTxCount == 0 && p2pkhTxCount == 0 && p2shTxCount == 0) {
|
|
||||||
receivingGapCounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("checking change addresses...", level: LogLevel.Info);
|
.log("checking change addresses...", level: LogLevel.Info);
|
||||||
// change addresses
|
// change addresses
|
||||||
for (int index = 0;
|
Future resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
index < maxNumberOfIndexesToCheck &&
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
|
||||||
changeGapCounter < maxUnusedAddressGap;
|
|
||||||
index += txCountBatchSize) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"index: $index, \t changeGapCounter: $changeGapCounter",
|
|
||||||
level: LogLevel.Info);
|
|
||||||
final changeP2pkhID = "k_$index";
|
|
||||||
final changeP2shID = "s_$index";
|
|
||||||
final changeP2wpkhID = "w_$index";
|
|
||||||
Map<String, String> args = {};
|
|
||||||
final Map<String, dynamic> changeNodes = {};
|
|
||||||
|
|
||||||
for (int j = 0; j < txCountBatchSize; j++) {
|
Future resultChange49 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
// bip44 / P2PKH
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1);
|
||||||
final node44 = await compute(
|
|
||||||
getBip32NodeFromRootWrapper,
|
|
||||||
Tuple4(
|
|
||||||
1,
|
|
||||||
index + j,
|
|
||||||
root,
|
|
||||||
DerivePathType.bip44,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2pkhChangeAddress = P2PKH(
|
|
||||||
data: PaymentData(pubkey: node44.publicKey),
|
|
||||||
network: _network)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
changeNodes.addAll({
|
|
||||||
"${changeP2pkhID}_$j": {
|
|
||||||
"node": node44,
|
|
||||||
"address": p2pkhChangeAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
args.addAll({
|
|
||||||
"${changeP2pkhID}_$j": p2pkhChangeAddress,
|
|
||||||
});
|
|
||||||
|
|
||||||
// bip49 / P2SH
|
Future resultChange84 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
final node49 = await compute(
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1);
|
||||||
getBip32NodeFromRootWrapper,
|
|
||||||
Tuple4(
|
|
||||||
1,
|
|
||||||
index + j,
|
|
||||||
root,
|
|
||||||
DerivePathType.bip49,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2shChangeAddress = P2SH(
|
|
||||||
data: PaymentData(
|
|
||||||
redeem: P2WPKH(
|
|
||||||
data: PaymentData(pubkey: node49.publicKey),
|
|
||||||
network: _network)
|
|
||||||
.data),
|
|
||||||
network: _network)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
changeNodes.addAll({
|
|
||||||
"${changeP2shID}_$j": {
|
|
||||||
"node": node49,
|
|
||||||
"address": p2shChangeAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
args.addAll({
|
|
||||||
"${changeP2shID}_$j": p2shChangeAddress,
|
|
||||||
});
|
|
||||||
|
|
||||||
// bip84 / P2WPKH
|
await Future.wait([
|
||||||
final node84 = await compute(
|
resultReceive44,
|
||||||
getBip32NodeFromRootWrapper,
|
resultReceive49,
|
||||||
Tuple4(
|
resultReceive84,
|
||||||
1,
|
resultChange44,
|
||||||
index + j,
|
resultChange49,
|
||||||
root,
|
resultChange84
|
||||||
DerivePathType.bip84,
|
]);
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2wpkhChangeAddress = P2WPKH(
|
|
||||||
network: _network,
|
|
||||||
data: PaymentData(pubkey: node84.publicKey))
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
changeNodes.addAll({
|
|
||||||
"${changeP2wpkhID}_$j": {
|
|
||||||
"node": node84,
|
|
||||||
"address": p2wpkhChangeAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
args.addAll({
|
|
||||||
"${changeP2wpkhID}_$j": p2wpkhChangeAddress,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// get address tx counts
|
p2pkhReceiveAddressArray =
|
||||||
final counts = await _getBatchTxCount(addresses: args);
|
(await resultReceive44)['addressArray'] as List<String>;
|
||||||
|
p2pkhReceiveIndex = (await resultReceive44)['index'] as int;
|
||||||
|
p2pkhReceiveDerivations = (await resultReceive44)['derivations']
|
||||||
|
as Map<String, Map<String, String>>;
|
||||||
|
|
||||||
// check and add appropriate addresses
|
p2shReceiveAddressArray =
|
||||||
for (int k = 0; k < txCountBatchSize; k++) {
|
(await resultReceive49)['addressArray'] as List<String>;
|
||||||
int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!;
|
p2shReceiveIndex = (await resultReceive49)['index'] as int;
|
||||||
if (p2pkhTxCount > 0) {
|
p2shReceiveDerivations = (await resultReceive49)['derivations']
|
||||||
final node = changeNodes["${changeP2pkhID}_$k"];
|
as Map<String, Map<String, String>>;
|
||||||
// add address to array
|
|
||||||
p2pkhChangeAddressArray.add(node["address"] as String);
|
|
||||||
// set current index
|
|
||||||
p2pkhChangeIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
changeGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2pkhChangeDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int p2shTxCount = counts["${changeP2shID}_$k"]!;
|
p2wpkhReceiveAddressArray =
|
||||||
if (p2shTxCount > 0) {
|
(await resultReceive84)['addressArray'] as List<String>;
|
||||||
final node = changeNodes["${changeP2shID}_$k"];
|
p2wpkhReceiveIndex = (await resultReceive84)['index'] as int;
|
||||||
// add address to array
|
p2wpkhReceiveDerivations = (await resultReceive84)['derivations']
|
||||||
p2shChangeAddressArray.add(node["address"] as String);
|
as Map<String, Map<String, String>>;
|
||||||
// set current index
|
|
||||||
p2shChangeIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
changeGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2shChangeDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int p2wpkhTxCount = counts["${changeP2wpkhID}_$k"]!;
|
p2pkhChangeAddressArray =
|
||||||
if (p2wpkhTxCount > 0) {
|
(await resultChange44)['addressArray'] as List<String>;
|
||||||
final node = changeNodes["${changeP2wpkhID}_$k"];
|
p2pkhChangeIndex = (await resultChange44)['index'] as int;
|
||||||
// add address to array
|
p2pkhChangeDerivations = (await resultChange44)['derivations']
|
||||||
p2wpkhChangeAddressArray.add(node["address"] as String);
|
as Map<String, Map<String, String>>;
|
||||||
// set current index
|
|
||||||
p2wpkhChangeIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
changeGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2wpkhChangeDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// increase counter when no tx history found
|
p2shChangeAddressArray =
|
||||||
if (p2wpkhTxCount == 0 && p2pkhTxCount == 0 && p2shTxCount == 0) {
|
(await resultChange49)['addressArray'] as List<String>;
|
||||||
changeGapCounter++;
|
p2shChangeIndex = (await resultChange49)['index'] as int;
|
||||||
}
|
p2shChangeDerivations = (await resultChange49)['derivations']
|
||||||
}
|
as Map<String, Map<String, String>>;
|
||||||
}
|
|
||||||
|
p2wpkhChangeAddressArray =
|
||||||
|
(await resultChange84)['addressArray'] as List<String>;
|
||||||
|
p2wpkhChangeIndex = (await resultChange84)['index'] as int;
|
||||||
|
p2wpkhChangeDerivations = (await resultChange84)['derivations']
|
||||||
|
as Map<String, Map<String, String>>;
|
||||||
|
|
||||||
// save the derivations (if any)
|
// save the derivations (if any)
|
||||||
if (p2pkhReceiveDerivations.isNotEmpty) {
|
if (p2pkhReceiveDerivations.isNotEmpty) {
|
||||||
|
@ -1098,10 +974,12 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
|
||||||
await _checkChangeAddressForTransactions(DerivePathType.bip84);
|
Future changeAddressForTransactions =
|
||||||
|
_checkChangeAddressForTransactions(DerivePathType.bip84);
|
||||||
|
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
||||||
await _checkCurrentReceivingAddressesForTransactions();
|
Future currentReceivingAddressesForTransactions =
|
||||||
|
_checkCurrentReceivingAddressesForTransactions();
|
||||||
|
|
||||||
final newTxData = _fetchTransactionData();
|
final newTxData = _fetchTransactionData();
|
||||||
GlobalEventBus.instance
|
GlobalEventBus.instance
|
||||||
|
@ -1121,7 +999,15 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
GlobalEventBus.instance
|
GlobalEventBus.instance
|
||||||
.fire(RefreshPercentChangedEvent(0.80, walletId));
|
.fire(RefreshPercentChangedEvent(0.80, walletId));
|
||||||
|
|
||||||
await getAllTxsToWatch(await newTxData);
|
Future allTxsToWatch = getAllTxsToWatch(await newTxData);
|
||||||
|
await Future.wait([
|
||||||
|
newTxData,
|
||||||
|
changeAddressForTransactions,
|
||||||
|
currentReceivingAddressesForTransactions,
|
||||||
|
newUtxoData,
|
||||||
|
feeObj,
|
||||||
|
allTxsToWatch,
|
||||||
|
]);
|
||||||
GlobalEventBus.instance
|
GlobalEventBus.instance
|
||||||
.fire(RefreshPercentChangedEvent(0.90, walletId));
|
.fire(RefreshPercentChangedEvent(0.90, walletId));
|
||||||
}
|
}
|
||||||
|
@ -1137,7 +1023,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldAutoSync) {
|
if (shouldAutoSync) {
|
||||||
timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async {
|
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Periodic refresh check for $walletId $walletName in object instance: $hashCode",
|
"Periodic refresh check for $walletId $walletName in object instance: $hashCode",
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
|
@ -1972,7 +1858,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||||
const batchSizeMax = 10;
|
const batchSizeMax = 100;
|
||||||
int batchNumber = 0;
|
int batchNumber = 0;
|
||||||
for (int i = 0; i < allAddresses.length; i++) {
|
for (int i = 0; i < allAddresses.length; i++) {
|
||||||
if (batches[batchNumber] == null) {
|
if (batches[batchNumber] == null) {
|
||||||
|
@ -2357,7 +2243,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||||
final Map<String, String> requestIdToAddressMap = {};
|
final Map<String, String> requestIdToAddressMap = {};
|
||||||
const batchSizeMax = 10;
|
const batchSizeMax = 100;
|
||||||
int batchNumber = 0;
|
int batchNumber = 0;
|
||||||
for (int i = 0; i < allAddresses.length; i++) {
|
for (int i = 0; i < allAddresses.length; i++) {
|
||||||
if (batches[batchNumber] == null) {
|
if (batches[batchNumber] == null) {
|
||||||
|
@ -2404,6 +2290,43 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fastFetch(List<String> allTxHashes) async {
|
||||||
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
|
||||||
|
const futureLimit = 30;
|
||||||
|
List<Future<Map<String, dynamic>>> transactionFutures = [];
|
||||||
|
int currentFutureCount = 0;
|
||||||
|
for (final txHash in allTxHashes) {
|
||||||
|
Future<Map<String, dynamic>> transactionFuture =
|
||||||
|
cachedElectrumXClient.getTransaction(
|
||||||
|
txHash: txHash,
|
||||||
|
verbose: true,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
transactionFutures.add(transactionFuture);
|
||||||
|
currentFutureCount++;
|
||||||
|
if (currentFutureCount > futureLimit) {
|
||||||
|
currentFutureCount = 0;
|
||||||
|
await Future.wait(transactionFutures);
|
||||||
|
for (final fTx in transactionFutures) {
|
||||||
|
final tx = await fTx;
|
||||||
|
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentFutureCount != 0) {
|
||||||
|
currentFutureCount = 0;
|
||||||
|
await Future.wait(transactionFutures);
|
||||||
|
for (final fTx in transactionFutures) {
|
||||||
|
final tx = await fTx;
|
||||||
|
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
Future<TransactionData> _fetchTransactionData() async {
|
Future<TransactionData> _fetchTransactionData() async {
|
||||||
final List<String> allAddresses = await _fetchAllOwnAddresses();
|
final List<String> allAddresses = await _fetchAllOwnAddresses();
|
||||||
|
|
||||||
|
@ -2451,6 +2374,11 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> hashes = {};
|
||||||
|
for (var element in allTxHashes) {
|
||||||
|
hashes.add(element['tx_hash'] as String);
|
||||||
|
}
|
||||||
|
await fastFetch(hashes.toList());
|
||||||
List<Map<String, dynamic>> allTransactions = [];
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
|
||||||
for (final txHash in allTxHashes) {
|
for (final txHash in allTxHashes) {
|
||||||
|
@ -2480,6 +2408,16 @@ class BitcoinWallet extends CoinServiceAPI {
|
||||||
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||||
final List<Map<String, dynamic>> midSortedArray = [];
|
final List<Map<String, dynamic>> midSortedArray = [];
|
||||||
|
|
||||||
|
Set<String> vHashes = {};
|
||||||
|
for (final txObject in allTransactions) {
|
||||||
|
for (int i = 0; i < (txObject["vin"] as List).length; i++) {
|
||||||
|
final input = txObject["vin"]![i] as Map;
|
||||||
|
final prevTxid = input["txid"] as String;
|
||||||
|
vHashes.add(prevTxid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fastFetch(vHashes.toList());
|
||||||
|
|
||||||
for (final txObject in allTransactions) {
|
for (final txObject in allTransactions) {
|
||||||
List<String> sendersArray = [];
|
List<String> sendersArray = [];
|
||||||
List<String> recipientsArray = [];
|
List<String> recipientsArray = [];
|
||||||
|
|
|
@ -346,6 +346,120 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _checkGaps(
|
||||||
|
int maxNumberOfIndexesToCheck,
|
||||||
|
int maxUnusedAddressGap,
|
||||||
|
int txCountBatchSize,
|
||||||
|
bip32.BIP32 root,
|
||||||
|
DerivePathType type,
|
||||||
|
int account) async {
|
||||||
|
List<String> addressArray = [];
|
||||||
|
int returningIndex = -1;
|
||||||
|
Map<String, Map<String, String>> derivations = {};
|
||||||
|
int gapCounter = 0;
|
||||||
|
for (int index = 0;
|
||||||
|
index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap;
|
||||||
|
index += txCountBatchSize) {
|
||||||
|
List<String> iterationsAddressArray = [];
|
||||||
|
Logging.instance.log(
|
||||||
|
"index: $index, \t GapCounter $account ${type.name}: $gapCounter",
|
||||||
|
level: LogLevel.Info);
|
||||||
|
|
||||||
|
final ID = "k_$index";
|
||||||
|
Map<String, String> txCountCallArgs = {};
|
||||||
|
final Map<String, dynamic> receivingNodes = {};
|
||||||
|
|
||||||
|
for (int j = 0; j < txCountBatchSize; j++) {
|
||||||
|
final node = await compute(
|
||||||
|
getBip32NodeFromRootWrapper,
|
||||||
|
Tuple4(
|
||||||
|
account,
|
||||||
|
index + j,
|
||||||
|
root,
|
||||||
|
type,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
String? address;
|
||||||
|
switch (type) {
|
||||||
|
case DerivePathType.bip44:
|
||||||
|
address = P2PKH(
|
||||||
|
data: PaymentData(pubkey: node.publicKey),
|
||||||
|
network: _network)
|
||||||
|
.data
|
||||||
|
.address!;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Exception("No Path type $type exists");
|
||||||
|
}
|
||||||
|
receivingNodes.addAll({
|
||||||
|
"${ID}_$j": {
|
||||||
|
"node": node,
|
||||||
|
"address": address,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
txCountCallArgs.addAll({
|
||||||
|
"${ID}_$j": address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get address tx counts
|
||||||
|
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
|
||||||
|
|
||||||
|
// check and add appropriate addresses
|
||||||
|
for (int k = 0; k < txCountBatchSize; k++) {
|
||||||
|
int count = counts["${ID}_$k"]!;
|
||||||
|
if (count > 0) {
|
||||||
|
final node = receivingNodes["${ID}_$k"];
|
||||||
|
// add address to array
|
||||||
|
addressArray.add(node["address"] as String);
|
||||||
|
iterationsAddressArray.add(node["address"] as String);
|
||||||
|
// set current index
|
||||||
|
returningIndex = index + k;
|
||||||
|
// reset counter
|
||||||
|
gapCounter = 0;
|
||||||
|
// add info to derivations
|
||||||
|
derivations[node["address"] as String] = {
|
||||||
|
"pubKey": Format.uint8listToString(
|
||||||
|
(node["node"] as bip32.BIP32).publicKey),
|
||||||
|
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase counter when no tx history found
|
||||||
|
if (count == 0) {
|
||||||
|
gapCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// cache all the transactions while waiting for the current function to finish.
|
||||||
|
unawaited(getTransactionCacheEarly(iterationsAddressArray));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"addressArray": addressArray,
|
||||||
|
"index": returningIndex,
|
||||||
|
"derivations": derivations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getTransactionCacheEarly(List<String> allAddresses) async {
|
||||||
|
try {
|
||||||
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
|
await _fetchHistory(allAddresses);
|
||||||
|
for (final txHash in allTxHashes) {
|
||||||
|
try {
|
||||||
|
unawaited(cachedElectrumXClient.getTransaction(
|
||||||
|
txHash: txHash["tx_hash"] as String,
|
||||||
|
verbose: true,
|
||||||
|
coin: coin,
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _recoverWalletFromBIP32SeedPhrase({
|
Future<void> _recoverWalletFromBIP32SeedPhrase({
|
||||||
required String mnemonic,
|
required String mnemonic,
|
||||||
int maxUnusedAddressGap = 20,
|
int maxUnusedAddressGap = 20,
|
||||||
|
@ -364,10 +478,6 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
List<String> p2pkhChangeAddressArray = [];
|
List<String> p2pkhChangeAddressArray = [];
|
||||||
int p2pkhChangeIndex = -1;
|
int p2pkhChangeIndex = -1;
|
||||||
|
|
||||||
// The gap limit will be capped at [maxUnusedAddressGap]
|
|
||||||
int receivingGapCounter = 0;
|
|
||||||
int changeGapCounter = 0;
|
|
||||||
|
|
||||||
// actual size is 12 due to p2pkh so 12x1
|
// actual size is 12 due to p2pkh so 12x1
|
||||||
const txCountBatchSize = 12;
|
const txCountBatchSize = 12;
|
||||||
|
|
||||||
|
@ -375,143 +485,31 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
// receiving addresses
|
// receiving addresses
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("checking receiving addresses...", level: LogLevel.Info);
|
.log("checking receiving addresses...", level: LogLevel.Info);
|
||||||
for (int index = 0;
|
Future resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
index < maxNumberOfIndexesToCheck &&
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
|
||||||
receivingGapCounter < maxUnusedAddressGap;
|
|
||||||
index += txCountBatchSize) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"index: $index, \t receivingGapCounter: $receivingGapCounter",
|
|
||||||
level: LogLevel.Info);
|
|
||||||
|
|
||||||
final receivingP2pkhID = "k_$index";
|
|
||||||
Map<String, String> txCountCallArgs = {};
|
|
||||||
final Map<String, dynamic> receivingNodes = {};
|
|
||||||
|
|
||||||
for (int j = 0; j < txCountBatchSize; j++) {
|
|
||||||
// bip44 / P2PKH
|
|
||||||
final node44 = await compute(
|
|
||||||
getBip32NodeFromRootWrapper,
|
|
||||||
Tuple4(
|
|
||||||
0,
|
|
||||||
index + j,
|
|
||||||
root,
|
|
||||||
DerivePathType.bip44,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2pkhReceiveAddress = P2PKH(
|
|
||||||
data: PaymentData(pubkey: node44.publicKey),
|
|
||||||
network: _network)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
receivingNodes.addAll({
|
|
||||||
"${receivingP2pkhID}_$j": {
|
|
||||||
"node": node44,
|
|
||||||
"address": p2pkhReceiveAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
txCountCallArgs.addAll({
|
|
||||||
"${receivingP2pkhID}_$j": p2pkhReceiveAddress,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// get address tx counts
|
|
||||||
final counts = await _getBatchTxCount(addresses: txCountCallArgs);
|
|
||||||
|
|
||||||
// check and add appropriate addresses
|
|
||||||
for (int k = 0; k < txCountBatchSize; k++) {
|
|
||||||
int p2pkhTxCount = counts["${receivingP2pkhID}_$k"]!;
|
|
||||||
if (p2pkhTxCount > 0) {
|
|
||||||
final node = receivingNodes["${receivingP2pkhID}_$k"];
|
|
||||||
// add address to array
|
|
||||||
p2pkhReceiveAddressArray.add(node["address"] as String);
|
|
||||||
// set current index
|
|
||||||
p2pkhReceiveIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
receivingGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2pkhReceiveDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// increase counter when no tx history found
|
|
||||||
if (p2pkhTxCount == 0) {
|
|
||||||
receivingGapCounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("checking change addresses...", level: LogLevel.Info);
|
.log("checking change addresses...", level: LogLevel.Info);
|
||||||
// change addresses
|
// change addresses
|
||||||
for (int index = 0;
|
Future resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||||
index < maxNumberOfIndexesToCheck &&
|
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
|
||||||
changeGapCounter < maxUnusedAddressGap;
|
|
||||||
index += txCountBatchSize) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"index: $index, \t changeGapCounter: $changeGapCounter",
|
|
||||||
level: LogLevel.Info);
|
|
||||||
final changeP2pkhID = "k_$index";
|
|
||||||
Map<String, String> args = {};
|
|
||||||
final Map<String, dynamic> changeNodes = {};
|
|
||||||
|
|
||||||
for (int j = 0; j < txCountBatchSize; j++) {
|
await Future.wait([
|
||||||
// bip44 / P2PKH
|
resultReceive44,
|
||||||
final node44 = await compute(
|
resultChange44,
|
||||||
getBip32NodeFromRootWrapper,
|
]);
|
||||||
Tuple4(
|
|
||||||
1,
|
|
||||||
index + j,
|
|
||||||
root,
|
|
||||||
DerivePathType.bip44,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final p2pkhChangeAddress = P2PKH(
|
|
||||||
data: PaymentData(pubkey: node44.publicKey),
|
|
||||||
network: _network)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
changeNodes.addAll({
|
|
||||||
"${changeP2pkhID}_$j": {
|
|
||||||
"node": node44,
|
|
||||||
"address": p2pkhChangeAddress,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
args.addAll({
|
|
||||||
"${changeP2pkhID}_$j": p2pkhChangeAddress,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// get address tx counts
|
p2pkhReceiveAddressArray =
|
||||||
final counts = await _getBatchTxCount(addresses: args);
|
(await resultReceive44)['addressArray'] as List<String>;
|
||||||
|
p2pkhReceiveIndex = (await resultReceive44)['index'] as int;
|
||||||
|
p2pkhReceiveDerivations = (await resultReceive44)['derivations']
|
||||||
|
as Map<String, Map<String, String>>;
|
||||||
|
|
||||||
// check and add appropriate addresses
|
p2pkhChangeAddressArray =
|
||||||
for (int k = 0; k < txCountBatchSize; k++) {
|
(await resultChange44)['addressArray'] as List<String>;
|
||||||
int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!;
|
p2pkhChangeIndex = (await resultChange44)['index'] as int;
|
||||||
if (p2pkhTxCount > 0) {
|
p2pkhChangeDerivations = (await resultChange44)['derivations']
|
||||||
final node = changeNodes["${changeP2pkhID}_$k"];
|
as Map<String, Map<String, String>>;
|
||||||
// add address to array
|
|
||||||
p2pkhChangeAddressArray.add(node["address"] as String);
|
|
||||||
// set current index
|
|
||||||
p2pkhChangeIndex = index + k;
|
|
||||||
// reset counter
|
|
||||||
changeGapCounter = 0;
|
|
||||||
// add info to derivations
|
|
||||||
p2pkhChangeDerivations[node["address"] as String] = {
|
|
||||||
"pubKey": Format.uint8listToString(
|
|
||||||
(node["node"] as bip32.BIP32).publicKey),
|
|
||||||
"wif": (node["node"] as bip32.BIP32).toWIF(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// increase counter when no tx history found
|
|
||||||
if (p2pkhTxCount == 0) {
|
|
||||||
changeGapCounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the derivations (if any)
|
// save the derivations (if any)
|
||||||
if (p2pkhReceiveDerivations.isNotEmpty) {
|
if (p2pkhReceiveDerivations.isNotEmpty) {
|
||||||
|
@ -815,7 +813,7 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
refreshMutex = false;
|
refreshMutex = false;
|
||||||
|
|
||||||
if (shouldAutoSync) {
|
if (shouldAutoSync) {
|
||||||
timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async {
|
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||||
// chain height check currently broken
|
// chain height check currently broken
|
||||||
// if ((await chainHeight) != (await storedChainHeight)) {
|
// if ((await chainHeight) != (await storedChainHeight)) {
|
||||||
if (await refreshIfThereIsNewData()) {
|
if (await refreshIfThereIsNewData()) {
|
||||||
|
@ -1496,6 +1494,43 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
await _secureStore.write(key: key, value: newReceiveDerivationsString);
|
await _secureStore.write(key: key, value: newReceiveDerivationsString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fastFetch(List<String> allTxHashes) async {
|
||||||
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
|
||||||
|
const futureLimit = 30;
|
||||||
|
List<Future<Map<String, dynamic>>> transactionFutures = [];
|
||||||
|
int currentFutureCount = 0;
|
||||||
|
for (final txHash in allTxHashes) {
|
||||||
|
Future<Map<String, dynamic>> transactionFuture =
|
||||||
|
cachedElectrumXClient.getTransaction(
|
||||||
|
txHash: txHash,
|
||||||
|
verbose: true,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
transactionFutures.add(transactionFuture);
|
||||||
|
currentFutureCount++;
|
||||||
|
if (currentFutureCount > futureLimit) {
|
||||||
|
currentFutureCount = 0;
|
||||||
|
await Future.wait(transactionFutures);
|
||||||
|
for (final fTx in transactionFutures) {
|
||||||
|
final tx = await fTx;
|
||||||
|
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentFutureCount != 0) {
|
||||||
|
currentFutureCount = 0;
|
||||||
|
await Future.wait(transactionFutures);
|
||||||
|
for (final fTx in transactionFutures) {
|
||||||
|
final tx = await fTx;
|
||||||
|
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
Future<UtxoData> _fetchUtxoData() async {
|
Future<UtxoData> _fetchUtxoData() async {
|
||||||
final List<String> allAddresses = await _fetchAllOwnAddresses();
|
final List<String> allAddresses = await _fetchAllOwnAddresses();
|
||||||
|
|
||||||
|
@ -1503,7 +1538,7 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||||
const batchSizeMax = 10;
|
const batchSizeMax = 100;
|
||||||
int batchNumber = 0;
|
int batchNumber = 0;
|
||||||
for (int i = 0; i < allAddresses.length; i++) {
|
for (int i = 0; i < allAddresses.length; i++) {
|
||||||
if (batches[batchNumber] == null) {
|
if (batches[batchNumber] == null) {
|
||||||
|
@ -1872,7 +1907,7 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||||
final Map<String, String> requestIdToAddressMap = {};
|
final Map<String, String> requestIdToAddressMap = {};
|
||||||
const batchSizeMax = 10;
|
const batchSizeMax = 100;
|
||||||
int batchNumber = 0;
|
int batchNumber = 0;
|
||||||
for (int i = 0; i < allAddresses.length; i++) {
|
for (int i = 0; i < allAddresses.length; i++) {
|
||||||
if (batches[batchNumber] == null) {
|
if (batches[batchNumber] == null) {
|
||||||
|
@ -1954,6 +1989,11 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> hashes = [];
|
||||||
|
for (var element in allTxHashes) {
|
||||||
|
hashes.add(element['tx_hash'] as String);
|
||||||
|
}
|
||||||
|
await fastFetch(hashes);
|
||||||
List<Map<String, dynamic>> allTransactions = [];
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
|
||||||
for (final txHash in allTxHashes) {
|
for (final txHash in allTxHashes) {
|
||||||
|
@ -1983,6 +2023,16 @@ class DogecoinWallet extends CoinServiceAPI {
|
||||||
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||||
final List<Map<String, dynamic>> midSortedArray = [];
|
final List<Map<String, dynamic>> midSortedArray = [];
|
||||||
|
|
||||||
|
Set<String> vHashes = {};
|
||||||
|
for (final txObject in allTransactions) {
|
||||||
|
for (int i = 0; i < (txObject["vin"] as List).length; i++) {
|
||||||
|
final input = txObject["vin"]![i] as Map;
|
||||||
|
final prevTxid = input["txid"] as String;
|
||||||
|
vHashes.add(prevTxid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fastFetch(vHashes.toList());
|
||||||
|
|
||||||
for (final txObject in allTransactions) {
|
for (final txObject in allTransactions) {
|
||||||
List<String> sendersArray = [];
|
List<String> sendersArray = [];
|
||||||
List<String> recipientsArray = [];
|
List<String> recipientsArray = [];
|
||||||
|
|
|
@ -43,6 +43,7 @@ import 'package:stackwallet/utilities/format.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
import 'package:stackwallet/utilities/prefs.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
const DUST_LIMIT = 1000;
|
const DUST_LIMIT = 1000;
|
||||||
const MINIMUM_CONFIRMATIONS = 1;
|
const MINIMUM_CONFIRMATIONS = 1;
|
||||||
|
@ -145,25 +146,17 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
||||||
final setDataMap = arguments['setDataMap'] as Map;
|
final setDataMap = arguments['setDataMap'] as Map;
|
||||||
final usedSerialNumbers = arguments['usedSerialNumbers'] as List?;
|
final usedSerialNumbers = arguments['usedSerialNumbers'] as List?;
|
||||||
final mnemonic = arguments['mnemonic'] as String;
|
final mnemonic = arguments['mnemonic'] as String;
|
||||||
final transactionData =
|
|
||||||
arguments['transactionData'] as models.TransactionData;
|
|
||||||
final currency = arguments['currency'] as String;
|
|
||||||
final coin = arguments['coin'] as Coin;
|
final coin = arguments['coin'] as Coin;
|
||||||
final network = arguments['network'] as NetworkType?;
|
final network = arguments['network'] as NetworkType?;
|
||||||
final currentPrice = arguments['currentPrice'] as Decimal;
|
|
||||||
final locale = arguments['locale'] as String;
|
|
||||||
if (!(usedSerialNumbers == null || network == null)) {
|
if (!(usedSerialNumbers == null || network == null)) {
|
||||||
var restoreData = await isolateRestore(
|
var restoreData = await isolateRestore(
|
||||||
mnemonic,
|
mnemonic,
|
||||||
transactionData,
|
coin,
|
||||||
currency,
|
latestSetId,
|
||||||
coin,
|
setDataMap,
|
||||||
latestSetId,
|
usedSerialNumbers,
|
||||||
setDataMap,
|
network,
|
||||||
usedSerialNumbers,
|
);
|
||||||
network,
|
|
||||||
currentPrice,
|
|
||||||
locale);
|
|
||||||
sendPort.send(restoreData);
|
sendPort.send(restoreData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -240,15 +233,11 @@ Future<Map<String, dynamic>> isolateDerive(
|
||||||
|
|
||||||
Future<Map<String, dynamic>> isolateRestore(
|
Future<Map<String, dynamic>> isolateRestore(
|
||||||
String mnemonic,
|
String mnemonic,
|
||||||
models.TransactionData data,
|
|
||||||
String currency,
|
|
||||||
Coin coin,
|
Coin coin,
|
||||||
int _latestSetId,
|
int _latestSetId,
|
||||||
Map<dynamic, dynamic> _setDataMap,
|
Map<dynamic, dynamic> _setDataMap,
|
||||||
List<dynamic> _usedSerialNumbers,
|
List<dynamic> _usedSerialNumbers,
|
||||||
NetworkType network,
|
NetworkType network,
|
||||||
Decimal currentPrice,
|
|
||||||
String locale,
|
|
||||||
) async {
|
) async {
|
||||||
List<int> jindexes = [];
|
List<int> jindexes = [];
|
||||||
List<Map<dynamic, LelantusCoin>> lelantusCoins = [];
|
List<Map<dynamic, LelantusCoin>> lelantusCoins = [];
|
||||||
|
@ -373,6 +362,20 @@ Future<Map<String, dynamic>> isolateRestore(
|
||||||
result['_lelantus_coins'] = lelantusCoins;
|
result['_lelantus_coins'] = lelantusCoins;
|
||||||
result['mintIndex'] = lastFoundIndex + 1;
|
result['mintIndex'] = lastFoundIndex + 1;
|
||||||
result['jindex'] = jindexes;
|
result['jindex'] = jindexes;
|
||||||
|
result['spendTxIds'] = spendTxIds;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<dynamic, dynamic>> staticProcessRestore(
|
||||||
|
models.TransactionData data,
|
||||||
|
Map<dynamic, dynamic> result,
|
||||||
|
) async {
|
||||||
|
List<dynamic>? _l = result['_lelantus_coins'] as List?;
|
||||||
|
final List<Map<dynamic, LelantusCoin>> lelantusCoins = [];
|
||||||
|
for (var el in _l ?? []) {
|
||||||
|
lelantusCoins.add({el.keys.first: el.values.first as LelantusCoin});
|
||||||
|
}
|
||||||
|
|
||||||
// Edit the receive transactions with the mint fees.
|
// Edit the receive transactions with the mint fees.
|
||||||
Map<String, models.Transaction> editedTransactions =
|
Map<String, models.Transaction> editedTransactions =
|
||||||
|
@ -437,7 +440,6 @@ Future<Map<String, dynamic>> isolateRestore(
|
||||||
(value.height == -1 && !value.confirmedStatus));
|
(value.height == -1 && !value.confirmedStatus));
|
||||||
|
|
||||||
result['newTxMap'] = transactionMap;
|
result['newTxMap'] = transactionMap;
|
||||||
result['spendTxIds'] = spendTxIds;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2193,7 +2195,7 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
final newTxData = _fetchTransactionData();
|
final newTxData = _fetchTransactionData();
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.35, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.35, walletId));
|
||||||
|
|
||||||
final FeeObject feeObj = await _getFees();
|
final feeObj = _getFees();
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
|
||||||
|
|
||||||
_utxoData = Future(() => newUtxoData);
|
_utxoData = Future(() => newUtxoData);
|
||||||
|
@ -2217,9 +2219,6 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.95, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.95, walletId));
|
||||||
|
|
||||||
final maxFee = await _fetchMaxFee();
|
|
||||||
_maxFee = Future(() => maxFee);
|
|
||||||
|
|
||||||
var txData = (await _txnData);
|
var txData = (await _txnData);
|
||||||
var lTxData = (await lelantusTransactionData);
|
var lTxData = (await lelantusTransactionData);
|
||||||
await getAllTxsToWatch(txData, lTxData);
|
await getAllTxsToWatch(txData, lTxData);
|
||||||
|
@ -2236,7 +2235,7 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
refreshMutex = false;
|
refreshMutex = false;
|
||||||
|
|
||||||
if (isActive || shouldAutoSync) {
|
if (isActive || shouldAutoSync) {
|
||||||
timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async {
|
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||||
bool shouldNotify = await refreshIfThereIsNewData();
|
bool shouldNotify = await refreshIfThereIsNewData();
|
||||||
if (shouldNotify) {
|
if (shouldNotify) {
|
||||||
await refresh();
|
await refresh();
|
||||||
|
@ -2744,7 +2743,7 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _refreshLelantusData() async {
|
Future<models.TransactionData> _refreshLelantusData() async {
|
||||||
final List<Map<dynamic, LelantusCoin>> lelantusCoins = getLelantusCoinMap();
|
final List<Map<dynamic, LelantusCoin>> lelantusCoins = getLelantusCoinMap();
|
||||||
final jindexes =
|
final jindexes =
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: 'jindex') as List?;
|
DB.instance.get<dynamic>(boxName: walletId, key: 'jindex') as List?;
|
||||||
|
@ -2826,6 +2825,7 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
_lelantusTransactionData = Future(() => newTxData);
|
_lelantusTransactionData = Future(() => newTxData);
|
||||||
await DB.instance.put<dynamic>(
|
await DB.instance.put<dynamic>(
|
||||||
boxName: walletId, key: 'latest_lelantus_tx_model', value: newTxData);
|
boxName: walletId, key: 'latest_lelantus_tx_model', value: newTxData);
|
||||||
|
return newTxData;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _getMintHex(int amount, int index) async {
|
Future<String> _getMintHex(int amount, int index) async {
|
||||||
|
@ -3122,24 +3122,44 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
List<String> allAddresses) async {
|
List<String> allAddresses) async {
|
||||||
try {
|
try {
|
||||||
List<Map<String, dynamic>> allTxHashes = [];
|
List<Map<String, dynamic>> allTxHashes = [];
|
||||||
// int latestTxnBlockHeight = 0;
|
|
||||||
|
|
||||||
for (final address in allAddresses) {
|
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||||
final scripthash = AddressUtils.convertToScriptHash(address, _network);
|
final Map<String, String> requestIdToAddressMap = {};
|
||||||
final txs = await electrumXClient.getHistory(scripthash: scripthash);
|
const batchSizeMax = 100;
|
||||||
for (final map in txs) {
|
int batchNumber = 0;
|
||||||
if (!allTxHashes.contains(map)) {
|
for (int i = 0; i < allAddresses.length; i++) {
|
||||||
map['address'] = address;
|
if (batches[batchNumber] == null) {
|
||||||
allTxHashes.add(map);
|
batches[batchNumber] = {};
|
||||||
|
}
|
||||||
|
final scripthash =
|
||||||
|
AddressUtils.convertToScriptHash(allAddresses[i], _network);
|
||||||
|
final id = const Uuid().v1();
|
||||||
|
requestIdToAddressMap[id] = allAddresses[i];
|
||||||
|
batches[batchNumber]!.addAll({
|
||||||
|
id: [scripthash]
|
||||||
|
});
|
||||||
|
if (i % batchSizeMax == batchSizeMax - 1) {
|
||||||
|
batchNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < batches.length; i++) {
|
||||||
|
final response =
|
||||||
|
await _electrumXClient.getBatchHistory(args: batches[i]!);
|
||||||
|
for (final entry in response.entries) {
|
||||||
|
for (int j = 0; j < entry.value.length; j++) {
|
||||||
|
entry.value[j]["address"] = requestIdToAddressMap[entry.key];
|
||||||
|
if (!allTxHashes.contains(entry.value[j])) {
|
||||||
|
allTxHashes.add(entry.value[j]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allTxHashes;
|
return allTxHashes;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Exception caught in _fetchHistory(): $e\n$s",
|
Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error);
|
||||||
level: LogLevel.Error);
|
rethrow;
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3178,20 +3198,11 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Map<String, dynamic>> allTransactions = [];
|
List<String> hashes = [];
|
||||||
|
for (var element in allTxHashes) {
|
||||||
for (final txHash in allTxHashes) {
|
hashes.add(element['tx_hash'] as String);
|
||||||
final tx = await cachedElectrumXClient.getTransaction(
|
|
||||||
txHash: txHash["tx_hash"] as String,
|
|
||||||
verbose: true,
|
|
||||||
coin: coin,
|
|
||||||
);
|
|
||||||
// delete unused large parts
|
|
||||||
tx.remove("hex");
|
|
||||||
tx.remove("lelantusData");
|
|
||||||
|
|
||||||
allTransactions.add(tx);
|
|
||||||
}
|
}
|
||||||
|
List<Map<String, dynamic>> allTransactions = await fastFetch(hashes);
|
||||||
|
|
||||||
Logging.instance.log("allTransactions length: ${allTransactions.length}",
|
Logging.instance.log("allTransactions length: ${allTransactions.length}",
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
|
@ -3441,81 +3452,83 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UtxoData> _fetchUtxoData() async {
|
Future<UtxoData> _fetchUtxoData() async {
|
||||||
final List<String> allAddresses = [];
|
final List<String> allAddresses = await _fetchAllOwnAddresses();
|
||||||
final receivingAddresses =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: 'receivingAddresses')
|
|
||||||
as List<dynamic>;
|
|
||||||
final changeAddresses =
|
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: 'changeAddresses')
|
|
||||||
as List<dynamic>;
|
|
||||||
|
|
||||||
for (var i = 0; i < receivingAddresses.length; i++) {
|
|
||||||
if (!allAddresses.contains(receivingAddresses[i])) {
|
|
||||||
allAddresses.add(receivingAddresses[i] as String);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var i = 0; i < changeAddresses.length; i++) {
|
|
||||||
if (!allAddresses.contains(changeAddresses[i])) {
|
|
||||||
allAddresses.add(changeAddresses[i] as String);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final utxoData = <List<Map<String, dynamic>>>[];
|
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
|
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||||
|
const batchSizeMax = 100;
|
||||||
|
int batchNumber = 0;
|
||||||
for (int i = 0; i < allAddresses.length; i++) {
|
for (int i = 0; i < allAddresses.length; i++) {
|
||||||
|
if (batches[batchNumber] == null) {
|
||||||
|
batches[batchNumber] = {};
|
||||||
|
}
|
||||||
final scripthash =
|
final scripthash =
|
||||||
AddressUtils.convertToScriptHash(allAddresses[i], _network);
|
AddressUtils.convertToScriptHash(allAddresses[i], _network);
|
||||||
final utxos = await electrumXClient.getUTXOs(scripthash: scripthash);
|
batches[batchNumber]!.addAll({
|
||||||
if (utxos.isNotEmpty) {
|
scripthash: [scripthash]
|
||||||
utxoData.add(utxos);
|
});
|
||||||
|
if (i % batchSizeMax == batchSizeMax - 1) {
|
||||||
|
batchNumber++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Decimal currentPrice = await firoPrice;
|
for (int i = 0; i < batches.length; i++) {
|
||||||
|
final response =
|
||||||
|
await _electrumXClient.getBatchUTXOs(args: batches[i]!);
|
||||||
|
for (final entry in response.entries) {
|
||||||
|
if (entry.value.isNotEmpty) {
|
||||||
|
fetchedUtxoList.add(entry.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final priceData =
|
||||||
|
await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||||
|
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||||
final List<Map<String, dynamic>> outputArray = [];
|
final List<Map<String, dynamic>> outputArray = [];
|
||||||
int satoshiBalance = 0;
|
int satoshiBalance = 0;
|
||||||
int satoshiBalancePending = 0;
|
int satoshiBalancePending = 0;
|
||||||
|
|
||||||
for (int i = 0; i < utxoData.length; i++) {
|
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||||
for (int j = 0; j < utxoData[i].length; j++) {
|
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||||
int value = utxoData[i][j]["value"] as int;
|
int value = fetchedUtxoList[i][j]["value"] as int;
|
||||||
satoshiBalance += value;
|
satoshiBalance += value;
|
||||||
|
|
||||||
final txn = await cachedElectrumXClient.getTransaction(
|
final txn = await cachedElectrumXClient.getTransaction(
|
||||||
txHash: utxoData[i][j]["tx_hash"] as String,
|
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||||
verbose: true,
|
verbose: true,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
final Map<String, dynamic> tx = {};
|
final Map<String, dynamic> utxo = {};
|
||||||
final int confirmations = txn["confirmations"] as int? ?? 0;
|
final int confirmations = txn["confirmations"] as int? ?? 0;
|
||||||
final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS;
|
final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS;
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
satoshiBalancePending += value;
|
satoshiBalancePending += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx["txid"] = txn["txid"];
|
utxo["txid"] = txn["txid"];
|
||||||
tx["vout"] = utxoData[i][j]["tx_pos"];
|
utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"];
|
||||||
tx["value"] = value;
|
utxo["value"] = value;
|
||||||
|
|
||||||
tx["status"] = <String, dynamic>{};
|
utxo["status"] = <String, dynamic>{};
|
||||||
tx["status"]["confirmed"] = confirmed;
|
utxo["status"]["confirmed"] = confirmed;
|
||||||
tx["status"]["confirmations"] = confirmations;
|
utxo["status"]["confirmations"] = confirmations;
|
||||||
tx["status"]["confirmed"] =
|
utxo["status"]["confirmed"] =
|
||||||
txn["confirmations"] == null ? false : txn["confirmations"] > 0;
|
txn["confirmations"] == null ? false : txn["confirmations"] > 0;
|
||||||
|
|
||||||
tx["status"]["block_height"] = txn["height"];
|
utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"];
|
||||||
tx["status"]["block_hash"] = txn["blockhash"];
|
utxo["status"]["block_hash"] = txn["blockhash"];
|
||||||
tx["status"]["block_time"] = txn["blocktime"];
|
utxo["status"]["block_time"] = txn["blocktime"];
|
||||||
|
|
||||||
final fiatValue = ((Decimal.fromInt(value) * currentPrice) /
|
final fiatValue = ((Decimal.fromInt(value) * currentPrice) /
|
||||||
Decimal.fromInt(Constants.satsPerCoin))
|
Decimal.fromInt(Constants.satsPerCoin))
|
||||||
.toDecimal(scaleOnInfinitePrecision: 2);
|
.toDecimal(scaleOnInfinitePrecision: 2);
|
||||||
tx["rawWorth"] = fiatValue;
|
utxo["rawWorth"] = fiatValue;
|
||||||
tx["fiatWorth"] = fiatValue.toString();
|
utxo["fiatWorth"] = fiatValue.toString();
|
||||||
tx["is_coinbase"] = txn['vin'][0]['coinbase'] != null;
|
utxo["is_coinbase"] = txn['vin'][0]['coinbase'] != null;
|
||||||
outputArray.add(tx);
|
outputArray.add(utxo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3538,13 +3551,19 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
final dataModel = UtxoData.fromJson(result);
|
final dataModel = UtxoData.fromJson(result);
|
||||||
|
|
||||||
final List<UtxoObject> allOutputs = dataModel.unspentOutputArray;
|
final List<UtxoObject> allOutputs = dataModel.unspentOutputArray;
|
||||||
// Logging.instance.log('Outputs fetched: $allOutputs');
|
Logging.instance
|
||||||
|
.log('Outputs fetched: $allOutputs', level: LogLevel.Info);
|
||||||
await _sortOutputs(allOutputs);
|
await _sortOutputs(allOutputs);
|
||||||
await DB.instance.put<dynamic>(
|
await DB.instance.put<dynamic>(
|
||||||
boxName: walletId, key: 'latest_utxo_model', value: dataModel);
|
boxName: walletId, key: 'latest_utxo_model', value: dataModel);
|
||||||
|
// await DB.instance.put<dynamic>(
|
||||||
|
// boxName: walletId,
|
||||||
|
// key: 'totalBalance',
|
||||||
|
// value: dataModel.satoshiBalance);
|
||||||
return dataModel;
|
return dataModel;
|
||||||
} catch (e) {
|
} catch (e, s) {
|
||||||
// Logging.instance.log("Output fetch unsuccessful: $e\n$s");
|
Logging.instance
|
||||||
|
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||||
final latestTxModel =
|
final latestTxModel =
|
||||||
DB.instance.get<dynamic>(boxName: walletId, key: 'latest_utxo_model')
|
DB.instance.get<dynamic>(boxName: walletId, key: 'latest_utxo_model')
|
||||||
as models.UtxoData?;
|
as models.UtxoData?;
|
||||||
|
@ -4056,6 +4075,135 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
|
|
||||||
bool longMutex = false;
|
bool longMutex = false;
|
||||||
|
|
||||||
|
Future<Map<int, dynamic>> getSetDataMap(int latestSetId) async {
|
||||||
|
final Map<int, dynamic> setDataMap = {};
|
||||||
|
final anonymitySets = await fetchAnonymitySets();
|
||||||
|
for (int setId = 1; setId <= latestSetId; setId++) {
|
||||||
|
final setData = anonymitySets
|
||||||
|
.firstWhere((element) => element["setId"] == setId, orElse: () => {});
|
||||||
|
|
||||||
|
if (setData.isNotEmpty) {
|
||||||
|
setDataMap[setId] = setData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _makeDerivations(
|
||||||
|
String suppliedMnemonic, int maxUnusedAddressGap) async {
|
||||||
|
List<String> receivingAddressArray = [];
|
||||||
|
List<String> changeAddressArray = [];
|
||||||
|
|
||||||
|
int receivingIndex = -1;
|
||||||
|
int changeIndex = -1;
|
||||||
|
|
||||||
|
// The gap limit will be capped at 20
|
||||||
|
int receivingGapCounter = 0;
|
||||||
|
int changeGapCounter = 0;
|
||||||
|
|
||||||
|
await fillAddresses(suppliedMnemonic,
|
||||||
|
numberOfThreads: Platform.numberOfProcessors - isolates.length - 1);
|
||||||
|
|
||||||
|
final receiveDerivationsString =
|
||||||
|
await _secureStore.read(key: "${walletId}_receiveDerivations");
|
||||||
|
final changeDerivationsString =
|
||||||
|
await _secureStore.read(key: "${walletId}_changeDerivations");
|
||||||
|
|
||||||
|
final receiveDerivations = Map<String, dynamic>.from(
|
||||||
|
jsonDecode(receiveDerivationsString ?? "{}") as Map);
|
||||||
|
final changeDerivations = Map<String, dynamic>.from(
|
||||||
|
jsonDecode(changeDerivationsString ?? "{}") as Map);
|
||||||
|
|
||||||
|
// log("rcv: $receiveDerivations");
|
||||||
|
// log("chg: $changeDerivations");
|
||||||
|
|
||||||
|
// Deriving and checking for receiving addresses
|
||||||
|
for (var i = 0; i < receiveDerivations.length; i++) {
|
||||||
|
// Break out of loop when receivingGapCounter hits maxUnusedAddressGap
|
||||||
|
// Same gap limit for change as for receiving, breaks when it hits maxUnusedAddressGap
|
||||||
|
if (receivingGapCounter >= maxUnusedAddressGap &&
|
||||||
|
changeGapCounter >= maxUnusedAddressGap) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final receiveDerivation = receiveDerivations["$i"];
|
||||||
|
final address = receiveDerivation['address'] as String;
|
||||||
|
|
||||||
|
final changeDerivation = changeDerivations["$i"];
|
||||||
|
final _address = changeDerivation['address'] as String;
|
||||||
|
Future<int>? futureNumTxs;
|
||||||
|
Future<int>? _futureNumTxs;
|
||||||
|
if (receivingGapCounter < maxUnusedAddressGap) {
|
||||||
|
futureNumTxs = _getReceivedTxCount(address: address);
|
||||||
|
}
|
||||||
|
if (changeGapCounter < maxUnusedAddressGap) {
|
||||||
|
_futureNumTxs = _getReceivedTxCount(address: _address);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (futureNumTxs != null) {
|
||||||
|
int numTxs = await futureNumTxs;
|
||||||
|
if (numTxs >= 1) {
|
||||||
|
receivingIndex = i;
|
||||||
|
receivingAddressArray.add(address);
|
||||||
|
} else if (numTxs == 0) {
|
||||||
|
receivingGapCounter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
|
||||||
|
level: LogLevel.Error);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (_futureNumTxs != null) {
|
||||||
|
int numTxs = await _futureNumTxs;
|
||||||
|
if (numTxs >= 1) {
|
||||||
|
changeIndex = i;
|
||||||
|
changeAddressArray.add(_address);
|
||||||
|
} else if (numTxs == 0) {
|
||||||
|
changeGapCounter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
|
||||||
|
level: LogLevel.Error);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If restoring a wallet that never received any funds, then set receivingArray manually
|
||||||
|
// If we didn't do this, it'd store an empty array
|
||||||
|
if (receivingIndex == -1) {
|
||||||
|
final String receivingAddress = await _generateAddressForChain(0, 0);
|
||||||
|
receivingAddressArray.add(receivingAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If restoring a wallet that never sent any funds with change, then set changeArray
|
||||||
|
// manually. If we didn't do this, it'd store an empty array.
|
||||||
|
if (changeIndex == -1) {
|
||||||
|
final String changeAddress = await _generateAddressForChain(1, 0);
|
||||||
|
changeAddressArray.add(changeAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: 'receivingAddresses',
|
||||||
|
value: receivingAddressArray);
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId, key: 'changeAddresses', value: changeAddressArray);
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: 'receivingIndex',
|
||||||
|
value: receivingIndex == -1 ? 0 : receivingIndex);
|
||||||
|
await DB.instance.put<dynamic>(
|
||||||
|
boxName: walletId,
|
||||||
|
key: 'changeIndex',
|
||||||
|
value: changeIndex == -1 ? 0 : changeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/// Recovers wallet from [suppliedMnemonic]. Expects a valid mnemonic.
|
/// Recovers wallet from [suppliedMnemonic]. Expects a valid mnemonic.
|
||||||
Future<void> _recoverWalletFromBIP32SeedPhrase(
|
Future<void> _recoverWalletFromBIP32SeedPhrase(
|
||||||
String suppliedMnemonic, int maxUnusedAddressGap) async {
|
String suppliedMnemonic, int maxUnusedAddressGap) async {
|
||||||
|
@ -4063,137 +4211,20 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("PROCESSORS ${Platform.numberOfProcessors}", level: LogLevel.Info);
|
.log("PROCESSORS ${Platform.numberOfProcessors}", level: LogLevel.Info);
|
||||||
try {
|
try {
|
||||||
final Map<int, dynamic> setDataMap = {};
|
|
||||||
final latestSetId = await getLatestSetId();
|
final latestSetId = await getLatestSetId();
|
||||||
final anonymitySets = await fetchAnonymitySets();
|
final setDataMap = getSetDataMap(latestSetId);
|
||||||
for (int setId = 1; setId <= latestSetId; setId++) {
|
|
||||||
final setData = anonymitySets.firstWhere(
|
|
||||||
(element) => element["setId"] == setId,
|
|
||||||
orElse: () => {});
|
|
||||||
|
|
||||||
if (setData.isNotEmpty) {
|
|
||||||
setDataMap[setId] = setData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final usedSerialNumbers = getUsedCoinSerials();
|
final usedSerialNumbers = getUsedCoinSerials();
|
||||||
|
final makeDerivations =
|
||||||
|
_makeDerivations(suppliedMnemonic, maxUnusedAddressGap);
|
||||||
|
|
||||||
List<String> receivingAddressArray = [];
|
|
||||||
List<String> changeAddressArray = [];
|
|
||||||
|
|
||||||
int receivingIndex = -1;
|
|
||||||
int changeIndex = -1;
|
|
||||||
|
|
||||||
// The gap limit will be capped at 20
|
|
||||||
int receivingGapCounter = 0;
|
|
||||||
int changeGapCounter = 0;
|
|
||||||
|
|
||||||
await fillAddresses(suppliedMnemonic,
|
|
||||||
numberOfThreads: Platform.numberOfProcessors - isolates.length - 1);
|
|
||||||
|
|
||||||
final receiveDerivationsString =
|
|
||||||
await _secureStore.read(key: "${walletId}_receiveDerivations");
|
|
||||||
final changeDerivationsString =
|
|
||||||
await _secureStore.read(key: "${walletId}_changeDerivations");
|
|
||||||
|
|
||||||
final receiveDerivations = Map<String, dynamic>.from(
|
|
||||||
jsonDecode(receiveDerivationsString ?? "{}") as Map);
|
|
||||||
final changeDerivations = Map<String, dynamic>.from(
|
|
||||||
jsonDecode(changeDerivationsString ?? "{}") as Map);
|
|
||||||
|
|
||||||
// log("rcv: $receiveDerivations");
|
|
||||||
// log("chg: $changeDerivations");
|
|
||||||
|
|
||||||
// Deriving and checking for receiving addresses
|
|
||||||
for (var i = 0; i < receiveDerivations.length; i++) {
|
|
||||||
// Break out of loop when receivingGapCounter hits maxUnusedAddressGap
|
|
||||||
// Same gap limit for change as for receiving, breaks when it hits maxUnusedAddressGap
|
|
||||||
if (receivingGapCounter >= maxUnusedAddressGap &&
|
|
||||||
changeGapCounter >= maxUnusedAddressGap) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
final receiveDerivation = receiveDerivations["$i"];
|
|
||||||
final address = receiveDerivation['address'] as String;
|
|
||||||
|
|
||||||
final changeDerivation = changeDerivations["$i"];
|
|
||||||
final _address = changeDerivation['address'] as String;
|
|
||||||
Future<int>? futureNumTxs;
|
|
||||||
Future<int>? _futureNumTxs;
|
|
||||||
if (receivingGapCounter < maxUnusedAddressGap) {
|
|
||||||
futureNumTxs = _getReceivedTxCount(address: address);
|
|
||||||
}
|
|
||||||
if (changeGapCounter < maxUnusedAddressGap) {
|
|
||||||
_futureNumTxs = _getReceivedTxCount(address: _address);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (futureNumTxs != null) {
|
|
||||||
int numTxs = await futureNumTxs;
|
|
||||||
if (numTxs >= 1) {
|
|
||||||
receivingIndex = i;
|
|
||||||
receivingAddressArray.add(address);
|
|
||||||
} else if (numTxs == 0) {
|
|
||||||
receivingGapCounter += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
|
|
||||||
level: LogLevel.Error);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (_futureNumTxs != null) {
|
|
||||||
int numTxs = await _futureNumTxs;
|
|
||||||
if (numTxs >= 1) {
|
|
||||||
changeIndex = i;
|
|
||||||
changeAddressArray.add(_address);
|
|
||||||
} else if (numTxs == 0) {
|
|
||||||
changeGapCounter += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s",
|
|
||||||
level: LogLevel.Error);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If restoring a wallet that never received any funds, then set receivingArray manually
|
|
||||||
// If we didn't do this, it'd store an empty array
|
|
||||||
if (receivingIndex == -1) {
|
|
||||||
final String receivingAddress = await _generateAddressForChain(0, 0);
|
|
||||||
receivingAddressArray.add(receivingAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If restoring a wallet that never sent any funds with change, then set changeArray
|
|
||||||
// manually. If we didn't do this, it'd store an empty array.
|
|
||||||
if (changeIndex == -1) {
|
|
||||||
final String changeAddress = await _generateAddressForChain(1, 0);
|
|
||||||
changeAddressArray.add(changeAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'receivingAddresses',
|
|
||||||
value: receivingAddressArray);
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId, key: 'changeAddresses', value: changeAddressArray);
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'receivingIndex',
|
|
||||||
value: receivingIndex == -1 ? 0 : receivingIndex);
|
|
||||||
await DB.instance.put<dynamic>(
|
|
||||||
boxName: walletId,
|
|
||||||
key: 'changeIndex',
|
|
||||||
value: changeIndex == -1 ? 0 : changeIndex);
|
|
||||||
await DB.instance
|
await DB.instance
|
||||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
||||||
await DB.instance
|
await DB.instance
|
||||||
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
|
.put<dynamic>(boxName: walletId, key: "isFavorite", value: false);
|
||||||
|
|
||||||
await _restore(latestSetId, setDataMap, await usedSerialNumbers);
|
await Future.wait([usedSerialNumbers, setDataMap, makeDerivations]);
|
||||||
|
|
||||||
|
await _restore(latestSetId, await setDataMap, await usedSerialNumbers);
|
||||||
longMutex = false;
|
longMutex = false;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
longMutex = false;
|
longMutex = false;
|
||||||
|
@ -4207,27 +4238,23 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
Future<void> _restore(int latestSetId, Map<dynamic, dynamic> setDataMap,
|
Future<void> _restore(int latestSetId, Map<dynamic, dynamic> setDataMap,
|
||||||
dynamic usedSerialNumbers) async {
|
dynamic usedSerialNumbers) async {
|
||||||
final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic');
|
final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic');
|
||||||
models.TransactionData data = await _txnData;
|
Future data = _txnData;
|
||||||
final String currency = _prefs.currency;
|
final String currency = _prefs.currency;
|
||||||
final Decimal currentPrice = await firoPrice;
|
final Decimal currentPrice = await firoPrice;
|
||||||
final locale = await Devicelocale.currentLocale;
|
|
||||||
|
|
||||||
ReceivePort receivePort = await getIsolate({
|
ReceivePort receivePort = await getIsolate({
|
||||||
"function": "restore",
|
"function": "restore",
|
||||||
"mnemonic": mnemonic,
|
"mnemonic": mnemonic,
|
||||||
"transactionData": data,
|
|
||||||
"currency": currency,
|
|
||||||
"coin": coin,
|
"coin": coin,
|
||||||
"latestSetId": latestSetId,
|
"latestSetId": latestSetId,
|
||||||
"setDataMap": setDataMap,
|
"setDataMap": setDataMap,
|
||||||
"usedSerialNumbers": usedSerialNumbers,
|
"usedSerialNumbers": usedSerialNumbers,
|
||||||
"network": _network,
|
"network": _network,
|
||||||
"currentPrice": currentPrice,
|
|
||||||
"locale": locale,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var message = await receivePort.first;
|
await Future.wait([data]);
|
||||||
if (message is String) {
|
var result = await receivePort.first;
|
||||||
|
if (result is String) {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("restore() ->> this is a string", level: LogLevel.Error);
|
.log("restore() ->> this is a string", level: LogLevel.Error);
|
||||||
stop(receivePort);
|
stop(receivePort);
|
||||||
|
@ -4235,6 +4262,10 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
}
|
}
|
||||||
stop(receivePort);
|
stop(receivePort);
|
||||||
|
|
||||||
|
final message = await staticProcessRestore(
|
||||||
|
(await data) as models.TransactionData,
|
||||||
|
result as Map<dynamic, dynamic>);
|
||||||
|
|
||||||
await DB.instance.put<dynamic>(
|
await DB.instance.put<dynamic>(
|
||||||
boxName: walletId, key: 'mintIndex', value: message['mintIndex']);
|
boxName: walletId, key: 'mintIndex', value: message['mintIndex']);
|
||||||
await DB.instance.put<dynamic>(
|
await DB.instance.put<dynamic>(
|
||||||
|
@ -4274,11 +4305,18 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
final latestSetId = await getLatestSetId();
|
final latestSetId = await getLatestSetId();
|
||||||
|
|
||||||
final List<Map<String, dynamic>> sets = [];
|
final List<Map<String, dynamic>> sets = [];
|
||||||
|
List<Future> anonFutures = [];
|
||||||
for (int i = 1; i <= latestSetId; i++) {
|
for (int i = 1; i <= latestSetId; i++) {
|
||||||
Map<String, dynamic> set = await cachedElectrumXClient.getAnonymitySet(
|
final set = cachedElectrumXClient.getAnonymitySet(
|
||||||
groupId: "$i",
|
groupId: "$i",
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
anonFutures.add(set);
|
||||||
|
}
|
||||||
|
await Future.wait(anonFutures);
|
||||||
|
for (int i = 1; i <= latestSetId; i++) {
|
||||||
|
Map<String, dynamic> set =
|
||||||
|
(await anonFutures[i - 1]) as Map<String, dynamic>;
|
||||||
set["setId"] = i;
|
set["setId"] = i;
|
||||||
sets.add(set);
|
sets.add(set);
|
||||||
}
|
}
|
||||||
|
@ -4567,6 +4605,49 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
return available - estimatedFee;
|
return available - estimatedFee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fastFetch(List<String> allTxHashes) async {
|
||||||
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
|
||||||
|
const futureLimit = 30;
|
||||||
|
List<Future<Map<String, dynamic>>> transactionFutures = [];
|
||||||
|
int currentFutureCount = 0;
|
||||||
|
for (final txHash in allTxHashes) {
|
||||||
|
Future<Map<String, dynamic>> transactionFuture =
|
||||||
|
cachedElectrumXClient.getTransaction(
|
||||||
|
txHash: txHash,
|
||||||
|
verbose: true,
|
||||||
|
coin: coin,
|
||||||
|
);
|
||||||
|
transactionFutures.add(transactionFuture);
|
||||||
|
currentFutureCount++;
|
||||||
|
if (currentFutureCount > futureLimit) {
|
||||||
|
currentFutureCount = 0;
|
||||||
|
await Future.wait(transactionFutures);
|
||||||
|
for (final fTx in transactionFutures) {
|
||||||
|
final tx = await fTx;
|
||||||
|
// delete unused large parts
|
||||||
|
tx.remove("hex");
|
||||||
|
tx.remove("lelantusData");
|
||||||
|
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentFutureCount != 0) {
|
||||||
|
currentFutureCount = 0;
|
||||||
|
await Future.wait(transactionFutures);
|
||||||
|
for (final fTx in transactionFutures) {
|
||||||
|
final tx = await fTx;
|
||||||
|
// delete unused large parts
|
||||||
|
tx.remove("hex");
|
||||||
|
tx.remove("lelantusData");
|
||||||
|
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<models.Transaction>> getJMintTransactions(
|
Future<List<models.Transaction>> getJMintTransactions(
|
||||||
CachedElectrumX cachedClient,
|
CachedElectrumX cachedClient,
|
||||||
List<String> transactions,
|
List<String> transactions,
|
||||||
|
@ -4577,14 +4658,12 @@ class FiroWallet extends CoinServiceAPI {
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
List<models.Transaction> txs = [];
|
List<models.Transaction> txs = [];
|
||||||
|
List<Map<String, dynamic>> allTransactions =
|
||||||
|
await fastFetch(transactions);
|
||||||
|
|
||||||
for (int i = 0; i < transactions.length; i++) {
|
for (int i = 0; i < allTransactions.length; i++) {
|
||||||
try {
|
try {
|
||||||
final tx = await cachedClient.getTransaction(
|
final tx = allTransactions[i];
|
||||||
txHash: transactions[i],
|
|
||||||
verbose: true,
|
|
||||||
coin: coin,
|
|
||||||
);
|
|
||||||
|
|
||||||
tx["confirmed_status"] =
|
tx["confirmed_status"] =
|
||||||
tx["confirmations"] != null && tx["confirmations"] as int > 0;
|
tx["confirmations"] != null && tx["confirmations"] as int > 0;
|
||||||
|
|
|
@ -66,18 +66,15 @@ void main() {
|
||||||
return SampleGetTransactionData.txData7;
|
return SampleGetTransactionData.txData7;
|
||||||
});
|
});
|
||||||
|
|
||||||
final result = await isolateRestore(
|
final message = await isolateRestore(
|
||||||
TEST_MNEMONIC,
|
TEST_MNEMONIC,
|
||||||
txData,
|
|
||||||
"USD",
|
|
||||||
Coin.firo,
|
Coin.firo,
|
||||||
1,
|
1,
|
||||||
setData,
|
setData,
|
||||||
usedSerials,
|
usedSerials,
|
||||||
firoNetwork,
|
firoNetwork,
|
||||||
Decimal.ten,
|
|
||||||
"en_US",
|
|
||||||
);
|
);
|
||||||
|
final result = await staticProcessRestore(txData, message);
|
||||||
|
|
||||||
expect(result, isA<Map<String, dynamic>>());
|
expect(result, isA<Map<String, dynamic>>());
|
||||||
expect(result["mintIndex"], 8);
|
expect(result["mintIndex"], 8);
|
||||||
|
@ -88,22 +85,17 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("isolateRestore throws", () async {
|
test("isolateRestore throws", () async {
|
||||||
final txData = TransactionData();
|
|
||||||
final Map setData = {};
|
final Map setData = {};
|
||||||
final usedSerials = [];
|
final usedSerials = [];
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
() => isolateRestore(
|
() => isolateRestore(
|
||||||
TEST_MNEMONIC,
|
TEST_MNEMONIC,
|
||||||
txData,
|
|
||||||
"USD",
|
|
||||||
Coin.firo,
|
Coin.firo,
|
||||||
1,
|
1,
|
||||||
setData,
|
setData,
|
||||||
usedSerials,
|
usedSerials,
|
||||||
firoNetwork,
|
firoNetwork,
|
||||||
Decimal.ten,
|
|
||||||
"en_US",
|
|
||||||
),
|
),
|
||||||
throwsA(isA<Error>()));
|
throwsA(isA<Error>()));
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue