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);
|
||||
}
|
||||
|
||||
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({
|
||||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
|
@ -429,10 +561,6 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
int p2shChangeIndex = -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
|
||||
const txCountBatchSize = 12;
|
||||
|
||||
|
@ -440,323 +568,71 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
// receiving addresses
|
||||
Logging.instance
|
||||
.log("checking receiving addresses...", level: LogLevel.Info);
|
||||
for (int index = 0;
|
||||
index < maxNumberOfIndexesToCheck &&
|
||||
receivingGapCounter < maxUnusedAddressGap;
|
||||
index += txCountBatchSize) {
|
||||
Logging.instance.log(
|
||||
"index: $index, \t receivingGapCounter: $receivingGapCounter",
|
||||
level: LogLevel.Info);
|
||||
Future resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
|
||||
|
||||
final receivingP2pkhID = "k_$index";
|
||||
final receivingP2shID = "s_$index";
|
||||
final receivingP2wpkhID = "w_$index";
|
||||
Map<String, String> txCountCallArgs = {};
|
||||
final Map<String, dynamic> receivingNodes = {};
|
||||
Future resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0);
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Future resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0);
|
||||
|
||||
Logging.instance
|
||||
.log("checking change addresses...", level: LogLevel.Info);
|
||||
// change addresses
|
||||
for (int index = 0;
|
||||
index < maxNumberOfIndexesToCheck &&
|
||||
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 = {};
|
||||
Future resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
|
||||
|
||||
for (int j = 0; j < txCountBatchSize; j++) {
|
||||
// bip44 / P2PKH
|
||||
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,
|
||||
});
|
||||
Future resultChange49 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1);
|
||||
|
||||
// bip49 / P2SH
|
||||
final node49 = await compute(
|
||||
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,
|
||||
});
|
||||
Future resultChange84 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1);
|
||||
|
||||
// bip84 / P2WPKH
|
||||
final node84 = await compute(
|
||||
getBip32NodeFromRootWrapper,
|
||||
Tuple4(
|
||||
1,
|
||||
index + j,
|
||||
root,
|
||||
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,
|
||||
});
|
||||
}
|
||||
await Future.wait([
|
||||
resultReceive44,
|
||||
resultReceive49,
|
||||
resultReceive84,
|
||||
resultChange44,
|
||||
resultChange49,
|
||||
resultChange84
|
||||
]);
|
||||
|
||||
// get address tx counts
|
||||
final counts = await _getBatchTxCount(addresses: args);
|
||||
p2pkhReceiveAddressArray =
|
||||
(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
|
||||
for (int k = 0; k < txCountBatchSize; k++) {
|
||||
int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!;
|
||||
if (p2pkhTxCount > 0) {
|
||||
final node = changeNodes["${changeP2pkhID}_$k"];
|
||||
// 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(),
|
||||
};
|
||||
}
|
||||
p2shReceiveAddressArray =
|
||||
(await resultReceive49)['addressArray'] as List<String>;
|
||||
p2shReceiveIndex = (await resultReceive49)['index'] as int;
|
||||
p2shReceiveDerivations = (await resultReceive49)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
int p2shTxCount = counts["${changeP2shID}_$k"]!;
|
||||
if (p2shTxCount > 0) {
|
||||
final node = changeNodes["${changeP2shID}_$k"];
|
||||
// add address to array
|
||||
p2shChangeAddressArray.add(node["address"] as 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(),
|
||||
};
|
||||
}
|
||||
p2wpkhReceiveAddressArray =
|
||||
(await resultReceive84)['addressArray'] as List<String>;
|
||||
p2wpkhReceiveIndex = (await resultReceive84)['index'] as int;
|
||||
p2wpkhReceiveDerivations = (await resultReceive84)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
int p2wpkhTxCount = counts["${changeP2wpkhID}_$k"]!;
|
||||
if (p2wpkhTxCount > 0) {
|
||||
final node = changeNodes["${changeP2wpkhID}_$k"];
|
||||
// add address to array
|
||||
p2wpkhChangeAddressArray.add(node["address"] as 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(),
|
||||
};
|
||||
}
|
||||
p2pkhChangeAddressArray =
|
||||
(await resultChange44)['addressArray'] as List<String>;
|
||||
p2pkhChangeIndex = (await resultChange44)['index'] as int;
|
||||
p2pkhChangeDerivations = (await resultChange44)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
// increase counter when no tx history found
|
||||
if (p2wpkhTxCount == 0 && p2pkhTxCount == 0 && p2shTxCount == 0) {
|
||||
changeGapCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
p2shChangeAddressArray =
|
||||
(await resultChange49)['addressArray'] as List<String>;
|
||||
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)
|
||||
if (p2pkhReceiveDerivations.isNotEmpty) {
|
||||
|
@ -1098,10 +974,12 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
}
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
|
||||
await _checkChangeAddressForTransactions(DerivePathType.bip84);
|
||||
Future changeAddressForTransactions =
|
||||
_checkChangeAddressForTransactions(DerivePathType.bip84);
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
||||
await _checkCurrentReceivingAddressesForTransactions();
|
||||
Future currentReceivingAddressesForTransactions =
|
||||
_checkCurrentReceivingAddressesForTransactions();
|
||||
|
||||
final newTxData = _fetchTransactionData();
|
||||
GlobalEventBus.instance
|
||||
|
@ -1121,7 +999,15 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
GlobalEventBus.instance
|
||||
.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
|
||||
.fire(RefreshPercentChangedEvent(0.90, walletId));
|
||||
}
|
||||
|
@ -1137,7 +1023,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
);
|
||||
|
||||
if (shouldAutoSync) {
|
||||
timer ??= Timer.periodic(const Duration(seconds: 150), (timer) async {
|
||||
timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||
Logging.instance.log(
|
||||
"Periodic refresh check for $walletId $walletName in object instance: $hashCode",
|
||||
level: LogLevel.Info);
|
||||
|
@ -1972,7 +1858,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
||||
|
||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||
const batchSizeMax = 10;
|
||||
const batchSizeMax = 100;
|
||||
int batchNumber = 0;
|
||||
for (int i = 0; i < allAddresses.length; i++) {
|
||||
if (batches[batchNumber] == null) {
|
||||
|
@ -2357,7 +2243,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
|
||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||
final Map<String, String> requestIdToAddressMap = {};
|
||||
const batchSizeMax = 10;
|
||||
const batchSizeMax = 100;
|
||||
int batchNumber = 0;
|
||||
for (int i = 0; i < allAddresses.length; i++) {
|
||||
if (batches[batchNumber] == null) {
|
||||
|
@ -2404,6 +2290,43 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
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 {
|
||||
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 = [];
|
||||
|
||||
for (final txHash in allTxHashes) {
|
||||
|
@ -2480,6 +2408,16 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
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) {
|
||||
List<String> sendersArray = [];
|
||||
List<String> recipientsArray = [];
|
||||
|
|
|
@ -346,6 +346,120 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
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({
|
||||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
|
@ -364,10 +478,6 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
List<String> p2pkhChangeAddressArray = [];
|
||||
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
|
||||
const txCountBatchSize = 12;
|
||||
|
||||
|
@ -375,143 +485,31 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
// receiving addresses
|
||||
Logging.instance
|
||||
.log("checking receiving addresses...", level: LogLevel.Info);
|
||||
for (int index = 0;
|
||||
index < maxNumberOfIndexesToCheck &&
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Future resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
|
||||
|
||||
Logging.instance
|
||||
.log("checking change addresses...", level: LogLevel.Info);
|
||||
// change addresses
|
||||
for (int index = 0;
|
||||
index < maxNumberOfIndexesToCheck &&
|
||||
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 = {};
|
||||
Future resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
|
||||
|
||||
for (int j = 0; j < txCountBatchSize; j++) {
|
||||
// bip44 / P2PKH
|
||||
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,
|
||||
});
|
||||
}
|
||||
await Future.wait([
|
||||
resultReceive44,
|
||||
resultChange44,
|
||||
]);
|
||||
|
||||
// get address tx counts
|
||||
final counts = await _getBatchTxCount(addresses: args);
|
||||
p2pkhReceiveAddressArray =
|
||||
(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
|
||||
for (int k = 0; k < txCountBatchSize; k++) {
|
||||
int p2pkhTxCount = counts["${changeP2pkhID}_$k"]!;
|
||||
if (p2pkhTxCount > 0) {
|
||||
final node = changeNodes["${changeP2pkhID}_$k"];
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
p2pkhChangeAddressArray =
|
||||
(await resultChange44)['addressArray'] as List<String>;
|
||||
p2pkhChangeIndex = (await resultChange44)['index'] as int;
|
||||
p2pkhChangeDerivations = (await resultChange44)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
// save the derivations (if any)
|
||||
if (p2pkhReceiveDerivations.isNotEmpty) {
|
||||
|
@ -815,7 +813,7 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
refreshMutex = false;
|
||||
|
||||
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
|
||||
// if ((await chainHeight) != (await storedChainHeight)) {
|
||||
if (await refreshIfThereIsNewData()) {
|
||||
|
@ -1496,6 +1494,43 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
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 {
|
||||
final List<String> allAddresses = await _fetchAllOwnAddresses();
|
||||
|
||||
|
@ -1503,7 +1538,7 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
||||
|
||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||
const batchSizeMax = 10;
|
||||
const batchSizeMax = 100;
|
||||
int batchNumber = 0;
|
||||
for (int i = 0; i < allAddresses.length; i++) {
|
||||
if (batches[batchNumber] == null) {
|
||||
|
@ -1872,7 +1907,7 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
|
||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||
final Map<String, String> requestIdToAddressMap = {};
|
||||
const batchSizeMax = 10;
|
||||
const batchSizeMax = 100;
|
||||
int batchNumber = 0;
|
||||
for (int i = 0; i < allAddresses.length; i++) {
|
||||
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 = [];
|
||||
|
||||
for (final txHash in allTxHashes) {
|
||||
|
@ -1983,6 +2023,16 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
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) {
|
||||
List<String> sendersArray = [];
|
||||
List<String> recipientsArray = [];
|
||||
|
|
|
@ -43,6 +43,7 @@ import 'package:stackwallet/utilities/format.dart';
|
|||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
const DUST_LIMIT = 1000;
|
||||
const MINIMUM_CONFIRMATIONS = 1;
|
||||
|
@ -145,25 +146,17 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
final setDataMap = arguments['setDataMap'] as Map;
|
||||
final usedSerialNumbers = arguments['usedSerialNumbers'] as List?;
|
||||
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 network = arguments['network'] as NetworkType?;
|
||||
final currentPrice = arguments['currentPrice'] as Decimal;
|
||||
final locale = arguments['locale'] as String;
|
||||
if (!(usedSerialNumbers == null || network == null)) {
|
||||
var restoreData = await isolateRestore(
|
||||
mnemonic,
|
||||
transactionData,
|
||||
currency,
|
||||
coin,
|
||||
latestSetId,
|
||||
setDataMap,
|
||||
usedSerialNumbers,
|
||||
network,
|
||||
currentPrice,
|
||||
locale);
|
||||
mnemonic,
|
||||
coin,
|
||||
latestSetId,
|
||||
setDataMap,
|
||||
usedSerialNumbers,
|
||||
network,
|
||||
);
|
||||
sendPort.send(restoreData);
|
||||
return;
|
||||
}
|
||||
|
@ -240,15 +233,11 @@ Future<Map<String, dynamic>> isolateDerive(
|
|||
|
||||
Future<Map<String, dynamic>> isolateRestore(
|
||||
String mnemonic,
|
||||
models.TransactionData data,
|
||||
String currency,
|
||||
Coin coin,
|
||||
int _latestSetId,
|
||||
Map<dynamic, dynamic> _setDataMap,
|
||||
List<dynamic> _usedSerialNumbers,
|
||||
NetworkType network,
|
||||
Decimal currentPrice,
|
||||
String locale,
|
||||
) async {
|
||||
List<int> jindexes = [];
|
||||
List<Map<dynamic, LelantusCoin>> lelantusCoins = [];
|
||||
|
@ -373,6 +362,20 @@ Future<Map<String, dynamic>> isolateRestore(
|
|||
result['_lelantus_coins'] = lelantusCoins;
|
||||
result['mintIndex'] = lastFoundIndex + 1;
|
||||
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.
|
||||
Map<String, models.Transaction> editedTransactions =
|
||||
|
@ -437,7 +440,6 @@ Future<Map<String, dynamic>> isolateRestore(
|
|||
(value.height == -1 && !value.confirmedStatus));
|
||||
|
||||
result['newTxMap'] = transactionMap;
|
||||
result['spendTxIds'] = spendTxIds;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -2193,7 +2195,7 @@ class FiroWallet extends CoinServiceAPI {
|
|||
final newTxData = _fetchTransactionData();
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.35, walletId));
|
||||
|
||||
final FeeObject feeObj = await _getFees();
|
||||
final feeObj = _getFees();
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
|
||||
|
||||
_utxoData = Future(() => newUtxoData);
|
||||
|
@ -2217,9 +2219,6 @@ class FiroWallet extends CoinServiceAPI {
|
|||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.95, walletId));
|
||||
|
||||
final maxFee = await _fetchMaxFee();
|
||||
_maxFee = Future(() => maxFee);
|
||||
|
||||
var txData = (await _txnData);
|
||||
var lTxData = (await lelantusTransactionData);
|
||||
await getAllTxsToWatch(txData, lTxData);
|
||||
|
@ -2236,7 +2235,7 @@ class FiroWallet extends CoinServiceAPI {
|
|||
refreshMutex = false;
|
||||
|
||||
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();
|
||||
if (shouldNotify) {
|
||||
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 jindexes =
|
||||
DB.instance.get<dynamic>(boxName: walletId, key: 'jindex') as List?;
|
||||
|
@ -2826,6 +2825,7 @@ class FiroWallet extends CoinServiceAPI {
|
|||
_lelantusTransactionData = Future(() => newTxData);
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId, key: 'latest_lelantus_tx_model', value: newTxData);
|
||||
return newTxData;
|
||||
}
|
||||
|
||||
Future<String> _getMintHex(int amount, int index) async {
|
||||
|
@ -3122,24 +3122,44 @@ class FiroWallet extends CoinServiceAPI {
|
|||
List<String> allAddresses) async {
|
||||
try {
|
||||
List<Map<String, dynamic>> allTxHashes = [];
|
||||
// int latestTxnBlockHeight = 0;
|
||||
|
||||
for (final address in allAddresses) {
|
||||
final scripthash = AddressUtils.convertToScriptHash(address, _network);
|
||||
final txs = await electrumXClient.getHistory(scripthash: scripthash);
|
||||
for (final map in txs) {
|
||||
if (!allTxHashes.contains(map)) {
|
||||
map['address'] = address;
|
||||
allTxHashes.add(map);
|
||||
final Map<int, Map<String, List<dynamic>>> batches = {};
|
||||
final Map<String, String> requestIdToAddressMap = {};
|
||||
const batchSizeMax = 100;
|
||||
int batchNumber = 0;
|
||||
for (int i = 0; i < allAddresses.length; i++) {
|
||||
if (batches[batchNumber] == null) {
|
||||
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;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in _fetchHistory(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
return [];
|
||||
Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3178,20 +3198,11 @@ class FiroWallet extends CoinServiceAPI {
|
|||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> allTransactions = [];
|
||||
|
||||
for (final txHash in allTxHashes) {
|
||||
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<String> hashes = [];
|
||||
for (var element in allTxHashes) {
|
||||
hashes.add(element['tx_hash'] as String);
|
||||
}
|
||||
List<Map<String, dynamic>> allTransactions = await fastFetch(hashes);
|
||||
|
||||
Logging.instance.log("allTransactions length: ${allTransactions.length}",
|
||||
level: LogLevel.Info);
|
||||
|
@ -3441,81 +3452,83 @@ class FiroWallet extends CoinServiceAPI {
|
|||
}
|
||||
|
||||
Future<UtxoData> _fetchUtxoData() async {
|
||||
final List<String> allAddresses = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
final List<String> allAddresses = await _fetchAllOwnAddresses();
|
||||
|
||||
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++) {
|
||||
if (batches[batchNumber] == null) {
|
||||
batches[batchNumber] = {};
|
||||
}
|
||||
final scripthash =
|
||||
AddressUtils.convertToScriptHash(allAddresses[i], _network);
|
||||
final utxos = await electrumXClient.getUTXOs(scripthash: scripthash);
|
||||
if (utxos.isNotEmpty) {
|
||||
utxoData.add(utxos);
|
||||
batches[batchNumber]!.addAll({
|
||||
scripthash: [scripthash]
|
||||
});
|
||||
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 = [];
|
||||
int satoshiBalance = 0;
|
||||
int satoshiBalancePending = 0;
|
||||
|
||||
for (int i = 0; i < utxoData.length; i++) {
|
||||
for (int j = 0; j < utxoData[i].length; j++) {
|
||||
int value = utxoData[i][j]["value"] as int;
|
||||
for (int i = 0; i < fetchedUtxoList.length; i++) {
|
||||
for (int j = 0; j < fetchedUtxoList[i].length; j++) {
|
||||
int value = fetchedUtxoList[i][j]["value"] as int;
|
||||
satoshiBalance += value;
|
||||
|
||||
final txn = await cachedElectrumXClient.getTransaction(
|
||||
txHash: utxoData[i][j]["tx_hash"] as String,
|
||||
txHash: fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
final Map<String, dynamic> tx = {};
|
||||
final Map<String, dynamic> utxo = {};
|
||||
final int confirmations = txn["confirmations"] as int? ?? 0;
|
||||
final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS;
|
||||
if (!confirmed) {
|
||||
satoshiBalancePending += value;
|
||||
}
|
||||
|
||||
tx["txid"] = txn["txid"];
|
||||
tx["vout"] = utxoData[i][j]["tx_pos"];
|
||||
tx["value"] = value;
|
||||
utxo["txid"] = txn["txid"];
|
||||
utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"];
|
||||
utxo["value"] = value;
|
||||
|
||||
tx["status"] = <String, dynamic>{};
|
||||
tx["status"]["confirmed"] = confirmed;
|
||||
tx["status"]["confirmations"] = confirmations;
|
||||
tx["status"]["confirmed"] =
|
||||
utxo["status"] = <String, dynamic>{};
|
||||
utxo["status"]["confirmed"] = confirmed;
|
||||
utxo["status"]["confirmations"] = confirmations;
|
||||
utxo["status"]["confirmed"] =
|
||||
txn["confirmations"] == null ? false : txn["confirmations"] > 0;
|
||||
|
||||
tx["status"]["block_height"] = txn["height"];
|
||||
tx["status"]["block_hash"] = txn["blockhash"];
|
||||
tx["status"]["block_time"] = txn["blocktime"];
|
||||
utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"];
|
||||
utxo["status"]["block_hash"] = txn["blockhash"];
|
||||
utxo["status"]["block_time"] = txn["blocktime"];
|
||||
|
||||
final fiatValue = ((Decimal.fromInt(value) * currentPrice) /
|
||||
Decimal.fromInt(Constants.satsPerCoin))
|
||||
.toDecimal(scaleOnInfinitePrecision: 2);
|
||||
tx["rawWorth"] = fiatValue;
|
||||
tx["fiatWorth"] = fiatValue.toString();
|
||||
tx["is_coinbase"] = txn['vin'][0]['coinbase'] != null;
|
||||
outputArray.add(tx);
|
||||
utxo["rawWorth"] = fiatValue;
|
||||
utxo["fiatWorth"] = fiatValue.toString();
|
||||
utxo["is_coinbase"] = txn['vin'][0]['coinbase'] != null;
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3538,13 +3551,19 @@ class FiroWallet extends CoinServiceAPI {
|
|||
final dataModel = UtxoData.fromJson(result);
|
||||
|
||||
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 DB.instance.put<dynamic>(
|
||||
boxName: walletId, key: 'latest_utxo_model', value: dataModel);
|
||||
// await DB.instance.put<dynamic>(
|
||||
// boxName: walletId,
|
||||
// key: 'totalBalance',
|
||||
// value: dataModel.satoshiBalance);
|
||||
return dataModel;
|
||||
} catch (e) {
|
||||
// Logging.instance.log("Output fetch unsuccessful: $e\n$s");
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
||||
final latestTxModel =
|
||||
DB.instance.get<dynamic>(boxName: walletId, key: 'latest_utxo_model')
|
||||
as models.UtxoData?;
|
||||
|
@ -4056,6 +4075,135 @@ class FiroWallet extends CoinServiceAPI {
|
|||
|
||||
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.
|
||||
Future<void> _recoverWalletFromBIP32SeedPhrase(
|
||||
String suppliedMnemonic, int maxUnusedAddressGap) async {
|
||||
|
@ -4063,137 +4211,20 @@ class FiroWallet extends CoinServiceAPI {
|
|||
Logging.instance
|
||||
.log("PROCESSORS ${Platform.numberOfProcessors}", level: LogLevel.Info);
|
||||
try {
|
||||
final Map<int, dynamic> setDataMap = {};
|
||||
final latestSetId = await getLatestSetId();
|
||||
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;
|
||||
}
|
||||
}
|
||||
final setDataMap = getSetDataMap(latestSetId);
|
||||
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
|
||||
.put<dynamic>(boxName: walletId, key: "id", value: _walletId);
|
||||
await DB.instance
|
||||
.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;
|
||||
} catch (e, s) {
|
||||
longMutex = false;
|
||||
|
@ -4207,27 +4238,23 @@ class FiroWallet extends CoinServiceAPI {
|
|||
Future<void> _restore(int latestSetId, Map<dynamic, dynamic> setDataMap,
|
||||
dynamic usedSerialNumbers) async {
|
||||
final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic');
|
||||
models.TransactionData data = await _txnData;
|
||||
Future data = _txnData;
|
||||
final String currency = _prefs.currency;
|
||||
final Decimal currentPrice = await firoPrice;
|
||||
final locale = await Devicelocale.currentLocale;
|
||||
|
||||
ReceivePort receivePort = await getIsolate({
|
||||
"function": "restore",
|
||||
"mnemonic": mnemonic,
|
||||
"transactionData": data,
|
||||
"currency": currency,
|
||||
"coin": coin,
|
||||
"latestSetId": latestSetId,
|
||||
"setDataMap": setDataMap,
|
||||
"usedSerialNumbers": usedSerialNumbers,
|
||||
"network": _network,
|
||||
"currentPrice": currentPrice,
|
||||
"locale": locale,
|
||||
});
|
||||
|
||||
var message = await receivePort.first;
|
||||
if (message is String) {
|
||||
await Future.wait([data]);
|
||||
var result = await receivePort.first;
|
||||
if (result is String) {
|
||||
Logging.instance
|
||||
.log("restore() ->> this is a string", level: LogLevel.Error);
|
||||
stop(receivePort);
|
||||
|
@ -4235,6 +4262,10 @@ class FiroWallet extends CoinServiceAPI {
|
|||
}
|
||||
stop(receivePort);
|
||||
|
||||
final message = await staticProcessRestore(
|
||||
(await data) as models.TransactionData,
|
||||
result as Map<dynamic, dynamic>);
|
||||
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId, key: 'mintIndex', value: message['mintIndex']);
|
||||
await DB.instance.put<dynamic>(
|
||||
|
@ -4274,11 +4305,18 @@ class FiroWallet extends CoinServiceAPI {
|
|||
final latestSetId = await getLatestSetId();
|
||||
|
||||
final List<Map<String, dynamic>> sets = [];
|
||||
List<Future> anonFutures = [];
|
||||
for (int i = 1; i <= latestSetId; i++) {
|
||||
Map<String, dynamic> set = await cachedElectrumXClient.getAnonymitySet(
|
||||
final set = cachedElectrumXClient.getAnonymitySet(
|
||||
groupId: "$i",
|
||||
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;
|
||||
sets.add(set);
|
||||
}
|
||||
|
@ -4567,6 +4605,49 @@ class FiroWallet extends CoinServiceAPI {
|
|||
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(
|
||||
CachedElectrumX cachedClient,
|
||||
List<String> transactions,
|
||||
|
@ -4577,14 +4658,12 @@ class FiroWallet extends CoinServiceAPI {
|
|||
) async {
|
||||
try {
|
||||
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 {
|
||||
final tx = await cachedClient.getTransaction(
|
||||
txHash: transactions[i],
|
||||
verbose: true,
|
||||
coin: coin,
|
||||
);
|
||||
final tx = allTransactions[i];
|
||||
|
||||
tx["confirmed_status"] =
|
||||
tx["confirmations"] != null && tx["confirmations"] as int > 0;
|
||||
|
|
|
@ -66,18 +66,15 @@ void main() {
|
|||
return SampleGetTransactionData.txData7;
|
||||
});
|
||||
|
||||
final result = await isolateRestore(
|
||||
final message = await isolateRestore(
|
||||
TEST_MNEMONIC,
|
||||
txData,
|
||||
"USD",
|
||||
Coin.firo,
|
||||
1,
|
||||
setData,
|
||||
usedSerials,
|
||||
firoNetwork,
|
||||
Decimal.ten,
|
||||
"en_US",
|
||||
);
|
||||
final result = await staticProcessRestore(txData, message);
|
||||
|
||||
expect(result, isA<Map<String, dynamic>>());
|
||||
expect(result["mintIndex"], 8);
|
||||
|
@ -88,22 +85,17 @@ void main() {
|
|||
});
|
||||
|
||||
test("isolateRestore throws", () async {
|
||||
final txData = TransactionData();
|
||||
final Map setData = {};
|
||||
final usedSerials = [];
|
||||
|
||||
expect(
|
||||
() => isolateRestore(
|
||||
TEST_MNEMONIC,
|
||||
txData,
|
||||
"USD",
|
||||
Coin.firo,
|
||||
1,
|
||||
setData,
|
||||
usedSerials,
|
||||
firoNetwork,
|
||||
Decimal.ten,
|
||||
"en_US",
|
||||
),
|
||||
throwsA(isA<Error>()));
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue