@arkade-os/sdk 0.4.44 → 0.4.46

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 (88) hide show
  1. package/README.md +35 -0
  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/{ark-I8jfe5VE.d.cts → ark-C9ognsOV.d.cts} +94 -2
  9. package/dist/{ark-I8jfe5VE.d.ts → ark-C9ognsOV.d.ts} +94 -2
  10. package/dist/{asyncStorageTaskQueue-BtSOVZFn.d.ts → asyncStorageTaskQueue-BAKZ5cS5.d.ts} +2 -2
  11. package/dist/{asyncStorageTaskQueue-Dap7qUlF.d.cts → asyncStorageTaskQueue-CmBYVlxw.d.cts} +2 -2
  12. package/dist/{chunk-32YKLQPS.cjs → chunk-4KXOCVXP.cjs} +32 -29
  13. package/dist/chunk-4KXOCVXP.cjs.map +1 -0
  14. package/dist/{chunk-RJ6XVCX4.cjs → chunk-6TQJOD72.cjs} +7 -7
  15. package/dist/{chunk-RJ6XVCX4.cjs.map → chunk-6TQJOD72.cjs.map} +1 -1
  16. package/dist/{chunk-UBYY2TPK.cjs → chunk-7IYIFMF4.cjs} +178 -79
  17. package/dist/chunk-7IYIFMF4.cjs.map +1 -0
  18. package/dist/{chunk-LJ35HPJW.js → chunk-CWJ7ZCP7.js} +171 -75
  19. package/dist/chunk-CWJ7ZCP7.js.map +1 -0
  20. package/dist/{chunk-XIVCG6EP.cjs → chunk-D46YDM36.cjs} +1173 -425
  21. package/dist/chunk-D46YDM36.cjs.map +1 -0
  22. package/dist/{chunk-BE5MUB7T.js → chunk-FWG4TSPN.js} +7 -2
  23. package/dist/chunk-FWG4TSPN.js.map +1 -0
  24. package/dist/{chunk-V7WIZR2B.js → chunk-JTCQ6QXQ.js} +4 -4
  25. package/dist/{chunk-V7WIZR2B.js.map → chunk-JTCQ6QXQ.js.map} +1 -1
  26. package/dist/{chunk-HXDZ2QTW.cjs → chunk-MQB5H4X5.cjs} +7 -2
  27. package/dist/chunk-MQB5H4X5.cjs.map +1 -0
  28. package/dist/{chunk-FMI2GKYO.js → chunk-P6VBP3CT.js} +977 -231
  29. package/dist/chunk-P6VBP3CT.js.map +1 -0
  30. package/dist/{chunk-YNEDJTX2.js → chunk-QKC2T54O.js} +6 -3
  31. package/dist/chunk-QKC2T54O.js.map +1 -0
  32. package/dist/{chunk-LCQQPOIT.js → chunk-R2SVAM5Q.js} +3 -3
  33. package/dist/{chunk-LCQQPOIT.js.map → chunk-R2SVAM5Q.js.map} +1 -1
  34. package/dist/{chunk-X37EKDIH.cjs → chunk-SOXGAA52.cjs} +14 -14
  35. package/dist/{chunk-X37EKDIH.cjs.map → chunk-SOXGAA52.cjs.map} +1 -1
  36. package/dist/contracts/handlers/index.cjs +7 -7
  37. package/dist/contracts/handlers/index.d.cts +3 -3
  38. package/dist/contracts/handlers/index.d.ts +3 -3
  39. package/dist/contracts/handlers/index.js +2 -2
  40. package/dist/{delegate-BRqbh2fW.d.cts → delegate-DVEgyRwI.d.cts} +1 -1
  41. package/dist/{delegate-CgjVjYOP.d.ts → delegate-aOd5qINr.d.ts} +1 -1
  42. package/dist/{index-DvWNB_rl.d.ts → index-CPzL4tqg.d.ts} +2 -2
  43. package/dist/{index-DHuXlDEg.d.cts → index-D8vmtWWO.d.cts} +2 -2
  44. package/dist/index.cjs +176 -156
  45. package/dist/index.d.cts +45 -20
  46. package/dist/index.d.ts +45 -20
  47. package/dist/index.js +4 -4
  48. package/dist/repositories/realm/index.cjs +23 -19
  49. package/dist/repositories/realm/index.cjs.map +1 -1
  50. package/dist/repositories/realm/index.d.cts +2 -2
  51. package/dist/repositories/realm/index.d.ts +2 -2
  52. package/dist/repositories/realm/index.js +9 -5
  53. package/dist/repositories/realm/index.js.map +1 -1
  54. package/dist/repositories/sqlite/index.cjs +22 -18
  55. package/dist/repositories/sqlite/index.cjs.map +1 -1
  56. package/dist/repositories/sqlite/index.d.cts +2 -2
  57. package/dist/repositories/sqlite/index.d.ts +2 -2
  58. package/dist/repositories/sqlite/index.js +9 -5
  59. package/dist/repositories/sqlite/index.js.map +1 -1
  60. package/dist/{taskRunner-Bptw9UMa.d.ts → taskRunner-B48-V-Pb.d.ts} +2 -2
  61. package/dist/{taskRunner-CeFxKhSH.d.cts → taskRunner-nNecmQ2C.d.cts} +2 -2
  62. package/dist/wallet/expo/background.cjs +14 -14
  63. package/dist/wallet/expo/background.d.cts +3 -3
  64. package/dist/wallet/expo/background.d.ts +3 -3
  65. package/dist/wallet/expo/background.js +6 -6
  66. package/dist/wallet/expo/index.cjs +27 -14
  67. package/dist/wallet/expo/index.cjs.map +1 -1
  68. package/dist/wallet/expo/index.d.cts +11 -4
  69. package/dist/wallet/expo/index.d.ts +11 -4
  70. package/dist/wallet/expo/index.js +18 -5
  71. package/dist/wallet/expo/index.js.map +1 -1
  72. package/dist/{wallet-BsqRU8KM.d.cts → wallet-BUg3D4WJ.d.cts} +139 -3
  73. package/dist/{wallet-D9z7wssl.d.ts → wallet-DM0oShTu.d.ts} +139 -3
  74. package/dist/worker/expo/index.cjs +10 -9
  75. package/dist/worker/expo/index.cjs.map +1 -1
  76. package/dist/worker/expo/index.d.cts +4 -4
  77. package/dist/worker/expo/index.d.ts +4 -4
  78. package/dist/worker/expo/index.js +6 -5
  79. package/dist/worker/expo/index.js.map +1 -1
  80. package/package.json +2 -2
  81. package/dist/chunk-32YKLQPS.cjs.map +0 -1
  82. package/dist/chunk-BE5MUB7T.js.map +0 -1
  83. package/dist/chunk-FMI2GKYO.js.map +0 -1
  84. package/dist/chunk-HXDZ2QTW.cjs.map +0 -1
  85. package/dist/chunk-LJ35HPJW.js.map +0 -1
  86. package/dist/chunk-UBYY2TPK.cjs.map +0 -1
  87. package/dist/chunk-XIVCG6EP.cjs.map +0 -1
  88. package/dist/chunk-YNEDJTX2.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { craftToSpendTx, Transaction, OP_RETURN_EMPTY_PKSCRIPT, baseFetch, Intent, getArkPsbtFields, CosignerPublicKey, setArkPsbtField, VtxoTaprootTree, maybeArkError, AssetRef, AssetId, AssetOutput, AssetGroup, AssetInput, Packet, Metadata, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError, BufferReader } from './chunk-LJ35HPJW.js';
2
- import { contractHandlers, isMainnetDescriptor, descriptorIsOurs, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, DefaultVtxo, BoardingContractHandler } from './chunk-YNEDJTX2.js';
3
- import { VtxoScript, sequenceToTimelock, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress, CSVMultisigTapscript, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-BE5MUB7T.js';
1
+ import { craftToSpendTx, Transaction, OP_RETURN_EMPTY_PKSCRIPT, baseFetch, Intent, getArkPsbtFields, CosignerPublicKey, setArkPsbtField, VtxoTaprootTree, maybeArkError, isArkError, ArkErrorName, ProviderUnavailableError, FetchError, isFetchTimeoutError, AssetRef, AssetId, AssetOutput, AssetGroup, AssetInput, Packet, Metadata, isEventSourceError, RestArkProvider, RestIndexerProvider, ArkError, BufferReader } from './chunk-CWJ7ZCP7.js';
2
+ import { contractHandlers, isMainnetDescriptor, descriptorIsOurs, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, DefaultVtxo, BoardingContractHandler } from './chunk-QKC2T54O.js';
3
+ import { VtxoScript, sequenceToTimelock, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress, CSVMultisigTapscript, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-FWG4TSPN.js';
4
4
  import { sha256, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr, equalBytes as equalBytes$1 } from '@scure/btc-signer/utils.js';
5
5
  import { SigHash, Script, p2tr, RawWitness, Address, OutScript, p2wpkh, TaprootControlBlock, DEFAULT_SEQUENCE, Transaction as Transaction$2 } from '@scure/btc-signer';
6
6
  import { hex, base58, base64 } from '@scure/base';
@@ -62,6 +62,8 @@ var PartialSig = class _PartialSig {
62
62
  throw new PartialSignatureError("Invalid R length");
63
63
  }
64
64
  }
65
+ s;
66
+ R;
65
67
  /**
66
68
  * Encodes the partial signature into bytes
67
69
  * Returns a 32-byte array containing just the s value
@@ -111,6 +113,7 @@ var TreeSignerSession = class _TreeSignerSession {
111
113
  constructor(secretKey) {
112
114
  this.secretKey = secretKey;
113
115
  }
116
+ secretKey;
114
117
  static NOT_INITIALIZED = new Error("session not initialized, call init method");
115
118
  myNonces = null;
116
119
  aggregateNonces = null;
@@ -337,6 +340,7 @@ var ReadonlySingleKey = class _ReadonlySingleKey {
337
340
  throw new Error("Invalid public key length");
338
341
  }
339
342
  }
343
+ publicKey;
340
344
  /**
341
345
  * Create a ReadonlySingleKey from a compressed public key.
342
346
  *
@@ -948,6 +952,7 @@ var RestDelegateProvider = class {
948
952
  constructor(url) {
949
953
  this.url = url;
950
954
  }
955
+ url;
951
956
  /**
952
957
  * Submit a delegation request to the remote delegation service.
953
958
  *
@@ -1017,6 +1022,7 @@ var EsploraProvider = class {
1017
1022
  this.pollingInterval = opts?.pollingInterval ?? 15e3;
1018
1023
  this.forcePolling = opts?.forcePolling ?? false;
1019
1024
  }
1025
+ baseUrl;
1020
1026
  pollingInterval;
1021
1027
  forcePolling;
1022
1028
  async getCoins(address) {
@@ -1496,6 +1502,8 @@ var UnknownPacket = class {
1496
1502
  this.packetType = packetType;
1497
1503
  this.data = data;
1498
1504
  }
1505
+ packetType;
1506
+ data;
1499
1507
  type() {
1500
1508
  return this.packetType;
1501
1509
  }
@@ -1516,6 +1524,7 @@ var Extension = class _Extension {
1516
1524
  constructor(packets) {
1517
1525
  this.packets = packets;
1518
1526
  }
1527
+ packets;
1519
1528
  static create(packets) {
1520
1529
  if (packets.length === 0) {
1521
1530
  throw new Error("missing packets");
@@ -1954,6 +1963,7 @@ function isExpired(vtxo) {
1954
1963
  return expiry <= Date.now();
1955
1964
  }
1956
1965
  function isSubdust(vtxo, dust) {
1966
+ if (typeof vtxo === "bigint") return vtxo < dust;
1957
1967
  return vtxo.value < dust;
1958
1968
  }
1959
1969
 
@@ -2312,6 +2322,7 @@ var FeeAmount = class _FeeAmount {
2312
2322
  constructor(value) {
2313
2323
  this.value = value;
2314
2324
  }
2325
+ value;
2315
2326
  static ZERO = new _FeeAmount(0);
2316
2327
  /** Returns the fee amount rounded up to whole satoshis. */
2317
2328
  get satoshis() {
@@ -2336,6 +2347,7 @@ var Estimator = class {
2336
2347
  this.intentOffchainOutput = config.offchainOutput ? parseProgram(config.offchainOutput, IntentOutputEnv) : void 0;
2337
2348
  this.intentOnchainOutput = config.onchainOutput ? parseProgram(config.onchainOutput, IntentOutputEnv) : void 0;
2338
2349
  }
2350
+ config;
2339
2351
  intentOffchainInput;
2340
2352
  intentOnchainInput;
2341
2353
  intentOffchainOutput;
@@ -2738,6 +2750,8 @@ var VtxoManager = class _VtxoManager {
2738
2750
  }
2739
2751
  this.contractEventsSubscriptionReady = this.initializeSubscription();
2740
2752
  }
2753
+ wallet;
2754
+ renewalConfig;
2741
2755
  settlementConfig;
2742
2756
  contractEventsSubscriptionReady;
2743
2757
  disposePromise;
@@ -3775,7 +3789,7 @@ var VtxoManager = class _VtxoManager {
3775
3789
  */
3776
3790
  extractSpentOutpoint(error) {
3777
3791
  const ark = maybeArkError(error);
3778
- if (!ark || ark.name !== "VTXO_ALREADY_SPENT") return void 0;
3792
+ if (!isArkError(ark, ArkErrorName.VTXO_ALREADY_SPENT)) return void 0;
3779
3793
  const raw = ark.metadata?.vtxo_outpoint;
3780
3794
  if (typeof raw !== "string") return void 0;
3781
3795
  const [txid, voutStr] = raw.split(":");
@@ -4079,6 +4093,9 @@ var ArkNote = class _ArkNote {
4079
4093
  this.status = { confirmed: true };
4080
4094
  this.extraWitness = [this.preimage];
4081
4095
  }
4096
+ preimage;
4097
+ value;
4098
+ HRP;
4082
4099
  static DefaultHRP = "arknote";
4083
4100
  static PreimageLength = 32;
4084
4101
  // 32 bytes for the preimage
@@ -4287,11 +4304,603 @@ function wrapHandlerWithIntentPersistence(base, deps) {
4287
4304
  }
4288
4305
  };
4289
4306
  }
4307
+
4308
+ // src/repositories/virtualTxRepository.ts
4309
+ var ChainedTxType = /* @__PURE__ */ ((ChainedTxType2) => {
4310
+ ChainedTxType2[ChainedTxType2["Unspecified"] = 0] = "Unspecified";
4311
+ ChainedTxType2[ChainedTxType2["Commitment"] = 1] = "Commitment";
4312
+ ChainedTxType2[ChainedTxType2["Ark"] = 2] = "Ark";
4313
+ ChainedTxType2[ChainedTxType2["Tree"] = 3] = "Tree";
4314
+ ChainedTxType2[ChainedTxType2["Checkpoint"] = 4] = "Checkpoint";
4315
+ return ChainedTxType2;
4316
+ })(ChainedTxType || {});
4317
+ function mergeChainedTxType(incoming, prev) {
4318
+ if (incoming !== 0 /* Unspecified */) return incoming;
4319
+ return prev ?? 0 /* Unspecified */;
4320
+ }
4321
+
4322
+ // src/wallet/exit/chain.ts
4323
+ var isCommitment = (t) => t === "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */ || t === "INDEXER_CHAINED_TX_TYPE_UNSPECIFIED" /* UNSPECIFIED */;
4324
+ async function buildExitDag(params) {
4325
+ const byTxid = /* @__PURE__ */ new Map();
4326
+ for (const vtxo of params.vtxos) {
4327
+ const outpoint = `${vtxo.txid}:${vtxo.vout}`;
4328
+ const chain = await params.chain.getVtxoChain({ txid: vtxo.txid, vout: vtxo.vout });
4329
+ for (const chainTx of chain) {
4330
+ const existing = byTxid.get(chainTx.txid);
4331
+ if (existing) {
4332
+ existing.forVtxos.add(outpoint);
4333
+ } else {
4334
+ byTxid.set(chainTx.txid, { ...chainTx, forVtxos: /* @__PURE__ */ new Set([outpoint]) });
4335
+ }
4336
+ }
4337
+ }
4338
+ const pending = new Map(
4339
+ [...byTxid.values()].filter((tx) => !isCommitment(tx.type)).map((tx) => [tx.txid, tx])
4340
+ );
4341
+ const inDegree = /* @__PURE__ */ new Map();
4342
+ for (const tx of pending.values()) {
4343
+ const parents = tx.spends.filter((p) => pending.has(p));
4344
+ inDegree.set(tx.txid, parents.length);
4345
+ }
4346
+ const order = [];
4347
+ const queue = [...pending.values()].filter((tx) => inDegree.get(tx.txid) === 0).sort((a, b) => a.txid.localeCompare(b.txid));
4348
+ while (queue.length > 0) {
4349
+ const tx = queue.shift();
4350
+ order.push(tx);
4351
+ const children = [...pending.values()].filter((c) => c.spends.includes(tx.txid)).sort((a, b) => a.txid.localeCompare(b.txid));
4352
+ for (const child of children) {
4353
+ const deg = inDegree.get(child.txid) - 1;
4354
+ inDegree.set(child.txid, deg);
4355
+ if (deg === 0) queue.push(child);
4356
+ }
4357
+ }
4358
+ if (order.length !== pending.size) {
4359
+ throw new Error("inconsistent vtxo chain from indexer (cycle detected)");
4360
+ }
4361
+ const nodes = [];
4362
+ for (const tx of order) {
4363
+ let confirmed = false;
4364
+ try {
4365
+ const status = await params.onchain.getTxStatus(tx.txid);
4366
+ confirmed = status.confirmed;
4367
+ } catch {
4368
+ confirmed = false;
4369
+ }
4370
+ const expires = Number(tx.expiresAt);
4371
+ nodes.push({
4372
+ txid: tx.txid,
4373
+ type: tx.type,
4374
+ expiresAt: Number.isFinite(expires) && expires > 0 ? expires : void 0,
4375
+ forVtxos: [...tx.forVtxos].sort(),
4376
+ confirmed
4377
+ });
4378
+ }
4379
+ return nodes;
4380
+ }
4381
+ function topoSortByDeps(items, idOf, depsOf) {
4382
+ const produced = new Set(items.map(idOf));
4383
+ const emitted = /* @__PURE__ */ new Set();
4384
+ const ordered = [];
4385
+ let remaining = items;
4386
+ while (remaining.length > 0) {
4387
+ const next = [];
4388
+ let progress = false;
4389
+ for (const item of remaining) {
4390
+ if (depsOf(item).every((d) => !produced.has(d) || emitted.has(d))) {
4391
+ ordered.push(item);
4392
+ emitted.add(idOf(item));
4393
+ progress = true;
4394
+ } else {
4395
+ next.push(item);
4396
+ }
4397
+ }
4398
+ if (!progress) {
4399
+ throw new Error("topological sort: cycle or unsatisfiable dependency");
4400
+ }
4401
+ remaining = next;
4402
+ }
4403
+ return ordered;
4404
+ }
4405
+
4406
+ // src/wallet/exit/capture.ts
4407
+ var DEFAULT_MIN_EXIT_WORTH_SATS = 1e3;
4408
+ var DEFAULT_EXIT_CAPTURE_MODE = "lite";
4409
+ function chainTxTypeToChainedExit(t) {
4410
+ switch (t) {
4411
+ case "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */:
4412
+ return 1 /* Commitment */;
4413
+ case "INDEXER_CHAINED_TX_TYPE_ARK" /* ARK */:
4414
+ return 2 /* Ark */;
4415
+ case "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */:
4416
+ return 3 /* Tree */;
4417
+ case "INDEXER_CHAINED_TX_TYPE_CHECKPOINT" /* CHECKPOINT */:
4418
+ return 4 /* Checkpoint */;
4419
+ default:
4420
+ return 0 /* Unspecified */;
4421
+ }
4422
+ }
4423
+ function orderAncestryFirst(chain) {
4424
+ const ids = new Set(chain.map((c) => c.txid));
4425
+ return topoSortByDeps(
4426
+ chain,
4427
+ (c) => c.txid,
4428
+ (c) => c.spends.filter((s) => ids.has(s))
4429
+ );
4430
+ }
4431
+ async function captureExitBranch(params) {
4432
+ const { resolver, repository, vtxo, value, mode, minExitWorthSats } = params;
4433
+ if (value < minExitWorthSats) return;
4434
+ if (await repository.hasBranch(vtxo)) return;
4435
+ const ordered = orderAncestryFirst(await resolver.getVtxoChain(vtxo));
4436
+ const psbtByTxid = /* @__PURE__ */ new Map();
4437
+ if (mode === "full") {
4438
+ const nonCommitment = ordered.filter((c) => chainTxTypeToChainedExit(c.type) !== 1 /* Commitment */).map((c) => c.txid);
4439
+ if (nonCommitment.length > 0) {
4440
+ for (const psbt of await resolver.getVirtualTxs(nonCommitment)) {
4441
+ psbtByTxid.set(Transaction.fromPSBT(base64.decode(psbt)).id, psbt);
4442
+ }
4443
+ }
4444
+ }
4445
+ const virtualTxs = ordered.map((c) => {
4446
+ const expires = Number(c.expiresAt);
4447
+ return {
4448
+ txid: c.txid,
4449
+ psbt: psbtByTxid.get(c.txid) ?? null,
4450
+ expiresAt: Number.isFinite(expires) && expires > 0 ? expires : null,
4451
+ type: chainTxTypeToChainedExit(c.type)
4452
+ };
4453
+ });
4454
+ const branch = ordered.map((c, i) => ({
4455
+ vtxoTxid: vtxo.txid,
4456
+ vtxoVout: vtxo.vout,
4457
+ virtualTxid: c.txid,
4458
+ position: i
4459
+ }));
4460
+ await repository.upsertVirtualTxs(virtualTxs);
4461
+ await repository.setBranch(vtxo, branch);
4462
+ }
4463
+ async function pruneExitBranches(repository, outpoints) {
4464
+ for (const outpoint of outpoints) {
4465
+ try {
4466
+ await repository.pruneForSpentVtxo(outpoint);
4467
+ } catch {
4468
+ }
4469
+ }
4470
+ }
4471
+ var IndexerExitDataSource = class {
4472
+ constructor(indexer) {
4473
+ this.indexer = indexer;
4474
+ }
4475
+ indexer;
4476
+ name = "indexer";
4477
+ async getVtxoChain(vtxo) {
4478
+ const chain = [];
4479
+ let pageIndex = 0;
4480
+ let hasMore = true;
4481
+ while (hasMore) {
4482
+ const { chain: page, page: meta } = await this.indexer.getVtxoChain(
4483
+ { txid: vtxo.txid, vout: vtxo.vout },
4484
+ { pageIndex, pageSize: DEFAULT_PAGE_SIZE }
4485
+ );
4486
+ chain.push(...page);
4487
+ hasMore = meta ? page.length === DEFAULT_PAGE_SIZE : false;
4488
+ pageIndex++;
4489
+ }
4490
+ return chain;
4491
+ }
4492
+ async getVirtualTxs(txids) {
4493
+ const out = /* @__PURE__ */ new Map();
4494
+ let pageIndex = 0;
4495
+ let hasMore = true;
4496
+ while (hasMore) {
4497
+ const { txs, page } = await this.indexer.getVirtualTxs(txids, {
4498
+ pageIndex,
4499
+ pageSize: DEFAULT_PAGE_SIZE
4500
+ });
4501
+ for (const psbt of txs) {
4502
+ out.set(Transaction.fromPSBT(base64.decode(psbt)).id, psbt);
4503
+ }
4504
+ hasMore = page ? txs.length === DEFAULT_PAGE_SIZE : false;
4505
+ pageIndex++;
4506
+ }
4507
+ return out;
4508
+ }
4509
+ };
4510
+ function chainedTxTypeToChainTxType(t) {
4511
+ switch (t) {
4512
+ case 1 /* Commitment */:
4513
+ return "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */;
4514
+ case 2 /* Ark */:
4515
+ return "INDEXER_CHAINED_TX_TYPE_ARK" /* ARK */;
4516
+ case 3 /* Tree */:
4517
+ return "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */;
4518
+ case 4 /* Checkpoint */:
4519
+ return "INDEXER_CHAINED_TX_TYPE_CHECKPOINT" /* CHECKPOINT */;
4520
+ default:
4521
+ return "INDEXER_CHAINED_TX_TYPE_UNSPECIFIED" /* UNSPECIFIED */;
4522
+ }
4523
+ }
4524
+ function psbtInputTxids(psbtBase64) {
4525
+ const tx = Transaction.fromPSBT(base64.decode(psbtBase64));
4526
+ const ids = [];
4527
+ for (let i = 0; i < tx.inputsLength; i++) {
4528
+ const txid = tx.getInput(i).txid;
4529
+ if (txid) ids.push(hex.encode(txid));
4530
+ }
4531
+ return ids;
4532
+ }
4533
+ var RepositoryExitDataSource = class {
4534
+ constructor(repo) {
4535
+ this.repo = repo;
4536
+ }
4537
+ repo;
4538
+ name = "repository";
4539
+ async getVtxoChain(vtxo) {
4540
+ const branch = await this.repo.getBranch(vtxo);
4541
+ if (branch.length === 0) return null;
4542
+ const chain = [];
4543
+ for (const v of branch) {
4544
+ const type = chainedTxTypeToChainTxType(v.type);
4545
+ let spends = [];
4546
+ if (v.type !== 1 /* Commitment */) {
4547
+ if (!v.psbt) return null;
4548
+ spends = psbtInputTxids(v.psbt);
4549
+ }
4550
+ chain.push({
4551
+ txid: v.txid,
4552
+ type,
4553
+ expiresAt: v.expiresAt != null ? String(v.expiresAt) : "",
4554
+ spends
4555
+ });
4556
+ }
4557
+ return chain;
4558
+ }
4559
+ async getVirtualTxs(txids) {
4560
+ const out = /* @__PURE__ */ new Map();
4561
+ for (const txid of txids) {
4562
+ const stored = await this.repo.getVirtualTx(txid);
4563
+ if (stored?.psbt) out.set(txid, stored.psbt);
4564
+ }
4565
+ return out;
4566
+ }
4567
+ };
4568
+
4569
+ // src/wallet/exit/resolver.ts
4570
+ var OrderedExitChainResolver = class {
4571
+ constructor(sources, persist) {
4572
+ this.sources = sources;
4573
+ this.persist = persist;
4574
+ }
4575
+ sources;
4576
+ persist;
4577
+ async getVtxoChain(vtxo) {
4578
+ let lastErr;
4579
+ for (const source of this.sources) {
4580
+ try {
4581
+ const chain = await source.getVtxoChain(vtxo);
4582
+ if (chain) return chain;
4583
+ } catch (e) {
4584
+ lastErr = e;
4585
+ }
4586
+ }
4587
+ if (lastErr) throw lastErr;
4588
+ throw new Error(`no exit-data source resolved the chain for ${vtxo.txid}:${vtxo.vout}`);
4589
+ }
4590
+ async getVirtualTxs(txids) {
4591
+ const found = /* @__PURE__ */ new Map();
4592
+ let remaining = txids;
4593
+ for (const [i, source] of this.sources.entries()) {
4594
+ if (remaining.length === 0) break;
4595
+ let got;
4596
+ try {
4597
+ got = await source.getVirtualTxs(remaining);
4598
+ } catch {
4599
+ continue;
4600
+ }
4601
+ const fresh = [];
4602
+ for (const [txid, psbt] of got) {
4603
+ if (found.has(txid)) continue;
4604
+ found.set(txid, psbt);
4605
+ fresh.push({ txid, psbt, expiresAt: null, type: 0 /* Unspecified */ });
4606
+ }
4607
+ if (i > 0 && this.persist && fresh.length > 0) {
4608
+ try {
4609
+ await this.persist.upsertVirtualTxs(fresh);
4610
+ } catch {
4611
+ }
4612
+ }
4613
+ remaining = txids.filter((t) => !found.has(t));
4614
+ }
4615
+ return [...found.values()];
4616
+ }
4617
+ };
4618
+ function createExitChainResolver(params) {
4619
+ const sources = [];
4620
+ if (params.repository) sources.push(new RepositoryExitDataSource(params.repository));
4621
+ if (params.extraSources) sources.push(...params.extraSources);
4622
+ sources.push(new IndexerExitDataSource(params.indexer));
4623
+ return new OrderedExitChainResolver(sources, params.repository);
4624
+ }
4625
+
4626
+ // src/providers/availability.ts
4627
+ function isRetryableProviderError(error) {
4628
+ return error instanceof ProviderUnavailableError || error instanceof FetchError || isFetchTimeoutError(error);
4629
+ }
4630
+
4631
+ // src/utils/syncCursors.ts
4632
+ var SAFETY_LAG_MS = 3e4;
4633
+ var OVERLAP_MS = 24 * 60 * 60 * 1e3;
4634
+ var walletStateLocks = /* @__PURE__ */ new WeakMap();
4635
+ async function updateWalletState(repo, updater) {
4636
+ const prev = walletStateLocks.get(repo) ?? Promise.resolve();
4637
+ const op = prev.then(async () => {
4638
+ const state = await repo.getWalletState() ?? {};
4639
+ await repo.saveWalletState(updater(state));
4640
+ });
4641
+ walletStateLocks.set(
4642
+ repo,
4643
+ op.catch(() => {
4644
+ })
4645
+ );
4646
+ return op;
4647
+ }
4648
+ var CURSOR_MIGRATED_KEY = "vtxoCursorMigrated";
4649
+ function hasMigrationMarker(state) {
4650
+ return state?.settings?.[CURSOR_MIGRATED_KEY] === true;
4651
+ }
4652
+ async function getSyncCursor(repo) {
4653
+ const state = await repo.getWalletState();
4654
+ if (!hasMigrationMarker(state)) return 0;
4655
+ return state?.lastSyncTime ?? 0;
4656
+ }
4657
+ async function advanceSyncCursor(repo, lastUpdatedAt) {
4658
+ await updateWalletState(repo, (state) => {
4659
+ const current = hasMigrationMarker(state) ? state.lastSyncTime ?? 0 : 0;
4660
+ return {
4661
+ ...state,
4662
+ lastSyncTime: Math.max(current, lastUpdatedAt),
4663
+ settings: {
4664
+ ...state.settings ?? {},
4665
+ [CURSOR_MIGRATED_KEY]: true
4666
+ }
4667
+ };
4668
+ });
4669
+ }
4670
+ async function clearSyncCursor(repo) {
4671
+ await updateWalletState(repo, (state) => {
4672
+ const { [CURSOR_MIGRATED_KEY]: _, ...restSettings } = state.settings ?? {};
4673
+ return {
4674
+ ...state,
4675
+ lastSyncTime: void 0,
4676
+ settings: restSettings
4677
+ };
4678
+ });
4679
+ }
4680
+ function computeSyncWindow(cursor) {
4681
+ const after = Math.max(0, cursor - OVERLAP_MS);
4682
+ return { after };
4683
+ }
4684
+ function cursorCutoff(requestStartedAt) {
4685
+ return (requestStartedAt ?? Date.now()) - SAFETY_LAG_MS;
4686
+ }
4687
+
4688
+ // src/wallet/arkInfoSnapshot.ts
4689
+ var ARK_INFO_SNAPSHOT_KEY = "arkInfoSnapshot";
4690
+ var MalformedArkInfoSnapshotError = class extends Error {
4691
+ constructor(message, options) {
4692
+ super(message, { cause: options?.cause });
4693
+ this.name = "MalformedArkInfoSnapshotError";
4694
+ }
4695
+ };
4696
+ var CURRENT_VERSION = 1;
4697
+ function serializeArkInfoSnapshot(info, savedAt) {
4698
+ return {
4699
+ version: CURRENT_VERSION,
4700
+ savedAt,
4701
+ source: "arkade.getInfo",
4702
+ arkInfo: {
4703
+ network: info.network,
4704
+ signerPubkey: info.signerPubkey,
4705
+ checkpointTapscript: info.checkpointTapscript,
4706
+ forfeitAddress: info.forfeitAddress,
4707
+ forfeitPubkey: info.forfeitPubkey,
4708
+ unilateralExitDelay: info.unilateralExitDelay.toString(),
4709
+ boardingExitDelay: info.boardingExitDelay.toString(),
4710
+ sessionDuration: info.sessionDuration.toString(),
4711
+ dust: info.dust.toString(),
4712
+ fees: info.fees,
4713
+ digest: info.digest,
4714
+ version: info.version,
4715
+ vtxoMinAmount: info.vtxoMinAmount.toString(),
4716
+ vtxoMaxAmount: info.vtxoMaxAmount.toString(),
4717
+ utxoMinAmount: info.utxoMinAmount.toString(),
4718
+ utxoMaxAmount: info.utxoMaxAmount.toString(),
4719
+ deprecatedSigners: info.deprecatedSigners.map((s) => ({
4720
+ pubkey: s.pubkey,
4721
+ cutoffDate: s.cutoffDate.toString()
4722
+ })),
4723
+ scheduledSession: info.scheduledSession ? {
4724
+ duration: info.scheduledSession.duration.toString(),
4725
+ fees: info.scheduledSession.fees,
4726
+ nextEndTime: info.scheduledSession.nextEndTime.toString(),
4727
+ nextStartTime: info.scheduledSession.nextStartTime.toString(),
4728
+ period: info.scheduledSession.period.toString()
4729
+ } : void 0
4730
+ }
4731
+ };
4732
+ }
4733
+ function hydrateArkInfo(snapshot) {
4734
+ const a = snapshot.arkInfo;
4735
+ return {
4736
+ boardingExitDelay: BigInt(a.boardingExitDelay),
4737
+ checkpointTapscript: a.checkpointTapscript,
4738
+ deprecatedSigners: a.deprecatedSigners.map((s) => ({
4739
+ cutoffDate: BigInt(s.cutoffDate),
4740
+ pubkey: s.pubkey
4741
+ })),
4742
+ digest: a.digest,
4743
+ dust: BigInt(a.dust),
4744
+ fees: a.fees,
4745
+ forfeitAddress: a.forfeitAddress,
4746
+ forfeitPubkey: a.forfeitPubkey,
4747
+ network: a.network,
4748
+ scheduledSession: a.scheduledSession ? {
4749
+ duration: BigInt(a.scheduledSession.duration),
4750
+ fees: a.scheduledSession.fees,
4751
+ nextEndTime: BigInt(a.scheduledSession.nextEndTime),
4752
+ nextStartTime: BigInt(a.scheduledSession.nextStartTime),
4753
+ period: BigInt(a.scheduledSession.period)
4754
+ } : void 0,
4755
+ serviceStatus: {},
4756
+ sessionDuration: BigInt(a.sessionDuration),
4757
+ signerPubkey: a.signerPubkey,
4758
+ unilateralExitDelay: BigInt(a.unilateralExitDelay),
4759
+ utxoMaxAmount: BigInt(a.utxoMaxAmount),
4760
+ utxoMinAmount: BigInt(a.utxoMinAmount),
4761
+ version: a.version,
4762
+ vtxoMaxAmount: BigInt(a.vtxoMaxAmount),
4763
+ vtxoMinAmount: BigInt(a.vtxoMinAmount)
4764
+ };
4765
+ }
4766
+ var DECIMAL_STRING = /^-?\d+$/;
4767
+ function assertString(value, path) {
4768
+ if (typeof value !== "string") {
4769
+ throw new MalformedArkInfoSnapshotError(`${path} must be a string`);
4770
+ }
4771
+ }
4772
+ function assertDecimalString(value, path) {
4773
+ assertString(value, path);
4774
+ if (!DECIMAL_STRING.test(value)) {
4775
+ throw new MalformedArkInfoSnapshotError(`${path} must be a decimal integer string`);
4776
+ }
4777
+ }
4778
+ function assertFeeInfo(value, path) {
4779
+ if (typeof value !== "object" || value === null) {
4780
+ throw new MalformedArkInfoSnapshotError(`${path} must be an object`);
4781
+ }
4782
+ const fees = value;
4783
+ assertString(fees.txFeeRate, `${path}.txFeeRate`);
4784
+ if (typeof fees.intentFee !== "object" || fees.intentFee === null) {
4785
+ throw new MalformedArkInfoSnapshotError(`${path}.intentFee must be an object`);
4786
+ }
4787
+ }
4788
+ function parseStoredArkInfoSnapshot(raw) {
4789
+ if (typeof raw !== "object" || raw === null) {
4790
+ throw new MalformedArkInfoSnapshotError("snapshot must be an object");
4791
+ }
4792
+ const snap = raw;
4793
+ if (snap.version !== CURRENT_VERSION) {
4794
+ throw new MalformedArkInfoSnapshotError(
4795
+ `unsupported snapshot version: ${String(snap.version)}`
4796
+ );
4797
+ }
4798
+ if (typeof snap.savedAt !== "number") {
4799
+ throw new MalformedArkInfoSnapshotError("savedAt must be a number");
4800
+ }
4801
+ if (snap.source !== "arkade.getInfo") {
4802
+ throw new MalformedArkInfoSnapshotError(`unexpected source: ${String(snap.source)}`);
4803
+ }
4804
+ if (typeof snap.arkInfo !== "object" || snap.arkInfo === null) {
4805
+ throw new MalformedArkInfoSnapshotError("arkInfo must be an object");
4806
+ }
4807
+ const a = snap.arkInfo;
4808
+ for (const field of [
4809
+ "network",
4810
+ "signerPubkey",
4811
+ "checkpointTapscript",
4812
+ "forfeitAddress",
4813
+ "forfeitPubkey",
4814
+ "digest",
4815
+ "version"
4816
+ ]) {
4817
+ assertString(a[field], `arkInfo.${field}`);
4818
+ }
4819
+ for (const field of [
4820
+ "unilateralExitDelay",
4821
+ "boardingExitDelay",
4822
+ "sessionDuration",
4823
+ "dust",
4824
+ "vtxoMinAmount",
4825
+ "vtxoMaxAmount",
4826
+ "utxoMinAmount",
4827
+ "utxoMaxAmount"
4828
+ ]) {
4829
+ assertDecimalString(a[field], `arkInfo.${field}`);
4830
+ }
4831
+ assertFeeInfo(a.fees, "arkInfo.fees");
4832
+ if (!Array.isArray(a.deprecatedSigners)) {
4833
+ throw new MalformedArkInfoSnapshotError("arkInfo.deprecatedSigners must be an array");
4834
+ }
4835
+ a.deprecatedSigners.forEach((s, i) => {
4836
+ if (typeof s !== "object" || s === null) {
4837
+ throw new MalformedArkInfoSnapshotError(
4838
+ `arkInfo.deprecatedSigners[${i}] must be an object`
4839
+ );
4840
+ }
4841
+ const signer = s;
4842
+ assertString(signer.pubkey, `arkInfo.deprecatedSigners[${i}].pubkey`);
4843
+ assertDecimalString(signer.cutoffDate, `arkInfo.deprecatedSigners[${i}].cutoffDate`);
4844
+ });
4845
+ if (a.scheduledSession !== void 0) {
4846
+ if (typeof a.scheduledSession !== "object" || a.scheduledSession === null) {
4847
+ throw new MalformedArkInfoSnapshotError("arkInfo.scheduledSession must be an object");
4848
+ }
4849
+ const s = a.scheduledSession;
4850
+ for (const field of ["duration", "nextEndTime", "nextStartTime", "period"]) {
4851
+ assertDecimalString(s[field], `arkInfo.scheduledSession.${field}`);
4852
+ }
4853
+ assertFeeInfo(s.fees, "arkInfo.scheduledSession.fees");
4854
+ }
4855
+ return raw;
4856
+ }
4857
+ async function loadArkInfoSnapshot(repo) {
4858
+ const state = await repo.getWalletState();
4859
+ const raw = state?.settings?.[ARK_INFO_SNAPSHOT_KEY];
4860
+ if (raw === void 0 || raw === null) return null;
4861
+ return parseStoredArkInfoSnapshot(raw);
4862
+ }
4863
+ async function saveArkInfoSnapshot(repo, info, savedAt) {
4864
+ const snapshot = serializeArkInfoSnapshot(info, savedAt);
4865
+ await updateWalletState(repo, (state) => ({
4866
+ ...state,
4867
+ settings: {
4868
+ ...state.settings ?? {},
4869
+ [ARK_INFO_SNAPSHOT_KEY]: snapshot
4870
+ }
4871
+ }));
4872
+ }
4873
+ async function resolveArkInfo(arkProvider, walletRepository) {
4874
+ let info;
4875
+ try {
4876
+ info = await arkProvider.getInfo();
4877
+ } catch (err) {
4878
+ if (!isRetryableProviderError(err)) throw err;
4879
+ const snapshot = await loadArkInfoSnapshot(walletRepository);
4880
+ if (!snapshot) {
4881
+ throw new ProviderUnavailableError(
4882
+ "Arkade server info is unavailable and no cached snapshot exists",
4883
+ { cause: err }
4884
+ );
4885
+ }
4886
+ return { info: hydrateArkInfo(snapshot), source: "cache", lastOnlineAt: snapshot.savedAt };
4887
+ }
4888
+ return { info, source: "live" };
4889
+ }
4890
+ async function saveValidatedArkInfoSnapshot(repo, info, savedAt) {
4891
+ try {
4892
+ await saveArkInfoSnapshot(repo, info, savedAt);
4893
+ } catch (err) {
4894
+ console.warn("Failed to persist Ark info snapshot", err);
4895
+ }
4896
+ }
4290
4897
  var TxTree = class {
4291
4898
  constructor(root, children = /* @__PURE__ */ new Map()) {
4292
4899
  this.root = root;
4293
4900
  this.children = children;
4294
4901
  }
4902
+ root;
4903
+ children;
4295
4904
  static create(chunks) {
4296
4905
  if (chunks.length === 0) {
4297
4906
  throw new Error("empty chunks");
@@ -4763,6 +5372,7 @@ var ReadonlyAssetManager = class {
4763
5372
  constructor(indexer) {
4764
5373
  this.indexer = indexer;
4765
5374
  }
5375
+ indexer;
4766
5376
  async getAssetDetails(assetId) {
4767
5377
  return this.indexer.getAssetDetails(assetId);
4768
5378
  }
@@ -4772,6 +5382,7 @@ var AssetManager = class extends ReadonlyAssetManager {
4772
5382
  super(wallet.indexerProvider);
4773
5383
  this.wallet = wallet;
4774
5384
  }
5385
+ wallet;
4775
5386
  /**
4776
5387
  * Issue a new asset.
4777
5388
  * @param params - Parameters for asset issuance
@@ -5093,6 +5704,9 @@ var DelegateManagerImpl = class {
5093
5704
  this.arkInfoProvider = arkInfoProvider;
5094
5705
  this.identity = identity;
5095
5706
  }
5707
+ delegateProvider;
5708
+ arkInfoProvider;
5709
+ identity;
5096
5710
  async getDelegateInfo() {
5097
5711
  return this.delegateProvider.getDelegateInfo();
5098
5712
  }
@@ -5259,12 +5873,12 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
5259
5873
  script: hex.encode(output.script)
5260
5874
  }).satoshis;
5261
5875
  }, 0);
5262
- if (amount - BigInt(outputFee) <= dust) {
5876
+ if (isSubdust(amount - BigInt(outputFee), dust)) {
5263
5877
  throw new Error("Amount is below dust limit, cannot delegate");
5264
5878
  }
5265
5879
  amount -= BigInt(outputFee);
5266
5880
  amount -= delegateFee;
5267
- if (amount <= dust) {
5881
+ if (isSubdust(amount, dust)) {
5268
5882
  throw new Error("Amount is below dust limit, cannot delegate");
5269
5883
  }
5270
5884
  outputs.push({
@@ -5396,20 +6010,6 @@ function isAnnotated(v) {
5396
6010
  return v.tapTree !== void 0 && v.forfeitTapLeafScript !== void 0 && v.intentTapLeafScript !== void 0;
5397
6011
  }
5398
6012
 
5399
- // src/repositories/virtualTxRepository.ts
5400
- var ChainedTxType = /* @__PURE__ */ ((ChainedTxType2) => {
5401
- ChainedTxType2[ChainedTxType2["Unspecified"] = 0] = "Unspecified";
5402
- ChainedTxType2[ChainedTxType2["Commitment"] = 1] = "Commitment";
5403
- ChainedTxType2[ChainedTxType2["Ark"] = 2] = "Ark";
5404
- ChainedTxType2[ChainedTxType2["Tree"] = 3] = "Tree";
5405
- ChainedTxType2[ChainedTxType2["Checkpoint"] = 4] = "Checkpoint";
5406
- return ChainedTxType2;
5407
- })(ChainedTxType || {});
5408
- function mergeChainedTxType(incoming, prev) {
5409
- if (incoming !== 0 /* Unspecified */) return incoming;
5410
- return prev ?? 0 /* Unspecified */;
5411
- }
5412
-
5413
6013
  // src/repositories/inMemory/intentRepository.ts
5414
6014
  var InMemoryIntentRepository = class {
5415
6015
  version = 1;
@@ -5875,6 +6475,7 @@ var IndexedDBVirtualTxRepository = class {
5875
6475
  constructor(dbName = DEFAULT_DB_NAME) {
5876
6476
  this.dbName = dbName;
5877
6477
  }
6478
+ dbName;
5878
6479
  version = 1;
5879
6480
  db = null;
5880
6481
  async getDB() {
@@ -6004,6 +6605,7 @@ var IndexedDBIntentRepository = class {
6004
6605
  constructor(dbName = DEFAULT_DB_NAME) {
6005
6606
  this.dbName = dbName;
6006
6607
  }
6608
+ dbName;
6007
6609
  version = 1;
6008
6610
  db = null;
6009
6611
  async getDB() {
@@ -6314,6 +6916,7 @@ var IndexedDBContractRepository = class {
6314
6916
  constructor(dbName = DEFAULT_DB_NAME) {
6315
6917
  this.dbName = dbName;
6316
6918
  }
6919
+ dbName;
6317
6920
  version = 1;
6318
6921
  db = null;
6319
6922
  async clear() {
@@ -6480,6 +7083,7 @@ var IndexedDBWalletRepository = class {
6480
7083
  constructor(dbName = DEFAULT_DB_NAME) {
6481
7084
  this.dbName = dbName;
6482
7085
  }
7086
+ dbName;
6483
7087
  version = 1;
6484
7088
  db = null;
6485
7089
  async clear() {
@@ -7803,87 +8407,30 @@ async function reconcileIntents(deps) {
7803
8407
  if (!next) continue;
7804
8408
  const [fresh] = await deps.intentRepository.getIntents({
7805
8409
  intentTxIds: [intent.intentTxId]
7806
- });
7807
- if (!fresh || fresh.state !== intent.state) continue;
7808
- await deps.intentRepository.saveIntent(next);
7809
- } catch (e) {
7810
- console.error(`reconcileIntents: failed to reconcile intent ${intent.intentTxId}`, e);
7811
- }
7812
- }
7813
- }
7814
- function terminal(intent, state, now, patch) {
7815
- return { ...intent, ...patch, state, updatedAt: now };
7816
- }
7817
- function outpointKey(o) {
7818
- return `${o.txid}:${o.vout}`;
7819
- }
7820
- function dedupeOutpoints(outpoints) {
7821
- const seen = /* @__PURE__ */ new Set();
7822
- const out = [];
7823
- for (const o of outpoints) {
7824
- const k = outpointKey(o);
7825
- if (seen.has(k)) continue;
7826
- seen.add(k);
7827
- out.push(o);
7828
- }
7829
- return out;
7830
- }
7831
-
7832
- // src/utils/syncCursors.ts
7833
- var SAFETY_LAG_MS = 3e4;
7834
- var OVERLAP_MS = 24 * 60 * 60 * 1e3;
7835
- var walletStateLocks = /* @__PURE__ */ new WeakMap();
7836
- async function updateWalletState(repo, updater) {
7837
- const prev = walletStateLocks.get(repo) ?? Promise.resolve();
7838
- const op = prev.then(async () => {
7839
- const state = await repo.getWalletState() ?? {};
7840
- await repo.saveWalletState(updater(state));
7841
- });
7842
- walletStateLocks.set(
7843
- repo,
7844
- op.catch(() => {
7845
- })
7846
- );
7847
- return op;
7848
- }
7849
- var CURSOR_MIGRATED_KEY = "vtxoCursorMigrated";
7850
- function hasMigrationMarker(state) {
7851
- return state?.settings?.[CURSOR_MIGRATED_KEY] === true;
7852
- }
7853
- async function getSyncCursor(repo) {
7854
- const state = await repo.getWalletState();
7855
- if (!hasMigrationMarker(state)) return 0;
7856
- return state?.lastSyncTime ?? 0;
7857
- }
7858
- async function advanceSyncCursor(repo, lastUpdatedAt) {
7859
- await updateWalletState(repo, (state) => {
7860
- const current = hasMigrationMarker(state) ? state.lastSyncTime ?? 0 : 0;
7861
- return {
7862
- ...state,
7863
- lastSyncTime: Math.max(current, lastUpdatedAt),
7864
- settings: {
7865
- ...state.settings ?? {},
7866
- [CURSOR_MIGRATED_KEY]: true
7867
- }
7868
- };
7869
- });
8410
+ });
8411
+ if (!fresh || fresh.state !== intent.state) continue;
8412
+ await deps.intentRepository.saveIntent(next);
8413
+ } catch (e) {
8414
+ console.error(`reconcileIntents: failed to reconcile intent ${intent.intentTxId}`, e);
8415
+ }
8416
+ }
7870
8417
  }
7871
- async function clearSyncCursor(repo) {
7872
- await updateWalletState(repo, (state) => {
7873
- const { [CURSOR_MIGRATED_KEY]: _, ...restSettings } = state.settings ?? {};
7874
- return {
7875
- ...state,
7876
- lastSyncTime: void 0,
7877
- settings: restSettings
7878
- };
7879
- });
8418
+ function terminal(intent, state, now, patch) {
8419
+ return { ...intent, ...patch, state, updatedAt: now };
7880
8420
  }
7881
- function computeSyncWindow(cursor) {
7882
- const after = Math.max(0, cursor - OVERLAP_MS);
7883
- return { after };
8421
+ function outpointKey(o) {
8422
+ return `${o.txid}:${o.vout}`;
7884
8423
  }
7885
- function cursorCutoff(requestStartedAt) {
7886
- return (requestStartedAt ?? Date.now()) - SAFETY_LAG_MS;
8424
+ function dedupeOutpoints(outpoints) {
8425
+ const seen = /* @__PURE__ */ new Set();
8426
+ const out = [];
8427
+ for (const o of outpoints) {
8428
+ const k = outpointKey(o);
8429
+ if (seen.has(k)) continue;
8430
+ seen.add(k);
8431
+ out.push(o);
8432
+ }
8433
+ return out;
7887
8434
  }
7888
8435
 
7889
8436
  // src/contracts/contractManager.ts
@@ -7898,6 +8445,10 @@ var ContractManager = class _ContractManager {
7898
8445
  initialized = false;
7899
8446
  eventCallbacks = /* @__PURE__ */ new Set();
7900
8447
  stopWatcherFn;
8448
+ /** `undefined` while online; the failure reason once a sync degrades. */
8449
+ syncDegradedReason;
8450
+ /** Epoch-ms of the last successful provider sync, if any. */
8451
+ lastSyncedAt;
7901
8452
  constructor(config) {
7902
8453
  this.config = config;
7903
8454
  this.watcher = new ContractWatcher({
@@ -7920,6 +8471,26 @@ var ContractManager = class _ContractManager {
7920
8471
  await cm.initialize();
7921
8472
  return cm;
7922
8473
  }
8474
+ /**
8475
+ * Latest provider-sync health. See {@link ContractSyncState}. Degradation is
8476
+ * recorded by {@link initialize}, {@link getContractsWithVtxos}, and
8477
+ * {@link createContract}; it flips back to `online` on the next successful
8478
+ * sync. Purely a freshness signal — not a source of truth for wallet data.
8479
+ */
8480
+ getSyncState() {
8481
+ return this.syncDegradedReason === void 0 ? { mode: "online", lastSyncedAt: this.lastSyncedAt } : {
8482
+ mode: "degraded",
8483
+ reason: this.syncDegradedReason,
8484
+ lastSyncedAt: this.lastSyncedAt
8485
+ };
8486
+ }
8487
+ markSyncOnline() {
8488
+ this.syncDegradedReason = void 0;
8489
+ this.lastSyncedAt = Date.now();
8490
+ }
8491
+ markSyncDegraded(err) {
8492
+ this.syncDegradedReason = err instanceof Error ? err.message : String(err);
8493
+ }
7923
8494
  async initialize() {
7924
8495
  if (this.initialized) {
7925
8496
  return;
@@ -7928,7 +8499,13 @@ var ContractManager = class _ContractManager {
7928
8499
  for (const contract of contracts) {
7929
8500
  await this.watcher.addContract(contract);
7930
8501
  }
7931
- await this.reconcileWatched();
8502
+ try {
8503
+ await this.reconcileWatched();
8504
+ this.markSyncOnline();
8505
+ } catch (err) {
8506
+ if (!isRetryableProviderError(err)) throw err;
8507
+ this.markSyncDegraded(err);
8508
+ }
7932
8509
  this.initialized = true;
7933
8510
  this.stopWatcherFn = await this.watcher.startWatching((event) => {
7934
8511
  this.handleContractEvent(event).catch((error) => {
@@ -7982,7 +8559,13 @@ var ContractManager = class _ContractManager {
7982
8559
  async createContract(params) {
7983
8560
  const { contract, persisted } = await this.upsertContract(params);
7984
8561
  if (persisted) {
7985
- await this.fetchContractVxosFromIndexer([contract]);
8562
+ try {
8563
+ await this.fetchContractVxosFromIndexer([contract]);
8564
+ this.markSyncOnline();
8565
+ } catch (err) {
8566
+ if (!isRetryableProviderError(err)) throw err;
8567
+ this.markSyncDegraded(err);
8568
+ }
7986
8569
  await this.watcher.addContract(contract);
7987
8570
  }
7988
8571
  return contract;
@@ -8181,7 +8764,13 @@ var ContractManager = class _ContractManager {
8181
8764
  }
8182
8765
  async getContractsWithVtxos(filter, pageSize) {
8183
8766
  const contracts = await this.getContracts(filter);
8184
- await this.syncContracts({ contracts, pageSize });
8767
+ try {
8768
+ await this.syncContracts({ contracts, pageSize });
8769
+ this.markSyncOnline();
8770
+ } catch (err) {
8771
+ if (!isRetryableProviderError(err)) throw err;
8772
+ this.markSyncDegraded(err);
8773
+ }
8185
8774
  const vtxos = await this.getVtxosForContracts(contracts);
8186
8775
  return contracts.map((contract) => ({
8187
8776
  contract,
@@ -8419,15 +9008,33 @@ var ContractManager = class _ContractManager {
8419
9008
  * Handle events from the watcher.
8420
9009
  */
8421
9010
  async handleContractEvent(event) {
8422
- switch (event.type) {
8423
- // Delta-sync only the changed virtual outputs for this contract.
8424
- case "vtxo_received":
8425
- case "vtxo_spent":
8426
- await this.syncContracts({ contracts: [event.contract] });
8427
- break;
8428
- case "connection_reset":
8429
- await this.reconcileWatched();
8430
- break;
9011
+ try {
9012
+ switch (event.type) {
9013
+ // Delta-sync only the changed virtual outputs for this contract.
9014
+ case "vtxo_received":
9015
+ await this.syncContracts({ contracts: [event.contract] });
9016
+ this.markSyncOnline();
9017
+ break;
9018
+ case "vtxo_spent":
9019
+ await this.syncContracts({ contracts: [event.contract] });
9020
+ this.markSyncOnline();
9021
+ if (this.config.onVtxosSpent) {
9022
+ try {
9023
+ await this.config.onVtxosSpent(
9024
+ event.vtxos.map((v) => ({ txid: v.txid, vout: v.vout }))
9025
+ );
9026
+ } catch {
9027
+ }
9028
+ }
9029
+ break;
9030
+ case "connection_reset":
9031
+ await this.reconcileWatched();
9032
+ this.markSyncOnline();
9033
+ break;
9034
+ }
9035
+ } catch (err) {
9036
+ if (!isRetryableProviderError(err)) throw err;
9037
+ this.markSyncDegraded(err);
8431
9038
  }
8432
9039
  this.emitEvent(event);
8433
9040
  }
@@ -8509,6 +9116,12 @@ var ContractManager = class _ContractManager {
8509
9116
  contract,
8510
9117
  filtered
8511
9118
  );
9119
+ if (this.config.onVtxosPersisted) {
9120
+ try {
9121
+ await this.config.onVtxosPersisted(contract, filtered);
9122
+ } catch {
9123
+ }
9124
+ }
8512
9125
  }
8513
9126
  }
8514
9127
  async fetchContractVxosFromIndexer(contracts, pageSize, syncWindow) {
@@ -8529,6 +9142,15 @@ var ContractManager = class _ContractManager {
8529
9142
  contract,
8530
9143
  filtered
8531
9144
  );
9145
+ if (this.config.onVtxosPersisted) {
9146
+ try {
9147
+ await this.config.onVtxosPersisted(
9148
+ contract,
9149
+ filtered
9150
+ );
9151
+ } catch {
9152
+ }
9153
+ }
8532
9154
  }
8533
9155
  }
8534
9156
  return result;
@@ -8607,6 +9229,8 @@ var HDDescriptorProvider = class _HDDescriptorProvider {
8607
9229
  this.identity = identity;
8608
9230
  this.walletRepository = walletRepository;
8609
9231
  }
9232
+ identity;
9233
+ walletRepository;
8610
9234
  /**
8611
9235
  * Construct an HDDescriptorProvider. No I/O is performed here;
8612
9236
  * persisted state is read lazily on the first call to
@@ -8799,6 +9423,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
8799
9423
  this.currentTaggedScript = priorTaggedScript;
8800
9424
  this.logger = logger ?? console;
8801
9425
  }
9426
+ provider;
8802
9427
  unsubscribe;
8803
9428
  chain = Promise.resolve();
8804
9429
  /**
@@ -9152,6 +9777,8 @@ var MissingSigningDescriptorError = class extends Error {
9152
9777
  this.contractScript = contractScript;
9153
9778
  this.contractType = contractType;
9154
9779
  }
9780
+ contractScript;
9781
+ contractType;
9155
9782
  name = "MissingSigningDescriptorError";
9156
9783
  };
9157
9784
  var DescriptorSigningProviderMissingError = class extends Error {
@@ -9167,6 +9794,7 @@ var InputSignerRouter = class {
9167
9794
  constructor(deps) {
9168
9795
  this.deps = deps;
9169
9796
  }
9797
+ deps;
9170
9798
  /**
9171
9799
  * Resolve each job to its target signer without invoking signing. The
9172
9800
  * returned plan is the single source of truth for both {@link sign} and
@@ -9385,6 +10013,14 @@ var ReadonlyWallet = class _ReadonlyWallet {
9385
10013
  this._assetManager = new ReadonlyAssetManager(this.indexerProvider);
9386
10014
  this.walletContractTimelocks = walletContractTimelocks && walletContractTimelocks.length > 0 ? dedupeTimelocks(walletContractTimelocks) : [this.offchainTapscript.options.csvTimelock];
9387
10015
  }
10016
+ identity;
10017
+ network;
10018
+ onchainProvider;
10019
+ indexerProvider;
10020
+ dustAmount;
10021
+ walletRepository;
10022
+ contractRepository;
10023
+ delegateProvider;
9388
10024
  _contractManager;
9389
10025
  _contractManagerInitializing;
9390
10026
  watcherConfig;
@@ -9402,6 +10038,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
9402
10038
  * (ContractManager isn't given it); `undefined` ⇒ no-op.
9403
10039
  */
9404
10040
  virtualTxRepository;
10041
+ /** Opt-in exit-data capture settings; see {@link StorageConfig.exitDataCapture}. */
10042
+ exitDataCapture;
9405
10043
  _assetManager;
9406
10044
  walletContractTimelocks;
9407
10045
  // Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
@@ -9444,6 +10082,72 @@ var ReadonlyWallet = class _ReadonlyWallet {
9444
10082
  * construction-time snapshot for their lifetime.
9445
10083
  */
9446
10084
  _arkServerPublicKey;
10085
+ /**
10086
+ * Whether this wallet was constructed from live operator server-info
10087
+ * (`"live"`) or from a cached snapshot because the operator was unreachable
10088
+ * (`"cache"`). Freshness signal for {@link getProviderConnectionState}.
10089
+ */
10090
+ _serverInfoSource = "live";
10091
+ /**
10092
+ * Epoch-ms of the last known live operator contact: construction time on the
10093
+ * `live` boot path, the cached snapshot's `savedAt` on the `cache` path.
10094
+ */
10095
+ _serverInfoLastOnlineAt;
10096
+ /** @see {@link _serverInfoSource} */
10097
+ get serverInfoSource() {
10098
+ return this._serverInfoSource;
10099
+ }
10100
+ /**
10101
+ * Composed provider-connection freshness: the boot server-info source
10102
+ * (Arkade) combined with the contract-manager's indexer-sync health, if the
10103
+ * manager has been initialized. Reads no live provider state — it never
10104
+ * forces a `ContractManager` to construct — so it is safe for readonly
10105
+ * callers that only use address/balance APIs.
10106
+ *
10107
+ * - Boot fell back to a cached snapshot → degraded on `arkade` (`cache`).
10108
+ * - Otherwise, if the contract manager has degraded to repository data →
10109
+ * degraded on `indexer` (`repository`).
10110
+ * - Otherwise online.
10111
+ *
10112
+ * This only describes sync freshness; wallet balances/VTXOs are always read
10113
+ * from the repository regardless of this state.
10114
+ */
10115
+ getProviderConnectionState() {
10116
+ if (this._serverInfoSource === "cache") {
10117
+ return {
10118
+ mode: "degraded",
10119
+ source: "cache",
10120
+ provider: "arkade",
10121
+ reason: "constructed from cached server-info; operator was unreachable at boot",
10122
+ lastOnlineAt: this._serverInfoLastOnlineAt
10123
+ };
10124
+ }
10125
+ const sync = this._contractManager?.getSyncState();
10126
+ if (sync?.mode === "degraded") {
10127
+ return {
10128
+ mode: "degraded",
10129
+ source: "repository",
10130
+ provider: "indexer",
10131
+ reason: sync.reason,
10132
+ lastOnlineAt: sync.lastSyncedAt ?? this._serverInfoLastOnlineAt
10133
+ };
10134
+ }
10135
+ return {
10136
+ mode: "online",
10137
+ source: "live",
10138
+ lastOnlineAt: sync?.lastSyncedAt ?? this._serverInfoLastOnlineAt ?? 0
10139
+ };
10140
+ }
10141
+ /**
10142
+ * The contract-manager's current provider-sync health **without forcing it
10143
+ * to initialize** — reads the already-constructed manager, or reports
10144
+ * `online` when none exists yet. Unlike {@link getContractManager}, this
10145
+ * never triggers a remote sync, so it is safe on a pure diagnostics path
10146
+ * (e.g. the service-worker sync-state message).
10147
+ */
10148
+ getContractSyncState() {
10149
+ return this._contractManager?.getSyncState() ?? { mode: "online" };
10150
+ }
9447
10151
  /**
9448
10152
  * x-only hex of the operator's deprecated signer keys (from
9449
10153
  * `ArkInfo.deprecatedSigners`), cached for the OFFLINE read/watch paths.
@@ -9578,7 +10282,13 @@ var ReadonlyWallet = class _ReadonlyWallet {
9578
10282
  }
9579
10283
  indexerProvider = new RestIndexerProvider(indexerUrl);
9580
10284
  }
9581
- const info = await arkProvider.getInfo();
10285
+ const walletRepository = config.storage?.walletRepository ?? new IndexedDBWalletRepository();
10286
+ const contractRepository = config.storage?.contractRepository ?? new IndexedDBContractRepository();
10287
+ const {
10288
+ info,
10289
+ source: serverInfoSource,
10290
+ lastOnlineAt: serverInfoLastOnlineAt
10291
+ } = await resolveArkInfo(arkProvider, walletRepository);
9582
10292
  const network = getNetwork(info.network);
9583
10293
  if ("descriptor" in config.identity) {
9584
10294
  const descriptor = config.identity.descriptor;
@@ -9632,8 +10342,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
9632
10342
  serverPubKey: hex.encode(serverPubKey),
9633
10343
  csvTimelock: timelockToSequence(boardingTimelock).toString()
9634
10344
  });
9635
- const walletRepository = config.storage?.walletRepository ?? new IndexedDBWalletRepository();
9636
- const contractRepository = config.storage?.contractRepository ?? new IndexedDBContractRepository();
9637
10345
  return {
9638
10346
  arkProvider,
9639
10347
  indexerProvider,
@@ -9647,6 +10355,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
9647
10355
  walletRepository,
9648
10356
  contractRepository,
9649
10357
  info,
10358
+ serverInfoSource,
10359
+ serverInfoLastOnlineAt,
9650
10360
  delegateProvider: config.delegateProvider || config.delegatorProvider,
9651
10361
  /** @deprecated alias for `delegateProvider` */
9652
10362
  delegatorProvider: config.delegateProvider || config.delegatorProvider,
@@ -9682,6 +10392,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
9682
10392
  );
9683
10393
  wallet.intentRepository = config.storage?.intentRepository;
9684
10394
  wallet.virtualTxRepository = config.storage?.virtualTxRepository;
10395
+ wallet.exitDataCapture = config.storage?.exitDataCapture;
10396
+ wallet._serverInfoSource = setup.serverInfoSource;
10397
+ if (setup.serverInfoSource === "live") {
10398
+ const now = Date.now();
10399
+ await saveValidatedArkInfoSnapshot(setup.walletRepository, setup.info, now);
10400
+ wallet._serverInfoLastOnlineAt = now;
10401
+ } else {
10402
+ wallet._serverInfoLastOnlineAt = setup.serverInfoLastOnlineAt;
10403
+ }
9685
10404
  wallet.refreshDeprecatedSigners(setup.info);
9686
10405
  return wallet;
9687
10406
  }
@@ -9812,7 +10531,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
9812
10531
  const response = await contractManager.getContractsWithVtxos();
9813
10532
  const allVtxos = response.flatMap((_) => _.vtxos);
9814
10533
  const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
9815
- const getTxCreatedAt = (txid) => this.indexerProvider.getVtxos({ outpoints: [{ txid, vout: 0 }] }).then((res) => res.vtxos[0]?.createdAt.getTime());
10534
+ const getTxCreatedAt = (txid) => this.indexerProvider.getVtxos({ outpoints: [{ txid, vout: 0 }] }).then((res) => res.vtxos[0]?.createdAt.getTime()).catch((err) => {
10535
+ if (isRetryableProviderError(err)) return void 0;
10536
+ throw err;
10537
+ });
9816
10538
  return buildTransactionHistory(allVtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
9817
10539
  }
9818
10540
  /**
@@ -10231,11 +10953,39 @@ var ReadonlyWallet = class _ReadonlyWallet {
10231
10953
  }
10232
10954
  }
10233
10955
  async initializeContractManager() {
10956
+ const virtualTxRepository = this.virtualTxRepository;
10957
+ let onVtxosPersisted;
10958
+ let onVtxosSpent;
10959
+ if (virtualTxRepository) {
10960
+ const capture = this.exitDataCapture;
10961
+ const resolver = createExitChainResolver({
10962
+ indexer: this.indexerProvider,
10963
+ repository: virtualTxRepository,
10964
+ extraSources: capture?.sources
10965
+ });
10966
+ onVtxosPersisted = async (_contract, vtxos) => {
10967
+ for (const v of vtxos) {
10968
+ if (v.virtualStatus.state === "spent") continue;
10969
+ await captureExitBranch({
10970
+ resolver,
10971
+ repository: virtualTxRepository,
10972
+ vtxo: { txid: v.txid, vout: v.vout },
10973
+ value: v.value,
10974
+ mode: capture?.mode ?? DEFAULT_EXIT_CAPTURE_MODE,
10975
+ minExitWorthSats: capture?.minExitWorthSats ?? DEFAULT_MIN_EXIT_WORTH_SATS
10976
+ }).catch(() => {
10977
+ });
10978
+ }
10979
+ };
10980
+ onVtxosSpent = (vtxos) => pruneExitBranches(virtualTxRepository, vtxos);
10981
+ }
10234
10982
  const manager = await ContractManager.create({
10235
10983
  indexerProvider: this.indexerProvider,
10236
10984
  contractRepository: this.contractRepository,
10237
10985
  walletRepository: this.walletRepository,
10238
10986
  intentRepository: this.intentRepository,
10987
+ onVtxosPersisted,
10988
+ onVtxosSpent,
10239
10989
  watcherConfig: this.watcherConfig
10240
10990
  });
10241
10991
  const baselinePubkey = await this.identity.xOnlyPublicKey();
@@ -10377,6 +11127,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10377
11127
  boardingPkScript: boardingTapscript.pkScript
10378
11128
  });
10379
11129
  }
11130
+ arkProvider;
11131
+ forfeitOutputScript;
11132
+ forfeitPubkey;
10380
11133
  static MIN_FEE_RATE = 1;
10381
11134
  // sats/vbyte
10382
11135
  identity;
@@ -10910,6 +11663,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10910
11663
  boot?.rotator,
10911
11664
  boot?.provider
10912
11665
  );
11666
+ wallet._serverInfoSource = setup.serverInfoSource;
11667
+ if (setup.serverInfoSource === "live") {
11668
+ const now = Date.now();
11669
+ await saveValidatedArkInfoSnapshot(setup.walletRepository, setup.info, now);
11670
+ wallet._serverInfoLastOnlineAt = now;
11671
+ } else {
11672
+ wallet._serverInfoLastOnlineAt = setup.serverInfoLastOnlineAt;
11673
+ }
10913
11674
  wallet.refreshDeprecatedSigners(setup.info);
10914
11675
  {
10915
11676
  const ap = setup.arkProvider;
@@ -10931,6 +11692,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10931
11692
  }
10932
11693
  wallet.intentRepository = config.storage?.intentRepository;
10933
11694
  wallet.virtualTxRepository = config.storage?.virtualTxRepository;
11695
+ wallet.exitDataCapture = config.storage?.exitDataCapture;
10934
11696
  await wallet.getVtxoManager();
10935
11697
  return wallet;
10936
11698
  }
@@ -11132,7 +11894,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11132
11894
  script: offchainOutputScript
11133
11895
  });
11134
11896
  output.amount -= BigInt(outputFee.satoshis);
11135
- if (output.amount <= this.dustAmount) {
11897
+ if (isSubdust(output.amount, this.dustAmount)) {
11136
11898
  throw new Error("Output amount is below dust limit");
11137
11899
  }
11138
11900
  params = {
@@ -11380,7 +12142,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11380
12142
  }
11381
12143
  continue;
11382
12144
  }
11383
- if (isRecoverable(input) || isSubdust(input, this.dustAmount)) {
12145
+ if (isRecoverable(input) || isSubdust({ value: input.value }, this.dustAmount)) {
11384
12146
  continue;
11385
12147
  }
11386
12148
  if (connectorsLeaves.length === 0) {
@@ -12459,6 +13221,8 @@ var MessageBus = class {
12459
13221
  this.debug = debug;
12460
13222
  this.buildServicesFn = buildServices ?? this.buildServices.bind(this);
12461
13223
  }
13224
+ walletRepository;
13225
+ contractRepository;
12462
13226
  handlers;
12463
13227
  tickIntervalMs;
12464
13228
  messageTimeoutMs;
@@ -12960,6 +13724,7 @@ var Ramps = class {
12960
13724
  constructor(wallet) {
12961
13725
  this.wallet = wallet;
12962
13726
  }
13727
+ wallet;
12963
13728
  /**
12964
13729
  * Onboard boarding inputs.
12965
13730
  *
@@ -13448,10 +14213,18 @@ var WalletMessageHandler = class {
13448
14213
  type: "WALLET_STATUS",
13449
14214
  payload: {
13450
14215
  walletInitialized: true,
13451
- xOnlyPublicKey: pubKey
14216
+ xOnlyPublicKey: pubKey,
14217
+ providerConnectionState: this.readonlyWallet.getProviderConnectionState()
13452
14218
  }
13453
14219
  });
13454
14220
  }
14221
+ case "GET_CONTRACT_SYNC_STATE": {
14222
+ return this.tagged({
14223
+ id,
14224
+ type: "CONTRACT_SYNC_STATE",
14225
+ payload: { syncState: this.readonlyWallet.getContractSyncState() }
14226
+ });
14227
+ }
13455
14228
  case "CLEAR": {
13456
14229
  await this.clear();
13457
14230
  return this.tagged({
@@ -14214,6 +14987,7 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
14214
14987
  GET_BALANCE: 1e4,
14215
14988
  GET_BOARDING_ADDRESS: 1e4,
14216
14989
  GET_STATUS: 1e4,
14990
+ GET_CONTRACT_SYNC_STATE: 1e4,
14217
14991
  GET_DELEGATE_INFO: 1e4,
14218
14992
  IS_CONTRACT_MANAGER_WATCHING: 1e4,
14219
14993
  // Medium reads — may involve indexer queries
@@ -14297,6 +15071,8 @@ var ServiceWorkerReadonlyAssetManager = class {
14297
15071
  this.sendMessage = sendMessage;
14298
15072
  this.messageTag = messageTag;
14299
15073
  }
15074
+ sendMessage;
15075
+ messageTag;
14300
15076
  async getAssetDetails(assetId) {
14301
15077
  const message = {
14302
15078
  tag: this.messageTag,
@@ -14381,6 +15157,8 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14381
15157
  messageTag
14382
15158
  );
14383
15159
  }
15160
+ serviceWorker;
15161
+ messageTag;
14384
15162
  walletRepository;
14385
15163
  contractRepository;
14386
15164
  identity;
@@ -14822,6 +15600,19 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14822
15600
  throw new Error(`Failed to get status: ${error}`);
14823
15601
  }
14824
15602
  }
15603
+ /**
15604
+ * Wallet-level provider-connection freshness, delegated to the worker via
15605
+ * `GET_STATUS`. Async by necessity (the worker boundary is asynchronous); no
15606
+ * synchronous variant is offered because it would have the same transport
15607
+ * mismatch as the contract-manager proxy's cached `getSyncState()`.
15608
+ */
15609
+ async getProviderConnectionState() {
15610
+ const { providerConnectionState } = await this.getStatus();
15611
+ if (!providerConnectionState) {
15612
+ throw new Error("Worker did not report provider connection state");
15613
+ }
15614
+ return providerConnectionState;
15615
+ }
14825
15616
  async getActivityHistory() {
14826
15617
  return buildActivities(await this.getTransactionHistory(), this.activity.all());
14827
15618
  }
@@ -14876,6 +15667,32 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14876
15667
  return wallet.sendMessage(message);
14877
15668
  };
14878
15669
  const messageTag = this.messageTag;
15670
+ const fetchSyncState = async () => {
15671
+ const message = {
15672
+ type: "GET_CONTRACT_SYNC_STATE",
15673
+ id: getRandomId(),
15674
+ tag: messageTag
15675
+ };
15676
+ const response = await sendContractMessage(message);
15677
+ return response.payload.syncState;
15678
+ };
15679
+ const UNKNOWN_STATE = {
15680
+ mode: "degraded",
15681
+ reason: "contract sync state unavailable from the worker"
15682
+ };
15683
+ let syncState = UNKNOWN_STATE;
15684
+ let everProbed = false;
15685
+ const refreshSyncState = async () => {
15686
+ try {
15687
+ syncState = await fetchSyncState();
15688
+ everProbed = true;
15689
+ } catch {
15690
+ if (!everProbed) {
15691
+ syncState = UNKNOWN_STATE;
15692
+ }
15693
+ }
15694
+ };
15695
+ await refreshSyncState();
14879
15696
  const manager = {
14880
15697
  async createContract(params) {
14881
15698
  const message = {
@@ -14886,6 +15703,7 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14886
15703
  };
14887
15704
  try {
14888
15705
  const response = await sendContractMessage(message);
15706
+ await refreshSyncState();
14889
15707
  return response.payload.contract;
14890
15708
  } catch (e) {
14891
15709
  throw new Error("Failed to create contract");
@@ -14914,11 +15732,15 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14914
15732
  };
14915
15733
  try {
14916
15734
  const response = await sendContractMessage(message);
15735
+ await refreshSyncState();
14917
15736
  return response.payload.contracts;
14918
15737
  } catch (e) {
14919
15738
  throw new Error("Failed to get contracts with vtxos");
14920
15739
  }
14921
15740
  },
15741
+ getSyncState() {
15742
+ return syncState;
15743
+ },
14922
15744
  async annotateVtxos(vtxos) {
14923
15745
  if (vtxos.length === 0) return [];
14924
15746
  const message = {
@@ -15027,7 +15849,11 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
15027
15849
  tag: messageTag,
15028
15850
  payload: opts
15029
15851
  };
15030
- await sendContractMessage(message);
15852
+ try {
15853
+ await sendContractMessage(message);
15854
+ } finally {
15855
+ await refreshSyncState();
15856
+ }
15031
15857
  },
15032
15858
  async refreshOutpoints(outpoints) {
15033
15859
  const message = {
@@ -15036,7 +15862,11 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
15036
15862
  tag: messageTag,
15037
15863
  payload: { outpoints }
15038
15864
  };
15039
- await sendContractMessage(message);
15865
+ try {
15866
+ await sendContractMessage(message);
15867
+ } finally {
15868
+ await refreshSyncState();
15869
+ }
15040
15870
  },
15041
15871
  scanContracts() {
15042
15872
  return Promise.reject(
@@ -15081,6 +15911,7 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
15081
15911
  );
15082
15912
  this.hasDelegate = hasDelegate;
15083
15913
  }
15914
+ serviceWorker;
15084
15915
  walletRepository;
15085
15916
  contractRepository;
15086
15917
  identity;
@@ -15462,6 +16293,7 @@ var OnchainWallet = class _OnchainWallet {
15462
16293
  this.onchainP2TR = onchainP2TR;
15463
16294
  this.provider = provider;
15464
16295
  }
16296
+ identity;
15465
16297
  static MIN_FEE_RATE = 1;
15466
16298
  // sat/vbyte
15467
16299
  onchainP2TR;
@@ -15763,6 +16595,8 @@ var WsElectrumChainSource = class {
15763
16595
  this.ws = ws;
15764
16596
  this.network = network;
15765
16597
  }
16598
+ ws;
16599
+ network;
15766
16600
  // Cached chain tip kept fresh by the headers subscription. Initialized
15767
16601
  // lazily on first call to subscribeHeaders().
15768
16602
  cachedTip = null;
@@ -16045,6 +16879,8 @@ var ElectrumOnchainProvider = class {
16045
16879
  this.network = network;
16046
16880
  this.chain = new WsElectrumChainSource(ws, network);
16047
16881
  }
16882
+ ws;
16883
+ network;
16048
16884
  chain;
16049
16885
  async getCoins(address) {
16050
16886
  const script = this.encodeAddress(address);
@@ -16598,6 +17434,7 @@ var ExitPathError = class extends Error {
16598
17434
  this.reason = reason;
16599
17435
  this.name = "ExitPathError";
16600
17436
  }
17437
+ reason;
16601
17438
  };
16602
17439
  function delayWeight(sequence) {
16603
17440
  const t = sequenceToTimelock(sequence);
@@ -16651,7 +17488,7 @@ async function resolveUnilateralPath(params) {
16651
17488
  }
16652
17489
 
16653
17490
  // src/wallet/unroll.ts
16654
- function chainTxTypeToChainedExit(t) {
17491
+ function chainTxTypeToChainedExit2(t) {
16655
17492
  switch (t) {
16656
17493
  case "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */:
16657
17494
  return 1 /* Commitment */;
@@ -16681,6 +17518,11 @@ var Unroll;
16681
17518
  this.indexer = indexer;
16682
17519
  this.virtualTxRepository = virtualTxRepository;
16683
17520
  }
17521
+ toUnroll;
17522
+ bumper;
17523
+ explorer;
17524
+ indexer;
17525
+ virtualTxRepository;
16684
17526
  /** Create an unroll session by loading the virtual output chain from the indexer. */
16685
17527
  static async create(toUnroll, bumper, explorer, indexer, virtualTxRepository) {
16686
17528
  const { chain } = await indexer.getVtxoChain(toUnroll);
@@ -16714,7 +17556,7 @@ var Unroll;
16714
17556
  txid: next.txid,
16715
17557
  psbt,
16716
17558
  expiresAt: null,
16717
- type: chainTxTypeToChainedExit(next.type)
17559
+ type: chainTxTypeToChainedExit2(next.type)
16718
17560
  };
16719
17561
  try {
16720
17562
  await repo.upsertVirtualTxs([cached]);
@@ -16892,99 +17734,6 @@ function doWait(onchainProvider, txid) {
16892
17734
  });
16893
17735
  };
16894
17736
  }
16895
-
16896
- // src/wallet/exit/chain.ts
16897
- var isCommitment = (t) => t === "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */ || t === "INDEXER_CHAINED_TX_TYPE_UNSPECIFIED" /* UNSPECIFIED */;
16898
- async function buildExitDag(params) {
16899
- const byTxid = /* @__PURE__ */ new Map();
16900
- for (const vtxo of params.vtxos) {
16901
- const outpoint = `${vtxo.txid}:${vtxo.vout}`;
16902
- let pageIndex = 0;
16903
- let hasMore = true;
16904
- while (hasMore) {
16905
- const { chain, page } = await params.indexer.getVtxoChain(
16906
- { txid: vtxo.txid, vout: vtxo.vout },
16907
- { pageIndex, pageSize: DEFAULT_PAGE_SIZE }
16908
- );
16909
- for (const chainTx of chain) {
16910
- const existing = byTxid.get(chainTx.txid);
16911
- if (existing) {
16912
- existing.forVtxos.add(outpoint);
16913
- } else {
16914
- byTxid.set(chainTx.txid, { ...chainTx, forVtxos: /* @__PURE__ */ new Set([outpoint]) });
16915
- }
16916
- }
16917
- hasMore = page ? chain.length === DEFAULT_PAGE_SIZE : false;
16918
- pageIndex++;
16919
- }
16920
- }
16921
- const pending = new Map(
16922
- [...byTxid.values()].filter((tx) => !isCommitment(tx.type)).map((tx) => [tx.txid, tx])
16923
- );
16924
- const inDegree = /* @__PURE__ */ new Map();
16925
- for (const tx of pending.values()) {
16926
- const parents = tx.spends.filter((p) => pending.has(p));
16927
- inDegree.set(tx.txid, parents.length);
16928
- }
16929
- const order = [];
16930
- const queue = [...pending.values()].filter((tx) => inDegree.get(tx.txid) === 0).sort((a, b) => a.txid.localeCompare(b.txid));
16931
- while (queue.length > 0) {
16932
- const tx = queue.shift();
16933
- order.push(tx);
16934
- const children = [...pending.values()].filter((c) => c.spends.includes(tx.txid)).sort((a, b) => a.txid.localeCompare(b.txid));
16935
- for (const child of children) {
16936
- const deg = inDegree.get(child.txid) - 1;
16937
- inDegree.set(child.txid, deg);
16938
- if (deg === 0) queue.push(child);
16939
- }
16940
- }
16941
- if (order.length !== pending.size) {
16942
- throw new Error("inconsistent vtxo chain from indexer (cycle detected)");
16943
- }
16944
- const nodes = [];
16945
- for (const tx of order) {
16946
- let confirmed = false;
16947
- try {
16948
- const status = await params.onchain.getTxStatus(tx.txid);
16949
- confirmed = status.confirmed;
16950
- } catch {
16951
- confirmed = false;
16952
- }
16953
- const expires = Number(tx.expiresAt);
16954
- nodes.push({
16955
- txid: tx.txid,
16956
- type: tx.type,
16957
- expiresAt: Number.isFinite(expires) && expires > 0 ? expires : void 0,
16958
- forVtxos: [...tx.forVtxos].sort(),
16959
- confirmed
16960
- });
16961
- }
16962
- return nodes;
16963
- }
16964
- function topoSortByDeps(items, idOf, depsOf) {
16965
- const produced = new Set(items.map(idOf));
16966
- const emitted = /* @__PURE__ */ new Set();
16967
- const ordered = [];
16968
- let remaining = items;
16969
- while (remaining.length > 0) {
16970
- const next = [];
16971
- let progress = false;
16972
- for (const item of remaining) {
16973
- if (depsOf(item).every((d) => !produced.has(d) || emitted.has(d))) {
16974
- ordered.push(item);
16975
- emitted.add(idOf(item));
16976
- progress = true;
16977
- } else {
16978
- next.push(item);
16979
- }
16980
- }
16981
- if (!progress) {
16982
- throw new Error("topological sort: cycle or unsatisfiable dependency");
16983
- }
16984
- remaining = next;
16985
- }
16986
- return ordered;
16987
- }
16988
17737
  function extraWitnessSize(path) {
16989
17738
  return (path.extraWitness ?? []).reduce((sum, item) => sum + 1 + item.length, 0);
16990
17739
  }
@@ -17091,27 +17840,22 @@ async function computeExitLayout(opts, feeRate) {
17091
17840
  const { wallet, onchainWallet } = opts;
17092
17841
  const vtxos = await selectExitVtxos(opts);
17093
17842
  if (vtxos.length === 0) throw new Error("no vtxos to exit");
17843
+ const resolver = createExitChainResolver({
17844
+ indexer: wallet.indexerProvider,
17845
+ repository: wallet.virtualTxRepository,
17846
+ extraSources: wallet.exitDataCapture?.sources
17847
+ });
17094
17848
  const dag = await buildExitDag({
17095
17849
  vtxos,
17096
- indexer: wallet.indexerProvider,
17850
+ chain: resolver,
17097
17851
  onchain: wallet.onchainProvider
17098
17852
  });
17099
17853
  const pendingNodes = dag.filter((n) => !n.confirmed);
17100
17854
  const psbts = /* @__PURE__ */ new Map();
17101
17855
  if (pendingNodes.length > 0) {
17102
- const txids = pendingNodes.map((n) => n.txid);
17103
- let pageIndex = 0;
17104
- let hasMore = true;
17105
- while (hasMore) {
17106
- const { txs, page } = await wallet.indexerProvider.getVirtualTxs(txids, {
17107
- pageIndex,
17108
- pageSize: DEFAULT_PAGE_SIZE
17109
- });
17110
- for (const psbt of txs) {
17111
- psbts.set(Transaction.fromPSBT(base64.decode(psbt)).id, psbt);
17112
- }
17113
- hasMore = page ? txs.length === DEFAULT_PAGE_SIZE : false;
17114
- pageIndex++;
17856
+ const txs = await resolver.getVirtualTxs(pendingNodes.map((n) => n.txid));
17857
+ for (const psbt of txs) {
17858
+ psbts.set(Transaction.fromPSBT(base64.decode(psbt)).id, psbt);
17115
17859
  }
17116
17860
  }
17117
17861
  const childVsize = Number(
@@ -17259,6 +18003,8 @@ var Executor = class {
17259
18003
  this.pollIntervalMs = opts?.pollIntervalMs ?? 5e3;
17260
18004
  this.feeWallet = opts?.feeWallet;
17261
18005
  }
18006
+ pkg;
18007
+ provider;
17262
18008
  pollIntervalMs;
17263
18009
  feeWallet;
17264
18010
  sleep() {
@@ -17754,6 +18500,6 @@ function isArkContract(str) {
17754
18500
  return str.startsWith(ARKCONTRACT_PREFIX + "=");
17755
18501
  }
17756
18502
 
17757
- export { ActivityRegistry, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
17758
- //# sourceMappingURL=chunk-FMI2GKYO.js.map
17759
- //# sourceMappingURL=chunk-FMI2GKYO.js.map
18503
+ export { ActivityRegistry, ArkNote, AssetManager, BIP322, Batch, ChainedTxType, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, INTENT_TERMINAL_STATES, InMemoryContractRepository, InMemoryIntentRepository, InMemoryVirtualTxRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBIntentRepository, IndexedDBVirtualTxRepository, IndexedDBWalletRepository, MESSAGE_BUS_INITIALIZING, MESSAGE_BUS_NOT_INITIALIZED, MIGRATION_KEY, MessageBus, MessageBusInitializingError, MessageBusNotInitializedError, MissingSigningDescriptorError, MnemonicIdentity, OnchainWallet, P2A, Ramps, ReadonlyAssetManager, ReadonlyDescriptorIdentity, ReadonlySingleKey, ReadonlyWallet, ReadonlyWalletError, RestDelegateProvider, RestDelegatorProvider, SeedIdentity, ServiceWorkerReadonlyWallet, ServiceWorkerTimeoutError, ServiceWorkerWallet, SingleKey, TxTree, TxType, TxWeightEstimator, UnilateralExit, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, createExitChainResolver, decodeArkContract, deserializeAssets, deserializeExitPackage, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, intentMatchesFilter, intentPageBounds, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isRetryableProviderError, isSpendable, isSubdust, isTerminalIntentState, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, mergeChainedTxType, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeExitPackage, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
18504
+ //# sourceMappingURL=chunk-P6VBP3CT.js.map
18505
+ //# sourceMappingURL=chunk-P6VBP3CT.js.map