@arkade-os/sdk 0.4.34 → 0.4.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/adapters/expo.cjs +4 -4
  2. package/dist/adapters/expo.d.cts +2 -2
  3. package/dist/adapters/expo.d.ts +2 -2
  4. package/dist/adapters/expo.js +2 -2
  5. package/dist/adapters/indexedDB.cjs +3 -3
  6. package/dist/adapters/indexedDB.js +2 -2
  7. package/dist/{ark-Dsv5Jq4E.d.cts → ark-D6sau_6-.d.cts} +458 -3
  8. package/dist/{ark-Dsv5Jq4E.d.ts → ark-D6sau_6-.d.ts} +458 -3
  9. package/dist/{asyncStorageTaskQueue-D92ch8yI.d.cts → asyncStorageTaskQueue-CpC027t_.d.cts} +2 -2
  10. package/dist/{asyncStorageTaskQueue-BH-zuth5.d.ts → asyncStorageTaskQueue-GT8fmPUG.d.ts} +2 -2
  11. package/dist/{chunk-FXFBPXV3.js → chunk-2JJKX2RK.js} +139 -41
  12. package/dist/chunk-2JJKX2RK.js.map +1 -0
  13. package/dist/{chunk-CCLNFHJ5.cjs → chunk-2XE5BSIY.cjs} +16 -10
  14. package/dist/chunk-2XE5BSIY.cjs.map +1 -0
  15. package/dist/{chunk-ZS3OZHC7.cjs → chunk-A5PY4NBP.cjs} +7 -7
  16. package/dist/{chunk-ZS3OZHC7.cjs.map → chunk-A5PY4NBP.cjs.map} +1 -1
  17. package/dist/{chunk-FSAXPBGP.cjs → chunk-C6OODRWD.cjs} +143 -40
  18. package/dist/chunk-C6OODRWD.cjs.map +1 -0
  19. package/dist/{chunk-HFXEUW55.js → chunk-HBPKIIMN.js} +1472 -202
  20. package/dist/chunk-HBPKIIMN.js.map +1 -0
  21. package/dist/{chunk-5WDBHWX3.js → chunk-KZV3FJJR.js} +10 -4
  22. package/dist/chunk-KZV3FJJR.js.map +1 -0
  23. package/dist/{chunk-XCHBQVMK.cjs → chunk-TUSGEWOX.cjs} +1540 -265
  24. package/dist/chunk-TUSGEWOX.cjs.map +1 -0
  25. package/dist/{chunk-VVGD3JIP.js → chunk-Z2VRVZW4.js} +3 -3
  26. package/dist/{chunk-VVGD3JIP.js.map → chunk-Z2VRVZW4.js.map} +1 -1
  27. package/dist/contracts/handlers/index.d.cts +3 -3
  28. package/dist/contracts/handlers/index.d.ts +3 -3
  29. package/dist/{delegate-BaS5SCIW.d.cts → delegate-C-L6gSZx.d.cts} +1 -1
  30. package/dist/{delegate-Baz_hb83.d.ts → delegate-De5__fpZ.d.ts} +1 -1
  31. package/dist/{index-FwXZveaX.d.ts → index-BETdjE_o.d.ts} +2 -2
  32. package/dist/{index-lNZ6qaO3.d.cts → index-jwQfHP6D.d.cts} +2 -2
  33. package/dist/index.cjs +129 -105
  34. package/dist/index.d.cts +69 -9
  35. package/dist/index.d.ts +69 -9
  36. package/dist/index.js +2 -2
  37. package/dist/repositories/realm/index.cjs +12 -12
  38. package/dist/repositories/realm/index.cjs.map +1 -1
  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/realm/index.js.map +1 -1
  43. package/dist/repositories/sqlite/index.cjs +11 -11
  44. package/dist/repositories/sqlite/index.d.cts +1 -1
  45. package/dist/repositories/sqlite/index.d.ts +1 -1
  46. package/dist/repositories/sqlite/index.js +2 -2
  47. package/dist/{taskRunner-vFRA3F9b.d.cts → taskRunner-DCyp6Gea.d.cts} +2 -2
  48. package/dist/{taskRunner-B1NUWyWR.d.ts → taskRunner-DnxtObeq.d.ts} +2 -2
  49. package/dist/wallet/expo/background.cjs +12 -12
  50. package/dist/wallet/expo/background.d.cts +3 -3
  51. package/dist/wallet/expo/background.d.ts +3 -3
  52. package/dist/wallet/expo/background.js +4 -4
  53. package/dist/wallet/expo/index.cjs +11 -11
  54. package/dist/wallet/expo/index.d.cts +4 -4
  55. package/dist/wallet/expo/index.d.ts +4 -4
  56. package/dist/wallet/expo/index.js +3 -3
  57. package/dist/{wallet-D6uoBLmS.d.ts → wallet-BWHbd5b1.d.cts} +231 -8
  58. package/dist/{wallet-By9HIo0Q.d.cts → wallet-Bth5uucA.d.ts} +231 -8
  59. package/dist/worker/expo/index.cjs +7 -7
  60. package/dist/worker/expo/index.d.cts +4 -4
  61. package/dist/worker/expo/index.d.ts +4 -4
  62. package/dist/worker/expo/index.js +3 -3
  63. package/package.json +2 -2
  64. package/dist/chunk-5WDBHWX3.js.map +0 -1
  65. package/dist/chunk-CCLNFHJ5.cjs.map +0 -1
  66. package/dist/chunk-FSAXPBGP.cjs.map +0 -1
  67. package/dist/chunk-FXFBPXV3.js.map +0 -1
  68. package/dist/chunk-HFXEUW55.js.map +0 -1
  69. package/dist/chunk-XCHBQVMK.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkFSAXPBGP_cjs = require('./chunk-FSAXPBGP.cjs');
3
+ var chunkC6OODRWD_cjs = require('./chunk-C6OODRWD.cjs');
4
4
  var chunkGUTKJMSF_cjs = require('./chunk-GUTKJMSF.cjs');
5
5
  var chunkCMPJR3HS_cjs = require('./chunk-CMPJR3HS.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 = chunkFSAXPBGP_cjs.getArkPsbtFields(tx.root, 0, chunkFSAXPBGP_cjs.CosignerPublicKey).map(
184
+ const cosigners = chunkC6OODRWD_cjs.getArkPsbtFields(tx.root, 0, chunkC6OODRWD_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 = chunkFSAXPBGP_cjs.getArkPsbtFields(g.root, 0, chunkFSAXPBGP_cjs.CosignerPublicKey).map((c) => c.key);
236
+ const cosigners = chunkC6OODRWD_cjs.getArkPsbtFields(g.root, 0, chunkC6OODRWD_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 fetch(url, {
983
+ const response = await chunkC6OODRWD_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: chunkFSAXPBGP_cjs.Intent.encodeMessage(intent.message),
990
+ message: chunkC6OODRWD_cjs.Intent.encodeMessage(intent.message),
991
991
  proof: intent.proof
992
992
  },
993
993
  forfeit_txs: forfeitTxs,
@@ -995,8 +995,8 @@ var RestDelegateProvider = class {
995
995
  })
996
996
  });
997
997
  if (!response.ok) {
998
- const errorText = await response.text();
999
- throw new Error(`Failed to delegate: ${errorText}`);
998
+ const errorText2 = await response.text();
999
+ throw new Error(`Failed to delegate: ${errorText2}`);
1000
1000
  }
1001
1001
  }
1002
1002
  /**
@@ -1007,10 +1007,10 @@ var RestDelegateProvider = class {
1007
1007
  */
1008
1008
  async getDelegateInfo() {
1009
1009
  const url = `${this.url}/v1/delegator/info`;
1010
- const response = await fetch(url);
1010
+ const response = await chunkC6OODRWD_cjs.baseFetch(url);
1011
1011
  if (!response.ok) {
1012
- const errorText = await response.text();
1013
- throw new Error(`Failed to get delegate info: ${errorText}`);
1012
+ const errorText2 = await response.text();
1013
+ throw new Error(`Failed to get delegate info: ${errorText2}`);
1014
1014
  }
1015
1015
  const data = await response.json();
1016
1016
  if (!isDelegateInfo(data)) {
@@ -1042,14 +1042,14 @@ var EsploraProvider = class {
1042
1042
  pollingInterval;
1043
1043
  forcePolling;
1044
1044
  async getCoins(address) {
1045
- const response = await fetch(`${this.baseUrl}/address/${address}/utxo`);
1045
+ const response = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/fee-estimates`);
1052
+ const response = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/tx/${txid}/outspends`);
1073
+ const response = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/address/${address}/txs`);
1081
+ const response = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/tx/${txid}`);
1089
+ const txresponse = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/tx/${txid}/status`);
1097
+ const response = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/blocks`);
1181
+ const tipBlocks = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/txs/package`, {
1200
+ const response = await chunkC6OODRWD_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 fetch(`${this.baseUrl}/tx`, {
1214
+ const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/tx`, {
1215
1215
  method: "POST",
1216
1216
  headers: {
1217
1217
  "Content-Type": "text/plain"
@@ -1280,7 +1280,7 @@ function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
1280
1280
  );
1281
1281
  }
1282
1282
  function buildForfeitTxWithOutput(inputs, output, txLocktime) {
1283
- const tx = new chunkFSAXPBGP_cjs.Transaction({
1283
+ const tx = new chunkC6OODRWD_cjs.Transaction({
1284
1284
  version: 3,
1285
1285
  lockTime: txLocktime
1286
1286
  });
@@ -1356,7 +1356,7 @@ function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
1356
1356
  if (previousScriptKey.length !== 32) {
1357
1357
  throw new Error(`parent output ${childIndex} has invalid script`);
1358
1358
  }
1359
- const cosigners = chunkFSAXPBGP_cjs.getArkPsbtFields(child.root, 0, chunkFSAXPBGP_cjs.CosignerPublicKey);
1359
+ const cosigners = chunkC6OODRWD_cjs.getArkPsbtFields(child.root, 0, chunkC6OODRWD_cjs.CosignerPublicKey);
1360
1360
  if (cosigners.length === 0) {
1361
1361
  throw ErrMissingCosignersPublicKeys;
1362
1362
  }
@@ -1456,7 +1456,7 @@ var Extension = class _Extension {
1456
1456
  `expected magic prefix ${base.hex.encode(ARKADE_MAGIC)}, got ${base.hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`
1457
1457
  );
1458
1458
  }
1459
- const reader = new chunkFSAXPBGP_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
1459
+ const reader = new chunkC6OODRWD_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
1460
1460
  const packets = [];
1461
1461
  while (reader.remaining() > 0) {
1462
1462
  const packetType = reader.readByte();
@@ -1530,7 +1530,7 @@ var Extension = class _Extension {
1530
1530
  */
1531
1531
  getAssetPacket() {
1532
1532
  for (const p of this.packets) {
1533
- if (p instanceof chunkFSAXPBGP_cjs.Packet) {
1533
+ if (p instanceof chunkC6OODRWD_cjs.Packet) {
1534
1534
  return p;
1535
1535
  }
1536
1536
  }
@@ -1538,8 +1538,8 @@ var Extension = class _Extension {
1538
1538
  }
1539
1539
  };
1540
1540
  function parsePacket(packetType, data) {
1541
- if (packetType === chunkFSAXPBGP_cjs.Packet.PACKET_TYPE) {
1542
- return chunkFSAXPBGP_cjs.Packet.fromBytes(data);
1541
+ if (packetType === chunkC6OODRWD_cjs.Packet.PACKET_TYPE) {
1542
+ return chunkC6OODRWD_cjs.Packet.fromBytes(data);
1543
1543
  }
1544
1544
  return new UnknownPacket(packetType, data);
1545
1545
  }
@@ -1730,7 +1730,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1730
1730
  const existing = inputsByAssetId.get(asset.assetId);
1731
1731
  inputsByAssetId.set(asset.assetId, [
1732
1732
  ...existing ?? [],
1733
- chunkFSAXPBGP_cjs.AssetInput.create(inputIndex, asset.amount)
1733
+ chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount)
1734
1734
  ]);
1735
1735
  }
1736
1736
  }
@@ -1742,7 +1742,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1742
1742
  const existing = outputsByAssetId.get(asset.assetId);
1743
1743
  outputsByAssetId.set(asset.assetId, [
1744
1744
  ...existing ?? [],
1745
- chunkFSAXPBGP_cjs.AssetOutput.create(outputIndex, asset.amount)
1745
+ chunkC6OODRWD_cjs.AssetOutput.create(outputIndex, asset.amount)
1746
1746
  ]);
1747
1747
  }
1748
1748
  }
@@ -1753,7 +1753,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1753
1753
  const existing = outputsByAssetId.get(asset.assetId);
1754
1754
  outputsByAssetId.set(asset.assetId, [
1755
1755
  ...existing ?? [],
1756
- chunkFSAXPBGP_cjs.AssetOutput.create(outputIndex, asset.amount)
1756
+ chunkC6OODRWD_cjs.AssetOutput.create(outputIndex, asset.amount)
1757
1757
  ]);
1758
1758
  }
1759
1759
  }
@@ -1762,11 +1762,11 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
1762
1762
  for (const assetIdStr of allAssetIds) {
1763
1763
  const inputs = inputsByAssetId.get(assetIdStr);
1764
1764
  const outputs = outputsByAssetId.get(assetIdStr);
1765
- const assetId = chunkFSAXPBGP_cjs.AssetId.fromString(assetIdStr);
1766
- const group = chunkFSAXPBGP_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
1765
+ const assetId = chunkC6OODRWD_cjs.AssetId.fromString(assetIdStr);
1766
+ const group = chunkC6OODRWD_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
1767
1767
  groups.push(group);
1768
1768
  }
1769
- return chunkFSAXPBGP_cjs.Packet.create(groups);
1769
+ return chunkC6OODRWD_cjs.Packet.create(groups);
1770
1770
  }
1771
1771
  function selectCoinsWithAsset(coins, assetId, requiredAmount) {
1772
1772
  const coinsWithAsset = coins.filter((coin) => coin.assets?.some((a) => a.assetId === assetId));
@@ -1801,6 +1801,45 @@ function selectedCoinsToAssetInputs(selectedCoins) {
1801
1801
  }
1802
1802
  return assetInputs;
1803
1803
  }
1804
+ function toXOnlySignerHex(pubkeyHex) {
1805
+ const bytes = base.hex.decode(pubkeyHex);
1806
+ if (bytes.length === 33) return base.hex.encode(bytes.slice(1));
1807
+ if (bytes.length === 32) return base.hex.encode(bytes);
1808
+ throw new Error(`invalid signer pubkey length: expected 32 or 33 bytes, got ${bytes.length}`);
1809
+ }
1810
+ function signerSetFromInfo(info) {
1811
+ const active = toXOnlySignerHex(info.signerPubkey);
1812
+ const deprecated = /* @__PURE__ */ new Map();
1813
+ for (const signer of info.deprecatedSigners) {
1814
+ if (!signer.pubkey) continue;
1815
+ deprecated.set(toXOnlySignerHex(signer.pubkey), signer.cutoffDate);
1816
+ }
1817
+ return { active, deprecated };
1818
+ }
1819
+ function classifyAgainstSignerSet(contractServerPubKeyHex, signerSet, nowSeconds = Math.floor(Date.now() / 1e3)) {
1820
+ const signerPubKey = toXOnlySignerHex(contractServerPubKeyHex);
1821
+ if (signerPubKey === signerSet.active) {
1822
+ return { status: "CURRENT", signerPubKey };
1823
+ }
1824
+ if (!signerSet.deprecated.has(signerPubKey)) {
1825
+ return { status: "UNKNOWN_SIGNER", signerPubKey };
1826
+ }
1827
+ const cutoffDate = signerSet.deprecated.get(signerPubKey);
1828
+ if (cutoffDate === 0n) {
1829
+ return { status: "DUE_NOW", signerPubKey };
1830
+ }
1831
+ const secondsUntilCutoff = Number(cutoffDate) - nowSeconds;
1832
+ if (secondsUntilCutoff <= 0) {
1833
+ return { status: "EXPIRED", signerPubKey, cutoffDate, secondsUntilCutoff };
1834
+ }
1835
+ return { status: "MIGRATABLE", signerPubKey, cutoffDate, secondsUntilCutoff };
1836
+ }
1837
+ function classifyContractSigner(contractServerPubKeyHex, info, nowSeconds = Math.floor(Date.now() / 1e3)) {
1838
+ return classifyAgainstSignerSet(contractServerPubKeyHex, signerSetFromInfo(info), nowSeconds);
1839
+ }
1840
+ function isCooperativelyMigratable(status) {
1841
+ return status === "MIGRATABLE" || status === "DUE_NOW";
1842
+ }
1804
1843
  function buildOffchainTx(inputs, outputs, serverUnrollScript) {
1805
1844
  const MAX_OP_RETURN = 2;
1806
1845
  let countOpReturn = 0;
@@ -1844,7 +1883,7 @@ function buildVirtualTx(inputs, outputs) {
1844
1883
  }
1845
1884
  }
1846
1885
  }
1847
- const tx = new chunkFSAXPBGP_cjs.Transaction({
1886
+ const tx = new chunkC6OODRWD_cjs.Transaction({
1848
1887
  version: 3,
1849
1888
  lockTime: Number(lockTime)
1850
1889
  });
@@ -1859,7 +1898,7 @@ function buildVirtualTx(inputs, outputs) {
1859
1898
  },
1860
1899
  tapLeafScript: [input.tapLeafScript]
1861
1900
  });
1862
- chunkFSAXPBGP_cjs.setArkPsbtField(tx, i, chunkFSAXPBGP_cjs.VtxoTaprootTree, input.tapTree);
1901
+ chunkC6OODRWD_cjs.setArkPsbtField(tx, i, chunkC6OODRWD_cjs.VtxoTaprootTree, input.tapTree);
1863
1902
  }
1864
1903
  for (const output of outputs) {
1865
1904
  tx.addOutput(output);
@@ -2381,6 +2420,20 @@ function validateRecipients(recipients, dustAmount) {
2381
2420
  }
2382
2421
 
2383
2422
  // src/wallet/vtxo-manager.ts
2423
+ function selectPendingRecoveryOutpoints(contractsWithVtxos, signerSet, nowSeconds = Math.floor(Date.now() / 1e3)) {
2424
+ const out = /* @__PURE__ */ new Set();
2425
+ for (const { contract, vtxos } of contractsWithVtxos) {
2426
+ const serverPubKey = contract.params.serverPubKey;
2427
+ if (!serverPubKey) continue;
2428
+ if (classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds).status !== "EXPIRED") {
2429
+ continue;
2430
+ }
2431
+ for (const v of vtxos) {
2432
+ if (isSpendable(v) && !isRecoverable(v)) out.add(`${v.txid}:${v.vout}`);
2433
+ }
2434
+ }
2435
+ return out;
2436
+ }
2384
2437
  function isSweepCapable(wallet) {
2385
2438
  return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet && "signOnchainBoardingTx" in wallet;
2386
2439
  }
@@ -2418,6 +2471,18 @@ function byExpiryAscending(vtxos) {
2418
2471
  };
2419
2472
  return [...vtxos].sort((a, b) => expiryKey(a) - expiryKey(b));
2420
2473
  }
2474
+ function capSettlementBatch(sorted, maxAmount) {
2475
+ const batch = [];
2476
+ let total = 0n;
2477
+ for (const vtxo of sorted) {
2478
+ if (batch.length >= MAX_VTXOS_PER_SETTLEMENT) break;
2479
+ const next = total + BigInt(vtxo.value);
2480
+ if (maxAmount >= 0n && next > maxAmount) continue;
2481
+ batch.push(vtxo);
2482
+ total = next;
2483
+ }
2484
+ return batch;
2485
+ }
2421
2486
  var DEFAULT_THRESHOLD_SECONDS = 259200;
2422
2487
  var DEFAULT_THRESHOLD_MS = DEFAULT_THRESHOLD_SECONDS * 1e3;
2423
2488
  var DEFAULT_RENEWAL_CONFIG = {
@@ -2427,7 +2492,8 @@ var DEFAULT_RENEWAL_CONFIG = {
2427
2492
  var DEFAULT_SETTLEMENT_CONFIG = {
2428
2493
  vtxoThreshold: DEFAULT_THRESHOLD_SECONDS,
2429
2494
  boardingUtxoSweep: true,
2430
- pollIntervalMs: 6e4
2495
+ pollIntervalMs: 6e4,
2496
+ deprecatedSignerMigration: true
2431
2497
  };
2432
2498
  function getRecoverableVtxos(vtxos, dustAmount) {
2433
2499
  return vtxos.filter((vtxo) => {
@@ -2481,6 +2547,51 @@ function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
2481
2547
  (vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) || isRecoverable(vtxo) || isSpendable(vtxo) && isExpired(vtxo) || isSubdust(vtxo, dustAmount)
2482
2548
  );
2483
2549
  }
2550
+ function isMigrationCapable(wallet) {
2551
+ return "arkProvider" in wallet && "arkServerPublicKey" in wallet && "onchainProvider" in wallet && typeof wallet.rotateServerSigner === "function" && typeof wallet.sendSelectedVtxosToSelf === "function" && typeof wallet.getBoardingUtxosForSigners === "function";
2552
+ }
2553
+ function classifiedToRef(c) {
2554
+ return {
2555
+ txid: c.vtxo.txid,
2556
+ vout: c.vtxo.vout,
2557
+ value: c.vtxo.value,
2558
+ signerPubKey: c.classification.signerPubKey,
2559
+ cutoffDate: c.classification.cutoffDate
2560
+ };
2561
+ }
2562
+ function classifiedBoardingToRef(c) {
2563
+ return {
2564
+ txid: c.coin.txid,
2565
+ vout: c.coin.vout,
2566
+ value: c.coin.value,
2567
+ signerPubKey: c.classification.signerPubKey,
2568
+ cutoffDate: c.classification.cutoffDate
2569
+ };
2570
+ }
2571
+ function mergeSignerReports(...reportLists) {
2572
+ const bySigner = /* @__PURE__ */ new Map();
2573
+ for (const list of reportLists) {
2574
+ for (const r of list) {
2575
+ const existing = bySigner.get(r.signerPubKey);
2576
+ if (existing) {
2577
+ existing.vtxoCount += r.vtxoCount;
2578
+ existing.totalValue += r.totalValue;
2579
+ existing.boardingCount += r.boardingCount;
2580
+ existing.boardingValue += r.boardingValue;
2581
+ existing.recoverableCount += r.recoverableCount;
2582
+ existing.recoverableValue += r.recoverableValue;
2583
+ existing.awaitingSweepCount += r.awaitingSweepCount;
2584
+ existing.awaitingSweepValue += r.awaitingSweepValue;
2585
+ if (r.nextSweepEta !== void 0) {
2586
+ existing.nextSweepEta = existing.nextSweepEta === void 0 ? r.nextSweepEta : Math.min(existing.nextSweepEta, r.nextSweepEta);
2587
+ }
2588
+ } else {
2589
+ bySigner.set(r.signerPubKey, { ...r });
2590
+ }
2591
+ }
2592
+ }
2593
+ return Array.from(bySigner.values());
2594
+ }
2484
2595
  var VtxoManager = class _VtxoManager {
2485
2596
  constructor(wallet, renewalConfig, settlementConfig) {
2486
2597
  this.wallet = wallet;
@@ -2496,15 +2607,9 @@ var VtxoManager = class _VtxoManager {
2496
2607
  } else {
2497
2608
  this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
2498
2609
  }
2499
- this.contractEventsSubscriptionReady = this.initializeSubscription().then(
2500
- (subscription) => {
2501
- this.contractEventsSubscription = subscription;
2502
- return subscription;
2503
- }
2504
- );
2610
+ this.contractEventsSubscriptionReady = this.initializeSubscription();
2505
2611
  }
2506
2612
  settlementConfig;
2507
- contractEventsSubscription;
2508
2613
  contractEventsSubscriptionReady;
2509
2614
  disposePromise;
2510
2615
  pollTimeoutId;
@@ -2541,6 +2646,15 @@ var VtxoManager = class _VtxoManager {
2541
2646
  lastVtxoSpentRefreshTimestamp = 0;
2542
2647
  vtxoSpentRefreshPromise;
2543
2648
  static VTXO_SPENT_REFRESH_COOLDOWN_MS = 3e4;
2649
+ // Cooldown/backoff for the automatic deprecated-signer migration pass.
2650
+ // Mirrors the periodic-settle machinery so a server-side migration failure
2651
+ // (e.g. arkd not yet accepting old-key inputs, or a closed cutoff window)
2652
+ // backs off exponentially instead of re-submitting an identical intent on
2653
+ // every poll. The manual migrateDeprecatedSignerVtxos() bypasses this.
2654
+ lastMigrationTimestamp = 0;
2655
+ consecutiveMigrationFailures = 0;
2656
+ static MIGRATION_COOLDOWN_MS = 3e4;
2657
+ static MIGRATION_MAX_BACKOFF_MS = 5 * 60 * 1e3;
2544
2658
  // ========== Recovery Methods ==========
2545
2659
  /**
2546
2660
  * Recover swept/expired virtual outputs by settling them back to the wallet's Arkade address.
@@ -2581,16 +2695,21 @@ var VtxoManager = class _VtxoManager {
2581
2695
  if (vtxosToRecover.length === 0) {
2582
2696
  throw new Error("No recoverable VTXOs found");
2583
2697
  }
2584
- if (vtxosToRecover.length > MAX_VTXOS_PER_SETTLEMENT) {
2698
+ const info = await this.getInfoProvider()?.getInfo();
2699
+ const vtxoMaxAmount = info?.vtxoMaxAmount ?? -1n;
2700
+ const capped = capSettlementBatch(byValueDescending(vtxosToRecover), vtxoMaxAmount);
2701
+ if (capped.length < vtxosToRecover.length) {
2585
2702
  const recoverableCount = vtxosToRecover.length;
2586
- const capped = byValueDescending(vtxosToRecover).slice(0, MAX_VTXOS_PER_SETTLEMENT);
2587
2703
  ({ vtxosToRecover, totalAmount } = getRecoverableWithSubdust(capped, dustAmount));
2588
2704
  if (vtxosToRecover.length === 0) {
2589
2705
  throw new Error(
2590
- `Capped recovery batch (highest-value ${MAX_VTXOS_PER_SETTLEMENT} of ${recoverableCount} recoverable VTXOs) is below the dust threshold ${dustAmount}`
2706
+ `Capped recovery batch (highest-value subset of ${recoverableCount} recoverable VTXOs within the ${MAX_VTXOS_PER_SETTLEMENT}-input and ${vtxoMaxAmount}-sat limits) is below the dust threshold ${dustAmount}`
2591
2707
  );
2592
2708
  }
2593
2709
  }
2710
+ if (info && isMigrationCapable(this.wallet)) {
2711
+ await this.rotateForRecoverableInputs(vtxosToRecover, info);
2712
+ }
2594
2713
  const arkAddress = await this.wallet.getAddress();
2595
2714
  return this.wallet.settle(
2596
2715
  {
@@ -2740,8 +2859,24 @@ var VtxoManager = class _VtxoManager {
2740
2859
  if (vtxos.length === 0) {
2741
2860
  throw new Error("No VTXOs available to renew");
2742
2861
  }
2743
- if (vtxos.length > MAX_VTXOS_PER_SETTLEMENT) {
2744
- vtxos = byExpiryAscending(vtxos).slice(0, MAX_VTXOS_PER_SETTLEMENT);
2862
+ const info = await this.getInfoProvider()?.getInfo();
2863
+ const vtxoMaxAmount = info?.vtxoMaxAmount ?? -1n;
2864
+ const capped = capSettlementBatch(byExpiryAscending(vtxos), vtxoMaxAmount);
2865
+ if (vtxoMaxAmount >= 0n) {
2866
+ const oversized = vtxos.filter((vtxo) => BigInt(vtxo.value) > vtxoMaxAmount);
2867
+ if (oversized.length > 0) {
2868
+ console.warn(
2869
+ `Renewal: ${oversized.length} VTXO(s) exceed the per-output limit ${vtxoMaxAmount} and cannot be renewed; they risk unilateral exit`
2870
+ );
2871
+ }
2872
+ }
2873
+ if (capped.length < vtxos.length) {
2874
+ vtxos = capped;
2875
+ if (vtxos.length === 0) {
2876
+ throw new Error(
2877
+ `No VTXOs available to renew within the per-output limit ${vtxoMaxAmount}`
2878
+ );
2879
+ }
2745
2880
  }
2746
2881
  const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
2747
2882
  const dustAmount = getDustAmount(this.wallet);
@@ -2750,6 +2885,9 @@ var VtxoManager = class _VtxoManager {
2750
2885
  `Total amount ${totalAmount} is below dust threshold ${dustAmount}`
2751
2886
  );
2752
2887
  }
2888
+ if (info && isMigrationCapable(this.wallet)) {
2889
+ await this.rotateForRecoverableInputs(vtxos, info);
2890
+ }
2753
2891
  const arkAddress = await this.wallet.getAddress();
2754
2892
  const txid = await this.wallet.settle(
2755
2893
  {
@@ -2868,7 +3006,7 @@ var VtxoManager = class _VtxoManager {
2868
3006
  `Sweep not economical: output ${outputAmount} sats after ${fee} sats fee is below dust (${dustAmount} sats)`
2869
3007
  );
2870
3008
  }
2871
- const tx = new chunkFSAXPBGP_cjs.Transaction();
3009
+ const tx = new chunkC6OODRWD_cjs.Transaction();
2872
3010
  for (const utxo of expiredUtxos) {
2873
3011
  const utxoScript = chunkCMPJR3HS_cjs.VtxoScript.decode(utxo.tapTree);
2874
3012
  const utxoExitLeaf = utxoScript.leaves.find(
@@ -2900,6 +3038,472 @@ var VtxoManager = class _VtxoManager {
2900
3038
  this.knownBoardingUtxos.add(`${txid}:0`);
2901
3039
  return txid;
2902
3040
  }
3041
+ // ========== Deprecated-Signer Migration Methods ==========
3042
+ /**
3043
+ * Cooperatively migrate VTXOs minted under a now-deprecated server signer
3044
+ * to the wallet's active-signer address. See {@link IVtxoManager}.
3045
+ */
3046
+ async migrateDeprecatedSignerVtxos(options) {
3047
+ return this.migrateCore(options);
3048
+ }
3049
+ /**
3050
+ * Machine-readable status of every deprecated server signer the wallet
3051
+ * currently holds funds under (Section 6), without migrating. Covers both
3052
+ * VTXO and boarding holdings (Section 7), merged per signer.
3053
+ *
3054
+ * @remarks This is no longer a pure repository/info read: surfacing boarding
3055
+ * holdings fans out per boarding address (`getCoins` round trips) and
3056
+ * refreshes the UTXO cache via `saveUtxos`.
3057
+ */
3058
+ async getDeprecatedSignerStatus() {
3059
+ const wallet = this.requireMigrationCapableWallet();
3060
+ const info = await wallet.arkProvider.getInfo();
3061
+ const { reports: vtxoReports } = await this.classifyDeprecatedSignerContracts(info);
3062
+ const { reports: boardingReports } = await this.classifyDeprecatedSignerBoarding(info);
3063
+ return mergeSignerReports(vtxoReports, boardingReports);
3064
+ }
3065
+ /**
3066
+ * Core migration routine shared by the manual API and the automatic poll
3067
+ * pass. Fetches a fresh {@link ArkInfo}, applies a mid-session signer
3068
+ * rotation when the wallet's own snapshot signer has been deprecated,
3069
+ * selects spendable VTXOs under deprecated-signer contracts (cutoff-first),
3070
+ * and settles them to the active-signer Ark address.
3071
+ */
3072
+ async migrateCore(options) {
3073
+ const wallet = this.requireMigrationCapableWallet();
3074
+ const info = await wallet.arkProvider.getInfo();
3075
+ const signerSet = signerSetFromInfo(info);
3076
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3077
+ const walletSignerHex = base.hex.encode(wallet.arkServerPublicKey);
3078
+ const walletClass = classifyAgainstSignerSet(walletSignerHex, signerSet, nowSeconds);
3079
+ if (signerSet.deprecated.size === 0 && walletClass.status === "CURRENT") {
3080
+ return { rotated: false, expired: [], signers: [] };
3081
+ }
3082
+ if (walletClass.status === "UNKNOWN_SIGNER") {
3083
+ const { reports: vtxoReports2 } = await this.classifyDeprecatedSignerContracts(info);
3084
+ const { reports: boardingReports2 } = await this.classifyDeprecatedSignerBoarding(info);
3085
+ return {
3086
+ rotated: false,
3087
+ expired: [],
3088
+ signers: mergeSignerReports(vtxoReports2, boardingReports2),
3089
+ skipped: "unknown-wallet-signer"
3090
+ };
3091
+ }
3092
+ const rotated = await this.ensureReceiveOnActiveSigner(info);
3093
+ const {
3094
+ reports: vtxoReports,
3095
+ migratable: vtxoMigratable,
3096
+ expired: vtxoExpired
3097
+ } = await this.classifyDeprecatedSignerContracts(info);
3098
+ const {
3099
+ reports: boardingReports,
3100
+ migratable: boardingMigratable,
3101
+ expired: boardingExpired
3102
+ } = await this.classifyDeprecatedSignerBoarding(info);
3103
+ const reports = mergeSignerReports(vtxoReports, boardingReports);
3104
+ const expiredRefs = [
3105
+ ...vtxoExpired.map(classifiedToRef),
3106
+ ...boardingExpired.map(classifiedBoardingToRef)
3107
+ ];
3108
+ if (vtxoMigratable.length === 0 && boardingMigratable.length === 0) {
3109
+ return {
3110
+ rotated,
3111
+ expired: expiredRefs,
3112
+ signers: reports,
3113
+ skipped: "no-deprecated-vtxos"
3114
+ };
3115
+ }
3116
+ const vtxoMaxAmount = info.vtxoMaxAmount;
3117
+ const dustAmount = getDustAmount(this.wallet);
3118
+ const report = {
3119
+ rotated,
3120
+ expired: expiredRefs,
3121
+ signers: reports
3122
+ };
3123
+ if (vtxoMigratable.length > 0) {
3124
+ report.vtxos = await this.runMigrationLeg(
3125
+ vtxoMigratable,
3126
+ (c) => c.vtxo.value,
3127
+ classifiedToRef,
3128
+ vtxoMaxAmount,
3129
+ dustAmount,
3130
+ "VTXO",
3131
+ (capped) => wallet.sendSelectedVtxosToSelf(capped.map((c) => c.vtxo))
3132
+ );
3133
+ }
3134
+ if (boardingMigratable.length > 0) {
3135
+ report.boarding = await this.runMigrationLeg(
3136
+ boardingMigratable,
3137
+ (c) => c.coin.value,
3138
+ classifiedBoardingToRef,
3139
+ vtxoMaxAmount,
3140
+ dustAmount,
3141
+ "boarding",
3142
+ async (capped) => {
3143
+ const arkAddress = await this.wallet.getAddress();
3144
+ const totalAmount = capped.reduce((sum, c) => sum + BigInt(c.coin.value), 0n);
3145
+ return this.wallet.settle(
3146
+ {
3147
+ inputs: capped.map((c) => c.coin),
3148
+ outputs: [{ address: arkAddress, amount: totalAmount }]
3149
+ },
3150
+ options?.eventCallback
3151
+ );
3152
+ }
3153
+ );
3154
+ }
3155
+ return report;
3156
+ }
3157
+ /**
3158
+ * Size and submit one migration leg. Filters inputs whose value alone
3159
+ * exceeds the per-output ceiling (`vtxoMaxAmount`; `< 0` means no limit) —
3160
+ * those can never form a ≤-ceiling output and must exit unilaterally — then
3161
+ * caps the rest (highest-value first; bounded by {@link MAX_VTXOS_PER_SETTLEMENT}
3162
+ * AND a gross total within `vtxoMaxAmount`), applies the protocol dust floor,
3163
+ * and submits the capped batch through `submit`. A throw from `submit` lands
3164
+ * in `error`; the caller's other leg still runs.
3165
+ *
3166
+ * Migration is mandatory and fee-exempt: every selected input moves at its
3167
+ * full value, so the gross total IS the aggregated output amount (kept under
3168
+ * the server ceiling by the cap). The dust floor guards the degenerate cases
3169
+ * where every input was oversized or the whole holding sums below dust.
3170
+ */
3171
+ async runMigrationLeg(candidates, valueOf, toRef, vtxoMaxAmount, dustAmount, legName, submit) {
3172
+ const oversizedRefs = [];
3173
+ const sized = [];
3174
+ for (const c of candidates) {
3175
+ if (vtxoMaxAmount >= 0n && BigInt(valueOf(c)) > vtxoMaxAmount) {
3176
+ oversizedRefs.push(toRef(c));
3177
+ } else {
3178
+ sized.push(c);
3179
+ }
3180
+ }
3181
+ if (oversizedRefs.length > 0) {
3182
+ console.warn(
3183
+ `Deprecated-signer migration (${legName}): ${oversizedRefs.length} input(s) exceed the per-output limit ${vtxoMaxAmount} and cannot be migrated cooperatively; they require a unilateral exit.`
3184
+ );
3185
+ }
3186
+ const oversizedField = oversizedRefs.length > 0 ? { oversized: oversizedRefs } : {};
3187
+ const capped = capSettlementBatch(
3188
+ byValueDescending(sized.map((c) => ({ value: valueOf(c), c }))),
3189
+ vtxoMaxAmount
3190
+ ).map((w) => w.c);
3191
+ const deferred = sized.length - capped.length;
3192
+ const totalAmount = capped.reduce((sum, c) => sum + BigInt(valueOf(c)), 0n);
3193
+ if (totalAmount < dustAmount) {
3194
+ const onlyOversized = sized.length === 0 && oversizedRefs.length > 0;
3195
+ return {
3196
+ migrated: [],
3197
+ skipped: onlyOversized ? "oversized-only" : "below-dust",
3198
+ ...oversizedField
3199
+ };
3200
+ }
3201
+ try {
3202
+ const txid = await submit(capped);
3203
+ return {
3204
+ txid,
3205
+ migrated: capped.map(toRef),
3206
+ ...deferred > 0 ? { deferred } : {},
3207
+ ...oversizedField
3208
+ };
3209
+ } catch (e) {
3210
+ return {
3211
+ migrated: [],
3212
+ error: e instanceof Error ? e.message : String(e),
3213
+ ...oversizedField
3214
+ };
3215
+ }
3216
+ }
3217
+ /**
3218
+ * Enumerate the wallet's `default`/`delegate` contracts, classify each
3219
+ * against the fresh signer set, and split their spendable VTXOs into
3220
+ * cooperatively-migratable and cutoff-expired sets while building the
3221
+ * per-signer status report. Current-signer contracts are skipped; swept
3222
+ * (recoverable) VTXOs are excluded from the settle sets — those follow the
3223
+ * recovery path — but are still counted on EXPIRED report rows
3224
+ * (`recoverableCount`) so post-cutoff funds in flight stay visible.
3225
+ */
3226
+ async classifyDeprecatedSignerContracts(info) {
3227
+ const cm = await this.wallet.getContractManager();
3228
+ const signerSet = signerSetFromInfo(info);
3229
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3230
+ const contractsWithVtxos = await cm.getContractsWithVtxos({
3231
+ type: ["default", "delegate"]
3232
+ });
3233
+ const reportsBySigner = /* @__PURE__ */ new Map();
3234
+ const migratable = [];
3235
+ const expired = [];
3236
+ for (const { contract, vtxos } of contractsWithVtxos) {
3237
+ const serverPubKey = contract.params.serverPubKey;
3238
+ if (!serverPubKey) continue;
3239
+ const cls = classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds);
3240
+ if (cls.status === "CURRENT") continue;
3241
+ const recoverable = vtxos.filter((v) => isRecoverable(v));
3242
+ const spendable = vtxos.filter((v) => isSpendable(v) && !isRecoverable(v));
3243
+ const value = spendable.reduce((sum, v) => sum + v.value, 0);
3244
+ let recoverableCount = 0;
3245
+ let recoverableValue = 0;
3246
+ let awaitingSweepCount = 0;
3247
+ let awaitingSweepValue = 0;
3248
+ let nextSweepEta;
3249
+ if (cls.status === "EXPIRED") {
3250
+ recoverableCount = recoverable.length;
3251
+ recoverableValue = recoverable.reduce((sum, v) => sum + v.value, 0);
3252
+ awaitingSweepCount = spendable.length;
3253
+ awaitingSweepValue = value;
3254
+ for (const v of spendable) {
3255
+ const exp = v.virtualStatus.batchExpiry;
3256
+ if (exp !== void 0 && (nextSweepEta === void 0 || exp < nextSweepEta)) {
3257
+ nextSweepEta = exp;
3258
+ }
3259
+ }
3260
+ }
3261
+ const existing = reportsBySigner.get(cls.signerPubKey);
3262
+ if (existing) {
3263
+ existing.vtxoCount += spendable.length;
3264
+ existing.totalValue += value;
3265
+ existing.recoverableCount += recoverableCount;
3266
+ existing.recoverableValue += recoverableValue;
3267
+ existing.awaitingSweepCount += awaitingSweepCount;
3268
+ existing.awaitingSweepValue += awaitingSweepValue;
3269
+ if (nextSweepEta !== void 0) {
3270
+ existing.nextSweepEta = existing.nextSweepEta === void 0 ? nextSweepEta : Math.min(existing.nextSweepEta, nextSweepEta);
3271
+ }
3272
+ } else {
3273
+ reportsBySigner.set(cls.signerPubKey, {
3274
+ signerPubKey: cls.signerPubKey,
3275
+ status: cls.status,
3276
+ cutoffDate: cls.cutoffDate,
3277
+ secondsUntilCutoff: cls.secondsUntilCutoff,
3278
+ vtxoCount: spendable.length,
3279
+ totalValue: value,
3280
+ boardingCount: 0,
3281
+ boardingValue: 0,
3282
+ recoverableCount,
3283
+ recoverableValue,
3284
+ awaitingSweepCount,
3285
+ awaitingSweepValue,
3286
+ nextSweepEta
3287
+ });
3288
+ }
3289
+ if (isCooperativelyMigratable(cls.status)) {
3290
+ for (const v of spendable) {
3291
+ if (!v.virtualStatus.batchExpiry) continue;
3292
+ migratable.push({ vtxo: v, classification: cls });
3293
+ }
3294
+ } else if (cls.status === "EXPIRED") {
3295
+ for (const v of spendable) expired.push({ vtxo: v, classification: cls });
3296
+ }
3297
+ }
3298
+ return {
3299
+ reports: Array.from(reportsBySigner.values()),
3300
+ migratable,
3301
+ expired
3302
+ };
3303
+ }
3304
+ /**
3305
+ * Boarding sibling of {@link classifyDeprecatedSignerContracts} (Section 7):
3306
+ * fan out over the wallet's boarding addresses (current + historical), group
3307
+ * the on-chain UTXOs per address, classify each address's signer against the
3308
+ * fresh signer set, and split the confirmed boarding coins into cooperatively-
3309
+ * migratable and cutoff-expired sets while building the per-signer report.
3310
+ *
3311
+ * Discovery sees the active signer plus EVERY deprecated key (EXPIRED
3312
+ * included), so expired-signer boarding is still reported; migration
3313
+ * eligibility is gated afterwards by {@link isCooperativelyMigratable} and a
3314
+ * per-row boarding-output CSV check — never by the fetch. Current-signer
3315
+ * coins are classified `CURRENT` and ignored; foreign-ASP rows are excluded
3316
+ * because their keys are not in the signer set.
3317
+ */
3318
+ async classifyDeprecatedSignerBoarding(info) {
3319
+ const wallet = this.requireMigrationCapableWallet();
3320
+ const signerSet = signerSetFromInfo(info);
3321
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3322
+ const allowed = /* @__PURE__ */ new Set([signerSet.active, ...signerSet.deprecated.keys()]);
3323
+ const groups = await wallet.getBoardingUtxosForSigners(allowed);
3324
+ let chainTipHeight;
3325
+ if (groups.some((g) => g.csvTimelock.type === "blocks")) {
3326
+ const tip = await wallet.onchainProvider.getChainTip();
3327
+ chainTipHeight = tip.height;
3328
+ }
3329
+ const reportsBySigner = /* @__PURE__ */ new Map();
3330
+ const migratable = [];
3331
+ const expired = [];
3332
+ for (const group of groups) {
3333
+ const cls = classifyAgainstSignerSet(group.serverPubKey, signerSet, nowSeconds);
3334
+ if (cls.status === "CURRENT") continue;
3335
+ const confirmed = group.coins.filter((c) => c.status.confirmed);
3336
+ if (confirmed.length === 0) continue;
3337
+ const value = confirmed.reduce((sum, c) => sum + c.value, 0);
3338
+ const existing = reportsBySigner.get(cls.signerPubKey);
3339
+ if (existing) {
3340
+ existing.boardingCount += confirmed.length;
3341
+ existing.boardingValue += value;
3342
+ } else {
3343
+ reportsBySigner.set(cls.signerPubKey, {
3344
+ signerPubKey: cls.signerPubKey,
3345
+ status: cls.status,
3346
+ cutoffDate: cls.cutoffDate,
3347
+ secondsUntilCutoff: cls.secondsUntilCutoff,
3348
+ vtxoCount: 0,
3349
+ totalValue: 0,
3350
+ boardingCount: confirmed.length,
3351
+ boardingValue: value,
3352
+ // Boarding UTXOs don't carry an offchain sweep lifecycle; the
3353
+ // post-cutoff recover-on-sweep fields apply to VTXOs only and
3354
+ // are merged in from the VTXO classifier (mergeSignerReports).
3355
+ recoverableCount: 0,
3356
+ recoverableValue: 0,
3357
+ awaitingSweepCount: 0,
3358
+ awaitingSweepValue: 0
3359
+ });
3360
+ }
3361
+ for (const coin of confirmed) {
3362
+ const boardingExpired = hasBoardingTxExpired(
3363
+ coin,
3364
+ group.csvTimelock,
3365
+ chainTipHeight
3366
+ );
3367
+ if (isCooperativelyMigratable(cls.status) && !boardingExpired) {
3368
+ migratable.push({ coin, classification: cls });
3369
+ } else if (cls.status === "EXPIRED") {
3370
+ expired.push({ coin, classification: cls });
3371
+ }
3372
+ }
3373
+ }
3374
+ return {
3375
+ reports: Array.from(reportsBySigner.values()),
3376
+ migratable,
3377
+ expired
3378
+ };
3379
+ }
3380
+ /**
3381
+ * Automatic migration pass invoked from the poll loop. Self-contained:
3382
+ * respects an exponential cooldown and logs failures rather than throwing,
3383
+ * so a persistently failing migration backs off instead of re-submitting
3384
+ * an identical intent every cycle.
3385
+ */
3386
+ async runMigrationPass() {
3387
+ const cooldownMs = Math.min(
3388
+ _VtxoManager.MIGRATION_COOLDOWN_MS * Math.pow(2, this.consecutiveMigrationFailures),
3389
+ _VtxoManager.MIGRATION_MAX_BACKOFF_MS
3390
+ );
3391
+ if (Date.now() - this.lastMigrationTimestamp < cooldownMs) return;
3392
+ try {
3393
+ const report = await this.migrateCore();
3394
+ const legError = report.vtxos?.error ?? report.boarding?.error;
3395
+ if (legError) {
3396
+ this.consecutiveMigrationFailures++;
3397
+ console.error("Deprecated-signer migration leg failed:", legError);
3398
+ } else {
3399
+ this.consecutiveMigrationFailures = 0;
3400
+ }
3401
+ } catch (e) {
3402
+ this.consecutiveMigrationFailures++;
3403
+ console.error("Error during deprecated-signer migration:", e);
3404
+ } finally {
3405
+ this.lastMigrationTimestamp = Date.now();
3406
+ }
3407
+ }
3408
+ /** Asserts migration capability and returns the typed wallet. */
3409
+ requireMigrationCapableWallet() {
3410
+ if (!isMigrationCapable(this.wallet)) {
3411
+ throw new Error(
3412
+ "Deprecated-signer migration requires a Wallet instance with arkProvider, arkServerPublicKey, and rotateServerSigner"
3413
+ );
3414
+ }
3415
+ return this.wallet;
3416
+ }
3417
+ /**
3418
+ * If the wallet's own construction-time signer snapshot has been deprecated,
3419
+ * re-derive its receive/boarding state under the active signer so any output
3420
+ * built afterwards commits to the active key. No-op when the snapshot is
3421
+ * already current. Returns whether a rotation was applied. Treats an
3422
+ * unknown-signer snapshot as "do not rotate" (caller decides).
3423
+ *
3424
+ * Shared by the migration pass (where the wallet's own snapshot is the thing
3425
+ * being migrated) and the recovery/renewal/periodic-settle paths (via
3426
+ * {@link rotateForRecoverableInputs}), so a swept old-signer VTXO recovered
3427
+ * after cutoff re-mints under the active signer rather than re-committing to
3428
+ * the deprecated key (Section 6 / post-cutoff). `rotateServerSigner` is
3429
+ * idempotent and serializes itself against HD receive rotation, so repeated
3430
+ * calls across passes are safe.
3431
+ */
3432
+ async ensureReceiveOnActiveSigner(info) {
3433
+ const wallet = this.requireMigrationCapableWallet();
3434
+ const signerSet = signerSetFromInfo(info);
3435
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3436
+ const walletClass = classifyAgainstSignerSet(
3437
+ base.hex.encode(wallet.arkServerPublicKey),
3438
+ signerSet,
3439
+ nowSeconds
3440
+ );
3441
+ if (walletClass.status === "CURRENT" || walletClass.status === "UNKNOWN_SIGNER") {
3442
+ return false;
3443
+ }
3444
+ await wallet.rotateServerSigner(base.hex.decode(info.signerPubkey), info.checkpointTapscript);
3445
+ return true;
3446
+ }
3447
+ /**
3448
+ * Rotation guard for the recovery-bearing settle paths (recover / renew /
3449
+ * periodic settle). Pins the wallet's receive snapshot to the active signer
3450
+ * before they build their output, but ONLY when this pass actually carries
3451
+ * an input minted under a deprecated signer — so a routine current-signer
3452
+ * settle on a long-lived pre-rotation instance does not eagerly rotate.
3453
+ *
3454
+ * Cheap in the common case: a watch-only/proxy wallet (not migration-capable)
3455
+ * and a current/unknown wallet snapshot both short-circuit before the
3456
+ * contract round-trip, so the only instance that pays for the input scan is
3457
+ * the long-lived deprecated-snapshot one that genuinely needs rotating.
3458
+ *
3459
+ * Runs OUTSIDE any `renewalInProgress` window the caller sets, and
3460
+ * `rotateServerSigner` does not depend on that flag, so it cannot deadlock
3461
+ * against the receive rotator. Returns whether a rotation was applied.
3462
+ */
3463
+ async rotateForRecoverableInputs(inputs, info) {
3464
+ if (!isMigrationCapable(this.wallet)) return false;
3465
+ const signerSet = signerSetFromInfo(info);
3466
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3467
+ const walletClass = classifyAgainstSignerSet(
3468
+ base.hex.encode(this.wallet.arkServerPublicKey),
3469
+ signerSet,
3470
+ nowSeconds
3471
+ );
3472
+ if (walletClass.status === "CURRENT" || walletClass.status === "UNKNOWN_SIGNER") {
3473
+ return false;
3474
+ }
3475
+ if (!await this.anyInputUnderDeprecatedSigner(inputs, signerSet, nowSeconds)) {
3476
+ return false;
3477
+ }
3478
+ return this.ensureReceiveOnActiveSigner(info);
3479
+ }
3480
+ /**
3481
+ * Whether any of the given input outpoints belongs to a contract whose
3482
+ * server signer classifies as non-`CURRENT` against the fresh signer set —
3483
+ * i.e. a deprecated-signer (incl. EXPIRED) input. Maps outpoints to their
3484
+ * owning contract via the ContractManager so it works on the typed
3485
+ * {@link ExtendedVirtualCoin}/{@link ExtendedCoin} inputs the recovery paths
3486
+ * carry (which don't expose `contractScript`).
3487
+ */
3488
+ async anyInputUnderDeprecatedSigner(inputs, signerSet, nowSeconds) {
3489
+ if (inputs.length === 0) return false;
3490
+ const wanted = new Set(inputs.map((i) => `${i.txid}:${i.vout}`));
3491
+ const cm = await this.wallet.getContractManager();
3492
+ const contractsWithVtxos = await cm.getContractsWithVtxos({
3493
+ type: ["default", "delegate"]
3494
+ });
3495
+ for (const { contract, vtxos } of contractsWithVtxos) {
3496
+ const serverPubKey = contract.params.serverPubKey;
3497
+ if (!serverPubKey) continue;
3498
+ if (classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds).status === "CURRENT") {
3499
+ continue;
3500
+ }
3501
+ for (const v of vtxos) {
3502
+ if (wanted.has(`${v.txid}:${v.vout}`)) return true;
3503
+ }
3504
+ }
3505
+ return false;
3506
+ }
2903
3507
  // ========== Private Helpers ==========
2904
3508
  /** Asserts sweep capability and returns the typed wallet. */
2905
3509
  getSweepWallet() {
@@ -2926,6 +3530,16 @@ var VtxoManager = class _VtxoManager {
2926
3530
  getArkProvider() {
2927
3531
  return this.getSweepWallet().arkProvider;
2928
3532
  }
3533
+ /**
3534
+ * Read-only access to the ark provider for fetching server limits. Unlike
3535
+ * {@link getArkProvider}, this does not require full boarding-sweep
3536
+ * capability — recovery and renewal only need it to read `vtxoMaxAmount`.
3537
+ * Returns undefined when no provider is wired, which callers treat as
3538
+ * "no limit".
3539
+ */
3540
+ getInfoProvider() {
3541
+ return this.wallet.arkProvider;
3542
+ }
2929
3543
  /** Returns the Bitcoin network configuration from the wallet. */
2930
3544
  getNetwork() {
2931
3545
  return this.getSweepWallet().network;
@@ -3030,7 +3644,7 @@ var VtxoManager = class _VtxoManager {
3030
3644
  * or doesn't carry the metadata.
3031
3645
  */
3032
3646
  extractSpentOutpoint(error) {
3033
- const ark = chunkFSAXPBGP_cjs.maybeArkError(error);
3647
+ const ark = chunkC6OODRWD_cjs.maybeArkError(error);
3034
3648
  if (!ark || ark.name !== "VTXO_ALREADY_SPENT") return void 0;
3035
3649
  const raw = ark.metadata?.vtxo_outpoint;
3036
3650
  if (typeof raw !== "string") return void 0;
@@ -3127,6 +3741,10 @@ var VtxoManager = class _VtxoManager {
3127
3741
  }
3128
3742
  }
3129
3743
  }
3744
+ const migrationEnabled = this.settlementConfig !== false && (this.settlementConfig?.deprecatedSignerMigration ?? DEFAULT_SETTLEMENT_CONFIG.deprecatedSignerMigration);
3745
+ if (migrationEnabled && isMigrationCapable(this.wallet)) {
3746
+ await this.runMigrationPass();
3747
+ }
3130
3748
  });
3131
3749
  } catch (e) {
3132
3750
  hadError = true;
@@ -3195,7 +3813,8 @@ var VtxoManager = class _VtxoManager {
3195
3813
  return;
3196
3814
  }
3197
3815
  const dustAmount = getDustAmount(this.wallet);
3198
- const { fees } = await this.getArkProvider().getInfo();
3816
+ const info = await this.getArkProvider().getInfo();
3817
+ const { fees, vtxoMaxAmount } = info;
3199
3818
  const estimator = new Estimator(fees.intentFee);
3200
3819
  let totalAmount = 0n;
3201
3820
  const filteredBoarding = [];
@@ -3224,12 +3843,19 @@ var VtxoManager = class _VtxoManager {
3224
3843
  if (inputFee.satoshis >= v.value) {
3225
3844
  continue;
3226
3845
  }
3846
+ const net = BigInt(v.value) - BigInt(inputFee.satoshis);
3847
+ if (vtxoMaxAmount >= 0n && totalAmount + net > vtxoMaxAmount) {
3848
+ continue;
3849
+ }
3227
3850
  filteredVtxos.push(v);
3228
- totalAmount += BigInt(v.value) - BigInt(inputFee.satoshis);
3851
+ totalAmount += net;
3229
3852
  }
3230
3853
  if (filteredBoarding.length === 0 && filteredVtxos.length === 0) {
3231
3854
  return;
3232
3855
  }
3856
+ if (isMigrationCapable(this.wallet)) {
3857
+ await this.rotateForRecoverableInputs([...filteredBoarding, ...filteredVtxos], info);
3858
+ }
3233
3859
  const arkAddress = await this.wallet.getAddress();
3234
3860
  const outputFee = estimator.evalOffchainOutput({
3235
3861
  amount: totalAmount,
@@ -3292,7 +3918,6 @@ var VtxoManager = class _VtxoManager {
3292
3918
  clearTimeout(timer);
3293
3919
  }
3294
3920
  const subscription = await this.contractEventsSubscriptionReady;
3295
- this.contractEventsSubscription = void 0;
3296
3921
  subscription?.();
3297
3922
  })();
3298
3923
  return this.disposePromise;
@@ -3833,15 +4458,17 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
3833
4458
  txTime = getTxCreatedAt ? await getTxCreatedAt(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1 : vtxo.createdAt.getTime() + 1;
3834
4459
  }
3835
4460
  const assets = subtractAssets(allSpent, changes);
3836
- sent.push({
3837
- key: { ...txKey, arkTxid: vtxo.arkTxId },
3838
- tag: "offchain",
3839
- type: "SENT" /* TxSent */,
3840
- amount: txAmount,
3841
- settled: true,
3842
- createdAt: txTime,
3843
- ...assets && { assets }
3844
- });
4461
+ if (txAmount !== 0 || assets) {
4462
+ sent.push({
4463
+ key: { ...txKey, arkTxid: vtxo.arkTxId },
4464
+ tag: "offchain",
4465
+ type: "SENT" /* TxSent */,
4466
+ amount: txAmount,
4467
+ settled: true,
4468
+ createdAt: txTime,
4469
+ ...assets && { assets }
4470
+ });
4471
+ }
3845
4472
  }
3846
4473
  if (vtxo.settledBy && !commitmentsToIgnore.has(vtxo.settledBy) && !sent.some((s) => s.key.commitmentTxid === vtxo.settledBy)) {
3847
4474
  const changes = fromOldestVtxo.filter(
@@ -3928,7 +4555,7 @@ var AssetManager = class extends ReadonlyAssetManager {
3928
4555
  const virtualCoins = await this.wallet.getVtxos({
3929
4556
  withRecoverable: false
3930
4557
  });
3931
- const controlAssetRef = params.controlAssetId ? chunkFSAXPBGP_cjs.AssetRef.fromId(chunkFSAXPBGP_cjs.AssetId.fromString(params.controlAssetId)) : null;
4558
+ const controlAssetRef = params.controlAssetId ? chunkC6OODRWD_cjs.AssetRef.fromId(chunkC6OODRWD_cjs.AssetId.fromString(params.controlAssetId)) : null;
3932
4559
  const coinSelection = selectVirtualCoins(virtualCoins, Number(this.wallet.dustAmount));
3933
4560
  let totalBtcSelected = 0n;
3934
4561
  const assetChanges = /* @__PURE__ */ new Map();
@@ -3941,8 +4568,8 @@ var AssetManager = class extends ReadonlyAssetManager {
3941
4568
  }
3942
4569
  }
3943
4570
  const groups = [];
3944
- const issuedAssetOutput = chunkFSAXPBGP_cjs.AssetOutput.create(0, params.amount);
3945
- const issuedAssetGroup = chunkFSAXPBGP_cjs.AssetGroup.create(
4571
+ const issuedAssetOutput = chunkC6OODRWD_cjs.AssetOutput.create(0, params.amount);
4572
+ const issuedAssetGroup = chunkC6OODRWD_cjs.AssetGroup.create(
3946
4573
  null,
3947
4574
  controlAssetRef,
3948
4575
  [],
@@ -3957,15 +4584,15 @@ var AssetManager = class extends ReadonlyAssetManager {
3957
4584
  for (const [inputIndex, assets] of assetInputs) {
3958
4585
  for (const asset of assets) {
3959
4586
  if (asset.assetId !== assetId) continue;
3960
- changeInputs.push(chunkFSAXPBGP_cjs.AssetInput.create(inputIndex, asset.amount));
4587
+ changeInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
3961
4588
  }
3962
4589
  }
3963
4590
  groups.push(
3964
- chunkFSAXPBGP_cjs.AssetGroup.create(
3965
- chunkFSAXPBGP_cjs.AssetId.fromString(assetId),
4591
+ chunkC6OODRWD_cjs.AssetGroup.create(
4592
+ chunkC6OODRWD_cjs.AssetId.fromString(assetId),
3966
4593
  null,
3967
4594
  changeInputs,
3968
- [chunkFSAXPBGP_cjs.AssetOutput.create(0, amount)],
4595
+ [chunkC6OODRWD_cjs.AssetOutput.create(0, amount)],
3969
4596
  []
3970
4597
  )
3971
4598
  );
@@ -3978,7 +4605,7 @@ var AssetManager = class extends ReadonlyAssetManager {
3978
4605
  script: outputAddress.pkScript,
3979
4606
  amount: BigInt(totalBtcSelected)
3980
4607
  },
3981
- Extension.create([chunkFSAXPBGP_cjs.Packet.create(groups)]).txOut()
4608
+ Extension.create([chunkC6OODRWD_cjs.Packet.create(groups)]).txOut()
3982
4609
  ];
3983
4610
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(
3984
4611
  coinSelection.inputs,
@@ -3986,7 +4613,7 @@ var AssetManager = class extends ReadonlyAssetManager {
3986
4613
  );
3987
4614
  return {
3988
4615
  arkTxId: arkTxid,
3989
- assetId: chunkFSAXPBGP_cjs.AssetId.create(arkTxid, 0).toString()
4616
+ assetId: chunkC6OODRWD_cjs.AssetId.create(arkTxid, 0).toString()
3990
4617
  };
3991
4618
  }
3992
4619
  /**
@@ -4058,16 +4685,16 @@ var AssetManager = class extends ReadonlyAssetManager {
4058
4685
  for (const [inputIndex, assets] of assetInputs) {
4059
4686
  for (const asset of assets) {
4060
4687
  if (asset.assetId !== params.assetId) continue;
4061
- reissueInputs.push(chunkFSAXPBGP_cjs.AssetInput.create(inputIndex, asset.amount));
4688
+ reissueInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
4062
4689
  }
4063
4690
  }
4064
4691
  const totalAssetAmount = assetToReissueAmount + params.amount;
4065
- const reissueAssetIdObj = chunkFSAXPBGP_cjs.AssetId.fromString(params.assetId);
4066
- const reissueAssetGroup = chunkFSAXPBGP_cjs.AssetGroup.create(
4692
+ const reissueAssetIdObj = chunkC6OODRWD_cjs.AssetId.fromString(params.assetId);
4693
+ const reissueAssetGroup = chunkC6OODRWD_cjs.AssetGroup.create(
4067
4694
  reissueAssetIdObj,
4068
4695
  null,
4069
4696
  reissueInputs,
4070
- [chunkFSAXPBGP_cjs.AssetOutput.create(0, totalAssetAmount)],
4697
+ [chunkC6OODRWD_cjs.AssetOutput.create(0, totalAssetAmount)],
4071
4698
  []
4072
4699
  );
4073
4700
  const groups = [reissueAssetGroup];
@@ -4076,15 +4703,15 @@ var AssetManager = class extends ReadonlyAssetManager {
4076
4703
  for (const [inputIndex, assets] of assetInputs) {
4077
4704
  for (const asset of assets) {
4078
4705
  if (asset.assetId !== assetId) continue;
4079
- changeInputs.push(chunkFSAXPBGP_cjs.AssetInput.create(inputIndex, asset.amount));
4706
+ changeInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
4080
4707
  }
4081
4708
  }
4082
4709
  groups.push(
4083
- chunkFSAXPBGP_cjs.AssetGroup.create(
4084
- chunkFSAXPBGP_cjs.AssetId.fromString(assetId),
4710
+ chunkC6OODRWD_cjs.AssetGroup.create(
4711
+ chunkC6OODRWD_cjs.AssetId.fromString(assetId),
4085
4712
  null,
4086
4713
  changeInputs,
4087
- [chunkFSAXPBGP_cjs.AssetOutput.create(0, amount)],
4714
+ [chunkC6OODRWD_cjs.AssetOutput.create(0, amount)],
4088
4715
  []
4089
4716
  )
4090
4717
  );
@@ -4096,7 +4723,7 @@ var AssetManager = class extends ReadonlyAssetManager {
4096
4723
  script: outputAddress.pkScript,
4097
4724
  amount: BigInt(totalBtcSelected)
4098
4725
  },
4099
- Extension.create([chunkFSAXPBGP_cjs.Packet.create(groups)]).txOut()
4726
+ Extension.create([chunkC6OODRWD_cjs.Packet.create(groups)]).txOut()
4100
4727
  ];
4101
4728
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
4102
4729
  return arkTxid;
@@ -4163,15 +4790,15 @@ var AssetManager = class extends ReadonlyAssetManager {
4163
4790
  for (const [inputIndex, assets] of assetInputs) {
4164
4791
  for (const asset of assets) {
4165
4792
  if (asset.assetId !== assetId) continue;
4166
- changeInputs.push(chunkFSAXPBGP_cjs.AssetInput.create(inputIndex, asset.amount));
4793
+ changeInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
4167
4794
  }
4168
4795
  }
4169
4796
  groups.push(
4170
- chunkFSAXPBGP_cjs.AssetGroup.create(
4171
- chunkFSAXPBGP_cjs.AssetId.fromString(assetId),
4797
+ chunkC6OODRWD_cjs.AssetGroup.create(
4798
+ chunkC6OODRWD_cjs.AssetId.fromString(assetId),
4172
4799
  null,
4173
4800
  changeInputs,
4174
- amount > 0n ? [chunkFSAXPBGP_cjs.AssetOutput.create(0, amount)] : [],
4801
+ amount > 0n ? [chunkC6OODRWD_cjs.AssetOutput.create(0, amount)] : [],
4175
4802
  []
4176
4803
  )
4177
4804
  );
@@ -4183,7 +4810,7 @@ var AssetManager = class extends ReadonlyAssetManager {
4183
4810
  script: outputAddress.pkScript,
4184
4811
  amount: BigInt(totalBtcSelected)
4185
4812
  },
4186
- Extension.create([chunkFSAXPBGP_cjs.Packet.create(groups)]).txOut()
4813
+ Extension.create([chunkC6OODRWD_cjs.Packet.create(groups)]).txOut()
4187
4814
  ];
4188
4815
  const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
4189
4816
  return arkTxid;
@@ -4208,7 +4835,7 @@ function castMetadata(metadata) {
4208
4835
  } else {
4209
4836
  throw new Error("Invalid metadata value type");
4210
4837
  }
4211
- md.push(chunkFSAXPBGP_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
4838
+ md.push(chunkC6OODRWD_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
4212
4839
  }
4213
4840
  return md;
4214
4841
  }
@@ -4493,7 +5120,7 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
4493
5120
  expire_at: 0,
4494
5121
  cosigners_public_keys: cosignerPubKeys
4495
5122
  };
4496
- const proof = chunkFSAXPBGP_cjs.Intent.create(message, coins, outputs);
5123
+ const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, outputs);
4497
5124
  const signedProof = await identity.sign(proof);
4498
5125
  return {
4499
5126
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -6014,6 +6641,16 @@ function isDiscoverable(handler) {
6014
6641
  }
6015
6642
 
6016
6643
  // src/contracts/contractWatcher.ts
6644
+ function computeReconnectDelay(attempt, baseMs, maxMs) {
6645
+ return Math.min(baseMs * Math.pow(2, attempt - 1), maxMs);
6646
+ }
6647
+ var DEFAULT_CONTRACT_WATCHER_CONFIG = {
6648
+ failsafePollIntervalMs: 2e4,
6649
+ reconnectDelayMs: 1e3,
6650
+ maxReconnectDelayMs: 5e3,
6651
+ maxReconnectAttempts: 0
6652
+ // unlimited
6653
+ };
6017
6654
  var ContractWatcher = class {
6018
6655
  config;
6019
6656
  contracts = /* @__PURE__ */ new Map();
@@ -6033,14 +6670,7 @@ var ContractWatcher = class {
6033
6670
  */
6034
6671
  constructor(config) {
6035
6672
  this.config = {
6036
- failsafePollIntervalMs: 6e4,
6037
- // 1 minute
6038
- reconnectDelayMs: 1e3,
6039
- // 1 second
6040
- maxReconnectDelayMs: 3e4,
6041
- // 30 seconds
6042
- maxReconnectAttempts: 0,
6043
- // unlimited
6673
+ ...DEFAULT_CONTRACT_WATCHER_CONFIG,
6044
6674
  ...config
6045
6675
  };
6046
6676
  }
@@ -6233,7 +6863,7 @@ var ContractWatcher = class {
6233
6863
  this.connectionState = "connected";
6234
6864
  this.reconnectAttempts = 0;
6235
6865
  this.listenLoop().catch((e) => {
6236
- if (chunkFSAXPBGP_cjs.isEventSourceError(e)) {
6866
+ if (chunkC6OODRWD_cjs.isEventSourceError(e)) {
6237
6867
  console.debug("ContractWatcher subscription disconnected; reconnecting");
6238
6868
  } else {
6239
6869
  console.error(e);
@@ -6268,8 +6898,9 @@ var ContractWatcher = class {
6268
6898
  }
6269
6899
  this.connectionState = "reconnecting";
6270
6900
  this.reconnectAttempts++;
6271
- const delay = Math.min(
6272
- this.config.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1),
6901
+ const delay = computeReconnectDelay(
6902
+ this.reconnectAttempts,
6903
+ this.config.reconnectDelayMs,
6273
6904
  this.config.maxReconnectDelayMs
6274
6905
  );
6275
6906
  this.reconnectTimeoutId = setTimeout(() => {
@@ -7640,6 +8271,22 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
7640
8271
  async drain() {
7641
8272
  await this.chain.catch(() => void 0);
7642
8273
  }
8274
+ /**
8275
+ * Run `fn` on the rotator's serialization chain, so it cannot interleave
8276
+ * with a receive `rotate()`. Used by {@link Wallet.rotateServerSigner} to
8277
+ * serialize server-signer rotation against HD receive rotation: both
8278
+ * rebuild and swap `offchainTapscript`, so running them concurrently could
8279
+ * tear the wallet's visible receive state. The chain keeps advancing even
8280
+ * if `fn` rejects (its own caller still sees the rejection).
8281
+ */
8282
+ runExclusive(fn) {
8283
+ const run = this.chain.catch(() => void 0).then(fn);
8284
+ this.chain = run.then(
8285
+ () => void 0,
8286
+ () => void 0
8287
+ );
8288
+ return run;
8289
+ }
7643
8290
  /**
7644
8291
  * Tear down the subscription first so no late `vtxo_received` event
7645
8292
  * can queue work on a disposing wallet, then drain any in-flight
@@ -7997,7 +8644,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
7997
8644
  this.network = network;
7998
8645
  this.onchainProvider = onchainProvider;
7999
8646
  this.indexerProvider = indexerProvider;
8000
- this.arkServerPublicKey = arkServerPublicKey;
8001
8647
  this.dustAmount = dustAmount;
8002
8648
  this.walletRepository = walletRepository;
8003
8649
  this.contractRepository = contractRepository;
@@ -8014,6 +8660,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8014
8660
  }
8015
8661
  this._offchainTapscript = offchainTapscript;
8016
8662
  this._boardingTapscript = boardingTapscript;
8663
+ this._arkServerPublicKey = arkServerPublicKey;
8017
8664
  this.watcherConfig = watcherConfig;
8018
8665
  this._assetManager = new ReadonlyAssetManager(this.indexerProvider);
8019
8666
  this.walletContractTimelocks = walletContractTimelocks && walletContractTimelocks.length > 0 ? dedupeTimelocks(walletContractTimelocks) : [this.offchainTapscript.options.csvTimelock];
@@ -8022,7 +8669,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
8022
8669
  _contractManagerInitializing;
8023
8670
  watcherConfig;
8024
8671
  _assetManager;
8025
- _syncVtxosInflight;
8026
8672
  walletContractTimelocks;
8027
8673
  // Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
8028
8674
  // from getVtxos() so concurrent callers (UI, VtxoManager auto-renewal,
@@ -8051,6 +8697,59 @@ var ReadonlyWallet = class _ReadonlyWallet {
8051
8697
  * it stays the index-0 baseline for their lifetime.
8052
8698
  */
8053
8699
  _boardingTapscript;
8700
+ /**
8701
+ * Backing field for the active server signer (x-only, 32 bytes). Read via
8702
+ * the public {@link arkServerPublicKey} getter; written only by
8703
+ * {@link Wallet.setArkServerPublicKeyForRotation}, the sanctioned
8704
+ * server-signer rotation write path (analogue of `_offchainTapscript`). It
8705
+ * is a *current value*, not a fixed constructor constant, because
8706
+ * mid-session server-signer rotation (plan §4) swaps it when arkd rotates
8707
+ * its active signer. Wallets that never span a rotation keep their
8708
+ * construction-time snapshot for their lifetime.
8709
+ */
8710
+ _arkServerPublicKey;
8711
+ /**
8712
+ * x-only hex of the operator's deprecated signer keys (from
8713
+ * `ArkInfo.deprecatedSigners`), cached for the OFFLINE read/watch paths.
8714
+ * The boarding watch/history surfaces ({@link getBoardingAddresses},
8715
+ * {@link getBoardingTxs}) fan out over {current} ∪ this set so a deposit at
8716
+ * a boarding address minted under a now-rotated operator signer keeps being
8717
+ * watched. Refreshed from the server-info snapshot at construction (via the
8718
+ * create() factories) and on a detected signer change. Deliberately NOT
8719
+ * consulted by the spend path — {@link getBoardingUtxos} stays
8720
+ * current-signer-only (a deprecated-signer input in a plain settle() is
8721
+ * rejected; old-signer recovery goes through the migration API).
8722
+ */
8723
+ _deprecatedSigners = /* @__PURE__ */ new Map();
8724
+ /**
8725
+ * Refresh the cached deprecated-signer set from a fresh server-info
8726
+ * snapshot. Called by the create() factories at construction and by the
8727
+ * server-info-change handler mid-session. Lenient: a malformed deprecated
8728
+ * entry is skipped, never fatal to wallet creation.
8729
+ */
8730
+ refreshDeprecatedSigners(info) {
8731
+ const next = /* @__PURE__ */ new Map();
8732
+ for (const s of info.deprecatedSigners ?? []) {
8733
+ if (!s.pubkey) continue;
8734
+ try {
8735
+ next.set(toXOnlySignerHex(s.pubkey), s.cutoffDate ?? 0n);
8736
+ } catch (e) {
8737
+ console.warn("Skipping malformed deprecated signer pubkey", s.pubkey, e);
8738
+ }
8739
+ }
8740
+ this._deprecatedSigners = next;
8741
+ }
8742
+ /**
8743
+ * The signer set the boarding WATCH/HISTORY paths fan out over: the wallet's
8744
+ * current signer plus every cached deprecated signer. Distinct from the
8745
+ * spend path, which is current-signer-only.
8746
+ */
8747
+ watchedBoardingSigners() {
8748
+ return /* @__PURE__ */ new Set([
8749
+ toXOnlySignerHex(base.hex.encode(this.boardingTapscript.options.serverPubKey)),
8750
+ ...this._deprecatedSigners.keys()
8751
+ ]);
8752
+ }
8054
8753
  /**
8055
8754
  * Currently-active receive tapscript. Read-only from the outside;
8056
8755
  * mutated only via {@link Wallet.setOffchainTapscriptForRotation}
@@ -8059,6 +8758,16 @@ var ReadonlyWallet = class _ReadonlyWallet {
8059
8758
  get offchainTapscript() {
8060
8759
  return this._offchainTapscript;
8061
8760
  }
8761
+ /**
8762
+ * The wallet's current active server signer (x-only, 32 bytes). Read-only
8763
+ * from the outside; mutated only via
8764
+ * {@link Wallet.setArkServerPublicKeyForRotation} during mid-session
8765
+ * server-signer rotation (plan §4). Single-valued for wallets that never
8766
+ * span a rotation.
8767
+ */
8768
+ get arkServerPublicKey() {
8769
+ return this._arkServerPublicKey;
8770
+ }
8062
8771
  /**
8063
8772
  * The wallet's current boarding tapscript (the on-chain onboarding
8064
8773
  * target). Read-only from the outside; mutated only via
@@ -8111,7 +8820,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8111
8820
  */
8112
8821
  static async setupWalletConfig(config, pubKey) {
8113
8822
  const arkadeServerUrl = getArkadeServerUrl(config);
8114
- const arkProvider = config.arkProvider || new chunkFSAXPBGP_cjs.RestArkProvider(arkadeServerUrl);
8823
+ const arkProvider = config.arkProvider || new chunkC6OODRWD_cjs.RestArkProvider(arkadeServerUrl);
8115
8824
  let indexerProvider = config.indexerProvider;
8116
8825
  if (!indexerProvider) {
8117
8826
  let indexerUrl = config.indexerUrl;
@@ -8128,7 +8837,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8128
8837
  indexerUrl = arkadeServerUrl;
8129
8838
  }
8130
8839
  }
8131
- indexerProvider = new chunkFSAXPBGP_cjs.RestIndexerProvider(indexerUrl);
8840
+ indexerProvider = new chunkC6OODRWD_cjs.RestIndexerProvider(indexerUrl);
8132
8841
  }
8133
8842
  const info = await arkProvider.getInfo();
8134
8843
  const network = chunkCMPJR3HS_cjs.getNetwork(info.network);
@@ -8217,7 +8926,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8217
8926
  throw new Error("Invalid configured public key");
8218
8927
  }
8219
8928
  const setup = await _ReadonlyWallet.setupWalletConfig(config, pubkey);
8220
- return new _ReadonlyWallet(
8929
+ const wallet = new _ReadonlyWallet(
8221
8930
  config.identity,
8222
8931
  setup.network,
8223
8932
  setup.onchainProvider,
@@ -8232,6 +8941,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
8232
8941
  config.watcherConfig,
8233
8942
  setup.walletContractTimelocks
8234
8943
  );
8944
+ wallet.refreshDeprecatedSigners(setup.info);
8945
+ return wallet;
8235
8946
  }
8236
8947
  get arkAddress() {
8237
8948
  return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
@@ -8255,10 +8966,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
8255
8966
  * Return the wallet's combined onchain and offchain balances.
8256
8967
  */
8257
8968
  async getBalance() {
8258
- const [boardingUtxos, vtxos] = await Promise.all([
8969
+ const [boardingUtxos, vtxos, pendingOutpoints] = await Promise.all([
8259
8970
  this.getBoardingUtxos(),
8260
- this.getVtxos()
8971
+ this.getVtxos(),
8972
+ this.pendingRecoveryOutpoints()
8261
8973
  ]);
8974
+ const isPendingRecovery = (coin) => pendingOutpoints.has(`${coin.txid}:${coin.vout}`);
8262
8975
  let confirmed = 0;
8263
8976
  let unconfirmed = 0;
8264
8977
  for (const utxo of boardingUtxos) {
@@ -8271,11 +8984,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
8271
8984
  let settled = 0;
8272
8985
  let preconfirmed = 0;
8273
8986
  let recoverable = 0;
8274
- settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled").reduce((sum, coin) => sum + coin.value, 0);
8275
- preconfirmed = vtxos.filter((coin) => coin.virtualStatus.state === "preconfirmed").reduce((sum, coin) => sum + coin.value, 0);
8987
+ let pendingRecovery = 0;
8988
+ settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
8989
+ preconfirmed = vtxos.filter(
8990
+ (coin) => coin.virtualStatus.state === "preconfirmed" && !isPendingRecovery(coin)
8991
+ ).reduce((sum, coin) => sum + coin.value, 0);
8276
8992
  recoverable = vtxos.filter((coin) => isSpendable(coin) && coin.virtualStatus.state === "swept").reduce((sum, coin) => sum + coin.value, 0);
8993
+ pendingRecovery = vtxos.filter(isPendingRecovery).reduce((sum, coin) => sum + coin.value, 0);
8277
8994
  const totalBoarding = confirmed + unconfirmed;
8278
- const totalOffchain = settled + preconfirmed + recoverable;
8995
+ const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
8279
8996
  const assetBalances = /* @__PURE__ */ new Map();
8280
8997
  for (const vtxo of vtxos) {
8281
8998
  if (!isSpendable(vtxo)) continue;
@@ -8300,6 +9017,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8300
9017
  preconfirmed,
8301
9018
  available: settled + preconfirmed,
8302
9019
  recoverable,
9020
+ pendingRecovery,
8303
9021
  total: totalBoarding + totalOffchain,
8304
9022
  assets
8305
9023
  };
@@ -8326,6 +9044,23 @@ var ReadonlyWallet = class _ReadonlyWallet {
8326
9044
  return !!(f.withUnrolled && vtxo.isUnrolled);
8327
9045
  });
8328
9046
  }
9047
+ /**
9048
+ * Outpoints of VTXOs whose deprecated signer is past its cutoff (EXPIRED) and
9049
+ * which have not yet been swept — unspendable until they recover. Offline:
9050
+ * classifies the repo's contracts against the cached signer set (active +
9051
+ * {@link _deprecatedSigners}, cutoffs included). Empty fast-path when no
9052
+ * signer is deprecated. Consumed by {@link getBalance} (the `pendingRecovery`
9053
+ * bucket) and the send coin-selection path so neither counts nor spends them.
9054
+ */
9055
+ async pendingRecoveryOutpoints() {
9056
+ if (this._deprecatedSigners.size === 0) return /* @__PURE__ */ new Set();
9057
+ const contractManager = await this.getContractManager();
9058
+ const contractsWithVtxos = await contractManager.getContractsWithVtxos();
9059
+ return selectPendingRecoveryOutpoints(contractsWithVtxos, {
9060
+ active: toXOnlySignerHex(base.hex.encode(this.offchainTapscript.options.serverPubKey)),
9061
+ deprecated: this._deprecatedSigners
9062
+ });
9063
+ }
8329
9064
  /**
8330
9065
  * Return wallet transaction history derived from Arkade state and boarding transactions.
8331
9066
  */
@@ -8352,7 +9087,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8352
9087
  * surfaced (plan §6-IV); {@link getBoardingAddress} stays single-valued.
8353
9088
  */
8354
9089
  async getBoardingAddresses() {
8355
- const tapscripts = await this.getBoardingTapscripts();
9090
+ const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
8356
9091
  return tapscripts.map((t) => t.onchainAddress(this.network));
8357
9092
  }
8358
9093
  /**
@@ -8362,7 +9097,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
8362
9097
  async getBoardingTxs() {
8363
9098
  const utxos = [];
8364
9099
  const commitmentsToIgnore = /* @__PURE__ */ new Set();
8365
- const tapscripts = await this.getBoardingTapscripts();
9100
+ const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
8366
9101
  const outspendCache = /* @__PURE__ */ new Map();
8367
9102
  for (const tapscript of tapscripts) {
8368
9103
  const boardingAddress = tapscript.onchainAddress(this.network);
@@ -8437,8 +9172,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
8437
9172
  * Always includes the index-0 baseline (identity x-only key), which covers
8438
9173
  * the degenerate equal-delay case where the index-0 boarding row is
8439
9174
  * coalesced onto a `default` row and so isn't a `boarding`-typed contract.
9175
+ *
9176
+ * @param allowedSigners - Optional set of x-only-hex server keys whose
9177
+ * persisted boarding rows are included. Defaults to `{current x-only
9178
+ * signer}`, preserving today's current-signer-only discovery (and the
9179
+ * foreign-ASP guard). The deprecated-signer migration path widens this to
9180
+ * reach old-signer boarding addresses. The index-0 baseline and the
9181
+ * current display tapscript are always included regardless of the set.
8440
9182
  */
8441
- async getBoardingTapscripts() {
9183
+ async getBoardingTapscripts(allowedSigners) {
8442
9184
  const byScript = /* @__PURE__ */ new Map();
8443
9185
  const add = (s) => byScript.set(base.hex.encode(s.pkScript), s);
8444
9186
  const boardingCsv = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
@@ -8451,11 +9193,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
8451
9193
  );
8452
9194
  add(this.boardingTapscript);
8453
9195
  const serverPubKeyHex = base.hex.encode(this.boardingTapscript.options.serverPubKey);
9196
+ const allowed = allowedSigners ?? /* @__PURE__ */ new Set([toXOnlySignerHex(serverPubKeyHex)]);
8454
9197
  const boardingContracts = await this.contractRepository.getContracts({
8455
9198
  type: ["boarding"]
8456
9199
  });
8457
9200
  for (const c of boardingContracts) {
8458
- if (c.params.serverPubKey !== serverPubKeyHex) continue;
9201
+ if (!allowed.has(toXOnlySignerHex(c.params.serverPubKey))) continue;
8459
9202
  try {
8460
9203
  add(chunkGUTKJMSF_cjs.BoardingContractHandler.createScript(c.params));
8461
9204
  } catch (e) {
@@ -8465,23 +9208,61 @@ var ReadonlyWallet = class _ReadonlyWallet {
8465
9208
  return [...byScript.values()];
8466
9209
  }
8467
9210
  /**
8468
- * Fetch and cache onchain inputs (UTXOs) received at the wallet's boarding
8469
- * addresses the current address plus any historical rotated boarding
8470
- * addresses that still hold unspent UTXOs (plan §6-III.1). Each UTXO is
8471
- * annotated with the tapscript of the address it actually sits on, so the
8472
- * spending path forfeits / exits it with the correct per-index leaves.
9211
+ * Fetch and cache onchain inputs (UTXOs) received at the boarding addresses
9212
+ * of the given signer set, grouped per boarding address so the caller keeps
9213
+ * the address↔signer association that {@link ExtendedCoin} cannot carry
9214
+ * (it retains only the encoded leaves/tapTree the spend needs, not the
9215
+ * `DefaultVtxo.Script` and its `serverPubKey`/CSV delay).
9216
+ *
9217
+ * Per group it does exactly what {@link getBoardingUtxos} does per tapscript:
9218
+ * `getCoins` → {@link extendCoinWithTapscript} → `saveUtxos`. Offline-first:
9219
+ * it does not call `getInfo()`; the caller supplies the allowed signer set,
9220
+ * so the only network calls are the per-address `getCoins`.
9221
+ *
9222
+ * @param allowedSigners - x-only-hex server keys whose boarding addresses to
9223
+ * fetch (passed through to {@link getBoardingTapscripts}).
8473
9224
  */
8474
- async getBoardingUtxos() {
8475
- const tapscripts = await this.getBoardingTapscripts();
8476
- const all = [];
9225
+ async getBoardingUtxosForSigners(allowedSigners) {
9226
+ const tapscripts = await this.getBoardingTapscripts(allowedSigners);
9227
+ const groups = [];
8477
9228
  for (const tapscript of tapscripts) {
8478
9229
  const address = tapscript.onchainAddress(this.network);
8479
9230
  const coins = await this.onchainProvider.getCoins(address);
8480
9231
  const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
8481
9232
  await this.walletRepository.saveUtxos(address, utxos);
8482
- all.push(...utxos);
9233
+ groups.push({
9234
+ tapscript,
9235
+ // Normalize so the group key matches the axis/contract x-only
9236
+ // form regardless of how the tapscript's key was stored.
9237
+ serverPubKey: toXOnlySignerHex(base.hex.encode(tapscript.options.serverPubKey)),
9238
+ // Per-row CSV delay decoded from THIS tapscript's exit leaf —
9239
+ // not the wallet's current boarding timelock, which a signer
9240
+ // rotation may have changed.
9241
+ csvTimelock: chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(base.hex.decode(tapscript.exitScript)).params.timelock,
9242
+ coins: utxos
9243
+ });
8483
9244
  }
8484
- return all;
9245
+ return groups;
9246
+ }
9247
+ /**
9248
+ * Fetch and cache onchain inputs (UTXOs) received at the wallet's boarding
9249
+ * addresses — the current address plus any historical rotated boarding
9250
+ * addresses that still hold unspent UTXOs (plan §6-III.1). Each UTXO is
9251
+ * annotated with the tapscript of the address it actually sits on, so the
9252
+ * spending path forfeits / exits it with the correct per-index leaves.
9253
+ *
9254
+ * Current-signer only: a flatten of {@link getBoardingUtxosForSigners} over
9255
+ * the wallet's current signer, so the two paths cannot drift. Old-signer
9256
+ * boarding recovery goes through the deprecated-signer migration API
9257
+ * instead (it would otherwise pull EXPIRED-signer inputs into a plain
9258
+ * `settle()` that the server must reject).
9259
+ */
9260
+ async getBoardingUtxos() {
9261
+ const currentOnly = /* @__PURE__ */ new Set([
9262
+ toXOnlySignerHex(base.hex.encode(this.boardingTapscript.options.serverPubKey))
9263
+ ]);
9264
+ const groups = await this.getBoardingUtxosForSigners(currentOnly);
9265
+ return groups.flatMap((g) => g.coins);
8485
9266
  }
8486
9267
  /**
8487
9268
  * Subscribe to onchain and offchain notifications for newly received funds.
@@ -8688,64 +9469,82 @@ var ReadonlyWallet = class _ReadonlyWallet {
8688
9469
  watcherConfig: this.watcherConfig
8689
9470
  });
8690
9471
  const baselinePubkey = await this.identity.xOnlyPublicKey();
8691
- for (const csvTimelock of this.walletContractTimelocks) {
8692
- const csvTimelockStr = chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString();
8693
- const defaultScript = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9472
+ const delegatePubKey = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? this.offchainTapscript.options.delegatePubKey : void 0;
9473
+ const baselineSigners = [
9474
+ this.offchainTapscript.options.serverPubKey,
9475
+ ...[...this._deprecatedSigners.keys()].map((h) => base.hex.decode(h))
9476
+ ];
9477
+ const seenBaselineScripts = /* @__PURE__ */ new Set();
9478
+ for (const serverPubKey of baselineSigners) {
9479
+ for (const csvTimelock of this.walletContractTimelocks) {
9480
+ const csvTimelockStr = chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString();
9481
+ const defaultScript = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9482
+ pubKey: baselinePubkey,
9483
+ serverPubKey,
9484
+ csvTimelock
9485
+ });
9486
+ const defaultScriptHex = base.hex.encode(defaultScript.pkScript);
9487
+ if (!seenBaselineScripts.has(defaultScriptHex)) {
9488
+ seenBaselineScripts.add(defaultScriptHex);
9489
+ await ensureWalletContract(manager, {
9490
+ type: "default",
9491
+ params: {
9492
+ pubKey: base.hex.encode(defaultScript.options.pubKey),
9493
+ serverPubKey: base.hex.encode(serverPubKey),
9494
+ csvTimelock: csvTimelockStr
9495
+ },
9496
+ script: defaultScriptHex,
9497
+ address: defaultScript.address(this.network.hrp, serverPubKey).encode(),
9498
+ state: "active"
9499
+ });
9500
+ }
9501
+ if (delegatePubKey) {
9502
+ const delegateScript = new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
9503
+ pubKey: baselinePubkey,
9504
+ serverPubKey,
9505
+ delegatePubKey,
9506
+ csvTimelock
9507
+ });
9508
+ const delegateScriptHex = base.hex.encode(delegateScript.pkScript);
9509
+ if (seenBaselineScripts.has(delegateScriptHex)) continue;
9510
+ seenBaselineScripts.add(delegateScriptHex);
9511
+ await manager.createContract({
9512
+ type: "delegate",
9513
+ params: {
9514
+ pubKey: base.hex.encode(delegateScript.options.pubKey),
9515
+ serverPubKey: base.hex.encode(serverPubKey),
9516
+ delegatePubKey: base.hex.encode(delegateScript.options.delegatePubKey),
9517
+ csvTimelock: csvTimelockStr
9518
+ },
9519
+ script: delegateScriptHex,
9520
+ address: delegateScript.address(this.network.hrp, serverPubKey).encode(),
9521
+ state: "active"
9522
+ });
9523
+ }
9524
+ }
9525
+ }
9526
+ const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
9527
+ for (const serverPubKey of baselineSigners) {
9528
+ const baselineBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
8694
9529
  pubKey: baselinePubkey,
8695
- serverPubKey: this.offchainTapscript.options.serverPubKey,
8696
- csvTimelock
9530
+ serverPubKey,
9531
+ csvTimelock: boardingCsvTimelock
8697
9532
  });
8698
- const defaultScriptHex = base.hex.encode(defaultScript.pkScript);
9533
+ const boardingScriptHex = base.hex.encode(baselineBoarding.pkScript);
9534
+ if (seenBaselineScripts.has(boardingScriptHex)) continue;
9535
+ seenBaselineScripts.add(boardingScriptHex);
8699
9536
  await ensureWalletContract(manager, {
8700
- type: "default",
9537
+ type: "boarding",
8701
9538
  params: {
8702
- pubKey: base.hex.encode(defaultScript.options.pubKey),
8703
- serverPubKey: base.hex.encode(defaultScript.options.serverPubKey),
8704
- csvTimelock: csvTimelockStr
9539
+ pubKey: base.hex.encode(baselineBoarding.options.pubKey),
9540
+ serverPubKey: base.hex.encode(serverPubKey),
9541
+ csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsvTimelock).toString()
8705
9542
  },
8706
- script: defaultScriptHex,
8707
- address: defaultScript.address(this.network.hrp, this.arkServerPublicKey).encode(),
9543
+ script: boardingScriptHex,
9544
+ address: baselineBoarding.address(this.network.hrp, serverPubKey).encode(),
8708
9545
  state: "active"
8709
9546
  });
8710
- if (this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
8711
- const delegateScript = new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
8712
- pubKey: baselinePubkey,
8713
- serverPubKey: this.offchainTapscript.options.serverPubKey,
8714
- delegatePubKey: this.offchainTapscript.options.delegatePubKey,
8715
- csvTimelock
8716
- });
8717
- const delegateScriptHex = base.hex.encode(delegateScript.pkScript);
8718
- await manager.createContract({
8719
- type: "delegate",
8720
- params: {
8721
- pubKey: base.hex.encode(delegateScript.options.pubKey),
8722
- serverPubKey: base.hex.encode(delegateScript.options.serverPubKey),
8723
- delegatePubKey: base.hex.encode(delegateScript.options.delegatePubKey),
8724
- csvTimelock: csvTimelockStr
8725
- },
8726
- script: delegateScriptHex,
8727
- address: delegateScript.address(this.network.hrp, this.arkServerPublicKey).encode(),
8728
- state: "active"
8729
- });
8730
- }
8731
9547
  }
8732
- const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
8733
- const baselineBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
8734
- pubKey: baselinePubkey,
8735
- serverPubKey: this.boardingTapscript.options.serverPubKey,
8736
- csvTimelock: boardingCsvTimelock
8737
- });
8738
- await ensureWalletContract(manager, {
8739
- type: "boarding",
8740
- params: {
8741
- pubKey: base.hex.encode(baselineBoarding.options.pubKey),
8742
- serverPubKey: base.hex.encode(baselineBoarding.options.serverPubKey),
8743
- csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsvTimelock).toString()
8744
- },
8745
- script: base.hex.encode(baselineBoarding.pkScript),
8746
- address: baselineBoarding.address(this.network.hrp, this.arkServerPublicKey).encode(),
8747
- state: "active"
8748
- });
8749
9548
  return manager;
8750
9549
  }
8751
9550
  /** Dispose wallet-owned managers and release background resources. */
@@ -8778,7 +9577,6 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8778
9577
  walletContractTimelocks
8779
9578
  );
8780
9579
  this.arkProvider = arkProvider;
8781
- this.serverUnrollScript = serverUnrollScript;
8782
9580
  this.forfeitOutputScript = forfeitOutputScript;
8783
9581
  this.forfeitPubkey = forfeitPubkey;
8784
9582
  this.identity = identity;
@@ -8799,6 +9597,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8799
9597
  this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
8800
9598
  }
8801
9599
  this._delegateManager = delegateProvider ? new DelegateManagerImpl(delegateProvider, arkProvider, identity) : void 0;
9600
+ this._serverUnrollScript = serverUnrollScript;
8802
9601
  this._receiveRotator = receiveRotator;
8803
9602
  this._descriptorProvider = descriptorProvider;
8804
9603
  this._signerRouter = new InputSignerRouter({
@@ -8824,6 +9623,43 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8824
9623
  * the contract manager is up first.
8825
9624
  */
8826
9625
  _receiveRotator;
9626
+ /**
9627
+ * Unsubscribe handle for the arkProvider's `onServerInfoChanged` stream
9628
+ * (mid-session signer-rotation detection). Torn down in {@link dispose}.
9629
+ */
9630
+ _serverInfoUnsub;
9631
+ /**
9632
+ * Tail of the serialized {@link handleServerInfoChanged} chain. Each
9633
+ * `onServerInfoChanged` event chains onto it so handlers run one at a time,
9634
+ * and {@link dispose} awaits it so an in-flight re-derive/rotation settles
9635
+ * before the contract manager is torn down underneath it.
9636
+ */
9637
+ _serverInfoInFlight = Promise.resolve();
9638
+ /**
9639
+ * React to a mid-session server-info change (driven by the arkProvider's
9640
+ * `DIGEST_MISMATCH` detection). First refresh the cached deprecated-signer
9641
+ * set so the boarding WATCH path immediately widens to the just-deprecated
9642
+ * signer, then — only if the active signer actually changed — rotate the
9643
+ * wallet onto it via {@link rotateServerSigner} (re-deriving the offchain +
9644
+ * boarding display tapscripts and registering the current-signer rows).
9645
+ * Old-signer rows stay active, so existing funds remain watched. Failures
9646
+ * are logged, never thrown back into the provider's emit loop.
9647
+ */
9648
+ async handleServerInfoChanged(info) {
9649
+ this.refreshDeprecatedSigners(info);
9650
+ try {
9651
+ const newActive = toXOnlySignerHex(info.signerPubkey);
9652
+ const current = toXOnlySignerHex(base.hex.encode(this.arkServerPublicKey));
9653
+ if (newActive !== current) {
9654
+ await this.rotateServerSigner(
9655
+ base.hex.decode(info.signerPubkey),
9656
+ info.checkpointTapscript
9657
+ );
9658
+ }
9659
+ } catch (e) {
9660
+ console.warn("server-signer rotation on info change failed", e);
9661
+ }
9662
+ }
8827
9663
  _receiveRotatorInstalled = false;
8828
9664
  /**
8829
9665
  * Descriptor-aware signer used by {@link _signerRouter} to sign
@@ -8853,6 +9689,44 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8853
9689
  this._boardingTapscript = tapscript;
8854
9690
  this.notifyBoardingRotation();
8855
9691
  }
9692
+ /**
9693
+ * @internal Sole write path for `arkServerPublicKey` after construction.
9694
+ * Called by {@link Wallet.rotateServerSigner} once the rotated offchain and
9695
+ * boarding contract rows have been persisted. External code must treat
9696
+ * `arkServerPublicKey` as read-only.
9697
+ */
9698
+ setArkServerPublicKeyForRotation(serverPubKey) {
9699
+ this._arkServerPublicKey = serverPubKey;
9700
+ }
9701
+ /**
9702
+ * Output script for checkpoint transactions, decoded from the server's
9703
+ * `checkpointTapscript`. Server-controlled state: pinned at construction
9704
+ * and re-sourced from a fresh `ArkInfo` on server-signer rotation. Read it
9705
+ * through {@link serverUnrollScript}; write it only through
9706
+ * {@link setServerUnrollScriptForRotation}.
9707
+ */
9708
+ _serverUnrollScript;
9709
+ get serverUnrollScript() {
9710
+ return this._serverUnrollScript;
9711
+ }
9712
+ /**
9713
+ * @internal Sole write path for `serverUnrollScript` after construction.
9714
+ * Called by {@link Wallet._doRotateServerSigner} with the checkpoint script
9715
+ * sourced from the fresh `ArkInfo` that triggered the rotation, so the send
9716
+ * path builds checkpoints against the new server epoch. External code must
9717
+ * treat `serverUnrollScript` as read-only.
9718
+ */
9719
+ setServerUnrollScriptForRotation(script) {
9720
+ this._serverUnrollScript = script;
9721
+ }
9722
+ /**
9723
+ * Serializes {@link rotateServerSigner} for static / non-HD wallets (which
9724
+ * have no {@link WalletReceiveRotator} chain to ride). Coalesces concurrent
9725
+ * migration passes so two callers cannot both rebuild and swap the
9726
+ * tapscripts. HD wallets serialize on the rotator's chain instead, via
9727
+ * {@link WalletReceiveRotator.runExclusive}.
9728
+ */
9729
+ _serverRotationChain = Promise.resolve();
8856
9730
  /**
8857
9731
  * Allocate and return a *fresh* on-chain boarding address, rotating the
8858
9732
  * wallet's current boarding tapscript to a new HD index.
@@ -8909,6 +9783,126 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
8909
9783
  this.setBoardingTapscriptForRotation(newBoarding);
8910
9784
  return newBoarding.onchainAddress(this.network);
8911
9785
  }
9786
+ /**
9787
+ * Mid-session server-signer rotation (plan §4). When arkd rotates its
9788
+ * active signer mid-session — the case the long-lived service worker and
9789
+ * Expo background processes that own automatic migration must handle — a
9790
+ * wallet constructed before the rotation keeps deriving old-signer receive
9791
+ * addresses. Building a migration output to such an address would produce a
9792
+ * VTXO the server must reject, so the wallet must first re-derive its own
9793
+ * receive state under the new active signer.
9794
+ *
9795
+ * Follows the {@link WalletReceiveRotator.rotate} write-path pattern with
9796
+ * the server key swapped instead of the user key: build the new offchain
9797
+ * and boarding tapscripts locally (preserving every other option),
9798
+ * register the matching `default`/`delegate` and `boarding` contract rows
9799
+ * through {@link ContractManager.createContract}, and only then commit the
9800
+ * new tapscripts and server key to the wallet's visible state. The signing
9801
+ * metadata of the current receive/boarding rows is carried onto the new
9802
+ * rows so a rotated (descriptor-backed) receive pubkey can still sign.
9803
+ *
9804
+ * The old-signer contract rows are intentionally left `active` and watched
9805
+ * — they are exactly the deprecated-signer contracts the migration pass
9806
+ * drains. Idempotent: a no-op when the wallet already tracks `xonly`.
9807
+ *
9808
+ * Serialized against HD receive rotation so the two paths (both of which
9809
+ * rebuild and swap `offchainTapscript`) cannot interleave.
9810
+ *
9811
+ * @internal Invoked by the {@link VtxoManager} migration pass; not part of
9812
+ * the stable public API.
9813
+ */
9814
+ async rotateServerSigner(newServerPubKey, checkpointTapscript) {
9815
+ const xonly = toXOnlyPubKey(newServerPubKey);
9816
+ let newServerUnrollScript;
9817
+ try {
9818
+ newServerUnrollScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(base.hex.decode(checkpointTapscript));
9819
+ } catch (e) {
9820
+ throw new Error("Invalid checkpointTapscript from server");
9821
+ }
9822
+ if (utils_js.equalBytes(xonly, this.arkServerPublicKey)) return;
9823
+ if (this._receiveRotator) {
9824
+ await this._receiveRotator.runExclusive(
9825
+ () => this._doRotateServerSigner(xonly, newServerUnrollScript)
9826
+ );
9827
+ return;
9828
+ }
9829
+ const run = this._serverRotationChain.catch(() => void 0).then(() => this._doRotateServerSigner(xonly, newServerUnrollScript));
9830
+ this._serverRotationChain = run.then(
9831
+ () => void 0,
9832
+ () => void 0
9833
+ );
9834
+ return run;
9835
+ }
9836
+ async _doRotateServerSigner(xonly, newServerUnrollScript) {
9837
+ if (utils_js.equalBytes(xonly, this.arkServerPublicKey)) return;
9838
+ const manager = await this.getContractManager();
9839
+ const [currentOffchainRow] = await manager.getContracts({
9840
+ script: this.defaultContractScript
9841
+ });
9842
+ const currentBoardingScript = base.hex.encode(this._boardingTapscript.pkScript);
9843
+ const [currentBoardingRow] = await manager.getContracts({
9844
+ script: currentBoardingScript
9845
+ });
9846
+ const newOffchain = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
9847
+ ...this.offchainTapscript.options,
9848
+ serverPubKey: xonly
9849
+ }) : new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9850
+ ...this.offchainTapscript.options,
9851
+ serverPubKey: xonly
9852
+ });
9853
+ const newBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
9854
+ ...this._boardingTapscript.options,
9855
+ serverPubKey: xonly
9856
+ });
9857
+ const offchainCsv = chunkCMPJR3HS_cjs.timelockToSequence(newOffchain.options.csvTimelock).toString();
9858
+ const newOffchainScript = base.hex.encode(newOffchain.pkScript);
9859
+ const newOffchainAddress = newOffchain.address(this.network.hrp, xonly).encode();
9860
+ if (newOffchain instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
9861
+ await manager.createContract({
9862
+ type: "delegate",
9863
+ params: {
9864
+ pubKey: base.hex.encode(newOffchain.options.pubKey),
9865
+ serverPubKey: base.hex.encode(xonly),
9866
+ delegatePubKey: base.hex.encode(newOffchain.options.delegatePubKey),
9867
+ csvTimelock: offchainCsv
9868
+ },
9869
+ script: newOffchainScript,
9870
+ address: newOffchainAddress,
9871
+ state: "active",
9872
+ metadata: currentOffchainRow?.metadata
9873
+ });
9874
+ } else {
9875
+ await manager.createContract({
9876
+ type: "default",
9877
+ params: {
9878
+ pubKey: base.hex.encode(newOffchain.options.pubKey),
9879
+ serverPubKey: base.hex.encode(xonly),
9880
+ csvTimelock: offchainCsv
9881
+ },
9882
+ script: newOffchainScript,
9883
+ address: newOffchainAddress,
9884
+ state: "active",
9885
+ metadata: currentOffchainRow?.metadata
9886
+ });
9887
+ }
9888
+ const boardingCsv = newBoarding.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
9889
+ await manager.createContract({
9890
+ type: "boarding",
9891
+ params: {
9892
+ pubKey: base.hex.encode(newBoarding.options.pubKey),
9893
+ serverPubKey: base.hex.encode(xonly),
9894
+ csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsv).toString()
9895
+ },
9896
+ script: base.hex.encode(newBoarding.pkScript),
9897
+ address: newBoarding.address(this.network.hrp, xonly).encode(),
9898
+ state: "active",
9899
+ metadata: currentBoardingRow?.metadata
9900
+ });
9901
+ this.setOffchainTapscriptForRotation(newOffchain);
9902
+ this.setBoardingTapscriptForRotation(newBoarding);
9903
+ this.setArkServerPublicKeyForRotation(xonly);
9904
+ this.setServerUnrollScriptForRotation(newServerUnrollScript);
9905
+ }
8912
9906
  /**
8913
9907
  * Async mutex that serializes all operations submitting VTXOs to the Arkade
8914
9908
  * server (`settle`, `send`, `sendBitcoin`). This prevents VtxoManager's
@@ -9069,6 +10063,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9069
10063
  }
9070
10064
  async dispose() {
9071
10065
  await this._restoreInFlight?.catch(() => void 0);
10066
+ this._serverInfoUnsub?.();
10067
+ this._serverInfoUnsub = void 0;
10068
+ await this._serverInfoInFlight?.catch(() => void 0);
9072
10069
  let rotatorError;
9073
10070
  try {
9074
10071
  await this._receiveRotator?.dispose();
@@ -9143,6 +10140,15 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9143
10140
  boot?.rotator,
9144
10141
  boot?.provider
9145
10142
  );
10143
+ wallet.refreshDeprecatedSigners(setup.info);
10144
+ {
10145
+ const ap = setup.arkProvider;
10146
+ if (typeof ap.onServerInfoChanged === "function") {
10147
+ wallet._serverInfoUnsub = ap.onServerInfoChanged((info) => {
10148
+ wallet._serverInfoInFlight = wallet._serverInfoInFlight.then(() => wallet.handleServerInfoChanged(info)).catch(() => void 0);
10149
+ });
10150
+ }
10151
+ }
9146
10152
  if (boot?.provider) {
9147
10153
  const resolvedBoarding = await resolveBoardingBootTapscript(
9148
10154
  setup.contractRepository,
@@ -9175,7 +10181,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9175
10181
  */
9176
10182
  async toReadonly() {
9177
10183
  const readonlyIdentity = hasToReadonly(this.identity) ? await this.identity.toReadonly() : this.identity;
9178
- return new ReadonlyWallet(
10184
+ const readonly = new ReadonlyWallet(
9179
10185
  readonlyIdentity,
9180
10186
  this.network,
9181
10187
  this.onchainProvider,
@@ -9190,6 +10196,8 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9190
10196
  this.watcherConfig,
9191
10197
  this.walletContractTimelocks
9192
10198
  );
10199
+ readonly._deprecatedSigners = new Map(this._deprecatedSigners);
10200
+ return readonly;
9193
10201
  }
9194
10202
  /** Returns the delegate manager when delegation support is configured. */
9195
10203
  async getDelegateManager() {
@@ -9215,10 +10223,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9215
10223
  if (params.selectedVtxos && params.selectedVtxos.length > 0) {
9216
10224
  return this._withTxLock(async () => {
9217
10225
  const offchainTapscript = this.offchainTapscript;
9218
- const arkAddress = offchainTapscript.address(
9219
- this.network.hrp,
9220
- this.arkServerPublicKey
9221
- );
10226
+ const serverPubKey = this.arkServerPublicKey;
10227
+ const serverUnrollScript = this.serverUnrollScript;
10228
+ const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
9222
10229
  const selectedVtxoSum = params.selectedVtxos.map((v) => v.value).reduce((a, b) => a + b, 0);
9223
10230
  if (selectedVtxoSum < params.amount) {
9224
10231
  throw new Error("Selected VTXOs do not cover specified amount");
@@ -9243,25 +10250,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9243
10250
  amount: BigInt(selected.changeAmount)
9244
10251
  });
9245
10252
  }
9246
- this._addPendingSpends(selected.inputs);
9247
- try {
9248
- const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(
9249
- selected.inputs,
9250
- outputs
9251
- );
9252
- await this.updateDbAfterOffchainTx(
9253
- selected.inputs,
9254
- arkTxid,
9255
- signedCheckpointTxs,
9256
- params.amount,
9257
- selected.changeAmount,
9258
- selected.changeAmount > 0n ? outputs.length - 1 : 0,
9259
- offchainTapscript
9260
- );
9261
- return arkTxid;
9262
- } finally {
9263
- this._removePendingSpends(selected.inputs);
9264
- }
10253
+ return this._submitOffchainSpend(selected.inputs, outputs, {
10254
+ sentAmount: params.amount,
10255
+ changeAmount: selected.changeAmount,
10256
+ changeVout: selected.changeAmount > 0n ? outputs.length - 1 : 0,
10257
+ offchainTapscript,
10258
+ serverPubKey,
10259
+ serverUnrollScript
10260
+ });
9265
10261
  });
9266
10262
  }
9267
10263
  return this.send({
@@ -9291,8 +10287,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9291
10287
  }
9292
10288
  }
9293
10289
  }
10290
+ const offchainAddress = await this.getAddress();
10291
+ const offchainPkScript = chunkCMPJR3HS_cjs.ArkAddress.decode(offchainAddress).pkScript;
10292
+ const offchainOutputScript = base.hex.encode(offchainPkScript);
9294
10293
  if (!params) {
9295
- const { fees } = await this.arkProvider.getInfo();
10294
+ const { fees, vtxoMaxAmount } = await this.arkProvider.getInfo();
9296
10295
  const estimator = new Estimator(fees.intentFee);
9297
10296
  let amount = 0;
9298
10297
  const exitScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(
@@ -9334,20 +10333,31 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9334
10333
  if (inputFee.satoshis >= vtxo.value) {
9335
10334
  continue;
9336
10335
  }
10336
+ const net = vtxo.value - inputFee.satoshis;
10337
+ if (vtxoMaxAmount >= 0n) {
10338
+ const projectedAmount = BigInt(amount + net);
10339
+ const projectedOutputFee = estimator.evalOffchainOutput({
10340
+ amount: projectedAmount,
10341
+ script: offchainOutputScript
10342
+ });
10343
+ if (projectedAmount - BigInt(projectedOutputFee.satoshis) > vtxoMaxAmount) {
10344
+ continue;
10345
+ }
10346
+ }
9337
10347
  filteredVtxos.push(vtxo);
9338
- amount += vtxo.value - inputFee.satoshis;
10348
+ amount += net;
9339
10349
  }
9340
10350
  const inputs = [...filteredBoardingUtxos, ...filteredVtxos];
9341
10351
  if (inputs.length === 0) {
9342
10352
  throw new Error("No inputs found");
9343
10353
  }
9344
10354
  const output = {
9345
- address: await this.getAddress(),
10355
+ address: offchainAddress,
9346
10356
  amount: BigInt(amount)
9347
10357
  };
9348
10358
  const outputFee = estimator.evalOffchainOutput({
9349
10359
  amount: output.amount,
9350
- script: base.hex.encode(chunkCMPJR3HS_cjs.ArkAddress.decode(output.address).pkScript)
10360
+ script: offchainOutputScript
9351
10361
  });
9352
10362
  output.amount -= BigInt(outputFee.satoshis);
9353
10363
  if (output.amount <= this.dustAmount) {
@@ -9387,8 +10397,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9387
10397
  }
9388
10398
  }
9389
10399
  let outputAssets;
9390
- const destinationScript = chunkCMPJR3HS_cjs.ArkAddress.decode(await this.getAddress()).pkScript;
9391
- const assetOutputIndex = findDestinationOutputIndex(outputs, destinationScript);
10400
+ const assetOutputIndex = findDestinationOutputIndex(outputs, offchainPkScript);
9392
10401
  if (assetInputs.size > 0) {
9393
10402
  if (assetOutputIndex === -1) {
9394
10403
  throw new Error("Cannot assign assets: no output matches the destination address");
@@ -9736,7 +10745,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9736
10745
  try {
9737
10746
  return await this.arkProvider.registerIntent(intent);
9738
10747
  } catch (error) {
9739
- if (error instanceof chunkFSAXPBGP_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
10748
+ if (error instanceof chunkC6OODRWD_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
9740
10749
  const deleteIntent = await this.makeDeleteIntentSignature(inputs);
9741
10750
  await this.arkProvider.deleteIntent(deleteIntent);
9742
10751
  return this.arkProvider.registerIntent(intent);
@@ -9752,7 +10761,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9752
10761
  expire_at: 0,
9753
10762
  cosigners_public_keys: cosignerPubKeys
9754
10763
  };
9755
- const proof = chunkFSAXPBGP_cjs.Intent.create(message, coins, outputs);
10764
+ const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, outputs);
9756
10765
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
9757
10766
  return {
9758
10767
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -9764,7 +10773,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9764
10773
  type: "delete",
9765
10774
  expire_at: 0
9766
10775
  };
9767
- const proof = chunkFSAXPBGP_cjs.Intent.create(message, coins, []);
10776
+ const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, []);
9768
10777
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
9769
10778
  return {
9770
10779
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -9776,7 +10785,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9776
10785
  type: "get-pending-tx",
9777
10786
  expire_at: 0
9778
10787
  };
9779
- const proof = chunkFSAXPBGP_cjs.Intent.create(message, coins, []);
10788
+ const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, []);
9780
10789
  const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
9781
10790
  return {
9782
10791
  proof: base.base64.encode(signedProof.toPSBT()),
@@ -9927,12 +10936,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
9927
10936
  throw new Error("At least one receiver is required");
9928
10937
  }
9929
10938
  const offchainTapscript = this.offchainTapscript;
9930
- const outputAddress = offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
10939
+ const serverPubKey = this.arkServerPublicKey;
10940
+ const serverUnrollScript = this.serverUnrollScript;
10941
+ const outputAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
9931
10942
  const address = outputAddress.encode();
9932
10943
  const recipients = validateRecipients(args, Number(this.dustAmount));
9933
- const virtualCoins = await this.getVtxos({
10944
+ const allVirtualCoins = await this.getVtxos({
9934
10945
  withRecoverable: false
9935
10946
  });
10947
+ const pendingRecovery = await this.pendingRecoveryOutpoints();
10948
+ const virtualCoins = pendingRecovery.size ? allVirtualCoins.filter((c) => !pendingRecovery.has(`${c.txid}:${c.vout}`)) : allVirtualCoins;
9936
10949
  const assetChanges = /* @__PURE__ */ new Map();
9937
10950
  let selectedCoins = [];
9938
10951
  let btcAmountToSelect = 0;
@@ -10054,33 +11067,128 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10054
11067
  outputs.push(Extension.create([assetPacket]).txOut());
10055
11068
  }
10056
11069
  const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
10057
- this._addPendingSpends(selectedCoins);
11070
+ return this._submitOffchainSpend(selectedCoins, outputs, {
11071
+ sentAmount,
11072
+ changeAmount: BigInt(changeAmount),
11073
+ changeVout: changeReceiver ? changeIndex : 0,
11074
+ offchainTapscript,
11075
+ serverPubKey,
11076
+ serverUnrollScript,
11077
+ changeAssets: changeReceiver?.assets
11078
+ });
11079
+ }
11080
+ /**
11081
+ * Shared tail of every Ark-transaction spend path (`send`, selected-VTXO
11082
+ * `sendBitcoin`, and {@link sendSelectedVtxosToSelf}): hide the inputs from
11083
+ * concurrent `getVtxos()`, build+submit the offchain tx, persist the spent
11084
+ * inputs and any wallet-owned (change / self) output, then release the
11085
+ * pending-spend hold. Callers own coin selection, output construction, and
11086
+ * the synchronous epoch snapshot; this owns the submit/persist sequence.
11087
+ */
11088
+ async _submitOffchainSpend(inputs, outputs, persist) {
11089
+ this._addPendingSpends(inputs);
10058
11090
  try {
10059
11091
  const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(
10060
- selectedCoins,
10061
- outputs
11092
+ inputs,
11093
+ outputs,
11094
+ persist.serverUnrollScript
10062
11095
  );
10063
11096
  await this.updateDbAfterOffchainTx(
10064
- selectedCoins,
11097
+ inputs,
10065
11098
  arkTxid,
10066
11099
  signedCheckpointTxs,
10067
- sentAmount,
10068
- BigInt(changeAmount),
10069
- changeReceiver ? changeIndex : 0,
10070
- offchainTapscript,
10071
- changeReceiver?.assets
11100
+ persist.sentAmount,
11101
+ persist.changeAmount,
11102
+ persist.changeVout,
11103
+ persist.offchainTapscript,
11104
+ persist.serverPubKey,
11105
+ persist.changeAssets,
11106
+ persist.recordSentHistory ?? true
10072
11107
  );
10073
11108
  return arkTxid;
10074
11109
  } finally {
10075
- this._removePendingSpends(selectedCoins);
10076
- }
11110
+ this._removePendingSpends(inputs);
11111
+ }
11112
+ }
11113
+ /**
11114
+ * @internal Migration primitive (deprecated-signer plan, step 1). Spend an
11115
+ * explicit set of the wallet's own deprecated-signer VTXOs into a single
11116
+ * full-value output on the wallet's *active* signer, through the Ark send
11117
+ * path (not `settle`) so arkd builds checkpoints against the active server
11118
+ * epoch. Consumed in-process by {@link VtxoManager}'s migration pass; not
11119
+ * part of the public `IWallet` API and never accepts boarding `ExtendedCoin`
11120
+ * inputs.
11121
+ *
11122
+ * The caller (`migrateCore`) must have already moved the wallet onto the
11123
+ * active signer (`ensureReceiveOnActiveSigner`) and sized the batch (caps +
11124
+ * dust floor); this method validates the inputs, preserves all input assets
11125
+ * on the self output, and persists the new active-signer VTXO even though
11126
+ * there is no separate change output. It records no `TxSent` history — the
11127
+ * funds never leave the wallet.
11128
+ */
11129
+ async sendSelectedVtxosToSelf(inputs) {
11130
+ if (inputs.length === 0) {
11131
+ throw new Error("sendSelectedVtxosToSelf: no inputs");
11132
+ }
11133
+ return this._withTxLock(async () => {
11134
+ const offchainTapscript = this.offchainTapscript;
11135
+ const serverPubKey = this.arkServerPublicKey;
11136
+ const serverUnrollScript = this.serverUnrollScript;
11137
+ const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
11138
+ for (const input of inputs) {
11139
+ if (!isSpendable(input) || isRecoverable(input)) {
11140
+ throw new Error(
11141
+ `sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} is not cooperatively spendable`
11142
+ );
11143
+ }
11144
+ if (!input.virtualStatus.batchExpiry) {
11145
+ throw new Error(
11146
+ `sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} has no batchExpiry`
11147
+ );
11148
+ }
11149
+ }
11150
+ const total = inputs.reduce((sum, c) => sum + BigInt(c.value), 0n);
11151
+ const outputs = [
11152
+ {
11153
+ script: total < this.dustAmount ? arkAddress.subdustPkScript : arkAddress.pkScript,
11154
+ amount: total
11155
+ }
11156
+ ];
11157
+ const assetInputs = selectedCoinsToAssetInputs(inputs);
11158
+ let selfAssets;
11159
+ if (assetInputs.size > 0) {
11160
+ const totals = /* @__PURE__ */ new Map();
11161
+ for (const [, assets] of assetInputs) {
11162
+ for (const a of assets) {
11163
+ totals.set(a.assetId, (totals.get(a.assetId) ?? 0n) + a.amount);
11164
+ }
11165
+ }
11166
+ selfAssets = [...totals].map(([assetId, amount]) => ({ assetId, amount }));
11167
+ const selfReceiver = {
11168
+ address: arkAddress.encode(),
11169
+ assets: selfAssets
11170
+ };
11171
+ const packet = createAssetPacket(assetInputs, [], selfReceiver);
11172
+ outputs.push(Extension.create([packet]).txOut());
11173
+ }
11174
+ return this._submitOffchainSpend(inputs, outputs, {
11175
+ sentAmount: 0,
11176
+ changeAmount: total,
11177
+ changeVout: 0,
11178
+ offchainTapscript,
11179
+ serverPubKey,
11180
+ changeAssets: selfAssets,
11181
+ recordSentHistory: false,
11182
+ serverUnrollScript
11183
+ });
11184
+ });
10077
11185
  }
10078
11186
  /**
10079
11187
  * Build an offchain transaction from the given inputs and outputs,
10080
11188
  * sign it, submit to the Arkade provider, and finalize.
10081
11189
  * @returns The Arkade transaction id and server-signed checkpoint PSBTs (for bookkeeping)
10082
11190
  */
10083
- async buildAndSubmitOffchainTx(inputs, outputs) {
11191
+ async buildAndSubmitOffchainTx(inputs, outputs, serverUnrollScript = this.serverUnrollScript) {
10084
11192
  const offchainTx = buildOffchainTx(
10085
11193
  inputs.map((input) => {
10086
11194
  return {
@@ -10089,7 +11197,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10089
11197
  };
10090
11198
  }),
10091
11199
  outputs,
10092
- this.serverUnrollScript
11200
+ serverUnrollScript
10093
11201
  );
10094
11202
  const arkTxJobs = inputs.map((input, index) => ({
10095
11203
  index,
@@ -10163,14 +11271,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10163
11271
  return { arkTxid, signedCheckpointTxs };
10164
11272
  }
10165
11273
  // mark virtual outputs as spent, save change outputs if any.
10166
- // `offchainTapscript` is the snapshot the caller captured under
10167
- // `_txLock` before any `await`; deriving both the change-VTXO
10168
- // metadata and `primaryAddress` from it here guarantees the local
10169
- // record matches the pkScript the server saw on the inbound
10170
- // transaction, even if `WalletReceiveRotator.rotate` swaps
10171
- // `this.offchainTapscript` mid-flight.
10172
- async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, changeAssets) {
10173
- const primaryAddress = offchainTapscript.address(this.network.hrp, this.arkServerPublicKey).encode();
11274
+ // `offchainTapscript` and `serverPubKey` are the epoch snapshot the
11275
+ // caller captured under `_txLock` before any `await`; deriving both the
11276
+ // change-VTXO metadata and `primaryAddress` from them here guarantees the
11277
+ // local record matches the address/pkScript the server saw on the inbound
11278
+ // transaction, even if `rotateServerSigner` swaps `this.offchainTapscript`
11279
+ // / `this.arkServerPublicKey` mid-flight.
11280
+ async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, serverPubKey, changeAssets, recordSentHistory = true) {
11281
+ const primaryAddress = offchainTapscript.address(this.network.hrp, serverPubKey).encode();
10174
11282
  try {
10175
11283
  const spentVtxos = [];
10176
11284
  const commitmentTxIds = /* @__PURE__ */ new Set();
@@ -10277,19 +11385,21 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
10277
11385
  [changeVtxo]
10278
11386
  );
10279
11387
  }
10280
- await this.walletRepository.saveTransactions(primaryAddress, [
10281
- {
10282
- key: {
10283
- boardingTxid: "",
10284
- commitmentTxid: "",
10285
- arkTxid
10286
- },
10287
- amount: sentAmount,
10288
- type: "SENT" /* TxSent */,
10289
- settled: false,
10290
- createdAt
10291
- }
10292
- ]);
11388
+ if (recordSentHistory) {
11389
+ await this.walletRepository.saveTransactions(primaryAddress, [
11390
+ {
11391
+ key: {
11392
+ boardingTxid: "",
11393
+ commitmentTxid: "",
11394
+ arkTxid
11395
+ },
11396
+ amount: sentAmount,
11397
+ type: "SENT" /* TxSent */,
11398
+ settled: false,
11399
+ createdAt
11400
+ }
11401
+ ]);
11402
+ }
10293
11403
  } catch (e) {
10294
11404
  console.warn("error saving offchain tx to repository", e);
10295
11405
  throw e;
@@ -10580,7 +11690,7 @@ var MessageBus = class {
10580
11690
  this.initialized = true;
10581
11691
  }
10582
11692
  async buildServices(config) {
10583
- const arkProvider = new chunkFSAXPBGP_cjs.RestArkProvider(config.arkServer.url);
11693
+ const arkProvider = new chunkC6OODRWD_cjs.RestArkProvider(config.arkServer.url);
10584
11694
  const storage = {
10585
11695
  walletRepository: this.walletRepository,
10586
11696
  contractRepository: this.contractRepository
@@ -11147,6 +12257,82 @@ var DelegateNotConfiguredError = class extends Error {
11147
12257
  };
11148
12258
  var DelegatorNotConfiguredError = DelegateNotConfiguredError;
11149
12259
  var DEFAULT_MESSAGE_TAG = "WALLET_UPDATER";
12260
+ var serializeMigrationVtxoRef = (ref) => ({
12261
+ txid: ref.txid,
12262
+ vout: ref.vout,
12263
+ value: ref.value,
12264
+ signerPubKey: ref.signerPubKey,
12265
+ cutoffDate: ref.cutoffDate?.toString()
12266
+ });
12267
+ var deserializeMigrationVtxoRef = (ref) => ({
12268
+ txid: ref.txid,
12269
+ vout: ref.vout,
12270
+ value: ref.value,
12271
+ signerPubKey: ref.signerPubKey,
12272
+ cutoffDate: ref.cutoffDate != null ? BigInt(ref.cutoffDate) : void 0
12273
+ });
12274
+ var serializeDeprecatedSignerReport = (report) => ({
12275
+ signerPubKey: report.signerPubKey,
12276
+ status: report.status,
12277
+ cutoffDate: report.cutoffDate?.toString(),
12278
+ secondsUntilCutoff: report.secondsUntilCutoff,
12279
+ vtxoCount: report.vtxoCount,
12280
+ totalValue: report.totalValue,
12281
+ boardingCount: report.boardingCount,
12282
+ boardingValue: report.boardingValue,
12283
+ recoverableCount: report.recoverableCount,
12284
+ recoverableValue: report.recoverableValue,
12285
+ awaitingSweepCount: report.awaitingSweepCount,
12286
+ awaitingSweepValue: report.awaitingSweepValue,
12287
+ nextSweepEta: report.nextSweepEta
12288
+ });
12289
+ var deserializeDeprecatedSignerReport = (report) => ({
12290
+ signerPubKey: report.signerPubKey,
12291
+ status: report.status,
12292
+ cutoffDate: report.cutoffDate != null ? BigInt(report.cutoffDate) : void 0,
12293
+ secondsUntilCutoff: report.secondsUntilCutoff,
12294
+ vtxoCount: report.vtxoCount,
12295
+ totalValue: report.totalValue,
12296
+ boardingCount: report.boardingCount,
12297
+ boardingValue: report.boardingValue,
12298
+ recoverableCount: report.recoverableCount,
12299
+ recoverableValue: report.recoverableValue,
12300
+ awaitingSweepCount: report.awaitingSweepCount,
12301
+ awaitingSweepValue: report.awaitingSweepValue,
12302
+ nextSweepEta: report.nextSweepEta
12303
+ });
12304
+ var serializeMigrationLegReport = (leg) => ({
12305
+ txid: leg.txid,
12306
+ migrated: leg.migrated.map(serializeMigrationVtxoRef),
12307
+ skipped: leg.skipped,
12308
+ deferred: leg.deferred,
12309
+ oversized: leg.oversized?.map(serializeMigrationVtxoRef),
12310
+ error: leg.error
12311
+ });
12312
+ var deserializeMigrationLegReport = (leg) => ({
12313
+ txid: leg.txid,
12314
+ migrated: leg.migrated.map(deserializeMigrationVtxoRef),
12315
+ skipped: leg.skipped,
12316
+ deferred: leg.deferred,
12317
+ oversized: leg.oversized?.map(deserializeMigrationVtxoRef),
12318
+ error: leg.error
12319
+ });
12320
+ var serializeMigrationReport = (report) => ({
12321
+ rotated: report.rotated,
12322
+ skipped: report.skipped,
12323
+ vtxos: report.vtxos ? serializeMigrationLegReport(report.vtxos) : void 0,
12324
+ boarding: report.boarding ? serializeMigrationLegReport(report.boarding) : void 0,
12325
+ expired: report.expired.map(serializeMigrationVtxoRef),
12326
+ signers: report.signers.map(serializeDeprecatedSignerReport)
12327
+ });
12328
+ var deserializeMigrationReport = (report) => ({
12329
+ rotated: report.rotated,
12330
+ skipped: report.skipped,
12331
+ vtxos: report.vtxos ? deserializeMigrationLegReport(report.vtxos) : void 0,
12332
+ boarding: report.boarding ? deserializeMigrationLegReport(report.boarding) : void 0,
12333
+ expired: report.expired.map(deserializeMigrationVtxoRef),
12334
+ signers: report.signers.map(deserializeDeprecatedSignerReport)
12335
+ });
11150
12336
  var WalletMessageHandler = class {
11151
12337
  messageTag;
11152
12338
  wallet;
@@ -11228,7 +12414,9 @@ var WalletMessageHandler = class {
11228
12414
  // page-side PING / MESSAGE_BUS_NOT_INITIALIZED path triggered by concurrent
11229
12415
  // short requests (GET_STATUS, GET_BALANCE, ...).
11230
12416
  isLongRunning(message) {
11231
- return message.type === "SETTLE" || message.type === "RECOVER_VTXOS" || message.type === "RENEW_VTXOS" || // HD restore walks the index range with one indexer round-trip per
12417
+ return message.type === "SETTLE" || message.type === "RECOVER_VTXOS" || message.type === "RENEW_VTXOS" || // Migration may apply a server-signer rotation and then run a full
12418
+ // settle, so it streams settlement events like RENEW_VTXOS.
12419
+ message.type === "MIGRATE_DEPRECATED_SIGNER_VTXOS" || // HD restore walks the index range with one indexer round-trip per
11232
12420
  // step until it hits gapLimit consecutive unused indices. The bus
11233
12421
  // deadline must not race the scan; liveness stays covered by PING.
11234
12422
  message.type === "RESTORE_WALLET";
@@ -11594,6 +12782,36 @@ var WalletMessageHandler = class {
11594
12782
  payload: { txid }
11595
12783
  });
11596
12784
  }
12785
+ case "MIGRATE_DEPRECATED_SIGNER_VTXOS": {
12786
+ const wallet = this.requireWallet();
12787
+ const vtxoManager = await wallet.getVtxoManager();
12788
+ const report = await vtxoManager.migrateDeprecatedSignerVtxos({
12789
+ eventCallback: (e) => {
12790
+ this.scheduleForNextTick(
12791
+ () => this.tagged({
12792
+ id,
12793
+ type: "MIGRATE_DEPRECATED_SIGNER_VTXOS_EVENT",
12794
+ payload: e
12795
+ })
12796
+ );
12797
+ }
12798
+ });
12799
+ return this.tagged({
12800
+ id,
12801
+ type: "MIGRATE_DEPRECATED_SIGNER_VTXOS_SUCCESS",
12802
+ payload: { report: serializeMigrationReport(report) }
12803
+ });
12804
+ }
12805
+ case "GET_DEPRECATED_SIGNER_STATUS": {
12806
+ const wallet = this.requireWallet();
12807
+ const vtxoManager = await wallet.getVtxoManager();
12808
+ const signers = await vtxoManager.getDeprecatedSignerStatus();
12809
+ return this.tagged({
12810
+ id,
12811
+ type: "DEPRECATED_SIGNER_STATUS",
12812
+ payload: { signers: signers.map(serializeDeprecatedSignerReport) }
12813
+ });
12814
+ }
11597
12815
  case "RESTORE_WALLET": {
11598
12816
  const wallet = this.requireWallet();
11599
12817
  try {
@@ -11623,13 +12841,14 @@ var WalletMessageHandler = class {
11623
12841
  // Wallet methods
11624
12842
  async handleInitWallet({ payload }) {
11625
12843
  const { arkServerUrl } = payload;
11626
- this.indexerProvider = new chunkFSAXPBGP_cjs.RestIndexerProvider(arkServerUrl);
12844
+ this.indexerProvider = new chunkC6OODRWD_cjs.RestIndexerProvider(arkServerUrl);
11627
12845
  await this.onWalletInitialized();
11628
12846
  }
11629
12847
  async handleGetBalance() {
11630
- const [boardingUtxos, allVtxos] = await Promise.all([
12848
+ const [boardingUtxos, allVtxos, pendingOutpoints] = await Promise.all([
11631
12849
  this.getAllBoardingUtxos(),
11632
- this.getVtxosFromRepo()
12850
+ this.getVtxosFromRepo(),
12851
+ this.readonlyWallet ? this.readonlyWallet.pendingRecoveryOutpoints() : Promise.resolve(/* @__PURE__ */ new Set())
11633
12852
  ]);
11634
12853
  let confirmed = 0;
11635
12854
  let unconfirmed = 0;
@@ -11645,8 +12864,11 @@ var WalletMessageHandler = class {
11645
12864
  let settled = 0;
11646
12865
  let preconfirmed = 0;
11647
12866
  let recoverable = 0;
12867
+ let pendingRecovery = 0;
11648
12868
  for (const vtxo of spendableVtxos) {
11649
- if (vtxo.virtualStatus.state === "settled") {
12869
+ if (pendingOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
12870
+ pendingRecovery += vtxo.value;
12871
+ } else if (vtxo.virtualStatus.state === "settled") {
11650
12872
  settled += vtxo.value;
11651
12873
  } else if (vtxo.virtualStatus.state === "preconfirmed") {
11652
12874
  preconfirmed += vtxo.value;
@@ -11658,7 +12880,7 @@ var WalletMessageHandler = class {
11658
12880
  }
11659
12881
  }
11660
12882
  const totalBoarding = confirmed + unconfirmed;
11661
- const totalOffchain = settled + preconfirmed + recoverable;
12883
+ const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
11662
12884
  const assetBalances = /* @__PURE__ */ new Map();
11663
12885
  for (const vtxo of spendableVtxos) {
11664
12886
  if (vtxo.assets) {
@@ -11682,6 +12904,7 @@ var WalletMessageHandler = class {
11682
12904
  preconfirmed,
11683
12905
  available: settled + preconfirmed,
11684
12906
  recoverable,
12907
+ pendingRecovery,
11685
12908
  total: totalBoarding + totalOffchain,
11686
12909
  assets
11687
12910
  };
@@ -12067,6 +13290,7 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
12067
13290
  GET_EXPIRING_VTXOS: 2e4,
12068
13291
  GET_EXPIRED_BOARDING_UTXOS: 2e4,
12069
13292
  GET_RECOVERABLE_BALANCE: 2e4,
13293
+ GET_DEPRECATED_SIGNER_STATUS: 2e4,
12070
13294
  RELOAD_WALLET: 2e4,
12071
13295
  // Transactions — need more headroom.
12072
13296
  // SETTLE / RECOVER_VTXOS / RENEW_VTXOS go through the streaming path and
@@ -12082,6 +13306,9 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
12082
13306
  RECOVER_VTXOS: 5e4,
12083
13307
  RENEW_VTXOS: 5e4,
12084
13308
  SWEEP_EXPIRED_BOARDING_UTXOS: 5e4,
13309
+ // Streaming/long-running like RENEW_VTXOS (rotation + settle); the value is
13310
+ // kept for type completeness and is never enforced as an inactivity deadline.
13311
+ MIGRATE_DEPRECATED_SIGNER_VTXOS: 5e4,
12085
13312
  // RESTORE_WALLET is a streaming/long-running path (sendMessageWithEvents)
12086
13313
  // like SETTLE; the value here is kept for type completeness and is never
12087
13314
  // enforced as an inactivity deadline.
@@ -12107,6 +13334,7 @@ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
12107
13334
  "GET_DELEGATE_INFO",
12108
13335
  "GET_RECOVERABLE_BALANCE",
12109
13336
  "GET_EXPIRED_BOARDING_UTXOS",
13337
+ "GET_DEPRECATED_SIGNER_STATUS",
12110
13338
  "GET_VTXOS",
12111
13339
  "GET_CONTRACTS",
12112
13340
  "GET_CONTRACTS_WITH_VTXOS",
@@ -13229,6 +14457,42 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
13229
14457
  throw new Error(`Failed to sweep expired boarding utxos: ${e}`);
13230
14458
  }
13231
14459
  },
14460
+ async migrateDeprecatedSignerVtxos(options) {
14461
+ const message = {
14462
+ tag: messageTag,
14463
+ type: "MIGRATE_DEPRECATED_SIGNER_VTXOS",
14464
+ id: getRandomId()
14465
+ };
14466
+ try {
14467
+ const response = await wallet.sendMessageWithEvents(
14468
+ message,
14469
+ (resp) => options?.eventCallback?.(
14470
+ resp.payload
14471
+ ),
14472
+ (resp) => resp.type === "MIGRATE_DEPRECATED_SIGNER_VTXOS_SUCCESS"
14473
+ );
14474
+ return deserializeMigrationReport(
14475
+ response.payload.report
14476
+ );
14477
+ } catch (e) {
14478
+ throw new Error(`Failed to migrate deprecated-signer vtxos: ${e}`);
14479
+ }
14480
+ },
14481
+ async getDeprecatedSignerStatus() {
14482
+ const message = {
14483
+ tag: messageTag,
14484
+ type: "GET_DEPRECATED_SIGNER_STATUS",
14485
+ id: getRandomId()
14486
+ };
14487
+ try {
14488
+ const response = await wallet.sendMessage(message);
14489
+ return response.payload.signers.map(
14490
+ deserializeDeprecatedSignerReport
14491
+ );
14492
+ } catch (e) {
14493
+ throw new Error(`Failed to get deprecated-signer status: ${e}`);
14494
+ }
14495
+ },
13232
14496
  async dispose() {
13233
14497
  return;
13234
14498
  }
@@ -13368,7 +14632,7 @@ var OnchainWallet = class _OnchainWallet {
13368
14632
  if (!inputs) {
13369
14633
  throw new Error("Fee estimation failed");
13370
14634
  }
13371
- let tx = new chunkFSAXPBGP_cjs.Transaction();
14635
+ let tx = new chunkC6OODRWD_cjs.Transaction();
13372
14636
  for (const input of inputs) {
13373
14637
  tx.addInput({
13374
14638
  txid: input.txid,
@@ -13399,7 +14663,7 @@ var OnchainWallet = class _OnchainWallet {
13399
14663
  */
13400
14664
  async bumpP2A(parent) {
13401
14665
  const parentVsize = parent.vsize;
13402
- let child = new chunkFSAXPBGP_cjs.Transaction({
14666
+ let child = new chunkC6OODRWD_cjs.Transaction({
13403
14667
  version: 3,
13404
14668
  allowLegacyWitnessUtxo: true
13405
14669
  });
@@ -14134,13 +15398,19 @@ function isHeaderSubscribeResult(v) {
14134
15398
  const obj = v;
14135
15399
  return typeof obj.height === "number" && typeof obj.hex === "string";
14136
15400
  }
15401
+ function errorText(err) {
15402
+ if (typeof err === "string") return err;
15403
+ if (err && typeof err === "object") {
15404
+ const e = err;
15405
+ return [e.message, e.str].filter((v) => typeof v === "string").join(" ");
15406
+ }
15407
+ return "";
15408
+ }
14137
15409
  function isMissingHeightError(err) {
14138
- const msg = err instanceof Error ? err.message : typeof err === "string" ? err : "";
14139
- return msg.toLowerCase().includes("missingheight");
15410
+ return errorText(err).toLowerCase().includes("missingheight");
14140
15411
  }
14141
15412
  function isTxNotInBlockError(err) {
14142
- const msg = err instanceof Error ? err.message : typeof err === "string" ? err : "";
14143
- const normalized = msg.toLowerCase();
15413
+ const normalized = errorText(err).toLowerCase();
14144
15414
  return normalized.includes("not yet in a block") || normalized.includes("not in a block") || normalized.includes("not in block") || normalized.includes("no confirmed transaction");
14145
15415
  }
14146
15416
  function childTxidFromHex(txHex) {
@@ -14156,7 +15426,7 @@ exports.BIP322 = void 0;
14156
15426
  async function sign2(message, identity, network) {
14157
15427
  const xOnlyPubKey = await identity.xOnlyPublicKey();
14158
15428
  const payment = btcSigner.p2tr(xOnlyPubKey, void 0, network);
14159
- const toSpend = chunkFSAXPBGP_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
15429
+ const toSpend = chunkC6OODRWD_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
14160
15430
  const toSign = craftBIP322ToSignP2TR(toSpend, payment.script, xOnlyPubKey);
14161
15431
  const signed = await identity.sign(toSign, [0]);
14162
15432
  signed.finalizeIdx(0);
@@ -14214,7 +15484,7 @@ function verifyP2TR(message, witnessItems, pkScript, pubkey) {
14214
15484
  if (sighashType !== btcSigner.SigHash.DEFAULT && sighashType !== btcSigner.SigHash.ALL) {
14215
15485
  return false;
14216
15486
  }
14217
- const toSpend = chunkFSAXPBGP_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
15487
+ const toSpend = chunkC6OODRWD_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
14218
15488
  const toSign = craftBIP322ToSignP2TR(toSpend, pkScript, pubkey);
14219
15489
  const sighash = toSign.preimageWitnessV1(0, [pkScript], sighashType, [0n]);
14220
15490
  const rawSig = sig.length === 65 ? sig.subarray(0, 64) : sig;
@@ -14235,7 +15505,7 @@ function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
14235
15505
  }
14236
15506
  const sighashType = sigWithHash[sigWithHash.length - 1];
14237
15507
  const derSig = sigWithHash.subarray(0, sigWithHash.length - 1);
14238
- const toSpend = chunkFSAXPBGP_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
15508
+ const toSpend = chunkC6OODRWD_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
14239
15509
  const toSign = craftBIP322ToSignSimple(toSpend, pkScript);
14240
15510
  const scriptCode = btcSigner.OutScript.encode({ type: "pkh", hash: addressHash });
14241
15511
  const sighash = toSign.preimageWitnessV0(0, scriptCode, sighashType, 0n);
@@ -14288,7 +15558,7 @@ function encodeCompactSize(n) {
14288
15558
  return buf;
14289
15559
  }
14290
15560
  function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
14291
- const tx = new chunkFSAXPBGP_cjs.Transaction({ version: 0 });
15561
+ const tx = new chunkC6OODRWD_cjs.Transaction({ version: 0 });
14292
15562
  tx.addInput({
14293
15563
  txid: toSpend.id,
14294
15564
  index: 0,
@@ -14302,12 +15572,12 @@ function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
14302
15572
  });
14303
15573
  tx.addOutput({
14304
15574
  amount: 0n,
14305
- script: chunkFSAXPBGP_cjs.OP_RETURN_EMPTY_PKSCRIPT
15575
+ script: chunkC6OODRWD_cjs.OP_RETURN_EMPTY_PKSCRIPT
14306
15576
  });
14307
15577
  return tx;
14308
15578
  }
14309
15579
  function craftBIP322ToSignSimple(toSpend, pkScript) {
14310
- const tx = new chunkFSAXPBGP_cjs.Transaction({ version: 0 });
15580
+ const tx = new chunkC6OODRWD_cjs.Transaction({ version: 0 });
14311
15581
  tx.addInput({
14312
15582
  txid: toSpend.id,
14313
15583
  index: 0,
@@ -14319,7 +15589,7 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
14319
15589
  });
14320
15590
  tx.addOutput({
14321
15591
  amount: 0n,
14322
- script: chunkFSAXPBGP_cjs.OP_RETURN_EMPTY_PKSCRIPT
15592
+ script: chunkC6OODRWD_cjs.OP_RETURN_EMPTY_PKSCRIPT
14323
15593
  });
14324
15594
  return tx;
14325
15595
  }
@@ -14380,7 +15650,7 @@ exports.Unroll = void 0;
14380
15650
  if (virtualTxs.txs.length === 0) {
14381
15651
  throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
14382
15652
  }
14383
- const tx = chunkFSAXPBGP_cjs.Transaction.fromPSBT(base.base64.decode(virtualTxs.txs[0]));
15653
+ const tx = chunkC6OODRWD_cjs.Transaction.fromPSBT(base.base64.decode(virtualTxs.txs[0]));
14384
15654
  if (nextTxToBroadcast.type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
14385
15655
  const input = tx.getInput(0);
14386
15656
  if (!input) {
@@ -14480,7 +15750,7 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
14480
15750
  btcSigner.TaprootControlBlock.encode(spendingLeaf[0]).length
14481
15751
  );
14482
15752
  }
14483
- const tx = new chunkFSAXPBGP_cjs.Transaction({ version: 2 });
15753
+ const tx = new chunkC6OODRWD_cjs.Transaction({ version: 2 });
14484
15754
  for (const input of inputs) {
14485
15755
  tx.addInput(input);
14486
15756
  }
@@ -14656,6 +15926,8 @@ exports.WalletRepositoryImpl = WalletRepositoryImpl;
14656
15926
  exports.WsElectrumChainSource = WsElectrumChainSource;
14657
15927
  exports.buildForfeitTx = buildForfeitTx;
14658
15928
  exports.buildOffchainTx = buildOffchainTx;
15929
+ exports.classifyAgainstSignerSet = classifyAgainstSignerSet;
15930
+ exports.classifyContractSigner = classifyContractSigner;
14659
15931
  exports.closeDatabase = closeDatabase;
14660
15932
  exports.combineTapscriptSigs = combineTapscriptSigs;
14661
15933
  exports.contractFromArkContract = contractFromArkContract;
@@ -14671,6 +15943,7 @@ exports.getRandomId = getRandomId;
14671
15943
  exports.hasBoardingTxExpired = hasBoardingTxExpired;
14672
15944
  exports.isArkContract = isArkContract;
14673
15945
  exports.isBatchSignable = isBatchSignable;
15946
+ exports.isCooperativelyMigratable = isCooperativelyMigratable;
14674
15947
  exports.isDiscoverable = isDiscoverable;
14675
15948
  exports.isExpired = isExpired;
14676
15949
  exports.isRecoverable = isRecoverable;
@@ -14689,10 +15962,12 @@ exports.serializeAssets = serializeAssets;
14689
15962
  exports.serializeUtxo = serializeUtxo;
14690
15963
  exports.serializeVtxo = serializeVtxo;
14691
15964
  exports.setupServiceWorker = setupServiceWorker;
15965
+ exports.signerSetFromInfo = signerSetFromInfo;
15966
+ exports.toXOnlySignerHex = toXOnlySignerHex;
14692
15967
  exports.validateConnectorsTxGraph = validateConnectorsTxGraph;
14693
15968
  exports.validateVtxoTxGraph = validateVtxoTxGraph;
14694
15969
  exports.verifyTapscriptSignatures = verifyTapscriptSignatures;
14695
15970
  exports.waitForIncomingFunds = waitForIncomingFunds;
14696
15971
  exports.warnAndFilterVtxosForScript = warnAndFilterVtxosForScript;
14697
- //# sourceMappingURL=chunk-XCHBQVMK.cjs.map
14698
- //# sourceMappingURL=chunk-XCHBQVMK.cjs.map
15972
+ //# sourceMappingURL=chunk-TUSGEWOX.cjs.map
15973
+ //# sourceMappingURL=chunk-TUSGEWOX.cjs.map