@arkade-os/sdk 0.4.41 → 0.4.43

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 (69) hide show
  1. package/dist/adapters/expo.cjs +4 -4
  2. package/dist/adapters/expo.d.cts +2 -2
  3. package/dist/adapters/expo.d.ts +2 -2
  4. package/dist/adapters/expo.js +2 -2
  5. package/dist/adapters/indexedDB.cjs +3 -3
  6. package/dist/adapters/indexedDB.js +2 -2
  7. package/dist/{ark-CSqMJczW.d.cts → ark-I8jfe5VE.d.cts} +157 -2
  8. package/dist/{ark-CSqMJczW.d.ts → ark-I8jfe5VE.d.ts} +157 -2
  9. package/dist/{asyncStorageTaskQueue-SZPrhw23.d.ts → asyncStorageTaskQueue-CjoL-35w.d.ts} +1 -1
  10. package/dist/{asyncStorageTaskQueue-79XdGsdW.d.cts → asyncStorageTaskQueue-D1FfHfgK.d.cts} +1 -1
  11. package/dist/{chunk-ALISWEQ5.cjs → chunk-3AQNLQPM.cjs} +39 -4
  12. package/dist/chunk-3AQNLQPM.cjs.map +1 -0
  13. package/dist/{chunk-5XAYUPK4.js → chunk-763XHDAJ.js} +39 -5
  14. package/dist/chunk-763XHDAJ.js.map +1 -0
  15. package/dist/{chunk-D5MQYYYF.js → chunk-B4R36PRN.js} +1106 -357
  16. package/dist/chunk-B4R36PRN.js.map +1 -0
  17. package/dist/{chunk-KNASU3QI.cjs → chunk-JIWCE2IK.cjs} +7 -7
  18. package/dist/{chunk-KNASU3QI.cjs.map → chunk-JIWCE2IK.cjs.map} +1 -1
  19. package/dist/{chunk-WBF3FNS3.cjs → chunk-PLJZRIFZ.cjs} +11 -11
  20. package/dist/{chunk-WBF3FNS3.cjs.map → chunk-PLJZRIFZ.cjs.map} +1 -1
  21. package/dist/{chunk-3MPXMLP4.cjs → chunk-QGNTDGMN.cjs} +1190 -430
  22. package/dist/chunk-QGNTDGMN.cjs.map +1 -0
  23. package/dist/{chunk-MYI333YH.js → chunk-US274KZO.js} +3 -3
  24. package/dist/{chunk-MYI333YH.js.map → chunk-US274KZO.js.map} +1 -1
  25. package/dist/{chunk-DDG4RE5F.js → chunk-VKKLOT2E.js} +3 -3
  26. package/dist/{chunk-DDG4RE5F.js.map → chunk-VKKLOT2E.js.map} +1 -1
  27. package/dist/contracts/handlers/index.d.cts +3 -3
  28. package/dist/contracts/handlers/index.d.ts +3 -3
  29. package/dist/{delegate-Bqwe-kbj.d.cts → delegate-BRqbh2fW.d.cts} +1 -1
  30. package/dist/{delegate-BTn9BK8E.d.ts → delegate-CgjVjYOP.d.ts} +1 -1
  31. package/dist/{index-aYiHTMkX.d.cts → index-DHuXlDEg.d.cts} +2 -2
  32. package/dist/{index-kITujXlS.d.ts → index-DvWNB_rl.d.ts} +2 -2
  33. package/dist/index.cjs +150 -118
  34. package/dist/index.d.cts +113 -9
  35. package/dist/index.d.ts +113 -9
  36. package/dist/index.js +2 -2
  37. package/dist/repositories/realm/index.cjs +268 -12
  38. package/dist/repositories/realm/index.cjs.map +1 -1
  39. package/dist/repositories/realm/index.d.cts +101 -3
  40. package/dist/repositories/realm/index.d.ts +101 -3
  41. package/dist/repositories/realm/index.js +254 -4
  42. package/dist/repositories/realm/index.js.map +1 -1
  43. package/dist/repositories/sqlite/index.cjs +366 -40
  44. package/dist/repositories/sqlite/index.cjs.map +1 -1
  45. package/dist/repositories/sqlite/index.d.cts +52 -6
  46. package/dist/repositories/sqlite/index.d.ts +52 -6
  47. package/dist/repositories/sqlite/index.js +353 -32
  48. package/dist/repositories/sqlite/index.js.map +1 -1
  49. package/dist/{taskRunner-DRHElPMG.d.cts → taskRunner-BSnslNmq.d.cts} +1 -1
  50. package/dist/{taskRunner-BuBAZQwB.d.ts → taskRunner-Djlhfgi1.d.ts} +1 -1
  51. package/dist/wallet/expo/background.cjs +12 -12
  52. package/dist/wallet/expo/background.d.cts +3 -3
  53. package/dist/wallet/expo/background.d.ts +3 -3
  54. package/dist/wallet/expo/background.js +4 -4
  55. package/dist/wallet/expo/index.cjs +12 -12
  56. package/dist/wallet/expo/index.d.cts +4 -4
  57. package/dist/wallet/expo/index.d.ts +4 -4
  58. package/dist/wallet/expo/index.js +3 -3
  59. package/dist/{wallet-CTEQpKG9.d.ts → wallet-BsqRU8KM.d.cts} +29 -5
  60. package/dist/{wallet-BZgRfnAE.d.cts → wallet-D9z7wssl.d.ts} +29 -5
  61. package/dist/worker/expo/index.cjs +7 -7
  62. package/dist/worker/expo/index.d.cts +4 -4
  63. package/dist/worker/expo/index.d.ts +4 -4
  64. package/dist/worker/expo/index.js +3 -3
  65. package/package.json +2 -2
  66. package/dist/chunk-3MPXMLP4.cjs.map +0 -1
  67. package/dist/chunk-5XAYUPK4.js.map +0 -1
  68. package/dist/chunk-ALISWEQ5.cjs.map +0 -1
  69. package/dist/chunk-D5MQYYYF.js.map +0 -1
@@ -1,4 +1,4 @@
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-5XAYUPK4.js';
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-763XHDAJ.js';
2
2
  import { isMainnetDescriptor, descriptorIsOurs, contractHandlers, DEFAULT_PAGE_SIZE, DelegateVtxo, WALLET_RECEIVE_SOURCE, deriveDescriptorLeafPubKey, DefaultVtxo, BoardingContractHandler } from './chunk-YNEDJTX2.js';
3
3
  import { VtxoScript, timelockToSequence, DEFAULT_NETWORK, DEFAULT_NETWORK_NAME, decodeTapscript, scriptFromTapLeafScript, CLTVMultisigTapscript, ArkAddress, CSVMultisigTapscript, getSequence, getNetwork, MultisigTapscript, networks as networks$1, DEFAULT_ARKADE_SERVER_URL } from './chunk-BE5MUB7T.js';
4
4
  import { sha256, hash160, sha256x2, concatBytes, randomPrivateKeyBytes, pubECDSA, pubSchnorr, equalBytes as equalBytes$1 } from '@scure/btc-signer/utils.js';
@@ -2644,7 +2644,7 @@ function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
2644
2644
  );
2645
2645
  }
2646
2646
  function isMigrationCapable(wallet) {
2647
- return "arkProvider" in wallet && "arkServerPublicKey" in wallet && "onchainProvider" in wallet && typeof wallet.rotateServerSigner === "function" && typeof wallet.sendSelectedVtxosToSelf === "function" && typeof wallet.getBoardingUtxosForSigners === "function";
2647
+ return "arkProvider" in wallet && "arkServerPublicKey" in wallet && "onchainProvider" in wallet && typeof wallet.rotateServerSigner === "function" && typeof wallet.refreshDeprecatedSigners === "function" && typeof wallet.sendSelectedVtxosToSelf === "function" && typeof wallet.getBoardingUtxosForSigners === "function";
2648
2648
  }
2649
2649
  function classifiedToRef(c) {
2650
2650
  return {
@@ -3168,6 +3168,7 @@ var VtxoManager = class _VtxoManager {
3168
3168
  async migrateCore(options) {
3169
3169
  const wallet = this.requireMigrationCapableWallet();
3170
3170
  const info = await wallet.arkProvider.getInfo();
3171
+ wallet.refreshDeprecatedSigners(info);
3171
3172
  const signerSet = signerSetFromInfo(info);
3172
3173
  const nowSeconds = Math.floor(Date.now() / 1e3);
3173
3174
  const walletSignerHex = hex.encode(wallet.arkServerPublicKey);
@@ -4136,6 +4137,123 @@ function readUInt32BE(array, offset) {
4136
4137
  function noteTapscript(preimageHash) {
4137
4138
  return Script.encode(["SHA256", preimageHash, "EQUAL"]);
4138
4139
  }
4140
+
4141
+ // src/repositories/intentRepository.ts
4142
+ var ALL_INTENT_STATES = [
4143
+ "waiting_to_submit",
4144
+ "waiting_for_batch",
4145
+ "batch_in_progress",
4146
+ "batch_failed",
4147
+ "batch_succeeded",
4148
+ "cancelled"
4149
+ ];
4150
+ var INTENT_TERMINAL_STATES = /* @__PURE__ */ new Set([
4151
+ "batch_failed",
4152
+ "batch_succeeded",
4153
+ "cancelled"
4154
+ ]);
4155
+ function isTerminalIntentState(s) {
4156
+ return INTENT_TERMINAL_STATES.has(s);
4157
+ }
4158
+ function assertIntentIdUnique(intent, existing) {
4159
+ if (intent.intentId === void 0) return;
4160
+ for (const e of existing) {
4161
+ if (e.intentTxId !== intent.intentTxId && e.intentId === intent.intentId) {
4162
+ throw new Error(
4163
+ `intentId "${intent.intentId}" is already used by intent ${e.intentTxId}`
4164
+ );
4165
+ }
4166
+ }
4167
+ }
4168
+ function intentMatchesFilter(i, f) {
4169
+ if (f.intentTxIds && !f.intentTxIds.includes(i.intentTxId)) return false;
4170
+ if (f.intentIds && (!i.intentId || !f.intentIds.includes(i.intentId))) return false;
4171
+ if (f.states && !f.states.includes(i.state)) return false;
4172
+ if (f.containingInputs) {
4173
+ const keys = new Set(i.intentVtxos.map((o) => `${o.txid}:${o.vout}`));
4174
+ if (!f.containingInputs.some((o) => keys.has(`${o.txid}:${o.vout}`))) return false;
4175
+ }
4176
+ if (f.validAt !== void 0) {
4177
+ if (i.validFrom !== void 0 && f.validAt < i.validFrom) return false;
4178
+ if (i.validUntil !== void 0 && f.validAt > i.validUntil) return false;
4179
+ }
4180
+ if (f.searchText) {
4181
+ const hay = [i.intentId, i.batchId, i.commitmentTransactionId].filter(Boolean).join(" ");
4182
+ if (!hay.includes(f.searchText)) return false;
4183
+ }
4184
+ return true;
4185
+ }
4186
+ function intentPageBounds(filter, total) {
4187
+ const skip = Math.max(0, filter?.skip ?? 0);
4188
+ const take = filter?.take === void 0 ? total : Math.max(0, filter.take);
4189
+ return { skip, end: skip + take };
4190
+ }
4191
+
4192
+ // src/wallet/intentStateReducer.ts
4193
+ function reduceIntentState(current, event) {
4194
+ if (isTerminalIntentState(current)) return current;
4195
+ switch (event.type) {
4196
+ case "batch_started" /* BatchStarted */:
4197
+ return "batch_in_progress";
4198
+ case "batch_finalized" /* BatchFinalized */:
4199
+ return "batch_succeeded";
4200
+ case "batch_failed" /* BatchFailed */:
4201
+ return "batch_failed";
4202
+ default:
4203
+ return current;
4204
+ }
4205
+ }
4206
+ function applySettlementEventToIntent(intent, event) {
4207
+ const next = reduceIntentState(intent.state, event);
4208
+ if (next === intent.state) return void 0;
4209
+ const updated = { ...intent, state: next };
4210
+ if ("id" in event && typeof event.id === "string") {
4211
+ updated.batchId = event.id;
4212
+ }
4213
+ if (event.type === "batch_finalized" /* BatchFinalized */) {
4214
+ updated.commitmentTransactionId = event.commitmentTxid;
4215
+ }
4216
+ if (event.type === "batch_failed" /* BatchFailed */) {
4217
+ updated.cancellationReason = event.reason;
4218
+ }
4219
+ return updated;
4220
+ }
4221
+
4222
+ // src/wallet/intentPersistenceHandler.ts
4223
+ async function persistTransition(deps, event) {
4224
+ const repo = deps.intentRepository;
4225
+ if (!repo) return;
4226
+ try {
4227
+ const cur = (await repo.getIntents({ intentTxIds: [deps.intentTxId] }))[0];
4228
+ if (!cur) return;
4229
+ const next = applySettlementEventToIntent(cur, event);
4230
+ if (next) await repo.saveIntent(next);
4231
+ } catch (e) {
4232
+ console.error(`Failed to apply settlement event to intent ${deps.intentTxId}`, e);
4233
+ }
4234
+ }
4235
+ function wrapHandlerWithIntentPersistence(base, deps) {
4236
+ return {
4237
+ ...base,
4238
+ onBatchStarted: async (event) => {
4239
+ const result = await base.onBatchStarted(event);
4240
+ if (!result.skip) await persistTransition(deps, event);
4241
+ return result;
4242
+ },
4243
+ onBatchFinalized: async (event) => {
4244
+ if (base.onBatchFinalized) await base.onBatchFinalized(event);
4245
+ await persistTransition(deps, event);
4246
+ },
4247
+ onBatchFailed: async (event) => {
4248
+ await persistTransition(deps, event);
4249
+ if (base.onBatchFailed) {
4250
+ await base.onBatchFailed(event);
4251
+ return;
4252
+ }
4253
+ throw new Error(event.reason);
4254
+ }
4255
+ };
4256
+ }
4139
4257
  var TxTree = class {
4140
4258
  constructor(root, children = /* @__PURE__ */ new Map()) {
4141
4259
  this.root = root;
@@ -5245,214 +5363,216 @@ function isAnnotated(v) {
5245
5363
  return v.tapTree !== void 0 && v.forfeitTapLeafScript !== void 0 && v.intentTapLeafScript !== void 0;
5246
5364
  }
5247
5365
 
5248
- // src/contracts/vtxoOwnership.ts
5249
- function vtxoOutpoint(vtxo) {
5250
- return `${vtxo.txid}:${vtxo.vout}`;
5251
- }
5252
- function isVtxoForScript(vtxo, script) {
5253
- return !!vtxo.script && vtxo.script === script;
5254
- }
5255
- function filterVtxosForScript(vtxos, script) {
5256
- return vtxos.filter((v) => isVtxoForScript(v, script));
5257
- }
5258
- function warnAndFilterVtxosForScript(vtxos, script, context) {
5259
- const matches = [];
5260
- const rejected = [];
5261
- for (const v of vtxos) {
5262
- if (isVtxoForScript(v, script)) {
5263
- matches.push(v);
5264
- } else {
5265
- rejected.push(`${vtxoOutpoint(v)}(script=${v.script ?? ""})`);
5266
- }
5267
- }
5268
- if (rejected.length > 0) {
5269
- console.warn(
5270
- `${context}: dropped ${rejected.length} wrong-script VTXO(s) for script ${script}: ${rejected.join(", ")}`
5271
- );
5272
- }
5273
- return matches;
5274
- }
5275
- function validateVtxosForScript(vtxos, script, context) {
5276
- const mismatches = vtxos.filter((v) => !isVtxoForScript(v, script));
5277
- if (mismatches.length === 0) return;
5278
- const detail = mismatches.map((v) => `${vtxoOutpoint(v)}(script=${v.script ?? ""})`).join(", ");
5279
- throw new Error(
5280
- `${context}: refusing to persist ${mismatches.length} VTXO(s) whose script does not match ${script}: ${detail}`
5281
- );
5282
- }
5283
- async function getVtxosForContract(repo, contract) {
5284
- return repo.getVtxosForScript ? repo.getVtxosForScript(contract.script) : filterVtxosForScript(await repo.getVtxos(contract.address), contract.script);
5285
- }
5286
- async function saveVtxosForContract(repo, contract, vtxos) {
5287
- if (repo.saveVtxosForScript) {
5288
- return repo.saveVtxosForScript(
5289
- { script: contract.script, address: contract.address },
5290
- vtxos
5291
- );
5292
- }
5293
- validateVtxosForScript(vtxos, contract.script, "saveVtxosForContract");
5294
- return repo.saveVtxos(contract.address, vtxos);
5366
+ // src/repositories/virtualTxRepository.ts
5367
+ var ChainedTxType = /* @__PURE__ */ ((ChainedTxType2) => {
5368
+ ChainedTxType2[ChainedTxType2["Unspecified"] = 0] = "Unspecified";
5369
+ ChainedTxType2[ChainedTxType2["Commitment"] = 1] = "Commitment";
5370
+ ChainedTxType2[ChainedTxType2["Ark"] = 2] = "Ark";
5371
+ ChainedTxType2[ChainedTxType2["Tree"] = 3] = "Tree";
5372
+ ChainedTxType2[ChainedTxType2["Checkpoint"] = 4] = "Checkpoint";
5373
+ return ChainedTxType2;
5374
+ })(ChainedTxType || {});
5375
+ function mergeChainedTxType(incoming, prev) {
5376
+ if (incoming !== 0 /* Unspecified */) return incoming;
5377
+ return prev ?? 0 /* Unspecified */;
5295
5378
  }
5296
5379
 
5297
- // src/repositories/inMemory/walletRepository.ts
5298
- var InMemoryWalletRepository = class {
5380
+ // src/repositories/inMemory/intentRepository.ts
5381
+ var InMemoryIntentRepository = class {
5299
5382
  version = 1;
5300
- vtxosByAddress = /* @__PURE__ */ new Map();
5301
- utxosByAddress = /* @__PURE__ */ new Map();
5302
- txsByAddress = /* @__PURE__ */ new Map();
5303
- walletState = null;
5304
- async getVtxos(address) {
5305
- return this.vtxosByAddress.get(address) ?? [];
5306
- }
5307
- async saveVtxos(address, vtxos) {
5308
- const existing = this.vtxosByAddress.get(address) ?? [];
5309
- const next = mergeByKey(existing, vtxos, (item) => `${item.txid}:${item.vout}`);
5310
- this.vtxosByAddress.set(address, next);
5383
+ byId = /* @__PURE__ */ new Map();
5384
+ async clear() {
5385
+ this.byId.clear();
5386
+ }
5387
+ async saveIntent(intent) {
5388
+ assertIntentIdUnique(intent, this.byId.values());
5389
+ this.byId.set(intent.intentTxId, {
5390
+ ...intent,
5391
+ intentVtxos: [...intent.intentVtxos],
5392
+ partialForfeits: [...intent.partialForfeits],
5393
+ updatedAt: Date.now()
5394
+ });
5311
5395
  }
5312
- async deleteVtxos(address) {
5313
- this.vtxosByAddress.delete(address);
5396
+ async getIntents(filter) {
5397
+ let out = [...this.byId.values()];
5398
+ if (filter) out = out.filter((i) => intentMatchesFilter(i, filter));
5399
+ out.sort((a, b) => a.createdAt - b.createdAt || a.intentTxId.localeCompare(b.intentTxId));
5400
+ const { skip, end } = intentPageBounds(filter, out.length);
5401
+ return out.slice(skip, end).map(clone);
5314
5402
  }
5315
- async getVtxosForScript(script) {
5316
- const allMatches = [];
5317
- for (const bucket of this.vtxosByAddress.values()) {
5318
- for (const vtxo of bucket) {
5319
- if (isVtxoForScript(vtxo, script)) {
5320
- allMatches.push(vtxo);
5321
- }
5322
- }
5323
- }
5324
- return mergeByKey([], allMatches, (item) => `${item.txid}:${item.vout}`);
5403
+ async getLockedVtxoOutpoints() {
5404
+ const out = [];
5405
+ for (const i of this.byId.values())
5406
+ if (!isTerminalIntentState(i.state)) for (const o of i.intentVtxos) out.push({ ...o });
5407
+ return out;
5325
5408
  }
5326
- async saveVtxosForScript(key, vtxos) {
5327
- if (!key.address) {
5328
- throw new Error("InMemoryWalletRepository requires an address");
5329
- }
5330
- for (const vtxo of vtxos) {
5331
- if (!isVtxoForScript(vtxo, key.script)) {
5332
- throw new Error(
5333
- `VTXO ${vtxo.txid}:${vtxo.vout} script mismatch: expected ${key.script}, got ${vtxo.script}`
5334
- );
5335
- }
5336
- }
5337
- return this.saveVtxos(key.address, vtxos);
5409
+ async [Symbol.asyncDispose]() {
5338
5410
  }
5339
- async deleteVtxosForScript(script) {
5340
- for (const [address, bucket] of this.vtxosByAddress.entries()) {
5341
- const next = bucket.filter((v) => !isVtxoForScript(v, script));
5342
- if (next.length === 0) {
5343
- this.vtxosByAddress.delete(address);
5344
- } else {
5345
- this.vtxosByAddress.set(address, next);
5411
+ };
5412
+ var clone = (i) => ({
5413
+ ...i,
5414
+ intentVtxos: i.intentVtxos.map((o) => ({ ...o })),
5415
+ partialForfeits: [...i.partialForfeits]
5416
+ });
5417
+
5418
+ // src/repositories/inMemory/virtualTxRepository.ts
5419
+ var opKey = (o) => `${o.txid}:${o.vout}`;
5420
+ var InMemoryVirtualTxRepository = class {
5421
+ version = 1;
5422
+ txs = /* @__PURE__ */ new Map();
5423
+ branches = /* @__PURE__ */ new Map();
5424
+ async clear() {
5425
+ this.txs.clear();
5426
+ this.branches.clear();
5427
+ }
5428
+ async upsertVirtualTxs(txs) {
5429
+ for (const t of txs) {
5430
+ const prev = this.txs.get(t.txid);
5431
+ if (!prev) {
5432
+ this.txs.set(t.txid, { ...t });
5433
+ continue;
5346
5434
  }
5435
+ this.txs.set(t.txid, {
5436
+ txid: t.txid,
5437
+ psbt: t.psbt ?? prev.psbt,
5438
+ expiresAt: t.expiresAt ?? prev.expiresAt,
5439
+ type: mergeChainedTxType(t.type, prev.type)
5440
+ });
5347
5441
  }
5348
5442
  }
5349
- async getUtxos(address) {
5350
- return this.utxosByAddress.get(address) ?? [];
5351
- }
5352
- async saveUtxos(address, utxos) {
5353
- const existing = this.utxosByAddress.get(address) ?? [];
5354
- const next = mergeByKey(existing, utxos, (item) => `${item.txid}:${item.vout}`);
5355
- this.utxosByAddress.set(address, next);
5356
- }
5357
- async deleteUtxos(address) {
5358
- this.utxosByAddress.delete(address);
5359
- }
5360
- async getTransactionHistory(address) {
5361
- return this.txsByAddress.get(address) ?? [];
5362
- }
5363
- async saveTransactions(address, txs) {
5364
- const existing = this.txsByAddress.get(address) ?? [];
5365
- const next = mergeByKey(existing, txs, serializeTxKey);
5366
- this.txsByAddress.set(address, next);
5443
+ async getVirtualTx(txid) {
5444
+ const t = this.txs.get(txid);
5445
+ return t ? { ...t } : null;
5367
5446
  }
5368
- async deleteTransactions(address) {
5369
- this.txsByAddress.delete(address);
5447
+ async setBranch(vtxo, branch) {
5448
+ this.branches.set(
5449
+ opKey(vtxo),
5450
+ [...branch].sort((a, b) => a.position - b.position)
5451
+ );
5370
5452
  }
5371
- async getWalletState() {
5372
- return this.walletState;
5453
+ async getBranch(vtxo) {
5454
+ const b = this.branches.get(opKey(vtxo)) ?? [];
5455
+ return b.map((x) => this.txs.get(x.virtualTxid)).filter((t) => !!t).map((t) => ({ ...t }));
5373
5456
  }
5374
- async saveWalletState(state) {
5375
- this.walletState = state;
5457
+ async hasBranch(vtxo) {
5458
+ return (this.branches.get(opKey(vtxo)) ?? []).length > 0;
5376
5459
  }
5377
- async clear() {
5378
- this.vtxosByAddress.clear();
5379
- this.utxosByAddress.clear();
5380
- this.txsByAddress.clear();
5381
- this.walletState = null;
5460
+ async pruneForSpentVtxo(vtxo) {
5461
+ const removed = this.branches.get(opKey(vtxo)) ?? [];
5462
+ this.branches.delete(opKey(vtxo));
5463
+ const stillReferenced = /* @__PURE__ */ new Set();
5464
+ for (const b of this.branches.values())
5465
+ for (const e of b) stillReferenced.add(e.virtualTxid);
5466
+ for (const e of removed)
5467
+ if (!stillReferenced.has(e.virtualTxid)) this.txs.delete(e.virtualTxid);
5382
5468
  }
5383
5469
  async [Symbol.asyncDispose]() {
5384
- return;
5385
5470
  }
5386
5471
  };
5387
- function serializeTxKey(tx) {
5388
- const key = tx.key;
5389
- return `${key.boardingTxid}:${key.commitmentTxid}:${key.arkTxid}`;
5390
- }
5391
- function mergeByKey(existing, incoming, toKey) {
5392
- const next = /* @__PURE__ */ new Map();
5393
- existing.forEach((item) => {
5394
- next.set(toKey(item), item);
5395
- });
5396
- incoming.forEach((item) => {
5397
- next.set(toKey(item), item);
5398
- });
5399
- return Array.from(next.values());
5400
- }
5401
5472
 
5402
- // src/repositories/inMemory/contractRepository.ts
5403
- var InMemoryContractRepository = class {
5404
- version = 1;
5405
- contractData = /* @__PURE__ */ new Map();
5406
- collections = /* @__PURE__ */ new Map();
5407
- contractsByScript = /* @__PURE__ */ new Map();
5408
- async clear() {
5409
- this.contractData.clear();
5410
- this.collections.clear();
5411
- this.contractsByScript.clear();
5412
- }
5413
- // Contract entity management methods
5414
- async getContracts(filter) {
5415
- const contracts = this.contractsByScript.values();
5416
- if (!filter) {
5417
- return [...contracts];
5473
+ // src/repositories/indexedDB/idbUtils.ts
5474
+ var promisifyRequest = (request) => new Promise((resolve, reject) => {
5475
+ request.onsuccess = () => resolve(request.result);
5476
+ request.onerror = () => reject(request.error);
5477
+ });
5478
+ var awaitTransaction = (transaction) => new Promise((resolve, reject) => {
5479
+ transaction.oncomplete = () => resolve();
5480
+ transaction.onerror = () => reject(transaction.error);
5481
+ transaction.onabort = () => reject(transaction.error ?? new Error("transaction aborted"));
5482
+ });
5483
+
5484
+ // src/repositories/indexedDB/manager.ts
5485
+ function getGlobalObject() {
5486
+ if (typeof globalThis !== "undefined") {
5487
+ if (typeof globalThis.self === "object" && globalThis.self !== null) {
5488
+ return { globalObject: globalThis.self };
5418
5489
  }
5419
- const matches = (value, criterion) => {
5420
- if (criterion === void 0) {
5421
- return true;
5422
- }
5423
- return Array.isArray(criterion) ? criterion.includes(value) : value === criterion;
5424
- };
5425
- const results = [];
5426
- for (const contract of contracts) {
5427
- if (matches(contract.script, filter.script) && matches(contract.state, filter.state) && matches(contract.type, filter.type)) {
5428
- results.push(contract);
5429
- }
5490
+ if (typeof globalThis.window === "object" && globalThis.window !== null) {
5491
+ return { globalObject: globalThis.window };
5430
5492
  }
5431
- return results;
5432
- }
5433
- async saveContract(contract) {
5434
- this.contractsByScript.set(contract.script, contract);
5493
+ return { globalObject: globalThis };
5435
5494
  }
5436
- async deleteContract(script) {
5437
- this.contractsByScript.delete(script);
5495
+ throw new Error("Global object not found");
5496
+ }
5497
+ var dbCache = /* @__PURE__ */ new Map();
5498
+ var refCounts = /* @__PURE__ */ new Map();
5499
+ async function openDatabase(dbName, dbVersion, initDatabase2) {
5500
+ const { globalObject } = getGlobalObject();
5501
+ if (!globalObject.indexedDB) {
5502
+ throw new Error("IndexedDB is not available in this environment");
5438
5503
  }
5439
- async [Symbol.asyncDispose]() {
5440
- return;
5504
+ const cached = dbCache.get(dbName);
5505
+ if (cached) {
5506
+ if (cached.version !== dbVersion) {
5507
+ throw new Error(
5508
+ `Database "${dbName}" already opened with version ${cached.version}; requested ${dbVersion}`
5509
+ );
5510
+ }
5511
+ refCounts.set(dbName, (refCounts.get(dbName) ?? 0) + 1);
5512
+ return cached.promise;
5441
5513
  }
5442
- };
5443
- function scriptFromArkAddress(address) {
5444
- return hex.encode(ArkAddress.decode(address).pkScript);
5445
- }
5446
-
5447
- // src/repositories/indexedDB/schema.ts
5448
- var STORE_VTXOS = "vtxos";
5449
- var STORE_UTXOS = "utxos";
5450
- var STORE_TRANSACTIONS = "transactions";
5451
- var STORE_WALLET_STATE = "walletState";
5452
- var STORE_CONTRACTS = "contracts";
5453
- var LEGACY_STORE_CONTRACT_COLLECTIONS = "contractsCollections";
5454
- var DB_VERSION = 3;
5455
- function initDatabase(db, oldVersion, transaction) {
5514
+ const dbPromise = new Promise((resolve, reject) => {
5515
+ const request = globalObject.indexedDB.open(dbName, dbVersion);
5516
+ request.onerror = () => {
5517
+ dbCache.delete(dbName);
5518
+ refCounts.delete(dbName);
5519
+ reject(request.error);
5520
+ };
5521
+ request.onsuccess = () => {
5522
+ const db = request.result;
5523
+ db.onversionchange = () => {
5524
+ db.close();
5525
+ dbCache.delete(dbName);
5526
+ refCounts.delete(dbName);
5527
+ };
5528
+ resolve(db);
5529
+ };
5530
+ request.onupgradeneeded = (event) => {
5531
+ const db = request.result;
5532
+ initDatabase2(db, event.oldVersion, request.transaction);
5533
+ };
5534
+ request.onblocked = () => {
5535
+ console.warn("Database upgrade blocked - close other tabs/connections");
5536
+ };
5537
+ });
5538
+ dbCache.set(dbName, { version: dbVersion, promise: dbPromise });
5539
+ refCounts.set(dbName, 1);
5540
+ return dbPromise;
5541
+ }
5542
+ async function closeDatabase(dbName) {
5543
+ const cachedEntry = dbCache.get(dbName);
5544
+ if (!cachedEntry) return false;
5545
+ const count = (refCounts.get(dbName) ?? 1) - 1;
5546
+ if (count > 0) {
5547
+ refCounts.set(dbName, count);
5548
+ return false;
5549
+ }
5550
+ refCounts.delete(dbName);
5551
+ dbCache.delete(dbName);
5552
+ try {
5553
+ const db = await cachedEntry.promise;
5554
+ db.close();
5555
+ } catch {
5556
+ }
5557
+ return true;
5558
+ }
5559
+ function scriptFromArkAddress(address) {
5560
+ return hex.encode(ArkAddress.decode(address).pkScript);
5561
+ }
5562
+
5563
+ // src/repositories/indexedDB/schema.ts
5564
+ var STORE_VTXOS = "vtxos";
5565
+ var STORE_UTXOS = "utxos";
5566
+ var STORE_TRANSACTIONS = "transactions";
5567
+ var STORE_WALLET_STATE = "walletState";
5568
+ var STORE_CONTRACTS = "contracts";
5569
+ var STORE_INTENTS = "intents";
5570
+ var STORE_VIRTUAL_TXS = "virtualTxs";
5571
+ var STORE_VTXO_BRANCHES = "vtxoBranches";
5572
+ var LEGACY_STORE_CONTRACT_COLLECTIONS = "contractsCollections";
5573
+ var DB_VERSION = 3;
5574
+ var INTENT_DB_VERSION = 5;
5575
+ function initDatabase(db, oldVersion, transaction) {
5456
5576
  if (!db.objectStoreNames.contains(STORE_VTXOS)) {
5457
5577
  const vtxosStore = db.createObjectStore(STORE_VTXOS, {
5458
5578
  keyPath: ["address", "txid", "vout"]
@@ -5590,33 +5710,506 @@ function initDatabase(db, oldVersion, transaction) {
5590
5710
  });
5591
5711
  }
5592
5712
  }
5593
- if (!db.objectStoreNames.contains(LEGACY_STORE_CONTRACT_COLLECTIONS)) {
5594
- db.createObjectStore(LEGACY_STORE_CONTRACT_COLLECTIONS, {
5595
- keyPath: "key"
5596
- });
5713
+ if (!db.objectStoreNames.contains(LEGACY_STORE_CONTRACT_COLLECTIONS)) {
5714
+ db.createObjectStore(LEGACY_STORE_CONTRACT_COLLECTIONS, {
5715
+ keyPath: "key"
5716
+ });
5717
+ }
5718
+ if (oldVersion >= 1 && oldVersion < 3 && transaction) {
5719
+ const vtxosStore = transaction.objectStore(STORE_VTXOS);
5720
+ if (!vtxosStore.indexNames.contains("script")) {
5721
+ vtxosStore.createIndex("script", "script", { unique: false });
5722
+ }
5723
+ backfillVtxoScripts(transaction);
5724
+ }
5725
+ }
5726
+ function initDatabaseWithIntents(db, oldVersion, transaction) {
5727
+ initDatabase(db, oldVersion, transaction);
5728
+ if (!db.objectStoreNames.contains(STORE_INTENTS)) {
5729
+ const intentsStore = db.createObjectStore(STORE_INTENTS, {
5730
+ keyPath: "intentTxId"
5731
+ });
5732
+ intentsStore.createIndex("intentId", "intentId", { unique: true });
5733
+ intentsStore.createIndex("state", "state", { unique: false });
5734
+ }
5735
+ if (!db.objectStoreNames.contains(STORE_VIRTUAL_TXS)) {
5736
+ db.createObjectStore(STORE_VIRTUAL_TXS, { keyPath: "txid" });
5737
+ }
5738
+ if (!db.objectStoreNames.contains(STORE_VTXO_BRANCHES)) {
5739
+ const branchesStore = db.createObjectStore(STORE_VTXO_BRANCHES, {
5740
+ keyPath: ["vtxoTxid", "vtxoVout", "position"]
5741
+ });
5742
+ branchesStore.createIndex("vtxo", ["vtxoTxid", "vtxoVout"], { unique: false });
5743
+ branchesStore.createIndex("virtualTxid", "virtualTxid", { unique: false });
5744
+ }
5745
+ if (oldVersion === 4 && transaction) {
5746
+ const intentsStore = transaction.objectStore(STORE_INTENTS);
5747
+ if (intentsStore.indexNames.contains("intentId")) {
5748
+ intentsStore.deleteIndex("intentId");
5749
+ }
5750
+ dedupeIntentIds(intentsStore, () => {
5751
+ intentsStore.createIndex("intentId", "intentId", { unique: true });
5752
+ });
5753
+ }
5754
+ }
5755
+ function dedupeIntentIds(store, onComplete) {
5756
+ const seen = /* @__PURE__ */ new Set();
5757
+ const cursorRequest = store.openCursor();
5758
+ cursorRequest.onsuccess = () => {
5759
+ const cursor = cursorRequest.result;
5760
+ if (!cursor) {
5761
+ onComplete();
5762
+ return;
5763
+ }
5764
+ const id = cursor.value.intentId;
5765
+ if (id != null) {
5766
+ if (seen.has(id)) {
5767
+ cursor.delete();
5768
+ } else {
5769
+ seen.add(id);
5770
+ }
5771
+ }
5772
+ cursor.continue();
5773
+ };
5774
+ }
5775
+ function backfillVtxoScripts(transaction) {
5776
+ const store = transaction.objectStore(STORE_VTXOS);
5777
+ const cursorRequest = store.openCursor();
5778
+ cursorRequest.onsuccess = () => {
5779
+ const cursor = cursorRequest.result;
5780
+ if (!cursor) return;
5781
+ const value = cursor.value;
5782
+ if (!value.script) {
5783
+ value.script = scriptFromArkAddress(value.address);
5784
+ cursor.update(value);
5785
+ }
5786
+ cursor.continue();
5787
+ };
5788
+ }
5789
+
5790
+ // src/worker/browser/utils.ts
5791
+ var DEFAULT_DB_NAME = "arkade-service-worker";
5792
+ var DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = 1e4;
5793
+ function normalizeOptions2(pathOrOptions) {
5794
+ if (typeof pathOrOptions === "string") {
5795
+ return {
5796
+ path: pathOrOptions,
5797
+ activationTimeoutMs: DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5798
+ };
5799
+ }
5800
+ return {
5801
+ path: pathOrOptions.path,
5802
+ activationTimeoutMs: pathOrOptions.activationTimeoutMs ?? DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5803
+ };
5804
+ }
5805
+ function waitForServiceWorkerReady(timeoutMs) {
5806
+ return new Promise((resolve, reject) => {
5807
+ const timeoutId = setTimeout(() => {
5808
+ reject(new Error(`Service worker activation timed out after ${timeoutMs}ms`));
5809
+ }, timeoutMs);
5810
+ navigator.serviceWorker.ready.then((registration) => {
5811
+ clearTimeout(timeoutId);
5812
+ resolve(registration);
5813
+ }).catch((error) => {
5814
+ clearTimeout(timeoutId);
5815
+ reject(error);
5816
+ });
5817
+ });
5818
+ }
5819
+ async function setupServiceWorker(pathOrOptions) {
5820
+ if (!("serviceWorker" in navigator)) {
5821
+ throw new Error("Service workers are not supported in this browser");
5822
+ }
5823
+ const { path, activationTimeoutMs } = normalizeOptions2(pathOrOptions);
5824
+ const registration = await navigator.serviceWorker.register(path);
5825
+ await registration.update();
5826
+ const serviceWorker = registration.active || registration.waiting || registration.installing;
5827
+ if (!serviceWorker) {
5828
+ throw new Error("Failed to get service worker instance");
5829
+ }
5830
+ if (serviceWorker.state === "activated") {
5831
+ return serviceWorker;
5832
+ }
5833
+ const readyRegistration = await waitForServiceWorkerReady(activationTimeoutMs);
5834
+ if (!readyRegistration.active) {
5835
+ throw new Error("Service worker registration is ready but has no active worker");
5836
+ }
5837
+ return readyRegistration.active;
5838
+ }
5839
+
5840
+ // src/repositories/indexedDB/virtualTxRepository.ts
5841
+ var IndexedDBVirtualTxRepository = class {
5842
+ constructor(dbName = DEFAULT_DB_NAME) {
5843
+ this.dbName = dbName;
5844
+ }
5845
+ version = 1;
5846
+ db = null;
5847
+ async getDB() {
5848
+ if (!this.db)
5849
+ this.db = await openDatabase(this.dbName, INTENT_DB_VERSION, initDatabaseWithIntents);
5850
+ return this.db;
5851
+ }
5852
+ async clear() {
5853
+ const db = await this.getDB();
5854
+ const transaction = db.transaction([STORE_VIRTUAL_TXS, STORE_VTXO_BRANCHES], "readwrite");
5855
+ transaction.objectStore(STORE_VIRTUAL_TXS).clear();
5856
+ transaction.objectStore(STORE_VTXO_BRANCHES).clear();
5857
+ await awaitTransaction(transaction);
5858
+ }
5859
+ async upsertVirtualTxs(txs) {
5860
+ if (txs.length === 0) return;
5861
+ const db = await this.getDB();
5862
+ const merged = /* @__PURE__ */ new Map();
5863
+ for (const tx of txs) {
5864
+ const acc = merged.get(tx.txid);
5865
+ merged.set(
5866
+ tx.txid,
5867
+ acc ? {
5868
+ txid: tx.txid,
5869
+ psbt: tx.psbt ?? acc.psbt,
5870
+ expiresAt: tx.expiresAt ?? acc.expiresAt,
5871
+ type: mergeChainedTxType(tx.type, acc.type)
5872
+ } : tx
5873
+ );
5874
+ }
5875
+ const transaction = db.transaction([STORE_VIRTUAL_TXS], "readwrite");
5876
+ const store = transaction.objectStore(STORE_VIRTUAL_TXS);
5877
+ for (const tx of merged.values()) {
5878
+ const getReq = store.get(tx.txid);
5879
+ getReq.onsuccess = () => {
5880
+ const prev = getReq.result;
5881
+ store.put({
5882
+ txid: tx.txid,
5883
+ psbt: tx.psbt ?? prev?.psbt ?? null,
5884
+ expiresAt: tx.expiresAt ?? prev?.expiresAt ?? null,
5885
+ type: mergeChainedTxType(tx.type, prev?.type)
5886
+ });
5887
+ };
5888
+ }
5889
+ await awaitTransaction(transaction);
5890
+ }
5891
+ async getVirtualTx(txid) {
5892
+ const db = await this.getDB();
5893
+ const store = db.transaction([STORE_VIRTUAL_TXS], "readonly").objectStore(STORE_VIRTUAL_TXS);
5894
+ const r = await promisifyRequest(store.get(txid));
5895
+ return r ?? null;
5896
+ }
5897
+ async setBranch(vtxo, branch) {
5898
+ const db = await this.getDB();
5899
+ const transaction = db.transaction([STORE_VTXO_BRANCHES], "readwrite");
5900
+ const store = transaction.objectStore(STORE_VTXO_BRANCHES);
5901
+ const getAllReq = store.index("vtxo").getAll(IDBKeyRange.only([vtxo.txid, vtxo.vout]));
5902
+ getAllReq.onsuccess = () => {
5903
+ const existing = getAllReq.result;
5904
+ for (const e of existing) store.delete([e.vtxoTxid, e.vtxoVout, e.position]);
5905
+ for (const b of branch) store.put(b);
5906
+ };
5907
+ await awaitTransaction(transaction);
5908
+ }
5909
+ async getBranch(vtxo) {
5910
+ const db = await this.getDB();
5911
+ const transaction = db.transaction([STORE_VTXO_BRANCHES, STORE_VIRTUAL_TXS], "readonly");
5912
+ const branchStore = transaction.objectStore(STORE_VTXO_BRANCHES);
5913
+ const txStore = transaction.objectStore(STORE_VIRTUAL_TXS);
5914
+ const getAllReq = branchStore.index("vtxo").getAll(IDBKeyRange.only([vtxo.txid, vtxo.vout]));
5915
+ const byTxid = /* @__PURE__ */ new Map();
5916
+ let ordered = [];
5917
+ getAllReq.onsuccess = () => {
5918
+ ordered = getAllReq.result.slice().sort((a, b) => a.position - b.position);
5919
+ for (const b of ordered) {
5920
+ const txReq = txStore.get(b.virtualTxid);
5921
+ txReq.onsuccess = () => {
5922
+ const r = txReq.result;
5923
+ if (r) byTxid.set(b.virtualTxid, r);
5924
+ };
5925
+ }
5926
+ };
5927
+ await awaitTransaction(transaction);
5928
+ const out = [];
5929
+ for (const b of ordered) {
5930
+ const tx = byTxid.get(b.virtualTxid);
5931
+ if (tx) out.push(tx);
5932
+ }
5933
+ return out;
5934
+ }
5935
+ async hasBranch(vtxo) {
5936
+ const db = await this.getDB();
5937
+ const vtxoIndex = db.transaction([STORE_VTXO_BRANCHES], "readonly").objectStore(STORE_VTXO_BRANCHES).index("vtxo");
5938
+ const count = await promisifyRequest(
5939
+ vtxoIndex.count(IDBKeyRange.only([vtxo.txid, vtxo.vout]))
5940
+ );
5941
+ return count > 0;
5942
+ }
5943
+ async pruneForSpentVtxo(vtxo) {
5944
+ const db = await this.getDB();
5945
+ const transaction = db.transaction([STORE_VTXO_BRANCHES, STORE_VIRTUAL_TXS], "readwrite");
5946
+ const branches = transaction.objectStore(STORE_VTXO_BRANCHES);
5947
+ const txStore = transaction.objectStore(STORE_VIRTUAL_TXS);
5948
+ const getAllReq = branches.index("vtxo").getAll(IDBKeyRange.only([vtxo.txid, vtxo.vout]));
5949
+ getAllReq.onsuccess = () => {
5950
+ const removed = getAllReq.result;
5951
+ if (removed.length === 0) return;
5952
+ for (const e of removed) branches.delete([e.vtxoTxid, e.vtxoVout, e.position]);
5953
+ for (const e of removed) {
5954
+ const countReq = branches.index("virtualTxid").count(IDBKeyRange.only(e.virtualTxid));
5955
+ countReq.onsuccess = () => {
5956
+ if (countReq.result === 0) txStore.delete(e.virtualTxid);
5957
+ };
5958
+ }
5959
+ };
5960
+ await awaitTransaction(transaction);
5961
+ }
5962
+ async [Symbol.asyncDispose]() {
5963
+ if (!this.db) return;
5964
+ await closeDatabase(this.dbName);
5965
+ this.db = null;
5966
+ }
5967
+ };
5968
+
5969
+ // src/repositories/indexedDB/intentRepository.ts
5970
+ var IndexedDBIntentRepository = class {
5971
+ constructor(dbName = DEFAULT_DB_NAME) {
5972
+ this.dbName = dbName;
5973
+ }
5974
+ version = 1;
5975
+ db = null;
5976
+ async getDB() {
5977
+ if (!this.db)
5978
+ this.db = await openDatabase(this.dbName, INTENT_DB_VERSION, initDatabaseWithIntents);
5979
+ return this.db;
5980
+ }
5981
+ async clear() {
5982
+ const db = await this.getDB();
5983
+ const transaction = db.transaction([STORE_INTENTS], "readwrite");
5984
+ transaction.objectStore(STORE_INTENTS).clear();
5985
+ await awaitTransaction(transaction);
5986
+ }
5987
+ async saveIntent(intent) {
5988
+ const db = await this.getDB();
5989
+ const transaction = db.transaction([STORE_INTENTS], "readwrite");
5990
+ transaction.objectStore(STORE_INTENTS).put({ ...intent, updatedAt: Date.now() });
5991
+ await awaitTransaction(transaction);
5992
+ }
5993
+ async getIntents(filter) {
5994
+ const db = await this.getDB();
5995
+ const store = db.transaction([STORE_INTENTS], "readonly").objectStore(STORE_INTENTS);
5996
+ const all = await promisifyRequest(store.getAll());
5997
+ all.sort((a, b) => a.createdAt - b.createdAt || a.intentTxId.localeCompare(b.intentTxId));
5998
+ const out = filter ? all.filter((i) => intentMatchesFilter(i, filter)) : all;
5999
+ const { skip, end } = intentPageBounds(filter, out.length);
6000
+ return out.slice(skip, end);
6001
+ }
6002
+ async getLockedVtxoOutpoints() {
6003
+ const db = await this.getDB();
6004
+ const store = db.transaction([STORE_INTENTS], "readonly").objectStore(STORE_INTENTS);
6005
+ const all = await promisifyRequest(store.getAll());
6006
+ const out = [];
6007
+ for (const i of all)
6008
+ if (!isTerminalIntentState(i.state)) for (const o of i.intentVtxos) out.push(o);
6009
+ return out;
6010
+ }
6011
+ async [Symbol.asyncDispose]() {
6012
+ if (!this.db) return;
6013
+ await closeDatabase(this.dbName);
6014
+ this.db = null;
6015
+ }
6016
+ };
6017
+
6018
+ // src/contracts/vtxoOwnership.ts
6019
+ function vtxoOutpoint(vtxo) {
6020
+ return `${vtxo.txid}:${vtxo.vout}`;
6021
+ }
6022
+ function isVtxoForScript(vtxo, script) {
6023
+ return !!vtxo.script && vtxo.script === script;
6024
+ }
6025
+ function filterVtxosForScript(vtxos, script) {
6026
+ return vtxos.filter((v) => isVtxoForScript(v, script));
6027
+ }
6028
+ function warnAndFilterVtxosForScript(vtxos, script, context) {
6029
+ const matches = [];
6030
+ const rejected = [];
6031
+ for (const v of vtxos) {
6032
+ if (isVtxoForScript(v, script)) {
6033
+ matches.push(v);
6034
+ } else {
6035
+ rejected.push(`${vtxoOutpoint(v)}(script=${v.script ?? ""})`);
6036
+ }
6037
+ }
6038
+ if (rejected.length > 0) {
6039
+ console.warn(
6040
+ `${context}: dropped ${rejected.length} wrong-script VTXO(s) for script ${script}: ${rejected.join(", ")}`
6041
+ );
6042
+ }
6043
+ return matches;
6044
+ }
6045
+ function validateVtxosForScript(vtxos, script, context) {
6046
+ const mismatches = vtxos.filter((v) => !isVtxoForScript(v, script));
6047
+ if (mismatches.length === 0) return;
6048
+ const detail = mismatches.map((v) => `${vtxoOutpoint(v)}(script=${v.script ?? ""})`).join(", ");
6049
+ throw new Error(
6050
+ `${context}: refusing to persist ${mismatches.length} VTXO(s) whose script does not match ${script}: ${detail}`
6051
+ );
6052
+ }
6053
+ async function getVtxosForContract(repo, contract) {
6054
+ return repo.getVtxosForScript ? repo.getVtxosForScript(contract.script) : filterVtxosForScript(await repo.getVtxos(contract.address), contract.script);
6055
+ }
6056
+ async function saveVtxosForContract(repo, contract, vtxos) {
6057
+ if (repo.saveVtxosForScript) {
6058
+ return repo.saveVtxosForScript(
6059
+ { script: contract.script, address: contract.address },
6060
+ vtxos
6061
+ );
6062
+ }
6063
+ validateVtxosForScript(vtxos, contract.script, "saveVtxosForContract");
6064
+ return repo.saveVtxos(contract.address, vtxos);
6065
+ }
6066
+
6067
+ // src/repositories/inMemory/walletRepository.ts
6068
+ var InMemoryWalletRepository = class {
6069
+ version = 1;
6070
+ vtxosByAddress = /* @__PURE__ */ new Map();
6071
+ utxosByAddress = /* @__PURE__ */ new Map();
6072
+ txsByAddress = /* @__PURE__ */ new Map();
6073
+ walletState = null;
6074
+ async getVtxos(address) {
6075
+ return this.vtxosByAddress.get(address) ?? [];
6076
+ }
6077
+ async saveVtxos(address, vtxos) {
6078
+ const existing = this.vtxosByAddress.get(address) ?? [];
6079
+ const next = mergeByKey(existing, vtxos, (item) => `${item.txid}:${item.vout}`);
6080
+ this.vtxosByAddress.set(address, next);
6081
+ }
6082
+ async deleteVtxos(address) {
6083
+ this.vtxosByAddress.delete(address);
6084
+ }
6085
+ async getVtxosForScript(script) {
6086
+ const allMatches = [];
6087
+ for (const bucket of this.vtxosByAddress.values()) {
6088
+ for (const vtxo of bucket) {
6089
+ if (isVtxoForScript(vtxo, script)) {
6090
+ allMatches.push(vtxo);
6091
+ }
6092
+ }
6093
+ }
6094
+ return mergeByKey([], allMatches, (item) => `${item.txid}:${item.vout}`);
6095
+ }
6096
+ async saveVtxosForScript(key, vtxos) {
6097
+ if (!key.address) {
6098
+ throw new Error("InMemoryWalletRepository requires an address");
6099
+ }
6100
+ for (const vtxo of vtxos) {
6101
+ if (!isVtxoForScript(vtxo, key.script)) {
6102
+ throw new Error(
6103
+ `VTXO ${vtxo.txid}:${vtxo.vout} script mismatch: expected ${key.script}, got ${vtxo.script}`
6104
+ );
6105
+ }
6106
+ }
6107
+ return this.saveVtxos(key.address, vtxos);
6108
+ }
6109
+ async deleteVtxosForScript(script) {
6110
+ for (const [address, bucket] of this.vtxosByAddress.entries()) {
6111
+ const next = bucket.filter((v) => !isVtxoForScript(v, script));
6112
+ if (next.length === 0) {
6113
+ this.vtxosByAddress.delete(address);
6114
+ } else {
6115
+ this.vtxosByAddress.set(address, next);
6116
+ }
6117
+ }
6118
+ }
6119
+ async getUtxos(address) {
6120
+ return this.utxosByAddress.get(address) ?? [];
6121
+ }
6122
+ async saveUtxos(address, utxos) {
6123
+ const existing = this.utxosByAddress.get(address) ?? [];
6124
+ const next = mergeByKey(existing, utxos, (item) => `${item.txid}:${item.vout}`);
6125
+ this.utxosByAddress.set(address, next);
6126
+ }
6127
+ async deleteUtxos(address) {
6128
+ this.utxosByAddress.delete(address);
6129
+ }
6130
+ async getTransactionHistory(address) {
6131
+ return this.txsByAddress.get(address) ?? [];
6132
+ }
6133
+ async saveTransactions(address, txs) {
6134
+ const existing = this.txsByAddress.get(address) ?? [];
6135
+ const next = mergeByKey(existing, txs, serializeTxKey);
6136
+ this.txsByAddress.set(address, next);
6137
+ }
6138
+ async deleteTransactions(address) {
6139
+ this.txsByAddress.delete(address);
6140
+ }
6141
+ async getWalletState() {
6142
+ return this.walletState;
6143
+ }
6144
+ async saveWalletState(state) {
6145
+ this.walletState = state;
6146
+ }
6147
+ async clear() {
6148
+ this.vtxosByAddress.clear();
6149
+ this.utxosByAddress.clear();
6150
+ this.txsByAddress.clear();
6151
+ this.walletState = null;
6152
+ }
6153
+ async [Symbol.asyncDispose]() {
6154
+ return;
6155
+ }
6156
+ };
6157
+ function serializeTxKey(tx) {
6158
+ const key = tx.key;
6159
+ return `${key.boardingTxid}:${key.commitmentTxid}:${key.arkTxid}`;
6160
+ }
6161
+ function mergeByKey(existing, incoming, toKey) {
6162
+ const next = /* @__PURE__ */ new Map();
6163
+ existing.forEach((item) => {
6164
+ next.set(toKey(item), item);
6165
+ });
6166
+ incoming.forEach((item) => {
6167
+ next.set(toKey(item), item);
6168
+ });
6169
+ return Array.from(next.values());
6170
+ }
6171
+
6172
+ // src/repositories/inMemory/contractRepository.ts
6173
+ var InMemoryContractRepository = class {
6174
+ version = 1;
6175
+ contractData = /* @__PURE__ */ new Map();
6176
+ collections = /* @__PURE__ */ new Map();
6177
+ contractsByScript = /* @__PURE__ */ new Map();
6178
+ async clear() {
6179
+ this.contractData.clear();
6180
+ this.collections.clear();
6181
+ this.contractsByScript.clear();
6182
+ }
6183
+ // Contract entity management methods
6184
+ async getContracts(filter) {
6185
+ const contracts = this.contractsByScript.values();
6186
+ if (!filter) {
6187
+ return [...contracts];
6188
+ }
6189
+ const matches = (value, criterion) => {
6190
+ if (criterion === void 0) {
6191
+ return true;
6192
+ }
6193
+ return Array.isArray(criterion) ? criterion.includes(value) : value === criterion;
6194
+ };
6195
+ const results = [];
6196
+ for (const contract of contracts) {
6197
+ if (matches(contract.script, filter.script) && matches(contract.state, filter.state) && matches(contract.type, filter.type)) {
6198
+ results.push(contract);
6199
+ }
6200
+ }
6201
+ return results;
6202
+ }
6203
+ async saveContract(contract) {
6204
+ this.contractsByScript.set(contract.script, contract);
5597
6205
  }
5598
- if (oldVersion >= 1 && oldVersion < 3 && transaction) {
5599
- const vtxosStore = transaction.objectStore(STORE_VTXOS);
5600
- if (!vtxosStore.indexNames.contains("script")) {
5601
- vtxosStore.createIndex("script", "script", { unique: false });
5602
- }
5603
- backfillVtxoScripts(transaction);
6206
+ async deleteContract(script) {
6207
+ this.contractsByScript.delete(script);
5604
6208
  }
5605
- }
5606
- function backfillVtxoScripts(transaction) {
5607
- const store = transaction.objectStore(STORE_VTXOS);
5608
- const cursorRequest = store.openCursor();
5609
- cursorRequest.onsuccess = () => {
5610
- const cursor = cursorRequest.result;
5611
- if (!cursor) return;
5612
- const value = cursor.value;
5613
- if (!value.script) {
5614
- value.script = scriptFromArkAddress(value.address);
5615
- cursor.update(value);
5616
- }
5617
- cursor.continue();
5618
- };
5619
- }
6209
+ async [Symbol.asyncDispose]() {
6210
+ return;
6211
+ }
6212
+ };
5620
6213
  var serializeTapLeaf = ([cb, s]) => ({
5621
6214
  cb: hex.encode(TaprootControlBlock.encode(cb)),
5622
6215
  s: hex.encode(s)
@@ -5683,132 +6276,6 @@ var deserializeTransaction = (o) => ({
5683
6276
  assets: deserializeAssets(o.assets)
5684
6277
  });
5685
6278
 
5686
- // src/repositories/indexedDB/manager.ts
5687
- function getGlobalObject() {
5688
- if (typeof globalThis !== "undefined") {
5689
- if (typeof globalThis.self === "object" && globalThis.self !== null) {
5690
- return { globalObject: globalThis.self };
5691
- }
5692
- if (typeof globalThis.window === "object" && globalThis.window !== null) {
5693
- return { globalObject: globalThis.window };
5694
- }
5695
- return { globalObject: globalThis };
5696
- }
5697
- throw new Error("Global object not found");
5698
- }
5699
- var dbCache = /* @__PURE__ */ new Map();
5700
- var refCounts = /* @__PURE__ */ new Map();
5701
- async function openDatabase(dbName, dbVersion, initDatabase2) {
5702
- const { globalObject } = getGlobalObject();
5703
- if (!globalObject.indexedDB) {
5704
- throw new Error("IndexedDB is not available in this environment");
5705
- }
5706
- const cached = dbCache.get(dbName);
5707
- if (cached) {
5708
- if (cached.version !== dbVersion) {
5709
- throw new Error(
5710
- `Database "${dbName}" already opened with version ${cached.version}; requested ${dbVersion}`
5711
- );
5712
- }
5713
- refCounts.set(dbName, (refCounts.get(dbName) ?? 0) + 1);
5714
- return cached.promise;
5715
- }
5716
- const dbPromise = new Promise((resolve, reject) => {
5717
- const request = globalObject.indexedDB.open(dbName, dbVersion);
5718
- request.onerror = () => {
5719
- dbCache.delete(dbName);
5720
- refCounts.delete(dbName);
5721
- reject(request.error);
5722
- };
5723
- request.onsuccess = () => {
5724
- const db = request.result;
5725
- db.onversionchange = () => {
5726
- db.close();
5727
- dbCache.delete(dbName);
5728
- refCounts.delete(dbName);
5729
- };
5730
- resolve(db);
5731
- };
5732
- request.onupgradeneeded = (event) => {
5733
- const db = request.result;
5734
- initDatabase2(db, event.oldVersion, request.transaction);
5735
- };
5736
- request.onblocked = () => {
5737
- console.warn("Database upgrade blocked - close other tabs/connections");
5738
- };
5739
- });
5740
- dbCache.set(dbName, { version: dbVersion, promise: dbPromise });
5741
- refCounts.set(dbName, 1);
5742
- return dbPromise;
5743
- }
5744
- async function closeDatabase(dbName) {
5745
- const cachedEntry = dbCache.get(dbName);
5746
- if (!cachedEntry) return false;
5747
- const count = (refCounts.get(dbName) ?? 1) - 1;
5748
- if (count > 0) {
5749
- refCounts.set(dbName, count);
5750
- return false;
5751
- }
5752
- refCounts.delete(dbName);
5753
- dbCache.delete(dbName);
5754
- try {
5755
- const db = await cachedEntry.promise;
5756
- db.close();
5757
- } catch {
5758
- }
5759
- return true;
5760
- }
5761
-
5762
- // src/worker/browser/utils.ts
5763
- var DEFAULT_DB_NAME = "arkade-service-worker";
5764
- var DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS = 1e4;
5765
- function normalizeOptions2(pathOrOptions) {
5766
- if (typeof pathOrOptions === "string") {
5767
- return {
5768
- path: pathOrOptions,
5769
- activationTimeoutMs: DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5770
- };
5771
- }
5772
- return {
5773
- path: pathOrOptions.path,
5774
- activationTimeoutMs: pathOrOptions.activationTimeoutMs ?? DEFAULT_SERVICE_WORKER_ACTIVATION_TIMEOUT_MS
5775
- };
5776
- }
5777
- function waitForServiceWorkerReady(timeoutMs) {
5778
- return new Promise((resolve, reject) => {
5779
- const timeoutId = setTimeout(() => {
5780
- reject(new Error(`Service worker activation timed out after ${timeoutMs}ms`));
5781
- }, timeoutMs);
5782
- navigator.serviceWorker.ready.then((registration) => {
5783
- clearTimeout(timeoutId);
5784
- resolve(registration);
5785
- }).catch((error) => {
5786
- clearTimeout(timeoutId);
5787
- reject(error);
5788
- });
5789
- });
5790
- }
5791
- async function setupServiceWorker(pathOrOptions) {
5792
- if (!("serviceWorker" in navigator)) {
5793
- throw new Error("Service workers are not supported in this browser");
5794
- }
5795
- const { path, activationTimeoutMs } = normalizeOptions2(pathOrOptions);
5796
- const registration = await navigator.serviceWorker.register(path);
5797
- await registration.update();
5798
- const serviceWorker = registration.active || registration.waiting || registration.installing;
5799
- if (!serviceWorker) {
5800
- throw new Error("Failed to get service worker instance");
5801
- }
5802
- if (serviceWorker.state === "activated") {
5803
- return serviceWorker;
5804
- }
5805
- const readyRegistration = await waitForServiceWorkerReady(activationTimeoutMs);
5806
- if (!readyRegistration.active) {
5807
- throw new Error("Service worker registration is ready but has no active worker");
5808
- }
5809
- return readyRegistration.active;
5810
- }
5811
-
5812
6279
  // src/repositories/indexedDB/contractRepository.ts
5813
6280
  var IndexedDBContractRepository = class {
5814
6281
  constructor(dbName = DEFAULT_DB_NAME) {
@@ -7116,7 +7583,7 @@ var ContractWatcher = class {
7116
7583
  this.subscriptionId
7117
7584
  );
7118
7585
  } catch (error) {
7119
- const isStale = error instanceof Error && /subscription\s+\S+\s+not\s+found/i.test(error.message);
7586
+ const isStale = error instanceof Error && /subscription\b.*\bnot\s+found/i.test(error.message);
7120
7587
  if (this.subscriptionId && isStale) {
7121
7588
  this.subscriptionId = void 0;
7122
7589
  this.subscriptionId = await this.config.indexerProvider.subscribeForScripts(scriptsToWatch);
@@ -7246,6 +7713,89 @@ var ContractWatcher = class {
7246
7713
  }
7247
7714
  };
7248
7715
 
7716
+ // src/wallet/intentReconciliation.ts
7717
+ var NON_TERMINAL_INTENT_STATES = ALL_INTENT_STATES.filter(
7718
+ (s) => !isTerminalIntentState(s)
7719
+ );
7720
+ function isConsumed(vtxo) {
7721
+ return vtxo !== void 0 && (vtxo.isSpent === true || vtxo.settledBy !== void 0);
7722
+ }
7723
+ async function classifyIntent(intent, vtxosByOutpoint, now) {
7724
+ if (isTerminalIntentState(intent.state)) return void 0;
7725
+ const inputs = intent.intentVtxos;
7726
+ const consumed = inputs.map((o) => vtxosByOutpoint.get(outpointKey(o)));
7727
+ if (inputs.length > 0 && consumed.every(isConsumed)) {
7728
+ const commitmentTransactionId = intent.commitmentTransactionId ?? consumed.map((v) => v?.settledBy).find((s) => !!s);
7729
+ return terminal(intent, "batch_succeeded", now, { commitmentTransactionId });
7730
+ }
7731
+ if (intent.state === "waiting_to_submit") {
7732
+ return terminal(intent, "cancelled", now, {
7733
+ cancellationReason: "reconciliation: intent was never submitted before restart"
7734
+ });
7735
+ }
7736
+ if (intent.validUntil !== void 0 && intent.validUntil < now) {
7737
+ return terminal(intent, "cancelled", now, {
7738
+ cancellationReason: "reconciliation: intent expired"
7739
+ });
7740
+ }
7741
+ return void 0;
7742
+ }
7743
+ async function reconcileIntents(deps) {
7744
+ const now = deps.now ?? Date.now;
7745
+ let intents;
7746
+ try {
7747
+ intents = await deps.intentRepository.getIntents({
7748
+ states: [...NON_TERMINAL_INTENT_STATES]
7749
+ });
7750
+ } catch (e) {
7751
+ console.error("reconcileIntents: failed to read non-terminal intents", e);
7752
+ return;
7753
+ }
7754
+ if (intents.length === 0) return;
7755
+ const outpoints = dedupeOutpoints(intents.flatMap((i) => i.intentVtxos));
7756
+ let vtxosByOutpoint = /* @__PURE__ */ new Map();
7757
+ if (outpoints.length > 0) {
7758
+ try {
7759
+ const { vtxos } = await deps.indexerProvider.getVtxos({ outpoints });
7760
+ vtxosByOutpoint = new Map(vtxos.map((v) => [outpointKey(v), v]));
7761
+ } catch (e) {
7762
+ console.error("reconcileIntents: failed to fetch input VTXO state", e);
7763
+ return;
7764
+ }
7765
+ }
7766
+ const nowMs = now();
7767
+ for (const intent of intents) {
7768
+ try {
7769
+ const next = await classifyIntent(intent, vtxosByOutpoint, nowMs);
7770
+ if (!next) continue;
7771
+ const [fresh] = await deps.intentRepository.getIntents({
7772
+ intentTxIds: [intent.intentTxId]
7773
+ });
7774
+ if (!fresh || fresh.state !== intent.state) continue;
7775
+ await deps.intentRepository.saveIntent(next);
7776
+ } catch (e) {
7777
+ console.error(`reconcileIntents: failed to reconcile intent ${intent.intentTxId}`, e);
7778
+ }
7779
+ }
7780
+ }
7781
+ function terminal(intent, state, now, patch) {
7782
+ return { ...intent, ...patch, state, updatedAt: now };
7783
+ }
7784
+ function outpointKey(o) {
7785
+ return `${o.txid}:${o.vout}`;
7786
+ }
7787
+ function dedupeOutpoints(outpoints) {
7788
+ const seen = /* @__PURE__ */ new Set();
7789
+ const out = [];
7790
+ for (const o of outpoints) {
7791
+ const k = outpointKey(o);
7792
+ if (seen.has(k)) continue;
7793
+ seen.add(k);
7794
+ out.push(o);
7795
+ }
7796
+ return out;
7797
+ }
7798
+
7249
7799
  // src/utils/syncCursors.ts
7250
7800
  var SAFETY_LAG_MS = 3e4;
7251
7801
  var OVERLAP_MS = 24 * 60 * 60 * 1e3;
@@ -7369,6 +7919,26 @@ var ContractManager = class _ContractManager {
7369
7919
  if (watched.length > 0) {
7370
7920
  await this.reconcilePendingFrontier(watched);
7371
7921
  }
7922
+ await this.reconcileStaleIntents();
7923
+ }
7924
+ /**
7925
+ * Crash-recovery for persisted settlement intents: reconcile any
7926
+ * non-terminal intent left behind by a mid-settle crash against
7927
+ * authoritative indexer state (see {@link reconcileIntents}). Runs on the
7928
+ * online sync path only — boot and subscription reconnect — never from a
7929
+ * wallet read API. Best-effort: intent recovery is not a sync invariant, so
7930
+ * a failure is logged and sync continues. No-op without an intent store.
7931
+ */
7932
+ async reconcileStaleIntents() {
7933
+ if (!this.config.intentRepository) return;
7934
+ try {
7935
+ await reconcileIntents({
7936
+ intentRepository: this.config.intentRepository,
7937
+ indexerProvider: this.config.indexerProvider
7938
+ });
7939
+ } catch (e) {
7940
+ console.error("ContractManager: intent reconciliation failed", e);
7941
+ }
7372
7942
  }
7373
7943
  /**
7374
7944
  * Create and register a new contract.
@@ -8739,6 +9309,22 @@ async function resolveBoardingBootTapscript(contractRepository, serverPubKey, ba
8739
9309
  function hasToReadonly(identity) {
8740
9310
  return typeof identity === "object" && identity !== null && "toReadonly" in identity && typeof identity.toReadonly === "function";
8741
9311
  }
9312
+ function excludeLockedOutpoints(vtxos, locked) {
9313
+ if (locked.length === 0) return vtxos;
9314
+ const lockedKeys = new Set(locked.map((o) => `${o.txid}:${o.vout}`));
9315
+ return vtxos.filter((v) => !lockedKeys.has(`${v.txid}:${v.vout}`));
9316
+ }
9317
+ async function spendableVtxosExcludingLocked(vtxos, intentRepository) {
9318
+ if (!intentRepository) return vtxos;
9319
+ let locked;
9320
+ try {
9321
+ locked = await intentRepository.getLockedVtxoOutpoints();
9322
+ } catch (e) {
9323
+ console.error("getLockedVtxoOutpoints failed; reporting unfiltered balance", e);
9324
+ return vtxos;
9325
+ }
9326
+ return excludeLockedOutpoints(vtxos, locked);
9327
+ }
8742
9328
  var ReadonlyWallet = class _ReadonlyWallet {
8743
9329
  constructor(identity, network, onchainProvider, indexerProvider, arkServerPublicKey, offchainTapscript, boardingTapscript, dustAmount, walletRepository, contractRepository, delegateProvider, watcherConfig, walletContractTimelocks) {
8744
9330
  this.identity = identity;
@@ -8769,6 +9355,20 @@ var ReadonlyWallet = class _ReadonlyWallet {
8769
9355
  _contractManager;
8770
9356
  _contractManagerInitializing;
8771
9357
  watcherConfig;
9358
+ /**
9359
+ * Opt-in intent-lifecycle repository. Assigned by the `create()`
9360
+ * factories from `config.storage.intentRepository`; `undefined` ⇒ all
9361
+ * intent-persistence code paths are no-ops (default behaviour unchanged).
9362
+ */
9363
+ intentRepository;
9364
+ /**
9365
+ * **Experimental / inert.** Opt-in virtual-tx / exit-branch repository,
9366
+ * exposed so callers can pass it to {@link Unroll.Session.create} as a
9367
+ * best-effort raw-tx cache. Assigned by `create()` from
9368
+ * `config.storage.virtualTxRepository`. Normal sync never writes it
9369
+ * (ContractManager isn't given it); `undefined` ⇒ no-op.
9370
+ */
9371
+ virtualTxRepository;
8772
9372
  _assetManager;
8773
9373
  walletContractTimelocks;
8774
9374
  // Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
@@ -8826,9 +9426,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
8826
9426
  _deprecatedSigners = /* @__PURE__ */ new Map();
8827
9427
  /**
8828
9428
  * Refresh the cached deprecated-signer set from a fresh server-info
8829
- * snapshot. Called by the create() factories at construction and by the
8830
- * server-info-change handler mid-session. Lenient: a malformed deprecated
8831
- * entry is skipped, never fatal to wallet creation.
9429
+ * snapshot. Called by the create() factories at construction, by the
9430
+ * server-info-change handler mid-session, and by the deprecated-signer
9431
+ * migration pass (`VtxoManager.migrateDeprecatedSignerVtxos`). This set feeds
9432
+ * {@link pendingRecoveryOutpoints}, which drops EXPIRED (past-cutoff)
9433
+ * deprecated-signer VTXOs from the wallet's own coin selection. Lenient: a
9434
+ * malformed deprecated entry is skipped, never fatal to wallet creation.
8832
9435
  */
8833
9436
  refreshDeprecatedSigners(info) {
8834
9437
  const next = /* @__PURE__ */ new Map();
@@ -9044,6 +9647,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
9044
9647
  config.watcherConfig,
9045
9648
  setup.walletContractTimelocks
9046
9649
  );
9650
+ wallet.intentRepository = config.storage?.intentRepository;
9651
+ wallet.virtualTxRepository = config.storage?.virtualTxRepository;
9047
9652
  wallet.refreshDeprecatedSigners(setup.info);
9048
9653
  return wallet;
9049
9654
  }
@@ -9075,6 +9680,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
9075
9680
  this.pendingRecoveryOutpoints()
9076
9681
  ]);
9077
9682
  const isPendingRecovery = (coin) => pendingOutpoints.has(`${coin.txid}:${coin.vout}`);
9683
+ const unlockedVtxos = await spendableVtxosExcludingLocked(vtxos, this.intentRepository);
9078
9684
  let confirmed = 0;
9079
9685
  let unconfirmed = 0;
9080
9686
  for (const utxo of boardingUtxos) {
@@ -9084,14 +9690,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
9084
9690
  unconfirmed += utxo.value;
9085
9691
  }
9086
9692
  }
9087
- let settled = 0;
9088
- let preconfirmed = 0;
9089
9693
  let recoverable = 0;
9090
9694
  let pendingRecovery = 0;
9091
- settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
9092
- preconfirmed = vtxos.filter(
9695
+ const settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
9696
+ const preconfirmed = vtxos.filter(
9093
9697
  (coin) => coin.virtualStatus.state === "preconfirmed" && !isPendingRecovery(coin)
9094
9698
  ).reduce((sum, coin) => sum + coin.value, 0);
9699
+ const available = unlockedVtxos.filter(
9700
+ (coin) => (coin.virtualStatus.state === "settled" || coin.virtualStatus.state === "preconfirmed") && !isPendingRecovery(coin)
9701
+ ).reduce((sum, coin) => sum + coin.value, 0);
9095
9702
  recoverable = vtxos.filter((coin) => isSpendable(coin) && coin.virtualStatus.state === "swept").reduce((sum, coin) => sum + coin.value, 0);
9096
9703
  pendingRecovery = vtxos.filter(isPendingRecovery).reduce((sum, coin) => sum + coin.value, 0);
9097
9704
  const totalBoarding = confirmed + unconfirmed;
@@ -9118,7 +9725,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
9118
9725
  },
9119
9726
  settled,
9120
9727
  preconfirmed,
9121
- available: settled + preconfirmed,
9728
+ available,
9122
9729
  recoverable,
9123
9730
  pendingRecovery,
9124
9731
  total: totalBoarding + totalOffchain,
@@ -9595,6 +10202,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
9595
10202
  indexerProvider: this.indexerProvider,
9596
10203
  contractRepository: this.contractRepository,
9597
10204
  walletRepository: this.walletRepository,
10205
+ intentRepository: this.intentRepository,
9598
10206
  watcherConfig: this.watcherConfig
9599
10207
  });
9600
10208
  const baselinePubkey = await this.identity.xOnlyPublicKey();
@@ -10288,6 +10896,8 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10288
10896
  wallet.setBoardingTapscriptForRotation(resolvedBoarding);
10289
10897
  }
10290
10898
  }
10899
+ wallet.intentRepository = config.storage?.intentRepository;
10900
+ wallet.virtualTxRepository = config.storage?.virtualTxRepository;
10291
10901
  await wallet.getVtxoManager();
10292
10902
  return wallet;
10293
10903
  }
@@ -10567,12 +11177,26 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10567
11177
  ),
10568
11178
  this.makeDeleteIntentSignature(params.inputs)
10569
11179
  ]);
11180
+ let intentTxId;
11181
+ try {
11182
+ intentTxId = Transaction$2.fromPSBT(base64.decode(intent.proof)).id;
11183
+ } catch {
11184
+ intentTxId = params.inputs.map((i) => `${i.txid}:${i.vout}`).sort().join("|");
11185
+ }
11186
+ await this.persistIntentSnapshot(
11187
+ intentTxId,
11188
+ "waiting_to_submit",
11189
+ intent,
11190
+ deleteIntent,
11191
+ params.inputs
11192
+ );
10570
11193
  const topics = [
10571
11194
  ...signingPublicKeys,
10572
11195
  ...params.inputs.map((input) => `${input.txid}:${input.vout}`)
10573
11196
  ];
10574
11197
  const abortController = new AbortController();
10575
11198
  let stream;
11199
+ let committedTxid;
10576
11200
  this._addPendingSpends(params.inputs);
10577
11201
  try {
10578
11202
  stream = this.arkProvider.getEventStream(abortController.signal, topics);
@@ -10587,16 +11211,43 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10587
11211
  yield* stream;
10588
11212
  })();
10589
11213
  const intentId = await this.safeRegisterIntent(intent, params.inputs);
10590
- const handler = this.createBatchHandler(intentId, params.inputs, recipients, session);
11214
+ await this.persistIntentSnapshot(
11215
+ intentTxId,
11216
+ "waiting_for_batch",
11217
+ intent,
11218
+ deleteIntent,
11219
+ params.inputs,
11220
+ { intentId }
11221
+ );
11222
+ const handler = wrapHandlerWithIntentPersistence(
11223
+ this.createBatchHandler(intentId, params.inputs, recipients, session),
11224
+ { intentRepository: this.intentRepository, intentTxId }
11225
+ );
10591
11226
  const commitmentTxid = await Batch.join(primedStream, handler, {
10592
11227
  abortController,
10593
11228
  skipVtxoTreeSigning: !hasOffchainOutputs,
10594
- eventCallback: eventCallback ? (event) => Promise.resolve(eventCallback(event)) : void 0
11229
+ // async so a synchronous throw becomes a rejection Batch.join
11230
+ // swallows, keeping the observational callback off the stream path.
11231
+ eventCallback: eventCallback && (async (event) => {
11232
+ await eventCallback(event);
11233
+ })
10595
11234
  });
11235
+ committedTxid = commitmentTxid;
10596
11236
  await this.updateDbAfterSettle(params.inputs, commitmentTxid);
10597
11237
  await this.maybeRotateBoardingAfterBoard(params.inputs);
10598
11238
  return commitmentTxid;
10599
11239
  } catch (error) {
11240
+ if (committedTxid !== void 0) {
11241
+ await this.persistIntentSnapshot(
11242
+ intentTxId,
11243
+ "batch_succeeded",
11244
+ intent,
11245
+ deleteIntent,
11246
+ params.inputs,
11247
+ { commitmentTransactionId: committedTxid }
11248
+ );
11249
+ throw error;
11250
+ }
10600
11251
  const inputIds = params.inputs.map((i) => `${i.txid}:${i.vout}`).join(",");
10601
11252
  await this.arkProvider.deleteIntent(deleteIntent).catch((e) => {
10602
11253
  console.warn(
@@ -10604,6 +11255,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10604
11255
  e
10605
11256
  );
10606
11257
  });
11258
+ await this.persistIntentSnapshot(
11259
+ intentTxId,
11260
+ "cancelled",
11261
+ intent,
11262
+ deleteIntent,
11263
+ params.inputs,
11264
+ {
11265
+ cancellationReason: error instanceof Error ? error.message : String(error)
11266
+ }
11267
+ );
10607
11268
  throw error;
10608
11269
  } finally {
10609
11270
  this._removePendingSpends(params.inputs);
@@ -10878,6 +11539,42 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10878
11539
  }
10879
11540
  return jobs;
10880
11541
  }
11542
+ /**
11543
+ * Best-effort upsert of the current settlement intent into the optional
11544
+ * {@link intentRepository}. Never throws into the settle path — intent
11545
+ * persistence is observational and must not break money flow. Preserves
11546
+ * fields already written by the event reducer (state-event ordering).
11547
+ */
11548
+ async persistIntentSnapshot(intentTxId, state, intent, deleteIntent, inputs, patch) {
11549
+ const repo = this.intentRepository;
11550
+ if (!repo) return;
11551
+ try {
11552
+ const now = Date.now();
11553
+ const existing = (await repo.getIntents({ intentTxIds: [intentTxId] }))[0];
11554
+ if (existing && isTerminalIntentState(existing.state)) {
11555
+ return;
11556
+ }
11557
+ await repo.saveIntent({
11558
+ ...existing ?? {},
11559
+ intentTxId,
11560
+ registerProof: intent.proof,
11561
+ registerProofMessage: Intent.encodeMessage(intent.message),
11562
+ deleteProof: deleteIntent.proof,
11563
+ deleteProofMessage: Intent.encodeMessage(deleteIntent.message),
11564
+ intentVtxos: inputs.map((i) => ({
11565
+ txid: i.txid,
11566
+ vout: i.vout
11567
+ })),
11568
+ partialForfeits: existing?.partialForfeits ?? [],
11569
+ createdAt: existing?.createdAt ?? now,
11570
+ updatedAt: now,
11571
+ state,
11572
+ ...patch
11573
+ });
11574
+ } catch (e) {
11575
+ console.error(`Failed to persist intent ${intentTxId} (state=${state})`, e);
11576
+ }
11577
+ }
10881
11578
  /**
10882
11579
  * @internal Sign an on-chain boarding exit / sweep transaction, routing
10883
11580
  * each input to the correct key by its `witnessUtxo.script`: the identity
@@ -15821,6 +16518,20 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
15821
16518
  });
15822
16519
  return tx;
15823
16520
  }
16521
+ function chainTxTypeToChainedExit(t) {
16522
+ switch (t) {
16523
+ case "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */:
16524
+ return 1 /* Commitment */;
16525
+ case "INDEXER_CHAINED_TX_TYPE_ARK" /* ARK */:
16526
+ return 2 /* Ark */;
16527
+ case "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */:
16528
+ return 3 /* Tree */;
16529
+ case "INDEXER_CHAINED_TX_TYPE_CHECKPOINT" /* CHECKPOINT */:
16530
+ return 4 /* Checkpoint */;
16531
+ default:
16532
+ return 0 /* Unspecified */;
16533
+ }
16534
+ }
15824
16535
  var Unroll;
15825
16536
  ((Unroll2) => {
15826
16537
  ((StepType2) => {
@@ -15830,16 +16541,54 @@ var Unroll;
15830
16541
  })(Unroll2.StepType || (Unroll2.StepType = {}));
15831
16542
  class Session2 {
15832
16543
  /** Create an unroll session from a virtual output outpoint and its dependency chain. */
15833
- constructor(toUnroll, bumper, explorer, indexer) {
16544
+ constructor(toUnroll, bumper, explorer, indexer, virtualTxRepository) {
15834
16545
  this.toUnroll = toUnroll;
15835
16546
  this.bumper = bumper;
15836
16547
  this.explorer = explorer;
15837
16548
  this.indexer = indexer;
16549
+ this.virtualTxRepository = virtualTxRepository;
15838
16550
  }
15839
16551
  /** Create an unroll session by loading the virtual output chain from the indexer. */
15840
- static async create(toUnroll, bumper, explorer, indexer) {
16552
+ static async create(toUnroll, bumper, explorer, indexer, virtualTxRepository) {
15841
16553
  const { chain } = await indexer.getVtxoChain(toUnroll);
15842
- return new Session2({ ...toUnroll, chain }, bumper, explorer, indexer);
16554
+ return new Session2(
16555
+ { ...toUnroll, chain },
16556
+ bumper,
16557
+ explorer,
16558
+ indexer,
16559
+ virtualTxRepository
16560
+ );
16561
+ }
16562
+ /**
16563
+ * Resolve a chain tx's raw PSBT (base64). Repo first when configured;
16564
+ * indexer on a miss, then best-effort cached so a later exit doesn't
16565
+ * re-fetch. Never throws from the cache
16566
+ * write — exit correctness must not depend on persistence.
16567
+ */
16568
+ async resolveVirtualTxBase64(next) {
16569
+ const repo = this.virtualTxRepository;
16570
+ if (repo) {
16571
+ try {
16572
+ const stored = await repo.getVirtualTx(next.txid);
16573
+ if (stored?.psbt) return stored.psbt;
16574
+ } catch {
16575
+ }
16576
+ }
16577
+ const fetched = await this.indexer.getVirtualTxs([next.txid]);
16578
+ const psbt = fetched.txs[0];
16579
+ if (psbt && repo) {
16580
+ const cached = {
16581
+ txid: next.txid,
16582
+ psbt,
16583
+ expiresAt: null,
16584
+ type: chainTxTypeToChainedExit(next.type)
16585
+ };
16586
+ try {
16587
+ await repo.upsertVirtualTxs([cached]);
16588
+ } catch {
16589
+ }
16590
+ }
16591
+ return psbt;
15843
16592
  }
15844
16593
  /**
15845
16594
  * Get the next step to be executed
@@ -15874,11 +16623,11 @@ var Unroll;
15874
16623
  do: () => Promise.resolve()
15875
16624
  };
15876
16625
  }
15877
- const virtualTxs = await this.indexer.getVirtualTxs([nextTxToBroadcast.txid]);
15878
- if (virtualTxs.txs.length === 0) {
16626
+ const virtualTxBase64 = await this.resolveVirtualTxBase64(nextTxToBroadcast);
16627
+ if (!virtualTxBase64) {
15879
16628
  throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
15880
16629
  }
15881
- const tx = Transaction.fromPSBT(base64.decode(virtualTxs.txs[0]));
16630
+ const tx = Transaction.fromPSBT(base64.decode(virtualTxBase64));
15882
16631
  if (nextTxToBroadcast.type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
15883
16632
  const input = tx.getInput(0);
15884
16633
  if (!input) {
@@ -16098,6 +16847,6 @@ function isArkContract(str) {
16098
16847
  return str.startsWith(ARKCONTRACT_PREFIX + "=");
16099
16848
  }
16100
16849
 
16101
- export { ActivityRegistry, ArkNote, AssetManager, BIP322, Batch, ContractManager, ContractRepositoryImpl, ContractWatcher, DB_VERSION, DEFAULT_MESSAGE_TIMEOUTS, DelegateManagerImpl, DelegateNotConfiguredError, DelegatorManagerImpl, DelegatorNotConfiguredError, DescriptorSigningProviderMissingError, DustChangeError, ELECTRUM_TCP_HOST, ELECTRUM_WS_URL, ESPLORA_URL, ElectrumOnchainProvider, EsploraProvider, Estimator, HDDescriptorProvider, InMemoryContractRepository, InMemoryWalletRepository, IndexedDBContractRepository, IndexedDBWalletRepository, MESSAGE_BUS_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, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, decodeArkContract, deserializeAssets, deserializeUtxo, deserializeVtxo, encodeArkContract, extendVirtualCoinForContract, getMigrationStatus, getRandomId, hasBoardingTxExpired, isArkContract, isBatchSignable, isCooperativelyMigratable, isDiscoverable, isExpired, isRecoverable, isSpendable, isSubdust, isValidArkAddress, isVtxoExpiringSoon, isVtxoForScript, migrateWalletRepository, openDatabase, requiresMigration, rollbackMigration, saveVtxosForContract, scriptFromArkAddress, serializeAssets, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
16102
- //# sourceMappingURL=chunk-D5MQYYYF.js.map
16103
- //# sourceMappingURL=chunk-D5MQYYYF.js.map
16850
+ 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, Unroll, VtxoManager, Wallet2 as Wallet, WalletMessageHandler, WalletNotInitializedError, WalletRepositoryImpl, WsElectrumChainSource, assertIntentIdUnique, boardingResolver, buildForfeitTx, buildOffchainTx, classifyAgainstSignerSet, classifyContractSigner, closeDatabase, combineTapscriptSigs, contractFromArkContract, contractFromArkContractWithAddress, createDefaultActivityRegistry, decodeArkContract, deserializeAssets, 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, serializeUtxo, serializeVtxo, setupServiceWorker, signerSetFromInfo, toXOnlySignerHex, validateConnectorsTxGraph, validateVtxoTxGraph, verifyTapscriptSignatures, waitForIncomingFunds, warnAndFilterVtxosForScript };
16851
+ //# sourceMappingURL=chunk-B4R36PRN.js.map
16852
+ //# sourceMappingURL=chunk-B4R36PRN.js.map