quais 1.0.0-alpha.13 → 1.0.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/quais.js +656 -231
- package/dist/quais.js.map +1 -1
- package/dist/quais.min.js +1 -1
- package/dist/quais.umd.js +656 -231
- package/dist/quais.umd.js.map +1 -1
- package/dist/quais.umd.min.js +1 -1
- package/examples/wallets/qi-send.js +165 -35
- package/lib/commonjs/providers/abstract-provider.d.ts +12 -0
- package/lib/commonjs/providers/abstract-provider.d.ts.map +1 -1
- package/lib/commonjs/providers/abstract-provider.js +36 -3
- package/lib/commonjs/providers/abstract-provider.js.map +1 -1
- package/lib/commonjs/transaction/abstract-coinselector.d.ts +1 -1
- package/lib/commonjs/transaction/abstract-coinselector.d.ts.map +1 -1
- package/lib/commonjs/transaction/coinselector-fewest.d.ts +52 -4
- package/lib/commonjs/transaction/coinselector-fewest.d.ts.map +1 -1
- package/lib/commonjs/transaction/coinselector-fewest.js +132 -84
- package/lib/commonjs/transaction/coinselector-fewest.js.map +1 -1
- package/lib/commonjs/wallet/qi-hdwallet.d.ts +132 -26
- package/lib/commonjs/wallet/qi-hdwallet.d.ts.map +1 -1
- package/lib/commonjs/wallet/qi-hdwallet.js +485 -127
- package/lib/commonjs/wallet/qi-hdwallet.js.map +1 -1
- package/lib/esm/providers/abstract-provider.d.ts +12 -0
- package/lib/esm/providers/abstract-provider.d.ts.map +1 -1
- package/lib/esm/providers/abstract-provider.js +37 -4
- package/lib/esm/providers/abstract-provider.js.map +1 -1
- package/lib/esm/transaction/abstract-coinselector.d.ts +1 -1
- package/lib/esm/transaction/abstract-coinselector.d.ts.map +1 -1
- package/lib/esm/transaction/coinselector-fewest.d.ts +52 -4
- package/lib/esm/transaction/coinselector-fewest.d.ts.map +1 -1
- package/lib/esm/transaction/coinselector-fewest.js +132 -84
- package/lib/esm/transaction/coinselector-fewest.js.map +1 -1
- package/lib/esm/wallet/qi-hdwallet.d.ts +132 -26
- package/lib/esm/wallet/qi-hdwallet.d.ts.map +1 -1
- package/lib/esm/wallet/qi-hdwallet.js +485 -127
- package/lib/esm/wallet/qi-hdwallet.js.map +1 -1
- package/package.json +1 -1
- package/src/providers/abstract-provider.ts +38 -3
- package/src/transaction/abstract-coinselector.ts +1 -1
- package/src/transaction/coinselector-fewest.ts +153 -104
- package/src/wallet/qi-hdwallet.ts +586 -141
package/dist/quais.js
CHANGED
|
@@ -782,21 +782,6 @@ function getBigInt(value, name) {
|
|
|
782
782
|
}
|
|
783
783
|
assertArgument(false, 'invalid BigNumberish value', name || 'value', value);
|
|
784
784
|
}
|
|
785
|
-
/**
|
|
786
|
-
* Returns absolute value of bigint `value`.
|
|
787
|
-
*
|
|
788
|
-
* @category Utils
|
|
789
|
-
* @param {BigNumberish} value - The value to convert.
|
|
790
|
-
* @returns {bigint} The absolute value.
|
|
791
|
-
*/
|
|
792
|
-
function bigIntAbs(value) {
|
|
793
|
-
value = getBigInt(value);
|
|
794
|
-
// if value is negative (including -0), return -value, else return value
|
|
795
|
-
if (value === -BN_0$8 || value < BN_0$8) {
|
|
796
|
-
return -value;
|
|
797
|
-
}
|
|
798
|
-
return value;
|
|
799
|
-
}
|
|
800
785
|
/**
|
|
801
786
|
* Returns `value` as a bigint, validating it is valid as a bigint value and that it is positive.
|
|
802
787
|
*
|
|
@@ -18286,6 +18271,7 @@ class UTXO {
|
|
|
18286
18271
|
}
|
|
18287
18272
|
}
|
|
18288
18273
|
|
|
18274
|
+
// import { bigIntAbs } from '../utils/maths.js';
|
|
18289
18275
|
/**
|
|
18290
18276
|
* The FewestCoinSelector class provides a coin selection algorithm that selects the fewest UTXOs required to meet the
|
|
18291
18277
|
* target amount. This algorithm is useful for minimizing the size of the transaction and the fees associated with it.
|
|
@@ -18298,115 +18284,171 @@ class UTXO {
|
|
|
18298
18284
|
*/
|
|
18299
18285
|
class FewestCoinSelector extends AbstractCoinSelector {
|
|
18300
18286
|
/**
|
|
18301
|
-
*
|
|
18287
|
+
* Performs coin selection to meet the target amount plus fee, using the smallest possible denominations and
|
|
18288
|
+
* minimizing the number of inputs and outputs.
|
|
18302
18289
|
*
|
|
18303
18290
|
* @param {bigint} target - The target amount to spend.
|
|
18304
|
-
* @
|
|
18291
|
+
* @param {bigint} fee - The fee amount to include in the selection.
|
|
18292
|
+
* @returns {SelectedCoinsResult} The selected UTXOs and outputs.
|
|
18305
18293
|
*/
|
|
18306
|
-
performSelection(target) {
|
|
18294
|
+
performSelection(target, fee = BigInt(0)) {
|
|
18307
18295
|
if (target <= BigInt(0)) {
|
|
18308
18296
|
throw new Error('Target amount must be greater than 0');
|
|
18309
18297
|
}
|
|
18298
|
+
if (fee < BigInt(0)) {
|
|
18299
|
+
throw new Error('Fee amount cannot be negative');
|
|
18300
|
+
}
|
|
18310
18301
|
this.validateUTXOs();
|
|
18311
18302
|
this.target = target;
|
|
18303
|
+
const totalRequired = BigInt(target) + BigInt(fee);
|
|
18312
18304
|
// Initialize selection state
|
|
18313
18305
|
this.selectedUTXOs = [];
|
|
18314
18306
|
this.totalInputValue = BigInt(0);
|
|
18315
|
-
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18321
|
-
|
|
18322
|
-
|
|
18323
|
-
if (currentUTXO.denomination === null)
|
|
18324
|
-
return minDenominationUTXO;
|
|
18325
|
-
return BigInt(denominations[currentUTXO.denomination]) <
|
|
18326
|
-
BigInt(denominations[minDenominationUTXO.denomination])
|
|
18327
|
-
? currentUTXO
|
|
18328
|
-
: minDenominationUTXO;
|
|
18329
|
-
}, UTXOsEqualOrGreaterThanTarget[0]);
|
|
18330
|
-
selectedUTXOs.push(optimalUTXO);
|
|
18331
|
-
totalValue += BigInt(denominations[optimalUTXO.denomination]);
|
|
18307
|
+
// Sort available UTXOs by denomination in ascending order
|
|
18308
|
+
const sortedUTXOs = this.sortUTXOsByDenomination(this.availableUTXOs, 'asc');
|
|
18309
|
+
// Attempt to find a single UTXO that can cover the total required amount
|
|
18310
|
+
const singleUTXO = sortedUTXOs.find((utxo) => BigInt(denominations[utxo.denomination]) >= totalRequired);
|
|
18311
|
+
if (singleUTXO) {
|
|
18312
|
+
// Use the smallest UTXO that can cover the total required amount
|
|
18313
|
+
this.selectedUTXOs.push(singleUTXO);
|
|
18314
|
+
this.totalInputValue = BigInt(denominations[singleUTXO.denomination]);
|
|
18332
18315
|
}
|
|
18333
18316
|
else {
|
|
18334
|
-
// If no single UTXO
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
return absThisDiff < currentClosestDiff ? utxo : closest;
|
|
18346
|
-
}, sortedUTXOs[0]);
|
|
18347
|
-
// Add the selected UTXO to the selection and update totalValue
|
|
18348
|
-
selectedUTXOs.push(nextOptimalUTXO);
|
|
18349
|
-
totalValue += BigInt(denominations[nextOptimalUTXO.denomination]);
|
|
18350
|
-
// Remove the selected UTXO from the list of available UTXOs
|
|
18351
|
-
const index = sortedUTXOs.findIndex((utxo) => utxo.denomination === nextOptimalUTXO.denomination && utxo.address === nextOptimalUTXO.address);
|
|
18352
|
-
sortedUTXOs.splice(index, 1);
|
|
18353
|
-
}
|
|
18354
|
-
}
|
|
18355
|
-
// Optimize the selection process
|
|
18356
|
-
let optimalSelection = selectedUTXOs;
|
|
18357
|
-
let minExcess = BigInt(totalValue) - BigInt(target);
|
|
18358
|
-
for (let i = 0; i < selectedUTXOs.length; i++) {
|
|
18359
|
-
const subsetUTXOs = selectedUTXOs.slice(0, i).concat(selectedUTXOs.slice(i + 1));
|
|
18360
|
-
const subsetTotal = subsetUTXOs.reduce((sum, utxo) => BigInt(sum) + BigInt(denominations[utxo.denomination]), BigInt(0));
|
|
18361
|
-
if (subsetTotal >= target) {
|
|
18362
|
-
const excess = BigInt(subsetTotal) - BigInt(target);
|
|
18363
|
-
if (excess < minExcess) {
|
|
18364
|
-
optimalSelection = subsetUTXOs;
|
|
18365
|
-
minExcess = excess;
|
|
18366
|
-
totalValue = subsetTotal;
|
|
18367
|
-
}
|
|
18368
|
-
}
|
|
18369
|
-
}
|
|
18370
|
-
selectedUTXOs = optimalSelection;
|
|
18371
|
-
// Find the largest denomination used in the inputs
|
|
18372
|
-
// Store the selected UTXOs and total input value
|
|
18373
|
-
this.selectedUTXOs = selectedUTXOs;
|
|
18374
|
-
this.totalInputValue = totalValue;
|
|
18375
|
-
// Check if the selected UTXOs meet or exceed the target amount
|
|
18376
|
-
if (totalValue < target) {
|
|
18377
|
-
throw new Error('Insufficient funds');
|
|
18378
|
-
}
|
|
18379
|
-
// Store spendOutputs and changeOutputs
|
|
18317
|
+
// If no single UTXO can cover the total required amount, find the minimal set
|
|
18318
|
+
this.selectedUTXOs = this.findMinimalUTXOSet(sortedUTXOs, totalRequired);
|
|
18319
|
+
if (this.selectedUTXOs.length === 0) {
|
|
18320
|
+
throw new Error('Insufficient funds');
|
|
18321
|
+
}
|
|
18322
|
+
// Calculate total input value
|
|
18323
|
+
this.totalInputValue = this.selectedUTXOs.reduce((sum, utxo) => sum + BigInt(denominations[utxo.denomination]), BigInt(0));
|
|
18324
|
+
}
|
|
18325
|
+
// Create outputs
|
|
18326
|
+
const changeAmount = this.totalInputValue - BigInt(target) - BigInt(fee);
|
|
18327
|
+
// Create spend outputs (to the recipient)
|
|
18380
18328
|
this.spendOutputs = this.createSpendOutputs(target);
|
|
18381
|
-
|
|
18329
|
+
// Create change outputs (to ourselves), if any
|
|
18330
|
+
this.changeOutputs = this.createChangeOutputs(changeAmount);
|
|
18331
|
+
// Verify that sum of outputs does not exceed sum of inputs
|
|
18332
|
+
const totalOutputValue = this.calculateTotalOutputValue();
|
|
18333
|
+
if (totalOutputValue > this.totalInputValue) {
|
|
18334
|
+
throw new Error('Total output value exceeds total input value');
|
|
18335
|
+
}
|
|
18336
|
+
// Ensure largest output denomination ≤ largest input denomination
|
|
18337
|
+
const maxInputDenomination = this.getMaxInputDenomination();
|
|
18338
|
+
const maxOutputDenomination = this.getMaxOutputDenomination();
|
|
18339
|
+
if (maxOutputDenomination > maxInputDenomination) {
|
|
18340
|
+
throw new Error('Largest output denomination exceeds largest input denomination');
|
|
18341
|
+
}
|
|
18382
18342
|
return {
|
|
18383
|
-
inputs: selectedUTXOs,
|
|
18343
|
+
inputs: this.selectedUTXOs,
|
|
18384
18344
|
spendOutputs: this.spendOutputs,
|
|
18385
18345
|
changeOutputs: this.changeOutputs,
|
|
18386
18346
|
};
|
|
18387
18347
|
}
|
|
18388
|
-
|
|
18348
|
+
/**
|
|
18349
|
+
* Finds the minimal set of UTXOs that can cover the total required amount.
|
|
18350
|
+
*
|
|
18351
|
+
* @param {UTXO[]} sortedUTXOs - Available UTXOs sorted by denomination (ascending).
|
|
18352
|
+
* @param {bigint} totalRequired - The total amount required (target + fee).
|
|
18353
|
+
* @returns {UTXO[]} The minimal set of UTXOs.
|
|
18354
|
+
*/
|
|
18355
|
+
findMinimalUTXOSet(sortedUTXOs, totalRequired) {
|
|
18356
|
+
// Use a greedy algorithm to select the fewest UTXOs
|
|
18357
|
+
// Starting from the largest denominations to minimize the number of inputs
|
|
18358
|
+
const utxos = [...sortedUTXOs].reverse(); // Largest to smallest
|
|
18359
|
+
let totalValue = BigInt(0);
|
|
18360
|
+
const selectedUTXOs = [];
|
|
18361
|
+
for (const utxo of utxos) {
|
|
18362
|
+
if (totalValue >= totalRequired) {
|
|
18363
|
+
break;
|
|
18364
|
+
}
|
|
18365
|
+
selectedUTXOs.push(utxo);
|
|
18366
|
+
totalValue += BigInt(denominations[utxo.denomination]);
|
|
18367
|
+
}
|
|
18368
|
+
if (totalValue >= totalRequired) {
|
|
18369
|
+
return selectedUTXOs;
|
|
18370
|
+
}
|
|
18371
|
+
else {
|
|
18372
|
+
return []; // Insufficient funds
|
|
18373
|
+
}
|
|
18374
|
+
}
|
|
18375
|
+
/**
|
|
18376
|
+
* Creates spend outputs based on the target amount and input denominations.
|
|
18377
|
+
*
|
|
18378
|
+
* @param {bigint} amount - The target amount to spend.
|
|
18379
|
+
* @param {UTXO[]} inputs - The selected inputs.
|
|
18380
|
+
* @returns {UTXO[]} The spend outputs.
|
|
18381
|
+
*/
|
|
18389
18382
|
createSpendOutputs(amount) {
|
|
18390
|
-
const
|
|
18391
|
-
|
|
18392
|
-
|
|
18383
|
+
const maxInputDenomination = this.getMaxInputDenomination();
|
|
18384
|
+
// Denominate the amount using available denominations up to the max input denomination
|
|
18385
|
+
const spendDenominations = denominate(amount, maxInputDenomination);
|
|
18386
|
+
return spendDenominations.map((denominationValue) => {
|
|
18393
18387
|
const utxo = new UTXO();
|
|
18394
|
-
utxo.denomination = denominations.indexOf(
|
|
18388
|
+
utxo.denomination = denominations.indexOf(denominationValue);
|
|
18395
18389
|
return utxo;
|
|
18396
18390
|
});
|
|
18397
18391
|
}
|
|
18392
|
+
/**
|
|
18393
|
+
* Creates change outputs based on the change amount and input denominations.
|
|
18394
|
+
*
|
|
18395
|
+
* @param {bigint} change - The change amount to return.
|
|
18396
|
+
* @param {UTXO[]} inputs - The selected inputs.
|
|
18397
|
+
* @returns {UTXO[]} The change outputs.
|
|
18398
|
+
*/
|
|
18398
18399
|
createChangeOutputs(change) {
|
|
18399
18400
|
if (change <= BigInt(0)) {
|
|
18400
18401
|
return [];
|
|
18401
18402
|
}
|
|
18402
|
-
const
|
|
18403
|
-
|
|
18404
|
-
|
|
18403
|
+
const maxInputDenomination = this.getMaxInputDenomination();
|
|
18404
|
+
// Denominate the change amount using available denominations up to the max input denomination
|
|
18405
|
+
const changeDenominations = denominate(change, maxInputDenomination);
|
|
18406
|
+
return changeDenominations.map((denominationValue) => {
|
|
18405
18407
|
const utxo = new UTXO();
|
|
18406
|
-
utxo.denomination = denominations.indexOf(
|
|
18408
|
+
utxo.denomination = denominations.indexOf(denominationValue);
|
|
18407
18409
|
return utxo;
|
|
18408
18410
|
});
|
|
18409
18411
|
}
|
|
18412
|
+
/**
|
|
18413
|
+
* Calculates the total value of outputs (spend + change).
|
|
18414
|
+
*
|
|
18415
|
+
* @returns {bigint} The total output value.
|
|
18416
|
+
*/
|
|
18417
|
+
calculateTotalOutputValue() {
|
|
18418
|
+
const spendValue = this.spendOutputs.reduce((sum, output) => sum + BigInt(denominations[output.denomination]), BigInt(0));
|
|
18419
|
+
const changeValue = this.changeOutputs.reduce((sum, output) => sum + BigInt(denominations[output.denomination]), BigInt(0));
|
|
18420
|
+
return spendValue + changeValue;
|
|
18421
|
+
}
|
|
18422
|
+
/**
|
|
18423
|
+
* Gets the maximum denomination value from the selected UTXOs.
|
|
18424
|
+
*
|
|
18425
|
+
* @returns {bigint} The maximum input denomination value.
|
|
18426
|
+
*/
|
|
18427
|
+
getMaxInputDenomination() {
|
|
18428
|
+
const inputs = [...this.selectedUTXOs];
|
|
18429
|
+
return this.getMaxDenomination(inputs);
|
|
18430
|
+
}
|
|
18431
|
+
/**
|
|
18432
|
+
* Gets the maximum denomination value from the spend and change outputs.
|
|
18433
|
+
*
|
|
18434
|
+
* @returns {bigint} The maximum output denomination value.
|
|
18435
|
+
*/
|
|
18436
|
+
getMaxOutputDenomination() {
|
|
18437
|
+
const outputs = [...this.spendOutputs, ...this.changeOutputs];
|
|
18438
|
+
return this.getMaxDenomination(outputs);
|
|
18439
|
+
}
|
|
18440
|
+
/**
|
|
18441
|
+
* Gets the maximum denomination value from a list of UTXOs.
|
|
18442
|
+
*
|
|
18443
|
+
* @param {UTXO[]} utxos - The list of UTXOs.
|
|
18444
|
+
* @returns {bigint} The maximum denomination value.
|
|
18445
|
+
*/
|
|
18446
|
+
getMaxDenomination(utxos) {
|
|
18447
|
+
return utxos.reduce((max, utxo) => {
|
|
18448
|
+
const denomValue = BigInt(denominations[utxo.denomination]);
|
|
18449
|
+
return denomValue > max ? denomValue : max;
|
|
18450
|
+
}, BigInt(0));
|
|
18451
|
+
}
|
|
18410
18452
|
/**
|
|
18411
18453
|
* Increases the total fee by first reducing change outputs, then selecting additional inputs if necessary.
|
|
18412
18454
|
*
|
|
@@ -18439,9 +18481,7 @@ class FewestCoinSelector extends AbstractCoinSelector {
|
|
|
18439
18481
|
if (remainingFee <= BigInt(0)) {
|
|
18440
18482
|
// If we have excess, create a new change output
|
|
18441
18483
|
if (remainingFee < BigInt(0)) {
|
|
18442
|
-
const change = BigInt(this.totalInputValue) -
|
|
18443
|
-
BigInt(this.target) -
|
|
18444
|
-
(BigInt(additionalFeeNeeded) - BigInt(remainingFee));
|
|
18484
|
+
const change = BigInt(this.totalInputValue) - BigInt(this.target) - BigInt(additionalFeeNeeded);
|
|
18445
18485
|
this.adjustChangeOutputs(change);
|
|
18446
18486
|
}
|
|
18447
18487
|
}
|
|
@@ -18485,12 +18525,6 @@ class FewestCoinSelector extends AbstractCoinSelector {
|
|
|
18485
18525
|
changeOutputs: this.changeOutputs,
|
|
18486
18526
|
};
|
|
18487
18527
|
}
|
|
18488
|
-
getMaxInputDenomination() {
|
|
18489
|
-
return this.selectedUTXOs.reduce((max, utxo) => {
|
|
18490
|
-
const denomValue = BigInt(denominations[utxo.denomination]);
|
|
18491
|
-
return denomValue > max ? denomValue : max;
|
|
18492
|
-
}, BigInt(0));
|
|
18493
|
-
}
|
|
18494
18528
|
/**
|
|
18495
18529
|
* Helper method to adjust change outputs.
|
|
18496
18530
|
*
|
|
@@ -27070,6 +27104,15 @@ class HDNodeBIP32Adapter {
|
|
|
27070
27104
|
}
|
|
27071
27105
|
}
|
|
27072
27106
|
|
|
27107
|
+
/**
|
|
27108
|
+
* Current known issues:
|
|
27109
|
+
*
|
|
27110
|
+
* - When generating send addresses we are not checking if the address has already been used before
|
|
27111
|
+
* - When syncing is seems like we are adding way too many change addresses
|
|
27112
|
+
* - Bip44 external and change address maps also have gap addresses in them
|
|
27113
|
+
* - It is unclear if we have checked if addresses have been used and if they are used
|
|
27114
|
+
* - We should always check all addresses that were previously included in a transaction to see if they have been used
|
|
27115
|
+
*/
|
|
27073
27116
|
/**
|
|
27074
27117
|
* The Qi HD wallet is a BIP44-compliant hierarchical deterministic wallet used for managing a set of addresses in the
|
|
27075
27118
|
* Qi ledger. This is wallet implementation is the primary way to interact with the Qi UTXO ledger on the Quai network.
|
|
@@ -27107,7 +27150,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27107
27150
|
* @ignore
|
|
27108
27151
|
* @type {number}
|
|
27109
27152
|
*/
|
|
27110
|
-
static _GAP_LIMIT =
|
|
27153
|
+
static _GAP_LIMIT = 5;
|
|
27111
27154
|
/**
|
|
27112
27155
|
* @ignore
|
|
27113
27156
|
* @type {AllowedCoinType}
|
|
@@ -27134,19 +27177,44 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27134
27177
|
* @type {NeuteredAddressInfo[]}
|
|
27135
27178
|
*/
|
|
27136
27179
|
_gapAddresses = [];
|
|
27180
|
+
/**
|
|
27181
|
+
* This array is used to keep track of gap addresses that have been included in a transaction, but whose outpoints
|
|
27182
|
+
* have not been imported into the wallet.
|
|
27183
|
+
*
|
|
27184
|
+
* @ignore
|
|
27185
|
+
* @type {NeuteredAddressInfo[]}
|
|
27186
|
+
*/
|
|
27187
|
+
_usedGapAddresses = [];
|
|
27188
|
+
/**
|
|
27189
|
+
* This array is used to keep track of gap change addresses that have been included in a transaction, but whose
|
|
27190
|
+
* outpoints have not been imported into the wallet.
|
|
27191
|
+
*
|
|
27192
|
+
* @ignore
|
|
27193
|
+
* @type {NeuteredAddressInfo[]}
|
|
27194
|
+
*/
|
|
27195
|
+
_usedGapChangeAddresses = [];
|
|
27137
27196
|
/**
|
|
27138
27197
|
* Array of outpoint information.
|
|
27139
27198
|
*
|
|
27140
27199
|
* @ignore
|
|
27141
27200
|
* @type {OutpointInfo[]}
|
|
27142
27201
|
*/
|
|
27143
|
-
|
|
27202
|
+
_availableOutpoints = [];
|
|
27144
27203
|
/**
|
|
27145
|
-
* Map of
|
|
27204
|
+
* Map of outpoints that are pending confirmation of being spent.
|
|
27205
|
+
*/
|
|
27206
|
+
_pendingOutpoints = [];
|
|
27207
|
+
/**
|
|
27208
|
+
* @ignore
|
|
27209
|
+
* @type {AddressUsageCallback}
|
|
27210
|
+
*/
|
|
27211
|
+
_addressUseChecker;
|
|
27212
|
+
/**
|
|
27213
|
+
* Map of paymentcodes to PaymentChannelAddressInfo for the receiver
|
|
27146
27214
|
*/
|
|
27147
27215
|
_receiverPaymentCodeInfo = new Map();
|
|
27148
27216
|
/**
|
|
27149
|
-
* Map of paymentcodes to
|
|
27217
|
+
* Map of paymentcodes to PaymentChannelAddressInfo for the sender
|
|
27150
27218
|
*/
|
|
27151
27219
|
_senderPaymentCodeInfo = new Map();
|
|
27152
27220
|
/**
|
|
@@ -27157,6 +27225,16 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27157
27225
|
constructor(guard, root, provider) {
|
|
27158
27226
|
super(guard, root, provider);
|
|
27159
27227
|
}
|
|
27228
|
+
/**
|
|
27229
|
+
* Sets the address use checker. The provided callback function should accept an address as input and return a
|
|
27230
|
+
* boolean indicating whether the address is in use. If the callback returns true, the address is considered used
|
|
27231
|
+
* and if it returns false, the address is considered unused.
|
|
27232
|
+
*
|
|
27233
|
+
* @param {AddressUsageCallback} checker - The address use checker.
|
|
27234
|
+
*/
|
|
27235
|
+
setAddressUseChecker(checker) {
|
|
27236
|
+
this._addressUseChecker = checker;
|
|
27237
|
+
}
|
|
27160
27238
|
// getters for the payment code info maps
|
|
27161
27239
|
get receiverPaymentCodeInfo() {
|
|
27162
27240
|
return Object.fromEntries(this._receiverPaymentCodeInfo);
|
|
@@ -27191,7 +27269,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27191
27269
|
*/
|
|
27192
27270
|
importOutpoints(outpoints) {
|
|
27193
27271
|
this.validateOutpointInfo(outpoints);
|
|
27194
|
-
this.
|
|
27272
|
+
this._availableOutpoints.push(...outpoints);
|
|
27195
27273
|
}
|
|
27196
27274
|
/**
|
|
27197
27275
|
* Gets the outpoints for the specified zone.
|
|
@@ -27201,7 +27279,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27201
27279
|
*/
|
|
27202
27280
|
getOutpoints(zone) {
|
|
27203
27281
|
this.validateZone(zone);
|
|
27204
|
-
return this.
|
|
27282
|
+
return this._availableOutpoints.filter((outpoint) => outpoint.zone === zone);
|
|
27205
27283
|
}
|
|
27206
27284
|
/**
|
|
27207
27285
|
* Signs a Qi transaction and returns the serialized transaction.
|
|
@@ -27231,6 +27309,47 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27231
27309
|
txobj.signature = signature;
|
|
27232
27310
|
return txobj.serialized;
|
|
27233
27311
|
}
|
|
27312
|
+
/**
|
|
27313
|
+
* Gets the payment channel address info for a given address.
|
|
27314
|
+
*
|
|
27315
|
+
* @param {string} address - The address to look up.
|
|
27316
|
+
* @returns {PaymentChannelAddressInfo | null} The address info or null if not found.
|
|
27317
|
+
*/
|
|
27318
|
+
getPaymentChannelAddressInfo(address) {
|
|
27319
|
+
for (const [paymentCode, pcInfoArray] of this._receiverPaymentCodeInfo.entries()) {
|
|
27320
|
+
const pcInfo = pcInfoArray.find((info) => info.address === address);
|
|
27321
|
+
if (pcInfo) {
|
|
27322
|
+
return { ...pcInfo, counterpartyPaymentCode: paymentCode };
|
|
27323
|
+
}
|
|
27324
|
+
}
|
|
27325
|
+
return null;
|
|
27326
|
+
}
|
|
27327
|
+
/**
|
|
27328
|
+
* Locates the address information for the given address, searching through standard addresses, change addresses,
|
|
27329
|
+
* and payment channel addresses.
|
|
27330
|
+
*
|
|
27331
|
+
* @param {string} address - The address to locate.
|
|
27332
|
+
* @returns {NeuteredAddressInfo | PaymentChannelAddressInfo | null} The address info or null if not found.
|
|
27333
|
+
*/
|
|
27334
|
+
locateAddressInfo(address) {
|
|
27335
|
+
// First, try to get standard address info
|
|
27336
|
+
let addressInfo = this.getAddressInfo(address);
|
|
27337
|
+
if (addressInfo) {
|
|
27338
|
+
return addressInfo;
|
|
27339
|
+
}
|
|
27340
|
+
// Next, try to get change address info
|
|
27341
|
+
addressInfo = this.getChangeAddressInfo(address);
|
|
27342
|
+
if (addressInfo) {
|
|
27343
|
+
return addressInfo;
|
|
27344
|
+
}
|
|
27345
|
+
// Finally, try to get payment channel address info
|
|
27346
|
+
const pcAddressInfo = this.getPaymentChannelAddressInfo(address);
|
|
27347
|
+
if (pcAddressInfo) {
|
|
27348
|
+
return pcAddressInfo;
|
|
27349
|
+
}
|
|
27350
|
+
// Address not found
|
|
27351
|
+
return null;
|
|
27352
|
+
}
|
|
27234
27353
|
/**
|
|
27235
27354
|
* Gets the balance for the specified zone.
|
|
27236
27355
|
*
|
|
@@ -27239,7 +27358,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27239
27358
|
*/
|
|
27240
27359
|
getBalanceForZone(zone) {
|
|
27241
27360
|
this.validateZone(zone);
|
|
27242
|
-
return this.
|
|
27361
|
+
return this._availableOutpoints
|
|
27243
27362
|
.filter((outpoint) => outpoint.zone === zone)
|
|
27244
27363
|
.reduce((total, outpoint) => {
|
|
27245
27364
|
const denominationValue = denominations[outpoint.outpoint.denomination];
|
|
@@ -27254,7 +27373,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27254
27373
|
*/
|
|
27255
27374
|
outpointsToUTXOs(zone) {
|
|
27256
27375
|
this.validateZone(zone);
|
|
27257
|
-
return this.
|
|
27376
|
+
return this._availableOutpoints
|
|
27258
27377
|
.filter((outpointInfo) => outpointInfo.zone === zone)
|
|
27259
27378
|
.map((outpointInfo) => {
|
|
27260
27379
|
const utxo = new UTXO();
|
|
@@ -27296,17 +27415,32 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27296
27415
|
let selection = fewestCoinSelector.performSelection(spendTarget);
|
|
27297
27416
|
// 3. Generate as many unused addresses as required to populate the spend outputs
|
|
27298
27417
|
const sendAddresses = [];
|
|
27299
|
-
|
|
27300
|
-
|
|
27418
|
+
while (sendAddresses.length < selection.spendOutputs.length) {
|
|
27419
|
+
const address = this.getNextSendAddress(recipientPaymentCode, destinationZone).address;
|
|
27420
|
+
const { isUsed } = await this.checkAddressUse(address);
|
|
27421
|
+
if (!isUsed) {
|
|
27422
|
+
sendAddresses.push(address);
|
|
27423
|
+
}
|
|
27301
27424
|
}
|
|
27302
|
-
// 4.
|
|
27425
|
+
// 4. get known change addresses, then populate with new ones as needed
|
|
27303
27426
|
const changeAddresses = [];
|
|
27304
27427
|
for (let i = 0; i < selection.changeOutputs.length; i++) {
|
|
27305
|
-
|
|
27428
|
+
if (this._gapChangeAddresses.length > 0) {
|
|
27429
|
+
// 1. get next change address from gap addresses array
|
|
27430
|
+
// 2. remove it from the gap change addresses array
|
|
27431
|
+
// 3. add it to the change addresses array
|
|
27432
|
+
// 4. add it to the used gap change addresses array
|
|
27433
|
+
const nextChangeAddressInfo = this._gapChangeAddresses.shift();
|
|
27434
|
+
changeAddresses.push(nextChangeAddressInfo.address);
|
|
27435
|
+
this._usedGapChangeAddresses.push(nextChangeAddressInfo);
|
|
27436
|
+
}
|
|
27437
|
+
else {
|
|
27438
|
+
changeAddresses.push((await this.getNextChangeAddress(0, originZone)).address);
|
|
27439
|
+
}
|
|
27306
27440
|
}
|
|
27307
27441
|
// 5. Create the transaction and sign it using the signTransaction method
|
|
27308
27442
|
// 5.1 Fetch the public keys for the input addresses
|
|
27309
|
-
let inputPubKeys = selection.inputs.map((input) => this.
|
|
27443
|
+
let inputPubKeys = selection.inputs.map((input) => this.locateAddressInfo(input.address)?.pubKey);
|
|
27310
27444
|
if (inputPubKeys.some((pubkey) => !pubkey)) {
|
|
27311
27445
|
throw new Error('Missing public key for input address');
|
|
27312
27446
|
}
|
|
@@ -27316,23 +27450,25 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27316
27450
|
const feeData = await this.provider.getFeeData(originZone, false);
|
|
27317
27451
|
// 5.6 Calculate total fee for the transaction using the gasLimit, gasPrice, maxFeePerGas and maxPriorityFeePerGas
|
|
27318
27452
|
const totalFee = gasLimit * (feeData.gasPrice ?? 1n) + (feeData.maxFeePerGas ?? 0n) + (feeData.maxPriorityFeePerGas ?? 0n);
|
|
27319
|
-
// Get new selection with
|
|
27320
|
-
selection = fewestCoinSelector.
|
|
27453
|
+
// Get new selection with fee
|
|
27454
|
+
selection = fewestCoinSelector.performSelection(spendTarget, totalFee);
|
|
27321
27455
|
// 5.7 Determine if new addresses are needed for the change outputs
|
|
27322
|
-
const changeAddressesNeeded = selection.changeOutputs.length
|
|
27323
|
-
if (changeAddressesNeeded) {
|
|
27324
|
-
for (let i = 0; i <
|
|
27456
|
+
const changeAddressesNeeded = selection.changeOutputs.length - changeAddresses.length;
|
|
27457
|
+
if (changeAddressesNeeded > 0) {
|
|
27458
|
+
for (let i = 0; i < changeAddressesNeeded; i++) {
|
|
27325
27459
|
changeAddresses.push((await this.getNextChangeAddress(0, originZone)).address);
|
|
27326
27460
|
}
|
|
27327
27461
|
}
|
|
27328
|
-
const spendAddressesNeeded = selection.spendOutputs.length
|
|
27329
|
-
if (spendAddressesNeeded) {
|
|
27330
|
-
for (let i = 0; i <
|
|
27331
|
-
sendAddresses.push(
|
|
27462
|
+
const spendAddressesNeeded = selection.spendOutputs.length - sendAddresses.length;
|
|
27463
|
+
if (spendAddressesNeeded > 0) {
|
|
27464
|
+
for (let i = 0; i < spendAddressesNeeded; i++) {
|
|
27465
|
+
sendAddresses.push(this.getNextSendAddress(recipientPaymentCode, destinationZone).address);
|
|
27332
27466
|
}
|
|
27333
27467
|
}
|
|
27334
|
-
inputPubKeys = selection.inputs.map((input) => this.
|
|
27468
|
+
inputPubKeys = selection.inputs.map((input) => this.locateAddressInfo(input.address)?.pubKey);
|
|
27335
27469
|
tx = await this.prepareTransaction(selection, inputPubKeys.map((pubkey) => pubkey), sendAddresses, changeAddresses, Number(chainId));
|
|
27470
|
+
// Move used outpoints to pendingOutpoints
|
|
27471
|
+
this.moveOutpointsToPending(tx.txInputs);
|
|
27336
27472
|
// 5.6 Sign the transaction
|
|
27337
27473
|
const signedTx = await this.signTransaction(tx);
|
|
27338
27474
|
// 6. Broadcast the transaction to the network using the provider
|
|
@@ -27362,6 +27498,60 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27362
27498
|
tx.chainId = chainId;
|
|
27363
27499
|
return tx;
|
|
27364
27500
|
}
|
|
27501
|
+
/**
|
|
27502
|
+
* Checks the status of pending outpoints and updates the wallet's UTXO set accordingly.
|
|
27503
|
+
*
|
|
27504
|
+
* @param zone The zone in which to check the pending outpoints.
|
|
27505
|
+
*/
|
|
27506
|
+
async checkPendingOutpoints(zone) {
|
|
27507
|
+
// Create a copy to iterate over, as we'll be modifying the _pendingOutpoints array
|
|
27508
|
+
const pendingOutpoints = [...this._pendingOutpoints.filter((info) => info.zone === zone)];
|
|
27509
|
+
const uniqueAddresses = new Set(pendingOutpoints.map((info) => info.address));
|
|
27510
|
+
const outpointsByAddress = await Promise.all(Array.from(uniqueAddresses).map((address) => this.getOutpointsByAddress(address)));
|
|
27511
|
+
const allOutpointsByAddress = outpointsByAddress.flat();
|
|
27512
|
+
for (const outpointInfo of pendingOutpoints) {
|
|
27513
|
+
const isSpent = !allOutpointsByAddress.some((outpoint) => outpoint.txhash === outpointInfo.outpoint.txhash && outpoint.index === outpointInfo.outpoint.index);
|
|
27514
|
+
if (isSpent) {
|
|
27515
|
+
// Outpoint has been spent; remove it from pendingOutpoints
|
|
27516
|
+
this.removeOutpointFromPending(outpointInfo.outpoint);
|
|
27517
|
+
}
|
|
27518
|
+
else {
|
|
27519
|
+
// Outpoint is still unspent; move it back to available outpoints
|
|
27520
|
+
this.moveOutpointToAvailable(outpointInfo);
|
|
27521
|
+
}
|
|
27522
|
+
}
|
|
27523
|
+
}
|
|
27524
|
+
/**
|
|
27525
|
+
* Moves specified inputs to pending outpoints.
|
|
27526
|
+
*
|
|
27527
|
+
* @param inputs List of inputs used in the transaction.
|
|
27528
|
+
*/
|
|
27529
|
+
moveOutpointsToPending(inputs) {
|
|
27530
|
+
inputs.forEach((input) => {
|
|
27531
|
+
const index = this._availableOutpoints.findIndex((outpointInfo) => outpointInfo.outpoint.txhash === input.txhash && outpointInfo.outpoint.index === input.index);
|
|
27532
|
+
if (index !== -1) {
|
|
27533
|
+
const [outpointInfo] = this._availableOutpoints.splice(index, 1);
|
|
27534
|
+
this._pendingOutpoints.push(outpointInfo);
|
|
27535
|
+
}
|
|
27536
|
+
});
|
|
27537
|
+
}
|
|
27538
|
+
/**
|
|
27539
|
+
* Removes an outpoint from the pending outpoints.
|
|
27540
|
+
*
|
|
27541
|
+
* @param outpoint The outpoint to remove.
|
|
27542
|
+
*/
|
|
27543
|
+
removeOutpointFromPending(outpoint) {
|
|
27544
|
+
this._pendingOutpoints = this._pendingOutpoints.filter((info) => !(info.outpoint.txhash === outpoint.txhash && info.outpoint.index === outpoint.index));
|
|
27545
|
+
}
|
|
27546
|
+
/**
|
|
27547
|
+
* Moves an outpoint from pending back to available outpoints.
|
|
27548
|
+
*
|
|
27549
|
+
* @param outpointInfo The outpoint info to move.
|
|
27550
|
+
*/
|
|
27551
|
+
moveOutpointToAvailable(outpointInfo) {
|
|
27552
|
+
this.removeOutpointFromPending(outpointInfo.outpoint);
|
|
27553
|
+
this._availableOutpoints.push(outpointInfo);
|
|
27554
|
+
}
|
|
27365
27555
|
/**
|
|
27366
27556
|
* Returns a schnorr signature for the given message and private key.
|
|
27367
27557
|
*
|
|
@@ -27414,15 +27604,12 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27414
27604
|
/**
|
|
27415
27605
|
* Retrieves the private key for a given transaction input.
|
|
27416
27606
|
*
|
|
27417
|
-
* This method derives the private key for a transaction input by
|
|
27607
|
+
* This method derives the private key for a transaction input by locating the address info and then deriving the
|
|
27608
|
+
* private key based on where the address info was found:
|
|
27418
27609
|
*
|
|
27419
|
-
*
|
|
27420
|
-
*
|
|
27421
|
-
* 3. Fetches address information associated with the computed address.
|
|
27422
|
-
* 4. Derives the hierarchical deterministic (HD) node corresponding to the address.
|
|
27423
|
-
* 5. Returns the private key of the derived HD node.
|
|
27610
|
+
* - For BIP44 addresses (standard or change), it uses the HD wallet to derive the private key.
|
|
27611
|
+
* - For payment channel addresses (BIP47), it uses PaymentCodePrivate to derive the private key.
|
|
27424
27612
|
*
|
|
27425
|
-
* @ignore
|
|
27426
27613
|
* @param {TxInput} input - The transaction input containing the public key.
|
|
27427
27614
|
* @returns {string} The private key corresponding to the transaction input.
|
|
27428
27615
|
* @throws {Error} If the input does not contain a public key or if the address information cannot be found.
|
|
@@ -27431,21 +27618,42 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27431
27618
|
if (!input.pubkey)
|
|
27432
27619
|
throw new Error('Missing public key for input');
|
|
27433
27620
|
const address = computeAddress(input.pubkey);
|
|
27434
|
-
|
|
27435
|
-
|
|
27436
|
-
if (!addressInfo)
|
|
27621
|
+
const addressInfo = this.locateAddressInfo(address);
|
|
27622
|
+
if (!addressInfo) {
|
|
27437
27623
|
throw new Error(`Address not found: ${address}`);
|
|
27438
|
-
|
|
27439
|
-
|
|
27440
|
-
|
|
27441
|
-
|
|
27442
|
-
.
|
|
27443
|
-
|
|
27444
|
-
|
|
27624
|
+
}
|
|
27625
|
+
if ('change' in addressInfo) {
|
|
27626
|
+
// NeuteredAddressInfo (BIP44 addresses)
|
|
27627
|
+
const changeIndex = addressInfo.change ? 1 : 0;
|
|
27628
|
+
const addressNode = this._root
|
|
27629
|
+
.deriveChild(addressInfo.account)
|
|
27630
|
+
.deriveChild(changeIndex)
|
|
27631
|
+
.deriveChild(addressInfo.index);
|
|
27632
|
+
return addressNode.privateKey;
|
|
27633
|
+
}
|
|
27634
|
+
else {
|
|
27635
|
+
// PaymentChannelAddressInfo (BIP47 addresses)
|
|
27636
|
+
const pcAddressInfo = addressInfo;
|
|
27637
|
+
const account = pcAddressInfo.account;
|
|
27638
|
+
const index = pcAddressInfo.index - 1;
|
|
27639
|
+
const counterpartyPaymentCode = pcAddressInfo.counterpartyPaymentCode;
|
|
27640
|
+
if (!counterpartyPaymentCode) {
|
|
27641
|
+
throw new Error('Counterparty payment code not found for payment channel address');
|
|
27642
|
+
}
|
|
27643
|
+
const bip32 = BIP32Factory(ecc);
|
|
27644
|
+
const buf = bs58check.decode(counterpartyPaymentCode);
|
|
27645
|
+
const version = buf[0];
|
|
27646
|
+
if (version !== PC_VERSION)
|
|
27647
|
+
throw new Error('Invalid payment code version');
|
|
27648
|
+
const counterpartyPCodePublic = new PaymentCodePublic(ecc, bip32, buf.slice(1));
|
|
27649
|
+
const paymentCodePrivate = this._getPaymentCodePrivate(account);
|
|
27650
|
+
const paymentPrivateKey = paymentCodePrivate.derivePaymentPrivateKey(counterpartyPCodePublic, index);
|
|
27651
|
+
return hexlify(paymentPrivateKey);
|
|
27652
|
+
}
|
|
27445
27653
|
}
|
|
27446
27654
|
/**
|
|
27447
27655
|
* Scans the specified zone for addresses with unspent outputs. Starting at index 0, it will generate new addresses
|
|
27448
|
-
* until the gap limit is reached for
|
|
27656
|
+
* until the gap limit is reached for external and change BIP44 addresses and payment channel addresses.
|
|
27449
27657
|
*
|
|
27450
27658
|
* @param {Zone} zone - The zone in which to scan for addresses.
|
|
27451
27659
|
* @param {number} [account=0] - The index of the account to scan. Default is `0`
|
|
@@ -27459,38 +27667,28 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27459
27667
|
this._changeAddresses = new Map();
|
|
27460
27668
|
this._gapAddresses = [];
|
|
27461
27669
|
this._gapChangeAddresses = [];
|
|
27462
|
-
this.
|
|
27670
|
+
this._availableOutpoints = [];
|
|
27671
|
+
// Reset each map so that all keys have empty array values but keys are preserved
|
|
27672
|
+
const resetSenderPaymentCodeInfo = new Map(Array.from(this._senderPaymentCodeInfo.keys()).map((key) => [key, []]));
|
|
27673
|
+
const resetReceiverPaymentCodeInfo = new Map(Array.from(this._receiverPaymentCodeInfo.keys()).map((key) => [key, []]));
|
|
27674
|
+
this._senderPaymentCodeInfo = resetSenderPaymentCodeInfo;
|
|
27675
|
+
this._receiverPaymentCodeInfo = resetReceiverPaymentCodeInfo;
|
|
27463
27676
|
await this._scan(zone, account);
|
|
27464
27677
|
}
|
|
27465
27678
|
/**
|
|
27466
27679
|
* Scans the specified zone for addresses with unspent outputs. Starting at the last address index, it will generate
|
|
27467
|
-
* new addresses until the gap limit is reached for
|
|
27468
|
-
*
|
|
27680
|
+
* new addresses until the gap limit is reached for external and change BIP44 addresses and payment channel
|
|
27681
|
+
* addresses.
|
|
27469
27682
|
*
|
|
27470
27683
|
* @param {Zone} zone - The zone in which to sync addresses.
|
|
27471
|
-
* @param {number} [account] - The index of the account to sync.
|
|
27684
|
+
* @param {number} [account=0] - The index of the account to sync. Default is `0`
|
|
27472
27685
|
* @returns {Promise<void>} A promise that resolves when the sync is complete.
|
|
27473
27686
|
* @throws {Error} If the zone is invalid.
|
|
27474
27687
|
*/
|
|
27475
|
-
async sync(zone, account) {
|
|
27688
|
+
async sync(zone, account = 0) {
|
|
27476
27689
|
this.validateZone(zone);
|
|
27477
|
-
|
|
27478
|
-
|
|
27479
|
-
const addressInfos = Array.from(this._addresses.values());
|
|
27480
|
-
const accounts = addressInfos.reduce((unique, info) => {
|
|
27481
|
-
if (!unique.includes(info.account)) {
|
|
27482
|
-
unique.push(info.account);
|
|
27483
|
-
}
|
|
27484
|
-
return unique;
|
|
27485
|
-
}, []);
|
|
27486
|
-
for (const acc of accounts) {
|
|
27487
|
-
await this._scan(zone, acc);
|
|
27488
|
-
}
|
|
27489
|
-
}
|
|
27490
|
-
else {
|
|
27491
|
-
await this._scan(zone, account);
|
|
27492
|
-
}
|
|
27493
|
-
return;
|
|
27690
|
+
await this._scan(zone, account);
|
|
27691
|
+
await this.checkPendingOutpoints(zone);
|
|
27494
27692
|
}
|
|
27495
27693
|
/**
|
|
27496
27694
|
* Internal method to scan the specified zone for addresses with unspent outputs. This method handles the actual
|
|
@@ -27504,18 +27702,17 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27504
27702
|
async _scan(zone, account = 0) {
|
|
27505
27703
|
if (!this.provider)
|
|
27506
27704
|
throw new Error('Provider not set');
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27510
|
-
|
|
27511
|
-
|
|
27512
|
-
|
|
27513
|
-
|
|
27514
|
-
|
|
27515
|
-
? this.scanAddress(zone, account, true, changeGapAddressesCount)
|
|
27516
|
-
: changeGapAddressesCount,
|
|
27517
|
-
]);
|
|
27705
|
+
// Start scanning processes for each derivation tree
|
|
27706
|
+
const scans = [
|
|
27707
|
+
this.scanBIP44Addresses(zone, account, false),
|
|
27708
|
+
this.scanBIP44Addresses(zone, account, true), // Change addresses
|
|
27709
|
+
];
|
|
27710
|
+
// Add scanning processes for each payment channel
|
|
27711
|
+
for (const paymentCode of this._receiverPaymentCodeInfo.keys()) {
|
|
27712
|
+
scans.push(this.scanPaymentChannel(zone, account, paymentCode));
|
|
27518
27713
|
}
|
|
27714
|
+
// Run all scans in parallel
|
|
27715
|
+
await Promise.all(scans);
|
|
27519
27716
|
}
|
|
27520
27717
|
/**
|
|
27521
27718
|
* Scans for the next address in the specified zone and account, checking for associated outpoints, and updates the
|
|
@@ -27524,29 +27721,169 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27524
27721
|
* @param {Zone} zone - The zone in which the address is being scanned.
|
|
27525
27722
|
* @param {number} account - The index of the account for which the address is being scanned.
|
|
27526
27723
|
* @param {boolean} isChange - A flag indicating whether the address is a change address.
|
|
27527
|
-
* @
|
|
27528
|
-
* @returns {Promise<number>} A promise that resolves to the updated address count.
|
|
27724
|
+
* @returns {Promise<void>} A promise that resolves when the scan is complete.
|
|
27529
27725
|
* @throws {Error} If an error occurs during the address scanning or outpoints retrieval process.
|
|
27530
27726
|
*/
|
|
27531
|
-
async
|
|
27727
|
+
async scanBIP44Addresses(zone, account, isChange) {
|
|
27532
27728
|
const addressMap = isChange ? this._changeAddresses : this._addresses;
|
|
27533
|
-
const
|
|
27534
|
-
const
|
|
27535
|
-
|
|
27536
|
-
|
|
27537
|
-
|
|
27538
|
-
|
|
27539
|
-
|
|
27540
|
-
|
|
27541
|
-
|
|
27542
|
-
|
|
27543
|
-
|
|
27729
|
+
const gapAddresses = isChange ? this._gapChangeAddresses : this._gapAddresses;
|
|
27730
|
+
const usedGapAddresses = isChange ? this._usedGapChangeAddresses : this._usedGapAddresses;
|
|
27731
|
+
// First, add all used gap addresses to the address map and import their outpoints
|
|
27732
|
+
for (const addressInfo of usedGapAddresses) {
|
|
27733
|
+
this._addAddress(addressMap, account, addressInfo.index, isChange);
|
|
27734
|
+
const outpoints = await this.getOutpointsByAddress(addressInfo.address);
|
|
27735
|
+
if (outpoints.length > 0) {
|
|
27736
|
+
this.importOutpoints(outpoints.map((outpoint) => ({
|
|
27737
|
+
outpoint,
|
|
27738
|
+
address: addressInfo.address,
|
|
27739
|
+
zone,
|
|
27740
|
+
account,
|
|
27741
|
+
})));
|
|
27742
|
+
}
|
|
27743
|
+
}
|
|
27744
|
+
let gapCount = 0;
|
|
27745
|
+
// Second, re-examine existing gap addresses
|
|
27746
|
+
const newlyUsedAddresses = [];
|
|
27747
|
+
for (let i = 0; i < gapAddresses.length;) {
|
|
27748
|
+
const addressInfo = gapAddresses[i];
|
|
27749
|
+
const { isUsed, outpoints } = await this.checkAddressUse(addressInfo.address);
|
|
27750
|
+
if (isUsed) {
|
|
27751
|
+
// Address has been used since last scan
|
|
27752
|
+
this._addAddress(addressMap, account, addressInfo.index, isChange);
|
|
27753
|
+
if (outpoints.length > 0) {
|
|
27754
|
+
this.importOutpoints(outpoints.map((outpoint) => ({
|
|
27755
|
+
outpoint,
|
|
27756
|
+
address: addressInfo.address,
|
|
27757
|
+
zone,
|
|
27758
|
+
account,
|
|
27759
|
+
})));
|
|
27760
|
+
}
|
|
27761
|
+
// Remove from gap addresses
|
|
27762
|
+
newlyUsedAddresses.push(addressInfo);
|
|
27763
|
+
gapCount = 0;
|
|
27764
|
+
}
|
|
27765
|
+
else {
|
|
27766
|
+
gapCount++;
|
|
27767
|
+
i++;
|
|
27768
|
+
}
|
|
27769
|
+
}
|
|
27770
|
+
// remove addresses that have been used from the gap addresses
|
|
27771
|
+
const updatedGapAddresses = gapAddresses.filter((addressInfo) => !newlyUsedAddresses.some((usedAddress) => usedAddress.address === addressInfo.address));
|
|
27772
|
+
// Scan for new gap addresses
|
|
27773
|
+
const newGapAddresses = [];
|
|
27774
|
+
while (gapCount < QiHDWallet._GAP_LIMIT) {
|
|
27775
|
+
const addressInfo = this._getNextAddress(account, zone, isChange, addressMap);
|
|
27776
|
+
const { isUsed, outpoints } = await this.checkAddressUse(addressInfo.address);
|
|
27777
|
+
if (isUsed) {
|
|
27778
|
+
if (outpoints.length > 0) {
|
|
27779
|
+
this.importOutpoints(outpoints.map((outpoint) => ({
|
|
27780
|
+
outpoint,
|
|
27781
|
+
address: addressInfo.address,
|
|
27782
|
+
zone,
|
|
27783
|
+
account,
|
|
27784
|
+
})));
|
|
27785
|
+
}
|
|
27786
|
+
gapCount = 0;
|
|
27787
|
+
}
|
|
27788
|
+
else {
|
|
27789
|
+
gapCount++;
|
|
27790
|
+
// check if the address is already in the updated gap addresses array
|
|
27791
|
+
if (!updatedGapAddresses.some((usedAddress) => usedAddress.address === addressInfo.address)) {
|
|
27792
|
+
newGapAddresses.push(addressInfo);
|
|
27793
|
+
}
|
|
27794
|
+
}
|
|
27795
|
+
}
|
|
27796
|
+
// update the gap addresses
|
|
27797
|
+
if (isChange) {
|
|
27798
|
+
this._gapChangeAddresses = [...updatedGapAddresses, ...newGapAddresses];
|
|
27544
27799
|
}
|
|
27545
27800
|
else {
|
|
27546
|
-
|
|
27547
|
-
|
|
27801
|
+
this._gapAddresses = [...updatedGapAddresses, ...newGapAddresses];
|
|
27802
|
+
}
|
|
27803
|
+
}
|
|
27804
|
+
/**
|
|
27805
|
+
* Scans the specified payment channel for addresses with unspent outputs. Starting at the last address index, it
|
|
27806
|
+
* will generate new addresses until the gap limit is reached.
|
|
27807
|
+
*
|
|
27808
|
+
* @param {Zone} zone - The zone in which to scan for addresses.
|
|
27809
|
+
* @param {number} account - The index of the account to scan.
|
|
27810
|
+
* @param {string} paymentCode - The payment code to scan.
|
|
27811
|
+
* @returns {Promise<void>} A promise that resolves when the scan is complete.
|
|
27812
|
+
* @throws {Error} If the zone is invalid.
|
|
27813
|
+
*/
|
|
27814
|
+
async scanPaymentChannel(zone, account, paymentCode) {
|
|
27815
|
+
let gapCount = 0;
|
|
27816
|
+
const paymentCodeInfoArray = this._receiverPaymentCodeInfo.get(paymentCode);
|
|
27817
|
+
if (!paymentCodeInfoArray) {
|
|
27818
|
+
throw new Error(`Payment code ${paymentCode} not found`);
|
|
27819
|
+
}
|
|
27820
|
+
// first, re-examine existing unused addresses
|
|
27821
|
+
const newlyUsedAddresses = [];
|
|
27822
|
+
const unusedAddresses = paymentCodeInfoArray.filter((info) => !info.isUsed);
|
|
27823
|
+
for (let i = 0; i < unusedAddresses.length;) {
|
|
27824
|
+
const addressInfo = unusedAddresses[i];
|
|
27825
|
+
const { isUsed, outpoints } = await this.checkAddressUse(addressInfo.address);
|
|
27826
|
+
if (outpoints.length > 0 || isUsed) {
|
|
27827
|
+
// Address has been used since last scan
|
|
27828
|
+
addressInfo.isUsed = true;
|
|
27829
|
+
const pcAddressInfoIndex = paymentCodeInfoArray.findIndex((info) => info.index === addressInfo.index);
|
|
27830
|
+
paymentCodeInfoArray[pcAddressInfoIndex] = addressInfo;
|
|
27831
|
+
this.importOutpoints(outpoints.map((outpoint) => ({
|
|
27832
|
+
outpoint,
|
|
27833
|
+
address: addressInfo.address,
|
|
27834
|
+
zone,
|
|
27835
|
+
account,
|
|
27836
|
+
})));
|
|
27837
|
+
// Remove from gap addresses
|
|
27838
|
+
newlyUsedAddresses.push(addressInfo);
|
|
27839
|
+
gapCount = 0;
|
|
27840
|
+
}
|
|
27841
|
+
else {
|
|
27842
|
+
// Address is still unused
|
|
27843
|
+
gapCount++;
|
|
27844
|
+
i++;
|
|
27845
|
+
}
|
|
27846
|
+
}
|
|
27847
|
+
// remove the addresses that have been used from the payment code info array
|
|
27848
|
+
const updatedPaymentCodeInfoArray = paymentCodeInfoArray.filter((addressInfo) => !newlyUsedAddresses.some((usedAddress) => usedAddress.index === addressInfo.index));
|
|
27849
|
+
// Then, scan for new gap addresses
|
|
27850
|
+
while (gapCount < QiHDWallet._GAP_LIMIT) {
|
|
27851
|
+
const pcAddressInfo = this.getNextReceiveAddress(paymentCode, zone, account);
|
|
27852
|
+
const outpoints = await this.getOutpointsByAddress(pcAddressInfo.address);
|
|
27853
|
+
let isUsed = false;
|
|
27854
|
+
if (outpoints.length > 0) {
|
|
27855
|
+
isUsed = true;
|
|
27856
|
+
this.importOutpoints(outpoints.map((outpoint) => ({
|
|
27857
|
+
outpoint,
|
|
27858
|
+
address: pcAddressInfo.address,
|
|
27859
|
+
zone,
|
|
27860
|
+
account,
|
|
27861
|
+
})));
|
|
27862
|
+
gapCount = 0;
|
|
27863
|
+
}
|
|
27864
|
+
else if (this._addressUseChecker !== undefined &&
|
|
27865
|
+
(await this._addressUseChecker(pcAddressInfo.address))) {
|
|
27866
|
+
// address checker returned true, so the address is used
|
|
27867
|
+
isUsed = true;
|
|
27868
|
+
gapCount = 0;
|
|
27869
|
+
}
|
|
27870
|
+
else {
|
|
27871
|
+
gapCount++;
|
|
27872
|
+
}
|
|
27873
|
+
if (isUsed) {
|
|
27874
|
+
// update the payment code info array if the address has been used
|
|
27875
|
+
pcAddressInfo.isUsed = isUsed;
|
|
27876
|
+
const pcAddressInfoIndex = updatedPaymentCodeInfoArray.findIndex((info) => info.index === pcAddressInfo.index);
|
|
27877
|
+
if (pcAddressInfoIndex !== -1) {
|
|
27878
|
+
updatedPaymentCodeInfoArray[pcAddressInfoIndex] = pcAddressInfo;
|
|
27879
|
+
}
|
|
27880
|
+
else {
|
|
27881
|
+
updatedPaymentCodeInfoArray.push(pcAddressInfo);
|
|
27882
|
+
}
|
|
27883
|
+
}
|
|
27548
27884
|
}
|
|
27549
|
-
|
|
27885
|
+
// update the payment code info map
|
|
27886
|
+
this._receiverPaymentCodeInfo.set(paymentCode, updatedPaymentCodeInfoArray);
|
|
27550
27887
|
}
|
|
27551
27888
|
/**
|
|
27552
27889
|
* Queries the network node for the outpoints of the specified address.
|
|
@@ -27564,6 +27901,24 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27564
27901
|
throw new Error(`Failed to get outpoints for address: ${address} - error: ${error}`);
|
|
27565
27902
|
}
|
|
27566
27903
|
}
|
|
27904
|
+
async checkAddressUse(address) {
|
|
27905
|
+
let isUsed = false;
|
|
27906
|
+
let outpoints = [];
|
|
27907
|
+
try {
|
|
27908
|
+
outpoints = await this.getOutpointsByAddress(address);
|
|
27909
|
+
if (outpoints.length > 0) {
|
|
27910
|
+
isUsed = true;
|
|
27911
|
+
}
|
|
27912
|
+
else if (this._addressUseChecker !== undefined && (await this._addressUseChecker(address))) {
|
|
27913
|
+
// address checker returned true, so the address is used
|
|
27914
|
+
isUsed = true;
|
|
27915
|
+
}
|
|
27916
|
+
}
|
|
27917
|
+
catch (error) {
|
|
27918
|
+
throw new Error(`Failed to get outpoints for address: ${address} - error: ${error}`);
|
|
27919
|
+
}
|
|
27920
|
+
return { isUsed, outpoints };
|
|
27921
|
+
}
|
|
27567
27922
|
/**
|
|
27568
27923
|
* Gets the change addresses for the specified zone.
|
|
27569
27924
|
*
|
|
@@ -27621,10 +27976,13 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27621
27976
|
serialize() {
|
|
27622
27977
|
const hdwalletSerialized = super.serialize();
|
|
27623
27978
|
return {
|
|
27624
|
-
outpoints: this.
|
|
27979
|
+
outpoints: this._availableOutpoints,
|
|
27980
|
+
pendingOutpoints: this._pendingOutpoints,
|
|
27625
27981
|
changeAddresses: Array.from(this._changeAddresses.values()),
|
|
27626
27982
|
gapAddresses: this._gapAddresses,
|
|
27627
27983
|
gapChangeAddresses: this._gapChangeAddresses,
|
|
27984
|
+
usedGapAddresses: this._usedGapAddresses,
|
|
27985
|
+
usedGapChangeAddresses: this._usedGapChangeAddresses,
|
|
27628
27986
|
receiverPaymentCodeInfo: Object.fromEntries(this._receiverPaymentCodeInfo),
|
|
27629
27987
|
senderPaymentCodeInfo: Object.fromEntries(this._senderPaymentCodeInfo),
|
|
27630
27988
|
...hdwalletSerialized,
|
|
@@ -27665,9 +28023,26 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27665
28023
|
}
|
|
27666
28024
|
wallet._gapChangeAddresses.push(gapChangeAddressInfo);
|
|
27667
28025
|
}
|
|
27668
|
-
// validate the
|
|
28026
|
+
// validate the used gap addresses and import them
|
|
28027
|
+
for (const usedGapAddressInfo of serialized.usedGapAddresses) {
|
|
28028
|
+
if (!wallet._addresses.has(usedGapAddressInfo.address)) {
|
|
28029
|
+
throw new Error(`Address ${usedGapAddressInfo.address} not found in wallet`);
|
|
28030
|
+
}
|
|
28031
|
+
wallet._usedGapAddresses.push(usedGapAddressInfo);
|
|
28032
|
+
}
|
|
28033
|
+
// validate the used gap change addresses and import them
|
|
28034
|
+
for (const usedGapChangeAddressInfo of serialized.usedGapChangeAddresses) {
|
|
28035
|
+
if (!wallet._changeAddresses.has(usedGapChangeAddressInfo.address)) {
|
|
28036
|
+
throw new Error(`Address ${usedGapChangeAddressInfo.address} not found in wallet`);
|
|
28037
|
+
}
|
|
28038
|
+
wallet._usedGapChangeAddresses.push(usedGapChangeAddressInfo);
|
|
28039
|
+
}
|
|
28040
|
+
// validate the available outpoints and import them
|
|
27669
28041
|
wallet.validateOutpointInfo(serialized.outpoints);
|
|
27670
|
-
wallet.
|
|
28042
|
+
wallet._availableOutpoints.push(...serialized.outpoints);
|
|
28043
|
+
// validate the pending outpoints and import them
|
|
28044
|
+
wallet.validateOutpointInfo(serialized.pendingOutpoints);
|
|
28045
|
+
wallet._pendingOutpoints.push(...serialized.pendingOutpoints);
|
|
27671
28046
|
// validate and import the payment code info
|
|
27672
28047
|
wallet.validateAndImportPaymentCodeInfo(serialized.receiverPaymentCodeInfo, 'receiver');
|
|
27673
28048
|
wallet.validateAndImportPaymentCodeInfo(serialized.senderPaymentCodeInfo, 'sender');
|
|
@@ -27676,7 +28051,8 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27676
28051
|
/**
|
|
27677
28052
|
* Validates and imports a map of payment code info.
|
|
27678
28053
|
*
|
|
27679
|
-
* @param {Map<string,
|
|
28054
|
+
* @param {Map<string, PaymentChannelAddressInfo[]>} paymentCodeInfoMap - The map of payment code info to validate
|
|
28055
|
+
* and import.
|
|
27680
28056
|
* @param {'receiver' | 'sender'} target - The target map to update ('receiver' or 'sender').
|
|
27681
28057
|
* @throws {Error} If any of the payment code info is invalid.
|
|
27682
28058
|
*/
|
|
@@ -27695,7 +28071,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27695
28071
|
/**
|
|
27696
28072
|
* Validates a payment code info object.
|
|
27697
28073
|
*
|
|
27698
|
-
* @param {
|
|
28074
|
+
* @param {PaymentChannelAddressInfo} pcInfo - The payment code info to validate.
|
|
27699
28075
|
* @throws {Error} If the payment code info is invalid.
|
|
27700
28076
|
*/
|
|
27701
28077
|
validatePaymentCodeInfo(pcInfo) {
|
|
@@ -27733,19 +28109,22 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27733
28109
|
// validate zone
|
|
27734
28110
|
this.validateZone(info.zone);
|
|
27735
28111
|
// validate address and account
|
|
27736
|
-
|
|
27737
|
-
if (!addressInfo) {
|
|
27738
|
-
throw new Error(`Address ${info.address} not found in wallet`);
|
|
27739
|
-
}
|
|
27740
|
-
if (info.account !== undefined && info.account !== addressInfo.account) {
|
|
27741
|
-
throw new Error(`Account ${info.account} not found for address ${info.address}`);
|
|
27742
|
-
}
|
|
28112
|
+
this.validateAddressAndAccount(info.address, info.account);
|
|
27743
28113
|
// validate Outpoint
|
|
27744
28114
|
if (info.outpoint.txhash == null || info.outpoint.index == null || info.outpoint.denomination == null) {
|
|
27745
28115
|
throw new Error(`Invalid Outpoint: ${JSON.stringify(info)} `);
|
|
27746
28116
|
}
|
|
27747
28117
|
});
|
|
27748
28118
|
}
|
|
28119
|
+
validateAddressAndAccount(address, account) {
|
|
28120
|
+
const addressInfo = this.locateAddressInfo(address);
|
|
28121
|
+
if (!addressInfo) {
|
|
28122
|
+
throw new Error(`Address ${address} not found in wallet`);
|
|
28123
|
+
}
|
|
28124
|
+
if (account && account !== addressInfo.account) {
|
|
28125
|
+
throw new Error(`Address ${address} does not match account ${account}`);
|
|
28126
|
+
}
|
|
28127
|
+
}
|
|
27749
28128
|
/**
|
|
27750
28129
|
* Creates a new BIP47 payment code for the specified account. The payment code is derived from the account's BIP32
|
|
27751
28130
|
* root key.
|
|
@@ -27758,11 +28137,11 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27758
28137
|
return privatePcode.toBase58();
|
|
27759
28138
|
}
|
|
27760
28139
|
// helper method to get a bip32 API instance
|
|
27761
|
-
|
|
28140
|
+
_getBIP32API() {
|
|
27762
28141
|
return BIP32Factory(ecc);
|
|
27763
28142
|
}
|
|
27764
28143
|
// helper method to decode a base58 string into a Uint8Array
|
|
27765
|
-
|
|
28144
|
+
_decodeBase58(base58) {
|
|
27766
28145
|
return bs58check.decode(base58);
|
|
27767
28146
|
}
|
|
27768
28147
|
/**
|
|
@@ -27773,8 +28152,8 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27773
28152
|
* @param {number} account - The account index for which to generate the private payment code.
|
|
27774
28153
|
* @returns {Promise<PaymentCodePrivate>} A promise that resolves to the PaymentCodePrivate instance.
|
|
27775
28154
|
*/
|
|
27776
|
-
|
|
27777
|
-
const bip32 =
|
|
28155
|
+
_getPaymentCodePrivate(account) {
|
|
28156
|
+
const bip32 = this._getBIP32API();
|
|
27778
28157
|
const accountNode = this._root.deriveChild(account);
|
|
27779
28158
|
// payment code array
|
|
27780
28159
|
const pc = new Uint8Array(80);
|
|
@@ -27797,15 +28176,15 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27797
28176
|
* @returns {Promise<string>} A promise that resolves to the payment address for sending funds.
|
|
27798
28177
|
* @throws {Error} Throws an error if the payment code version is invalid.
|
|
27799
28178
|
*/
|
|
27800
|
-
|
|
27801
|
-
const bip32 =
|
|
27802
|
-
const buf =
|
|
28179
|
+
getNextSendAddress(receiverPaymentCode, zone, account = 0) {
|
|
28180
|
+
const bip32 = this._getBIP32API();
|
|
28181
|
+
const buf = this._decodeBase58(receiverPaymentCode);
|
|
27803
28182
|
const version = buf[0];
|
|
27804
28183
|
if (version !== PC_VERSION)
|
|
27805
28184
|
throw new Error('Invalid payment code version');
|
|
27806
|
-
const receiverPCodePrivate =
|
|
28185
|
+
const receiverPCodePrivate = this._getPaymentCodePrivate(account);
|
|
27807
28186
|
const senderPCodePublic = new PaymentCodePublic(ecc, bip32, buf.slice(1));
|
|
27808
|
-
const paymentCodeInfoArray = this.
|
|
28187
|
+
const paymentCodeInfoArray = this._senderPaymentCodeInfo.get(receiverPaymentCode);
|
|
27809
28188
|
const lastIndex = paymentCodeInfoArray && paymentCodeInfoArray.length > 0
|
|
27810
28189
|
? paymentCodeInfoArray[paymentCodeInfoArray.length - 1].index
|
|
27811
28190
|
: 0;
|
|
@@ -27815,6 +28194,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27815
28194
|
if (this.isValidAddressForZone(address, zone)) {
|
|
27816
28195
|
const pcInfo = {
|
|
27817
28196
|
address,
|
|
28197
|
+
pubKey: hexlify(senderPCodePublic.pubKey),
|
|
27818
28198
|
index: addrIndex,
|
|
27819
28199
|
account,
|
|
27820
28200
|
zone,
|
|
@@ -27824,9 +28204,9 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27824
28204
|
paymentCodeInfoArray.push(pcInfo);
|
|
27825
28205
|
}
|
|
27826
28206
|
else {
|
|
27827
|
-
this.
|
|
28207
|
+
this._senderPaymentCodeInfo.set(receiverPaymentCode, [pcInfo]);
|
|
27828
28208
|
}
|
|
27829
|
-
return
|
|
28209
|
+
return pcInfo;
|
|
27830
28210
|
}
|
|
27831
28211
|
}
|
|
27832
28212
|
throw new Error(`Failed to derive a valid address for the zone ${zone} after ${MAX_ADDRESS_DERIVATION_ATTEMPTS} attempts.`);
|
|
@@ -27839,15 +28219,15 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27839
28219
|
* @returns {Promise<string>} A promise that resolves to the payment address for receiving funds.
|
|
27840
28220
|
* @throws {Error} Throws an error if the payment code version is invalid.
|
|
27841
28221
|
*/
|
|
27842
|
-
|
|
27843
|
-
const bip32 =
|
|
27844
|
-
const buf =
|
|
28222
|
+
getNextReceiveAddress(senderPaymentCode, zone, account = 0) {
|
|
28223
|
+
const bip32 = this._getBIP32API();
|
|
28224
|
+
const buf = this._decodeBase58(senderPaymentCode);
|
|
27845
28225
|
const version = buf[0];
|
|
27846
28226
|
if (version !== PC_VERSION)
|
|
27847
28227
|
throw new Error('Invalid payment code version');
|
|
27848
28228
|
const senderPCodePublic = new PaymentCodePublic(ecc, bip32, buf.slice(1));
|
|
27849
|
-
const receiverPCodePrivate =
|
|
27850
|
-
const paymentCodeInfoArray = this.
|
|
28229
|
+
const receiverPCodePrivate = this._getPaymentCodePrivate(account);
|
|
28230
|
+
const paymentCodeInfoArray = this._receiverPaymentCodeInfo.get(senderPaymentCode);
|
|
27851
28231
|
const lastIndex = paymentCodeInfoArray && paymentCodeInfoArray.length > 0
|
|
27852
28232
|
? paymentCodeInfoArray[paymentCodeInfoArray.length - 1].index
|
|
27853
28233
|
: 0;
|
|
@@ -27857,6 +28237,7 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27857
28237
|
if (this.isValidAddressForZone(address, zone)) {
|
|
27858
28238
|
const pcInfo = {
|
|
27859
28239
|
address,
|
|
28240
|
+
pubKey: hexlify(receiverPCodePrivate.pubKey),
|
|
27860
28241
|
index: addrIndex,
|
|
27861
28242
|
account,
|
|
27862
28243
|
zone,
|
|
@@ -27866,9 +28247,9 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27866
28247
|
paymentCodeInfoArray.push(pcInfo);
|
|
27867
28248
|
}
|
|
27868
28249
|
else {
|
|
27869
|
-
this.
|
|
28250
|
+
this._receiverPaymentCodeInfo.set(senderPaymentCode, [pcInfo]);
|
|
27870
28251
|
}
|
|
27871
|
-
return
|
|
28252
|
+
return pcInfo;
|
|
27872
28253
|
}
|
|
27873
28254
|
}
|
|
27874
28255
|
throw new Error(`Failed to derive a valid address for the zone ${zone} after ${MAX_ADDRESS_DERIVATION_ATTEMPTS} attempts.`);
|
|
@@ -27885,18 +28266,29 @@ class QiHDWallet extends AbstractHDWallet {
|
|
|
27885
28266
|
throw new Error(`Invalid payment code: ${paymentCode}`);
|
|
27886
28267
|
}
|
|
27887
28268
|
if (type === 'receiver') {
|
|
27888
|
-
if (this._receiverPaymentCodeInfo.has(paymentCode)) {
|
|
27889
|
-
|
|
28269
|
+
if (!this._receiverPaymentCodeInfo.has(paymentCode)) {
|
|
28270
|
+
this._receiverPaymentCodeInfo.set(paymentCode, []);
|
|
27890
28271
|
}
|
|
27891
|
-
this._receiverPaymentCodeInfo.set(paymentCode, []);
|
|
27892
28272
|
}
|
|
27893
28273
|
else {
|
|
27894
|
-
if (this._senderPaymentCodeInfo.has(paymentCode)) {
|
|
27895
|
-
|
|
28274
|
+
if (!this._senderPaymentCodeInfo.has(paymentCode)) {
|
|
28275
|
+
this._senderPaymentCodeInfo.set(paymentCode, []);
|
|
27896
28276
|
}
|
|
27897
|
-
this._senderPaymentCodeInfo.set(paymentCode, []);
|
|
27898
28277
|
}
|
|
27899
28278
|
}
|
|
28279
|
+
/**
|
|
28280
|
+
* Gets the address info for a given address.
|
|
28281
|
+
*
|
|
28282
|
+
* @param {string} address - The address.
|
|
28283
|
+
* @returns {NeuteredAddressInfo | null} The address info or null if not found.
|
|
28284
|
+
*/
|
|
28285
|
+
getChangeAddressInfo(address) {
|
|
28286
|
+
const changeAddressInfo = this._changeAddresses.get(address);
|
|
28287
|
+
if (!changeAddressInfo) {
|
|
28288
|
+
return null;
|
|
28289
|
+
}
|
|
28290
|
+
return changeAddressInfo;
|
|
28291
|
+
}
|
|
27900
28292
|
}
|
|
27901
28293
|
|
|
27902
28294
|
/**
|
|
@@ -28848,9 +29240,42 @@ class AbstractProvider {
|
|
|
28848
29240
|
* @returns {Promise<number>} A promise that resolves to the protocol expansion number.
|
|
28849
29241
|
*/
|
|
28850
29242
|
async getProtocolExpansionNumber() {
|
|
28851
|
-
return await this.#perform({
|
|
28852
|
-
|
|
28853
|
-
|
|
29243
|
+
return getNumber(await this.#perform({ method: 'getProtocolExpansionNumber' }));
|
|
29244
|
+
}
|
|
29245
|
+
/**
|
|
29246
|
+
* Get the active region shards based on the protocol expansion number.
|
|
29247
|
+
*
|
|
29248
|
+
* @returns {Promise<Shard[]>} A promise that resolves to the active shards.
|
|
29249
|
+
*/
|
|
29250
|
+
async getActiveRegions() {
|
|
29251
|
+
const protocolExpansionNumber = await this.getProtocolExpansionNumber();
|
|
29252
|
+
const shards = [Shard.Cyprus];
|
|
29253
|
+
if (protocolExpansionNumber >= 1) {
|
|
29254
|
+
shards.push(Shard.Paxos);
|
|
29255
|
+
}
|
|
29256
|
+
if (protocolExpansionNumber >= 3) {
|
|
29257
|
+
shards.push(Shard.Hydra);
|
|
29258
|
+
}
|
|
29259
|
+
return shards.sort((a, b) => a.localeCompare(b));
|
|
29260
|
+
}
|
|
29261
|
+
/**
|
|
29262
|
+
* Get the active zones for a shard based on the protocol expansion number.
|
|
29263
|
+
*
|
|
29264
|
+
* @returns {Promise<Zone[]>} A promise that resolves to the active zones.
|
|
29265
|
+
*/
|
|
29266
|
+
async getActiveZones() {
|
|
29267
|
+
const protocolExpansionNumber = await this.getProtocolExpansionNumber();
|
|
29268
|
+
const zones = [Zone.Cyprus1];
|
|
29269
|
+
if (protocolExpansionNumber >= 1) {
|
|
29270
|
+
zones.push(Zone.Cyprus2);
|
|
29271
|
+
}
|
|
29272
|
+
if (protocolExpansionNumber >= 2) {
|
|
29273
|
+
zones.push(Zone.Paxos1, Zone.Paxos2);
|
|
29274
|
+
}
|
|
29275
|
+
if (protocolExpansionNumber >= 3) {
|
|
29276
|
+
zones.push(Zone.Cyprus3, Zone.Paxos3, Zone.Hydra1, Zone.Hydra2, Zone.Hydra3);
|
|
29277
|
+
}
|
|
29278
|
+
return zones.sort((a, b) => a.localeCompare(b));
|
|
28854
29279
|
}
|
|
28855
29280
|
/**
|
|
28856
29281
|
* Get the latest Qi rate for a zone.
|