@arkade-os/sdk 0.4.43 → 0.4.45

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 (68) hide show
  1. package/README.md +100 -0
  2. package/dist/adapters/expo.cjs +4 -4
  3. package/dist/adapters/expo.d.cts +2 -2
  4. package/dist/adapters/expo.d.ts +2 -2
  5. package/dist/adapters/expo.js +2 -2
  6. package/dist/adapters/indexedDB.cjs +3 -3
  7. package/dist/adapters/indexedDB.js +2 -2
  8. package/dist/{ark-I8jfe5VE.d.cts → ark-C-PjOngm.d.cts} +89 -1
  9. package/dist/{ark-I8jfe5VE.d.ts → ark-C-PjOngm.d.ts} +89 -1
  10. package/dist/{asyncStorageTaskQueue-D1FfHfgK.d.cts → asyncStorageTaskQueue-BmBPvTyh.d.cts} +1 -1
  11. package/dist/{asyncStorageTaskQueue-CjoL-35w.d.ts → asyncStorageTaskQueue-CHpI3moJ.d.ts} +1 -1
  12. package/dist/{chunk-VKKLOT2E.js → chunk-BOA4SHCD.js} +3 -3
  13. package/dist/{chunk-VKKLOT2E.js.map → chunk-BOA4SHCD.js.map} +1 -1
  14. package/dist/{chunk-PLJZRIFZ.cjs → chunk-HZ7VIN7P.cjs} +11 -11
  15. package/dist/{chunk-PLJZRIFZ.cjs.map → chunk-HZ7VIN7P.cjs.map} +1 -1
  16. package/dist/{chunk-JIWCE2IK.cjs → chunk-OFQGANH2.cjs} +7 -7
  17. package/dist/{chunk-JIWCE2IK.cjs.map → chunk-OFQGANH2.cjs.map} +1 -1
  18. package/dist/{chunk-US274KZO.js → chunk-QZ6HZZZK.js} +3 -3
  19. package/dist/{chunk-US274KZO.js.map → chunk-QZ6HZZZK.js.map} +1 -1
  20. package/dist/{chunk-QGNTDGMN.cjs → chunk-SRCWBFSZ.cjs} +1945 -357
  21. package/dist/chunk-SRCWBFSZ.cjs.map +1 -0
  22. package/dist/{chunk-B4R36PRN.js → chunk-TGRJ5WAX.js} +1868 -285
  23. package/dist/chunk-TGRJ5WAX.js.map +1 -0
  24. package/dist/{chunk-3AQNLQPM.cjs → chunk-UHOHLN3C.cjs} +119 -69
  25. package/dist/chunk-UHOHLN3C.cjs.map +1 -0
  26. package/dist/{chunk-763XHDAJ.js → chunk-YBQV7XBC.js} +119 -70
  27. package/dist/chunk-YBQV7XBC.js.map +1 -0
  28. package/dist/contracts/handlers/index.d.cts +3 -3
  29. package/dist/contracts/handlers/index.d.ts +3 -3
  30. package/dist/{delegate-CgjVjYOP.d.ts → delegate-BKNqva1j.d.ts} +1 -1
  31. package/dist/{delegate-BRqbh2fW.d.cts → delegate-DOEobYwH.d.cts} +1 -1
  32. package/dist/{index-DHuXlDEg.d.cts → index-BlW4I30l.d.cts} +2 -2
  33. package/dist/{index-DvWNB_rl.d.ts → index-Cm2fGeox.d.ts} +2 -2
  34. package/dist/index.cjs +150 -126
  35. package/dist/index.d.cts +275 -19
  36. package/dist/index.d.ts +275 -19
  37. package/dist/index.js +2 -2
  38. package/dist/repositories/realm/index.cjs +17 -17
  39. package/dist/repositories/realm/index.d.cts +2 -2
  40. package/dist/repositories/realm/index.d.ts +2 -2
  41. package/dist/repositories/realm/index.js +3 -3
  42. package/dist/repositories/sqlite/index.cjs +16 -16
  43. package/dist/repositories/sqlite/index.d.cts +2 -2
  44. package/dist/repositories/sqlite/index.d.ts +2 -2
  45. package/dist/repositories/sqlite/index.js +3 -3
  46. package/dist/{taskRunner-Djlhfgi1.d.ts → taskRunner-BngWSFMi.d.ts} +1 -1
  47. package/dist/{taskRunner-BSnslNmq.d.cts → taskRunner-DOiyRuJa.d.cts} +1 -1
  48. package/dist/wallet/expo/background.cjs +12 -12
  49. package/dist/wallet/expo/background.d.cts +3 -3
  50. package/dist/wallet/expo/background.d.ts +3 -3
  51. package/dist/wallet/expo/background.js +4 -4
  52. package/dist/wallet/expo/index.cjs +21 -12
  53. package/dist/wallet/expo/index.cjs.map +1 -1
  54. package/dist/wallet/expo/index.d.cts +11 -4
  55. package/dist/wallet/expo/index.d.ts +11 -4
  56. package/dist/wallet/expo/index.js +12 -3
  57. package/dist/wallet/expo/index.js.map +1 -1
  58. package/dist/{wallet-BsqRU8KM.d.cts → wallet-B3awP1Nj.d.ts} +105 -3
  59. package/dist/{wallet-D9z7wssl.d.ts → wallet-DBjumS42.d.cts} +105 -3
  60. package/dist/worker/expo/index.cjs +7 -7
  61. package/dist/worker/expo/index.d.cts +4 -4
  62. package/dist/worker/expo/index.d.ts +4 -4
  63. package/dist/worker/expo/index.js +3 -3
  64. package/package.json +1 -1
  65. package/dist/chunk-3AQNLQPM.cjs.map +0 -1
  66. package/dist/chunk-763XHDAJ.js.map +0 -1
  67. package/dist/chunk-B4R36PRN.js.map +0 -1
  68. package/dist/chunk-QGNTDGMN.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk3AQNLQPM_cjs = require('./chunk-3AQNLQPM.cjs');
3
+ var chunkUHOHLN3C_cjs = require('./chunk-UHOHLN3C.cjs');
4
4
  var chunk32YKLQPS_cjs = require('./chunk-32YKLQPS.cjs');
5
5
  var chunkHXDZ2QTW_cjs = require('./chunk-HXDZ2QTW.cjs');
6
6
  var utils_js = require('@scure/btc-signer/utils.js');
@@ -181,7 +181,7 @@ var TreeSignerSession = class _TreeSignerSession {
181
181
  noncesByPubkey.set(base.hex.encode(myPublicKey.subarray(1)), myNonce);
182
182
  const tx = this.graph.find(txid);
183
183
  if (!tx) throw new Error(`missing tx for txid ${txid}`);
184
- const cosigners = chunk3AQNLQPM_cjs.getArkPsbtFields(tx.root, 0, chunk3AQNLQPM_cjs.CosignerPublicKey).map(
184
+ const cosigners = chunkUHOHLN3C_cjs.getArkPsbtFields(tx.root, 0, chunkUHOHLN3C_cjs.CosignerPublicKey).map(
185
185
  (c) => base.hex.encode(c.key.subarray(1))
186
186
  // xonly pubkey
187
187
  );
@@ -233,7 +233,7 @@ var TreeSignerSession = class _TreeSignerSession {
233
233
  if (!aggNonce) throw new Error("missing aggregate nonce");
234
234
  const prevoutAmounts = [];
235
235
  const prevoutScripts = [];
236
- const cosigners = chunk3AQNLQPM_cjs.getArkPsbtFields(g.root, 0, chunk3AQNLQPM_cjs.CosignerPublicKey).map((c) => c.key);
236
+ const cosigners = chunkUHOHLN3C_cjs.getArkPsbtFields(g.root, 0, chunkUHOHLN3C_cjs.CosignerPublicKey).map((c) => c.key);
237
237
  const { finalKey } = aggregateKeys(cosigners, true, {
238
238
  taprootTweak: this.scriptRoot
239
239
  });
@@ -980,14 +980,14 @@ var RestDelegateProvider = class {
980
980
  */
981
981
  async delegate(intent, forfeitTxs, options) {
982
982
  const url = `${this.url}/v1/delegate`;
983
- const response = await chunk3AQNLQPM_cjs.baseFetch(url, {
983
+ const response = await chunkUHOHLN3C_cjs.baseFetch(url, {
984
984
  method: "POST",
985
985
  headers: {
986
986
  "Content-Type": "application/json"
987
987
  },
988
988
  body: JSON.stringify({
989
989
  intent: {
990
- message: chunk3AQNLQPM_cjs.Intent.encodeMessage(intent.message),
990
+ message: chunkUHOHLN3C_cjs.Intent.encodeMessage(intent.message),
991
991
  proof: intent.proof
992
992
  },
993
993
  forfeit_txs: forfeitTxs,
@@ -1007,7 +1007,7 @@ var RestDelegateProvider = class {
1007
1007
  */
1008
1008
  async getDelegateInfo() {
1009
1009
  const url = `${this.url}/v1/delegator/info`;
1010
- const response = await chunk3AQNLQPM_cjs.baseFetch(url);
1010
+ const response = await chunkUHOHLN3C_cjs.baseFetch(url);
1011
1011
  if (!response.ok) {
1012
1012
  const errorText2 = await response.text();
1013
1013
  throw new Error(`Failed to get delegate info: ${errorText2}`);
@@ -1042,14 +1042,14 @@ var EsploraProvider = class {
1042
1042
  pollingInterval;
1043
1043
  forcePolling;
1044
1044
  async getCoins(address) {
1045
- const response = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/address/${address}/utxo`);
1045
+ const response = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/address/${address}/utxo`);
1046
1046
  if (!response.ok) {
1047
1047
  throw new Error(`Failed to fetch UTXOs: ${response.statusText}`);
1048
1048
  }
1049
1049
  return response.json();
1050
1050
  }
1051
1051
  async getFeeRate() {
1052
- const response = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/fee-estimates`);
1052
+ const response = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/fee-estimates`);
1053
1053
  if (response.status === 404) {
1054
1054
  return void 0;
1055
1055
  }
@@ -1070,7 +1070,7 @@ var EsploraProvider = class {
1070
1070
  }
1071
1071
  }
1072
1072
  async getTxOutspends(txid) {
1073
- const response = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/outspends`);
1073
+ const response = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/outspends`);
1074
1074
  if (!response.ok) {
1075
1075
  const error = await response.text();
1076
1076
  throw new Error(`Failed to get transaction outspends: ${error}`);
@@ -1078,7 +1078,7 @@ var EsploraProvider = class {
1078
1078
  return response.json();
1079
1079
  }
1080
1080
  async getTransactions(address) {
1081
- const response = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/address/${address}/txs`);
1081
+ const response = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/address/${address}/txs`);
1082
1082
  if (!response.ok) {
1083
1083
  const error = await response.text();
1084
1084
  throw new Error(`Failed to get transactions: ${error}`);
@@ -1086,7 +1086,7 @@ var EsploraProvider = class {
1086
1086
  return response.json();
1087
1087
  }
1088
1088
  async getTxStatus(txid) {
1089
- const txresponse = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/tx/${txid}`);
1089
+ const txresponse = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/tx/${txid}`);
1090
1090
  if (!txresponse.ok) {
1091
1091
  throw new Error(txresponse.statusText);
1092
1092
  }
@@ -1094,7 +1094,7 @@ var EsploraProvider = class {
1094
1094
  if (!tx.status.confirmed) {
1095
1095
  return { confirmed: false };
1096
1096
  }
1097
- const response = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/status`);
1097
+ const response = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/status`);
1098
1098
  if (!response.ok) {
1099
1099
  throw new Error(`Failed to get transaction status: ${response.statusText}`);
1100
1100
  }
@@ -1178,7 +1178,7 @@ var EsploraProvider = class {
1178
1178
  return stopFunc;
1179
1179
  }
1180
1180
  async getChainTip() {
1181
- const tipBlocks = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/blocks`);
1181
+ const tipBlocks = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/blocks`);
1182
1182
  if (!tipBlocks.ok) {
1183
1183
  throw new Error(`Failed to get chain tip: ${tipBlocks.statusText}`);
1184
1184
  }
@@ -1197,7 +1197,7 @@ var EsploraProvider = class {
1197
1197
  };
1198
1198
  }
1199
1199
  async broadcastPackage(parent, child) {
1200
- const response = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/txs/package`, {
1200
+ const response = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/txs/package`, {
1201
1201
  method: "POST",
1202
1202
  headers: {
1203
1203
  "Content-Type": "application/json"
@@ -1211,7 +1211,7 @@ var EsploraProvider = class {
1211
1211
  return response.json();
1212
1212
  }
1213
1213
  async broadcastTx(tx) {
1214
- const response = await chunk3AQNLQPM_cjs.baseFetch(`${this.baseUrl}/tx`, {
1214
+ const response = await chunkUHOHLN3C_cjs.baseFetch(`${this.baseUrl}/tx`, {
1215
1215
  method: "POST",
1216
1216
  headers: {
1217
1217
  "Content-Type": "text/plain"
@@ -1237,6 +1237,114 @@ var isExplorerTransaction = (tx) => {
1237
1237
  (vout) => typeof vout.scriptpubkey_address === "string" && typeof vout.value === "number"
1238
1238
  ) && typeof tx.status === "object" && typeof tx.status.confirmed === "boolean";
1239
1239
  };
1240
+ var getVarIntSize = (n) => {
1241
+ if (n < 253) return 1;
1242
+ if (n <= 65535) return 3;
1243
+ if (n <= 4294967295) return 5;
1244
+ return 9;
1245
+ };
1246
+ var TxWeightEstimator = class _TxWeightEstimator {
1247
+ static P2PKH_SCRIPT_SIG_SIZE = 1 + 73 + 1 + 33;
1248
+ static INPUT_SIZE = 32 + 4 + 1 + 4;
1249
+ static BASE_CONTROL_BLOCK_SIZE = 1 + 32;
1250
+ static OUTPUT_SIZE = 8 + 1;
1251
+ static P2WPKH_OUTPUT_SIZE = 1 + 1 + 20;
1252
+ static BASE_TX_SIZE = 8 + 2;
1253
+ // Version + LockTime
1254
+ static WITNESS_HEADER_SIZE = 2;
1255
+ // Flag + Marker
1256
+ static WITNESS_SCALE_FACTOR = 4;
1257
+ static P2TR_OUTPUT_SIZE = 1 + 1 + 32;
1258
+ hasWitness;
1259
+ inputCount;
1260
+ outputCount;
1261
+ inputSize;
1262
+ inputWitnessSize;
1263
+ outputSize;
1264
+ constructor(hasWitness, inputCount, outputCount, inputSize, inputWitnessSize, outputSize) {
1265
+ this.hasWitness = hasWitness;
1266
+ this.inputCount = inputCount;
1267
+ this.outputCount = outputCount;
1268
+ this.inputSize = inputSize;
1269
+ this.inputWitnessSize = inputWitnessSize;
1270
+ this.outputSize = outputSize;
1271
+ }
1272
+ static create() {
1273
+ return new _TxWeightEstimator(false, 0, 0, 0, 0, 0);
1274
+ }
1275
+ addP2AInput() {
1276
+ this.inputCount++;
1277
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE;
1278
+ return this;
1279
+ }
1280
+ addKeySpendInput(isDefault = true) {
1281
+ this.inputCount++;
1282
+ this.inputWitnessSize += 64 + 1 + (isDefault ? 0 : 1);
1283
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE;
1284
+ this.hasWitness = true;
1285
+ return this;
1286
+ }
1287
+ addP2PKHInput() {
1288
+ this.inputCount++;
1289
+ this.inputWitnessSize++;
1290
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE + _TxWeightEstimator.P2PKH_SCRIPT_SIG_SIZE;
1291
+ return this;
1292
+ }
1293
+ addTapscriptInput(leafWitnessSize, leafScriptSize, leafControlBlockSize) {
1294
+ const controlBlockWitnessSize = 1 + _TxWeightEstimator.BASE_CONTROL_BLOCK_SIZE + 1 + leafScriptSize + 1 + leafControlBlockSize;
1295
+ this.inputCount++;
1296
+ this.inputWitnessSize += leafWitnessSize + 1 + controlBlockWitnessSize;
1297
+ this.inputSize += _TxWeightEstimator.INPUT_SIZE;
1298
+ this.hasWitness = true;
1299
+ return this;
1300
+ }
1301
+ addP2WPKHOutput() {
1302
+ this.outputCount++;
1303
+ this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2WPKH_OUTPUT_SIZE;
1304
+ return this;
1305
+ }
1306
+ addP2TROutput() {
1307
+ this.outputCount++;
1308
+ this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2TR_OUTPUT_SIZE;
1309
+ return this;
1310
+ }
1311
+ /**
1312
+ * Adds an output given a raw script.
1313
+ * Cost = 8 bytes (amount) + varint(scriptLen) + scriptLen
1314
+ */
1315
+ addOutputScript(script) {
1316
+ this.outputCount++;
1317
+ this.outputSize += 8 + getVarIntSize(script.length) + script.length;
1318
+ return this;
1319
+ }
1320
+ /**
1321
+ * Adds an output by decoding the address to get the exact script size.
1322
+ */
1323
+ addOutputAddress(address, network) {
1324
+ const payment = btcSigner.Address(network).decode(address);
1325
+ const script = btcSigner.OutScript.encode(payment);
1326
+ return this.addOutputScript(script);
1327
+ }
1328
+ vsize() {
1329
+ const inputCount = getVarIntSize(this.inputCount);
1330
+ const outputCount = getVarIntSize(this.outputCount);
1331
+ const txSizeStripped = _TxWeightEstimator.BASE_TX_SIZE + inputCount + this.inputSize + outputCount + this.outputSize;
1332
+ let weight = txSizeStripped * _TxWeightEstimator.WITNESS_SCALE_FACTOR;
1333
+ if (this.hasWitness) {
1334
+ weight += _TxWeightEstimator.WITNESS_HEADER_SIZE + this.inputWitnessSize;
1335
+ }
1336
+ return vsize(weight);
1337
+ }
1338
+ };
1339
+ var vsize = (weight) => {
1340
+ const value = BigInt(Math.ceil(weight / TxWeightEstimator.WITNESS_SCALE_FACTOR));
1341
+ return {
1342
+ value,
1343
+ fee: (feeRate) => feeRate * value
1344
+ };
1345
+ };
1346
+
1347
+ // src/utils/anchor.ts
1240
1348
  var ANCHOR_VALUE = 0n;
1241
1349
  var ANCHOR_PKSCRIPT = new Uint8Array([81, 2, 78, 115]);
1242
1350
  var P2A = {
@@ -1262,6 +1370,37 @@ function findP2AOutput(tx) {
1262
1370
  }
1263
1371
  throw new Error("P2A output not found");
1264
1372
  }
1373
+ var CHILD_DUST_AMOUNT = 546;
1374
+ function buildAnchorChild(params) {
1375
+ const { parent, feeRate, fundingCoins, changeAddress, tapInternalKey, network } = params;
1376
+ const child = new chunkUHOHLN3C_cjs.Transaction({ version: 3, allowLegacyWitnessUtxo: true });
1377
+ child.addInput(findP2AOutput(parent));
1378
+ const estimator = TxWeightEstimator.create().addP2AInput();
1379
+ for (const _ of fundingCoins) {
1380
+ estimator.addKeySpendInput(true);
1381
+ }
1382
+ estimator.addOutputAddress(changeAddress, network);
1383
+ const childVsize = Number(estimator.vsize().value);
1384
+ const fee = Math.ceil(feeRate * (parent.vsize + childVsize));
1385
+ let total = 0n;
1386
+ for (const coin of fundingCoins) {
1387
+ total += BigInt(coin.value);
1388
+ child.addInput({
1389
+ txid: coin.txid,
1390
+ index: coin.vout,
1391
+ witnessUtxo: { script: params.changeScript, amount: BigInt(coin.value) },
1392
+ tapInternalKey
1393
+ });
1394
+ }
1395
+ const change = total + P2A.amount - BigInt(fee);
1396
+ if (change < BigInt(CHILD_DUST_AMOUNT)) {
1397
+ throw new Error(
1398
+ `insufficient funding for anchor child: need change >= ${CHILD_DUST_AMOUNT}, got ${change}`
1399
+ );
1400
+ }
1401
+ child.addOutputAddress(changeAddress, change, network);
1402
+ return { child, fee };
1403
+ }
1265
1404
 
1266
1405
  // src/forfeit.ts
1267
1406
  function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
@@ -1282,7 +1421,7 @@ function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
1282
1421
  );
1283
1422
  }
1284
1423
  function buildForfeitTxWithOutput(inputs, output, txLocktime) {
1285
- const tx = new chunk3AQNLQPM_cjs.Transaction({
1424
+ const tx = new chunkUHOHLN3C_cjs.Transaction({
1286
1425
  version: 3,
1287
1426
  lockTime: txLocktime
1288
1427
  });
@@ -1358,7 +1497,7 @@ function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
1358
1497
  if (previousScriptKey.length !== 32) {
1359
1498
  throw new Error(`parent output ${childIndex} has invalid script`);
1360
1499
  }
1361
- const cosigners = chunk3AQNLQPM_cjs.getArkPsbtFields(child.root, 0, chunk3AQNLQPM_cjs.CosignerPublicKey);
1500
+ const cosigners = chunkUHOHLN3C_cjs.getArkPsbtFields(child.root, 0, chunkUHOHLN3C_cjs.CosignerPublicKey);
1362
1501
  if (cosigners.length === 0) {
1363
1502
  throw ErrMissingCosignersPublicKeys;
1364
1503
  }
@@ -1458,7 +1597,7 @@ var Extension = class _Extension {
1458
1597
  `expected magic prefix ${base.hex.encode(ARKADE_MAGIC)}, got ${base.hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`
1459
1598
  );
1460
1599
  }
1461
- const reader = new chunk3AQNLQPM_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
1600
+ const reader = new chunkUHOHLN3C_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
1462
1601
  const packets = [];
1463
1602
  while (reader.remaining() > 0) {
1464
1603
  const packetType = reader.readByte();
@@ -1532,7 +1671,7 @@ var Extension = class _Extension {
1532
1671
  */
1533
1672
  getAssetPacket() {
1534
1673
  for (const p of this.packets) {
1535
- if (p instanceof chunk3AQNLQPM_cjs.Packet) {
1674
+ if (p instanceof chunkUHOHLN3C_cjs.Packet) {
1536
1675
  return p;
1537
1676
  }
1538
1677
  }
@@ -1540,8 +1679,8 @@ var Extension = class _Extension {
1540
1679
  }
1541
1680
  };
1542
1681
  function parsePacket(packetType, data) {
1543
- if (packetType === chunk3AQNLQPM_cjs.Packet.PACKET_TYPE) {
1544
- return chunk3AQNLQPM_cjs.Packet.fromBytes(data);
1682
+ if (packetType === chunkUHOHLN3C_cjs.Packet.PACKET_TYPE) {
1683
+ return chunkUHOHLN3C_cjs.Packet.fromBytes(data);
1545
1684
  }
1546
1685
  return new UnknownPacket(packetType, data);
1547
1686
  }
@@ -1848,7 +1987,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1848
1987
  const existing = inputsByAssetId.get(asset.assetId);
1849
1988
  inputsByAssetId.set(asset.assetId, [
1850
1989
  ...existing ?? [],
1851
- chunk3AQNLQPM_cjs.AssetInput.create(inputIndex, asset.amount)
1990
+ chunkUHOHLN3C_cjs.AssetInput.create(inputIndex, asset.amount)
1852
1991
  ]);
1853
1992
  }
1854
1993
  }
@@ -1860,7 +1999,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1860
1999
  const existing = outputsByAssetId.get(asset.assetId);
1861
2000
  outputsByAssetId.set(asset.assetId, [
1862
2001
  ...existing ?? [],
1863
- chunk3AQNLQPM_cjs.AssetOutput.create(outputIndex, asset.amount)
2002
+ chunkUHOHLN3C_cjs.AssetOutput.create(outputIndex, asset.amount)
1864
2003
  ]);
1865
2004
  }
1866
2005
  }
@@ -1871,7 +2010,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1871
2010
  const existing = outputsByAssetId.get(asset.assetId);
1872
2011
  outputsByAssetId.set(asset.assetId, [
1873
2012
  ...existing ?? [],
1874
- chunk3AQNLQPM_cjs.AssetOutput.create(outputIndex, asset.amount)
2013
+ chunkUHOHLN3C_cjs.AssetOutput.create(outputIndex, asset.amount)
1875
2014
  ]);
1876
2015
  }
1877
2016
  }
@@ -1880,11 +2019,11 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1880
2019
  for (const assetIdStr of allAssetIds) {
1881
2020
  const inputs = inputsByAssetId.get(assetIdStr);
1882
2021
  const outputs = outputsByAssetId.get(assetIdStr);
1883
- const assetId = chunk3AQNLQPM_cjs.AssetId.fromString(assetIdStr);
1884
- const group = chunk3AQNLQPM_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
2022
+ const assetId = chunkUHOHLN3C_cjs.AssetId.fromString(assetIdStr);
2023
+ const group = chunkUHOHLN3C_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
1885
2024
  groups.push(group);
1886
2025
  }
1887
- return chunk3AQNLQPM_cjs.Packet.create(groups);
2026
+ return chunkUHOHLN3C_cjs.Packet.create(groups);
1888
2027
  }
1889
2028
  function selectCoinsWithAsset(coins, assetId, requiredAmount) {
1890
2029
  const coinsWithAsset = coins.filter((coin) => coin.assets?.some((a) => a.assetId === assetId));
@@ -2001,7 +2140,7 @@ function buildVirtualTx(inputs, outputs) {
2001
2140
  }
2002
2141
  }
2003
2142
  }
2004
- const tx = new chunk3AQNLQPM_cjs.Transaction({
2143
+ const tx = new chunkUHOHLN3C_cjs.Transaction({
2005
2144
  version: 3,
2006
2145
  lockTime: Number(lockTime)
2007
2146
  });
@@ -2016,7 +2155,7 @@ function buildVirtualTx(inputs, outputs) {
2016
2155
  },
2017
2156
  tapLeafScript: [input.tapLeafScript]
2018
2157
  });
2019
- chunk3AQNLQPM_cjs.setArkPsbtField(tx, i, chunk3AQNLQPM_cjs.VtxoTaprootTree, input.tapTree);
2158
+ chunkUHOHLN3C_cjs.setArkPsbtField(tx, i, chunkUHOHLN3C_cjs.VtxoTaprootTree, input.tapTree);
2020
2159
  }
2021
2160
  for (const output of outputs) {
2022
2161
  tx.addOutput(output);
@@ -2176,112 +2315,6 @@ function isValidArkAddress(address) {
2176
2315
  return false;
2177
2316
  }
2178
2317
  }
2179
- var getVarIntSize = (n) => {
2180
- if (n < 253) return 1;
2181
- if (n <= 65535) return 3;
2182
- if (n <= 4294967295) return 5;
2183
- return 9;
2184
- };
2185
- var TxWeightEstimator = class _TxWeightEstimator {
2186
- static P2PKH_SCRIPT_SIG_SIZE = 1 + 73 + 1 + 33;
2187
- static INPUT_SIZE = 32 + 4 + 1 + 4;
2188
- static BASE_CONTROL_BLOCK_SIZE = 1 + 32;
2189
- static OUTPUT_SIZE = 8 + 1;
2190
- static P2WPKH_OUTPUT_SIZE = 1 + 1 + 20;
2191
- static BASE_TX_SIZE = 8 + 2;
2192
- // Version + LockTime
2193
- static WITNESS_HEADER_SIZE = 2;
2194
- // Flag + Marker
2195
- static WITNESS_SCALE_FACTOR = 4;
2196
- static P2TR_OUTPUT_SIZE = 1 + 1 + 32;
2197
- hasWitness;
2198
- inputCount;
2199
- outputCount;
2200
- inputSize;
2201
- inputWitnessSize;
2202
- outputSize;
2203
- constructor(hasWitness, inputCount, outputCount, inputSize, inputWitnessSize, outputSize) {
2204
- this.hasWitness = hasWitness;
2205
- this.inputCount = inputCount;
2206
- this.outputCount = outputCount;
2207
- this.inputSize = inputSize;
2208
- this.inputWitnessSize = inputWitnessSize;
2209
- this.outputSize = outputSize;
2210
- }
2211
- static create() {
2212
- return new _TxWeightEstimator(false, 0, 0, 0, 0, 0);
2213
- }
2214
- addP2AInput() {
2215
- this.inputCount++;
2216
- this.inputSize += _TxWeightEstimator.INPUT_SIZE;
2217
- return this;
2218
- }
2219
- addKeySpendInput(isDefault = true) {
2220
- this.inputCount++;
2221
- this.inputWitnessSize += 64 + 1 + (isDefault ? 0 : 1);
2222
- this.inputSize += _TxWeightEstimator.INPUT_SIZE;
2223
- this.hasWitness = true;
2224
- return this;
2225
- }
2226
- addP2PKHInput() {
2227
- this.inputCount++;
2228
- this.inputWitnessSize++;
2229
- this.inputSize += _TxWeightEstimator.INPUT_SIZE + _TxWeightEstimator.P2PKH_SCRIPT_SIG_SIZE;
2230
- return this;
2231
- }
2232
- addTapscriptInput(leafWitnessSize, leafScriptSize, leafControlBlockSize) {
2233
- const controlBlockWitnessSize = 1 + _TxWeightEstimator.BASE_CONTROL_BLOCK_SIZE + 1 + leafScriptSize + 1 + leafControlBlockSize;
2234
- this.inputCount++;
2235
- this.inputWitnessSize += leafWitnessSize + 1 + controlBlockWitnessSize;
2236
- this.inputSize += _TxWeightEstimator.INPUT_SIZE;
2237
- this.hasWitness = true;
2238
- return this;
2239
- }
2240
- addP2WPKHOutput() {
2241
- this.outputCount++;
2242
- this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2WPKH_OUTPUT_SIZE;
2243
- return this;
2244
- }
2245
- addP2TROutput() {
2246
- this.outputCount++;
2247
- this.outputSize += _TxWeightEstimator.OUTPUT_SIZE + _TxWeightEstimator.P2TR_OUTPUT_SIZE;
2248
- return this;
2249
- }
2250
- /**
2251
- * Adds an output given a raw script.
2252
- * Cost = 8 bytes (amount) + varint(scriptLen) + scriptLen
2253
- */
2254
- addOutputScript(script) {
2255
- this.outputCount++;
2256
- this.outputSize += 8 + getVarIntSize(script.length) + script.length;
2257
- return this;
2258
- }
2259
- /**
2260
- * Adds an output by decoding the address to get the exact script size.
2261
- */
2262
- addOutputAddress(address, network) {
2263
- const payment = btcSigner.Address(network).decode(address);
2264
- const script = btcSigner.OutScript.encode(payment);
2265
- return this.addOutputScript(script);
2266
- }
2267
- vsize() {
2268
- const inputCount = getVarIntSize(this.inputCount);
2269
- const outputCount = getVarIntSize(this.outputCount);
2270
- const txSizeStripped = _TxWeightEstimator.BASE_TX_SIZE + inputCount + this.inputSize + outputCount + this.outputSize;
2271
- let weight = txSizeStripped * _TxWeightEstimator.WITNESS_SCALE_FACTOR;
2272
- if (this.hasWitness) {
2273
- weight += _TxWeightEstimator.WITNESS_HEADER_SIZE + this.inputWitnessSize;
2274
- }
2275
- return vsize(weight);
2276
- }
2277
- };
2278
- var vsize = (weight) => {
2279
- const value = BigInt(Math.ceil(weight / TxWeightEstimator.WITNESS_SCALE_FACTOR));
2280
- return {
2281
- value,
2282
- fee: (feeRate) => feeRate * value
2283
- };
2284
- };
2285
2318
  var AmountVariableName = "amount";
2286
2319
  var ExpiryVariableName = "expiry";
2287
2320
  var BirthVariableName = "birth";
@@ -3124,7 +3157,7 @@ var VtxoManager = class _VtxoManager {
3124
3157
  `Sweep not economical: output ${outputAmount} sats after ${fee} sats fee is below dust (${dustAmount} sats)`
3125
3158
  );
3126
3159
  }
3127
- const tx = new chunk3AQNLQPM_cjs.Transaction();
3160
+ const tx = new chunkUHOHLN3C_cjs.Transaction();
3128
3161
  for (const utxo of expiredUtxos) {
3129
3162
  const utxoScript = chunkHXDZ2QTW_cjs.VtxoScript.decode(utxo.tapTree);
3130
3163
  const utxoExitLeaf = utxoScript.leaves.find(
@@ -3763,7 +3796,7 @@ var VtxoManager = class _VtxoManager {
3763
3796
  * or doesn't carry the metadata.
3764
3797
  */
3765
3798
  extractSpentOutpoint(error) {
3766
- const ark = chunk3AQNLQPM_cjs.maybeArkError(error);
3799
+ const ark = chunkUHOHLN3C_cjs.maybeArkError(error);
3767
3800
  if (!ark || ark.name !== "VTXO_ALREADY_SPENT") return void 0;
3768
3801
  const raw = ark.metadata?.vtxo_outpoint;
3769
3802
  if (typeof raw !== "string") return void 0;
@@ -4276,17 +4309,603 @@ function wrapHandlerWithIntentPersistence(base, deps) {
4276
4309
  }
4277
4310
  };
4278
4311
  }
4279
- var TxTree = class {
4280
- constructor(root, children = /* @__PURE__ */ new Map()) {
4281
- this.root = root;
4282
- this.children = children;
4312
+
4313
+ // src/repositories/virtualTxRepository.ts
4314
+ var ChainedTxType = /* @__PURE__ */ ((ChainedTxType2) => {
4315
+ ChainedTxType2[ChainedTxType2["Unspecified"] = 0] = "Unspecified";
4316
+ ChainedTxType2[ChainedTxType2["Commitment"] = 1] = "Commitment";
4317
+ ChainedTxType2[ChainedTxType2["Ark"] = 2] = "Ark";
4318
+ ChainedTxType2[ChainedTxType2["Tree"] = 3] = "Tree";
4319
+ ChainedTxType2[ChainedTxType2["Checkpoint"] = 4] = "Checkpoint";
4320
+ return ChainedTxType2;
4321
+ })(ChainedTxType || {});
4322
+ function mergeChainedTxType(incoming, prev) {
4323
+ if (incoming !== 0 /* Unspecified */) return incoming;
4324
+ return prev ?? 0 /* Unspecified */;
4325
+ }
4326
+
4327
+ // src/wallet/exit/chain.ts
4328
+ var isCommitment = (t) => t === "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */ || t === "INDEXER_CHAINED_TX_TYPE_UNSPECIFIED" /* UNSPECIFIED */;
4329
+ async function buildExitDag(params) {
4330
+ const byTxid = /* @__PURE__ */ new Map();
4331
+ for (const vtxo of params.vtxos) {
4332
+ const outpoint = `${vtxo.txid}:${vtxo.vout}`;
4333
+ const chain = await params.chain.getVtxoChain({ txid: vtxo.txid, vout: vtxo.vout });
4334
+ for (const chainTx of chain) {
4335
+ const existing = byTxid.get(chainTx.txid);
4336
+ if (existing) {
4337
+ existing.forVtxos.add(outpoint);
4338
+ } else {
4339
+ byTxid.set(chainTx.txid, { ...chainTx, forVtxos: /* @__PURE__ */ new Set([outpoint]) });
4340
+ }
4341
+ }
4283
4342
  }
4284
- static create(chunks) {
4285
- if (chunks.length === 0) {
4286
- throw new Error("empty chunks");
4343
+ const pending = new Map(
4344
+ [...byTxid.values()].filter((tx) => !isCommitment(tx.type)).map((tx) => [tx.txid, tx])
4345
+ );
4346
+ const inDegree = /* @__PURE__ */ new Map();
4347
+ for (const tx of pending.values()) {
4348
+ const parents = tx.spends.filter((p) => pending.has(p));
4349
+ inDegree.set(tx.txid, parents.length);
4350
+ }
4351
+ const order = [];
4352
+ const queue = [...pending.values()].filter((tx) => inDegree.get(tx.txid) === 0).sort((a, b) => a.txid.localeCompare(b.txid));
4353
+ while (queue.length > 0) {
4354
+ const tx = queue.shift();
4355
+ order.push(tx);
4356
+ const children = [...pending.values()].filter((c) => c.spends.includes(tx.txid)).sort((a, b) => a.txid.localeCompare(b.txid));
4357
+ for (const child of children) {
4358
+ const deg = inDegree.get(child.txid) - 1;
4359
+ inDegree.set(child.txid, deg);
4360
+ if (deg === 0) queue.push(child);
4361
+ }
4362
+ }
4363
+ if (order.length !== pending.size) {
4364
+ throw new Error("inconsistent vtxo chain from indexer (cycle detected)");
4365
+ }
4366
+ const nodes = [];
4367
+ for (const tx of order) {
4368
+ let confirmed = false;
4369
+ try {
4370
+ const status = await params.onchain.getTxStatus(tx.txid);
4371
+ confirmed = status.confirmed;
4372
+ } catch {
4373
+ confirmed = false;
4374
+ }
4375
+ const expires = Number(tx.expiresAt);
4376
+ nodes.push({
4377
+ txid: tx.txid,
4378
+ type: tx.type,
4379
+ expiresAt: Number.isFinite(expires) && expires > 0 ? expires : void 0,
4380
+ forVtxos: [...tx.forVtxos].sort(),
4381
+ confirmed
4382
+ });
4383
+ }
4384
+ return nodes;
4385
+ }
4386
+ function topoSortByDeps(items, idOf, depsOf) {
4387
+ const produced = new Set(items.map(idOf));
4388
+ const emitted = /* @__PURE__ */ new Set();
4389
+ const ordered = [];
4390
+ let remaining = items;
4391
+ while (remaining.length > 0) {
4392
+ const next = [];
4393
+ let progress = false;
4394
+ for (const item of remaining) {
4395
+ if (depsOf(item).every((d) => !produced.has(d) || emitted.has(d))) {
4396
+ ordered.push(item);
4397
+ emitted.add(idOf(item));
4398
+ progress = true;
4399
+ } else {
4400
+ next.push(item);
4401
+ }
4287
4402
  }
4288
- const chunksByTxid = /* @__PURE__ */ new Map();
4289
- for (const chunk of chunks) {
4403
+ if (!progress) {
4404
+ throw new Error("topological sort: cycle or unsatisfiable dependency");
4405
+ }
4406
+ remaining = next;
4407
+ }
4408
+ return ordered;
4409
+ }
4410
+
4411
+ // src/wallet/exit/capture.ts
4412
+ var DEFAULT_MIN_EXIT_WORTH_SATS = 1e3;
4413
+ var DEFAULT_EXIT_CAPTURE_MODE = "lite";
4414
+ function chainTxTypeToChainedExit(t) {
4415
+ switch (t) {
4416
+ case "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */:
4417
+ return 1 /* Commitment */;
4418
+ case "INDEXER_CHAINED_TX_TYPE_ARK" /* ARK */:
4419
+ return 2 /* Ark */;
4420
+ case "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */:
4421
+ return 3 /* Tree */;
4422
+ case "INDEXER_CHAINED_TX_TYPE_CHECKPOINT" /* CHECKPOINT */:
4423
+ return 4 /* Checkpoint */;
4424
+ default:
4425
+ return 0 /* Unspecified */;
4426
+ }
4427
+ }
4428
+ function orderAncestryFirst(chain) {
4429
+ const ids = new Set(chain.map((c) => c.txid));
4430
+ return topoSortByDeps(
4431
+ chain,
4432
+ (c) => c.txid,
4433
+ (c) => c.spends.filter((s) => ids.has(s))
4434
+ );
4435
+ }
4436
+ async function captureExitBranch(params) {
4437
+ const { resolver, repository, vtxo, value, mode, minExitWorthSats } = params;
4438
+ if (value < minExitWorthSats) return;
4439
+ if (await repository.hasBranch(vtxo)) return;
4440
+ const ordered = orderAncestryFirst(await resolver.getVtxoChain(vtxo));
4441
+ const psbtByTxid = /* @__PURE__ */ new Map();
4442
+ if (mode === "full") {
4443
+ const nonCommitment = ordered.filter((c) => chainTxTypeToChainedExit(c.type) !== 1 /* Commitment */).map((c) => c.txid);
4444
+ if (nonCommitment.length > 0) {
4445
+ for (const psbt of await resolver.getVirtualTxs(nonCommitment)) {
4446
+ psbtByTxid.set(chunkUHOHLN3C_cjs.Transaction.fromPSBT(base.base64.decode(psbt)).id, psbt);
4447
+ }
4448
+ }
4449
+ }
4450
+ const virtualTxs = ordered.map((c) => {
4451
+ const expires = Number(c.expiresAt);
4452
+ return {
4453
+ txid: c.txid,
4454
+ psbt: psbtByTxid.get(c.txid) ?? null,
4455
+ expiresAt: Number.isFinite(expires) && expires > 0 ? expires : null,
4456
+ type: chainTxTypeToChainedExit(c.type)
4457
+ };
4458
+ });
4459
+ const branch = ordered.map((c, i) => ({
4460
+ vtxoTxid: vtxo.txid,
4461
+ vtxoVout: vtxo.vout,
4462
+ virtualTxid: c.txid,
4463
+ position: i
4464
+ }));
4465
+ await repository.upsertVirtualTxs(virtualTxs);
4466
+ await repository.setBranch(vtxo, branch);
4467
+ }
4468
+ async function pruneExitBranches(repository, outpoints) {
4469
+ for (const outpoint of outpoints) {
4470
+ try {
4471
+ await repository.pruneForSpentVtxo(outpoint);
4472
+ } catch {
4473
+ }
4474
+ }
4475
+ }
4476
+ var IndexerExitDataSource = class {
4477
+ constructor(indexer) {
4478
+ this.indexer = indexer;
4479
+ }
4480
+ name = "indexer";
4481
+ async getVtxoChain(vtxo) {
4482
+ const chain = [];
4483
+ let pageIndex = 0;
4484
+ let hasMore = true;
4485
+ while (hasMore) {
4486
+ const { chain: page, page: meta } = await this.indexer.getVtxoChain(
4487
+ { txid: vtxo.txid, vout: vtxo.vout },
4488
+ { pageIndex, pageSize: chunk32YKLQPS_cjs.DEFAULT_PAGE_SIZE }
4489
+ );
4490
+ chain.push(...page);
4491
+ hasMore = meta ? page.length === chunk32YKLQPS_cjs.DEFAULT_PAGE_SIZE : false;
4492
+ pageIndex++;
4493
+ }
4494
+ return chain;
4495
+ }
4496
+ async getVirtualTxs(txids) {
4497
+ const out = /* @__PURE__ */ new Map();
4498
+ let pageIndex = 0;
4499
+ let hasMore = true;
4500
+ while (hasMore) {
4501
+ const { txs, page } = await this.indexer.getVirtualTxs(txids, {
4502
+ pageIndex,
4503
+ pageSize: chunk32YKLQPS_cjs.DEFAULT_PAGE_SIZE
4504
+ });
4505
+ for (const psbt of txs) {
4506
+ out.set(chunkUHOHLN3C_cjs.Transaction.fromPSBT(base.base64.decode(psbt)).id, psbt);
4507
+ }
4508
+ hasMore = page ? txs.length === chunk32YKLQPS_cjs.DEFAULT_PAGE_SIZE : false;
4509
+ pageIndex++;
4510
+ }
4511
+ return out;
4512
+ }
4513
+ };
4514
+ function chainedTxTypeToChainTxType(t) {
4515
+ switch (t) {
4516
+ case 1 /* Commitment */:
4517
+ return "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */;
4518
+ case 2 /* Ark */:
4519
+ return "INDEXER_CHAINED_TX_TYPE_ARK" /* ARK */;
4520
+ case 3 /* Tree */:
4521
+ return "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */;
4522
+ case 4 /* Checkpoint */:
4523
+ return "INDEXER_CHAINED_TX_TYPE_CHECKPOINT" /* CHECKPOINT */;
4524
+ default:
4525
+ return "INDEXER_CHAINED_TX_TYPE_UNSPECIFIED" /* UNSPECIFIED */;
4526
+ }
4527
+ }
4528
+ function psbtInputTxids(psbtBase64) {
4529
+ const tx = chunkUHOHLN3C_cjs.Transaction.fromPSBT(base.base64.decode(psbtBase64));
4530
+ const ids = [];
4531
+ for (let i = 0; i < tx.inputsLength; i++) {
4532
+ const txid = tx.getInput(i).txid;
4533
+ if (txid) ids.push(base.hex.encode(txid));
4534
+ }
4535
+ return ids;
4536
+ }
4537
+ var RepositoryExitDataSource = class {
4538
+ constructor(repo) {
4539
+ this.repo = repo;
4540
+ }
4541
+ name = "repository";
4542
+ async getVtxoChain(vtxo) {
4543
+ const branch = await this.repo.getBranch(vtxo);
4544
+ if (branch.length === 0) return null;
4545
+ const chain = [];
4546
+ for (const v of branch) {
4547
+ const type = chainedTxTypeToChainTxType(v.type);
4548
+ let spends = [];
4549
+ if (v.type !== 1 /* Commitment */) {
4550
+ if (!v.psbt) return null;
4551
+ spends = psbtInputTxids(v.psbt);
4552
+ }
4553
+ chain.push({
4554
+ txid: v.txid,
4555
+ type,
4556
+ expiresAt: v.expiresAt != null ? String(v.expiresAt) : "",
4557
+ spends
4558
+ });
4559
+ }
4560
+ return chain;
4561
+ }
4562
+ async getVirtualTxs(txids) {
4563
+ const out = /* @__PURE__ */ new Map();
4564
+ for (const txid of txids) {
4565
+ const stored = await this.repo.getVirtualTx(txid);
4566
+ if (stored?.psbt) out.set(txid, stored.psbt);
4567
+ }
4568
+ return out;
4569
+ }
4570
+ };
4571
+
4572
+ // src/wallet/exit/resolver.ts
4573
+ var OrderedExitChainResolver = class {
4574
+ constructor(sources, persist) {
4575
+ this.sources = sources;
4576
+ this.persist = persist;
4577
+ }
4578
+ async getVtxoChain(vtxo) {
4579
+ let lastErr;
4580
+ for (const source of this.sources) {
4581
+ try {
4582
+ const chain = await source.getVtxoChain(vtxo);
4583
+ if (chain) return chain;
4584
+ } catch (e) {
4585
+ lastErr = e;
4586
+ }
4587
+ }
4588
+ if (lastErr) throw lastErr;
4589
+ throw new Error(`no exit-data source resolved the chain for ${vtxo.txid}:${vtxo.vout}`);
4590
+ }
4591
+ async getVirtualTxs(txids) {
4592
+ const found = /* @__PURE__ */ new Map();
4593
+ let remaining = txids;
4594
+ for (const [i, source] of this.sources.entries()) {
4595
+ if (remaining.length === 0) break;
4596
+ let got;
4597
+ try {
4598
+ got = await source.getVirtualTxs(remaining);
4599
+ } catch {
4600
+ continue;
4601
+ }
4602
+ const fresh = [];
4603
+ for (const [txid, psbt] of got) {
4604
+ if (found.has(txid)) continue;
4605
+ found.set(txid, psbt);
4606
+ fresh.push({ txid, psbt, expiresAt: null, type: 0 /* Unspecified */ });
4607
+ }
4608
+ if (i > 0 && this.persist && fresh.length > 0) {
4609
+ try {
4610
+ await this.persist.upsertVirtualTxs(fresh);
4611
+ } catch {
4612
+ }
4613
+ }
4614
+ remaining = txids.filter((t) => !found.has(t));
4615
+ }
4616
+ return [...found.values()];
4617
+ }
4618
+ };
4619
+ function createExitChainResolver(params) {
4620
+ const sources = [];
4621
+ if (params.repository) sources.push(new RepositoryExitDataSource(params.repository));
4622
+ if (params.extraSources) sources.push(...params.extraSources);
4623
+ sources.push(new IndexerExitDataSource(params.indexer));
4624
+ return new OrderedExitChainResolver(sources, params.repository);
4625
+ }
4626
+
4627
+ // src/providers/availability.ts
4628
+ function isRetryableProviderError(error) {
4629
+ return error instanceof chunkUHOHLN3C_cjs.ProviderUnavailableError || error instanceof chunkUHOHLN3C_cjs.FetchError || chunkUHOHLN3C_cjs.isFetchTimeoutError(error);
4630
+ }
4631
+
4632
+ // src/utils/syncCursors.ts
4633
+ var SAFETY_LAG_MS = 3e4;
4634
+ var OVERLAP_MS = 24 * 60 * 60 * 1e3;
4635
+ var walletStateLocks = /* @__PURE__ */ new WeakMap();
4636
+ async function updateWalletState(repo, updater) {
4637
+ const prev = walletStateLocks.get(repo) ?? Promise.resolve();
4638
+ const op = prev.then(async () => {
4639
+ const state = await repo.getWalletState() ?? {};
4640
+ await repo.saveWalletState(updater(state));
4641
+ });
4642
+ walletStateLocks.set(
4643
+ repo,
4644
+ op.catch(() => {
4645
+ })
4646
+ );
4647
+ return op;
4648
+ }
4649
+ var CURSOR_MIGRATED_KEY = "vtxoCursorMigrated";
4650
+ function hasMigrationMarker(state) {
4651
+ return state?.settings?.[CURSOR_MIGRATED_KEY] === true;
4652
+ }
4653
+ async function getSyncCursor(repo) {
4654
+ const state = await repo.getWalletState();
4655
+ if (!hasMigrationMarker(state)) return 0;
4656
+ return state?.lastSyncTime ?? 0;
4657
+ }
4658
+ async function advanceSyncCursor(repo, lastUpdatedAt) {
4659
+ await updateWalletState(repo, (state) => {
4660
+ const current = hasMigrationMarker(state) ? state.lastSyncTime ?? 0 : 0;
4661
+ return {
4662
+ ...state,
4663
+ lastSyncTime: Math.max(current, lastUpdatedAt),
4664
+ settings: {
4665
+ ...state.settings ?? {},
4666
+ [CURSOR_MIGRATED_KEY]: true
4667
+ }
4668
+ };
4669
+ });
4670
+ }
4671
+ async function clearSyncCursor(repo) {
4672
+ await updateWalletState(repo, (state) => {
4673
+ const { [CURSOR_MIGRATED_KEY]: _, ...restSettings } = state.settings ?? {};
4674
+ return {
4675
+ ...state,
4676
+ lastSyncTime: void 0,
4677
+ settings: restSettings
4678
+ };
4679
+ });
4680
+ }
4681
+ function computeSyncWindow(cursor) {
4682
+ const after = Math.max(0, cursor - OVERLAP_MS);
4683
+ return { after };
4684
+ }
4685
+ function cursorCutoff(requestStartedAt) {
4686
+ return (requestStartedAt ?? Date.now()) - SAFETY_LAG_MS;
4687
+ }
4688
+
4689
+ // src/wallet/arkInfoSnapshot.ts
4690
+ var ARK_INFO_SNAPSHOT_KEY = "arkInfoSnapshot";
4691
+ var MalformedArkInfoSnapshotError = class extends Error {
4692
+ constructor(message, options) {
4693
+ super(message, { cause: options?.cause });
4694
+ this.name = "MalformedArkInfoSnapshotError";
4695
+ }
4696
+ };
4697
+ var CURRENT_VERSION = 1;
4698
+ function serializeArkInfoSnapshot(info, savedAt) {
4699
+ return {
4700
+ version: CURRENT_VERSION,
4701
+ savedAt,
4702
+ source: "arkade.getInfo",
4703
+ arkInfo: {
4704
+ network: info.network,
4705
+ signerPubkey: info.signerPubkey,
4706
+ checkpointTapscript: info.checkpointTapscript,
4707
+ forfeitAddress: info.forfeitAddress,
4708
+ forfeitPubkey: info.forfeitPubkey,
4709
+ unilateralExitDelay: info.unilateralExitDelay.toString(),
4710
+ boardingExitDelay: info.boardingExitDelay.toString(),
4711
+ sessionDuration: info.sessionDuration.toString(),
4712
+ dust: info.dust.toString(),
4713
+ fees: info.fees,
4714
+ digest: info.digest,
4715
+ version: info.version,
4716
+ vtxoMinAmount: info.vtxoMinAmount.toString(),
4717
+ vtxoMaxAmount: info.vtxoMaxAmount.toString(),
4718
+ utxoMinAmount: info.utxoMinAmount.toString(),
4719
+ utxoMaxAmount: info.utxoMaxAmount.toString(),
4720
+ deprecatedSigners: info.deprecatedSigners.map((s) => ({
4721
+ pubkey: s.pubkey,
4722
+ cutoffDate: s.cutoffDate.toString()
4723
+ })),
4724
+ scheduledSession: info.scheduledSession ? {
4725
+ duration: info.scheduledSession.duration.toString(),
4726
+ fees: info.scheduledSession.fees,
4727
+ nextEndTime: info.scheduledSession.nextEndTime.toString(),
4728
+ nextStartTime: info.scheduledSession.nextStartTime.toString(),
4729
+ period: info.scheduledSession.period.toString()
4730
+ } : void 0
4731
+ }
4732
+ };
4733
+ }
4734
+ function hydrateArkInfo(snapshot) {
4735
+ const a = snapshot.arkInfo;
4736
+ return {
4737
+ boardingExitDelay: BigInt(a.boardingExitDelay),
4738
+ checkpointTapscript: a.checkpointTapscript,
4739
+ deprecatedSigners: a.deprecatedSigners.map((s) => ({
4740
+ cutoffDate: BigInt(s.cutoffDate),
4741
+ pubkey: s.pubkey
4742
+ })),
4743
+ digest: a.digest,
4744
+ dust: BigInt(a.dust),
4745
+ fees: a.fees,
4746
+ forfeitAddress: a.forfeitAddress,
4747
+ forfeitPubkey: a.forfeitPubkey,
4748
+ network: a.network,
4749
+ scheduledSession: a.scheduledSession ? {
4750
+ duration: BigInt(a.scheduledSession.duration),
4751
+ fees: a.scheduledSession.fees,
4752
+ nextEndTime: BigInt(a.scheduledSession.nextEndTime),
4753
+ nextStartTime: BigInt(a.scheduledSession.nextStartTime),
4754
+ period: BigInt(a.scheduledSession.period)
4755
+ } : void 0,
4756
+ serviceStatus: {},
4757
+ sessionDuration: BigInt(a.sessionDuration),
4758
+ signerPubkey: a.signerPubkey,
4759
+ unilateralExitDelay: BigInt(a.unilateralExitDelay),
4760
+ utxoMaxAmount: BigInt(a.utxoMaxAmount),
4761
+ utxoMinAmount: BigInt(a.utxoMinAmount),
4762
+ version: a.version,
4763
+ vtxoMaxAmount: BigInt(a.vtxoMaxAmount),
4764
+ vtxoMinAmount: BigInt(a.vtxoMinAmount)
4765
+ };
4766
+ }
4767
+ var DECIMAL_STRING = /^-?\d+$/;
4768
+ function assertString(value, path) {
4769
+ if (typeof value !== "string") {
4770
+ throw new MalformedArkInfoSnapshotError(`${path} must be a string`);
4771
+ }
4772
+ }
4773
+ function assertDecimalString(value, path) {
4774
+ assertString(value, path);
4775
+ if (!DECIMAL_STRING.test(value)) {
4776
+ throw new MalformedArkInfoSnapshotError(`${path} must be a decimal integer string`);
4777
+ }
4778
+ }
4779
+ function assertFeeInfo(value, path) {
4780
+ if (typeof value !== "object" || value === null) {
4781
+ throw new MalformedArkInfoSnapshotError(`${path} must be an object`);
4782
+ }
4783
+ const fees = value;
4784
+ assertString(fees.txFeeRate, `${path}.txFeeRate`);
4785
+ if (typeof fees.intentFee !== "object" || fees.intentFee === null) {
4786
+ throw new MalformedArkInfoSnapshotError(`${path}.intentFee must be an object`);
4787
+ }
4788
+ }
4789
+ function parseStoredArkInfoSnapshot(raw) {
4790
+ if (typeof raw !== "object" || raw === null) {
4791
+ throw new MalformedArkInfoSnapshotError("snapshot must be an object");
4792
+ }
4793
+ const snap = raw;
4794
+ if (snap.version !== CURRENT_VERSION) {
4795
+ throw new MalformedArkInfoSnapshotError(
4796
+ `unsupported snapshot version: ${String(snap.version)}`
4797
+ );
4798
+ }
4799
+ if (typeof snap.savedAt !== "number") {
4800
+ throw new MalformedArkInfoSnapshotError("savedAt must be a number");
4801
+ }
4802
+ if (snap.source !== "arkade.getInfo") {
4803
+ throw new MalformedArkInfoSnapshotError(`unexpected source: ${String(snap.source)}`);
4804
+ }
4805
+ if (typeof snap.arkInfo !== "object" || snap.arkInfo === null) {
4806
+ throw new MalformedArkInfoSnapshotError("arkInfo must be an object");
4807
+ }
4808
+ const a = snap.arkInfo;
4809
+ for (const field of [
4810
+ "network",
4811
+ "signerPubkey",
4812
+ "checkpointTapscript",
4813
+ "forfeitAddress",
4814
+ "forfeitPubkey",
4815
+ "digest",
4816
+ "version"
4817
+ ]) {
4818
+ assertString(a[field], `arkInfo.${field}`);
4819
+ }
4820
+ for (const field of [
4821
+ "unilateralExitDelay",
4822
+ "boardingExitDelay",
4823
+ "sessionDuration",
4824
+ "dust",
4825
+ "vtxoMinAmount",
4826
+ "vtxoMaxAmount",
4827
+ "utxoMinAmount",
4828
+ "utxoMaxAmount"
4829
+ ]) {
4830
+ assertDecimalString(a[field], `arkInfo.${field}`);
4831
+ }
4832
+ assertFeeInfo(a.fees, "arkInfo.fees");
4833
+ if (!Array.isArray(a.deprecatedSigners)) {
4834
+ throw new MalformedArkInfoSnapshotError("arkInfo.deprecatedSigners must be an array");
4835
+ }
4836
+ a.deprecatedSigners.forEach((s, i) => {
4837
+ if (typeof s !== "object" || s === null) {
4838
+ throw new MalformedArkInfoSnapshotError(
4839
+ `arkInfo.deprecatedSigners[${i}] must be an object`
4840
+ );
4841
+ }
4842
+ const signer = s;
4843
+ assertString(signer.pubkey, `arkInfo.deprecatedSigners[${i}].pubkey`);
4844
+ assertDecimalString(signer.cutoffDate, `arkInfo.deprecatedSigners[${i}].cutoffDate`);
4845
+ });
4846
+ if (a.scheduledSession !== void 0) {
4847
+ if (typeof a.scheduledSession !== "object" || a.scheduledSession === null) {
4848
+ throw new MalformedArkInfoSnapshotError("arkInfo.scheduledSession must be an object");
4849
+ }
4850
+ const s = a.scheduledSession;
4851
+ for (const field of ["duration", "nextEndTime", "nextStartTime", "period"]) {
4852
+ assertDecimalString(s[field], `arkInfo.scheduledSession.${field}`);
4853
+ }
4854
+ assertFeeInfo(s.fees, "arkInfo.scheduledSession.fees");
4855
+ }
4856
+ return raw;
4857
+ }
4858
+ async function loadArkInfoSnapshot(repo) {
4859
+ const state = await repo.getWalletState();
4860
+ const raw = state?.settings?.[ARK_INFO_SNAPSHOT_KEY];
4861
+ if (raw === void 0 || raw === null) return null;
4862
+ return parseStoredArkInfoSnapshot(raw);
4863
+ }
4864
+ async function saveArkInfoSnapshot(repo, info, savedAt) {
4865
+ const snapshot = serializeArkInfoSnapshot(info, savedAt);
4866
+ await updateWalletState(repo, (state) => ({
4867
+ ...state,
4868
+ settings: {
4869
+ ...state.settings ?? {},
4870
+ [ARK_INFO_SNAPSHOT_KEY]: snapshot
4871
+ }
4872
+ }));
4873
+ }
4874
+ async function resolveArkInfo(arkProvider, walletRepository) {
4875
+ let info;
4876
+ try {
4877
+ info = await arkProvider.getInfo();
4878
+ } catch (err) {
4879
+ if (!isRetryableProviderError(err)) throw err;
4880
+ const snapshot = await loadArkInfoSnapshot(walletRepository);
4881
+ if (!snapshot) {
4882
+ throw new chunkUHOHLN3C_cjs.ProviderUnavailableError(
4883
+ "Arkade server info is unavailable and no cached snapshot exists",
4884
+ { cause: err }
4885
+ );
4886
+ }
4887
+ return { info: hydrateArkInfo(snapshot), source: "cache", lastOnlineAt: snapshot.savedAt };
4888
+ }
4889
+ return { info, source: "live" };
4890
+ }
4891
+ async function saveValidatedArkInfoSnapshot(repo, info, savedAt) {
4892
+ try {
4893
+ await saveArkInfoSnapshot(repo, info, savedAt);
4894
+ } catch (err) {
4895
+ console.warn("Failed to persist Ark info snapshot", err);
4896
+ }
4897
+ }
4898
+ var TxTree = class {
4899
+ constructor(root, children = /* @__PURE__ */ new Map()) {
4900
+ this.root = root;
4901
+ this.children = children;
4902
+ }
4903
+ static create(chunks) {
4904
+ if (chunks.length === 0) {
4905
+ throw new Error("empty chunks");
4906
+ }
4907
+ const chunksByTxid = /* @__PURE__ */ new Map();
4908
+ for (const chunk of chunks) {
4290
4909
  const decodedChunk = decodeNode(chunk);
4291
4910
  const txid = decodedChunk.tx.id;
4292
4911
  chunksByTxid.set(txid, decodedChunk);
@@ -4791,7 +5410,7 @@ var AssetManager = class extends ReadonlyAssetManager {
4791
5410
  const virtualCoins = await this.wallet.getVtxos({
4792
5411
  withRecoverable: false
4793
5412
  });
4794
- const controlAssetRef = params.controlAssetId ? chunk3AQNLQPM_cjs.AssetRef.fromId(chunk3AQNLQPM_cjs.AssetId.fromString(params.controlAssetId)) : null;
5413
+ const controlAssetRef = params.controlAssetId ? chunkUHOHLN3C_cjs.AssetRef.fromId(chunkUHOHLN3C_cjs.AssetId.fromString(params.controlAssetId)) : null;
4795
5414
  const coinSelection = selectVirtualCoins(virtualCoins, Number(this.wallet.dustAmount));
4796
5415
  let totalBtcSelected = 0n;
4797
5416
  const assetChanges = /* @__PURE__ */ new Map();
@@ -4804,8 +5423,8 @@ var AssetManager = class extends ReadonlyAssetManager {
4804
5423
  }
4805
5424
  }
4806
5425
  const groups = [];
4807
- const issuedAssetOutput = chunk3AQNLQPM_cjs.AssetOutput.create(0, params.amount);
4808
- const issuedAssetGroup = chunk3AQNLQPM_cjs.AssetGroup.create(
5426
+ const issuedAssetOutput = chunkUHOHLN3C_cjs.AssetOutput.create(0, params.amount);
5427
+ const issuedAssetGroup = chunkUHOHLN3C_cjs.AssetGroup.create(
4809
5428
  null,
4810
5429
  controlAssetRef,
4811
5430
  [],
@@ -4820,15 +5439,15 @@ var AssetManager = class extends ReadonlyAssetManager {
4820
5439
  for (const [inputIndex, assets] of assetInputs) {
4821
5440
  for (const asset of assets) {
4822
5441
  if (asset.assetId !== assetId) continue;
4823
- changeInputs.push(chunk3AQNLQPM_cjs.AssetInput.create(inputIndex, asset.amount));
5442
+ changeInputs.push(chunkUHOHLN3C_cjs.AssetInput.create(inputIndex, asset.amount));
4824
5443
  }
4825
5444
  }
4826
5445
  groups.push(
4827
- chunk3AQNLQPM_cjs.AssetGroup.create(
4828
- chunk3AQNLQPM_cjs.AssetId.fromString(assetId),
5446
+ chunkUHOHLN3C_cjs.AssetGroup.create(
5447
+ chunkUHOHLN3C_cjs.AssetId.fromString(assetId),
4829
5448
  null,
4830
5449
  changeInputs,
4831
- [chunk3AQNLQPM_cjs.AssetOutput.create(0, amount)],
5450
+ [chunkUHOHLN3C_cjs.AssetOutput.create(0, amount)],
4832
5451
  []
4833
5452
  )
4834
5453
  );
@@ -4841,7 +5460,7 @@ var AssetManager = class extends ReadonlyAssetManager {
4841
5460
  script: outputAddress.pkScript,
4842
5461
  amount: BigInt(totalBtcSelected)
4843
5462
  },
4844
- Extension.create([chunk3AQNLQPM_cjs.Packet.create(groups)]).txOut()
5463
+ Extension.create([chunkUHOHLN3C_cjs.Packet.create(groups)]).txOut()
4845
5464
  ];
4846
5465
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(
4847
5466
  coinSelection.inputs,
@@ -4849,7 +5468,7 @@ var AssetManager = class extends ReadonlyAssetManager {
4849
5468
  );
4850
5469
  return {
4851
5470
  arkTxId: arkTxid,
4852
- assetId: chunk3AQNLQPM_cjs.AssetId.create(arkTxid, 0).toString()
5471
+ assetId: chunkUHOHLN3C_cjs.AssetId.create(arkTxid, 0).toString()
4853
5472
  };
4854
5473
  }
4855
5474
  /**
@@ -4921,16 +5540,16 @@ var AssetManager = class extends ReadonlyAssetManager {
4921
5540
  for (const [inputIndex, assets] of assetInputs) {
4922
5541
  for (const asset of assets) {
4923
5542
  if (asset.assetId !== params.assetId) continue;
4924
- reissueInputs.push(chunk3AQNLQPM_cjs.AssetInput.create(inputIndex, asset.amount));
5543
+ reissueInputs.push(chunkUHOHLN3C_cjs.AssetInput.create(inputIndex, asset.amount));
4925
5544
  }
4926
5545
  }
4927
5546
  const totalAssetAmount = assetToReissueAmount + params.amount;
4928
- const reissueAssetIdObj = chunk3AQNLQPM_cjs.AssetId.fromString(params.assetId);
4929
- const reissueAssetGroup = chunk3AQNLQPM_cjs.AssetGroup.create(
5547
+ const reissueAssetIdObj = chunkUHOHLN3C_cjs.AssetId.fromString(params.assetId);
5548
+ const reissueAssetGroup = chunkUHOHLN3C_cjs.AssetGroup.create(
4930
5549
  reissueAssetIdObj,
4931
5550
  null,
4932
5551
  reissueInputs,
4933
- [chunk3AQNLQPM_cjs.AssetOutput.create(0, totalAssetAmount)],
5552
+ [chunkUHOHLN3C_cjs.AssetOutput.create(0, totalAssetAmount)],
4934
5553
  []
4935
5554
  );
4936
5555
  const groups = [reissueAssetGroup];
@@ -4939,15 +5558,15 @@ var AssetManager = class extends ReadonlyAssetManager {
4939
5558
  for (const [inputIndex, assets] of assetInputs) {
4940
5559
  for (const asset of assets) {
4941
5560
  if (asset.assetId !== assetId) continue;
4942
- changeInputs.push(chunk3AQNLQPM_cjs.AssetInput.create(inputIndex, asset.amount));
5561
+ changeInputs.push(chunkUHOHLN3C_cjs.AssetInput.create(inputIndex, asset.amount));
4943
5562
  }
4944
5563
  }
4945
5564
  groups.push(
4946
- chunk3AQNLQPM_cjs.AssetGroup.create(
4947
- chunk3AQNLQPM_cjs.AssetId.fromString(assetId),
5565
+ chunkUHOHLN3C_cjs.AssetGroup.create(
5566
+ chunkUHOHLN3C_cjs.AssetId.fromString(assetId),
4948
5567
  null,
4949
5568
  changeInputs,
4950
- [chunk3AQNLQPM_cjs.AssetOutput.create(0, amount)],
5569
+ [chunkUHOHLN3C_cjs.AssetOutput.create(0, amount)],
4951
5570
  []
4952
5571
  )
4953
5572
  );
@@ -4959,7 +5578,7 @@ var AssetManager = class extends ReadonlyAssetManager {
4959
5578
  script: outputAddress.pkScript,
4960
5579
  amount: BigInt(totalBtcSelected)
4961
5580
  },
4962
- Extension.create([chunk3AQNLQPM_cjs.Packet.create(groups)]).txOut()
5581
+ Extension.create([chunkUHOHLN3C_cjs.Packet.create(groups)]).txOut()
4963
5582
  ];
4964
5583
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
4965
5584
  return arkTxid;
@@ -5026,15 +5645,15 @@ var AssetManager = class extends ReadonlyAssetManager {
5026
5645
  for (const [inputIndex, assets] of assetInputs) {
5027
5646
  for (const asset of assets) {
5028
5647
  if (asset.assetId !== assetId) continue;
5029
- changeInputs.push(chunk3AQNLQPM_cjs.AssetInput.create(inputIndex, asset.amount));
5648
+ changeInputs.push(chunkUHOHLN3C_cjs.AssetInput.create(inputIndex, asset.amount));
5030
5649
  }
5031
5650
  }
5032
5651
  groups.push(
5033
- chunk3AQNLQPM_cjs.AssetGroup.create(
5034
- chunk3AQNLQPM_cjs.AssetId.fromString(assetId),
5652
+ chunkUHOHLN3C_cjs.AssetGroup.create(
5653
+ chunkUHOHLN3C_cjs.AssetId.fromString(assetId),
5035
5654
  null,
5036
5655
  changeInputs,
5037
- amount > 0n ? [chunk3AQNLQPM_cjs.AssetOutput.create(0, amount)] : [],
5656
+ amount > 0n ? [chunkUHOHLN3C_cjs.AssetOutput.create(0, amount)] : [],
5038
5657
  []
5039
5658
  )
5040
5659
  );
@@ -5046,7 +5665,7 @@ var AssetManager = class extends ReadonlyAssetManager {
5046
5665
  script: outputAddress.pkScript,
5047
5666
  amount: BigInt(totalBtcSelected)
5048
5667
  },
5049
- Extension.create([chunk3AQNLQPM_cjs.Packet.create(groups)]).txOut()
5668
+ Extension.create([chunkUHOHLN3C_cjs.Packet.create(groups)]).txOut()
5050
5669
  ];
5051
5670
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
5052
5671
  return arkTxid;
@@ -5071,7 +5690,7 @@ function castMetadata(metadata) {
5071
5690
  } else {
5072
5691
  throw new Error("Invalid metadata value type");
5073
5692
  }
5074
- md.push(chunk3AQNLQPM_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
5693
+ md.push(chunkUHOHLN3C_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
5075
5694
  }
5076
5695
  return md;
5077
5696
  }
@@ -5356,7 +5975,7 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
5356
5975
  expire_at: 0,
5357
5976
  cosigners_public_keys: cosignerPubKeys
5358
5977
  };
5359
- const proof = chunk3AQNLQPM_cjs.Intent.create(message, coins, outputs);
5978
+ const proof = chunkUHOHLN3C_cjs.Intent.create(message, coins, outputs);
5360
5979
  const signedProof = await identity.sign(proof);
5361
5980
  return {
5362
5981
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -5385,20 +6004,6 @@ function isAnnotated(v) {
5385
6004
  return v.tapTree !== void 0 && v.forfeitTapLeafScript !== void 0 && v.intentTapLeafScript !== void 0;
5386
6005
  }
5387
6006
 
5388
- // src/repositories/virtualTxRepository.ts
5389
- var ChainedTxType = /* @__PURE__ */ ((ChainedTxType2) => {
5390
- ChainedTxType2[ChainedTxType2["Unspecified"] = 0] = "Unspecified";
5391
- ChainedTxType2[ChainedTxType2["Commitment"] = 1] = "Commitment";
5392
- ChainedTxType2[ChainedTxType2["Ark"] = 2] = "Ark";
5393
- ChainedTxType2[ChainedTxType2["Tree"] = 3] = "Tree";
5394
- ChainedTxType2[ChainedTxType2["Checkpoint"] = 4] = "Checkpoint";
5395
- return ChainedTxType2;
5396
- })(ChainedTxType || {});
5397
- function mergeChainedTxType(incoming, prev) {
5398
- if (incoming !== 0 /* Unspecified */) return incoming;
5399
- return prev ?? 0 /* Unspecified */;
5400
- }
5401
-
5402
6007
  // src/repositories/inMemory/intentRepository.ts
5403
6008
  var InMemoryIntentRepository = class {
5404
6009
  version = 1;
@@ -7454,7 +8059,7 @@ var ContractWatcher = class {
7454
8059
  this.connectionState = "connected";
7455
8060
  this.reconnectAttempts = 0;
7456
8061
  this.listenLoop().catch((e) => {
7457
- if (chunk3AQNLQPM_cjs.isEventSourceError(e)) {
8062
+ if (chunkUHOHLN3C_cjs.isEventSourceError(e)) {
7458
8063
  console.debug("ContractWatcher subscription disconnected; reconnecting");
7459
8064
  } else {
7460
8065
  console.error(e);
@@ -7818,66 +8423,9 @@ function dedupeOutpoints(outpoints) {
7818
8423
  return out;
7819
8424
  }
7820
8425
 
7821
- // src/utils/syncCursors.ts
7822
- var SAFETY_LAG_MS = 3e4;
7823
- var OVERLAP_MS = 24 * 60 * 60 * 1e3;
7824
- var walletStateLocks = /* @__PURE__ */ new WeakMap();
7825
- async function updateWalletState(repo, updater) {
7826
- const prev = walletStateLocks.get(repo) ?? Promise.resolve();
7827
- const op = prev.then(async () => {
7828
- const state = await repo.getWalletState() ?? {};
7829
- await repo.saveWalletState(updater(state));
7830
- });
7831
- walletStateLocks.set(
7832
- repo,
7833
- op.catch(() => {
7834
- })
7835
- );
7836
- return op;
7837
- }
7838
- var CURSOR_MIGRATED_KEY = "vtxoCursorMigrated";
7839
- function hasMigrationMarker(state) {
7840
- return state?.settings?.[CURSOR_MIGRATED_KEY] === true;
7841
- }
7842
- async function getSyncCursor(repo) {
7843
- const state = await repo.getWalletState();
7844
- if (!hasMigrationMarker(state)) return 0;
7845
- return state?.lastSyncTime ?? 0;
7846
- }
7847
- async function advanceSyncCursor(repo, lastUpdatedAt) {
7848
- await updateWalletState(repo, (state) => {
7849
- const current = hasMigrationMarker(state) ? state.lastSyncTime ?? 0 : 0;
7850
- return {
7851
- ...state,
7852
- lastSyncTime: Math.max(current, lastUpdatedAt),
7853
- settings: {
7854
- ...state.settings ?? {},
7855
- [CURSOR_MIGRATED_KEY]: true
7856
- }
7857
- };
7858
- });
7859
- }
7860
- async function clearSyncCursor(repo) {
7861
- await updateWalletState(repo, (state) => {
7862
- const { [CURSOR_MIGRATED_KEY]: _, ...restSettings } = state.settings ?? {};
7863
- return {
7864
- ...state,
7865
- lastSyncTime: void 0,
7866
- settings: restSettings
7867
- };
7868
- });
7869
- }
7870
- function computeSyncWindow(cursor) {
7871
- const after = Math.max(0, cursor - OVERLAP_MS);
7872
- return { after };
7873
- }
7874
- function cursorCutoff(requestStartedAt) {
7875
- return (requestStartedAt ?? Date.now()) - SAFETY_LAG_MS;
7876
- }
7877
-
7878
- // src/contracts/contractManager.ts
7879
- function areCoalescibleContractTypes(a, b) {
7880
- return a === "default" && b === "boarding" || a === "boarding" && b === "default";
8426
+ // src/contracts/contractManager.ts
8427
+ function areCoalescibleContractTypes(a, b) {
8428
+ return a === "default" && b === "boarding" || a === "boarding" && b === "default";
7881
8429
  }
7882
8430
  var SCAN_MAX_INDEX = 1e4;
7883
8431
  var DEFAULT_SCAN_BATCH = 10;
@@ -7887,6 +8435,10 @@ var ContractManager = class _ContractManager {
7887
8435
  initialized = false;
7888
8436
  eventCallbacks = /* @__PURE__ */ new Set();
7889
8437
  stopWatcherFn;
8438
+ /** `undefined` while online; the failure reason once a sync degrades. */
8439
+ syncDegradedReason;
8440
+ /** Epoch-ms of the last successful provider sync, if any. */
8441
+ lastSyncedAt;
7890
8442
  constructor(config) {
7891
8443
  this.config = config;
7892
8444
  this.watcher = new ContractWatcher({
@@ -7909,6 +8461,26 @@ var ContractManager = class _ContractManager {
7909
8461
  await cm.initialize();
7910
8462
  return cm;
7911
8463
  }
8464
+ /**
8465
+ * Latest provider-sync health. See {@link ContractSyncState}. Degradation is
8466
+ * recorded by {@link initialize}, {@link getContractsWithVtxos}, and
8467
+ * {@link createContract}; it flips back to `online` on the next successful
8468
+ * sync. Purely a freshness signal — not a source of truth for wallet data.
8469
+ */
8470
+ getSyncState() {
8471
+ return this.syncDegradedReason === void 0 ? { mode: "online", lastSyncedAt: this.lastSyncedAt } : {
8472
+ mode: "degraded",
8473
+ reason: this.syncDegradedReason,
8474
+ lastSyncedAt: this.lastSyncedAt
8475
+ };
8476
+ }
8477
+ markSyncOnline() {
8478
+ this.syncDegradedReason = void 0;
8479
+ this.lastSyncedAt = Date.now();
8480
+ }
8481
+ markSyncDegraded(err) {
8482
+ this.syncDegradedReason = err instanceof Error ? err.message : String(err);
8483
+ }
7912
8484
  async initialize() {
7913
8485
  if (this.initialized) {
7914
8486
  return;
@@ -7917,7 +8489,13 @@ var ContractManager = class _ContractManager {
7917
8489
  for (const contract of contracts) {
7918
8490
  await this.watcher.addContract(contract);
7919
8491
  }
7920
- await this.reconcileWatched();
8492
+ try {
8493
+ await this.reconcileWatched();
8494
+ this.markSyncOnline();
8495
+ } catch (err) {
8496
+ if (!isRetryableProviderError(err)) throw err;
8497
+ this.markSyncDegraded(err);
8498
+ }
7921
8499
  this.initialized = true;
7922
8500
  this.stopWatcherFn = await this.watcher.startWatching((event) => {
7923
8501
  this.handleContractEvent(event).catch((error) => {
@@ -7971,7 +8549,13 @@ var ContractManager = class _ContractManager {
7971
8549
  async createContract(params) {
7972
8550
  const { contract, persisted } = await this.upsertContract(params);
7973
8551
  if (persisted) {
7974
- await this.fetchContractVxosFromIndexer([contract]);
8552
+ try {
8553
+ await this.fetchContractVxosFromIndexer([contract]);
8554
+ this.markSyncOnline();
8555
+ } catch (err) {
8556
+ if (!isRetryableProviderError(err)) throw err;
8557
+ this.markSyncDegraded(err);
8558
+ }
7975
8559
  await this.watcher.addContract(contract);
7976
8560
  }
7977
8561
  return contract;
@@ -8170,7 +8754,13 @@ var ContractManager = class _ContractManager {
8170
8754
  }
8171
8755
  async getContractsWithVtxos(filter, pageSize) {
8172
8756
  const contracts = await this.getContracts(filter);
8173
- await this.syncContracts({ contracts, pageSize });
8757
+ try {
8758
+ await this.syncContracts({ contracts, pageSize });
8759
+ this.markSyncOnline();
8760
+ } catch (err) {
8761
+ if (!isRetryableProviderError(err)) throw err;
8762
+ this.markSyncDegraded(err);
8763
+ }
8174
8764
  const vtxos = await this.getVtxosForContracts(contracts);
8175
8765
  return contracts.map((contract) => ({
8176
8766
  contract,
@@ -8408,15 +8998,33 @@ var ContractManager = class _ContractManager {
8408
8998
  * Handle events from the watcher.
8409
8999
  */
8410
9000
  async handleContractEvent(event) {
8411
- switch (event.type) {
8412
- // Delta-sync only the changed virtual outputs for this contract.
8413
- case "vtxo_received":
8414
- case "vtxo_spent":
8415
- await this.syncContracts({ contracts: [event.contract] });
8416
- break;
8417
- case "connection_reset":
8418
- await this.reconcileWatched();
8419
- break;
9001
+ try {
9002
+ switch (event.type) {
9003
+ // Delta-sync only the changed virtual outputs for this contract.
9004
+ case "vtxo_received":
9005
+ await this.syncContracts({ contracts: [event.contract] });
9006
+ this.markSyncOnline();
9007
+ break;
9008
+ case "vtxo_spent":
9009
+ await this.syncContracts({ contracts: [event.contract] });
9010
+ this.markSyncOnline();
9011
+ if (this.config.onVtxosSpent) {
9012
+ try {
9013
+ await this.config.onVtxosSpent(
9014
+ event.vtxos.map((v) => ({ txid: v.txid, vout: v.vout }))
9015
+ );
9016
+ } catch {
9017
+ }
9018
+ }
9019
+ break;
9020
+ case "connection_reset":
9021
+ await this.reconcileWatched();
9022
+ this.markSyncOnline();
9023
+ break;
9024
+ }
9025
+ } catch (err) {
9026
+ if (!isRetryableProviderError(err)) throw err;
9027
+ this.markSyncDegraded(err);
8420
9028
  }
8421
9029
  this.emitEvent(event);
8422
9030
  }
@@ -8498,6 +9106,12 @@ var ContractManager = class _ContractManager {
8498
9106
  contract,
8499
9107
  filtered
8500
9108
  );
9109
+ if (this.config.onVtxosPersisted) {
9110
+ try {
9111
+ await this.config.onVtxosPersisted(contract, filtered);
9112
+ } catch {
9113
+ }
9114
+ }
8501
9115
  }
8502
9116
  }
8503
9117
  async fetchContractVxosFromIndexer(contracts, pageSize, syncWindow) {
@@ -8518,6 +9132,15 @@ var ContractManager = class _ContractManager {
8518
9132
  contract,
8519
9133
  filtered
8520
9134
  );
9135
+ if (this.config.onVtxosPersisted) {
9136
+ try {
9137
+ await this.config.onVtxosPersisted(
9138
+ contract,
9139
+ filtered
9140
+ );
9141
+ } catch {
9142
+ }
9143
+ }
8521
9144
  }
8522
9145
  }
8523
9146
  return result;
@@ -9391,6 +10014,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
9391
10014
  * (ContractManager isn't given it); `undefined` ⇒ no-op.
9392
10015
  */
9393
10016
  virtualTxRepository;
10017
+ /** Opt-in exit-data capture settings; see {@link StorageConfig.exitDataCapture}. */
10018
+ exitDataCapture;
9394
10019
  _assetManager;
9395
10020
  walletContractTimelocks;
9396
10021
  // Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
@@ -9433,6 +10058,72 @@ var ReadonlyWallet = class _ReadonlyWallet {
9433
10058
  * construction-time snapshot for their lifetime.
9434
10059
  */
9435
10060
  _arkServerPublicKey;
10061
+ /**
10062
+ * Whether this wallet was constructed from live operator server-info
10063
+ * (`"live"`) or from a cached snapshot because the operator was unreachable
10064
+ * (`"cache"`). Freshness signal for {@link getProviderConnectionState}.
10065
+ */
10066
+ _serverInfoSource = "live";
10067
+ /**
10068
+ * Epoch-ms of the last known live operator contact: construction time on the
10069
+ * `live` boot path, the cached snapshot's `savedAt` on the `cache` path.
10070
+ */
10071
+ _serverInfoLastOnlineAt;
10072
+ /** @see {@link _serverInfoSource} */
10073
+ get serverInfoSource() {
10074
+ return this._serverInfoSource;
10075
+ }
10076
+ /**
10077
+ * Composed provider-connection freshness: the boot server-info source
10078
+ * (Arkade) combined with the contract-manager's indexer-sync health, if the
10079
+ * manager has been initialized. Reads no live provider state — it never
10080
+ * forces a `ContractManager` to construct — so it is safe for readonly
10081
+ * callers that only use address/balance APIs.
10082
+ *
10083
+ * - Boot fell back to a cached snapshot → degraded on `arkade` (`cache`).
10084
+ * - Otherwise, if the contract manager has degraded to repository data →
10085
+ * degraded on `indexer` (`repository`).
10086
+ * - Otherwise online.
10087
+ *
10088
+ * This only describes sync freshness; wallet balances/VTXOs are always read
10089
+ * from the repository regardless of this state.
10090
+ */
10091
+ getProviderConnectionState() {
10092
+ if (this._serverInfoSource === "cache") {
10093
+ return {
10094
+ mode: "degraded",
10095
+ source: "cache",
10096
+ provider: "arkade",
10097
+ reason: "constructed from cached server-info; operator was unreachable at boot",
10098
+ lastOnlineAt: this._serverInfoLastOnlineAt
10099
+ };
10100
+ }
10101
+ const sync = this._contractManager?.getSyncState();
10102
+ if (sync?.mode === "degraded") {
10103
+ return {
10104
+ mode: "degraded",
10105
+ source: "repository",
10106
+ provider: "indexer",
10107
+ reason: sync.reason,
10108
+ lastOnlineAt: sync.lastSyncedAt ?? this._serverInfoLastOnlineAt
10109
+ };
10110
+ }
10111
+ return {
10112
+ mode: "online",
10113
+ source: "live",
10114
+ lastOnlineAt: sync?.lastSyncedAt ?? this._serverInfoLastOnlineAt ?? 0
10115
+ };
10116
+ }
10117
+ /**
10118
+ * The contract-manager's current provider-sync health **without forcing it
10119
+ * to initialize** — reads the already-constructed manager, or reports
10120
+ * `online` when none exists yet. Unlike {@link getContractManager}, this
10121
+ * never triggers a remote sync, so it is safe on a pure diagnostics path
10122
+ * (e.g. the service-worker sync-state message).
10123
+ */
10124
+ getContractSyncState() {
10125
+ return this._contractManager?.getSyncState() ?? { mode: "online" };
10126
+ }
9436
10127
  /**
9437
10128
  * x-only hex of the operator's deprecated signer keys (from
9438
10129
  * `ArkInfo.deprecatedSigners`), cached for the OFFLINE read/watch paths.
@@ -9548,7 +10239,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
9548
10239
  */
9549
10240
  static async setupWalletConfig(config, pubKey) {
9550
10241
  const arkadeServerUrl = getArkadeServerUrl(config);
9551
- const arkProvider = config.arkProvider || new chunk3AQNLQPM_cjs.RestArkProvider(arkadeServerUrl);
10242
+ const arkProvider = config.arkProvider || new chunkUHOHLN3C_cjs.RestArkProvider(arkadeServerUrl);
9552
10243
  let indexerProvider = config.indexerProvider;
9553
10244
  if (!indexerProvider) {
9554
10245
  let indexerUrl = config.indexerUrl;
@@ -9565,9 +10256,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
9565
10256
  indexerUrl = arkadeServerUrl;
9566
10257
  }
9567
10258
  }
9568
- indexerProvider = new chunk3AQNLQPM_cjs.RestIndexerProvider(indexerUrl);
10259
+ indexerProvider = new chunkUHOHLN3C_cjs.RestIndexerProvider(indexerUrl);
9569
10260
  }
9570
- const info = await arkProvider.getInfo();
10261
+ const walletRepository = config.storage?.walletRepository ?? new IndexedDBWalletRepository();
10262
+ const contractRepository = config.storage?.contractRepository ?? new IndexedDBContractRepository();
10263
+ const {
10264
+ info,
10265
+ source: serverInfoSource,
10266
+ lastOnlineAt: serverInfoLastOnlineAt
10267
+ } = await resolveArkInfo(arkProvider, walletRepository);
9571
10268
  const network = chunkHXDZ2QTW_cjs.getNetwork(info.network);
9572
10269
  if ("descriptor" in config.identity) {
9573
10270
  const descriptor = config.identity.descriptor;
@@ -9621,8 +10318,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
9621
10318
  serverPubKey: base.hex.encode(serverPubKey),
9622
10319
  csvTimelock: chunkHXDZ2QTW_cjs.timelockToSequence(boardingTimelock).toString()
9623
10320
  });
9624
- const walletRepository = config.storage?.walletRepository ?? new IndexedDBWalletRepository();
9625
- const contractRepository = config.storage?.contractRepository ?? new IndexedDBContractRepository();
9626
10321
  return {
9627
10322
  arkProvider,
9628
10323
  indexerProvider,
@@ -9636,6 +10331,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
9636
10331
  walletRepository,
9637
10332
  contractRepository,
9638
10333
  info,
10334
+ serverInfoSource,
10335
+ serverInfoLastOnlineAt,
9639
10336
  delegateProvider: config.delegateProvider || config.delegatorProvider,
9640
10337
  /** @deprecated alias for `delegateProvider` */
9641
10338
  delegatorProvider: config.delegateProvider || config.delegatorProvider,
@@ -9671,6 +10368,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
9671
10368
  );
9672
10369
  wallet.intentRepository = config.storage?.intentRepository;
9673
10370
  wallet.virtualTxRepository = config.storage?.virtualTxRepository;
10371
+ wallet.exitDataCapture = config.storage?.exitDataCapture;
10372
+ wallet._serverInfoSource = setup.serverInfoSource;
10373
+ if (setup.serverInfoSource === "live") {
10374
+ const now = Date.now();
10375
+ await saveValidatedArkInfoSnapshot(setup.walletRepository, setup.info, now);
10376
+ wallet._serverInfoLastOnlineAt = now;
10377
+ } else {
10378
+ wallet._serverInfoLastOnlineAt = setup.serverInfoLastOnlineAt;
10379
+ }
9674
10380
  wallet.refreshDeprecatedSigners(setup.info);
9675
10381
  return wallet;
9676
10382
  }
@@ -9801,7 +10507,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
9801
10507
  const response = await contractManager.getContractsWithVtxos();
9802
10508
  const allVtxos = response.flatMap((_) => _.vtxos);
9803
10509
  const { boardingTxs, commitmentsToIgnore } = await this.getBoardingTxs();
9804
- const getTxCreatedAt = (txid) => this.indexerProvider.getVtxos({ outpoints: [{ txid, vout: 0 }] }).then((res) => res.vtxos[0]?.createdAt.getTime());
10510
+ const getTxCreatedAt = (txid) => this.indexerProvider.getVtxos({ outpoints: [{ txid, vout: 0 }] }).then((res) => res.vtxos[0]?.createdAt.getTime()).catch((err) => {
10511
+ if (isRetryableProviderError(err)) return void 0;
10512
+ throw err;
10513
+ });
9805
10514
  return buildTransactionHistory(allVtxos, boardingTxs, commitmentsToIgnore, getTxCreatedAt);
9806
10515
  }
9807
10516
  /**
@@ -10220,11 +10929,39 @@ var ReadonlyWallet = class _ReadonlyWallet {
10220
10929
  }
10221
10930
  }
10222
10931
  async initializeContractManager() {
10932
+ const virtualTxRepository = this.virtualTxRepository;
10933
+ let onVtxosPersisted;
10934
+ let onVtxosSpent;
10935
+ if (virtualTxRepository) {
10936
+ const capture = this.exitDataCapture;
10937
+ const resolver = createExitChainResolver({
10938
+ indexer: this.indexerProvider,
10939
+ repository: virtualTxRepository,
10940
+ extraSources: capture?.sources
10941
+ });
10942
+ onVtxosPersisted = async (_contract, vtxos) => {
10943
+ for (const v of vtxos) {
10944
+ if (v.virtualStatus.state === "spent") continue;
10945
+ await captureExitBranch({
10946
+ resolver,
10947
+ repository: virtualTxRepository,
10948
+ vtxo: { txid: v.txid, vout: v.vout },
10949
+ value: v.value,
10950
+ mode: capture?.mode ?? DEFAULT_EXIT_CAPTURE_MODE,
10951
+ minExitWorthSats: capture?.minExitWorthSats ?? DEFAULT_MIN_EXIT_WORTH_SATS
10952
+ }).catch(() => {
10953
+ });
10954
+ }
10955
+ };
10956
+ onVtxosSpent = (vtxos) => pruneExitBranches(virtualTxRepository, vtxos);
10957
+ }
10223
10958
  const manager = await ContractManager.create({
10224
10959
  indexerProvider: this.indexerProvider,
10225
10960
  contractRepository: this.contractRepository,
10226
10961
  walletRepository: this.walletRepository,
10227
10962
  intentRepository: this.intentRepository,
10963
+ onVtxosPersisted,
10964
+ onVtxosSpent,
10228
10965
  watcherConfig: this.watcherConfig
10229
10966
  });
10230
10967
  const baselinePubkey = await this.identity.xOnlyPublicKey();
@@ -10899,6 +11636,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10899
11636
  boot?.rotator,
10900
11637
  boot?.provider
10901
11638
  );
11639
+ wallet._serverInfoSource = setup.serverInfoSource;
11640
+ if (setup.serverInfoSource === "live") {
11641
+ const now = Date.now();
11642
+ await saveValidatedArkInfoSnapshot(setup.walletRepository, setup.info, now);
11643
+ wallet._serverInfoLastOnlineAt = now;
11644
+ } else {
11645
+ wallet._serverInfoLastOnlineAt = setup.serverInfoLastOnlineAt;
11646
+ }
10902
11647
  wallet.refreshDeprecatedSigners(setup.info);
10903
11648
  {
10904
11649
  const ap = setup.arkProvider;
@@ -10920,6 +11665,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10920
11665
  }
10921
11666
  wallet.intentRepository = config.storage?.intentRepository;
10922
11667
  wallet.virtualTxRepository = config.storage?.virtualTxRepository;
11668
+ wallet.exitDataCapture = config.storage?.exitDataCapture;
10923
11669
  await wallet.getVtxoManager();
10924
11670
  return wallet;
10925
11671
  }
@@ -11580,9 +12326,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11580
12326
  ...existing ?? {},
11581
12327
  intentTxId,
11582
12328
  registerProof: intent.proof,
11583
- registerProofMessage: chunk3AQNLQPM_cjs.Intent.encodeMessage(intent.message),
12329
+ registerProofMessage: chunkUHOHLN3C_cjs.Intent.encodeMessage(intent.message),
11584
12330
  deleteProof: deleteIntent.proof,
11585
- deleteProofMessage: chunk3AQNLQPM_cjs.Intent.encodeMessage(deleteIntent.message),
12331
+ deleteProofMessage: chunkUHOHLN3C_cjs.Intent.encodeMessage(deleteIntent.message),
11586
12332
  intentVtxos: inputs.map((i) => ({
11587
12333
  txid: i.txid,
11588
12334
  vout: i.vout
@@ -11614,7 +12360,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11614
12360
  try {
11615
12361
  return await this.arkProvider.registerIntent(intent);
11616
12362
  } catch (error) {
11617
- if (error instanceof chunk3AQNLQPM_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
12363
+ if (error instanceof chunkUHOHLN3C_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
11618
12364
  const deleteIntent = await this.makeDeleteIntentSignature(inputs);
11619
12365
  await this.arkProvider.deleteIntent(deleteIntent);
11620
12366
  return this.arkProvider.registerIntent(intent);
@@ -11630,7 +12376,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11630
12376
  expire_at: 0,
11631
12377
  cosigners_public_keys: cosignerPubKeys
11632
12378
  };
11633
- const proof = chunk3AQNLQPM_cjs.Intent.create(message, coins, outputs);
12379
+ const proof = chunkUHOHLN3C_cjs.Intent.create(message, coins, outputs);
11634
12380
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
11635
12381
  return {
11636
12382
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -11642,7 +12388,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11642
12388
  type: "delete",
11643
12389
  expire_at: 0
11644
12390
  };
11645
- const proof = chunk3AQNLQPM_cjs.Intent.create(message, coins, []);
12391
+ const proof = chunkUHOHLN3C_cjs.Intent.create(message, coins, []);
11646
12392
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
11647
12393
  return {
11648
12394
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -11654,7 +12400,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
11654
12400
  type: "get-pending-tx",
11655
12401
  expire_at: 0
11656
12402
  };
11657
- const proof = chunk3AQNLQPM_cjs.Intent.create(message, coins, []);
12403
+ const proof = chunkUHOHLN3C_cjs.Intent.create(message, coins, []);
11658
12404
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
11659
12405
  return {
11660
12406
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -12612,7 +13358,7 @@ var MessageBus = class {
12612
13358
  this.initialized = true;
12613
13359
  }
12614
13360
  async buildServices(config) {
12615
- const arkProvider = new chunk3AQNLQPM_cjs.RestArkProvider(config.arkServer.url);
13361
+ const arkProvider = new chunkUHOHLN3C_cjs.RestArkProvider(config.arkServer.url);
12616
13362
  const storage = {
12617
13363
  walletRepository: this.walletRepository,
12618
13364
  contractRepository: this.contractRepository
@@ -13437,10 +14183,18 @@ var WalletMessageHandler = class {
13437
14183
  type: "WALLET_STATUS",
13438
14184
  payload: {
13439
14185
  walletInitialized: true,
13440
- xOnlyPublicKey: pubKey
14186
+ xOnlyPublicKey: pubKey,
14187
+ providerConnectionState: this.readonlyWallet.getProviderConnectionState()
13441
14188
  }
13442
14189
  });
13443
14190
  }
14191
+ case "GET_CONTRACT_SYNC_STATE": {
14192
+ return this.tagged({
14193
+ id,
14194
+ type: "CONTRACT_SYNC_STATE",
14195
+ payload: { syncState: this.readonlyWallet.getContractSyncState() }
14196
+ });
14197
+ }
13444
14198
  case "CLEAR": {
13445
14199
  await this.clear();
13446
14200
  return this.tagged({
@@ -13769,7 +14523,7 @@ var WalletMessageHandler = class {
13769
14523
  // Wallet methods
13770
14524
  async handleInitWallet({ payload }) {
13771
14525
  const { arkServerUrl } = payload;
13772
- this.indexerProvider = new chunk3AQNLQPM_cjs.RestIndexerProvider(arkServerUrl);
14526
+ this.indexerProvider = new chunkUHOHLN3C_cjs.RestIndexerProvider(arkServerUrl);
13773
14527
  await this.onWalletInitialized();
13774
14528
  }
13775
14529
  async handleGetBalance() {
@@ -14203,6 +14957,7 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
14203
14957
  GET_BALANCE: 1e4,
14204
14958
  GET_BOARDING_ADDRESS: 1e4,
14205
14959
  GET_STATUS: 1e4,
14960
+ GET_CONTRACT_SYNC_STATE: 1e4,
14206
14961
  GET_DELEGATE_INFO: 1e4,
14207
14962
  IS_CONTRACT_MANAGER_WATCHING: 1e4,
14208
14963
  // Medium reads — may involve indexer queries
@@ -14811,6 +15566,19 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14811
15566
  throw new Error(`Failed to get status: ${error}`);
14812
15567
  }
14813
15568
  }
15569
+ /**
15570
+ * Wallet-level provider-connection freshness, delegated to the worker via
15571
+ * `GET_STATUS`. Async by necessity (the worker boundary is asynchronous); no
15572
+ * synchronous variant is offered because it would have the same transport
15573
+ * mismatch as the contract-manager proxy's cached `getSyncState()`.
15574
+ */
15575
+ async getProviderConnectionState() {
15576
+ const { providerConnectionState } = await this.getStatus();
15577
+ if (!providerConnectionState) {
15578
+ throw new Error("Worker did not report provider connection state");
15579
+ }
15580
+ return providerConnectionState;
15581
+ }
14814
15582
  async getActivityHistory() {
14815
15583
  return buildActivities(await this.getTransactionHistory(), this.activity.all());
14816
15584
  }
@@ -14865,6 +15633,32 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14865
15633
  return wallet.sendMessage(message);
14866
15634
  };
14867
15635
  const messageTag = this.messageTag;
15636
+ const fetchSyncState = async () => {
15637
+ const message = {
15638
+ type: "GET_CONTRACT_SYNC_STATE",
15639
+ id: getRandomId(),
15640
+ tag: messageTag
15641
+ };
15642
+ const response = await sendContractMessage(message);
15643
+ return response.payload.syncState;
15644
+ };
15645
+ const UNKNOWN_STATE = {
15646
+ mode: "degraded",
15647
+ reason: "contract sync state unavailable from the worker"
15648
+ };
15649
+ let syncState = UNKNOWN_STATE;
15650
+ let everProbed = false;
15651
+ const refreshSyncState = async () => {
15652
+ try {
15653
+ syncState = await fetchSyncState();
15654
+ everProbed = true;
15655
+ } catch {
15656
+ if (!everProbed) {
15657
+ syncState = UNKNOWN_STATE;
15658
+ }
15659
+ }
15660
+ };
15661
+ await refreshSyncState();
14868
15662
  const manager = {
14869
15663
  async createContract(params) {
14870
15664
  const message = {
@@ -14875,6 +15669,7 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14875
15669
  };
14876
15670
  try {
14877
15671
  const response = await sendContractMessage(message);
15672
+ await refreshSyncState();
14878
15673
  return response.payload.contract;
14879
15674
  } catch (e) {
14880
15675
  throw new Error("Failed to create contract");
@@ -14903,11 +15698,15 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
14903
15698
  };
14904
15699
  try {
14905
15700
  const response = await sendContractMessage(message);
15701
+ await refreshSyncState();
14906
15702
  return response.payload.contracts;
14907
15703
  } catch (e) {
14908
15704
  throw new Error("Failed to get contracts with vtxos");
14909
15705
  }
14910
15706
  },
15707
+ getSyncState() {
15708
+ return syncState;
15709
+ },
14911
15710
  async annotateVtxos(vtxos) {
14912
15711
  if (vtxos.length === 0) return [];
14913
15712
  const message = {
@@ -15016,7 +15815,11 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
15016
15815
  tag: messageTag,
15017
15816
  payload: opts
15018
15817
  };
15019
- await sendContractMessage(message);
15818
+ try {
15819
+ await sendContractMessage(message);
15820
+ } finally {
15821
+ await refreshSyncState();
15822
+ }
15020
15823
  },
15021
15824
  async refreshOutpoints(outpoints) {
15022
15825
  const message = {
@@ -15025,7 +15828,11 @@ var ServiceWorkerReadonlyWallet = class _ServiceWorkerReadonlyWallet {
15025
15828
  tag: messageTag,
15026
15829
  payload: { outpoints }
15027
15830
  };
15028
- await sendContractMessage(message);
15831
+ try {
15832
+ await sendContractMessage(message);
15833
+ } finally {
15834
+ await refreshSyncState();
15835
+ }
15029
15836
  },
15030
15837
  scanContracts() {
15031
15838
  return Promise.reject(
@@ -15576,7 +16383,7 @@ var OnchainWallet = class _OnchainWallet {
15576
16383
  if (!inputs) {
15577
16384
  throw new Error("Fee estimation failed");
15578
16385
  }
15579
- let tx = new chunk3AQNLQPM_cjs.Transaction();
16386
+ let tx = new chunkUHOHLN3C_cjs.Transaction();
15580
16387
  for (const input of inputs) {
15581
16388
  tx.addInput({
15582
16389
  txid: input.txid,
@@ -15606,42 +16413,11 @@ var OnchainWallet = class _OnchainWallet {
15606
16413
  * @see send
15607
16414
  */
15608
16415
  async bumpP2A(parent) {
15609
- const parentVsize = parent.vsize;
15610
- let child = new chunk3AQNLQPM_cjs.Transaction({
15611
- version: 3,
15612
- allowLegacyWitnessUtxo: true
15613
- });
15614
- child.addInput(findP2AOutput(parent));
15615
- const childVsize = TxWeightEstimator.create().addKeySpendInput(true).addP2AInput().addOutputAddress(this.address, this.network).vsize().value;
15616
- const packageVSize = parentVsize + Number(childVsize);
15617
16416
  let feeRate = await this.provider.getFeeRate();
15618
16417
  if (!feeRate || feeRate < _OnchainWallet.MIN_FEE_RATE) {
15619
16418
  feeRate = _OnchainWallet.MIN_FEE_RATE;
15620
16419
  }
15621
- const fee = Math.ceil(feeRate * packageVSize);
15622
- if (!fee) {
15623
- throw new Error(
15624
- `invalid fee, got ${fee} with vsize ${packageVSize}, feeRate ${feeRate}`
15625
- );
15626
- }
15627
- const coins = await this.getCoins();
15628
- const selected = selectCoins(coins, fee, true);
15629
- for (const input of selected.inputs) {
15630
- child.addInput({
15631
- txid: input.txid,
15632
- index: input.vout,
15633
- witnessUtxo: {
15634
- script: this.onchainP2TR.script,
15635
- amount: BigInt(input.value)
15636
- },
15637
- tapInternalKey: this.onchainP2TR.tapInternalKey
15638
- });
15639
- }
15640
- child.addOutputAddress(this.address, P2A.amount + selected.changeAmount, this.network);
15641
- child = await this.identity.sign(child);
15642
- for (let i = 1; i < child.inputsLength; i++) {
15643
- child.finalizeIdx(i);
15644
- }
16420
+ const child = await this.buildBumpPackage(parent, feeRate, await this.getCoins());
15645
16421
  try {
15646
16422
  await this.provider.broadcastTransaction(parent.hex, child.hex);
15647
16423
  } catch (error) {
@@ -15650,6 +16426,59 @@ var OnchainWallet = class _OnchainWallet {
15650
16426
  return [parent.hex, child.hex];
15651
16427
  }
15652
16428
  }
16429
+ /**
16430
+ * Build and sign a CPFP fee child for a parent tx (given as raw hex)
16431
+ * carrying a P2A anchor, funding it from this wallet's **confirmed**
16432
+ * coins, and return the 1P1C package hexes WITHOUT broadcasting.
16433
+ *
16434
+ * This is the graph-mode fee source ({@link ExitFeeWallet}): the exit
16435
+ * executor calls it to bump each transported virtual tx at execution
16436
+ * time, so funding can be deferred rather than pre-signed.
16437
+ *
16438
+ * @param parentHex - Finalized parent transaction, raw network hex
16439
+ * @param feeRate - sat/vB floor for the package (raised to MIN_FEE_RATE)
16440
+ * @returns Tuple of parent hex (unchanged) and signed child hex
16441
+ * @throws If the parent has no anchor, or funding cannot be selected/signed
16442
+ */
16443
+ async bumpAnchor(parentHex, feeRate) {
16444
+ const parent = chunkUHOHLN3C_cjs.Transaction.fromRaw(base.hex.decode(parentHex));
16445
+ const coins = (await this.getCoins()).filter((c) => c.status.confirmed);
16446
+ const child = await this.buildBumpPackage(parent, feeRate, coins);
16447
+ return [parent.hex, child.hex];
16448
+ }
16449
+ /**
16450
+ * Shared core of {@link bumpP2A} and {@link bumpAnchor}: probe the package
16451
+ * fee with a single-input child, select coins for it, then build and sign
16452
+ * with the actual selection (the fee grows per extra input).
16453
+ */
16454
+ async buildBumpPackage(parent, feeRate, coins) {
16455
+ let rate = feeRate;
16456
+ if (!rate || rate < _OnchainWallet.MIN_FEE_RATE) {
16457
+ rate = _OnchainWallet.MIN_FEE_RATE;
16458
+ }
16459
+ const probeVsize = TxWeightEstimator.create().addKeySpendInput(true).addP2AInput().addOutputAddress(this.address, this.network).vsize().value;
16460
+ const probeFee = Math.ceil(rate * (parent.vsize + Number(probeVsize)));
16461
+ if (!probeFee) {
16462
+ throw new Error(
16463
+ `invalid fee, got ${probeFee} with vsize ${parent.vsize + Number(probeVsize)}, feeRate ${rate}`
16464
+ );
16465
+ }
16466
+ const selected = selectCoins(coins, probeFee, true);
16467
+ const { child } = buildAnchorChild({
16468
+ parent,
16469
+ feeRate: rate,
16470
+ fundingCoins: selected.inputs,
16471
+ changeAddress: this.address,
16472
+ changeScript: this.onchainP2TR.script,
16473
+ tapInternalKey: this.onchainP2TR.tapInternalKey,
16474
+ network: this.network
16475
+ });
16476
+ const signed = await this.identity.sign(child);
16477
+ for (let i = 1; i < signed.inputsLength; i++) {
16478
+ signed.finalizeIdx(i);
16479
+ }
16480
+ return signed;
16481
+ }
15653
16482
  };
15654
16483
  function selectCoins(coins, targetAmount, forceChange = false) {
15655
16484
  if (isNaN(targetAmount)) {
@@ -16373,7 +17202,7 @@ exports.BIP322 = void 0;
16373
17202
  async function sign2(message, identity, network) {
16374
17203
  const xOnlyPubKey = await identity.xOnlyPublicKey();
16375
17204
  const payment = btcSigner.p2tr(xOnlyPubKey, void 0, network);
16376
- const toSpend = chunk3AQNLQPM_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
17205
+ const toSpend = chunkUHOHLN3C_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
16377
17206
  const toSign = craftBIP322ToSignP2TR(toSpend, payment.script, xOnlyPubKey);
16378
17207
  const signed = await identity.sign(toSign, [0]);
16379
17208
  signed.finalizeIdx(0);
@@ -16431,7 +17260,7 @@ function verifyP2TR(message, witnessItems, pkScript, pubkey) {
16431
17260
  if (sighashType !== btcSigner.SigHash.DEFAULT && sighashType !== btcSigner.SigHash.ALL) {
16432
17261
  return false;
16433
17262
  }
16434
- const toSpend = chunk3AQNLQPM_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
17263
+ const toSpend = chunkUHOHLN3C_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
16435
17264
  const toSign = craftBIP322ToSignP2TR(toSpend, pkScript, pubkey);
16436
17265
  const sighash = toSign.preimageWitnessV1(0, [pkScript], sighashType, [0n]);
16437
17266
  const rawSig = sig.length === 65 ? sig.subarray(0, 64) : sig;
@@ -16452,7 +17281,7 @@ function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
16452
17281
  }
16453
17282
  const sighashType = sigWithHash[sigWithHash.length - 1];
16454
17283
  const derSig = sigWithHash.subarray(0, sigWithHash.length - 1);
16455
- const toSpend = chunk3AQNLQPM_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
17284
+ const toSpend = chunkUHOHLN3C_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
16456
17285
  const toSign = craftBIP322ToSignSimple(toSpend, pkScript);
16457
17286
  const scriptCode = btcSigner.OutScript.encode({ type: "pkh", hash: addressHash });
16458
17287
  const sighash = toSign.preimageWitnessV0(0, scriptCode, sighashType, 0n);
@@ -16505,7 +17334,7 @@ function encodeCompactSize(n) {
16505
17334
  return buf;
16506
17335
  }
16507
17336
  function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
16508
- const tx = new chunk3AQNLQPM_cjs.Transaction({ version: 0 });
17337
+ const tx = new chunkUHOHLN3C_cjs.Transaction({ version: 0 });
16509
17338
  tx.addInput({
16510
17339
  txid: toSpend.id,
16511
17340
  index: 0,
@@ -16519,12 +17348,12 @@ function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
16519
17348
  });
16520
17349
  tx.addOutput({
16521
17350
  amount: 0n,
16522
- script: chunk3AQNLQPM_cjs.OP_RETURN_EMPTY_PKSCRIPT
17351
+ script: chunkUHOHLN3C_cjs.OP_RETURN_EMPTY_PKSCRIPT
16523
17352
  });
16524
17353
  return tx;
16525
17354
  }
16526
17355
  function craftBIP322ToSignSimple(toSpend, pkScript) {
16527
- const tx = new chunk3AQNLQPM_cjs.Transaction({ version: 0 });
17356
+ const tx = new chunkUHOHLN3C_cjs.Transaction({ version: 0 });
16528
17357
  tx.addInput({
16529
17358
  txid: toSpend.id,
16530
17359
  index: 0,
@@ -16536,11 +17365,89 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
16536
17365
  });
16537
17366
  tx.addOutput({
16538
17367
  amount: 0n,
16539
- script: chunk3AQNLQPM_cjs.OP_RETURN_EMPTY_PKSCRIPT
17368
+ script: chunkUHOHLN3C_cjs.OP_RETURN_EMPTY_PKSCRIPT
16540
17369
  });
16541
17370
  return tx;
16542
17371
  }
16543
- function chainTxTypeToChainedExit(t) {
17372
+ function finalizeVirtualTx(type, psbtBase64) {
17373
+ const tx = chunkUHOHLN3C_cjs.Transaction.fromPSBT(base.base64.decode(psbtBase64));
17374
+ if (type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
17375
+ const input = tx.getInput(0);
17376
+ if (!input) {
17377
+ throw new Error("Input not found");
17378
+ }
17379
+ const tapKeySig = input.tapKeySig;
17380
+ if (!tapKeySig) {
17381
+ throw new Error("Tap key sig not found");
17382
+ }
17383
+ tx.updateInput(0, {
17384
+ finalScriptWitness: [tapKeySig]
17385
+ });
17386
+ return tx;
17387
+ }
17388
+ tx.finalize();
17389
+ return tx;
17390
+ }
17391
+ var ExitPathError = class extends Error {
17392
+ constructor(reason, message) {
17393
+ super(message);
17394
+ this.reason = reason;
17395
+ this.name = "ExitPathError";
17396
+ }
17397
+ };
17398
+ function delayWeight(sequence) {
17399
+ const t = chunkHXDZ2QTW_cjs.sequenceToTimelock(sequence);
17400
+ return t.type === "blocks" ? Number(t.value) : Number(t.value) / 600;
17401
+ }
17402
+ async function resolveUnilateralPath(params) {
17403
+ const { vtxo, scriptHex, contractRepository } = params;
17404
+ const contract = contractRepository ? (await contractRepository.getContracts({ script: scriptHex }))[0] : void 0;
17405
+ if (contract) {
17406
+ const handler = chunk32YKLQPS_cjs.contractHandlers.get(contract.type);
17407
+ if (!handler) {
17408
+ throw new ExitPathError(
17409
+ "no-handler",
17410
+ `no contract handler registered for type '${contract.type}'`
17411
+ );
17412
+ }
17413
+ const script = handler.createScript(contract.params);
17414
+ const paths = handler.getAllSpendingPaths(script, contract, {
17415
+ collaborative: false,
17416
+ currentTime: params.currentTime,
17417
+ walletDescriptor: params.walletDescriptor,
17418
+ walletPubKey: params.walletPubKeyHex
17419
+ }).filter((p) => p.sequence !== void 0);
17420
+ if (paths.length === 0) {
17421
+ throw new ExitPathError(
17422
+ "no-unilateral-path",
17423
+ `no unilateral path for vtxo ${vtxo.txid}:${vtxo.vout} (type '${contract.type}')`
17424
+ );
17425
+ }
17426
+ paths.sort((a, b) => delayWeight(a.sequence) - delayWeight(b.sequence));
17427
+ return { selection: paths[0], label: `${contract.type}:unilateral` };
17428
+ }
17429
+ const decoded = chunkHXDZ2QTW_cjs.VtxoScript.decode(vtxo.tapTree);
17430
+ let best;
17431
+ for (const exit of decoded.exitPaths()) {
17432
+ const leaf = decoded.findLeaf(base.hex.encode(exit.script));
17433
+ if (!leaf) continue;
17434
+ const sequence = chunkHXDZ2QTW_cjs.timelockToSequence(exit.params.timelock);
17435
+ const weight = delayWeight(sequence);
17436
+ if (!best || weight < best.weight) {
17437
+ best = { selection: { leaf, sequence }, weight };
17438
+ }
17439
+ }
17440
+ if (!best) {
17441
+ throw new ExitPathError(
17442
+ "no-unilateral-path",
17443
+ `no exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`
17444
+ );
17445
+ }
17446
+ return { selection: best.selection, label: "default:exit" };
17447
+ }
17448
+
17449
+ // src/wallet/unroll.ts
17450
+ function chainTxTypeToChainedExit2(t) {
16544
17451
  switch (t) {
16545
17452
  case "INDEXER_CHAINED_TX_TYPE_COMMITMENT" /* COMMITMENT */:
16546
17453
  return 1 /* Commitment */;
@@ -16603,7 +17510,7 @@ exports.Unroll = void 0;
16603
17510
  txid: next.txid,
16604
17511
  psbt,
16605
17512
  expiresAt: null,
16606
- type: chainTxTypeToChainedExit(next.type)
17513
+ type: chainTxTypeToChainedExit2(next.type)
16607
17514
  };
16608
17515
  try {
16609
17516
  await repo.upsertVirtualTxs([cached]);
@@ -16649,22 +17556,7 @@ exports.Unroll = void 0;
16649
17556
  if (!virtualTxBase64) {
16650
17557
  throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
16651
17558
  }
16652
- const tx = chunk3AQNLQPM_cjs.Transaction.fromPSBT(base.base64.decode(virtualTxBase64));
16653
- if (nextTxToBroadcast.type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
16654
- const input = tx.getInput(0);
16655
- if (!input) {
16656
- throw new Error("Input not found");
16657
- }
16658
- const tapKeySig = input.tapKeySig;
16659
- if (!tapKeySig) {
16660
- throw new Error("Tap key sig not found");
16661
- }
16662
- tx.updateInput(0, {
16663
- finalScriptWitness: [tapKeySig]
16664
- });
16665
- } else {
16666
- tx.finalize();
16667
- }
17559
+ const tx = finalizeVirtualTx(nextTxToBroadcast.type, virtualTxBase64);
16668
17560
  const pkg = await this.bumper.bumpP2A(tx);
16669
17561
  return {
16670
17562
  type: 0 /* UNROLL */,
@@ -16718,20 +17610,21 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
16718
17610
  if (!txStatus.confirmed) {
16719
17611
  throw new Error(`tx ${vtxo.txid} is not confirmed`);
16720
17612
  }
16721
- const exit = availableExitPath(
16722
- { height: txStatus.blockHeight, time: txStatus.blockTime },
16723
- chainTip,
16724
- vtxo
16725
- );
16726
- if (!exit) {
17613
+ const resolved = await resolveUnilateralPath({
17614
+ vtxo,
17615
+ scriptHex: base.hex.encode(chunkHXDZ2QTW_cjs.VtxoScript.decode(vtxo.tapTree).pkScript),
17616
+ contractRepository: wallet.contractRepository,
17617
+ walletPubKeyHex: base.hex.encode(await wallet.identity.xOnlyPublicKey()),
17618
+ currentTime: Date.now()
17619
+ });
17620
+ const spendingLeaf = resolved.selection.leaf;
17621
+ const sequence = resolved.selection.sequence;
17622
+ const timelock = chunkHXDZ2QTW_cjs.sequenceToTimelock(sequence);
17623
+ const elapsed = timelock.type === "blocks" ? chainTip.height >= txStatus.blockHeight + Number(timelock.value) : chainTip.time >= txStatus.blockTime + Number(timelock.value);
17624
+ if (!elapsed) {
16727
17625
  throw new Error(`no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`);
16728
17626
  }
16729
- const spendingLeaf = chunkHXDZ2QTW_cjs.VtxoScript.decode(vtxo.tapTree).findLeaf(base.hex.encode(exit.script));
16730
- if (!spendingLeaf) {
16731
- throw new Error(`spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}`);
16732
- }
16733
17627
  totalAmount += BigInt(vtxo.value);
16734
- const sequence = chunkHXDZ2QTW_cjs.timelockToSequence(exit.params.timelock);
16735
17628
  inputs.push({
16736
17629
  txid: vtxo.txid,
16737
17630
  index: vtxo.vout,
@@ -16749,7 +17642,7 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
16749
17642
  btcSigner.TaprootControlBlock.encode(spendingLeaf[0]).length
16750
17643
  );
16751
17644
  }
16752
- const tx = new chunk3AQNLQPM_cjs.Transaction({ version: 2 });
17645
+ const tx = new chunkUHOHLN3C_cjs.Transaction({ version: 2 });
16753
17646
  for (const input of inputs) {
16754
17647
  tx.addInput(input);
16755
17648
  }
@@ -16795,21 +17688,711 @@ function doWait(onchainProvider, txid) {
16795
17688
  });
16796
17689
  };
16797
17690
  }
16798
- function availableExitPath(confirmedAt, current, vtxo) {
16799
- const exits = chunkHXDZ2QTW_cjs.VtxoScript.decode(vtxo.tapTree).exitPaths();
16800
- for (const exit of exits) {
16801
- if (exit.params.timelock.type === "blocks") {
16802
- if (current.height >= confirmedAt.height + Number(exit.params.timelock.value)) {
16803
- return exit;
17691
+ function extraWitnessSize(path) {
17692
+ return (path.extraWitness ?? []).reduce((sum, item) => sum + 1 + item.length, 0);
17693
+ }
17694
+ function sweepFeeFor(path, outputAddress, network, feeRate) {
17695
+ const [controlBlock, scriptWithVersion] = path.leaf;
17696
+ const estimator = TxWeightEstimator.create().addTapscriptInput(
17697
+ 64 + extraWitnessSize(path),
17698
+ scriptWithVersion.length,
17699
+ btcSigner.TaprootControlBlock.encode(controlBlock).length
17700
+ ).addOutputAddress(outputAddress, network);
17701
+ return Number(estimator.vsize().fee(BigInt(Math.ceil(feeRate))));
17702
+ }
17703
+ async function buildSignedSweep(params) {
17704
+ const { vtxo, path, outputAddress, feeRate, network, identity } = params;
17705
+ const fee = sweepFeeFor(path, outputAddress, network, feeRate);
17706
+ const sendAmount = BigInt(vtxo.value) - BigInt(fee);
17707
+ if (sendAmount < BigInt(DUST_AMOUNT)) {
17708
+ throw new Error(
17709
+ `uneconomic vtxo ${vtxo.txid}:${vtxo.vout}: value ${vtxo.value} - fee ${fee} < dust`
17710
+ );
17711
+ }
17712
+ const tx = new chunkUHOHLN3C_cjs.Transaction({ version: 2 });
17713
+ tx.addInput({
17714
+ txid: vtxo.txid,
17715
+ index: vtxo.vout,
17716
+ tapLeafScript: [path.leaf],
17717
+ sequence: path.sequence,
17718
+ witnessUtxo: { amount: BigInt(vtxo.value), script: vtxo.pkScript },
17719
+ sighashType: btcSigner.SigHash.DEFAULT
17720
+ });
17721
+ tx.addOutputAddress(outputAddress, sendAmount, network);
17722
+ const signed = await identity.sign(tx);
17723
+ if (!path.extraWitness || path.extraWitness.length === 0) {
17724
+ signed.finalize();
17725
+ return { tx: signed, fee };
17726
+ }
17727
+ const input = signed.getInput(0);
17728
+ const tapScriptSig = input.tapScriptSig;
17729
+ if (!tapScriptSig || tapScriptSig.length === 0) {
17730
+ throw new Error("exit path requires additional signers");
17731
+ }
17732
+ const [controlBlock] = path.leaf;
17733
+ signed.updateInput(0, {
17734
+ finalScriptWitness: [
17735
+ tapScriptSig[0][1],
17736
+ ...path.extraWitness,
17737
+ chunkHXDZ2QTW_cjs.scriptFromTapLeafScript(path.leaf),
17738
+ btcSigner.TaprootControlBlock.encode(controlBlock)
17739
+ ]
17740
+ });
17741
+ return { tx: signed, fee };
17742
+ }
17743
+
17744
+ // src/wallet/exit/estimate.ts
17745
+ var CHILD_OUTPUT_DUST = 546;
17746
+ function stepFundingAmount(stepFee) {
17747
+ return stepFee + CHILD_OUTPUT_DUST;
17748
+ }
17749
+ function resolveMode(opts) {
17750
+ return opts.mode ?? "funded";
17751
+ }
17752
+ function resolveNetworkName(opts) {
17753
+ if (opts.networkName) return opts.networkName;
17754
+ const { bech32 } = opts.wallet.network;
17755
+ if (bech32 === "bc") return "bitcoin";
17756
+ if (bech32 === "bcrt") return "regtest";
17757
+ return "testnet";
17758
+ }
17759
+ async function resolveFeeRate(opts) {
17760
+ if (opts.feeRate) return Math.ceil(opts.feeRate);
17761
+ const feeRate = await opts.wallet.onchainProvider.getFeeRate();
17762
+ if (!feeRate || feeRate < OnchainWallet.MIN_FEE_RATE) return OnchainWallet.MIN_FEE_RATE;
17763
+ return Math.ceil(feeRate);
17764
+ }
17765
+ async function selectExitVtxos(opts) {
17766
+ if (!opts.vtxos) return opts.wallet.getVtxos();
17767
+ const res = await opts.wallet.indexerProvider.getVtxos({ outpoints: opts.vtxos });
17768
+ const tapTrees = /* @__PURE__ */ new Map();
17769
+ const out = [];
17770
+ for (const vtxo of res.vtxos) {
17771
+ if (vtxo.isSpent) continue;
17772
+ let tapTree = tapTrees.get(vtxo.script);
17773
+ if (!tapTree) {
17774
+ const [contract] = await opts.wallet.contractRepository.getContracts({
17775
+ script: vtxo.script
17776
+ });
17777
+ if (!contract) {
17778
+ throw new Error(
17779
+ `no contract registered for vtxo script ${vtxo.script} \u2014 register the contract before exiting`
17780
+ );
16804
17781
  }
16805
- } else {
16806
- if (current.time >= confirmedAt.time + Number(exit.params.timelock.value)) {
16807
- return exit;
17782
+ const handler = chunk32YKLQPS_cjs.contractHandlers.get(contract.type);
17783
+ if (!handler) {
17784
+ throw new Error(`no contract handler registered for type '${contract.type}'`);
16808
17785
  }
17786
+ tapTree = handler.createScript(contract.params).encode();
17787
+ tapTrees.set(vtxo.script, tapTree);
16809
17788
  }
17789
+ out.push({ txid: vtxo.txid, vout: vtxo.vout, value: vtxo.value, tapTree });
16810
17790
  }
16811
- return void 0;
17791
+ return out;
17792
+ }
17793
+ async function computeExitLayout(opts, feeRate) {
17794
+ const { wallet, onchainWallet } = opts;
17795
+ const vtxos = await selectExitVtxos(opts);
17796
+ if (vtxos.length === 0) throw new Error("no vtxos to exit");
17797
+ const resolver = createExitChainResolver({
17798
+ indexer: wallet.indexerProvider,
17799
+ repository: wallet.virtualTxRepository,
17800
+ extraSources: wallet.exitDataCapture?.sources
17801
+ });
17802
+ const dag = await buildExitDag({
17803
+ vtxos,
17804
+ chain: resolver,
17805
+ onchain: wallet.onchainProvider
17806
+ });
17807
+ const pendingNodes = dag.filter((n) => !n.confirmed);
17808
+ const psbts = /* @__PURE__ */ new Map();
17809
+ if (pendingNodes.length > 0) {
17810
+ const txs = await resolver.getVirtualTxs(pendingNodes.map((n) => n.txid));
17811
+ for (const psbt of txs) {
17812
+ psbts.set(chunkUHOHLN3C_cjs.Transaction.fromPSBT(base.base64.decode(psbt)).id, psbt);
17813
+ }
17814
+ }
17815
+ const childVsize = Number(
17816
+ TxWeightEstimator.create().addP2AInput().addKeySpendInput(true).addOutputAddress(onchainWallet.address, wallet.network).vsize().value
17817
+ );
17818
+ const rawSteps = pendingNodes.map((node) => {
17819
+ const psbt = psbts.get(node.txid);
17820
+ if (!psbt) {
17821
+ throw new Error(`indexer did not return virtual tx ${node.txid}`);
17822
+ }
17823
+ const parent = finalizeVirtualTx(node.type, psbt);
17824
+ const stepFee = Math.ceil(feeRate * (parent.vsize + childVsize));
17825
+ return { node, parent, stepFee, funding: stepFundingAmount(stepFee) };
17826
+ });
17827
+ const parentInputTxids = (tx) => {
17828
+ const ids = [];
17829
+ for (let i = 0; i < tx.inputsLength; i++) {
17830
+ const txid = tx.getInput(i).txid;
17831
+ if (txid) ids.push(base.hex.encode(txid));
17832
+ }
17833
+ return ids;
17834
+ };
17835
+ const steps = topoSortByDeps(
17836
+ rawSteps,
17837
+ (s) => s.parent.id,
17838
+ (s) => parentInputTxids(s.parent)
17839
+ );
17840
+ const walletPubKeyHex = base.hex.encode(await wallet.identity.xOnlyPublicKey());
17841
+ const infos = [];
17842
+ const sweeps = [];
17843
+ for (const vtxo of vtxos) {
17844
+ const outpoint = `${vtxo.txid}:${vtxo.vout}`;
17845
+ try {
17846
+ const resolved = await resolveUnilateralPath({
17847
+ vtxo,
17848
+ scriptHex: base.hex.encode(chunkHXDZ2QTW_cjs.VtxoScript.decode(vtxo.tapTree).pkScript),
17849
+ contractRepository: wallet.contractRepository,
17850
+ walletPubKeyHex,
17851
+ currentTime: Date.now()
17852
+ });
17853
+ const sweepFee = sweepFeeFor(
17854
+ resolved.selection,
17855
+ opts.sweepAddress,
17856
+ wallet.network,
17857
+ feeRate
17858
+ );
17859
+ if (vtxo.value - sweepFee < CHILD_OUTPUT_DUST) {
17860
+ infos.push({
17861
+ outpoint,
17862
+ value: vtxo.value,
17863
+ skipped: `uneconomic: value ${vtxo.value} <= sweep fee + dust`
17864
+ });
17865
+ continue;
17866
+ }
17867
+ const t = chunkHXDZ2QTW_cjs.sequenceToTimelock(resolved.selection.sequence);
17868
+ const delay = { type: t.type, value: Number(t.value) };
17869
+ infos.push({ outpoint, value: vtxo.value, sweepFee, path: resolved.label, delay });
17870
+ sweeps.push({ vtxo, resolved, sweepFee, delay });
17871
+ } catch (e) {
17872
+ if (e instanceof ExitPathError) {
17873
+ infos.push({ outpoint, value: vtxo.value, skipped: e.message });
17874
+ continue;
17875
+ }
17876
+ throw e;
17877
+ }
17878
+ }
17879
+ const coins = (await onchainWallet.getCoins()).filter((c) => c.status.confirmed);
17880
+ const balance = coins.reduce((sum, c) => sum + c.value, 0);
17881
+ const fundingTotal = steps.reduce((sum, s) => sum + s.funding, 0);
17882
+ const splitterFeeFor = (inputCount) => {
17883
+ if (steps.length === 0) return 0;
17884
+ const est = TxWeightEstimator.create();
17885
+ for (let i = 0; i < inputCount; i++) est.addKeySpendInput(true);
17886
+ for (let i = 0; i < steps.length + 1; i++) {
17887
+ est.addOutputAddress(onchainWallet.address, wallet.network);
17888
+ }
17889
+ return Number(est.vsize().fee(BigInt(feeRate)));
17890
+ };
17891
+ const graph = resolveMode(opts) === "graph";
17892
+ let splitterFee = 0;
17893
+ if (!graph) {
17894
+ splitterFee = splitterFeeFor(Math.max(1, coins.length));
17895
+ if (steps.length > 0 && balance < fundingTotal + splitterFee) {
17896
+ splitterFee = splitterFeeFor(coins.length + 1);
17897
+ }
17898
+ }
17899
+ const stepFees = steps.reduce((s, x) => s + x.stepFee, 0);
17900
+ const sweepFees = sweeps.reduce((s, x) => s + x.sweepFee, 0);
17901
+ const totals = {
17902
+ // graph: each unroll step is parent + a live-built child (2 txs), no
17903
+ // splitter. funded: + 1 splitter tx and pre-signed children.
17904
+ txCount: (graph ? 0 : steps.length > 0 ? 1 : 0) + steps.length * 2 + sweeps.length,
17905
+ totalFeeSats: splitterFee + stepFees + sweepFees,
17906
+ // graph funding is what the executor sends to its own fee address:
17907
+ // just the CPFP fees (change recycles); funded also locks the
17908
+ // per-child dust into the splitter.
17909
+ fundingRequiredSats: graph ? stepFees : splitterFee + fundingTotal,
17910
+ recoveredSats: sweeps.reduce((s, x) => s + (x.vtxo.value - x.sweepFee), 0)
17911
+ };
17912
+ const expiries = pendingNodes.map((n) => n.expiresAt).filter((x) => x !== void 0);
17913
+ const validUntil = expiries.length > 0 ? Math.min(...expiries) : void 0;
17914
+ return {
17915
+ vtxos,
17916
+ dag,
17917
+ steps,
17918
+ sweeps,
17919
+ infos,
17920
+ totals,
17921
+ splitterFee,
17922
+ validUntil,
17923
+ balance,
17924
+ coins
17925
+ };
16812
17926
  }
17927
+ async function estimate(opts) {
17928
+ const feeRate = await resolveFeeRate(opts);
17929
+ const layout = await computeExitLayout(opts, feeRate);
17930
+ if (resolveMode(opts) === "graph") {
17931
+ return {
17932
+ feeRate,
17933
+ fundingAddress: "",
17934
+ currentBalanceSats: 0,
17935
+ shortfallSats: layout.totals.fundingRequiredSats,
17936
+ validUntil: layout.validUntil,
17937
+ totals: layout.totals,
17938
+ vtxos: layout.infos
17939
+ };
17940
+ }
17941
+ return {
17942
+ feeRate,
17943
+ fundingAddress: opts.onchainWallet.address,
17944
+ currentBalanceSats: layout.balance,
17945
+ shortfallSats: Math.max(0, layout.totals.fundingRequiredSats - layout.balance),
17946
+ validUntil: layout.validUntil,
17947
+ totals: layout.totals,
17948
+ vtxos: layout.infos
17949
+ };
17950
+ }
17951
+
17952
+ // src/wallet/exit/executor.ts
17953
+ var Executor = class {
17954
+ constructor(pkg, provider, opts) {
17955
+ this.pkg = pkg;
17956
+ this.provider = provider;
17957
+ this.pollIntervalMs = opts?.pollIntervalMs ?? 5e3;
17958
+ this.feeWallet = opts?.feeWallet;
17959
+ }
17960
+ pollIntervalMs;
17961
+ feeWallet;
17962
+ sleep() {
17963
+ return new Promise((r) => setTimeout(r, this.pollIntervalMs));
17964
+ }
17965
+ async status(txid) {
17966
+ try {
17967
+ return await this.provider.getTxStatus(txid);
17968
+ } catch {
17969
+ return void 0;
17970
+ }
17971
+ }
17972
+ async waitConfirmed(txid) {
17973
+ for (; ; ) {
17974
+ const s = await this.status(txid);
17975
+ if (s?.confirmed) return s;
17976
+ await this.sleep();
17977
+ }
17978
+ }
17979
+ async *[Symbol.asyncIterator]() {
17980
+ const dead = /* @__PURE__ */ new Set();
17981
+ if (this.pkg.validUntil && Date.now() / 1e3 > this.pkg.validUntil) {
17982
+ yield {
17983
+ stepIndex: -1,
17984
+ kind: "broadcast",
17985
+ status: "warning",
17986
+ reason: `package validUntil ${this.pkg.validUntil} has passed; the operator may have swept \u2014 attempting anyway`
17987
+ };
17988
+ }
17989
+ const sweeps = [];
17990
+ for (let i = 0; i < this.pkg.steps.length; i++) {
17991
+ const step = this.pkg.steps[i];
17992
+ if (step.kind === "sweep") {
17993
+ sweeps.push({ index: i, step });
17994
+ continue;
17995
+ }
17996
+ const forVtxos = step.kind === "package" || step.kind === "bump" ? step.forVtxos : void 0;
17997
+ if (forVtxos && forVtxos.every((v) => dead.has(v))) {
17998
+ yield {
17999
+ stepIndex: i,
18000
+ kind: step.kind,
18001
+ status: "skipped",
18002
+ forVtxos,
18003
+ reason: "branch failed earlier"
18004
+ };
18005
+ continue;
18006
+ }
18007
+ const anchorTxid = step.kind === "package" || step.kind === "bump" ? step.parentTxid : step.txid;
18008
+ const existing = await this.status(anchorTxid);
18009
+ if (existing?.confirmed) {
18010
+ yield {
18011
+ stepIndex: i,
18012
+ kind: step.kind,
18013
+ status: "skipped",
18014
+ txid: anchorTxid,
18015
+ forVtxos
18016
+ };
18017
+ continue;
18018
+ }
18019
+ if (!existing) {
18020
+ try {
18021
+ if (step.kind === "package") {
18022
+ await this.provider.broadcastTransaction(step.parentHex, step.childHex);
18023
+ } else if (step.kind === "bump") {
18024
+ if (!this.feeWallet) {
18025
+ throw new Error(
18026
+ "graph package requires a fee wallet (opts.feeWallet) to fund CPFP bumps"
18027
+ );
18028
+ }
18029
+ const [parentHex, childHex] = await this.feeWallet.bumpAnchor(
18030
+ step.parentHex,
18031
+ this.pkg.feeRate
18032
+ );
18033
+ await this.provider.broadcastTransaction(parentHex, childHex);
18034
+ } else {
18035
+ await this.provider.broadcastTransaction(step.hex);
18036
+ }
18037
+ yield {
18038
+ stepIndex: i,
18039
+ kind: step.kind,
18040
+ status: "broadcast",
18041
+ txid: anchorTxid,
18042
+ forVtxos
18043
+ };
18044
+ } catch (e) {
18045
+ const reason = e instanceof Error ? e.message : String(e);
18046
+ if (step.kind === "broadcast") {
18047
+ for (const s of this.pkg.steps) {
18048
+ if (s.kind === "package") s.forVtxos.forEach((v) => dead.add(v));
18049
+ }
18050
+ } else if (step.kind === "package" || step.kind === "bump") {
18051
+ step.forVtxos.forEach((v) => dead.add(v));
18052
+ }
18053
+ yield {
18054
+ stepIndex: i,
18055
+ kind: step.kind,
18056
+ status: "failed",
18057
+ txid: anchorTxid,
18058
+ forVtxos,
18059
+ reason
18060
+ };
18061
+ continue;
18062
+ }
18063
+ }
18064
+ await this.waitConfirmed(anchorTxid);
18065
+ yield {
18066
+ stepIndex: i,
18067
+ kind: step.kind,
18068
+ status: "confirmed",
18069
+ txid: anchorTxid,
18070
+ forVtxos
18071
+ };
18072
+ }
18073
+ for (const { index, step } of sweeps) {
18074
+ if (dead.has(step.vtxo)) {
18075
+ yield {
18076
+ stepIndex: index,
18077
+ kind: "sweep",
18078
+ status: "failed",
18079
+ txid: step.txid,
18080
+ forVtxos: [step.vtxo],
18081
+ reason: "branch failed earlier"
18082
+ };
18083
+ }
18084
+ }
18085
+ const pending = sweeps.filter(({ step }) => !dead.has(step.vtxo));
18086
+ const waitingAnnounced = /* @__PURE__ */ new Set();
18087
+ const done = /* @__PURE__ */ new Set();
18088
+ while (done.size < pending.length) {
18089
+ for (const { index, step } of pending) {
18090
+ if (done.has(index)) continue;
18091
+ const swept = await this.status(step.txid);
18092
+ if (swept?.confirmed) {
18093
+ done.add(index);
18094
+ yield {
18095
+ stepIndex: index,
18096
+ kind: "sweep",
18097
+ status: "confirmed",
18098
+ txid: step.txid,
18099
+ forVtxos: [step.vtxo]
18100
+ };
18101
+ continue;
18102
+ }
18103
+ if (swept) continue;
18104
+ const dep = await this.status(step.dependsOnTxid);
18105
+ if (!dep?.confirmed) continue;
18106
+ const tip = await this.provider.getChainTip();
18107
+ const mature = step.delay.type === "blocks" ? tip.height >= (dep.blockHeight ?? 0) + step.delay.value : tip.time >= (dep.blockTime ?? 0) + step.delay.value;
18108
+ if (!mature) {
18109
+ if (!waitingAnnounced.has(index)) {
18110
+ waitingAnnounced.add(index);
18111
+ yield {
18112
+ stepIndex: index,
18113
+ kind: "sweep",
18114
+ status: "waiting_csv",
18115
+ txid: step.txid,
18116
+ forVtxos: [step.vtxo],
18117
+ ...step.delay.type === "blocks" ? { maturesAtHeight: (dep.blockHeight ?? 0) + step.delay.value } : { maturesAtTime: (dep.blockTime ?? 0) + step.delay.value }
18118
+ };
18119
+ }
18120
+ continue;
18121
+ }
18122
+ try {
18123
+ await this.provider.broadcastTransaction(step.hex);
18124
+ yield {
18125
+ stepIndex: index,
18126
+ kind: "sweep",
18127
+ status: "broadcast",
18128
+ txid: step.txid,
18129
+ forVtxos: [step.vtxo]
18130
+ };
18131
+ } catch (e) {
18132
+ const reason = e instanceof Error ? e.message : String(e);
18133
+ if (/non-?bip68|non-?final|premature/i.test(reason)) {
18134
+ continue;
18135
+ }
18136
+ done.add(index);
18137
+ yield {
18138
+ stepIndex: index,
18139
+ kind: "sweep",
18140
+ status: "failed",
18141
+ txid: step.txid,
18142
+ forVtxos: [step.vtxo],
18143
+ reason
18144
+ };
18145
+ }
18146
+ }
18147
+ if (done.size < pending.length) await this.sleep();
18148
+ }
18149
+ }
18150
+ };
18151
+ async function prepare(opts) {
18152
+ const { wallet, onchainWallet } = opts;
18153
+ const feeRate = await resolveFeeRate(opts);
18154
+ const mode = resolveMode(opts);
18155
+ if (mode === "funded") {
18156
+ const walletKey = await wallet.identity.xOnlyPublicKey();
18157
+ if (base.hex.encode(onchainWallet.onchainP2TR.tapInternalKey) !== base.hex.encode(walletKey)) {
18158
+ throw new Error("onchainWallet must share the wallet identity");
18159
+ }
18160
+ }
18161
+ const layout = await computeExitLayout(opts, feeRate);
18162
+ const sweepSteps = [];
18163
+ const activeOutpoints = /* @__PURE__ */ new Set();
18164
+ for (const sweep of layout.sweeps) {
18165
+ const outpoint = `${sweep.vtxo.txid}:${sweep.vtxo.vout}`;
18166
+ try {
18167
+ const { tx } = await buildSignedSweep({
18168
+ vtxo: {
18169
+ txid: sweep.vtxo.txid,
18170
+ vout: sweep.vtxo.vout,
18171
+ value: sweep.vtxo.value,
18172
+ pkScript: chunkHXDZ2QTW_cjs.VtxoScript.decode(sweep.vtxo.tapTree).pkScript
18173
+ },
18174
+ path: sweep.resolved.selection,
18175
+ outputAddress: opts.sweepAddress,
18176
+ feeRate,
18177
+ network: wallet.network,
18178
+ identity: wallet.identity
18179
+ });
18180
+ activeOutpoints.add(outpoint);
18181
+ sweepSteps.push({
18182
+ kind: "sweep",
18183
+ vtxo: outpoint,
18184
+ txid: tx.id,
18185
+ hex: tx.hex,
18186
+ dependsOnTxid: sweep.vtxo.txid,
18187
+ delay: sweep.delay
18188
+ });
18189
+ } catch (e) {
18190
+ const info = layout.infos.find((i) => i.outpoint === outpoint);
18191
+ if (info) {
18192
+ info.skipped = e instanceof Error ? e.message : String(e);
18193
+ delete info.sweepFee;
18194
+ delete info.path;
18195
+ delete info.delay;
18196
+ }
18197
+ }
18198
+ }
18199
+ const steps = layout.steps.filter((s) => s.node.forVtxos.some((v) => activeOutpoints.has(v)));
18200
+ if (steps.length === 0 && sweepSteps.length === 0) {
18201
+ throw new Error("no exitable vtxos (all skipped)");
18202
+ }
18203
+ if (mode === "graph") {
18204
+ const bumpSteps = steps.map((step) => ({
18205
+ kind: "bump",
18206
+ parentTxid: step.parent.id,
18207
+ parentHex: step.parent.hex,
18208
+ forVtxos: step.node.forVtxos.filter((v) => activeOutpoints.has(v))
18209
+ }));
18210
+ const stepFees2 = steps.reduce((s, x) => s + x.stepFee, 0);
18211
+ const activeInfos2 = layout.infos.filter((i) => !i.skipped);
18212
+ const sweepFees2 = activeInfos2.reduce((s, i) => s + (i.sweepFee ?? 0), 0);
18213
+ const recovered2 = activeInfos2.reduce((s, i) => s + (i.value ?? 0) - (i.sweepFee ?? 0), 0);
18214
+ return {
18215
+ version: 1,
18216
+ mode: "graph",
18217
+ network: resolveNetworkName(opts),
18218
+ createdAt: Math.floor(Date.now() / 1e3),
18219
+ validUntil: layout.validUntil,
18220
+ feeRate,
18221
+ sweepAddress: opts.sweepAddress,
18222
+ totals: {
18223
+ txCount: bumpSteps.length * 2 + sweepSteps.length,
18224
+ totalFeeSats: stepFees2 + sweepFees2,
18225
+ fundingRequiredSats: stepFees2,
18226
+ recoveredSats: recovered2
18227
+ },
18228
+ vtxos: layout.infos,
18229
+ steps: [...bumpSteps, ...sweepSteps]
18230
+ };
18231
+ }
18232
+ const packageSteps = [];
18233
+ let broadcastStep;
18234
+ let splitterFee = 0;
18235
+ if (steps.length > 0) {
18236
+ const fundingTotal = steps.reduce((sum, s) => sum + s.funding, 0);
18237
+ let selected = { inputs: [], changeAmount: 0n };
18238
+ for (let i = 0; i < 10; i++) {
18239
+ const target = fundingTotal + splitterFee;
18240
+ try {
18241
+ selected = selectCoins(layout.coins, target);
18242
+ } catch {
18243
+ throw new Error(
18244
+ `insufficient confirmed onchain funds: need ${target} sats, have ${layout.balance} (deposit the shortfall to ${onchainWallet.address})`
18245
+ );
18246
+ }
18247
+ const est = TxWeightEstimator.create();
18248
+ for (const _ of selected.inputs) est.addKeySpendInput(true);
18249
+ for (const _ of steps) est.addOutputAddress(onchainWallet.address, wallet.network);
18250
+ if (selected.changeAmount >= BigInt(DUST_AMOUNT)) {
18251
+ est.addOutputAddress(onchainWallet.address, wallet.network);
18252
+ }
18253
+ const newFee = Number(est.vsize().fee(BigInt(feeRate)));
18254
+ if (newFee <= splitterFee) break;
18255
+ splitterFee = newFee;
18256
+ }
18257
+ const splitter = new chunkUHOHLN3C_cjs.Transaction({ version: 2 });
18258
+ for (const coin of selected.inputs) {
18259
+ splitter.addInput({
18260
+ txid: coin.txid,
18261
+ index: coin.vout,
18262
+ witnessUtxo: {
18263
+ script: onchainWallet.onchainP2TR.script,
18264
+ amount: BigInt(coin.value)
18265
+ },
18266
+ tapInternalKey: onchainWallet.onchainP2TR.tapInternalKey
18267
+ });
18268
+ }
18269
+ for (const step of steps) {
18270
+ splitter.addOutputAddress(onchainWallet.address, BigInt(step.funding), wallet.network);
18271
+ }
18272
+ const inputSum = selected.inputs.reduce((s, c) => s + c.value, 0);
18273
+ const change = BigInt(inputSum - fundingTotal - splitterFee);
18274
+ if (change < 0n) {
18275
+ throw new Error(
18276
+ `insufficient confirmed onchain funds for the splitter fee: need ${fundingTotal + splitterFee} sats, have ${inputSum} (deposit the shortfall to ${onchainWallet.address})`
18277
+ );
18278
+ }
18279
+ if (change >= BigInt(DUST_AMOUNT)) {
18280
+ splitter.addOutputAddress(onchainWallet.address, change, wallet.network);
18281
+ } else if (change > 0n) {
18282
+ splitterFee += Number(change);
18283
+ }
18284
+ const signedSplitter = await wallet.identity.sign(splitter);
18285
+ signedSplitter.finalize();
18286
+ for (let k = 0; k < steps.length; k++) {
18287
+ const step = steps[k];
18288
+ const { child } = buildAnchorChild({
18289
+ parent: step.parent,
18290
+ feeRate,
18291
+ fundingCoins: [{ txid: signedSplitter.id, vout: k, value: step.funding }],
18292
+ changeAddress: onchainWallet.address,
18293
+ changeScript: onchainWallet.onchainP2TR.script,
18294
+ tapInternalKey: onchainWallet.onchainP2TR.tapInternalKey,
18295
+ network: wallet.network
18296
+ });
18297
+ const signedChild = await wallet.identity.sign(child);
18298
+ for (let i = 1; i < signedChild.inputsLength; i++) {
18299
+ signedChild.finalizeIdx(i);
18300
+ }
18301
+ packageSteps.push({
18302
+ kind: "package",
18303
+ parentTxid: step.parent.id,
18304
+ parentHex: step.parent.hex,
18305
+ childTxid: signedChild.id,
18306
+ childHex: signedChild.hex,
18307
+ forVtxos: step.node.forVtxos.filter((v) => activeOutpoints.has(v))
18308
+ });
18309
+ }
18310
+ broadcastStep = { kind: "broadcast", txid: signedSplitter.id, hex: signedSplitter.hex };
18311
+ await onchainWallet.provider.broadcastTransaction(signedSplitter.hex);
18312
+ }
18313
+ const stepFees = steps.reduce((s, x) => s + x.stepFee, 0);
18314
+ const activeInfos = layout.infos.filter((i) => !i.skipped);
18315
+ const sweepFees = activeInfos.reduce((s, i) => s + (i.sweepFee ?? 0), 0);
18316
+ const recovered = activeInfos.reduce((s, i) => s + (i.value ?? 0) - (i.sweepFee ?? 0), 0);
18317
+ const fundingRequired = splitterFee + steps.reduce((s, x) => s + x.funding, 0);
18318
+ return {
18319
+ version: 1,
18320
+ mode: "funded",
18321
+ network: resolveNetworkName(opts),
18322
+ createdAt: Math.floor(Date.now() / 1e3),
18323
+ validUntil: layout.validUntil,
18324
+ feeRate,
18325
+ sweepAddress: opts.sweepAddress,
18326
+ totals: {
18327
+ txCount: (broadcastStep ? 1 : 0) + packageSteps.length * 2 + sweepSteps.length,
18328
+ totalFeeSats: splitterFee + stepFees + sweepFees,
18329
+ fundingRequiredSats: fundingRequired,
18330
+ recoveredSats: recovered
18331
+ },
18332
+ vtxos: layout.infos,
18333
+ steps: [...broadcastStep ? [broadcastStep] : [], ...packageSteps, ...sweepSteps]
18334
+ };
18335
+ }
18336
+
18337
+ // src/wallet/exit/types.ts
18338
+ function serializeExitPackage(pkg) {
18339
+ return JSON.stringify(pkg);
18340
+ }
18341
+ function deserializeExitPackage(json) {
18342
+ const data = JSON.parse(json);
18343
+ if (data?.version !== 1) {
18344
+ throw new Error(`unsupported exit package version: ${data?.version}`);
18345
+ }
18346
+ if (typeof data.network !== "string" || typeof data.sweepAddress !== "string") {
18347
+ throw new Error("invalid exit package: missing network or sweepAddress");
18348
+ }
18349
+ if (typeof data.feeRate !== "number" || typeof data.createdAt !== "number") {
18350
+ throw new Error("invalid exit package: missing feeRate or createdAt");
18351
+ }
18352
+ if (!Array.isArray(data.steps) || !Array.isArray(data.vtxos)) {
18353
+ throw new Error("invalid exit package: steps and vtxos must be arrays");
18354
+ }
18355
+ if (!isValidTotals(data.totals)) {
18356
+ throw new Error("invalid exit package: malformed totals");
18357
+ }
18358
+ for (const step of data.steps) {
18359
+ if (!isValidStep(step)) {
18360
+ throw new Error(`invalid step: ${JSON.stringify(step)}`);
18361
+ }
18362
+ }
18363
+ return data;
18364
+ }
18365
+ function isValidDelay(d) {
18366
+ const delay = d;
18367
+ return !!delay && (delay.type === "blocks" || delay.type === "seconds") && typeof delay.value === "number";
18368
+ }
18369
+ function isValidTotals(t) {
18370
+ const totals = t;
18371
+ return !!totals && typeof totals === "object" && typeof totals.txCount === "number" && typeof totals.totalFeeSats === "number" && typeof totals.fundingRequiredSats === "number" && typeof totals.recoveredSats === "number";
18372
+ }
18373
+ function isValidStep(step) {
18374
+ const s = step;
18375
+ if (!s || typeof s !== "object") return false;
18376
+ switch (s.kind) {
18377
+ case "broadcast":
18378
+ return typeof s.txid === "string" && typeof s.hex === "string";
18379
+ case "package":
18380
+ return typeof s.parentTxid === "string" && typeof s.parentHex === "string" && typeof s.childTxid === "string" && typeof s.childHex === "string" && Array.isArray(s.forVtxos);
18381
+ case "bump":
18382
+ return typeof s.parentTxid === "string" && typeof s.parentHex === "string" && Array.isArray(s.forVtxos);
18383
+ case "sweep":
18384
+ return typeof s.vtxo === "string" && typeof s.txid === "string" && typeof s.hex === "string" && typeof s.dependsOnTxid === "string" && isValidDelay(s.delay);
18385
+ default:
18386
+ return false;
18387
+ }
18388
+ }
18389
+
18390
+ // src/wallet/exit/index.ts
18391
+ var UnilateralExit = {
18392
+ estimate,
18393
+ prepare,
18394
+ Executor
18395
+ };
16813
18396
  var ARKCONTRACT_PREFIX = "arkcontract";
16814
18397
  function encodeArkContract(contract) {
16815
18398
  const params = new URLSearchParams();
@@ -16926,6 +18509,7 @@ exports.SingleKey = SingleKey;
16926
18509
  exports.TxTree = TxTree;
16927
18510
  exports.TxType = TxType;
16928
18511
  exports.TxWeightEstimator = TxWeightEstimator;
18512
+ exports.UnilateralExit = UnilateralExit;
16929
18513
  exports.VtxoManager = VtxoManager;
16930
18514
  exports.Wallet = Wallet2;
16931
18515
  exports.WalletMessageHandler = WalletMessageHandler;
@@ -16943,8 +18527,10 @@ exports.combineTapscriptSigs = combineTapscriptSigs;
16943
18527
  exports.contractFromArkContract = contractFromArkContract;
16944
18528
  exports.contractFromArkContractWithAddress = contractFromArkContractWithAddress;
16945
18529
  exports.createDefaultActivityRegistry = createDefaultActivityRegistry;
18530
+ exports.createExitChainResolver = createExitChainResolver;
16946
18531
  exports.decodeArkContract = decodeArkContract;
16947
18532
  exports.deserializeAssets = deserializeAssets;
18533
+ exports.deserializeExitPackage = deserializeExitPackage;
16948
18534
  exports.deserializeUtxo = deserializeUtxo;
16949
18535
  exports.deserializeVtxo = deserializeVtxo;
16950
18536
  exports.encodeArkContract = encodeArkContract;
@@ -16960,6 +18546,7 @@ exports.isCooperativelyMigratable = isCooperativelyMigratable;
16960
18546
  exports.isDiscoverable = isDiscoverable;
16961
18547
  exports.isExpired = isExpired;
16962
18548
  exports.isRecoverable = isRecoverable;
18549
+ exports.isRetryableProviderError = isRetryableProviderError;
16963
18550
  exports.isSpendable = isSpendable;
16964
18551
  exports.isSubdust = isSubdust;
16965
18552
  exports.isTerminalIntentState = isTerminalIntentState;
@@ -16974,6 +18561,7 @@ exports.rollbackMigration = rollbackMigration;
16974
18561
  exports.saveVtxosForContract = saveVtxosForContract;
16975
18562
  exports.scriptFromArkAddress = scriptFromArkAddress;
16976
18563
  exports.serializeAssets = serializeAssets;
18564
+ exports.serializeExitPackage = serializeExitPackage;
16977
18565
  exports.serializeUtxo = serializeUtxo;
16978
18566
  exports.serializeVtxo = serializeVtxo;
16979
18567
  exports.setupServiceWorker = setupServiceWorker;
@@ -16984,5 +18572,5 @@ exports.validateVtxoTxGraph = validateVtxoTxGraph;
16984
18572
  exports.verifyTapscriptSignatures = verifyTapscriptSignatures;
16985
18573
  exports.waitForIncomingFunds = waitForIncomingFunds;
16986
18574
  exports.warnAndFilterVtxosForScript = warnAndFilterVtxosForScript;
16987
- //# sourceMappingURL=chunk-QGNTDGMN.cjs.map
16988
- //# sourceMappingURL=chunk-QGNTDGMN.cjs.map
18575
+ //# sourceMappingURL=chunk-SRCWBFSZ.cjs.map
18576
+ //# sourceMappingURL=chunk-SRCWBFSZ.cjs.map