@arkade-os/sdk 0.4.66 → 0.4.68

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.
Files changed (80) hide show
  1. package/README.md +71 -15
  2. package/dist/adapters/expo.cjs +5 -5
  3. package/dist/adapters/expo.d.cts +2 -2
  4. package/dist/adapters/expo.d.ts +2 -2
  5. package/dist/adapters/expo.js +3 -3
  6. package/dist/adapters/indexedDB.cjs +5 -5
  7. package/dist/adapters/indexedDB.js +4 -4
  8. package/dist/{asyncStorageTaskQueue-BZcIN7sA.d.cts → asyncStorageTaskQueue-B2CcXIHU.d.cts} +1 -1
  9. package/dist/{asyncStorageTaskQueue-B6SijMi3.d.ts → asyncStorageTaskQueue-DaZlVTbM.d.ts} +1 -1
  10. package/dist/{chunk-2JZGPXU4.js → chunk-33Q2RFET.js} +3 -3
  11. package/dist/{chunk-2JZGPXU4.js.map → chunk-33Q2RFET.js.map} +1 -1
  12. package/dist/{chunk-CYOI4PEA.js → chunk-45FKMOJH.js} +160 -114
  13. package/dist/chunk-45FKMOJH.js.map +1 -0
  14. package/dist/{chunk-J33ZEM3A.js → chunk-4CMXV5SF.js} +45 -14
  15. package/dist/chunk-4CMXV5SF.js.map +1 -0
  16. package/dist/{chunk-LL47QIHD.cjs → chunk-4KCVS4ZN.cjs} +10 -10
  17. package/dist/{chunk-LL47QIHD.cjs.map → chunk-4KCVS4ZN.cjs.map} +1 -1
  18. package/dist/{chunk-PXPPZF5K.cjs → chunk-6YUACVX4.cjs} +205 -159
  19. package/dist/chunk-6YUACVX4.cjs.map +1 -0
  20. package/dist/{chunk-ZIRFXBKL.cjs → chunk-EJOFL5WH.cjs} +47 -13
  21. package/dist/chunk-EJOFL5WH.cjs.map +1 -0
  22. package/dist/{chunk-R5POS3RS.js → chunk-G4I6PU5H.js} +4 -4
  23. package/dist/{chunk-R5POS3RS.js.map → chunk-G4I6PU5H.js.map} +1 -1
  24. package/dist/{chunk-55ZZ6RBL.js → chunk-GQ4NMQGR.js} +4 -4
  25. package/dist/{chunk-55ZZ6RBL.js.map → chunk-GQ4NMQGR.js.map} +1 -1
  26. package/dist/{chunk-IM6IKNSL.cjs → chunk-JGMSZIE4.cjs} +17 -17
  27. package/dist/{chunk-IM6IKNSL.cjs.map → chunk-JGMSZIE4.cjs.map} +1 -1
  28. package/dist/{chunk-3S3JZUEN.cjs → chunk-PJR63FTK.cjs} +17 -17
  29. package/dist/{chunk-3S3JZUEN.cjs.map → chunk-PJR63FTK.cjs.map} +1 -1
  30. package/dist/{chunk-DSC3G6QZ.js → chunk-VBHXBI7F.js} +1060 -185
  31. package/dist/chunk-VBHXBI7F.js.map +1 -0
  32. package/dist/{chunk-5E6IZNRG.cjs → chunk-WCJVKFAP.cjs} +1467 -581
  33. package/dist/chunk-WCJVKFAP.cjs.map +1 -0
  34. package/dist/contracts/handlers/index.cjs +9 -9
  35. package/dist/contracts/handlers/index.d.cts +3 -3
  36. package/dist/contracts/handlers/index.d.ts +3 -3
  37. package/dist/contracts/handlers/index.js +2 -2
  38. package/dist/{delegate-T-vgxMYZ.d.ts → delegate-AomVWxO_.d.ts} +1 -1
  39. package/dist/{delegate-YXupcWWJ.d.cts → delegate-Bffc5f54.d.cts} +1 -1
  40. package/dist/{wallet-CNibc3yJ.d.ts → hdWalletCapable-B5nRs3RH.d.cts} +318 -127
  41. package/dist/{wallet-DJN1QgFt.d.cts → hdWalletCapable-DZdIVa23.d.ts} +318 -127
  42. package/dist/{index-uUE_uQPn.d.ts → index-C7RZCfoY.d.cts} +222 -19
  43. package/dist/{index-uUE_uQPn.d.cts → index-C7RZCfoY.d.ts} +222 -19
  44. package/dist/{index-e7LYXk1d.d.cts → index-jouvy8FD.d.ts} +172 -99
  45. package/dist/{index-D_FgwLH9.d.ts → index-nfyQM33h.d.cts} +172 -99
  46. package/dist/index.cjs +326 -278
  47. package/dist/index.d.cts +295 -41
  48. package/dist/index.d.ts +295 -41
  49. package/dist/index.js +4 -4
  50. package/dist/repositories/realm/index.cjs +19 -19
  51. package/dist/repositories/realm/index.d.cts +2 -2
  52. package/dist/repositories/realm/index.d.ts +2 -2
  53. package/dist/repositories/realm/index.js +5 -5
  54. package/dist/repositories/sqlite/index.cjs +18 -18
  55. package/dist/repositories/sqlite/index.d.cts +2 -2
  56. package/dist/repositories/sqlite/index.d.ts +2 -2
  57. package/dist/repositories/sqlite/index.js +5 -5
  58. package/dist/{taskRunner-BOVcIH1N.d.cts → taskRunner-D2vnpPBT.d.cts} +1 -1
  59. package/dist/{taskRunner-DNyD1kXG.d.ts → taskRunner-DuwK9t8O.d.ts} +1 -1
  60. package/dist/wallet/expo/background.cjs +14 -14
  61. package/dist/wallet/expo/background.d.cts +3 -3
  62. package/dist/wallet/expo/background.d.ts +3 -3
  63. package/dist/wallet/expo/background.js +6 -6
  64. package/dist/wallet/expo/index.cjs +18 -14
  65. package/dist/wallet/expo/index.cjs.map +1 -1
  66. package/dist/wallet/expo/index.d.cts +7 -5
  67. package/dist/wallet/expo/index.d.ts +7 -5
  68. package/dist/wallet/expo/index.js +10 -6
  69. package/dist/wallet/expo/index.js.map +1 -1
  70. package/dist/worker/expo/index.cjs +9 -9
  71. package/dist/worker/expo/index.d.cts +4 -4
  72. package/dist/worker/expo/index.d.ts +4 -4
  73. package/dist/worker/expo/index.js +5 -5
  74. package/package.json +3 -3
  75. package/dist/chunk-5E6IZNRG.cjs.map +0 -1
  76. package/dist/chunk-CYOI4PEA.js.map +0 -1
  77. package/dist/chunk-DSC3G6QZ.js.map +0 -1
  78. package/dist/chunk-J33ZEM3A.js.map +0 -1
  79. package/dist/chunk-PXPPZF5K.cjs.map +0 -1
  80. package/dist/chunk-ZIRFXBKL.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
- import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-2JZGPXU4.js';
2
- import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, toXOnly, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, parseHDDescriptor, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler, identityDescriptor } from './chunk-CYOI4PEA.js';
3
- import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, resolveEmulatorPubkey, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, isEventSourceError, isEventSourceUnavailableError, isRetryableProviderError, getAllNormalizedVtxos, DEFAULT_PAGE_SIZE, RestArkProvider, toVirtualStatus, ArkError, TapTreeCoder, resolveTimeHeight, canSpendOffchain, parseLegacyExpiry, isExpired, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError, networks as networks$1, isPastExpiry, fetchVtxoCreatedAtByTxid } from './chunk-J33ZEM3A.js';
1
+ import { AssetId, AssetGroup, AssetInput, AssetOutput, Metadata, Packet, BufferReader, BufferWriter, AssetRef, RestIndexerProvider } from './chunk-33Q2RFET.js';
2
+ import { witnessRefToBytes, validateProgram, toASM, stringifyArtifact, serializeArkadeContractParams, resolveAsm, parseArtifact, getOpcodeValue, getOpcodeName, fromASM, deserializeArkadeContractParams, computeArkadeScriptPublicKey, bytesToASM, asmToBytes, arkadeWitnessHash, arkadeScriptHash, SUPPORTED_PROGRAM_VERSION, OPCODE_VALUES, OPCODE_NAMES, bignum_exports, ArkadeScript, ArkadeProgramScript, ARKADE_OPS, ARKADE_OPCODE_VALUES, ARKADE_OPCODE_NAMES, ARKADE_OPCODES, ARKADE_OP, toXOnly, inputName, contractHandlers, isMainnetDescriptor, descriptorIsOurs, deriveDescriptorLeafPubKey, deriveDescriptorLeafCompressedPubKey, parseHDDescriptor, identityDescriptor, DefaultVtxo, speculativeReceiveMetadata, DelegateVtxo, WALLET_RECEIVE_SOURCE, BoardingContractHandler } from './chunk-45FKMOJH.js';
3
+ import { VtxoScript, Transaction, isRecoverable, CSVMultisigTapscript, DEFAULT_NETWORK, resolveEmulatorPubkey, hasTerminalSpend, getNormalizedVtxos, setArkPsbtField, PrevArkTxField, ConditionWitness, craftToSpendTx, ArkAddress, isVirtualCoin, getArkPsbtFields, CosignerPublicKey, ServerResponseMismatchError, OP_RETURN_EMPTY_PKSCRIPT, canSweepOnchain, sequenceToTimelock, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, VtxoTaprootTree, timelockToSequence, assertAllowedSighashTypes, baseFetch, Intent, rateGate, DEFAULT_NETWORK_NAME, formatSighash, normalizeVtxo, getSequence, toBatchExpiry, maybeArkError, isArkError, ArkErrorName, toOffchainInputFeeParams, canRecoverOnchain, getNetwork, MultisigTapscript, isEventSourceError, isEventSourceUnavailableError, isRetryableProviderError, getAllNormalizedVtxos, DEFAULT_PAGE_SIZE, RestArkProvider, toVirtualStatus, ArkError, TapTreeCoder, resolveTimeHeight, canSpendOffchain, parseLegacyExpiry, isExpired, PrevoutTxField, DEFAULT_ARKADE_SERVER_URL, ProviderUnavailableError, networks as networks$1, isPastExpiry, fetchVtxoCreatedAtByTxid } from './chunk-4CMXV5SF.js';
4
4
  import { __export } from './chunk-NSBPE2FW.js';
5
5
  import { sha256 as sha256$1, equalBytes, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr } from '@scure/btc-signer/utils.js';
6
6
  import { SigHash, Script, OP, OutScript, Address, RawWitness, p2tr, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
@@ -272,7 +272,7 @@ function getPrevOutput(finalKey, graph, sharedOutputAmount, tx) {
272
272
  script: pkScript
273
273
  };
274
274
  }
275
- var ALL_SIGHASH = Object.values(SigHash).filter((x) => typeof x === "number");
275
+ var ALLOWED_SIGHASH = [SigHash.DEFAULT, SigHash.ALL, SigHash.ALL_ANYONECANPAY];
276
276
  var SingleKey = class _SingleKey {
277
277
  key;
278
278
  constructor(key) {
@@ -301,8 +301,9 @@ var SingleKey = class _SingleKey {
301
301
  async sign(tx, inputIndexes) {
302
302
  const txCpy = tx.clone();
303
303
  if (!inputIndexes) {
304
+ assertAllowedSighashTypes(txCpy, ALLOWED_SIGHASH);
304
305
  try {
305
- if (!txCpy.sign(this.key, ALL_SIGHASH)) {
306
+ if (!txCpy.sign(this.key, ALLOWED_SIGHASH)) {
306
307
  throw new Error("Failed to sign transaction");
307
308
  }
308
309
  } catch (e) {
@@ -313,7 +314,7 @@ var SingleKey = class _SingleKey {
313
314
  return txCpy;
314
315
  }
315
316
  for (const inputIndex of inputIndexes) {
316
- if (!txCpy.signIdx(this.key, inputIndex, ALL_SIGHASH)) {
317
+ if (!txCpy.signIdx(this.key, inputIndex, ALLOWED_SIGHASH)) {
317
318
  throw new Error(`Failed to sign input #${inputIndex}`);
318
319
  }
319
320
  }
@@ -378,7 +379,7 @@ var ReadonlySingleKey = class _ReadonlySingleKey {
378
379
  return Promise.resolve(this.publicKey);
379
380
  }
380
381
  };
381
- var ALL_SIGHASH2 = Object.values(SigHash).filter((x) => typeof x === "number");
382
+ var ALLOWED_SIGHASH2 = [SigHash.DEFAULT, SigHash.ALL, SigHash.ALL_ANYONECANPAY];
382
383
  var seedBytes = /* @__PURE__ */ new WeakMap();
383
384
  var mnemonicMeta = /* @__PURE__ */ new WeakMap();
384
385
  var SeedIdentity = class _SeedIdentity {
@@ -581,8 +582,9 @@ var SeedIdentity = class _SeedIdentity {
581
582
  signTxWithKey(tx, key, inputIndexes) {
582
583
  const txCpy = tx.clone();
583
584
  if (!inputIndexes) {
585
+ assertAllowedSighashTypes(txCpy, ALLOWED_SIGHASH2);
584
586
  try {
585
- if (!txCpy.sign(key, ALL_SIGHASH2)) {
587
+ if (!txCpy.sign(key, ALLOWED_SIGHASH2)) {
586
588
  throw new Error("Failed to sign transaction");
587
589
  }
588
590
  } catch (e) {
@@ -592,7 +594,7 @@ var SeedIdentity = class _SeedIdentity {
592
594
  }
593
595
  } else {
594
596
  for (const idx of inputIndexes) {
595
- if (!txCpy.signIdx(key, idx, ALL_SIGHASH2)) {
597
+ if (!txCpy.signIdx(key, idx, ALLOWED_SIGHASH2)) {
596
598
  throw new Error(`Failed to sign input #${idx}`);
597
599
  }
598
600
  }
@@ -917,6 +919,12 @@ var ForeignDescriptorError = class extends Error {
917
919
  descriptor;
918
920
  name = "ForeignDescriptorError";
919
921
  };
922
+ var WalletCannotAllocateAddressError = class extends Error {
923
+ name = "WalletCannotAllocateAddressError";
924
+ constructor(reason, options) {
925
+ super(`cannot allocate a fresh address: ${reason}`, options);
926
+ }
927
+ };
920
928
  async function resolveDescriptorSigner(descriptor, identity, provider) {
921
929
  const owner = provider?.isOurs(descriptor) ? provider : isHDCapableIdentity(identity) && identity.isOurs(descriptor) ? identity : void 0;
922
930
  if (owner && parseHDDescriptor(descriptor)) {
@@ -941,6 +949,10 @@ function isHDAllocationCapable(value) {
941
949
  const v = value;
942
950
  return typeof v.getNextSigningDescriptor === "function" && typeof v.advanceSigningDescriptorWatermark === "function";
943
951
  }
952
+ function isAddressAllocationCapable(value) {
953
+ if (typeof value !== "object" || value === null) return false;
954
+ return typeof value.getNewAddresses === "function";
955
+ }
944
956
 
945
957
  // ../../node_modules/@noble/hashes/utils.js
946
958
  function isBytes(a) {
@@ -1302,15 +1314,26 @@ async function provisionDescriptor(wallet) {
1302
1314
  return allocated ?? await identityDescriptor(wallet.identity);
1303
1315
  }
1304
1316
  async function provisionRefundKey(wallet) {
1305
- const descriptor = await provisionDescriptor(wallet);
1317
+ const descriptor = await identityDescriptor(wallet.identity);
1306
1318
  const signer = await contractSigner(wallet, descriptor);
1307
- return { descriptor, pubkey: await signer.xOnlyPublicKey() };
1319
+ const pubkey = await signer.xOnlyPublicKey();
1320
+ const identityPubkey = await wallet.identity.xOnlyPublicKey();
1321
+ if (!equalBytes(pubkey, identityPubkey)) {
1322
+ throw new Error(
1323
+ "provisionRefundKey: descriptor pubkey does not match wallet identity \u2014 the refund key and the refund address would be on different keys"
1324
+ );
1325
+ }
1326
+ const address = await wallet.getAddress();
1327
+ const { pkScript } = ArkAddress.decode(address);
1328
+ return { descriptor, pubkey, pkScript, address };
1308
1329
  }
1309
1330
  async function provisionClaimSecret(wallet, opts = {}) {
1310
1331
  if (opts.preimage && opts.preimage.length !== 32) {
1311
1332
  throw new Error(`preimage must be 32 bytes, got ${opts.preimage.length}`);
1312
1333
  }
1313
- const { descriptor, pubkey } = await provisionRefundKey(wallet);
1334
+ const descriptor = await provisionDescriptor(wallet);
1335
+ const signer = await contractSigner(wallet, descriptor);
1336
+ const pubkey = await signer.xOnlyPublicKey();
1314
1337
  const claim = async () => {
1315
1338
  if (opts.preimage) return { preimage: opts.preimage, mustPersistPreimage: true };
1316
1339
  if (isPerArtifactDescriptor(descriptor)) {
@@ -1610,8 +1633,6 @@ var RestDelegatorProvider = RestDelegateProvider;
1610
1633
  function isDelegateInfo(data) {
1611
1634
  return !!data && typeof data === "object" && "pubkey" in data && "fee" in data && typeof data.pubkey === "string" && typeof data.fee === "string" && data.pubkey !== "" && data.fee !== "" && (typeof data.delegateAddress === "string" && data.delegateAddress !== "" || typeof data.delegatorAddress === "string" && data.delegatorAddress !== "");
1612
1635
  }
1613
-
1614
- // src/providers/onchain.ts
1615
1636
  var ESPLORA_URL = {
1616
1637
  bitcoin: "https://mempool.arkade.sh/api",
1617
1638
  testnet: "https://mempool.space/testnet/api",
@@ -1672,6 +1693,14 @@ var EsploraProvider = class {
1672
1693
  }
1673
1694
  return response.json();
1674
1695
  }
1696
+ async getRawTransaction(txid) {
1697
+ const response = await baseFetch(`${this.baseUrl}/tx/${txid}/hex`);
1698
+ if (!response.ok) {
1699
+ const error = await response.text();
1700
+ throw new Error(`Failed to get raw transaction ${txid}: ${error}`);
1701
+ }
1702
+ return hex.decode((await response.text()).trim());
1703
+ }
1675
1704
  async getTxStatus(txid) {
1676
1705
  const txresponse = await baseFetch(`${this.baseUrl}/tx/${txid}`);
1677
1706
  if (!txresponse.ok) {
@@ -1795,7 +1824,9 @@ var EsploraProvider = class {
1795
1824
  const error = await response.text();
1796
1825
  throw new Error(`Failed to broadcast package: ${error}`);
1797
1826
  }
1798
- return response.json();
1827
+ const result = await response.json();
1828
+ assertPackageAccepted(result);
1829
+ return result;
1799
1830
  }
1800
1831
  async broadcastTx(tx) {
1801
1832
  const response = await baseFetch(`${this.baseUrl}/tx`, {
@@ -1812,6 +1843,24 @@ var EsploraProvider = class {
1812
1843
  return response.text();
1813
1844
  }
1814
1845
  };
1846
+ function assertPackageAccepted(result) {
1847
+ if (!result || typeof result !== "object") return;
1848
+ const r = result;
1849
+ const msg = typeof r.package_msg === "string" ? r.package_msg : void 0;
1850
+ if (msg === void 0 || msg === "success") return;
1851
+ const reasons = [];
1852
+ const results = r["tx-results"];
1853
+ if (results && typeof results === "object") {
1854
+ for (const entry of Object.values(results)) {
1855
+ if (!entry || typeof entry !== "object" || entry.error === void 0) continue;
1856
+ const txid = typeof entry.txid === "string" ? entry.txid : "unknown tx";
1857
+ reasons.push(`${txid}: ${String(entry.error)}`);
1858
+ }
1859
+ }
1860
+ throw new Error(
1861
+ reasons.length > 0 ? `Package rejected (${msg}) \u2014 ${reasons.join("; ")}` : `Package rejected (${msg})`
1862
+ );
1863
+ }
1815
1864
  function isValidBlocksTip(tip) {
1816
1865
  return Array.isArray(tip) && tip.every((t) => {
1817
1866
  return t && typeof t === "object" && typeof t.id === "string" && t.id.length > 0 && typeof t.height === "number" && t.height >= 0 && typeof t.mediantime === "number" && t.mediantime > 0;
@@ -3038,9 +3087,6 @@ function hasBoardingTxExpired(coin, boardingTimelock, chainTipHeight) {
3038
3087
  const blockTime = BigInt(Math.floor(coin.status.block_time));
3039
3088
  return blockTime + boardingTimelock.value <= now;
3040
3089
  }
3041
- function formatSighash(type) {
3042
- return `0x${type.toString(16).padStart(2, "0")}`;
3043
- }
3044
3090
  function verifyTapscriptSignatures(tx, inputIndex, requiredSigners, excludePubkeys = [], allowedSighashTypes = [SigHash.DEFAULT], expectedLeafHash) {
3045
3091
  const input = tx.getInput(inputIndex);
3046
3092
  const prevoutScripts = [];
@@ -3258,6 +3304,15 @@ async function submitOffchainTx(provider, offchainTx, signer, hooks, options) {
3258
3304
  assertSubmittedArkTxid(response, signedArkTx, "submitTx");
3259
3305
  const { arkTxid, signedCheckpointTxs } = response;
3260
3306
  const matched = matchServerCheckpoints(signedCheckpointTxs, offchainTx.checkpoints, "submitTx");
3307
+ matched.forEach(({ server }, index) => {
3308
+ try {
3309
+ assertAllowedSighashTypes(server);
3310
+ } catch (error) {
3311
+ throw new ServerResponseMismatchError(
3312
+ `submitTx checkpoint ${index}: ${error instanceof Error ? error.message : String(error)}`
3313
+ );
3314
+ }
3315
+ });
3261
3316
  const verify = options?.verifyServerSignatures;
3262
3317
  if (verify) {
3263
3318
  if (response.finalArkTx === void 0) {
@@ -3709,7 +3764,9 @@ function selectPendingRecoveryOutpoints(contractsWithVtxos, signerSet, nowSecond
3709
3764
  continue;
3710
3765
  }
3711
3766
  for (const v of vtxos) {
3712
- if (!hasTerminalSpend(v) && !v.isSwept) out.add(`${v.txid}:${v.vout}`);
3767
+ if (!hasTerminalSpend(v) && !v.isSwept && !v.isUnrolled) {
3768
+ out.add(`${v.txid}:${v.vout}`);
3769
+ }
3713
3770
  }
3714
3771
  }
3715
3772
  return out;
@@ -3857,6 +3914,7 @@ function getRecoverableWithSubdust(vtxos, dustAmount, now) {
3857
3914
  };
3858
3915
  }
3859
3916
  function isVtxoExpiringSoon(vtxo, thresholdMs) {
3917
+ if (vtxo.isUnrolled) return false;
3860
3918
  const realThresholdMs = thresholdMs <= 100 ? DEFAULT_THRESHOLD_MS : thresholdMs;
3861
3919
  const expiresAt = normalizeVtxo(vtxo).expiresAt;
3862
3920
  if (expiresAt === void 0) return false;
@@ -3867,7 +3925,9 @@ function isVtxoExpiringSoon(vtxo, thresholdMs) {
3867
3925
  }
3868
3926
  function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount, now) {
3869
3927
  return vtxos.filter(
3870
- (vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) || canRecoverOnchain(vtxo, now) || isSubdust(vtxo, dustAmount)
3928
+ // The leading exclusion covers the `isSubdust` arm too, which is a value
3929
+ // property and would otherwise re-admit an exited coin on its own.
3930
+ (vtxo) => !vtxo.isUnrolled && (isVtxoExpiringSoon(vtxo, thresholdMs) || canRecoverOnchain(vtxo, now) || isSubdust(vtxo, dustAmount))
3871
3931
  );
3872
3932
  }
3873
3933
  function isMigrationCapable(wallet) {
@@ -4518,6 +4578,7 @@ var VtxoManager = class _VtxoManager {
4518
4578
  async getDeprecatedSignerStatus() {
4519
4579
  const wallet = this.requireMigrationCapableWallet();
4520
4580
  const info = await wallet.arkProvider.getInfo();
4581
+ await wallet.settleServerInfoChanges?.();
4521
4582
  const { reports: vtxoReports } = await this.classifyDeprecatedSignerContracts(info);
4522
4583
  const { reports: boardingReports } = await this.classifyDeprecatedSignerBoarding(info);
4523
4584
  return mergeSignerReports(vtxoReports, boardingReports);
@@ -4531,26 +4592,33 @@ var VtxoManager = class _VtxoManager {
4531
4592
  */
4532
4593
  async migrateCore(options) {
4533
4594
  const wallet = this.requireMigrationCapableWallet();
4595
+ const signerBeforeRefresh = hex.encode(wallet.arkServerPublicKey);
4534
4596
  const info = await wallet.arkProvider.getInfo();
4597
+ await wallet.settleServerInfoChanges?.();
4598
+ const rotatedOnRefresh = signerBeforeRefresh !== hex.encode(wallet.arkServerPublicKey);
4535
4599
  wallet.refreshDeprecatedSigners(info);
4536
4600
  const signerSet = signerSetFromInfo(info);
4537
4601
  const nowSeconds = Math.floor(Date.now() / 1e3);
4538
4602
  const walletSignerHex = hex.encode(wallet.arkServerPublicKey);
4539
4603
  const walletClass = classifyAgainstSignerSet(walletSignerHex, signerSet, nowSeconds);
4540
4604
  if (signerSet.deprecated.size === 0 && walletClass.status === "CURRENT") {
4541
- return { rotated: false, expired: [], signers: [] };
4605
+ return { rotated: rotatedOnRefresh, expired: [], signers: [] };
4542
4606
  }
4543
4607
  if (walletClass.status === "UNKNOWN_SIGNER") {
4544
4608
  const { reports: vtxoReports2 } = await this.classifyDeprecatedSignerContracts(info);
4545
4609
  const { reports: boardingReports2 } = await this.classifyDeprecatedSignerBoarding(info);
4546
4610
  return {
4547
- rotated: false,
4611
+ // Normally false here — a drained rotation targets the active
4612
+ // signer, which classifies CURRENT, not UNKNOWN. It is true only
4613
+ // when a second rotation landed inside the drain, leaving us on a
4614
+ // signer newer than `info`: still a rotation this pass applied.
4615
+ rotated: rotatedOnRefresh,
4548
4616
  expired: [],
4549
4617
  signers: mergeSignerReports(vtxoReports2, boardingReports2),
4550
4618
  skipped: "unknown-wallet-signer"
4551
4619
  };
4552
4620
  }
4553
- const rotated = await this.ensureReceiveOnActiveSigner(info);
4621
+ const rotated = await this.ensureReceiveOnActiveSigner(info) || rotatedOnRefresh;
4554
4622
  const {
4555
4623
  reports: vtxoReports,
4556
4624
  migratable: vtxoMigratable,
@@ -4719,8 +4787,9 @@ var VtxoManager = class _VtxoManager {
4719
4787
  if (!serverPubKey) continue;
4720
4788
  const cls = classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds);
4721
4789
  if (cls.status === "CURRENT") continue;
4722
- const recoverable = vtxos.filter((v) => v.isSwept && !hasTerminalSpend(v));
4723
- const spendable = vtxos.filter((v) => !hasTerminalSpend(v) && !v.isSwept);
4790
+ const live = vtxos.filter((v) => !v.isUnrolled);
4791
+ const recoverable = live.filter((v) => v.isSwept && !hasTerminalSpend(v));
4792
+ const spendable = live.filter((v) => !hasTerminalSpend(v) && !v.isSwept);
4724
4793
  const value = spendable.reduce((sum, v) => sum + v.value, 0);
4725
4794
  let recoverableCount = 0;
4726
4795
  let recoverableValue = 0;
@@ -6390,6 +6459,13 @@ var TxTree = class {
6390
6459
  if (nbOfInputs !== 1) {
6391
6460
  throw new Error(`unexpected number of inputs: ${nbOfInputs}, expected 1`);
6392
6461
  }
6462
+ if (this.root.lockTime !== 0) {
6463
+ throw new Error(`unexpected locktime: ${this.root.lockTime}, expected 0`);
6464
+ }
6465
+ const sequence = this.root.getInput(0).sequence;
6466
+ if (sequence !== void 0 && sequence !== DEFAULT_SEQUENCE) {
6467
+ throw new Error(`unexpected sequence: ${sequence}, expected ${DEFAULT_SEQUENCE}`);
6468
+ }
6393
6469
  if (this.children.size > nbOfOutputs - 1) {
6394
6470
  throw new Error(
6395
6471
  `unexpected number of children: ${this.children.size}, expected maximum ${nbOfOutputs - 1}`
@@ -8985,7 +9061,7 @@ var NON_TERMINAL_INTENT_STATES = ALL_INTENT_STATES.filter(
8985
9061
  (s) => !isTerminalIntentState(s)
8986
9062
  );
8987
9063
  function isConsumed(vtxo) {
8988
- return vtxo !== void 0 && (vtxo.isSpent === true || vtxo.settledBy !== void 0);
9064
+ return vtxo !== void 0 && hasTerminalSpend(vtxo);
8989
9065
  }
8990
9066
  async function classifyIntent(intent, vtxosByOutpoint, now) {
8991
9067
  if (isTerminalIntentState(intent.state)) return void 0;
@@ -8995,6 +9071,11 @@ async function classifyIntent(intent, vtxosByOutpoint, now) {
8995
9071
  const commitmentTransactionId = intent.commitmentTransactionId ?? consumed.map((v) => v?.settledBy).find((s) => !!s);
8996
9072
  return terminal(intent, "batch_succeeded", now, { commitmentTransactionId });
8997
9073
  }
9074
+ if (consumed.some((vtxo) => vtxo?.isUnrolled)) {
9075
+ return terminal(intent, "cancelled", now, {
9076
+ cancellationReason: "reconciliation: an input was unilaterally exited"
9077
+ });
9078
+ }
8998
9079
  if (intent.state === "waiting_to_submit") {
8999
9080
  return terminal(intent, "cancelled", now, {
9000
9081
  cancellationReason: "reconciliation: intent was never submitted before restart"
@@ -11604,7 +11685,9 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
11604
11685
  const { tapscript: newTapscript, params } = buildReceiveContract(
11605
11686
  wallet.offchainTapscript,
11606
11687
  descriptor,
11607
- wallet.network.hrp);
11688
+ wallet.network.hrp,
11689
+ true
11690
+ );
11608
11691
  const newScript = params.script;
11609
11692
  const manager = await wallet.getContractManager();
11610
11693
  await manager.createContract(params);
@@ -11646,7 +11729,7 @@ function buildReceiveContract(current, descriptor, hrp, tagSource) {
11646
11729
  // index-0 key. Without it, spends produce unsigned PSBTs that the
11647
11730
  // server rejects with `INVALID_PSBT_INPUT (5): missing tapscript spend sig`.
11648
11731
  metadata: {
11649
- ...{ source: WALLET_RECEIVE_SOURCE },
11732
+ ...tagSource && { source: WALLET_RECEIVE_SOURCE },
11650
11733
  signingDescriptor: descriptor
11651
11734
  }
11652
11735
  };
@@ -11730,6 +11813,7 @@ function computeOffchainBalance(vtxos, caps) {
11730
11813
  let intentLocked = 0;
11731
11814
  let recoverable = 0;
11732
11815
  let pendingRecovery = 0;
11816
+ let unrolled = 0;
11733
11817
  const owned = /* @__PURE__ */ new Map();
11734
11818
  const spendable = /* @__PURE__ */ new Map();
11735
11819
  const addAssets = (into, vtxo) => {
@@ -11740,6 +11824,10 @@ function computeOffchainBalance(vtxos, caps) {
11740
11824
  for (const vtxo of vtxos) {
11741
11825
  if (hasTerminalSpend(vtxo)) continue;
11742
11826
  addAssets(owned, vtxo);
11827
+ if (vtxo.isUnrolled) {
11828
+ unrolled += vtxo.value;
11829
+ continue;
11830
+ }
11743
11831
  if (isPendingRecovery(vtxo)) {
11744
11832
  pendingRecovery += vtxo.value;
11745
11833
  continue;
@@ -11772,7 +11860,8 @@ function computeOffchainBalance(vtxos, caps) {
11772
11860
  intentLocked,
11773
11861
  recoverable,
11774
11862
  pendingRecovery,
11775
- total: settled + preconfirmed + recoverable + pendingRecovery,
11863
+ unrolled,
11864
+ total: settled + preconfirmed + recoverable + pendingRecovery + unrolled,
11776
11865
  assets: toAssets(owned),
11777
11866
  availableAssets: toAssets(spendable)
11778
11867
  };
@@ -11995,16 +12084,20 @@ function filterSnapshotVtxos(snapshot, filter, pendingSpendOutpoints) {
11995
12084
  if (pendingSpendOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
11996
12085
  return false;
11997
12086
  }
11998
- if (!hasTerminalSpend(vtxo)) {
11999
- if (!f.withRecoverable && canRecoverOnchain(vtxo, now)) {
12000
- return false;
12001
- }
12002
- return true;
12087
+ if (vtxo.isUnrolled) {
12088
+ return !!f.withUnrolled;
12089
+ }
12090
+ if (hasTerminalSpend(vtxo)) {
12091
+ return false;
12092
+ }
12093
+ if (!f.withRecoverable && canRecoverOnchain(vtxo, now)) {
12094
+ return false;
12003
12095
  }
12004
- return !!(f.withUnrolled && vtxo.isUnrolled);
12096
+ return true;
12005
12097
  });
12006
12098
  }
12007
12099
  var PENDING_RECOVERY_REASON = "is past its operator signer's rotation cutoff \u2014 the operator will not co-sign it until it recovers";
12100
+ var UNROLLED_REASON = "was unilaterally exited and lives onchain \u2014 `Unroll.completeUnroll` is the only spend that reaches it";
12008
12101
  function omittedOutpoints(before, after) {
12009
12102
  if (before.length === after.length) return /* @__PURE__ */ new Set();
12010
12103
  const kept = new Set(after.map((vtxo) => `${vtxo.txid}:${vtxo.vout}`));
@@ -12504,7 +12597,11 @@ var ReadonlyWallet = class _ReadonlyWallet {
12504
12597
  this.getBoardingUtxos(),
12505
12598
  this.contractSnapshot()
12506
12599
  ]);
12507
- const vtxos = filterSnapshotVtxos(snapshot, void 0, this._pendingSpendOutpoints);
12600
+ const vtxos = filterSnapshotVtxos(
12601
+ snapshot,
12602
+ { withRecoverable: true, withUnrolled: true },
12603
+ this._pendingSpendOutpoints
12604
+ );
12508
12605
  let confirmed = 0;
12509
12606
  let unconfirmed = 0;
12510
12607
  for (const utxo of boardingUtxos) {
@@ -12532,6 +12629,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
12532
12629
  intentLocked: offchain.intentLocked,
12533
12630
  recoverable: offchain.recoverable,
12534
12631
  pendingRecovery: offchain.pendingRecovery,
12632
+ unrolled: offchain.unrolled,
12535
12633
  total: totalBoarding + offchain.total,
12536
12634
  assets: offchain.assets,
12537
12635
  availableAssets: offchain.availableAssets
@@ -12572,6 +12670,13 @@ var ReadonlyWallet = class _ReadonlyWallet {
12572
12670
  "is locked by an in-flight settlement intent"
12573
12671
  )
12574
12672
  ]);
12673
+ if (!filter?.withUnrolled) {
12674
+ logExcludedVtxos(
12675
+ "getSpendableVtxos",
12676
+ snapshot.flatMap((contract) => contract.vtxos).filter((vtxo) => vtxo.isUnrolled && !hasTerminalSpend(vtxo)),
12677
+ [() => UNROLLED_REASON]
12678
+ );
12679
+ }
12575
12680
  return unlocked;
12576
12681
  }
12577
12682
  /**
@@ -13391,6 +13496,27 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13391
13496
  console.warn("server-signer rotation on info change failed", e);
13392
13497
  }
13393
13498
  }
13499
+ /**
13500
+ * Await an `onServerInfoChanged` handler still applying a rotation.
13501
+ *
13502
+ * The provider emits synchronously but {@link handleServerInfoChanged} runs
13503
+ * off the emit, so between the two a caller can observe a half-applied
13504
+ * rotation: {@link rotateServerSigner} persists the active signer's contract
13505
+ * rows *before* committing the tapscripts, so `arkServerPublicKey` still
13506
+ * reads the old key while the new key's rows are already in the repository.
13507
+ * A path that refreshes server info and then reads signer-derived state must
13508
+ * drain the chain first, or it both reads that torn state and races the
13509
+ * handler for the rotation itself.
13510
+ *
13511
+ * Resolves once the chain is idle; a handler that threw already logged, and
13512
+ * its rejection is swallowed here.
13513
+ *
13514
+ * @internal Invoked by {@link dispose} and the {@link VtxoManager} migration
13515
+ * pass; not part of the stable public API.
13516
+ */
13517
+ async settleServerInfoChanges() {
13518
+ await this._serverInfoInFlight?.catch(() => void 0);
13519
+ }
13394
13520
  _receiveRotatorInstalled = false;
13395
13521
  /**
13396
13522
  * Descriptor-aware signer used by {@link _signerRouter} to sign
@@ -13525,43 +13651,250 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13525
13651
  * provider and keeps a single index-0 boarding address for its lifetime,
13526
13652
  * so this returns the existing {@link getBoardingAddress} unchanged
13527
13653
  * (no rotation, no index burned).
13654
+ *
13655
+ * **Behaviour change.** A wallet configured with a custom
13656
+ * {@link DescriptorProvider} (`walletMode: <provider>`) now rotates here.
13657
+ * It previously did not: allocation went through the contract manager,
13658
+ * which only wires an `allocate` hook for {@link HDDescriptorProvider}, so
13659
+ * such a wallet read as "declined to allocate" and silently kept its
13660
+ * index-0 boarding address forever. Those wallets now burn an index per
13661
+ * call — including via {@link maybeRotateBoardingAfterBoard}, which fires
13662
+ * on every settle that consumes a boarding UTXO. Funds at retired
13663
+ * addresses stay reachable: each rotation persists its `boarding` contract
13664
+ * before swapping, and {@link getBoardingUtxos} fans out over the full
13665
+ * historical set.
13666
+ *
13667
+ * @deprecated Use {@link getNewAddresses} — it mints any combination of
13668
+ * address types at one shared index, and reports the contract row behind
13669
+ * each. Note the difference in display behaviour: this method *swaps* the
13670
+ * advertised boarding address, where `getNewAddresses` mints side
13671
+ * addresses and leaves {@link getBoardingAddress} alone. To keep this
13672
+ * method's behaviour, keep calling this method.
13528
13673
  */
13529
13674
  async getNewBoardingAddress() {
13530
- const provider = this._descriptorProvider;
13531
- if (!provider) {
13532
- return this.getBoardingAddress();
13675
+ const [minted] = await this.allocateAddresses(["boarding"], false, true);
13676
+ if (!minted?.allocated) return this.getBoardingAddress();
13677
+ if (minted.type === "boarding") this.setBoardingTapscriptForRotation(minted.tapscript);
13678
+ return minted.address;
13679
+ }
13680
+ /**
13681
+ * Allocate a *fresh* address of each requested type, all derived from one
13682
+ * newly allocated HD index.
13683
+ *
13684
+ * This is the explicit allocator the receive path otherwise lacks:
13685
+ * {@link getAddress} and {@link getBoardingAddress} are stable reads of the
13686
+ * wallet's display addresses, and the display receive address only advances
13687
+ * when a payment arrives ({@link WalletReceiveRotator} rotates on
13688
+ * `vtxo_received`). Issuing a second address before the first is paid — one
13689
+ * invoice for Alice, another for Bob — has to go through here.
13690
+ *
13691
+ * Each call:
13692
+ *
13693
+ * - allocates **one** index from the shared HD stream, however many types
13694
+ * were asked for, so a `default` + `boarding` pair are siblings rather
13695
+ * than two burnt indices;
13696
+ * - builds each requested script at that index, preserving every other
13697
+ * option of the wallet's current script for that flavour (including a
13698
+ * delegate wallet's `delegate` shape);
13699
+ * - persists each as an `active` contract carrying its `signingDescriptor`,
13700
+ * so the ContractWatcher monitors it, the balance counts it, and
13701
+ * {@link signerForDescriptor} can recover the key;
13702
+ * - slides the look-ahead band, since the watermark moved past indices an
13703
+ * external issuer may still be handing out.
13704
+ *
13705
+ * The minted rows are deliberately left **untagged**: the boot lookups
13706
+ * adopt the newest {@link WALLET_RECEIVE_SOURCE}-tagged row as the display
13707
+ * address, and a side address issued to one counterparty must not become
13708
+ * the address the wallet advertises to everyone else. {@link getAddress}
13709
+ * and {@link getBoardingAddress} are unchanged by this call.
13710
+ *
13711
+ * A wallet with no HD stream (`walletMode: 'static'` / `'auto'`) has one
13712
+ * address per flavour for its lifetime. Without `forceNew` it returns those
13713
+ * — the real persisted rows, no index burned; with `forceNew` it throws
13714
+ * {@link WalletCannotAllocateAddressError} rather than hand back an address
13715
+ * that is not in fact fresh.
13716
+ *
13717
+ * @example
13718
+ * ```typescript
13719
+ * const [invoice] = await wallet.getNewAddresses({ forceNew: true });
13720
+ * // hand `invoice.address` to Alice, keep the descriptor with the invoice
13721
+ * const signer = await wallet.signerForDescriptor(invoice.signingDescriptor);
13722
+ * ```
13723
+ */
13724
+ async getNewAddresses(opts) {
13725
+ const minted = await this.allocateAddresses(
13726
+ opts?.types ?? ["default"],
13727
+ opts?.forceNew ?? false,
13728
+ false
13729
+ );
13730
+ return minted.map(({ address, signingDescriptor, contract }) => ({
13731
+ address,
13732
+ signingDescriptor,
13733
+ contract
13734
+ }));
13735
+ }
13736
+ /**
13737
+ * Shared allocation core behind {@link getNewAddresses} and the deprecated
13738
+ * {@link getNewBoardingAddress}, so the two cannot drift on what an
13739
+ * allocation costs or what gets persisted.
13740
+ *
13741
+ * @param tagSource - Tag the persisted rows {@link WALLET_RECEIVE_SOURCE},
13742
+ * making the next boot adopt them as the display address. Only the
13743
+ * deprecated boarding allocator passes `true`; `getNewAddresses` mints side
13744
+ * addresses and must not hijack what the wallet advertises.
13745
+ */
13746
+ async allocateAddresses(types, forceNew, tagSource) {
13747
+ if (types.length === 0) {
13748
+ throw new Error("getNewAddresses: `types` must name at least one address type");
13533
13749
  }
13534
13750
  const manager = await this.getContractManager();
13535
- const descriptor = await manager.getNextSigningDescriptor();
13536
- if (!descriptor) return this.getBoardingAddress();
13537
- const pubKey = deriveDescriptorLeafPubKey(descriptor);
13538
- const newBoarding = new DefaultVtxo.Script({
13751
+ const provider = this._descriptorProvider;
13752
+ const descriptor = !provider ? void 0 : provider instanceof HDDescriptorProvider ? await manager.getNextSigningDescriptor() : await provider.getNextSigningDescriptor();
13753
+ if (!descriptor) {
13754
+ if (forceNew) {
13755
+ throw new WalletCannotAllocateAddressError(
13756
+ provider ? "the descriptor provider declined to allocate an index" : "this wallet has no HD stream (walletMode 'static' / 'auto')"
13757
+ );
13758
+ }
13759
+ return this.currentAddresses(manager, types);
13760
+ }
13761
+ const allocated = [];
13762
+ try {
13763
+ for (const type of types) {
13764
+ allocated.push(await this.mintAddress(manager, type, descriptor, tagSource, true));
13765
+ }
13766
+ } finally {
13767
+ try {
13768
+ await manager.refillLookAhead();
13769
+ } catch (e) {
13770
+ console.warn("look-ahead refill after address allocation failed", e);
13771
+ }
13772
+ }
13773
+ return allocated;
13774
+ }
13775
+ /**
13776
+ * The wallet's existing display addresses, as persisted contract rows —
13777
+ * the honest answer for a wallet with no index to burn.
13778
+ *
13779
+ * Each type is minted from the descriptor that already owns its display
13780
+ * script ({@link displayDescriptor}), not from the identity key, so the
13781
+ * returned `signingDescriptor` is the one that signs for the returned
13782
+ * `address` even on a wallet that has rotated.
13783
+ */
13784
+ async currentAddresses(manager, types) {
13785
+ const out = [];
13786
+ for (const type of types) {
13787
+ const descriptor = await this.displayDescriptor(manager, type);
13788
+ out.push(await this.mintAddress(manager, type, descriptor, false, false));
13789
+ }
13790
+ return out;
13791
+ }
13792
+ /**
13793
+ * The descriptor that owns the wallet's current display script for `type`,
13794
+ * read off the row the wallet persisted when it adopted that script.
13795
+ *
13796
+ * Deriving it from the identity instead is only correct for a wallet that
13797
+ * never rotates. On one that has — an HD wallet whose provider later
13798
+ * declines to allocate — the display script belongs to an HD child, and
13799
+ * pairing its address with the identity descriptor hands the caller a
13800
+ * {@link NewAddress} whose `signingDescriptor` resolves, via
13801
+ * {@link signerForDescriptor}, to a signer holding the wrong key.
13802
+ *
13803
+ * Falls back to the identity's pathless `tr(pubkey)` when no row carries a
13804
+ * descriptor: the static / `auto` case, where the identity key genuinely
13805
+ * owns the display script and this is what `getNextSigningDescriptor`
13806
+ * answers too. (The index-0 baselines registered at construction carry no
13807
+ * `signingDescriptor` metadata, so those wallets take this path.)
13808
+ */
13809
+ async displayDescriptor(manager, type) {
13810
+ const display = type === "boarding" ? this._boardingTapscript : this.offchainTapscript;
13811
+ const [row] = await manager.getContracts({ script: hex.encode(display.pkScript) });
13812
+ const persisted = row?.metadata?.signingDescriptor;
13813
+ if (typeof persisted === "string") return persisted;
13814
+ return identityDescriptor(this.identity);
13815
+ }
13816
+ /**
13817
+ * Build, persist and shape one address of `type` owned by `descriptor` —
13818
+ * the single build-and-register path behind both {@link allocateAddresses}
13819
+ * and {@link currentAddresses}, so a freshly allocated address and a
13820
+ * re-registered display address cannot disagree on what gets written.
13821
+ *
13822
+ * The descriptor is the sole source of the script's owner key in both
13823
+ * branches. Deriving one flavour from the descriptor and the other from a
13824
+ * tapscript field is what lets `address` and `signingDescriptor` drift
13825
+ * apart.
13826
+ */
13827
+ async mintAddress(manager, type, descriptor, tagSource, allocated) {
13828
+ if (type === "boarding") {
13829
+ const built2 = this.buildBoardingContract(
13830
+ deriveDescriptorLeafPubKey(descriptor),
13831
+ descriptor,
13832
+ tagSource
13833
+ );
13834
+ const contract2 = await manager.createContract(built2.params);
13835
+ return {
13836
+ type,
13837
+ // `built.tapscript` is a `DefaultVtxo.Script` here without a
13838
+ // cast — the branch, not a widened local, does the narrowing.
13839
+ tapscript: built2.tapscript,
13840
+ // A boarding row persists the *ark* encoding of its script, so
13841
+ // `contract.address` is unusable for an onchain sender: the
13842
+ // onchain form has to be re-derived.
13843
+ address: built2.tapscript.onchainAddress(this.network),
13844
+ signingDescriptor: descriptor,
13845
+ contract: contract2,
13846
+ allocated
13847
+ };
13848
+ }
13849
+ const built = buildReceiveContract(
13850
+ this.offchainTapscript,
13851
+ descriptor,
13852
+ this.network.hrp,
13853
+ tagSource
13854
+ );
13855
+ const contract = await manager.createContract(built.params);
13856
+ return {
13857
+ type,
13858
+ tapscript: built.tapscript,
13859
+ // Offchain rows already store the address they advertise, and
13860
+ // reading it back keeps a coalesced row (`createContract` is
13861
+ // first-wins on script) honest about what was actually persisted.
13862
+ address: contract.address,
13863
+ signingDescriptor: descriptor,
13864
+ contract,
13865
+ allocated
13866
+ };
13867
+ }
13868
+ /**
13869
+ * Build the boarding contract owned by `pubKey`, keeping every other option
13870
+ * of the wallet's current boarding tapscript (notably the boarding-exit
13871
+ * CSV, which is index-independent). The offchain analogue lives in
13872
+ * {@link buildReceiveContract}.
13873
+ */
13874
+ buildBoardingContract(pubKey, descriptor, tagSource) {
13875
+ const tapscript = new DefaultVtxo.Script({
13539
13876
  ...this._boardingTapscript.options,
13540
13877
  pubKey
13541
13878
  });
13542
- const csvTimelock = newBoarding.options.csvTimelock ?? DefaultVtxo.Script.DEFAULT_TIMELOCK;
13543
- await manager.createContract({
13544
- type: "boarding",
13879
+ const csvTimelock = tapscript.options.csvTimelock ?? DefaultVtxo.Script.DEFAULT_TIMELOCK;
13880
+ return {
13881
+ tapscript,
13545
13882
  params: {
13546
- pubKey: hex.encode(pubKey),
13547
- serverPubKey: hex.encode(newBoarding.options.serverPubKey),
13548
- csvTimelock: timelockToSequence(csvTimelock).toString()
13549
- },
13550
- script: hex.encode(newBoarding.pkScript),
13551
- address: newBoarding.address(this.network.hrp, this.arkServerPublicKey).encode(),
13552
- state: "active",
13553
- metadata: {
13554
- source: WALLET_RECEIVE_SOURCE,
13555
- signingDescriptor: descriptor
13883
+ type: "boarding",
13884
+ params: {
13885
+ pubKey: hex.encode(pubKey),
13886
+ serverPubKey: hex.encode(tapscript.options.serverPubKey),
13887
+ csvTimelock: timelockToSequence(csvTimelock).toString()
13888
+ },
13889
+ script: hex.encode(tapscript.pkScript),
13890
+ address: tapscript.address(this.network.hrp, this.arkServerPublicKey).encode(),
13891
+ state: "active",
13892
+ metadata: {
13893
+ ...tagSource && { source: WALLET_RECEIVE_SOURCE },
13894
+ signingDescriptor: descriptor
13895
+ }
13556
13896
  }
13557
- });
13558
- this.setBoardingTapscriptForRotation(newBoarding);
13559
- try {
13560
- await manager.refillLookAhead();
13561
- } catch (e) {
13562
- console.warn("look-ahead refill after boarding allocation failed", e);
13563
- }
13564
- return newBoarding.onchainAddress(this.network);
13897
+ };
13565
13898
  }
13566
13899
  /**
13567
13900
  * @see HDWalletCapable.getCurrentSigningDescriptor
@@ -13946,7 +14279,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
13946
14279
  await this._restoreInFlight?.catch(() => void 0);
13947
14280
  this._serverInfoUnsub?.();
13948
14281
  this._serverInfoUnsub = void 0;
13949
- await this._serverInfoInFlight?.catch(() => void 0);
14282
+ await this.settleServerInfoChanges();
13950
14283
  let rotatorError;
13951
14284
  try {
13952
14285
  await this._receiveRotator?.dispose();
@@ -14478,7 +14811,10 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
14478
14811
  * `typeof` guard skips the arknote strings settle also accepts.
14479
14812
  *
14480
14813
  * No-ops for static / `auto` wallets (no descriptor provider — boarding
14481
- * stays on its fixed index-0 address). Best-effort and non-fatal: the
14814
+ * stays on its fixed index-0 address). Wallets on a custom
14815
+ * {@link DescriptorProvider} DO rotate here now, where they previously
14816
+ * no-opped — see the behaviour-change note on {@link
14817
+ * getNewBoardingAddress}. Best-effort and non-fatal: the
14482
14818
  * settle has already committed and its txid must be returned, so a
14483
14819
  * rotation failure is logged and swallowed rather than thrown. Funds at
14484
14820
  * the retired boarding address remain discoverable — the old `boarding`
@@ -14893,9 +15229,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
14893
15229
  seen.add(pendingTx.arkTxid);
14894
15230
  batchPending.push(pendingTx.arkTxid);
14895
15231
  try {
14896
- const checkpointTxs = pendingTx.signedCheckpointTxs.map(
14897
- (c) => Transaction$2.fromPSBT(base64.decode(c))
14898
- );
15232
+ const checkpointTxs = pendingTx.signedCheckpointTxs.map((c) => {
15233
+ const tx = Transaction$2.fromPSBT(base64.decode(c));
15234
+ assertAllowedSighashTypes(tx);
15235
+ return tx;
15236
+ });
14899
15237
  assertCheckpointsMatchInputs(
14900
15238
  checkpointTxs,
14901
15239
  checkpointInputs,
@@ -15063,7 +15401,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
15063
15401
  const myPkScript = ArkAddress.decode(await this.getAddress()).pkScript;
15064
15402
  let { spendable, unclaimed } = this.classifyCashVtxos(vtxos);
15065
15403
  let swept = 0;
15066
- const drainable = vtxos.filter((vtxo) => !isSubdust(vtxo, this.dustAmount));
15404
+ const drainable = vtxos.filter(
15405
+ (vtxo) => !isSubdust(vtxo, this.dustAmount) && !vtxo.isUnrolled
15406
+ );
15067
15407
  if (drainable.length > 0) {
15068
15408
  let drained = { count: 0, swept: 0, claimed: /* @__PURE__ */ new Set() };
15069
15409
  try {
@@ -15136,6 +15476,8 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
15136
15476
  for (const vtxo of vtxos) {
15137
15477
  if (hasTerminalSpend(vtxo)) {
15138
15478
  unclaimed.push(cashReport(vtxo, "already-spent"));
15479
+ } else if (vtxo.isUnrolled) {
15480
+ unclaimed.push(cashReport(vtxo, "exited"));
15139
15481
  } else if (isSubdust(vtxo, this.dustAmount)) {
15140
15482
  unclaimed.push(cashReport(vtxo, "subdust"));
15141
15483
  } else if (vtxo.isSwept) {
@@ -15221,9 +15563,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
15221
15563
  const unrollCandidates = this.checkpointUnrollCandidates(await this.arkProvider.getInfo());
15222
15564
  for (const pendingTx of pendingTxs) {
15223
15565
  try {
15224
- const checkpointTxs = pendingTx.signedCheckpointTxs.map(
15225
- (checkpoint) => Transaction$2.fromPSBT(base64.decode(checkpoint))
15226
- );
15566
+ const checkpointTxs = pendingTx.signedCheckpointTxs.map((checkpoint) => {
15567
+ const tx = Transaction$2.fromPSBT(base64.decode(checkpoint));
15568
+ assertAllowedSighashTypes(tx);
15569
+ return tx;
15570
+ });
15227
15571
  assertCheckpointsMatchInputs(
15228
15572
  checkpointTxs,
15229
15573
  inputs.map((input) => ({
@@ -16026,6 +16370,11 @@ var MessageBus = class {
16026
16370
  buildServicesFn;
16027
16371
  boundOnMessage = this.onMessage.bind(this);
16028
16372
  intentRepository;
16373
+ /** Pending broadcasts, drained FIFO by a single {@link drain} loop. */
16374
+ broadcastQueue = [];
16375
+ draining = false;
16376
+ /** Bumped wherever the generation owning the queued broadcasts ends. */
16377
+ broadcastEpoch = 0;
16029
16378
  /** Start the message bus and attach service-worker event listeners. */
16030
16379
  async start() {
16031
16380
  if (this.running) return;
@@ -16048,6 +16397,7 @@ var MessageBus = class {
16048
16397
  this.running = false;
16049
16398
  this.tickInProgress = false;
16050
16399
  this.initialized = false;
16400
+ this.invalidateBroadcasts();
16051
16401
  if (this.tickTimeout !== null) {
16052
16402
  self.clearTimeout(this.tickTimeout);
16053
16403
  this.tickTimeout = null;
@@ -16060,6 +16410,78 @@ var MessageBus = class {
16060
16410
  self.removeEventListener("message", this.boundOnMessage);
16061
16411
  await Promise.all(Array.from(this.handlers.values()).map((updater) => updater.stop()));
16062
16412
  }
16413
+ /** Invalidate every broadcast belonging to the outgoing generation. */
16414
+ invalidateBroadcasts() {
16415
+ this.broadcastEpoch++;
16416
+ this.broadcastQueue.length = 0;
16417
+ }
16418
+ broadcastToClients(responses) {
16419
+ this.broadcastQueue.push(...responses);
16420
+ void this.drain();
16421
+ }
16422
+ /**
16423
+ * Single fan-out loop, so pushes and tick responses keep one FIFO order.
16424
+ * A helper posting per response would race: `clients.matchAll()` gives no
16425
+ * cross-call resolution order, and it would de-batch `runTick`.
16426
+ */
16427
+ async drain() {
16428
+ if (this.draining) return;
16429
+ this.draining = true;
16430
+ try {
16431
+ while (this.broadcastQueue.length) {
16432
+ const epoch = this.broadcastEpoch;
16433
+ const batch = this.broadcastQueue.splice(0);
16434
+ if (!this.running) {
16435
+ this.logDroppedBroadcasts(batch, "bus stopped");
16436
+ return;
16437
+ }
16438
+ let clients;
16439
+ try {
16440
+ clients = await self.clients.matchAll({
16441
+ includeUncontrolled: true,
16442
+ type: "window"
16443
+ });
16444
+ } catch (err) {
16445
+ this.logDroppedBroadcasts(batch, "clients.matchAll failed", err);
16446
+ continue;
16447
+ }
16448
+ if (!this.running) {
16449
+ this.logDroppedBroadcasts(batch, "bus stopped during matchAll");
16450
+ return;
16451
+ }
16452
+ if (this.broadcastEpoch !== epoch) {
16453
+ this.logDroppedBroadcasts(batch, "re-init landed during matchAll");
16454
+ continue;
16455
+ }
16456
+ for (const message of batch) {
16457
+ for (const client of clients) {
16458
+ try {
16459
+ client.postMessage(message);
16460
+ } catch (err) {
16461
+ if (this.debug)
16462
+ console.warn(`[${message.tag}] broadcast to client failed`, err);
16463
+ }
16464
+ }
16465
+ }
16466
+ }
16467
+ } catch {
16468
+ } finally {
16469
+ this.draining = false;
16470
+ if (this.running && this.broadcastQueue.length) void this.drain();
16471
+ }
16472
+ }
16473
+ /**
16474
+ * A dropped batch has no periodic recovery to fall back on now that events
16475
+ * no longer ride the tick, so name the loss under {@link Options.debug}.
16476
+ */
16477
+ logDroppedBroadcasts(batch, reason, ...error) {
16478
+ if (!this.debug) return;
16479
+ console.warn(
16480
+ `dropping ${batch.length} broadcast(s): ${reason}`,
16481
+ batch.map((response) => response.tag),
16482
+ ...error
16483
+ );
16484
+ }
16063
16485
  scheduleNextTick() {
16064
16486
  if (!this.running) return;
16065
16487
  if (this.tickTimeout !== null) return;
@@ -16088,16 +16510,7 @@ var MessageBus = class {
16088
16510
  if (this.debug)
16089
16511
  console.log(`[${updater.messageTag}] outgoing tick response:`, response);
16090
16512
  if (response && response.length > 0) {
16091
- self.clients.matchAll({
16092
- includeUncontrolled: true,
16093
- type: "window"
16094
- }).then((clients) => {
16095
- for (const message of response) {
16096
- clients.forEach((client) => {
16097
- client.postMessage(message);
16098
- });
16099
- }
16100
- });
16513
+ this.broadcastToClients(response);
16101
16514
  }
16102
16515
  } catch (err) {
16103
16516
  if (this.debug) console.error(`[${updater.messageTag}] tick failed`, err);
@@ -16136,6 +16549,14 @@ var MessageBus = class {
16136
16549
  self.clearTimeout(this.tickTimeout);
16137
16550
  this.tickTimeout = null;
16138
16551
  }
16552
+ this.invalidateBroadcasts();
16553
+ const epoch = this.broadcastEpoch;
16554
+ const channel = {
16555
+ broadcast: (response) => {
16556
+ if (epoch !== this.broadcastEpoch) return;
16557
+ this.broadcastToClients([response]);
16558
+ }
16559
+ };
16139
16560
  if (this.initialized) {
16140
16561
  this.initialized = false;
16141
16562
  await Promise.all(
@@ -16152,9 +16573,7 @@ var MessageBus = class {
16152
16573
  try {
16153
16574
  for (const updater of this.handlers.values()) {
16154
16575
  if (this.debug) console.log(`Starting updater: ${updater.messageTag}`);
16155
- await updater.start(services, {
16156
- walletRepository: this.walletRepository
16157
- });
16576
+ await updater.start(services, { walletRepository: this.walletRepository }, channel);
16158
16577
  started.push(updater);
16159
16578
  }
16160
16579
  } catch (err) {
@@ -16824,6 +17243,17 @@ var WalletMessageHandler = class {
16824
17243
  incomingFundsSubscription;
16825
17244
  contractEventsSubscription;
16826
17245
  onNextTick = [];
17246
+ channel;
17247
+ // One counter per subscription field, bumped exactly where that field is
17248
+ // dropped — never where a neighbour's is. They cannot be merged:
17249
+ // `onWalletInitialized` re-subscribes incoming funds while
17250
+ // `ensureContractEventBroadcasting` deliberately keeps its subscription, so
17251
+ // a shared bump would leave the live contract-event emitter permanently
17252
+ // stale, and folding requests in would truncate an in-flight settle.
17253
+ incomingFundsEpoch = 0;
17254
+ contractEventsEpoch = 0;
17255
+ /** Per-request progress callbacks: ends with the handler, not with a re-init. */
17256
+ requestEpoch = 0;
16827
17257
  /**
16828
17258
  * Instantiate a new WalletUpdater.
16829
17259
  * Can override the default `messageTag` allowing more than one updater to run in parallel.
@@ -16834,13 +17264,20 @@ var WalletMessageHandler = class {
16834
17264
  }
16835
17265
  // lifecycle methods
16836
17266
  async start(...params) {
16837
- const [services, repositories] = params;
17267
+ const [services, repositories, channel] = params;
16838
17268
  this.readonlyWallet = services.readonlyWallet;
16839
17269
  this.wallet = services.wallet;
16840
17270
  this.arkProvider = services.arkProvider;
16841
17271
  this.walletRepository = repositories.walletRepository;
17272
+ this.channel = channel;
17273
+ this.onNextTick = [];
16842
17274
  }
16843
17275
  async stop() {
17276
+ this.requestEpoch++;
17277
+ this.incomingFundsEpoch++;
17278
+ this.contractEventsEpoch++;
17279
+ this.channel = void 0;
17280
+ this.onNextTick = [];
16844
17281
  if (this.incomingFundsSubscription) {
16845
17282
  this.incomingFundsSubscription();
16846
17283
  this.incomingFundsSubscription = void 0;
@@ -16863,8 +17300,8 @@ var WalletMessageHandler = class {
16863
17300
  this.indexerProvider = void 0;
16864
17301
  }
16865
17302
  async tick(_now) {
16866
- const results = await Promise.allSettled(this.onNextTick.map((fn) => fn()));
16867
- this.onNextTick = [];
17303
+ const pending = this.onNextTick.splice(0);
17304
+ const results = await Promise.allSettled(pending.map((fn) => fn()));
16868
17305
  return results.map((result) => {
16869
17306
  if (result.status === "fulfilled") {
16870
17307
  return result.value;
@@ -16874,8 +17311,46 @@ var WalletMessageHandler = class {
16874
17311
  }
16875
17312
  }).filter((response) => response !== null);
16876
17313
  }
16877
- scheduleForNextTick(callback) {
16878
- this.onNextTick.push(callback);
17314
+ epochOf(scope) {
17315
+ switch (scope) {
17316
+ case "incomingFunds":
17317
+ return this.incomingFundsEpoch;
17318
+ case "contractEvents":
17319
+ return this.contractEventsEpoch;
17320
+ case "request":
17321
+ return this.requestEpoch;
17322
+ default: {
17323
+ const _exhaustive = scope;
17324
+ throw new Error(`Unknown scope: ${_exhaustive}`);
17325
+ }
17326
+ }
17327
+ }
17328
+ newGeneration(scope) {
17329
+ const born = this.epochOf(scope);
17330
+ const stale = () => born !== this.epochOf(scope);
17331
+ return {
17332
+ get stale() {
17333
+ return stale();
17334
+ }
17335
+ };
17336
+ }
17337
+ /**
17338
+ * A handle rather than a `broadcast(response, epoch)` call, because it can
17339
+ * only be written where the closure it guards is created — an epoch passed
17340
+ * at the call site reads identically whether it guards anything or not.
17341
+ */
17342
+ newEmitter(scope) {
17343
+ const gen = this.newGeneration(scope);
17344
+ return {
17345
+ get stale() {
17346
+ return gen.stale;
17347
+ },
17348
+ emit: (response) => {
17349
+ if (!response || gen.stale) return;
17350
+ if (this.channel) return this.channel.broadcast(response);
17351
+ this.onNextTick.push(() => response);
17352
+ }
17353
+ };
16879
17354
  }
16880
17355
  requireWallet() {
16881
17356
  if (!this.wallet) {
@@ -16904,6 +17379,7 @@ var WalletMessageHandler = class {
16904
17379
  }
16905
17380
  async handleMessage(message) {
16906
17381
  const id = message.id;
17382
+ const emitter = this.newEmitter("request");
16907
17383
  if (message.type === "INIT_WALLET") {
16908
17384
  await this.handleInitWallet(message);
16909
17385
  return this.tagged({
@@ -17172,6 +17648,18 @@ var WalletMessageHandler = class {
17172
17648
  payload: { descriptor }
17173
17649
  });
17174
17650
  }
17651
+ case "GET_NEW_ADDRESSES": {
17652
+ const wallet = this.wallet;
17653
+ if (!wallet) throw new WalletNotInitializedError();
17654
+ const addresses = await wallet.getNewAddresses(
17655
+ message.payload
17656
+ );
17657
+ return this.tagged({
17658
+ id,
17659
+ type: "NEW_ADDRESSES",
17660
+ payload: { addresses }
17661
+ });
17662
+ }
17175
17663
  case "GET_USED_SIGNING_DESCRIPTORS": {
17176
17664
  const wallet = this.wallet;
17177
17665
  const descriptors = isHDWalletCapable(wallet) ? await wallet.getUsedSigningDescriptors(
@@ -17262,8 +17750,8 @@ var WalletMessageHandler = class {
17262
17750
  const wallet = this.requireWallet();
17263
17751
  const vtxoManager = await wallet.getVtxoManager();
17264
17752
  const txid = await vtxoManager.recoverVtxos((e) => {
17265
- this.scheduleForNextTick(
17266
- () => this.tagged({
17753
+ emitter.emit(
17754
+ this.tagged({
17267
17755
  id,
17268
17756
  type: "RECOVER_VTXOS_EVENT",
17269
17757
  payload: e
@@ -17307,8 +17795,8 @@ var WalletMessageHandler = class {
17307
17795
  const wallet = this.requireWallet();
17308
17796
  const vtxoManager = await wallet.getVtxoManager();
17309
17797
  const txid = await vtxoManager.renewVtxos((e) => {
17310
- this.scheduleForNextTick(
17311
- () => this.tagged({
17798
+ emitter.emit(
17799
+ this.tagged({
17312
17800
  id,
17313
17801
  type: "RENEW_VTXOS_EVENT",
17314
17802
  payload: e
@@ -17346,8 +17834,8 @@ var WalletMessageHandler = class {
17346
17834
  const vtxoManager = await wallet.getVtxoManager();
17347
17835
  const report = await vtxoManager.migrateDeprecatedSignerVtxos({
17348
17836
  eventCallback: (e) => {
17349
- this.scheduleForNextTick(
17350
- () => this.tagged({
17837
+ emitter.emit(
17838
+ this.tagged({
17351
17839
  id,
17352
17840
  type: "MIGRATE_DEPRECATED_SIGNER_VTXOS_EVENT",
17353
17841
  payload: e
@@ -17450,6 +17938,7 @@ var WalletMessageHandler = class {
17450
17938
  intentLocked: offchain.intentLocked,
17451
17939
  recoverable: offchain.recoverable,
17452
17940
  pendingRecovery: offchain.pendingRecovery,
17941
+ unrolled: offchain.unrolled,
17453
17942
  total: totalBoarding + offchain.total,
17454
17943
  assets: offchain.assets,
17455
17944
  availableAssets: offchain.availableAssets
@@ -17460,11 +17949,15 @@ var WalletMessageHandler = class {
17460
17949
  return this.readonlyWallet.getBoardingUtxos();
17461
17950
  }
17462
17951
  async onWalletInitialized() {
17463
- if (!this.readonlyWallet || !this.arkProvider || !this.indexerProvider || !this.walletRepository) {
17952
+ const wallet = this.readonlyWallet;
17953
+ if (!wallet || !this.arkProvider || !this.indexerProvider || !this.walletRepository) {
17464
17954
  return;
17465
17955
  }
17956
+ const init = this.newGeneration("incomingFunds");
17466
17957
  await this.ensureContractEventBroadcasting();
17958
+ if (init.stale) return;
17467
17959
  await this.refreshCachedData();
17960
+ if (init.stale) return;
17468
17961
  if (this.wallet) {
17469
17962
  try {
17470
17963
  const vtxos = await this.getVtxosFromRepo();
@@ -17477,72 +17970,81 @@ var WalletMessageHandler = class {
17477
17970
  } catch (error) {
17478
17971
  console.error("Error recovering pending transactions:", error);
17479
17972
  }
17973
+ if (init.stale) return;
17480
17974
  }
17481
17975
  if (this.incomingFundsSubscription) this.incomingFundsSubscription();
17482
- const address = await this.readonlyWallet.getAddress();
17483
- this.incomingFundsSubscription = await this.readonlyWallet.notifyIncomingFunds(
17484
- async (funds) => {
17485
- if (funds.type === "vtxo") {
17486
- const { newVtxos, spentVtxos } = funds;
17487
- if (newVtxos.length + spentVtxos.length === 0) return;
17488
- const byScript = /* @__PURE__ */ new Map();
17489
- for (const v of [...newVtxos, ...spentVtxos]) {
17490
- if (!v.script) {
17491
- console.warn(
17492
- `WalletMessageHandler.notifyIncomingFunds: dropping VTXO without script ${v.txid}:${v.vout}`
17493
- );
17494
- continue;
17495
- }
17496
- const arr = byScript.get(v.script) ?? [];
17497
- arr.push(v);
17498
- byScript.set(v.script, arr);
17499
- }
17500
- let walletScript;
17501
- try {
17502
- walletScript = scriptFromArkAddress(address);
17503
- } catch {
17504
- walletScript = void 0;
17505
- }
17506
- const cm = await this.readonlyWallet.getContractManager();
17507
- const contracts = await cm.getContracts();
17508
- const addrByScript = new Map(contracts.map((c) => [c.script, c.address]));
17509
- for (const [script, vtxos] of byScript) {
17510
- const filtered = warnAndFilterVtxosForScript(
17511
- vtxos,
17512
- script,
17513
- "WalletMessageHandler.notifyIncomingFunds"
17976
+ this.incomingFundsSubscription = void 0;
17977
+ this.incomingFundsEpoch++;
17978
+ const emitter = this.newEmitter("incomingFunds");
17979
+ const address = await wallet.getAddress();
17980
+ const unsubscribe = await wallet.notifyIncomingFunds(async (funds) => {
17981
+ if (emitter.stale) return;
17982
+ if (funds.type === "vtxo") {
17983
+ const { newVtxos, spentVtxos } = funds;
17984
+ if (newVtxos.length + spentVtxos.length === 0) return;
17985
+ const byScript = /* @__PURE__ */ new Map();
17986
+ for (const v of [...newVtxos, ...spentVtxos]) {
17987
+ if (!v.script) {
17988
+ console.warn(
17989
+ `WalletMessageHandler.notifyIncomingFunds: dropping VTXO without script ${v.txid}:${v.vout}`
17514
17990
  );
17515
- if (filtered.length === 0) continue;
17516
- const targetAddress = script === walletScript ? address : addrByScript.get(script);
17517
- if (!targetAddress) continue;
17518
- if (this.walletRepository) {
17519
- await saveVtxosForContract(
17520
- this.walletRepository,
17521
- { script, address: targetAddress },
17522
- filtered
17523
- );
17524
- }
17991
+ continue;
17525
17992
  }
17526
- this.scheduleForNextTick(
17527
- () => this.tagged({
17528
- type: "VTXO_UPDATE",
17529
- broadcast: true,
17530
- payload: { newVtxos, spentVtxos }
17531
- })
17532
- );
17993
+ const arr = byScript.get(v.script) ?? [];
17994
+ arr.push(v);
17995
+ byScript.set(v.script, arr);
17533
17996
  }
17534
- if (funds.type === "utxo") {
17535
- const utxos = await this.readonlyWallet.getBoardingUtxos();
17536
- this.scheduleForNextTick(
17537
- () => this.tagged({
17538
- type: "UTXO_UPDATE",
17539
- broadcast: true,
17540
- payload: { coins: utxos }
17541
- })
17997
+ let walletScript;
17998
+ try {
17999
+ walletScript = scriptFromArkAddress(address);
18000
+ } catch {
18001
+ walletScript = void 0;
18002
+ }
18003
+ const cm = await wallet.getContractManager();
18004
+ if (emitter.stale) return;
18005
+ const contracts = await cm.getContracts();
18006
+ if (emitter.stale) return;
18007
+ const addrByScript = new Map(contracts.map((c) => [c.script, c.address]));
18008
+ for (const [script, vtxos] of byScript) {
18009
+ const filtered = warnAndFilterVtxosForScript(
18010
+ vtxos,
18011
+ script,
18012
+ "WalletMessageHandler.notifyIncomingFunds"
17542
18013
  );
18014
+ if (filtered.length === 0) continue;
18015
+ const targetAddress = script === walletScript ? address : addrByScript.get(script);
18016
+ if (!targetAddress) continue;
18017
+ if (this.walletRepository) {
18018
+ if (emitter.stale) return;
18019
+ await saveVtxosForContract(
18020
+ this.walletRepository,
18021
+ { script, address: targetAddress },
18022
+ filtered
18023
+ );
18024
+ }
17543
18025
  }
18026
+ emitter.emit(
18027
+ this.tagged({
18028
+ type: "VTXO_UPDATE",
18029
+ broadcast: true,
18030
+ payload: { newVtxos, spentVtxos }
18031
+ })
18032
+ );
17544
18033
  }
17545
- );
18034
+ if (funds.type === "utxo") {
18035
+ if (emitter.stale) return;
18036
+ const utxos = await wallet.getBoardingUtxos();
18037
+ emitter.emit(
18038
+ this.tagged({
18039
+ type: "UTXO_UPDATE",
18040
+ broadcast: true,
18041
+ payload: { coins: utxos }
18042
+ })
18043
+ );
18044
+ }
18045
+ });
18046
+ if (emitter.stale) return unsubscribe();
18047
+ this.incomingFundsSubscription = unsubscribe;
17546
18048
  if (this.wallet) {
17547
18049
  try {
17548
18050
  await this.wallet.getVtxoManager();
@@ -17587,10 +18089,11 @@ var WalletMessageHandler = class {
17587
18089
  await this.refreshCachedData();
17588
18090
  }
17589
18091
  async handleSettle(message) {
18092
+ const emitter = this.newEmitter("request");
17590
18093
  const wallet = this.requireWallet();
17591
18094
  const txid = await wallet.settle(message.payload.params, (e) => {
17592
- this.scheduleForNextTick(
17593
- () => this.tagged({
18095
+ emitter.emit(
18096
+ this.tagged({
17594
18097
  id: message.id,
17595
18098
  type: "SETTLE_EVENT",
17596
18099
  payload: e
@@ -17667,10 +18170,19 @@ var WalletMessageHandler = class {
17667
18170
  throw new WalletNotInitializedError();
17668
18171
  }
17669
18172
  const allVtxos = await this.getVtxosFromRepo();
17670
- const vtxos = allVtxos.filter((v) => !hasTerminalSpend(v));
17671
18173
  const dustAmount = this.readonlyWallet.dustAmount;
18174
+ const withUnrolled = message.payload.filter?.withUnrolled ?? false;
17672
18175
  const includeRecoverable = message.payload.filter?.withRecoverable ?? false;
17673
- const filteredVtxos = includeRecoverable ? vtxos : vtxos.filter((v) => {
18176
+ return allVtxos.filter((v) => {
18177
+ if (v.isUnrolled) {
18178
+ return withUnrolled;
18179
+ }
18180
+ if (hasTerminalSpend(v)) {
18181
+ return false;
18182
+ }
18183
+ if (includeRecoverable) {
18184
+ return true;
18185
+ }
17674
18186
  if (dustAmount != null && isSubdust(v, dustAmount)) {
17675
18187
  return false;
17676
18188
  }
@@ -17682,15 +18194,15 @@ var WalletMessageHandler = class {
17682
18194
  }
17683
18195
  return true;
17684
18196
  });
17685
- if (!message.payload.filter?.withUnrolled) {
17686
- return filteredVtxos;
17687
- }
17688
- return filteredVtxos.concat(allVtxos.filter((v) => hasTerminalSpend(v) && v.isUnrolled));
17689
18197
  }
17690
18198
  /** Tear down handler subscriptions, then delegate the full wipe to the wallet. */
17691
18199
  async clear() {
17692
18200
  const wallet = this.wallet ?? this.readonlyWallet;
17693
18201
  if (!wallet) return;
18202
+ this.requestEpoch++;
18203
+ this.incomingFundsEpoch++;
18204
+ this.contractEventsEpoch++;
18205
+ this.onNextTick = [];
17694
18206
  if (this.incomingFundsSubscription) {
17695
18207
  this.incomingFundsSubscription();
17696
18208
  this.incomingFundsSubscription = void 0;
@@ -17779,13 +18291,17 @@ var WalletMessageHandler = class {
17779
18291
  return buildTransactionHistory(vtxos, boardingTxs, commitmentsToIgnore, resolveTxCreatedAt);
17780
18292
  }
17781
18293
  async ensureContractEventBroadcasting() {
17782
- if (!this.readonlyWallet) return;
18294
+ const wallet = this.readonlyWallet;
18295
+ if (!wallet) return;
17783
18296
  if (this.contractEventsSubscription) return;
18297
+ const gen = this.newGeneration("contractEvents");
17784
18298
  try {
17785
- const manager = await this.readonlyWallet.getContractManager();
18299
+ const manager = await wallet.getContractManager();
18300
+ if (gen.stale || this.contractEventsSubscription) return;
18301
+ const emitter = this.newEmitter("contractEvents");
17786
18302
  this.contractEventsSubscription = manager.onContractEvent((event) => {
17787
- this.scheduleForNextTick(
17788
- () => this.tagged({
18303
+ emitter.emit(
18304
+ this.tagged({
17789
18305
  type: "CONTRACT_EVENT",
17790
18306
  broadcast: true,
17791
18307
  payload: { event }
@@ -17816,6 +18332,9 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
17816
18332
  // Allocation is a local repository write plus a fire-and-forget band
17817
18333
  // slide — no indexer round trip on the request path.
17818
18334
  GET_NEXT_SIGNING_DESCRIPTOR: 1e4,
18335
+ // Same shape as the bare allocation above, plus one contract write per
18336
+ // requested type — still local, still no indexer round trip.
18337
+ GET_NEW_ADDRESSES: 1e4,
17819
18338
  GET_USED_SIGNING_DESCRIPTORS: 2e4,
17820
18339
  ADVANCE_SIGNING_DESCRIPTOR_WATERMARK: 1e4,
17821
18340
  // Medium reads — may involve indexer queries
@@ -18961,6 +19480,35 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
18961
19480
  throw new Error(`Failed to allocate next signing descriptor: ${error}`);
18962
19481
  }
18963
19482
  }
19483
+ /**
19484
+ * @see Wallet.getNewAddresses
19485
+ *
19486
+ * Proxied as a single request rather than rebuilt page-side out of
19487
+ * {@link getNextSigningDescriptor}: the worker owns the HD watermark *and*
19488
+ * the contract repository, so splitting the operation would let a page burn
19489
+ * an index and then fail to register the script, leaving an address nothing
19490
+ * watches.
19491
+ *
19492
+ * Note the error shape — `WalletCannotAllocateAddressError` is thrown
19493
+ * worker-side and cannot cross the message boundary as a class, so a
19494
+ * `forceNew` refusal surfaces here as a plain `Error` carrying its message
19495
+ * (and the worker-side error as `cause`). Callers that must branch on the
19496
+ * refusal match the message rather than `instanceof`.
19497
+ */
19498
+ async getNewAddresses(opts) {
19499
+ const message = {
19500
+ id: getRandomId(),
19501
+ tag: this.messageTag,
19502
+ type: "GET_NEW_ADDRESSES",
19503
+ ...opts ? { payload: opts } : {}
19504
+ };
19505
+ try {
19506
+ const response = await this.sendMessage(message);
19507
+ return response.payload.addresses;
19508
+ } catch (error) {
19509
+ throw new Error(`Failed to allocate new addresses: ${error}`, { cause: error });
19510
+ }
19511
+ }
18964
19512
  /** @see HDWalletCapable.getUsedSigningDescriptors */
18965
19513
  async getUsedSigningDescriptors(opts) {
18966
19514
  const message = {
@@ -19927,6 +20475,10 @@ var ElectrumOnchainProvider = class {
19927
20475
  }
19928
20476
  throw new Error("Only 1 or 1P1C package can be broadcast");
19929
20477
  }
20478
+ async getRawTransaction(txid) {
20479
+ const [tx] = await this.chain.fetchTransactions([txid]);
20480
+ return hex.decode(tx.hex);
20481
+ }
19930
20482
  async getTxOutspends(txid) {
19931
20483
  const [txResult] = await this.chain.fetchTransactions([txid]);
19932
20484
  const tx = Transaction$2.fromRaw(hex.decode(txResult.hex), {
@@ -20232,6 +20784,183 @@ function childTxidFromHex(txHex) {
20232
20784
  });
20233
20785
  return tx.id;
20234
20786
  }
20787
+
20788
+ // src/providers/cachingArk.ts
20789
+ var DEFAULT_TTL_MS = 6e4;
20790
+ var CachingArkProvider = class {
20791
+ constructor(inner, ttlMs = DEFAULT_TTL_MS) {
20792
+ this.inner = inner;
20793
+ this.ttlMs = ttlMs;
20794
+ this.unsubscribe = inner.onServerInfoChanged?.((info) => {
20795
+ this.cached = info;
20796
+ this.expiresAt = Date.now() + this.ttlMs;
20797
+ this.generation++;
20798
+ }) ?? (() => {
20799
+ });
20800
+ }
20801
+ inner;
20802
+ ttlMs;
20803
+ cached;
20804
+ expiresAt = 0;
20805
+ inflight;
20806
+ /** Bumped on every event-driven cache write; see {@link getInfo}. */
20807
+ generation = 0;
20808
+ unsubscribe;
20809
+ /** Releases the inner provider's server-info subscription. */
20810
+ dispose() {
20811
+ this.unsubscribe();
20812
+ }
20813
+ get serverUrl() {
20814
+ const url = this.inner.serverUrl;
20815
+ return typeof url === "string" ? url : void 0;
20816
+ }
20817
+ async getInfo() {
20818
+ if (this.cached && Date.now() < this.expiresAt) return this.cached;
20819
+ if (!this.inflight) {
20820
+ const generation = this.generation;
20821
+ this.inflight = this.inner.getInfo().then((info) => {
20822
+ if (this.generation !== generation) return this.cached ?? info;
20823
+ this.cached = info;
20824
+ this.expiresAt = Date.now() + this.ttlMs;
20825
+ return info;
20826
+ }).finally(() => {
20827
+ this.inflight = void 0;
20828
+ });
20829
+ }
20830
+ return this.inflight;
20831
+ }
20832
+ submitTx(signedArkTx, checkpointTxs) {
20833
+ return this.inner.submitTx(signedArkTx, checkpointTxs);
20834
+ }
20835
+ finalizeTx(arkTxid, finalCheckpointTxs) {
20836
+ return this.inner.finalizeTx(arkTxid, finalCheckpointTxs);
20837
+ }
20838
+ registerIntent(intent) {
20839
+ return this.inner.registerIntent(intent);
20840
+ }
20841
+ deleteIntent(intent) {
20842
+ return this.inner.deleteIntent(intent);
20843
+ }
20844
+ confirmRegistration(intentId) {
20845
+ return this.inner.confirmRegistration(intentId);
20846
+ }
20847
+ submitTreeNonces(batchId, pubkey, nonces) {
20848
+ return this.inner.submitTreeNonces(batchId, pubkey, nonces);
20849
+ }
20850
+ submitTreeSignatures(batchId, pubkey, signatures) {
20851
+ return this.inner.submitTreeSignatures(batchId, pubkey, signatures);
20852
+ }
20853
+ submitSignedForfeitTxs(signedForfeitTxs, signedCommitmentTx) {
20854
+ return this.inner.submitSignedForfeitTxs(signedForfeitTxs, signedCommitmentTx);
20855
+ }
20856
+ getEventStream(signal, topics) {
20857
+ return this.inner.getEventStream(signal, topics);
20858
+ }
20859
+ getTransactionsStream(signal) {
20860
+ return this.inner.getTransactionsStream(signal);
20861
+ }
20862
+ getPendingTxs(intent) {
20863
+ return this.inner.getPendingTxs(intent);
20864
+ }
20865
+ onServerInfoChanged(listener) {
20866
+ return this.inner.onServerInfoChanged?.(listener) ?? (() => {
20867
+ });
20868
+ }
20869
+ };
20870
+ var PrevTxUnavailableError = class extends Error {
20871
+ constructor(message, txids = []) {
20872
+ super(message);
20873
+ this.txids = txids;
20874
+ this.name = "PrevTxUnavailableError";
20875
+ }
20876
+ txids;
20877
+ };
20878
+ async function resolvePrevTxs(txids, source, onchain) {
20879
+ const resolved = /* @__PURE__ */ new Map();
20880
+ const wanted = [...new Set(txids)];
20881
+ if (wanted.length === 0) return resolved;
20882
+ try {
20883
+ const { txs } = await source.getVirtualTxs(wanted);
20884
+ for (const psbt of txs) {
20885
+ const tx = Transaction.fromPSBT(base64.decode(psbt));
20886
+ resolved.set(tx.id, tx.toBytes());
20887
+ }
20888
+ } catch (err) {
20889
+ if (!onchain) throw err;
20890
+ }
20891
+ let missing = wanted.filter((txid) => !resolved.has(txid));
20892
+ if (missing.length > 0 && onchain) {
20893
+ const raw = await Promise.all(
20894
+ missing.map(async (txid) => {
20895
+ try {
20896
+ return await onchain.getRawTransaction(txid);
20897
+ } catch {
20898
+ return void 0;
20899
+ }
20900
+ })
20901
+ );
20902
+ for (const [i, bytes] of raw.entries()) {
20903
+ if (bytes) resolved.set(missing[i], bytes);
20904
+ }
20905
+ missing = missing.filter((txid) => !resolved.has(txid));
20906
+ }
20907
+ if (missing.length > 0) {
20908
+ throw new PrevTxUnavailableError(
20909
+ `cannot resolve the previous transaction of ${missing.join(", ")}` + (onchain ? "" : " (no onchain source configured)"),
20910
+ missing
20911
+ );
20912
+ }
20913
+ return resolved;
20914
+ }
20915
+ async function attachPrevArkTxs(tx, txids, source, onchain) {
20916
+ const pending = [];
20917
+ for (let i = 0; i < tx.inputsLength; i++) {
20918
+ if (getArkPsbtFields(tx, i, PrevArkTxField).length === 0) pending.push(i);
20919
+ }
20920
+ if (pending.length === 0) return;
20921
+ const resolved = await resolvePrevTxs(
20922
+ pending.map((i) => txidAt(txids, i)),
20923
+ source,
20924
+ onchain
20925
+ );
20926
+ for (const i of pending) {
20927
+ setArkPsbtField(tx, i, PrevArkTxField, resolved.get(txidAt(txids, i)));
20928
+ }
20929
+ }
20930
+ async function attachPrevoutTxs(tx, source) {
20931
+ const pending = [];
20932
+ for (let i = 0; i < tx.inputsLength; i++) {
20933
+ if (getArkPsbtFields(tx, i, PrevoutTxField).length > 0) continue;
20934
+ const input = tx.getInput(i);
20935
+ if (!input?.txid) throw new PrevTxUnavailableError(`input ${i} has no outpoint`);
20936
+ pending.push({ index: i, txid: hex.encode(input.txid) });
20937
+ }
20938
+ if (pending.length === 0) return;
20939
+ const resolved = /* @__PURE__ */ new Map();
20940
+ for (const txid of new Set(pending.map((p) => p.txid))) {
20941
+ resolved.set(txid, await source.getRawTransaction(txid));
20942
+ }
20943
+ for (const { index, txid } of pending) {
20944
+ setArkPsbtField(tx, index, PrevoutTxField, resolved.get(txid));
20945
+ }
20946
+ }
20947
+ async function withPrevTxs(coins, source, onchain) {
20948
+ const pending = coins.filter((c) => !c.prevTx);
20949
+ if (pending.length === 0) return coins;
20950
+ const resolved = await resolvePrevTxs(
20951
+ pending.map((c) => c.txid),
20952
+ source,
20953
+ onchain
20954
+ );
20955
+ return coins.map((c) => c.prevTx ? c : { ...c, prevTx: resolved.get(c.txid) });
20956
+ }
20957
+ function txidAt(txids, index) {
20958
+ const txid = txids[index];
20959
+ if (txid === void 0) {
20960
+ throw new PrevTxUnavailableError(`no source txid supplied for input ${index}`);
20961
+ }
20962
+ return txid;
20963
+ }
20235
20964
  var TAG_BIP322 = "BIP0322-signed-message";
20236
20965
  var BIP322;
20237
20966
  ((BIP3222) => {
@@ -20609,6 +21338,22 @@ async function resolveUnilateralPath(params) {
20609
21338
  return { selection: best.selection, label: "default:exit" };
20610
21339
  }
20611
21340
 
21341
+ // src/wallet/exitObserver.ts
21342
+ function exitObserverFor(manager) {
21343
+ return (outpoint) => manager.refreshOutpoints([outpoint]);
21344
+ }
21345
+ async function notifyExitObserved(onExitObserved, outpoint) {
21346
+ if (!onExitObserved) return;
21347
+ try {
21348
+ await onExitObserved(outpoint);
21349
+ } catch (e) {
21350
+ console.error(
21351
+ `exit observer failed for ${outpoint.txid}:${outpoint.vout} \u2014 the exit is unaffected`,
21352
+ e
21353
+ );
21354
+ }
21355
+ }
21356
+
20612
21357
  // src/wallet/unroll.ts
20613
21358
  function chainTxTypeToChainedExit2(t) {
20614
21359
  switch (t) {
@@ -20633,27 +21378,30 @@ var Unroll;
20633
21378
  })(Unroll2.StepType || (Unroll2.StepType = {}));
20634
21379
  class Session2 {
20635
21380
  /** Create an unroll session from a virtual output outpoint and its dependency chain. */
20636
- constructor(toUnroll, bumper, explorer, indexer, virtualTxRepository) {
21381
+ constructor(toUnroll, bumper, explorer, indexer, virtualTxRepository, onExitObserved) {
20637
21382
  this.toUnroll = toUnroll;
20638
21383
  this.bumper = bumper;
20639
21384
  this.explorer = explorer;
20640
21385
  this.indexer = indexer;
20641
21386
  this.virtualTxRepository = virtualTxRepository;
21387
+ this.onExitObserved = onExitObserved;
20642
21388
  }
20643
21389
  toUnroll;
20644
21390
  bumper;
20645
21391
  explorer;
20646
21392
  indexer;
20647
21393
  virtualTxRepository;
21394
+ onExitObserved;
20648
21395
  /** Create an unroll session by loading the virtual output chain from the indexer. */
20649
- static async create(toUnroll, bumper, explorer, indexer, virtualTxRepository) {
21396
+ static async create(toUnroll, bumper, explorer, indexer, virtualTxRepository, onExitObserved) {
20650
21397
  const { chain } = await indexer.getVtxoChain(toUnroll);
20651
21398
  return new Session2(
20652
21399
  { ...toUnroll, chain },
20653
21400
  bumper,
20654
21401
  explorer,
20655
21402
  indexer,
20656
- virtualTxRepository
21403
+ virtualTxRepository,
21404
+ onExitObserved
20657
21405
  );
20658
21406
  }
20659
21407
  /**
@@ -20689,6 +21437,14 @@ var Unroll;
20689
21437
  }
20690
21438
  /**
20691
21439
  * Get the next step to be executed
21440
+ *
21441
+ * @remarks
21442
+ * `do()` is the step, not a formality: every effect lives there, and at
21443
+ * `StepType.DONE` that includes firing {@link onExitObserved}. A caller
21444
+ * driving `next()` by hand must always await it — the async iterator
21445
+ * below does. Reading `type` and skipping `do()` leaves the exit
21446
+ * unobserved, and on the other step kinds unbroadcast.
21447
+ *
20692
21448
  * @returns The next step to be executed + the function to execute it
20693
21449
  */
20694
21450
  async next() {
@@ -20717,7 +21473,12 @@ var Unroll;
20717
21473
  return {
20718
21474
  type: 2 /* DONE */,
20719
21475
  vtxoTxid: this.toUnroll.txid,
20720
- do: () => Promise.resolve()
21476
+ // The observation rides on `do` rather than the iterator so
21477
+ // a caller driving `next()` by hand fires it too.
21478
+ do: () => notifyExitObserved(this.onExitObserved, {
21479
+ txid: this.toUnroll.txid,
21480
+ vout: this.toUnroll.vout
21481
+ })
20721
21482
  };
20722
21483
  }
20723
21484
  const virtualTxBase64 = await this.resolveVirtualTxBase64(nextTxToBroadcast);
@@ -20751,9 +21512,22 @@ var Unroll;
20751
21512
  }
20752
21513
  }
20753
21514
  Unroll2.Session = Session2;
21515
+ async function sessionFor(wallet, toUnroll, bumper) {
21516
+ const manager = await wallet.getContractManager();
21517
+ return Session2.create(
21518
+ toUnroll,
21519
+ bumper,
21520
+ wallet.onchainProvider,
21521
+ wallet.indexerProvider,
21522
+ wallet.virtualTxRepository,
21523
+ exitObserverFor(manager)
21524
+ );
21525
+ }
21526
+ Unroll2.sessionFor = sessionFor;
20754
21527
  async function completeUnroll(wallet, vtxoTxids, outputAddress) {
20755
21528
  const signedTx = await prepareUnrollTransaction(wallet, vtxoTxids, outputAddress);
20756
21529
  await wallet.onchainProvider.broadcastTransaction(signedTx.hex);
21530
+ await refreshSpentExitOutputs(wallet, signedTx);
20757
21531
  return signedTx.id;
20758
21532
  }
20759
21533
  Unroll2.completeUnroll = completeUnroll;
@@ -20769,9 +21543,9 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
20769
21543
  let totalAmount = 0n;
20770
21544
  const txWeightEstimator = TxWeightEstimator.create();
20771
21545
  for (const vtxo of vtxos) {
20772
- if (!vtxo.isUnrolled) {
21546
+ if (!canSweepOnchain(vtxo)) {
20773
21547
  throw new Error(
20774
- `Vtxo ${vtxo.txid}:${vtxo.vout} is not fully unrolled, use unroll first`
21548
+ `Vtxo ${vtxo.txid}:${vtxo.vout} cannot be swept onchain: it is not fully unrolled (use unroll first), or its exit output was already spent`
20775
21549
  );
20776
21550
  }
20777
21551
  const txStatus = await wallet.onchainProvider.getTxStatus(vtxo.txid);
@@ -20832,6 +21606,22 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
20832
21606
  signedTx.finalize();
20833
21607
  return signedTx;
20834
21608
  }
21609
+ async function refreshSpentExitOutputs(wallet, signedTx) {
21610
+ const outpoints = [];
21611
+ for (let i = 0; i < signedTx.inputsLength; i++) {
21612
+ const input = signedTx.getInput(i);
21613
+ if (input.txid && input.index !== void 0) {
21614
+ outpoints.push({ txid: hex.encode(input.txid), vout: input.index });
21615
+ }
21616
+ }
21617
+ if (outpoints.length === 0) return;
21618
+ try {
21619
+ const manager = await wallet.getContractManager();
21620
+ await manager.refreshOutpoints(outpoints);
21621
+ } catch (e) {
21622
+ console.error("completeUnroll: failed to refresh the spent exit outputs", e);
21623
+ }
21624
+ }
20835
21625
  function sleep2(ms) {
20836
21626
  return new Promise((resolve) => setTimeout(resolve, ms));
20837
21627
  }
@@ -20910,7 +21700,7 @@ async function buildSignedSweep(params) {
20910
21700
  }
20911
21701
 
20912
21702
  // src/wallet/exit/estimate.ts
20913
- var CHILD_OUTPUT_DUST = 546;
21703
+ var CHILD_OUTPUT_DUST = CHILD_DUST_AMOUNT;
20914
21704
  function stepFundingAmount(stepFee) {
20915
21705
  return stepFee + CHILD_OUTPUT_DUST;
20916
21706
  }
@@ -21071,10 +21861,18 @@ async function computeExitLayout(opts, feeRate) {
21071
21861
  // splitter. funded: + 1 splitter tx and pre-signed children.
21072
21862
  txCount: (graph ? 0 : steps.length > 0 ? 1 : 0) + steps.length * 2 + sweeps.length,
21073
21863
  totalFeeSats: splitterFee + stepFees + sweepFees,
21074
- // graph funding is what the executor sends to its own fee address:
21075
- // just the CPFP fees (change recycles); funded also locks the
21076
- // per-child dust into the splitter.
21077
- fundingRequiredSats: graph ? stepFees : splitterFee + fundingTotal,
21864
+ // graph funding is what the executor sends to its own fee address: the
21865
+ // CPFP fees, plus one dust floor. Change recycles through that single
21866
+ // address, so each bump hands its remainder to the next and only the
21867
+ // *final* change has to clear `CHILD_DUST_AMOUNT` — but it does have to,
21868
+ // and `buildAnchorChild` throws rather than absorbing a short remainder
21869
+ // into the fee. Quoting bare fees quotes an amount that strands the last
21870
+ // bump and every sweep behind it.
21871
+ //
21872
+ // funded reserves the same floor per step instead of once overall (see
21873
+ // `stepFundingAmount`), because it pre-funds each child separately
21874
+ // through the splitter rather than recycling one output.
21875
+ fundingRequiredSats: graph ? steps.length > 0 ? stepFees + CHILD_OUTPUT_DUST : 0 : splitterFee + fundingTotal,
21078
21876
  recoveredSats: sweeps.reduce((s, x) => s + (x.vtxo.value - x.sweepFee), 0)
21079
21877
  };
21080
21878
  const expiries = pendingNodes.map((n) => n.expiresAt).filter((x) => x !== void 0);
@@ -21134,12 +21932,32 @@ var Executor = class {
21134
21932
  this.pollIntervalMs = opts?.pollIntervalMs ?? 5e3;
21135
21933
  this.feeWallet = opts?.feeWallet;
21136
21934
  this.signal = opts?.signal;
21935
+ this.onExitObserved = opts?.onExitObserved;
21137
21936
  }
21138
21937
  pkg;
21139
21938
  provider;
21140
21939
  pollIntervalMs;
21141
21940
  feeWallet;
21142
21941
  signal;
21942
+ onExitObserved;
21943
+ /**
21944
+ * Per-VTXO branch progress, which the executor otherwise does not track:
21945
+ * `dead` / `done` are per-step, and a VTXO is only onchain once EVERY
21946
+ * `package`/`bump` step naming it has confirmed. (`broadcast` — the funding
21947
+ * splitter — carries no `forVtxos` and serves no single VTXO.)
21948
+ */
21949
+ branchSteps() {
21950
+ const byVtxo = /* @__PURE__ */ new Map();
21951
+ this.pkg.steps.forEach((step, i) => {
21952
+ if (step.kind !== "package" && step.kind !== "bump") return;
21953
+ for (const vtxo of step.forVtxos) {
21954
+ let steps = byVtxo.get(vtxo);
21955
+ if (!steps) byVtxo.set(vtxo, steps = /* @__PURE__ */ new Set());
21956
+ steps.add(i);
21957
+ }
21958
+ });
21959
+ return byVtxo;
21960
+ }
21143
21961
  /**
21144
21962
  * Poll delay. Nearly all wall-clock time is spent here, so it must be the
21145
21963
  * thing that reacts to abort — checking only between polls would leave
@@ -21179,6 +21997,19 @@ var Executor = class {
21179
21997
  async *[Symbol.asyncIterator]() {
21180
21998
  throwIfAborted(this.signal);
21181
21999
  const dead = /* @__PURE__ */ new Set();
22000
+ const branchSteps = this.branchSteps();
22001
+ const onchainSteps = /* @__PURE__ */ new Set();
22002
+ const branchObserved = /* @__PURE__ */ new Set();
22003
+ const observeBranch = async (stepIndex, forVtxos) => {
22004
+ onchainSteps.add(stepIndex);
22005
+ for (const vtxo of forVtxos ?? []) {
22006
+ if (branchObserved.has(vtxo)) continue;
22007
+ const steps = branchSteps.get(vtxo);
22008
+ if (!steps || ![...steps].every((i) => onchainSteps.has(i))) continue;
22009
+ branchObserved.add(vtxo);
22010
+ await this.observe(vtxo);
22011
+ }
22012
+ };
21182
22013
  if (this.pkg.validUntil && Date.now() / 1e3 > this.pkg.validUntil) {
21183
22014
  yield {
21184
22015
  stepIndex: -1,
@@ -21208,6 +22039,7 @@ var Executor = class {
21208
22039
  const anchorTxid = step.kind === "package" || step.kind === "bump" ? step.parentTxid : step.txid;
21209
22040
  const existing = await this.status(anchorTxid);
21210
22041
  if (existing?.confirmed) {
22042
+ await observeBranch(i, forVtxos);
21211
22043
  yield {
21212
22044
  stepIndex: i,
21213
22045
  kind: step.kind,
@@ -21263,6 +22095,7 @@ var Executor = class {
21263
22095
  }
21264
22096
  }
21265
22097
  await this.waitConfirmed(anchorTxid);
22098
+ await observeBranch(i, forVtxos);
21266
22099
  yield {
21267
22100
  stepIndex: i,
21268
22101
  kind: step.kind,
@@ -21293,6 +22126,7 @@ var Executor = class {
21293
22126
  const swept = await this.status(step.txid);
21294
22127
  if (swept?.confirmed) {
21295
22128
  done.add(index);
22129
+ await this.observe(step.vtxo);
21296
22130
  yield {
21297
22131
  stepIndex: index,
21298
22132
  kind: "sweep",
@@ -21349,7 +22183,25 @@ var Executor = class {
21349
22183
  if (done.size < pending.length) await this.sleep();
21350
22184
  }
21351
22185
  }
22186
+ /** `"txid:vout"` -> outpoint, then hand it to the hook. A malformed entry is skipped. */
22187
+ async observe(outpoint) {
22188
+ if (!this.onExitObserved) return;
22189
+ const sep = outpoint.lastIndexOf(":");
22190
+ const rest = outpoint.slice(sep + 1);
22191
+ const vout = Number(rest);
22192
+ if (sep <= 0 || rest === "" || !Number.isInteger(vout)) return;
22193
+ await notifyExitObserved(this.onExitObserved, { txid: outpoint.slice(0, sep), vout });
22194
+ }
21352
22195
  };
22196
+
22197
+ // src/wallet/exit/execute.ts
22198
+ async function execute(wallet, pkg, opts) {
22199
+ const manager = await wallet.getContractManager();
22200
+ return new Executor(pkg, opts?.provider ?? wallet.onchainProvider, {
22201
+ ...opts,
22202
+ onExitObserved: opts?.onExitObserved ?? exitObserverFor(manager)
22203
+ });
22204
+ }
21353
22205
  async function prepare(opts) {
21354
22206
  const { wallet, onchainWallet } = opts;
21355
22207
  const feeRate = await resolveFeeRate(opts);
@@ -21424,7 +22276,10 @@ async function prepare(opts) {
21424
22276
  totals: {
21425
22277
  txCount: bumpSteps.length * 2 + sweepSteps.length,
21426
22278
  totalFeeSats: stepFees2 + sweepFees2,
21427
- fundingRequiredSats: stepFees2,
22279
+ // Fees plus the single dust floor the recycling change output
22280
+ // must keep clear; see the note in `estimate.ts`. Must match
22281
+ // the quote `estimate` gives for the same package.
22282
+ fundingRequiredSats: bumpSteps.length > 0 ? stepFees2 + CHILD_OUTPUT_DUST : 0,
21428
22283
  recoveredSats: recovered2
21429
22284
  },
21430
22285
  vtxos: layout.infos,
@@ -21593,6 +22448,7 @@ function isValidStep(step) {
21593
22448
  var UnilateralExit = {
21594
22449
  estimate,
21595
22450
  prepare,
22451
+ execute,
21596
22452
  Executor
21597
22453
  };
21598
22454
 
@@ -22010,6 +22866,10 @@ var ArkadeContract = class _ArkadeContract {
22010
22866
  * When a `contractManager` is configured and this contract is registered,
22011
22867
  * reads the repository-backed state (offline-first, kept fresh by the
22012
22868
  * contract watcher). Otherwise falls back to a direct indexer query.
22869
+ *
22870
+ * Both branches refuse a spent or unilaterally exited output. They are not
22871
+ * otherwise interchangeable: the fallback asks `spendableOnly`, which also
22872
+ * drops swept coins — the manager branch keeps those.
22013
22873
  */
22014
22874
  async getUtxos() {
22015
22875
  const manager = this.client.contractManager;
@@ -22018,7 +22878,9 @@ var ArkadeContract = class _ArkadeContract {
22018
22878
  const [registered] = await manager.getContracts({ script: scriptHex });
22019
22879
  if (registered) {
22020
22880
  const [withVtxos] = await manager.getContractsWithVtxos({ script: scriptHex });
22021
- return (withVtxos?.vtxos ?? []).filter((v) => !hasTerminalSpend(v));
22881
+ return (withVtxos?.vtxos ?? []).filter(
22882
+ (v) => !hasTerminalSpend(v) && !v.isUnrolled
22883
+ );
22022
22884
  }
22023
22885
  }
22024
22886
  if (!this.client.indexer) {
@@ -22028,7 +22890,7 @@ var ArkadeContract = class _ArkadeContract {
22028
22890
  scripts: [scriptHex],
22029
22891
  spendableOnly: true
22030
22892
  });
22031
- return vtxos;
22893
+ return vtxos.filter((v) => !hasTerminalSpend(v) && !v.isUnrolled);
22032
22894
  }
22033
22895
  /** Total spendable balance (requires an indexer). */
22034
22896
  async getBalance() {
@@ -22128,6 +22990,19 @@ var ArkadeTransactionBuilder = class {
22128
22990
  if (coin.sourceTx) {
22129
22991
  setArkPsbtField(arkTx, 0, PrevArkTxField, coin.sourceTx);
22130
22992
  }
22993
+ if (this.fn.arkadeScript) {
22994
+ const indexer = this.contract.client.indexer;
22995
+ if (!indexer) {
22996
+ throw new PrevTxUnavailableError(
22997
+ "covenant spends require an `indexer` on the Arkade client to resolve the previous ark tx of each input"
22998
+ );
22999
+ }
23000
+ await attachPrevArkTxs(
23001
+ arkTx,
23002
+ [coin.txid, ...this.fundingCoins.map((c) => c.txid)],
23003
+ indexer
23004
+ );
23005
+ }
22131
23006
  const condition = (def.tapscript.witness ?? []).map((w) => this.witnessBytes(w));
22132
23007
  if (condition.length > 0) {
22133
23008
  setArkPsbtField(arkTx, 0, ConditionWitness, condition);
@@ -22633,6 +23508,6 @@ function createDefaultPaymentRouter(wallet) {
22633
23508
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
22634
23509
  */
22635
23510
 
22636
- export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, ChainedTxType, ConnectionDisposedError, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, ForeignDescriptorError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, PaymentRouter, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, adoptContractDescriptor, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertIntentIdUnique, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, gateExclusion, gatedContracts, getAllByIndexValues, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, isWatchedContract, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, onchainRail, openDatabase, outpointExclusion, outpointReasons, promisifyRequest, provisionClaimSecret, provisionRefundKey, requiresMigration, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, toXOnlySignerHex, tryResolveSendAmount, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
22637
- //# sourceMappingURL=chunk-DSC3G6QZ.js.map
22638
- //# sourceMappingURL=chunk-DSC3G6QZ.js.map
23511
+ export { ARKADE_MAGIC, ARKADE_SALTED_PREIMAGE_TAG, ARKADE_SWAP_PREIMAGE_TAG, ActivityRegistry, AmbiguousRouteError, ArkNote, ArkadeCash, ArkadeCashCreateError, AssetManager, BIP21, BIP21Error, BIP322, BLOCKED_UPGRADE_TIMEOUT_MS, Batch, CachingArkProvider, ChainedTxType, ConnectionDisposedError, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DEFAULT_MIN_BATCH_EXPIRY_SECONDS, DEFAULT_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, DatabaseUpgradeBlockedError, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorIdentity, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EmulatorPacket, EsploraProvider, Estimator, Extension, ExtensionNotFoundError, ForeignDescriptorError, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MAX_USED_SIGNING_DESCRIPTORS_LOOK_AHEAD, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MUTINYNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, PaymentRouter, PrevTxUnavailableError, REGTEST_MIN_BATCH_EXPIRY_SECONDS, REGTEST_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, RestEmulatorProvider, SIGNET_MIN_CHECKPOINT_EXIT_DELAY_SECONDS, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnannotatableInputError, UnilateralExit, UnknownPacket, Unroll, VtxoManager, Wallet2 as Wallet, WalletCannotAllocateAddressError, WalletCannotSignError, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, adoptContractDescriptor, arkRail, arkTarget, arkade_exports, assertFinalCommitmentMatchesValidated, assertIntentIdUnique, assertSendableAmount, assertSubmittedArkTxid, assertValidBatchExpiry, assertValidServerUnrollScript, assetMintResolver, attachPrevArkTxs, attachPrevoutTxs, awaitTransaction, boardingResolver, btcTarget, buildForfeitTx, buildOffchainTx, buildPreimageMessage, buildSaltedPreimageMessage, claimWithPreimageIdentity, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, collabExitResolver, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, contractPreimage, contractSigner, createAssetPacket, createDefaultActivityRegistry, createDefaultPaymentRouter, createExitChainResolver, createManagedConnection, decodeArkContract, defaultBatchExpiryPolicy, defaultCheckpointExitDelayPolicy, deleteByIndex, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, exitObserverFor, extendVirtualCoinForContract, extractArkProviderUrl, gateExclusion, gatedContracts, getAllByIndexValues, getMigrationStatus, getRandomId, hasBoardingTxExpired, hasCandidates, intentMatchesFilter, intentPageBounds, invoiceTarget, isAddressAllocationCapable, isArkContract, isBatchSignable, isBtcAddress, isContractGenericallySpendable, isCooperativelyMigratable, isDeterministicSigner, isDiscoverable, isHDAllocationCapable, isHDDeterministicSignCapable, isHDWalletCapable, isLightningInvoice, isLnurl, isMessageBusInitializingError, isMessageBusNotInitializedError, isPerArtifactDescriptor, isSigningIdentity, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, isWatchedContract, logExcludedVtxos, makeHandle, matchServerCheckpoints, mergeChainedTxType, migrateWalletRepository, notifyExitObserved, onchainRail, openDatabase, outpointExclusion, outpointReasons, promisifyRequest, provisionClaimSecret, provisionRefundKey, requiresMigration, resolveBatchExpiryPolicy, resolveCheckpointExitDelayPolicy, resolveDescriptorSigner, resolvePrevTxs, resolveSendAmount, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, selectCoinsWithAsset, selectVirtualCoins, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signAndSubmitOffchainTx, signerSetFromInfo, signingDescriptorIndex, strictSigningDescriptorIndex, toXOnlySignerHex, tryResolveSendAmount, validateBatchRecipients, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript, withPrevTxs };
23512
+ //# sourceMappingURL=chunk-VBHXBI7F.js.map
23513
+ //# sourceMappingURL=chunk-VBHXBI7F.js.map