@arkade-os/sdk 0.4.32 → 0.4.34
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/README.md +1 -1
- package/dist/adapters/expo.cjs +5 -5
- package/dist/adapters/expo.d.cts +2 -2
- package/dist/adapters/expo.d.ts +2 -2
- package/dist/adapters/expo.js +3 -3
- package/dist/adapters/indexedDB.cjs +5 -5
- package/dist/adapters/indexedDB.js +4 -4
- package/dist/{ark-ibLW4Hte.d.cts → ark-Dsv5Jq4E.d.cts} +81 -10
- package/dist/{ark-ibLW4Hte.d.ts → ark-Dsv5Jq4E.d.ts} +81 -10
- package/dist/{asyncStorageTaskQueue-BEOFPNc0.d.ts → asyncStorageTaskQueue-BH-zuth5.d.ts} +1 -1
- package/dist/{asyncStorageTaskQueue-VGHXWR9F.d.cts → asyncStorageTaskQueue-D92ch8yI.d.cts} +1 -1
- package/dist/{chunk-ABWRLTX5.js → chunk-5WDBHWX3.js} +4 -4
- package/dist/{chunk-ABWRLTX5.js.map → chunk-5WDBHWX3.js.map} +1 -1
- package/dist/{chunk-GIGILVVP.cjs → chunk-CCLNFHJ5.cjs} +11 -11
- package/dist/{chunk-GIGILVVP.cjs.map → chunk-CCLNFHJ5.cjs.map} +1 -1
- package/dist/{chunk-WMIPYZSB.cjs → chunk-CMPJR3HS.cjs} +42 -9
- package/dist/chunk-CMPJR3HS.cjs.map +1 -0
- package/dist/{chunk-YA4G7RFB.js → chunk-CUSABEUQ.js} +166 -38
- package/dist/chunk-CUSABEUQ.js.map +1 -0
- package/dist/{chunk-6FLL2Q36.cjs → chunk-FSAXPBGP.cjs} +9 -9
- package/dist/chunk-FSAXPBGP.cjs.map +1 -0
- package/dist/{chunk-6NWNOLL3.js → chunk-FXFBPXV3.js} +4 -4
- package/dist/chunk-FXFBPXV3.js.map +1 -0
- package/dist/{chunk-IEO3XDKI.cjs → chunk-GUTKJMSF.cjs} +190 -58
- package/dist/chunk-GUTKJMSF.cjs.map +1 -0
- package/dist/{chunk-XROGFOPX.js → chunk-HFXEUW55.js} +740 -175
- package/dist/chunk-HFXEUW55.js.map +1 -0
- package/dist/{chunk-TU3LVAPX.js → chunk-OUVTG72A.js} +43 -11
- package/dist/chunk-OUVTG72A.js.map +1 -0
- package/dist/{chunk-SHEBNWOQ.js → chunk-VVGD3JIP.js} +3 -3
- package/dist/{chunk-SHEBNWOQ.js.map → chunk-VVGD3JIP.js.map} +1 -1
- package/dist/{chunk-KQK4PP6L.cjs → chunk-XCHBQVMK.cjs} +879 -314
- package/dist/chunk-XCHBQVMK.cjs.map +1 -0
- package/dist/{chunk-I2UIKZM5.cjs → chunk-ZS3OZHC7.cjs} +7 -7
- package/dist/{chunk-I2UIKZM5.cjs.map → chunk-ZS3OZHC7.cjs.map} +1 -1
- package/dist/contracts/handlers/index.cjs +10 -6
- package/dist/contracts/handlers/index.d.cts +3 -3
- package/dist/contracts/handlers/index.d.ts +3 -3
- package/dist/contracts/handlers/index.js +2 -2
- package/dist/{delegate-BvNTw44a.d.cts → delegate-BaS5SCIW.d.cts} +10 -2
- package/dist/{delegate-BXaR1RNG.d.ts → delegate-Baz_hb83.d.ts} +10 -2
- package/dist/{index-BusKawmy.d.ts → index-FwXZveaX.d.ts} +63 -3
- package/dist/{index-C-5Tw7VA.d.cts → index-lNZ6qaO3.d.cts} +63 -3
- package/dist/index.cjs +143 -127
- package/dist/index.d.cts +89 -16
- package/dist/index.d.ts +89 -16
- package/dist/index.js +4 -4
- package/dist/repositories/realm/index.cjs +13 -13
- package/dist/repositories/realm/index.d.cts +1 -1
- package/dist/repositories/realm/index.d.ts +1 -1
- package/dist/repositories/realm/index.js +4 -4
- package/dist/repositories/sqlite/index.cjs +13 -13
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.js +4 -4
- package/dist/{taskRunner-B1igKGAo.d.ts → taskRunner-B1NUWyWR.d.ts} +1 -1
- package/dist/{taskRunner-By92TQ1m.d.cts → taskRunner-vFRA3F9b.d.cts} +1 -1
- package/dist/wallet/expo/background.cjs +14 -14
- package/dist/wallet/expo/background.d.cts +3 -3
- package/dist/wallet/expo/background.d.ts +3 -3
- package/dist/wallet/expo/background.js +6 -6
- package/dist/wallet/expo/index.cjs +14 -14
- package/dist/wallet/expo/index.cjs.map +1 -1
- package/dist/wallet/expo/index.d.cts +5 -5
- package/dist/wallet/expo/index.d.ts +5 -5
- package/dist/wallet/expo/index.js +6 -6
- package/dist/wallet/expo/index.js.map +1 -1
- package/dist/{wallet-B_rxgQTu.d.cts → wallet-By9HIo0Q.d.cts} +160 -5
- package/dist/{wallet-CyM4F7Bs.d.ts → wallet-D6uoBLmS.d.ts} +160 -5
- package/dist/worker/expo/index.cjs +9 -9
- package/dist/worker/expo/index.d.cts +4 -4
- package/dist/worker/expo/index.d.ts +4 -4
- package/dist/worker/expo/index.js +5 -5
- package/package.json +4 -4
- package/dist/chunk-6FLL2Q36.cjs.map +0 -1
- package/dist/chunk-6NWNOLL3.js.map +0 -1
- package/dist/chunk-IEO3XDKI.cjs.map +0 -1
- package/dist/chunk-KQK4PP6L.cjs.map +0 -1
- package/dist/chunk-TU3LVAPX.js.map +0 -1
- package/dist/chunk-WMIPYZSB.cjs.map +0 -1
- package/dist/chunk-XROGFOPX.js.map +0 -1
- package/dist/chunk-YA4G7RFB.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { craftToSpendTx, Transaction, OP_RETURN_EMPTY_PKSCRIPT, Intent, getArkPsbtFields, CosignerPublicKey, setArkPsbtField, VtxoTaprootTree, maybeArkError, AssetRef, AssetId, AssetOutput, AssetGroup, AssetInput, Packet, Metadata, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError, BufferReader } from './chunk-
|
|
2
|
-
import { isMainnetDescriptor, descriptorIsOurs, contractHandlers,
|
|
3
|
-
import { VtxoScript, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress,
|
|
1
|
+
import { craftToSpendTx, Transaction, OP_RETURN_EMPTY_PKSCRIPT, Intent, getArkPsbtFields, CosignerPublicKey, setArkPsbtField, VtxoTaprootTree, maybeArkError, AssetRef, AssetId, AssetOutput, AssetGroup, AssetInput, Packet, Metadata, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError, BufferReader } from './chunk-FXFBPXV3.js';
|
|
2
|
+
import { isMainnetDescriptor, descriptorIsOurs, contractHandlers, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, DefaultVtxo, BoardingContractHandler } from './chunk-CUSABEUQ.js';
|
|
3
|
+
import { VtxoScript, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress, CSVMultisigTapscript, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-OUVTG72A.js';
|
|
4
4
|
import { sha256, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr, equalBytes as equalBytes$1 } from '@scure/btc-signer/utils.js';
|
|
5
5
|
import { SigHash, Script, p2tr, RawWitness, Address, OutScript, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
|
|
6
6
|
import { hex, base58, base64 } from '@scure/base';
|
|
@@ -1009,7 +1009,7 @@ var ESPLORA_URL = {
|
|
|
1009
1009
|
testnet: "https://mempool.space/testnet/api",
|
|
1010
1010
|
signet: "https://mempool.signet.arkade.sh/api",
|
|
1011
1011
|
mutinynet: "https://mempool.mutinynet.arkade.sh/api",
|
|
1012
|
-
regtest: "http://localhost:3000"
|
|
1012
|
+
regtest: "http://localhost:3000/api"
|
|
1013
1013
|
};
|
|
1014
1014
|
var EsploraProvider = class {
|
|
1015
1015
|
constructor(baseUrl = ESPLORA_URL[DEFAULT_NETWORK_NAME], opts) {
|
|
@@ -1028,6 +1028,9 @@ var EsploraProvider = class {
|
|
|
1028
1028
|
}
|
|
1029
1029
|
async getFeeRate() {
|
|
1030
1030
|
const response = await fetch(`${this.baseUrl}/fee-estimates`);
|
|
1031
|
+
if (response.status === 404) {
|
|
1032
|
+
return void 0;
|
|
1033
|
+
}
|
|
1031
1034
|
if (!response.ok) {
|
|
1032
1035
|
throw new Error(`Failed to fetch fee rate: ${response.statusText}`);
|
|
1033
1036
|
}
|
|
@@ -1153,7 +1156,7 @@ var EsploraProvider = class {
|
|
|
1153
1156
|
return stopFunc;
|
|
1154
1157
|
}
|
|
1155
1158
|
async getChainTip() {
|
|
1156
|
-
const tipBlocks = await fetch(`${this.baseUrl}/blocks
|
|
1159
|
+
const tipBlocks = await fetch(`${this.baseUrl}/blocks`);
|
|
1157
1160
|
if (!tipBlocks.ok) {
|
|
1158
1161
|
throw new Error(`Failed to get chain tip: ${tipBlocks.statusText}`);
|
|
1159
1162
|
}
|
|
@@ -1966,12 +1969,22 @@ function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubke
|
|
|
1966
1969
|
}
|
|
1967
1970
|
}
|
|
1968
1971
|
function combineTapscriptSigs(signedTx, originalTx) {
|
|
1972
|
+
if (signedTx.inputsLength !== originalTx.inputsLength) {
|
|
1973
|
+
throw new Error(
|
|
1974
|
+
`combineTapscriptSigs: input count mismatch (signedTx ${signedTx.inputsLength}, originalTx ${originalTx.inputsLength})`
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1969
1977
|
for (let i = 0; i < signedTx.inputsLength; i++) {
|
|
1970
1978
|
const input = originalTx.getInput(i);
|
|
1971
1979
|
const signedInput = signedTx.getInput(i);
|
|
1972
|
-
if (!input.tapScriptSig)
|
|
1980
|
+
if (!input.tapScriptSig) {
|
|
1981
|
+
throw new Error(`combineTapscriptSigs: originalTx input ${i} has no tapScriptSig`);
|
|
1982
|
+
}
|
|
1983
|
+
if (!signedInput.tapScriptSig) {
|
|
1984
|
+
throw new Error(`combineTapscriptSigs: signedTx input ${i} has no tapScriptSig`);
|
|
1985
|
+
}
|
|
1973
1986
|
originalTx.updateInput(i, {
|
|
1974
|
-
tapScriptSig: input.tapScriptSig
|
|
1987
|
+
tapScriptSig: input.tapScriptSig.concat(signedInput.tapScriptSig)
|
|
1975
1988
|
});
|
|
1976
1989
|
}
|
|
1977
1990
|
return originalTx;
|
|
@@ -2248,12 +2261,12 @@ var FALLBACK_WALLET_DUST_AMOUNT = 330n;
|
|
|
2248
2261
|
function getDustAmount(wallet) {
|
|
2249
2262
|
return "dustAmount" in wallet ? wallet.dustAmount : FALLBACK_WALLET_DUST_AMOUNT;
|
|
2250
2263
|
}
|
|
2251
|
-
function
|
|
2264
|
+
function extendCoinWithTapscript(boardingTapscript, utxo) {
|
|
2252
2265
|
return {
|
|
2253
2266
|
...utxo,
|
|
2254
|
-
forfeitTapLeafScript:
|
|
2255
|
-
intentTapLeafScript:
|
|
2256
|
-
tapTree:
|
|
2267
|
+
forfeitTapLeafScript: boardingTapscript.forfeit(),
|
|
2268
|
+
intentTapLeafScript: boardingTapscript.forfeit(),
|
|
2269
|
+
tapTree: boardingTapscript.encode()
|
|
2257
2270
|
};
|
|
2258
2271
|
}
|
|
2259
2272
|
function deriveContractTapscripts(contract) {
|
|
@@ -2347,12 +2360,12 @@ function validateRecipients(recipients, dustAmount) {
|
|
|
2347
2360
|
|
|
2348
2361
|
// src/wallet/vtxo-manager.ts
|
|
2349
2362
|
function isSweepCapable(wallet) {
|
|
2350
|
-
return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet;
|
|
2363
|
+
return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet && "signOnchainBoardingTx" in wallet;
|
|
2351
2364
|
}
|
|
2352
2365
|
function assertSweepCapable(wallet) {
|
|
2353
2366
|
if (!isSweepCapable(wallet)) {
|
|
2354
2367
|
throw new Error(
|
|
2355
|
-
"Boarding UTXO sweep requires a Wallet instance with boardingTapscript, onchainProvider, arkProvider, and
|
|
2368
|
+
"Boarding UTXO sweep requires a Wallet instance with boardingTapscript, onchainProvider, arkProvider, network, and signOnchainBoardingTx"
|
|
2356
2369
|
);
|
|
2357
2370
|
}
|
|
2358
2371
|
}
|
|
@@ -2368,6 +2381,21 @@ async function runWithCrossInstanceLock(name, fn) {
|
|
|
2368
2381
|
await fn();
|
|
2369
2382
|
});
|
|
2370
2383
|
}
|
|
2384
|
+
var MAX_VTXOS_PER_SETTLEMENT = 50;
|
|
2385
|
+
function byValueDescending(vtxos) {
|
|
2386
|
+
return [...vtxos].sort((a, b) => b.value - a.value);
|
|
2387
|
+
}
|
|
2388
|
+
function byExpiryAscending(vtxos) {
|
|
2389
|
+
const expiryKey = (vtxo) => {
|
|
2390
|
+
if (isRecoverable(vtxo)) return -Infinity;
|
|
2391
|
+
const batchExpiry = vtxo.virtualStatus.batchExpiry;
|
|
2392
|
+
if (isExpired(vtxo)) return batchExpiry ?? -Infinity;
|
|
2393
|
+
if (!batchExpiry) return Infinity;
|
|
2394
|
+
if (new Date(batchExpiry).getFullYear() < 2025) return Infinity;
|
|
2395
|
+
return batchExpiry;
|
|
2396
|
+
};
|
|
2397
|
+
return [...vtxos].sort((a, b) => expiryKey(a) - expiryKey(b));
|
|
2398
|
+
}
|
|
2371
2399
|
var DEFAULT_THRESHOLD_SECONDS = 259200;
|
|
2372
2400
|
var DEFAULT_THRESHOLD_MS = DEFAULT_THRESHOLD_SECONDS * 1e3;
|
|
2373
2401
|
var DEFAULT_RENEWAL_CONFIG = {
|
|
@@ -2527,10 +2555,20 @@ var VtxoManager = class _VtxoManager {
|
|
|
2527
2555
|
withUnrolled: false
|
|
2528
2556
|
});
|
|
2529
2557
|
const dustAmount = getDustAmount(this.wallet);
|
|
2530
|
-
|
|
2558
|
+
let { vtxosToRecover, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount);
|
|
2531
2559
|
if (vtxosToRecover.length === 0) {
|
|
2532
2560
|
throw new Error("No recoverable VTXOs found");
|
|
2533
2561
|
}
|
|
2562
|
+
if (vtxosToRecover.length > MAX_VTXOS_PER_SETTLEMENT) {
|
|
2563
|
+
const recoverableCount = vtxosToRecover.length;
|
|
2564
|
+
const capped = byValueDescending(vtxosToRecover).slice(0, MAX_VTXOS_PER_SETTLEMENT);
|
|
2565
|
+
({ vtxosToRecover, totalAmount } = getRecoverableWithSubdust(capped, dustAmount));
|
|
2566
|
+
if (vtxosToRecover.length === 0) {
|
|
2567
|
+
throw new Error(
|
|
2568
|
+
`Capped recovery batch (highest-value ${MAX_VTXOS_PER_SETTLEMENT} of ${recoverableCount} recoverable VTXOs) is below the dust threshold ${dustAmount}`
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2534
2572
|
const arkAddress = await this.wallet.getAddress();
|
|
2535
2573
|
return this.wallet.settle(
|
|
2536
2574
|
{
|
|
@@ -2680,6 +2718,9 @@ var VtxoManager = class _VtxoManager {
|
|
|
2680
2718
|
if (vtxos.length === 0) {
|
|
2681
2719
|
throw new Error("No VTXOs available to renew");
|
|
2682
2720
|
}
|
|
2721
|
+
if (vtxos.length > MAX_VTXOS_PER_SETTLEMENT) {
|
|
2722
|
+
vtxos = byExpiryAscending(vtxos).slice(0, MAX_VTXOS_PER_SETTLEMENT);
|
|
2723
|
+
}
|
|
2683
2724
|
const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
|
|
2684
2725
|
const dustAmount = getDustAmount(this.wallet);
|
|
2685
2726
|
if (BigInt(totalAmount) < dustAmount) {
|
|
@@ -2787,7 +2828,6 @@ var VtxoManager = class _VtxoManager {
|
|
|
2787
2828
|
const boardingAddress = await this.wallet.getBoardingAddress();
|
|
2788
2829
|
const feeRate = await this.getOnchainProvider().getFeeRate() ?? 1;
|
|
2789
2830
|
const exitTapLeafScript = this.getBoardingExitLeaf();
|
|
2790
|
-
const sequence = getSequence(exitTapLeafScript);
|
|
2791
2831
|
const leafScript = exitTapLeafScript[1];
|
|
2792
2832
|
const leafScriptSize = leafScript.length - 1;
|
|
2793
2833
|
const controlBlockSize = exitTapLeafScript[0].merklePath.length * 32;
|
|
@@ -2808,19 +2848,28 @@ var VtxoManager = class _VtxoManager {
|
|
|
2808
2848
|
}
|
|
2809
2849
|
const tx = new Transaction();
|
|
2810
2850
|
for (const utxo of expiredUtxos) {
|
|
2851
|
+
const utxoScript = VtxoScript.decode(utxo.tapTree);
|
|
2852
|
+
const utxoExitLeaf = utxoScript.leaves.find(
|
|
2853
|
+
(leaf) => CSVMultisigTapscript.isScriptValid(scriptFromTapLeafScript(leaf)) === true
|
|
2854
|
+
);
|
|
2855
|
+
if (!utxoExitLeaf) {
|
|
2856
|
+
throw new Error(
|
|
2857
|
+
`Boarding sweep: no CSV exit leaf for UTXO ${utxo.txid}:${utxo.vout}`
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2811
2860
|
tx.addInput({
|
|
2812
2861
|
txid: utxo.txid,
|
|
2813
2862
|
index: utxo.vout,
|
|
2814
2863
|
witnessUtxo: {
|
|
2815
|
-
script:
|
|
2864
|
+
script: utxoScript.pkScript,
|
|
2816
2865
|
amount: BigInt(utxo.value)
|
|
2817
2866
|
},
|
|
2818
|
-
tapLeafScript: [
|
|
2819
|
-
sequence
|
|
2867
|
+
tapLeafScript: [utxoExitLeaf],
|
|
2868
|
+
sequence: getSequence(utxoExitLeaf)
|
|
2820
2869
|
});
|
|
2821
2870
|
}
|
|
2822
2871
|
tx.addOutputAddress(boardingAddress, outputAmount, this.getNetwork());
|
|
2823
|
-
const signedTx = await this.
|
|
2872
|
+
const signedTx = await this.getSweepWallet().signOnchainBoardingTx(tx);
|
|
2824
2873
|
signedTx.finalize();
|
|
2825
2874
|
const txid = await this.getOnchainProvider().broadcastTransaction(signedTx.hex);
|
|
2826
2875
|
for (const u of expiredUtxos) {
|
|
@@ -2847,10 +2896,6 @@ var VtxoManager = class _VtxoManager {
|
|
|
2847
2896
|
getBoardingExitLeaf() {
|
|
2848
2897
|
return this.getSweepWallet().boardingTapscript.exit();
|
|
2849
2898
|
}
|
|
2850
|
-
/** Returns the pkScript (output script) of the boarding tapscript. */
|
|
2851
|
-
getBoardingOutputScript() {
|
|
2852
|
-
return this.getSweepWallet().boardingTapscript.pkScript;
|
|
2853
|
-
}
|
|
2854
2899
|
/** Returns the onchain provider for fee estimation and broadcasting. */
|
|
2855
2900
|
getOnchainProvider() {
|
|
2856
2901
|
return this.getSweepWallet().onchainProvider;
|
|
@@ -2863,10 +2908,6 @@ var VtxoManager = class _VtxoManager {
|
|
|
2863
2908
|
getNetwork() {
|
|
2864
2909
|
return this.getSweepWallet().network;
|
|
2865
2910
|
}
|
|
2866
|
-
/** Returns the wallet's identity for transaction signing. */
|
|
2867
|
-
getIdentity() {
|
|
2868
|
-
return this.wallet.identity;
|
|
2869
|
-
}
|
|
2870
2911
|
async initializeSubscription() {
|
|
2871
2912
|
if (this.settlementConfig === false) {
|
|
2872
2913
|
return void 0;
|
|
@@ -3147,7 +3188,10 @@ var VtxoManager = class _VtxoManager {
|
|
|
3147
3188
|
totalAmount += BigInt(u.value) - BigInt(inputFee.satoshis);
|
|
3148
3189
|
}
|
|
3149
3190
|
const filteredVtxos = [];
|
|
3150
|
-
for (const v of expiringVtxos) {
|
|
3191
|
+
for (const v of byExpiryAscending(expiringVtxos)) {
|
|
3192
|
+
if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
|
|
3193
|
+
break;
|
|
3194
|
+
}
|
|
3151
3195
|
const inputFee = estimator.evalOffchainInput({
|
|
3152
3196
|
amount: BigInt(v.value),
|
|
3153
3197
|
type: v.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
|
|
@@ -6505,8 +6549,11 @@ function cursorCutoff(requestStartedAt) {
|
|
|
6505
6549
|
}
|
|
6506
6550
|
|
|
6507
6551
|
// src/contracts/contractManager.ts
|
|
6508
|
-
|
|
6552
|
+
function areCoalescibleContractTypes(a, b) {
|
|
6553
|
+
return a === "default" && b === "boarding" || a === "boarding" && b === "default";
|
|
6554
|
+
}
|
|
6509
6555
|
var SCAN_MAX_INDEX = 1e4;
|
|
6556
|
+
var DEFAULT_SCAN_BATCH = 10;
|
|
6510
6557
|
var ContractManager = class _ContractManager {
|
|
6511
6558
|
config;
|
|
6512
6559
|
watcher;
|
|
@@ -6630,8 +6677,11 @@ var ContractManager = class _ContractManager {
|
|
|
6630
6677
|
const [existing] = await this.getContracts({ script: params.script });
|
|
6631
6678
|
if (existing) {
|
|
6632
6679
|
if (existing.type === params.type) return { contract: existing, persisted: false };
|
|
6680
|
+
if (areCoalescibleContractTypes(existing.type, params.type)) {
|
|
6681
|
+
return { contract: existing, persisted: false };
|
|
6682
|
+
}
|
|
6633
6683
|
throw new Error(
|
|
6634
|
-
`Contract with script ${params.script} already exists with
|
|
6684
|
+
`Contract with script ${params.script} already exists with type ${existing.type}.`
|
|
6635
6685
|
);
|
|
6636
6686
|
}
|
|
6637
6687
|
const contract = {
|
|
@@ -6660,6 +6710,19 @@ var ContractManager = class _ContractManager {
|
|
|
6660
6710
|
* other handler hit it).
|
|
6661
6711
|
* - `persistAndWatchContract` rejecting is operational/fatal and
|
|
6662
6712
|
* propagates (only `discoverAt` is guarded).
|
|
6713
|
+
* - Within an index the handler probes run concurrently (independent
|
|
6714
|
+
* network reads); their hits are persisted sequentially in
|
|
6715
|
+
* `discoverables` order to preserve the first-wins collision tie-break.
|
|
6716
|
+
* - Indices are probed `batchSize` at a time (a second concurrency layer
|
|
6717
|
+
* over the per-index probes), but each window is CAPPED to
|
|
6718
|
+
* `gapLimit - unused` indices — the most a serial scan could still reach
|
|
6719
|
+
* before the gap window is guaranteed to close. So every index probed in
|
|
6720
|
+
* a window is one a one-index-at-a-time scan would also reach: nothing is
|
|
6721
|
+
* over-scanned, nothing is discarded, and `materialize`/`discoverAt` are
|
|
6722
|
+
* invoked on exactly the same index set. The window's hits are still
|
|
6723
|
+
* processed strictly in ascending index order, so the discovered set,
|
|
6724
|
+
* persisted rows, `lastIndexUsed`, and `handlerErrors` are byte-for-byte
|
|
6725
|
+
* identical to the serial path — only the wall-clock differs.
|
|
6663
6726
|
*/
|
|
6664
6727
|
async scanContracts(opts) {
|
|
6665
6728
|
const gapLimit = opts.gapLimit ?? 20;
|
|
@@ -6668,35 +6731,69 @@ var ContractManager = class _ContractManager {
|
|
|
6668
6731
|
`scanContracts: gapLimit must be a positive integer (got ${String(opts.gapLimit)})`
|
|
6669
6732
|
);
|
|
6670
6733
|
}
|
|
6671
|
-
const
|
|
6734
|
+
const batchSize = opts.batchSize ?? DEFAULT_SCAN_BATCH;
|
|
6735
|
+
if (!Number.isInteger(batchSize) || batchSize <= 0) {
|
|
6736
|
+
throw new Error(
|
|
6737
|
+
`scanContracts: batchSize must be a positive integer (got ${String(opts.batchSize)})`
|
|
6738
|
+
);
|
|
6739
|
+
}
|
|
6740
|
+
const registered = contractHandlers.getRegisteredTypes().map((t) => contractHandlers.get(t)).filter(isDiscoverable);
|
|
6741
|
+
const discoverables = [
|
|
6742
|
+
...registered.filter((h) => h.type === "boarding"),
|
|
6743
|
+
...registered.filter((h) => h.type !== "boarding")
|
|
6744
|
+
];
|
|
6672
6745
|
const maxIdx = opts.hd ? SCAN_MAX_INDEX : 0;
|
|
6673
6746
|
const handlerErrors = [];
|
|
6674
6747
|
let lastIndexUsed = -1;
|
|
6675
6748
|
let unused = 0;
|
|
6676
6749
|
let i = 0;
|
|
6750
|
+
const probeIndex = async (index) => {
|
|
6751
|
+
const descriptor = opts.materialize(index);
|
|
6752
|
+
return Promise.all(
|
|
6753
|
+
discoverables.map(async (h) => {
|
|
6754
|
+
try {
|
|
6755
|
+
return {
|
|
6756
|
+
ok: true,
|
|
6757
|
+
found: await h.discoverAt(index, descriptor, opts.deps)
|
|
6758
|
+
};
|
|
6759
|
+
} catch (error) {
|
|
6760
|
+
return { ok: false, error };
|
|
6761
|
+
}
|
|
6762
|
+
})
|
|
6763
|
+
);
|
|
6764
|
+
};
|
|
6677
6765
|
while (i <= maxIdx && unused < gapLimit) {
|
|
6678
|
-
const
|
|
6679
|
-
|
|
6680
|
-
for (
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6766
|
+
const windowEnd = Math.min(maxIdx, i + Math.min(batchSize, gapLimit - unused) - 1);
|
|
6767
|
+
const windowIndices = [];
|
|
6768
|
+
for (let idx = i; idx <= windowEnd; idx++) windowIndices.push(idx);
|
|
6769
|
+
const windowProbes = await Promise.all(windowIndices.map(probeIndex));
|
|
6770
|
+
for (let w = 0; w < windowIndices.length; w++) {
|
|
6771
|
+
const index = windowIndices[w];
|
|
6772
|
+
const probes = windowProbes[w];
|
|
6773
|
+
let hitAtThisIndex = false;
|
|
6774
|
+
for (let h = 0; h < discoverables.length; h++) {
|
|
6775
|
+
const probe = probes[h];
|
|
6776
|
+
if (!probe.ok) {
|
|
6777
|
+
handlerErrors.push({
|
|
6778
|
+
handler: discoverables[h].type,
|
|
6779
|
+
index,
|
|
6780
|
+
error: probe.error
|
|
6781
|
+
});
|
|
6782
|
+
continue;
|
|
6783
|
+
}
|
|
6784
|
+
for (const c of probe.found) {
|
|
6785
|
+
await this.persistAndWatchContract(c);
|
|
6786
|
+
hitAtThisIndex = true;
|
|
6787
|
+
}
|
|
6687
6788
|
}
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6789
|
+
if (hitAtThisIndex) {
|
|
6790
|
+
lastIndexUsed = index;
|
|
6791
|
+
unused = 0;
|
|
6792
|
+
} else {
|
|
6793
|
+
unused += 1;
|
|
6691
6794
|
}
|
|
6692
6795
|
}
|
|
6693
|
-
|
|
6694
|
-
lastIndexUsed = i;
|
|
6695
|
-
unused = 0;
|
|
6696
|
-
} else {
|
|
6697
|
-
unused += 1;
|
|
6698
|
-
}
|
|
6699
|
-
i += 1;
|
|
6796
|
+
i = windowEnd + 1;
|
|
6700
6797
|
}
|
|
6701
6798
|
if (opts.hd && i > maxIdx && unused < gapLimit) {
|
|
6702
6799
|
throw new Error(
|
|
@@ -7402,7 +7499,8 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7402
7499
|
walletRepository: setup.walletRepository,
|
|
7403
7500
|
contractRepository: setup.contractRepository,
|
|
7404
7501
|
serverPubKey: setup.serverPubKey,
|
|
7405
|
-
expectedContractType
|
|
7502
|
+
expectedContractType,
|
|
7503
|
+
baselineReceivePubKey: setup.offchainTapscript.options.pubKey
|
|
7406
7504
|
};
|
|
7407
7505
|
let boot;
|
|
7408
7506
|
try {
|
|
@@ -7447,14 +7545,17 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7447
7545
|
receivePubkey: existing.pubKey
|
|
7448
7546
|
};
|
|
7449
7547
|
}
|
|
7450
|
-
|
|
7451
|
-
if (
|
|
7452
|
-
descriptor = await provider.
|
|
7548
|
+
const current = hasPeekableDescriptor(provider) ? await provider.getCurrentSigningDescriptor() : void 0;
|
|
7549
|
+
if (current === void 0) {
|
|
7550
|
+
const descriptor = await provider.getNextSigningDescriptor();
|
|
7551
|
+
return {
|
|
7552
|
+
rotator: new _WalletReceiveRotator(provider, void 0, opts.logger),
|
|
7553
|
+
receivePubkey: deriveLeafPubkey(descriptor)
|
|
7554
|
+
};
|
|
7453
7555
|
}
|
|
7454
|
-
descriptor ??= await provider.getNextSigningDescriptor();
|
|
7455
7556
|
return {
|
|
7456
7557
|
rotator: new _WalletReceiveRotator(provider, void 0, opts.logger),
|
|
7457
|
-
receivePubkey: deriveLeafPubkey(
|
|
7558
|
+
receivePubkey: opts.baselineReceivePubKey ?? deriveLeafPubkey(current)
|
|
7458
7559
|
};
|
|
7459
7560
|
}
|
|
7460
7561
|
/**
|
|
@@ -7559,7 +7660,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7559
7660
|
const newScript = hex.encode(newTapscript.pkScript);
|
|
7560
7661
|
const newAddress = newTapscript.address(wallet.network.hrp, wallet.arkServerPublicKey).encode();
|
|
7561
7662
|
const manager = await wallet.getContractManager();
|
|
7562
|
-
const csvTimelock = newTapscript.options.csvTimelock
|
|
7663
|
+
const csvTimelock = newTapscript.options.csvTimelock;
|
|
7563
7664
|
const csvTimelockStr = timelockToSequence(csvTimelock).toString();
|
|
7564
7665
|
const serverPubKeyHex = hex.encode(newTapscript.options.serverPubKey);
|
|
7565
7666
|
const baseParams = {
|
|
@@ -7687,13 +7788,29 @@ var DescriptorSigningProviderMissingError = class extends Error {
|
|
|
7687
7788
|
};
|
|
7688
7789
|
|
|
7689
7790
|
// src/wallet/inputSignerRouter.ts
|
|
7690
|
-
var DESCRIPTOR_CAPABLE_CONTRACT_TYPES = /* @__PURE__ */ new Set(["default", "delegate"]);
|
|
7791
|
+
var DESCRIPTOR_CAPABLE_CONTRACT_TYPES = /* @__PURE__ */ new Set(["default", "delegate", "boarding"]);
|
|
7691
7792
|
var InputSignerRouter = class {
|
|
7692
7793
|
constructor(deps) {
|
|
7693
7794
|
this.deps = deps;
|
|
7694
7795
|
}
|
|
7695
|
-
|
|
7696
|
-
|
|
7796
|
+
/**
|
|
7797
|
+
* Resolve each job to its target signer without invoking signing. The
|
|
7798
|
+
* returned plan is the single source of truth for both {@link sign} and
|
|
7799
|
+
* the batch-eligibility predicate {@link canBatch} — callers that want
|
|
7800
|
+
* to pre-flight a batch path call {@link canBatch} (which delegates
|
|
7801
|
+
* here) so the routing rules never live in two places.
|
|
7802
|
+
*
|
|
7803
|
+
* Throws {@link MissingSigningDescriptorError} for a non-baseline
|
|
7804
|
+
* default/delegate contract whose `metadata.signingDescriptor` is
|
|
7805
|
+
* missing — the same condition that would later abort signing. Failing
|
|
7806
|
+
* here moves the failure earlier, before any PSBT is mutated.
|
|
7807
|
+
*/
|
|
7808
|
+
async classify(jobs) {
|
|
7809
|
+
const identityIndexes = [];
|
|
7810
|
+
const descriptorGroups = /* @__PURE__ */ new Map();
|
|
7811
|
+
if (jobs.length === 0) {
|
|
7812
|
+
return { identityIndexes, descriptorGroups };
|
|
7813
|
+
}
|
|
7697
7814
|
const distinctScripts = Array.from(new Set(jobs.map((j) => hex.encode(j.lookupScript))));
|
|
7698
7815
|
const contracts = await this.deps.contractRepository.getContracts({
|
|
7699
7816
|
script: distinctScripts
|
|
@@ -7706,8 +7823,6 @@ var InputSignerRouter = class {
|
|
|
7706
7823
|
}
|
|
7707
7824
|
const baselinePubKeyHex = hex.encode(await this.deps.identity.xOnlyPublicKey());
|
|
7708
7825
|
const boardingScriptHex = hex.encode(this.deps.boardingPkScript);
|
|
7709
|
-
const identityIndexes = [];
|
|
7710
|
-
const descriptorGroups = /* @__PURE__ */ new Map();
|
|
7711
7826
|
for (const job of jobs) {
|
|
7712
7827
|
const scriptHex = hex.encode(job.lookupScript);
|
|
7713
7828
|
const contract = scriptToContract.get(scriptHex);
|
|
@@ -7740,6 +7855,31 @@ var InputSignerRouter = class {
|
|
|
7740
7855
|
descriptorGroups.set(descriptor, [job.index]);
|
|
7741
7856
|
}
|
|
7742
7857
|
}
|
|
7858
|
+
return { identityIndexes, descriptorGroups };
|
|
7859
|
+
}
|
|
7860
|
+
/**
|
|
7861
|
+
* Returns `true` when every signable input across all `jobSets` resolves
|
|
7862
|
+
* to the baseline {@link Identity} key — i.e. the descriptor provider
|
|
7863
|
+
* would not be invoked. Used by the wallet's send/recovery paths to
|
|
7864
|
+
* pre-flight the {@link BatchSignableIdentity.signMultiple} fast path,
|
|
7865
|
+
* which can only fold work a single identity key can sign.
|
|
7866
|
+
*
|
|
7867
|
+
* Accepts several job sets (e.g. an arkTx's jobs plus one set per
|
|
7868
|
+
* checkpoint) and classifies their union in a single pass. Eligibility
|
|
7869
|
+
* is monotonic — the union routes entirely to the baseline key iff every
|
|
7870
|
+
* set does — so this returns the same answer as ANDing the per-set
|
|
7871
|
+
* results, but with one {@link classify} (one repo round-trip + one
|
|
7872
|
+
* `xOnlyPublicKey` call) instead of one per set. Only the routing buckets
|
|
7873
|
+
* matter here, so the input-index collisions produced by flattening jobs
|
|
7874
|
+
* from different transactions are irrelevant.
|
|
7875
|
+
*/
|
|
7876
|
+
async canBatch(...jobSets) {
|
|
7877
|
+
const plan = await this.classify(jobSets.flat());
|
|
7878
|
+
return plan.descriptorGroups.size === 0;
|
|
7879
|
+
}
|
|
7880
|
+
async sign(tx, jobs) {
|
|
7881
|
+
if (jobs.length === 0) return tx;
|
|
7882
|
+
const { identityIndexes, descriptorGroups } = await this.classify(jobs);
|
|
7743
7883
|
let signed = tx;
|
|
7744
7884
|
if (identityIndexes.length > 0) {
|
|
7745
7885
|
signed = await this.deps.identity.sign(signed, identityIndexes);
|
|
@@ -7780,6 +7920,11 @@ function extractArkProviderUrl(provider) {
|
|
|
7780
7920
|
return typeof serverUrl === "string" && serverUrl.length > 0 ? serverUrl : void 0;
|
|
7781
7921
|
}
|
|
7782
7922
|
var MAINNET_UNILATERAL_EXIT_DELAY = 605184n;
|
|
7923
|
+
function toXOnlyPubKey(pubkey) {
|
|
7924
|
+
if (pubkey.length === 33) return pubkey.slice(1);
|
|
7925
|
+
if (pubkey.length === 32) return pubkey;
|
|
7926
|
+
throw new Error(`invalid signer pubkey length: expected 32 or 33, got ${pubkey.length}`);
|
|
7927
|
+
}
|
|
7783
7928
|
function delayToTimelock(delay) {
|
|
7784
7929
|
return {
|
|
7785
7930
|
value: delay,
|
|
@@ -7797,6 +7942,30 @@ function dedupeTimelocks(timelocks) {
|
|
|
7797
7942
|
}
|
|
7798
7943
|
return deduped;
|
|
7799
7944
|
}
|
|
7945
|
+
async function ensureWalletContract(manager, params) {
|
|
7946
|
+
await manager.createContract(params);
|
|
7947
|
+
}
|
|
7948
|
+
async function resolveBoardingBootTapscript(contractRepository, serverPubKey, baseline) {
|
|
7949
|
+
const serverPubKeyHex = hex.encode(serverPubKey);
|
|
7950
|
+
const candidates = await contractRepository.getContracts({
|
|
7951
|
+
type: ["boarding"],
|
|
7952
|
+
state: "active"
|
|
7953
|
+
});
|
|
7954
|
+
const newest = candidates.filter(
|
|
7955
|
+
(c) => c.params.serverPubKey === serverPubKeyHex && c.metadata?.source === WALLET_RECEIVE_SOURCE
|
|
7956
|
+
).sort((a, b) => {
|
|
7957
|
+
if (b.createdAt !== a.createdAt) return b.createdAt - a.createdAt;
|
|
7958
|
+
return signingDescriptorIndex(b.metadata?.signingDescriptor) - signingDescriptorIndex(a.metadata?.signingDescriptor);
|
|
7959
|
+
})[0];
|
|
7960
|
+
if (!newest?.params.pubKey) return baseline;
|
|
7961
|
+
try {
|
|
7962
|
+
const pubKey = hex.decode(newest.params.pubKey);
|
|
7963
|
+
return new DefaultVtxo.Script({ ...baseline.options, pubKey });
|
|
7964
|
+
} catch (e) {
|
|
7965
|
+
console.warn("Skipping malformed boarding contract at boot", newest.script, e);
|
|
7966
|
+
return baseline;
|
|
7967
|
+
}
|
|
7968
|
+
}
|
|
7800
7969
|
function hasToReadonly(identity) {
|
|
7801
7970
|
return typeof identity === "object" && identity !== null && "toReadonly" in identity && typeof identity.toReadonly === "function";
|
|
7802
7971
|
}
|
|
@@ -7807,7 +7976,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7807
7976
|
this.onchainProvider = onchainProvider;
|
|
7808
7977
|
this.indexerProvider = indexerProvider;
|
|
7809
7978
|
this.arkServerPublicKey = arkServerPublicKey;
|
|
7810
|
-
this.boardingTapscript = boardingTapscript;
|
|
7811
7979
|
this.dustAmount = dustAmount;
|
|
7812
7980
|
this.walletRepository = walletRepository;
|
|
7813
7981
|
this.contractRepository = contractRepository;
|
|
@@ -7823,11 +7991,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7823
7991
|
}
|
|
7824
7992
|
}
|
|
7825
7993
|
this._offchainTapscript = offchainTapscript;
|
|
7994
|
+
this._boardingTapscript = boardingTapscript;
|
|
7826
7995
|
this.watcherConfig = watcherConfig;
|
|
7827
7996
|
this._assetManager = new ReadonlyAssetManager(this.indexerProvider);
|
|
7828
|
-
this.walletContractTimelocks = walletContractTimelocks && walletContractTimelocks.length > 0 ? dedupeTimelocks(walletContractTimelocks) : [
|
|
7829
|
-
this.offchainTapscript.options.csvTimelock ?? DefaultVtxo.Script.DEFAULT_TIMELOCK
|
|
7830
|
-
];
|
|
7997
|
+
this.walletContractTimelocks = walletContractTimelocks && walletContractTimelocks.length > 0 ? dedupeTimelocks(walletContractTimelocks) : [this.offchainTapscript.options.csvTimelock];
|
|
7831
7998
|
}
|
|
7832
7999
|
_contractManager;
|
|
7833
8000
|
_contractManagerInitializing;
|
|
@@ -7851,6 +8018,17 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7851
8018
|
* {@link WalletReceiveRotator.rotate} is the sole intended caller of.
|
|
7852
8019
|
*/
|
|
7853
8020
|
_offchainTapscript;
|
|
8021
|
+
/**
|
|
8022
|
+
* Backing field for the current boarding tapscript (the QR / onboarding
|
|
8023
|
+
* target). Read via the public `boardingTapscript` getter; written only
|
|
8024
|
+
* by {@link Wallet.setBoardingTapscriptForRotation}, the sanctioned
|
|
8025
|
+
* boarding-rotation write path (analogue of `_offchainTapscript`). It is
|
|
8026
|
+
* a *current value*, not a fixed setup constant, because per-derivation
|
|
8027
|
+
* boarding rotation (plan §6-II) swaps it when a fresh boarding address
|
|
8028
|
+
* is explicitly allocated. Static / `auto` wallets never rotate it, so
|
|
8029
|
+
* it stays the index-0 baseline for their lifetime.
|
|
8030
|
+
*/
|
|
8031
|
+
_boardingTapscript;
|
|
7854
8032
|
/**
|
|
7855
8033
|
* Currently-active receive tapscript. Read-only from the outside;
|
|
7856
8034
|
* mutated only via {@link Wallet.setOffchainTapscriptForRotation}
|
|
@@ -7859,6 +8037,52 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7859
8037
|
get offchainTapscript() {
|
|
7860
8038
|
return this._offchainTapscript;
|
|
7861
8039
|
}
|
|
8040
|
+
/**
|
|
8041
|
+
* The wallet's current boarding tapscript (the on-chain onboarding
|
|
8042
|
+
* target). Read-only from the outside; mutated only via
|
|
8043
|
+
* {@link Wallet.setBoardingTapscriptForRotation} when a fresh boarding
|
|
8044
|
+
* address is explicitly allocated. Single-valued for static / `auto`
|
|
8045
|
+
* wallets.
|
|
8046
|
+
*/
|
|
8047
|
+
get boardingTapscript() {
|
|
8048
|
+
return this._boardingTapscript;
|
|
8049
|
+
}
|
|
8050
|
+
/**
|
|
8051
|
+
* Listeners fired after the boarding tapscript rotates to a fresh index
|
|
8052
|
+
* (see {@link Wallet.setBoardingTapscriptForRotation}). A live
|
|
8053
|
+
* {@link notifyIncomingFunds} onchain watcher registers one so it can
|
|
8054
|
+
* re-subscribe to include the newly allocated boarding address within the
|
|
8055
|
+
* same session — without it, a deposit to the fresh address wouldn't fire
|
|
8056
|
+
* a notification until the watcher's next re-init. Always empty for
|
|
8057
|
+
* readonly / static / `auto` wallets, which never rotate boarding.
|
|
8058
|
+
*/
|
|
8059
|
+
_boardingRotationListeners = /* @__PURE__ */ new Set();
|
|
8060
|
+
/**
|
|
8061
|
+
* Register a listener invoked synchronously after each boarding rotation.
|
|
8062
|
+
* Returns an unsubscribe function. Protected: only internal subscribers
|
|
8063
|
+
* (the incoming-funds watcher) participate.
|
|
8064
|
+
*/
|
|
8065
|
+
onBoardingRotation(listener) {
|
|
8066
|
+
this._boardingRotationListeners.add(listener);
|
|
8067
|
+
return () => {
|
|
8068
|
+
this._boardingRotationListeners.delete(listener);
|
|
8069
|
+
};
|
|
8070
|
+
}
|
|
8071
|
+
/**
|
|
8072
|
+
* Notify boarding-rotation listeners. Called by the boarding-rotation
|
|
8073
|
+
* write path ({@link Wallet.setBoardingTapscriptForRotation}) once the new
|
|
8074
|
+
* tapscript is in place. A throwing listener is isolated so it can neither
|
|
8075
|
+
* break the rotation nor starve sibling listeners.
|
|
8076
|
+
*/
|
|
8077
|
+
notifyBoardingRotation() {
|
|
8078
|
+
for (const listener of this._boardingRotationListeners) {
|
|
8079
|
+
try {
|
|
8080
|
+
listener();
|
|
8081
|
+
} catch (e) {
|
|
8082
|
+
console.warn("Boarding-rotation listener failed", e);
|
|
8083
|
+
}
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
7862
8086
|
/**
|
|
7863
8087
|
* Protected helper to set up shared wallet configuration.
|
|
7864
8088
|
* Extracts common logic used by both ReadonlyWallet.create() and Wallet.create().
|
|
@@ -7933,9 +8157,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7933
8157
|
csvTimelock: exitTimelock
|
|
7934
8158
|
};
|
|
7935
8159
|
const offchainTapscript = !delegatePubKey ? new DefaultVtxo.Script(offchainOptions) : new DelegateVtxo.Script({ ...offchainOptions, delegatePubKey });
|
|
7936
|
-
const boardingTapscript =
|
|
7937
|
-
|
|
7938
|
-
|
|
8160
|
+
const boardingTapscript = BoardingContractHandler.createScript({
|
|
8161
|
+
pubKey: hex.encode(pubKey),
|
|
8162
|
+
serverPubKey: hex.encode(serverPubKey),
|
|
8163
|
+
csvTimelock: timelockToSequence(boardingTimelock).toString()
|
|
7939
8164
|
});
|
|
7940
8165
|
const walletRepository = config.storage?.walletRepository ?? new IndexedDBWalletRepository();
|
|
7941
8166
|
const contractRepository = config.storage?.contractRepository ?? new IndexedDBContractRepository();
|
|
@@ -8098,43 +8323,59 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8098
8323
|
await clearSyncCursor(this.walletRepository);
|
|
8099
8324
|
}
|
|
8100
8325
|
/**
|
|
8101
|
-
*
|
|
8326
|
+
* The on-chain (P2TR) addresses of every boarding tapscript this wallet
|
|
8327
|
+
* uses — the current address plus any historical rotated boarding
|
|
8328
|
+
* addresses. The aggregating boarding readers (history, notifications) fan
|
|
8329
|
+
* out over this set so deposits at previous boarding addresses are still
|
|
8330
|
+
* surfaced (plan §6-IV); {@link getBoardingAddress} stays single-valued.
|
|
8331
|
+
*/
|
|
8332
|
+
async getBoardingAddresses() {
|
|
8333
|
+
const tapscripts = await this.getBoardingTapscripts();
|
|
8334
|
+
return tapscripts.map((t) => t.onchainAddress(this.network));
|
|
8335
|
+
}
|
|
8336
|
+
/**
|
|
8337
|
+
* Build a transaction history view across the wallet's boarding addresses
|
|
8338
|
+
* (current + historical rotated; plan §6-IV.1).
|
|
8102
8339
|
*/
|
|
8103
8340
|
async getBoardingTxs() {
|
|
8104
8341
|
const utxos = [];
|
|
8105
8342
|
const commitmentsToIgnore = /* @__PURE__ */ new Set();
|
|
8106
|
-
const
|
|
8107
|
-
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
8343
|
+
const tapscripts = await this.getBoardingTapscripts();
|
|
8108
8344
|
const outspendCache = /* @__PURE__ */ new Map();
|
|
8109
|
-
for (const
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8345
|
+
for (const tapscript of tapscripts) {
|
|
8346
|
+
const boardingAddress = tapscript.onchainAddress(this.network);
|
|
8347
|
+
const scriptHex = hex.encode(tapscript.pkScript);
|
|
8348
|
+
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
8349
|
+
for (const tx of txs) {
|
|
8350
|
+
for (let i = 0; i < tx.vout.length; i++) {
|
|
8351
|
+
const vout = tx.vout[i];
|
|
8352
|
+
if (vout.scriptpubkey_address === boardingAddress) {
|
|
8353
|
+
let spentStatuses = outspendCache.get(tx.txid);
|
|
8354
|
+
if (!spentStatuses) {
|
|
8355
|
+
spentStatuses = await this.onchainProvider.getTxOutspends(tx.txid);
|
|
8356
|
+
outspendCache.set(tx.txid, spentStatuses);
|
|
8357
|
+
}
|
|
8358
|
+
const spentStatus = spentStatuses[i];
|
|
8359
|
+
if (spentStatus?.spent) {
|
|
8360
|
+
commitmentsToIgnore.add(spentStatus.txid);
|
|
8361
|
+
}
|
|
8362
|
+
utxos.push({
|
|
8363
|
+
txid: tx.txid,
|
|
8364
|
+
vout: i,
|
|
8365
|
+
value: Number(vout.value),
|
|
8366
|
+
status: {
|
|
8367
|
+
confirmed: tx.status.confirmed,
|
|
8368
|
+
block_time: tx.status.block_time
|
|
8369
|
+
},
|
|
8370
|
+
isUnrolled: true,
|
|
8371
|
+
virtualStatus: {
|
|
8372
|
+
state: spentStatus?.spent ? "spent" : "settled",
|
|
8373
|
+
commitmentTxIds: spentStatus?.spent ? [spentStatus.txid] : void 0
|
|
8374
|
+
},
|
|
8375
|
+
createdAt: tx.status.confirmed ? new Date(tx.status.block_time * 1e3) : /* @__PURE__ */ new Date(0),
|
|
8376
|
+
script: scriptHex
|
|
8377
|
+
});
|
|
8121
8378
|
}
|
|
8122
|
-
utxos.push({
|
|
8123
|
-
txid: tx.txid,
|
|
8124
|
-
vout: i,
|
|
8125
|
-
value: Number(vout.value),
|
|
8126
|
-
status: {
|
|
8127
|
-
confirmed: tx.status.confirmed,
|
|
8128
|
-
block_time: tx.status.block_time
|
|
8129
|
-
},
|
|
8130
|
-
isUnrolled: true,
|
|
8131
|
-
virtualStatus: {
|
|
8132
|
-
state: spentStatus?.spent ? "spent" : "settled",
|
|
8133
|
-
commitmentTxIds: spentStatus?.spent ? [spentStatus.txid] : void 0
|
|
8134
|
-
},
|
|
8135
|
-
createdAt: tx.status.confirmed ? new Date(tx.status.block_time * 1e3) : /* @__PURE__ */ new Date(0),
|
|
8136
|
-
script: hex.encode(this.boardingTapscript.pkScript)
|
|
8137
|
-
});
|
|
8138
8379
|
}
|
|
8139
8380
|
}
|
|
8140
8381
|
}
|
|
@@ -8164,48 +8405,130 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8164
8405
|
};
|
|
8165
8406
|
}
|
|
8166
8407
|
/**
|
|
8167
|
-
*
|
|
8408
|
+
* The set of boarding tapscripts whose on-chain UTXOs belong to this
|
|
8409
|
+
* wallet — the current display tapscript plus every historical boarding
|
|
8410
|
+
* address it has used. Under per-derivation rotation (plan §6-II) a wallet
|
|
8411
|
+
* can hold unspent boarding UTXOs at several addresses at once, so fund
|
|
8412
|
+
* discovery / spending must enumerate them all, not just the current one
|
|
8413
|
+
* (plan §6-III.1). Deduplicated by scriptPubKey.
|
|
8414
|
+
*
|
|
8415
|
+
* Always includes the index-0 baseline (identity x-only key), which covers
|
|
8416
|
+
* the degenerate equal-delay case where the index-0 boarding row is
|
|
8417
|
+
* coalesced onto a `default` row and so isn't a `boarding`-typed contract.
|
|
8168
8418
|
*/
|
|
8169
|
-
async
|
|
8170
|
-
const
|
|
8171
|
-
const
|
|
8172
|
-
const
|
|
8173
|
-
|
|
8419
|
+
async getBoardingTapscripts() {
|
|
8420
|
+
const byScript = /* @__PURE__ */ new Map();
|
|
8421
|
+
const add = (s) => byScript.set(hex.encode(s.pkScript), s);
|
|
8422
|
+
const boardingCsv = this.boardingTapscript.options.csvTimelock ?? DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
8423
|
+
add(
|
|
8424
|
+
new DefaultVtxo.Script({
|
|
8425
|
+
pubKey: await this.identity.xOnlyPublicKey(),
|
|
8426
|
+
serverPubKey: this.boardingTapscript.options.serverPubKey,
|
|
8427
|
+
csvTimelock: boardingCsv
|
|
8428
|
+
})
|
|
8429
|
+
);
|
|
8430
|
+
add(this.boardingTapscript);
|
|
8431
|
+
const serverPubKeyHex = hex.encode(this.boardingTapscript.options.serverPubKey);
|
|
8432
|
+
const boardingContracts = await this.contractRepository.getContracts({
|
|
8433
|
+
type: ["boarding"]
|
|
8174
8434
|
});
|
|
8175
|
-
|
|
8176
|
-
|
|
8435
|
+
for (const c of boardingContracts) {
|
|
8436
|
+
if (c.params.serverPubKey !== serverPubKeyHex) continue;
|
|
8437
|
+
try {
|
|
8438
|
+
add(BoardingContractHandler.createScript(c.params));
|
|
8439
|
+
} catch (e) {
|
|
8440
|
+
console.warn("Skipping malformed boarding contract", c.script, e);
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
return [...byScript.values()];
|
|
8444
|
+
}
|
|
8445
|
+
/**
|
|
8446
|
+
* Fetch and cache onchain inputs (UTXOs) received at the wallet's boarding
|
|
8447
|
+
* addresses — the current address plus any historical rotated boarding
|
|
8448
|
+
* addresses that still hold unspent UTXOs (plan §6-III.1). Each UTXO is
|
|
8449
|
+
* annotated with the tapscript of the address it actually sits on, so the
|
|
8450
|
+
* spending path forfeits / exits it with the correct per-index leaves.
|
|
8451
|
+
*/
|
|
8452
|
+
async getBoardingUtxos() {
|
|
8453
|
+
const tapscripts = await this.getBoardingTapscripts();
|
|
8454
|
+
const all = [];
|
|
8455
|
+
for (const tapscript of tapscripts) {
|
|
8456
|
+
const address = tapscript.onchainAddress(this.network);
|
|
8457
|
+
const coins = await this.onchainProvider.getCoins(address);
|
|
8458
|
+
const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
|
|
8459
|
+
await this.walletRepository.saveUtxos(address, utxos);
|
|
8460
|
+
all.push(...utxos);
|
|
8461
|
+
}
|
|
8462
|
+
return all;
|
|
8177
8463
|
}
|
|
8178
8464
|
/**
|
|
8179
8465
|
* Subscribe to onchain and offchain notifications for newly received funds.
|
|
8180
8466
|
*
|
|
8467
|
+
* The onchain watcher tracks the full boarding-address set (current +
|
|
8468
|
+
* historical rotated). When boarding rotates *after* subscribing — e.g.
|
|
8469
|
+
* rotate-on-board allocates a fresh address via
|
|
8470
|
+
* {@link getNewBoardingAddress} — the watcher automatically re-subscribes
|
|
8471
|
+
* to widen its set, so a deposit to the new address fires a notification
|
|
8472
|
+
* within the same session (no watcher re-init required). The re-subscribe
|
|
8473
|
+
* is driven by {@link onBoardingRotation}; static / `auto` / readonly
|
|
8474
|
+
* wallets never rotate boarding, so it never fires for them.
|
|
8475
|
+
*
|
|
8181
8476
|
* @param eventCallback - Callback invoked when matching funds are detected
|
|
8182
8477
|
* @returns A function that stops the subscriptions
|
|
8183
8478
|
*/
|
|
8184
8479
|
async notifyIncomingFunds(eventCallback) {
|
|
8185
8480
|
const arkAddress = await this.getAddress();
|
|
8186
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
8187
8481
|
let onchainStopFunc;
|
|
8188
8482
|
let indexerStopFunc;
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
(
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8483
|
+
let boardingRotationStopFunc;
|
|
8484
|
+
let stopped = false;
|
|
8485
|
+
let onchainChain = Promise.resolve();
|
|
8486
|
+
const subscribeOnchain = () => {
|
|
8487
|
+
onchainChain = onchainChain.then(async () => {
|
|
8488
|
+
if (stopped || !this.onchainProvider) return;
|
|
8489
|
+
const boardingAddresses = await this.getBoardingAddresses();
|
|
8490
|
+
if (boardingAddresses.length === 0) return;
|
|
8491
|
+
const boardingAddressSet = new Set(boardingAddresses);
|
|
8492
|
+
const previousStop = onchainStopFunc;
|
|
8493
|
+
const stop = await this.onchainProvider.watchAddresses(
|
|
8494
|
+
boardingAddresses,
|
|
8495
|
+
(txs) => {
|
|
8496
|
+
const coins = txs.flatMap((tx) => {
|
|
8497
|
+
const { txid, status } = tx;
|
|
8498
|
+
const matched = [];
|
|
8499
|
+
tx.vout.forEach((v, vout) => {
|
|
8500
|
+
if (boardingAddressSet.has(v.scriptpubkey_address)) {
|
|
8501
|
+
matched.push({
|
|
8502
|
+
txid,
|
|
8503
|
+
vout,
|
|
8504
|
+
value: Number(v.value),
|
|
8505
|
+
status
|
|
8506
|
+
});
|
|
8507
|
+
}
|
|
8508
|
+
});
|
|
8509
|
+
return matched;
|
|
8510
|
+
});
|
|
8511
|
+
eventCallback({
|
|
8512
|
+
type: "utxo",
|
|
8513
|
+
coins
|
|
8514
|
+
});
|
|
8515
|
+
}
|
|
8516
|
+
);
|
|
8517
|
+
if (stopped) {
|
|
8518
|
+
stop();
|
|
8519
|
+
return;
|
|
8206
8520
|
}
|
|
8207
|
-
|
|
8208
|
-
|
|
8521
|
+
onchainStopFunc = stop;
|
|
8522
|
+
previousStop?.();
|
|
8523
|
+
}).catch((e) => {
|
|
8524
|
+
console.warn("Failed to (re)subscribe boarding-funds watcher", e);
|
|
8525
|
+
});
|
|
8526
|
+
return onchainChain;
|
|
8527
|
+
};
|
|
8528
|
+
boardingRotationStopFunc = this.onBoardingRotation(() => {
|
|
8529
|
+
void subscribeOnchain();
|
|
8530
|
+
});
|
|
8531
|
+
await subscribeOnchain();
|
|
8209
8532
|
if (this.indexerProvider && arkAddress) {
|
|
8210
8533
|
const cm = await this.getContractManager();
|
|
8211
8534
|
let annotationQueue = Promise.resolve();
|
|
@@ -8234,7 +8557,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8234
8557
|
});
|
|
8235
8558
|
}
|
|
8236
8559
|
const stopFunc = () => {
|
|
8560
|
+
stopped = true;
|
|
8561
|
+
boardingRotationStopFunc?.();
|
|
8237
8562
|
onchainStopFunc?.();
|
|
8563
|
+
onchainStopFunc = void 0;
|
|
8238
8564
|
indexerStopFunc?.();
|
|
8239
8565
|
};
|
|
8240
8566
|
return stopFunc;
|
|
@@ -8348,7 +8674,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8348
8674
|
csvTimelock
|
|
8349
8675
|
});
|
|
8350
8676
|
const defaultScriptHex = hex.encode(defaultScript.pkScript);
|
|
8351
|
-
await manager
|
|
8677
|
+
await ensureWalletContract(manager, {
|
|
8352
8678
|
type: "default",
|
|
8353
8679
|
params: {
|
|
8354
8680
|
pubKey: hex.encode(defaultScript.options.pubKey),
|
|
@@ -8381,6 +8707,23 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8381
8707
|
});
|
|
8382
8708
|
}
|
|
8383
8709
|
}
|
|
8710
|
+
const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
8711
|
+
const baselineBoarding = new DefaultVtxo.Script({
|
|
8712
|
+
pubKey: baselinePubkey,
|
|
8713
|
+
serverPubKey: this.boardingTapscript.options.serverPubKey,
|
|
8714
|
+
csvTimelock: boardingCsvTimelock
|
|
8715
|
+
});
|
|
8716
|
+
await ensureWalletContract(manager, {
|
|
8717
|
+
type: "boarding",
|
|
8718
|
+
params: {
|
|
8719
|
+
pubKey: hex.encode(baselineBoarding.options.pubKey),
|
|
8720
|
+
serverPubKey: hex.encode(baselineBoarding.options.serverPubKey),
|
|
8721
|
+
csvTimelock: timelockToSequence(boardingCsvTimelock).toString()
|
|
8722
|
+
},
|
|
8723
|
+
script: hex.encode(baselineBoarding.pkScript),
|
|
8724
|
+
address: baselineBoarding.address(this.network.hrp, this.arkServerPublicKey).encode(),
|
|
8725
|
+
state: "active"
|
|
8726
|
+
});
|
|
8384
8727
|
return manager;
|
|
8385
8728
|
}
|
|
8386
8729
|
/** Dispose wallet-owned managers and release background resources. */
|
|
@@ -8478,6 +8821,72 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8478
8821
|
setOffchainTapscriptForRotation(tapscript) {
|
|
8479
8822
|
this._offchainTapscript = tapscript;
|
|
8480
8823
|
}
|
|
8824
|
+
/**
|
|
8825
|
+
* @internal Sole write path for `boardingTapscript` after construction.
|
|
8826
|
+
* Called by {@link Wallet.getNewBoardingAddress} once the rotated
|
|
8827
|
+
* boarding contract has been persisted. External code must treat
|
|
8828
|
+
* `boardingTapscript` as read-only.
|
|
8829
|
+
*/
|
|
8830
|
+
setBoardingTapscriptForRotation(tapscript) {
|
|
8831
|
+
this._boardingTapscript = tapscript;
|
|
8832
|
+
this.notifyBoardingRotation();
|
|
8833
|
+
}
|
|
8834
|
+
/**
|
|
8835
|
+
* Allocate and return a *fresh* on-chain boarding address, rotating the
|
|
8836
|
+
* wallet's current boarding tapscript to a new HD index.
|
|
8837
|
+
*
|
|
8838
|
+
* This is the explicit boarding allocator — the analogue of dotnet's
|
|
8839
|
+
* `GetNextContract(NextContractPurpose.Boarding)`. Unlike
|
|
8840
|
+
* {@link getBoardingAddress} (a stable read of the current display
|
|
8841
|
+
* address that never burns an index), each call here:
|
|
8842
|
+
*
|
|
8843
|
+
* - allocates the next index from the shared HD stream (so boarding and
|
|
8844
|
+
* L2 receive interleave on one monotonic index);
|
|
8845
|
+
* - builds the boarding tapscript at that index with the boarding-exit
|
|
8846
|
+
* CSV;
|
|
8847
|
+
* - persists an `active` `boarding` contract tagged
|
|
8848
|
+
* {@link WALLET_RECEIVE_SOURCE} (with its `signingDescriptor`) so the
|
|
8849
|
+
* ContractWatcher monitors it, boot can restore it as the current
|
|
8850
|
+
* boarding address, and descriptor-aware signing can recover the
|
|
8851
|
+
* per-index key;
|
|
8852
|
+
* - swaps the wallet's current `boardingTapscript`.
|
|
8853
|
+
*
|
|
8854
|
+
* Gated by `walletMode`: a static / `auto` wallet has no descriptor
|
|
8855
|
+
* provider and keeps a single index-0 boarding address for its lifetime,
|
|
8856
|
+
* so this returns the existing {@link getBoardingAddress} unchanged
|
|
8857
|
+
* (no rotation, no index burned).
|
|
8858
|
+
*/
|
|
8859
|
+
async getNewBoardingAddress() {
|
|
8860
|
+
const provider = this._descriptorProvider;
|
|
8861
|
+
if (!provider) {
|
|
8862
|
+
return this.getBoardingAddress();
|
|
8863
|
+
}
|
|
8864
|
+
const descriptor = await provider.getNextSigningDescriptor();
|
|
8865
|
+
const pubKey = deriveDescriptorLeafPubKey(descriptor);
|
|
8866
|
+
const newBoarding = new DefaultVtxo.Script({
|
|
8867
|
+
...this._boardingTapscript.options,
|
|
8868
|
+
pubKey
|
|
8869
|
+
});
|
|
8870
|
+
const csvTimelock = newBoarding.options.csvTimelock ?? DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
8871
|
+
const manager = await this.getContractManager();
|
|
8872
|
+
await manager.createContract({
|
|
8873
|
+
type: "boarding",
|
|
8874
|
+
params: {
|
|
8875
|
+
pubKey: hex.encode(pubKey),
|
|
8876
|
+
serverPubKey: hex.encode(newBoarding.options.serverPubKey),
|
|
8877
|
+
csvTimelock: timelockToSequence(csvTimelock).toString()
|
|
8878
|
+
},
|
|
8879
|
+
script: hex.encode(newBoarding.pkScript),
|
|
8880
|
+
address: newBoarding.address(this.network.hrp, this.arkServerPublicKey).encode(),
|
|
8881
|
+
state: "active",
|
|
8882
|
+
metadata: {
|
|
8883
|
+
source: WALLET_RECEIVE_SOURCE,
|
|
8884
|
+
signingDescriptor: descriptor
|
|
8885
|
+
}
|
|
8886
|
+
});
|
|
8887
|
+
this.setBoardingTapscriptForRotation(newBoarding);
|
|
8888
|
+
return newBoarding.onchainAddress(this.network);
|
|
8889
|
+
}
|
|
8481
8890
|
/**
|
|
8482
8891
|
* Async mutex that serializes all operations submitting VTXOs to the Arkade
|
|
8483
8892
|
* server (`settle`, `send`, `sendBitcoin`). This prevents VtxoManager's
|
|
@@ -8562,12 +8971,25 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8562
8971
|
const staticDescriptor = hd ? void 0 : `tr(${hex.encode(await this.identity.xOnlyPublicKey())})`;
|
|
8563
8972
|
const materialize = (index) => hd ? provider.materializeDescriptorAt(index) : staticDescriptor;
|
|
8564
8973
|
const delegatePubKey = this.offchainTapscript instanceof DelegateVtxo.Script ? this.offchainTapscript.options.delegatePubKey : void 0;
|
|
8974
|
+
const arkInfo = await this.arkProvider.getInfo();
|
|
8975
|
+
const currentSignerPubKey = toXOnlyPubKey(hex.decode(arkInfo.signerPubkey));
|
|
8976
|
+
const deprecatedSignerPubKeys = arkInfo.deprecatedSigners.map(
|
|
8977
|
+
(s) => toXOnlyPubKey(hex.decode(s.pubkey))
|
|
8978
|
+
);
|
|
8565
8979
|
const deps = {
|
|
8566
8980
|
indexerProvider: this.indexerProvider,
|
|
8567
8981
|
onchainProvider: this.onchainProvider,
|
|
8568
8982
|
network: { hrp: this.network.hrp },
|
|
8569
|
-
|
|
8983
|
+
// Full network for the boarding on-chain (P2TR) probe — the
|
|
8984
|
+
// `{ hrp }` shape above lacks the `bech32` data
|
|
8985
|
+
// `VtxoScript.onchainAddress` needs (plan §6-I.1).
|
|
8986
|
+
onchainNetwork: this.network,
|
|
8987
|
+
serverPubKey: currentSignerPubKey,
|
|
8988
|
+
deprecatedSignerPubKeys,
|
|
8570
8989
|
csvTimelocks: this.walletContractTimelocks,
|
|
8990
|
+
// Boarding-exit CSV so the boarding handler can build its
|
|
8991
|
+
// candidate script (distinct from the unilateral-exit matrix).
|
|
8992
|
+
boardingTimelock: this.boardingTapscript.options.csvTimelock ?? DefaultVtxo.Script.DEFAULT_TIMELOCK,
|
|
8571
8993
|
delegatePubKey
|
|
8572
8994
|
};
|
|
8573
8995
|
const result = await manager.scanContracts({
|
|
@@ -8699,6 +9121,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8699
9121
|
boot?.rotator,
|
|
8700
9122
|
boot?.provider
|
|
8701
9123
|
);
|
|
9124
|
+
if (boot?.provider) {
|
|
9125
|
+
const resolvedBoarding = await resolveBoardingBootTapscript(
|
|
9126
|
+
setup.contractRepository,
|
|
9127
|
+
setup.serverPubKey,
|
|
9128
|
+
setup.boardingTapscript
|
|
9129
|
+
);
|
|
9130
|
+
if (resolvedBoarding !== setup.boardingTapscript) {
|
|
9131
|
+
wallet.setBoardingTapscriptForRotation(resolvedBoarding);
|
|
9132
|
+
}
|
|
9133
|
+
}
|
|
8702
9134
|
await wallet.getVtxoManager();
|
|
8703
9135
|
return wallet;
|
|
8704
9136
|
}
|
|
@@ -8866,7 +9298,10 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8866
9298
|
}
|
|
8867
9299
|
const vtxos = await this.getVtxos({ withRecoverable: true });
|
|
8868
9300
|
const filteredVtxos = [];
|
|
8869
|
-
for (const vtxo of vtxos) {
|
|
9301
|
+
for (const vtxo of byValueDescending(vtxos)) {
|
|
9302
|
+
if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
|
|
9303
|
+
break;
|
|
9304
|
+
}
|
|
8870
9305
|
const inputFee = estimator.evalOffchainInput({
|
|
8871
9306
|
amount: BigInt(vtxo.value),
|
|
8872
9307
|
type: vtxo.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
|
|
@@ -8999,6 +9434,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8999
9434
|
eventCallback: eventCallback ? (event) => Promise.resolve(eventCallback(event)) : void 0
|
|
9000
9435
|
});
|
|
9001
9436
|
await this.updateDbAfterSettle(params.inputs, commitmentTxid);
|
|
9437
|
+
await this.maybeRotateBoardingAfterBoard(params.inputs);
|
|
9002
9438
|
return commitmentTxid;
|
|
9003
9439
|
} catch (error) {
|
|
9004
9440
|
const inputIds = params.inputs.map((i) => `${i.txid}:${i.vout}`).join(",");
|
|
@@ -9016,6 +9452,41 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9016
9452
|
});
|
|
9017
9453
|
}
|
|
9018
9454
|
}
|
|
9455
|
+
/**
|
|
9456
|
+
* Rotate the boarding address after a board (rotate-on-board trigger).
|
|
9457
|
+
*
|
|
9458
|
+
* Mirrors {@link WalletReceiveRotator}'s L2 rotation, but driven by a
|
|
9459
|
+
* board instead of a `vtxo_received` event: when a settle consumes at
|
|
9460
|
+
* least one boarding (on-chain) UTXO, the current boarding address has
|
|
9461
|
+
* served its purpose, so we allocate a fresh one via
|
|
9462
|
+
* {@link getNewBoardingAddress}. A settle that consumed only VTXOs (a
|
|
9463
|
+
* renewal / offboard) is not a board and leaves the boarding address
|
|
9464
|
+
* untouched.
|
|
9465
|
+
*
|
|
9466
|
+
* Boarding inputs are the non-VTXO coins (no `virtualStatus`), the same
|
|
9467
|
+
* discriminator {@link handleSettlementFinalizationEvent} uses; the
|
|
9468
|
+
* `typeof` guard skips arknote string inputs before the `in` test.
|
|
9469
|
+
*
|
|
9470
|
+
* No-ops for static / `auto` wallets (no descriptor provider — boarding
|
|
9471
|
+
* stays on its fixed index-0 address). Best-effort and non-fatal: the
|
|
9472
|
+
* settle has already committed and its txid must be returned, so a
|
|
9473
|
+
* rotation failure is logged and swallowed rather than thrown. Funds at
|
|
9474
|
+
* the retired boarding address remain discoverable — the old `boarding`
|
|
9475
|
+
* contract stays active and {@link getBoardingUtxos} fans out over the
|
|
9476
|
+
* full historical boarding set.
|
|
9477
|
+
*/
|
|
9478
|
+
async maybeRotateBoardingAfterBoard(inputs) {
|
|
9479
|
+
if (!this._descriptorProvider) return;
|
|
9480
|
+
const consumedBoarding = inputs.some(
|
|
9481
|
+
(input) => typeof input !== "string" && !("virtualStatus" in input)
|
|
9482
|
+
);
|
|
9483
|
+
if (!consumedBoarding) return;
|
|
9484
|
+
try {
|
|
9485
|
+
await this.getNewBoardingAddress();
|
|
9486
|
+
} catch (e) {
|
|
9487
|
+
console.warn("Failed to rotate boarding address after board", e);
|
|
9488
|
+
}
|
|
9489
|
+
}
|
|
9019
9490
|
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
9020
9491
|
const signedForfeits = [];
|
|
9021
9492
|
const isVtxo = (input) => "virtualStatus" in input;
|
|
@@ -9226,6 +9697,19 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9226
9697
|
}
|
|
9227
9698
|
return jobs;
|
|
9228
9699
|
}
|
|
9700
|
+
/**
|
|
9701
|
+
* @internal Sign an on-chain boarding exit / sweep transaction, routing
|
|
9702
|
+
* each input to the correct key by its `witnessUtxo.script`: the identity
|
|
9703
|
+
* for index-0 / static boarding, the per-index descriptor for a rotated
|
|
9704
|
+
* boarding UTXO (plan §6-III.3). Used by
|
|
9705
|
+
* {@link VtxoManager.sweepExpiredBoardingUtxos}; without it, the
|
|
9706
|
+
* unilateral exit of a rotated boarding UTXO would be signed with the
|
|
9707
|
+
* wrong (index-0) key and rejected.
|
|
9708
|
+
*/
|
|
9709
|
+
async signOnchainBoardingTx(tx) {
|
|
9710
|
+
const signed = await this._signerRouter.sign(tx, this.inputSigningJobsFromWitnessUtxos(tx));
|
|
9711
|
+
return signed;
|
|
9712
|
+
}
|
|
9229
9713
|
async safeRegisterIntent(intent, inputs) {
|
|
9230
9714
|
try {
|
|
9231
9715
|
return await this.arkProvider.registerIntent(intent);
|
|
@@ -9330,16 +9814,38 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9330
9814
|
seen.add(pendingTx.arkTxid);
|
|
9331
9815
|
batchPending.push(pendingTx.arkTxid);
|
|
9332
9816
|
try {
|
|
9333
|
-
const
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
this.inputSigningJobsFromWitnessUtxos(tx)
|
|
9339
|
-
);
|
|
9340
|
-
return base64.encode(signedCheckpoint.toPSBT());
|
|
9341
|
-
})
|
|
9817
|
+
const checkpointTxs = pendingTx.signedCheckpointTxs.map(
|
|
9818
|
+
(c) => Transaction$2.fromPSBT(base64.decode(c))
|
|
9819
|
+
);
|
|
9820
|
+
const checkpointJobs = checkpointTxs.map(
|
|
9821
|
+
(tx) => this.inputSigningJobsFromWitnessUtxos(tx)
|
|
9342
9822
|
);
|
|
9823
|
+
const identity = this.identity;
|
|
9824
|
+
const batchEligible = isBatchSignable(identity) && await this._signerRouter.canBatch(...checkpointJobs);
|
|
9825
|
+
let finalCheckpoints;
|
|
9826
|
+
if (batchEligible) {
|
|
9827
|
+
const requests = checkpointTxs.map((tx, i) => ({
|
|
9828
|
+
tx,
|
|
9829
|
+
inputIndexes: checkpointJobs[i].map((j) => j.index)
|
|
9830
|
+
}));
|
|
9831
|
+
const signed = await identity.signMultiple(requests);
|
|
9832
|
+
if (signed.length !== requests.length) {
|
|
9833
|
+
throw new Error(
|
|
9834
|
+
`signMultiple returned ${signed.length} transactions, expected ${requests.length}`
|
|
9835
|
+
);
|
|
9836
|
+
}
|
|
9837
|
+
finalCheckpoints = signed.map((tx) => base64.encode(tx.toPSBT()));
|
|
9838
|
+
} else {
|
|
9839
|
+
finalCheckpoints = await Promise.all(
|
|
9840
|
+
checkpointTxs.map(async (tx, i) => {
|
|
9841
|
+
const signedCheckpoint = await this._signerRouter.sign(
|
|
9842
|
+
tx,
|
|
9843
|
+
checkpointJobs[i]
|
|
9844
|
+
);
|
|
9845
|
+
return base64.encode(signedCheckpoint.toPSBT());
|
|
9846
|
+
})
|
|
9847
|
+
);
|
|
9848
|
+
}
|
|
9343
9849
|
await this.arkProvider.finalizeTx(pendingTx.arkTxid, finalCheckpoints);
|
|
9344
9850
|
batchFinalized.push(pendingTx.arkTxid);
|
|
9345
9851
|
} catch (error) {
|
|
@@ -9567,22 +10073,65 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9567
10073
|
index,
|
|
9568
10074
|
lookupScript: VtxoScript.decode(input.tapTree).pkScript
|
|
9569
10075
|
}));
|
|
9570
|
-
const
|
|
10076
|
+
const checkpointJobs = offchainTx.checkpoints.map(
|
|
10077
|
+
(c) => this.inputSigningJobsFromWitnessUtxos(c)
|
|
10078
|
+
);
|
|
10079
|
+
let signedVirtualTx;
|
|
10080
|
+
let userSignedCheckpoints;
|
|
10081
|
+
const identity = this.identity;
|
|
10082
|
+
const batchEligible = isBatchSignable(identity) && await this._signerRouter.canBatch(arkTxJobs, ...checkpointJobs);
|
|
10083
|
+
if (batchEligible) {
|
|
10084
|
+
const requests = [
|
|
10085
|
+
{
|
|
10086
|
+
tx: offchainTx.arkTx.clone(),
|
|
10087
|
+
inputIndexes: arkTxJobs.map((j) => j.index)
|
|
10088
|
+
},
|
|
10089
|
+
...offchainTx.checkpoints.map((c, i) => ({
|
|
10090
|
+
tx: c.clone(),
|
|
10091
|
+
inputIndexes: checkpointJobs[i].map((j) => j.index)
|
|
10092
|
+
}))
|
|
10093
|
+
];
|
|
10094
|
+
const signed = await identity.signMultiple(requests);
|
|
10095
|
+
if (signed.length !== requests.length) {
|
|
10096
|
+
throw new Error(
|
|
10097
|
+
`signMultiple returned ${signed.length} transactions, expected ${requests.length}`
|
|
10098
|
+
);
|
|
10099
|
+
}
|
|
10100
|
+
const [firstSignedTx, ...signedCheckpoints] = signed;
|
|
10101
|
+
signedVirtualTx = firstSignedTx;
|
|
10102
|
+
userSignedCheckpoints = signedCheckpoints;
|
|
10103
|
+
} else {
|
|
10104
|
+
signedVirtualTx = await this._signerRouter.sign(offchainTx.arkTx, arkTxJobs);
|
|
10105
|
+
}
|
|
9571
10106
|
await this.setPendingTxFlag(true);
|
|
9572
10107
|
const { arkTxid, signedCheckpointTxs } = await this.arkProvider.submitTx(
|
|
9573
10108
|
base64.encode(signedVirtualTx.toPSBT()),
|
|
9574
10109
|
offchainTx.checkpoints.map((c) => base64.encode(c.toPSBT()))
|
|
9575
10110
|
);
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
this.inputSigningJobsFromWitnessUtxos(tx)
|
|
10111
|
+
let finalCheckpoints;
|
|
10112
|
+
if (userSignedCheckpoints) {
|
|
10113
|
+
if (signedCheckpointTxs.length !== userSignedCheckpoints.length) {
|
|
10114
|
+
throw new Error(
|
|
10115
|
+
`submitTx returned ${signedCheckpointTxs.length} checkpoints, expected ${userSignedCheckpoints.length}`
|
|
9582
10116
|
);
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
10117
|
+
}
|
|
10118
|
+
finalCheckpoints = signedCheckpointTxs.map((c, i) => {
|
|
10119
|
+
const serverSigned = Transaction$2.fromPSBT(base64.decode(c));
|
|
10120
|
+
combineTapscriptSigs(userSignedCheckpoints[i], serverSigned);
|
|
10121
|
+
return base64.encode(serverSigned.toPSBT());
|
|
10122
|
+
});
|
|
10123
|
+
} else {
|
|
10124
|
+
finalCheckpoints = await Promise.all(
|
|
10125
|
+
signedCheckpointTxs.map(async (c) => {
|
|
10126
|
+
const tx = Transaction$2.fromPSBT(base64.decode(c));
|
|
10127
|
+
const signedCheckpoint = await this._signerRouter.sign(
|
|
10128
|
+
tx,
|
|
10129
|
+
this.inputSigningJobsFromWitnessUtxos(tx)
|
|
10130
|
+
);
|
|
10131
|
+
return base64.encode(signedCheckpoint.toPSBT());
|
|
10132
|
+
})
|
|
10133
|
+
);
|
|
10134
|
+
}
|
|
9586
10135
|
await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
|
|
9587
10136
|
try {
|
|
9588
10137
|
await this.setPendingTxFlag(false);
|
|
@@ -9727,10 +10276,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9727
10276
|
// mark virtual outputs as spent/settled, remove boarding inputs
|
|
9728
10277
|
async updateDbAfterSettle(inputs, commitmentTxid) {
|
|
9729
10278
|
try {
|
|
9730
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
9731
10279
|
const spentVtxos = [];
|
|
9732
10280
|
const inputArkTxIds = /* @__PURE__ */ new Set();
|
|
9733
|
-
const
|
|
10281
|
+
const boardingRemovalsByAddress = /* @__PURE__ */ new Map();
|
|
9734
10282
|
const isVtxo = (input) => "virtualStatus" in input;
|
|
9735
10283
|
const vtxoInputs = inputs.filter(isVtxo);
|
|
9736
10284
|
const cm = await this.getContractManager();
|
|
@@ -9752,7 +10300,20 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9752
10300
|
isSpent: true
|
|
9753
10301
|
});
|
|
9754
10302
|
} else {
|
|
9755
|
-
|
|
10303
|
+
let sourceAddress;
|
|
10304
|
+
try {
|
|
10305
|
+
sourceAddress = VtxoScript.decode(input.tapTree).onchainAddress(
|
|
10306
|
+
this.network
|
|
10307
|
+
);
|
|
10308
|
+
} catch {
|
|
10309
|
+
sourceAddress = this.boardingTapscript.onchainAddress(this.network);
|
|
10310
|
+
}
|
|
10311
|
+
let set = boardingRemovalsByAddress.get(sourceAddress);
|
|
10312
|
+
if (!set) {
|
|
10313
|
+
set = /* @__PURE__ */ new Set();
|
|
10314
|
+
boardingRemovalsByAddress.set(sourceAddress, set);
|
|
10315
|
+
}
|
|
10316
|
+
set.add(`${input.txid}:${input.vout}`);
|
|
9756
10317
|
}
|
|
9757
10318
|
}
|
|
9758
10319
|
if (spentVtxos.length > 0) {
|
|
@@ -9784,14 +10345,12 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9784
10345
|
);
|
|
9785
10346
|
}
|
|
9786
10347
|
}
|
|
9787
|
-
|
|
9788
|
-
const currentUtxos = await this.walletRepository.getUtxos(
|
|
9789
|
-
const filtered = currentUtxos.filter(
|
|
9790
|
-
|
|
9791
|
-
);
|
|
9792
|
-
await this.walletRepository.deleteUtxos(boardingAddress);
|
|
10348
|
+
for (const [address, toRemove] of boardingRemovalsByAddress) {
|
|
10349
|
+
const currentUtxos = await this.walletRepository.getUtxos(address);
|
|
10350
|
+
const filtered = currentUtxos.filter((u) => !toRemove.has(`${u.txid}:${u.vout}`));
|
|
10351
|
+
await this.walletRepository.deleteUtxos(address);
|
|
9793
10352
|
if (filtered.length > 0) {
|
|
9794
|
-
await this.walletRepository.saveUtxos(
|
|
10353
|
+
await this.walletRepository.saveUtxos(address, filtered);
|
|
9795
10354
|
}
|
|
9796
10355
|
}
|
|
9797
10356
|
} catch (e) {
|
|
@@ -9832,12 +10391,17 @@ function selectVirtualCoins(coins, targetAmount) {
|
|
|
9832
10391
|
}
|
|
9833
10392
|
async function waitForIncomingFunds(wallet) {
|
|
9834
10393
|
let stopFunc;
|
|
10394
|
+
let settled = false;
|
|
9835
10395
|
return new Promise((resolve) => {
|
|
9836
|
-
wallet.notifyIncomingFunds((
|
|
9837
|
-
|
|
9838
|
-
if (
|
|
10396
|
+
wallet.notifyIncomingFunds((funds) => {
|
|
10397
|
+
const hasFunds = funds.type === "utxo" ? funds.coins.length > 0 : funds.newVtxos.length > 0;
|
|
10398
|
+
if (settled || !hasFunds) return;
|
|
10399
|
+
settled = true;
|
|
10400
|
+
resolve(funds);
|
|
10401
|
+
stopFunc?.();
|
|
9839
10402
|
}).then((stop) => {
|
|
9840
10403
|
stopFunc = stop;
|
|
10404
|
+
if (settled) stop();
|
|
9841
10405
|
});
|
|
9842
10406
|
});
|
|
9843
10407
|
}
|
|
@@ -11186,9 +11750,7 @@ var WalletMessageHandler = class {
|
|
|
11186
11750
|
);
|
|
11187
11751
|
}
|
|
11188
11752
|
if (funds.type === "utxo") {
|
|
11189
|
-
const utxos =
|
|
11190
|
-
const boardingAddress = await this.readonlyWallet.getBoardingAddress();
|
|
11191
|
-
await this.walletRepository?.saveUtxos(boardingAddress, utxos);
|
|
11753
|
+
const utxos = await this.readonlyWallet.getBoardingUtxos();
|
|
11192
11754
|
this.scheduleForNextTick(
|
|
11193
11755
|
() => this.tagged({
|
|
11194
11756
|
type: "UTXO_UPDATE",
|
|
@@ -11217,13 +11779,16 @@ var WalletMessageHandler = class {
|
|
|
11217
11779
|
return;
|
|
11218
11780
|
}
|
|
11219
11781
|
const vtxos = await this.getVtxosFromRepo();
|
|
11220
|
-
const
|
|
11221
|
-
const
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11782
|
+
const boardingAddresses = await this.readonlyWallet.getBoardingAddresses();
|
|
11783
|
+
const fresh = await this.readonlyWallet.getBoardingUtxos();
|
|
11784
|
+
const freshKeys = new Set(fresh.map((u) => `${u.txid}:${u.vout}`));
|
|
11785
|
+
for (const addr of boardingAddresses) {
|
|
11786
|
+
const cached = await this.walletRepository.getUtxos(addr);
|
|
11787
|
+
const kept = cached.filter((u) => freshKeys.has(`${u.txid}:${u.vout}`));
|
|
11788
|
+
if (kept.length === cached.length) continue;
|
|
11789
|
+
await this.walletRepository.deleteUtxos(addr);
|
|
11790
|
+
if (kept.length > 0) await this.walletRepository.saveUtxos(addr, kept);
|
|
11791
|
+
}
|
|
11227
11792
|
const address = await this.readonlyWallet.getAddress();
|
|
11228
11793
|
const txs = await this.buildTransactionHistoryFromCache(vtxos);
|
|
11229
11794
|
if (txs) await this.walletRepository.saveTransactions(address, txs);
|
|
@@ -14014,5 +14579,5 @@ function isArkContract(str) {
|
|
|
14014
14579
|
}
|
|
14015
14580
|
|
|
14016
14581
|
export { ArkNote, AssetManager, BIP322, Batch, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, InMemoryContractRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBWalletRepository, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, buildForfeitTx, buildOffchainTx, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, decodeArkContract, deserializeAssets, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, isArkContract, isBatchSignable, isDiscoverable, isExpired, isRecoverable, isSpendable, isSubdust, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeUtxo, serializeVtxo, setupServiceWorker, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
|
|
14017
|
-
//# sourceMappingURL=chunk-
|
|
14018
|
-
//# sourceMappingURL=chunk-
|
|
14582
|
+
//# sourceMappingURL=chunk-HFXEUW55.js.map
|
|
14583
|
+
//# sourceMappingURL=chunk-HFXEUW55.js.map
|