@gvnrdao/dh-sdk 0.0.307 → 0.0.308

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.
package/dist/index.mjs CHANGED
@@ -1514,6 +1514,19 @@ var require_pkg_src = __commonJS({
1514
1514
  publicKey: "0x046da0ad6da7dd4a0063cbeac6f2bdb6a9889c2178b23470833356a8ee5630f7f0eee0db348d9d9aaaadd23eccf0063c8f68ee5e446abe30b882bf1de4c932bc21",
1515
1515
  ethAddress: "0xc7e1588Db94Bbe92Ab209B833235144Fc5b97607"
1516
1516
  }
1517
+ },
1518
+ btcUtxoInvalidator: {
1519
+ cid: "QmRue1EAjuJwCDTtTH4czdkkmDEfNpT7iudvwtjaRs6AdA",
1520
+ authorizedCidHex: cidToHex(
1521
+ "QmRue1EAjuJwCDTtTH4czdkkmDEfNpT7iudvwtjaRs6AdA"
1522
+ ),
1523
+ name: "Btc Utxo Invalidator",
1524
+ description: "Production Btc Utxo Invalidator",
1525
+ version: "1.0.0",
1526
+ deployed: true,
1527
+ deployedAt: 1784801043540,
1528
+ size: 60717,
1529
+ hash: "2ca9736edf3d54bba7445491920cba28429029c32a2f2830cad831a37a3cb310"
1517
1530
  }
1518
1531
  };
1519
1532
  function getDeploymentsForNetwork(network) {
@@ -2259,7 +2272,7 @@ ${errorReport}`);
2259
2272
  }
2260
2273
  init_debug_logger();
2261
2274
  init_session_signature_cache();
2262
- var import_ethers16 = __require("ethers");
2275
+ var import_ethers17 = __require("ethers");
2263
2276
  var EXPIRED_LOAN_MIN_LIQUIDATION_THRESHOLD_BPS = 11e3;
2264
2277
  var GRACE_PERIOD_DAYS = 30;
2265
2278
  var SATOSHIS_PER_BITCOIN = 100000000n;
@@ -5089,12 +5102,12 @@ ${auth.clientId}`;
5089
5102
  }
5090
5103
  async function executeVaultSnapshot(params) {
5091
5104
  global.ethers = {
5092
- ...import_ethers16.ethers,
5105
+ ...import_ethers17.ethers,
5093
5106
  providers: {
5094
- StaticJsonRpcProvider: import_ethers16.ethers.JsonRpcProvider
5107
+ StaticJsonRpcProvider: import_ethers17.ethers.JsonRpcProvider
5095
5108
  },
5096
- Contract: import_ethers16.ethers.Contract,
5097
- utils: import_ethers16.ethers
5109
+ Contract: import_ethers17.ethers.Contract,
5110
+ utils: import_ethers17.ethers
5098
5111
  // v6 moved utils to top level
5099
5112
  };
5100
5113
  global.Lit = {
@@ -5552,7 +5565,7 @@ var init_network_configs = __esm({
5552
5565
  });
5553
5566
 
5554
5567
  // src/modules/diamond-hands-sdk.ts
5555
- import { Contract as Contract6, Interface as Interface4, JsonRpcProvider, AbiCoder as AbiCoder2, keccak256 as keccak2565, solidityPackedKeccak256 as solidityPackedKeccak2564, toUtf8Bytes as toUtf8Bytes5, getBytes as getBytes4, zeroPadValue as zeroPadValue4, toBeHex as toBeHex2, parseEther, formatEther, formatUnits, computeAddress, Signature, ZeroHash, ZeroAddress as ZeroAddress3, MaxUint256 } from "ethers";
5568
+ import { Contract as Contract6, Interface as Interface4, JsonRpcProvider, AbiCoder as AbiCoder2, keccak256 as keccak2566, solidityPackedKeccak256 as solidityPackedKeccak2565, toUtf8Bytes as toUtf8Bytes6, getBytes as getBytes5, zeroPadValue as zeroPadValue5, toBeHex as toBeHex2, parseEther, formatEther, formatUnits, computeAddress, Signature, ZeroHash, ZeroAddress as ZeroAddress3, MaxUint256 } from "ethers";
5556
5569
 
5557
5570
  // src/types/result.ts
5558
5571
  function success(value) {
@@ -7068,10 +7081,87 @@ async function reconcileAuthorizedSpend(spend, esploraBaseUrl, httpGetJson = def
7068
7081
  };
7069
7082
  }
7070
7083
 
7071
- // src/utils/btc-withdrawal-message.ts
7072
- import { keccak256 as keccak2563, toUtf8Bytes as toUtf8Bytes3, solidityPackedKeccak256 as solidityPackedKeccak2563, getBytes as getBytes3, zeroPadValue } from "ethers";
7084
+ // src/utils/stale-spend-invalidate-message.ts
7085
+ import {
7086
+ keccak256 as keccak2563,
7087
+ toUtf8Bytes as toUtf8Bytes3,
7088
+ solidityPackedKeccak256 as solidityPackedKeccak2563,
7089
+ getBytes as getBytes3,
7090
+ zeroPadValue
7091
+ } from "ethers";
7092
+ var RECOVERY_INVALIDATE_ACTION = "btc-utxo-invalidator";
7073
7093
  var QUANTUM_SECONDS = 60;
7074
7094
  var QUANTUM_SAFE_THRESHOLD = 40;
7095
+ function buildInvalidateMessageHash(params) {
7096
+ const positionIdBytes32 = zeroPadValue(
7097
+ params.positionId.startsWith("0x") ? params.positionId : `0x${params.positionId}`,
7098
+ 32
7099
+ );
7100
+ const actionHash = keccak2563(toUtf8Bytes3(RECOVERY_INVALIDATE_ACTION));
7101
+ return solidityPackedKeccak2563(
7102
+ [
7103
+ "bytes32",
7104
+ // positionId
7105
+ "uint256",
7106
+ // timestamp
7107
+ "uint256",
7108
+ // chainId
7109
+ "string",
7110
+ // utxoTxid (display order)
7111
+ "uint32",
7112
+ // utxoVout
7113
+ "bytes32"
7114
+ // actionHash
7115
+ ],
7116
+ [
7117
+ positionIdBytes32,
7118
+ params.timestamp,
7119
+ params.chainId,
7120
+ params.utxoTxid,
7121
+ params.utxoVout,
7122
+ actionHash
7123
+ ]
7124
+ );
7125
+ }
7126
+ async function buildInvalidateStaleSpendEnvelope(params) {
7127
+ const { positionId, utxoTxid, utxoVout, chainId, signer } = params;
7128
+ const nowSec = Date.now() / 1e3;
7129
+ const currentQuantumStart = Math.floor(nowSec / QUANTUM_SECONDS) * QUANTUM_SECONDS;
7130
+ const secsIntoQuantum = nowSec - currentQuantumStart;
7131
+ let timestamp;
7132
+ if (secsIntoQuantum > QUANTUM_SAFE_THRESHOLD) {
7133
+ const nextQuantumStart = currentQuantumStart + QUANTUM_SECONDS;
7134
+ const waitMs = Math.max(0, (nextQuantumStart - nowSec) * 1e3 + 500);
7135
+ await new Promise((r) => setTimeout(r, waitMs));
7136
+ timestamp = nextQuantumStart;
7137
+ } else {
7138
+ timestamp = currentQuantumStart;
7139
+ }
7140
+ const messageHash = buildInvalidateMessageHash({
7141
+ positionId,
7142
+ timestamp,
7143
+ chainId,
7144
+ utxoTxid,
7145
+ utxoVout
7146
+ });
7147
+ const signature = await signer.signMessage(getBytes3(messageHash));
7148
+ const borrowerAddress = await signer.getAddress();
7149
+ return {
7150
+ positionId,
7151
+ utxoTxid,
7152
+ utxoVout,
7153
+ timestamp,
7154
+ chainId,
7155
+ action: RECOVERY_INVALIDATE_ACTION,
7156
+ signature,
7157
+ borrowerAddress
7158
+ };
7159
+ }
7160
+
7161
+ // src/utils/btc-withdrawal-message.ts
7162
+ import { keccak256 as keccak2564, toUtf8Bytes as toUtf8Bytes4, solidityPackedKeccak256 as solidityPackedKeccak2564, getBytes as getBytes4, zeroPadValue as zeroPadValue2 } from "ethers";
7163
+ var QUANTUM_SECONDS2 = 60;
7164
+ var QUANTUM_SAFE_THRESHOLD2 = 40;
7075
7165
  function hashBtcInputSet(utxos) {
7076
7166
  if (!Array.isArray(utxos) || utxos.length === 0) {
7077
7167
  throw new Error("hashBtcInputSet: at least one input is required");
@@ -7100,7 +7190,7 @@ function hashBtcInputSet(utxos) {
7100
7190
  seen.add(key);
7101
7191
  }
7102
7192
  const canonical = normalized.sort((a, b) => a.txid < b.txid ? -1 : a.txid > b.txid ? 1 : a.vout - b.vout).map((u) => `${u.txid}:${u.vout}:${u.value}`).join("|");
7103
- return keccak2563(toUtf8Bytes3(canonical));
7193
+ return keccak2564(toUtf8Bytes4(canonical));
7104
7194
  }
7105
7195
  async function buildBtcExecuteEnvelope(params) {
7106
7196
  const {
@@ -7134,26 +7224,26 @@ async function buildBtcExecuteEnvelope(params) {
7134
7224
  }
7135
7225
  const utxosHash = hashBtcInputSet(utxos);
7136
7226
  const nowSec = Date.now() / 1e3;
7137
- const currentQuantumStart = Math.floor(nowSec / QUANTUM_SECONDS) * QUANTUM_SECONDS;
7227
+ const currentQuantumStart = Math.floor(nowSec / QUANTUM_SECONDS2) * QUANTUM_SECONDS2;
7138
7228
  const secsIntoQuantum = nowSec - currentQuantumStart;
7139
7229
  let timestamp;
7140
- if (secsIntoQuantum > QUANTUM_SAFE_THRESHOLD) {
7141
- const nextQuantumStart = currentQuantumStart + QUANTUM_SECONDS;
7230
+ if (secsIntoQuantum > QUANTUM_SAFE_THRESHOLD2) {
7231
+ const nextQuantumStart = currentQuantumStart + QUANTUM_SECONDS2;
7142
7232
  const waitMs = Math.max(0, (nextQuantumStart - nowSec) * 1e3 + 500);
7143
7233
  await new Promise((r) => setTimeout(r, waitMs));
7144
7234
  timestamp = nextQuantumStart;
7145
7235
  } else {
7146
7236
  timestamp = currentQuantumStart;
7147
7237
  }
7148
- const actionHash = keccak2563(
7149
- toUtf8Bytes3("execute-btc-withdrawal")
7238
+ const actionHash = keccak2564(
7239
+ toUtf8Bytes4("execute-btc-withdrawal")
7150
7240
  );
7151
7241
  const utxoIdentifier = `${txid}:${vout}`;
7152
- const positionIdBytes32 = zeroPadValue(
7242
+ const positionIdBytes32 = zeroPadValue2(
7153
7243
  positionId.startsWith("0x") ? positionId : `0x${positionId}`,
7154
7244
  32
7155
7245
  );
7156
- const messageHash = solidityPackedKeccak2563(
7246
+ const messageHash = solidityPackedKeccak2564(
7157
7247
  [
7158
7248
  "bytes32",
7159
7249
  // positionId
@@ -7186,7 +7276,7 @@ async function buildBtcExecuteEnvelope(params) {
7186
7276
  actionHash
7187
7277
  ]
7188
7278
  );
7189
- const userSignature = await signer.signMessage(getBytes3(messageHash));
7279
+ const userSignature = await signer.signMessage(getBytes4(messageHash));
7190
7280
  const borrowerAddress = await signer.getAddress();
7191
7281
  return {
7192
7282
  positionId,
@@ -7250,7 +7340,7 @@ function maybeTemperSignature(signature, shouldTemper, operationName) {
7250
7340
  }
7251
7341
 
7252
7342
  // src/utils/address-conversion.utils.ts
7253
- import { zeroPadValue as zeroPadValue2 } from "ethers";
7343
+ import { zeroPadValue as zeroPadValue3 } from "ethers";
7254
7344
 
7255
7345
  // src/utils/chunks/bitcoin-utils.ts
7256
7346
  import { sha256 as nobleSha256 } from "@noble/hashes/sha256";
@@ -8834,7 +8924,7 @@ function createPKPManager(config) {
8834
8924
  }
8835
8925
 
8836
8926
  // src/modules/loan/loan-creator.module.ts
8837
- import { Interface as Interface2, zeroPadValue as zeroPadValue3, toBeHex, SigningKey } from "ethers";
8927
+ import { Interface as Interface2, zeroPadValue as zeroPadValue4, toBeHex, SigningKey } from "ethers";
8838
8928
  var import_dh_lit_actions = __toESM(require_pkg_src());
8839
8929
  var LoanCreator = class {
8840
8930
  config;
@@ -9164,7 +9254,7 @@ var LoanCreator = class {
9164
9254
  try {
9165
9255
  const mainnetVaultAddress = bitcoinAddresses.mainnet;
9166
9256
  const regtestVaultAddress = bitcoinAddresses.regtest;
9167
- const pkpIdBytes32 = zeroPadValue3(
9257
+ const pkpIdBytes32 = zeroPadValue4(
9168
9258
  toBeHex(BigInt(pkpData.tokenId)),
9169
9259
  32
9170
9260
  );
@@ -9345,7 +9435,7 @@ var LoanCreator = class {
9345
9435
  }
9346
9436
  }
9347
9437
  if (this.config.debug) {
9348
- const pkpIdBytes32Check = zeroPadValue3(
9438
+ const pkpIdBytes32Check = zeroPadValue4(
9349
9439
  toBeHex(BigInt(pkpData.tokenId)),
9350
9440
  32
9351
9441
  );
@@ -10108,7 +10198,7 @@ function createLoanQuery(config) {
10108
10198
  }
10109
10199
 
10110
10200
  // src/modules/withdrawal-address/withdrawal-address.module.ts
10111
- import { keccak256 as keccak2564, toUtf8Bytes as toUtf8Bytes4, ZeroAddress as ZeroAddress2 } from "ethers";
10201
+ import { keccak256 as keccak2565, toUtf8Bytes as toUtf8Bytes5, ZeroAddress as ZeroAddress2 } from "ethers";
10112
10202
  var WithdrawalAddressModule = class {
10113
10203
  config;
10114
10204
  graphClient;
@@ -10143,7 +10233,7 @@ var WithdrawalAddressModule = class {
10143
10233
  }
10144
10234
  /** keccak256 of the canonicalized address — matches the on-chain key. */
10145
10235
  static addressHash(btcAddress) {
10146
- return keccak2564(toUtf8Bytes4(normalizeBitcoinAddress(btcAddress)));
10236
+ return keccak2565(toUtf8Bytes5(normalizeBitcoinAddress(btcAddress)));
10147
10237
  }
10148
10238
  /**
10149
10239
  * Add a Bitcoin address to the caller's allowlist. It becomes usable only
@@ -17592,8 +17682,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
17592
17682
  throw new Error(`Position not found: ${request.positionId}`);
17593
17683
  }
17594
17684
  const currentDebt = position.ucdDebt.toString();
17595
- const ucdDebtHash = keccak2565(AbiCoder2.defaultAbiCoder().encode(["uint256"], [currentDebt]));
17596
- const contractHash = keccak2565(AbiCoder2.defaultAbiCoder().encode(
17685
+ const ucdDebtHash = keccak2566(AbiCoder2.defaultAbiCoder().encode(["uint256"], [currentDebt]));
17686
+ const contractHash = keccak2566(AbiCoder2.defaultAbiCoder().encode(
17597
17687
  ["address", "address", "address", "address"],
17598
17688
  [
17599
17689
  positionManagerAddress,
@@ -17670,7 +17760,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
17670
17760
  const currentPosition = await positionCore["getPositionDetails"](
17671
17761
  positionIdBytes32
17672
17762
  );
17673
- const currentDebtHash = keccak2565(
17763
+ const currentDebtHash = keccak2566(
17674
17764
  AbiCoder2.defaultAbiCoder().encode(
17675
17765
  ["uint256"],
17676
17766
  [currentPosition.ucdDebt]
@@ -21406,7 +21496,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21406
21496
  * been broadcast to the Bitcoin network. An empty array means no pending withdrawals.
21407
21497
  *
21408
21498
  * @param positionId - Position identifier
21409
- * @returns Array of pending withdrawals, each including a pre-computed utxoKey for use with cancelPendingWithdrawal
21499
+ * @returns Array of pending withdrawals, each including a pre-computed utxoKey (the key recoverStaleSpend and the admin clearing path operate on)
21410
21500
  */
21411
21501
  async getPendingWithdrawals(positionId) {
21412
21502
  this.ensureInitialized();
@@ -21438,7 +21528,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21438
21528
  targetAddress: spend.targetAddress,
21439
21529
  targetAmount: Number(spend.targetAmount),
21440
21530
  authorizedAt: Number(spend.authorizedAt),
21441
- utxoKey: solidityPackedKeccak2564(
21531
+ utxoKey: solidityPackedKeccak2565(
21442
21532
  ["string", "uint32"],
21443
21533
  [spend.txid, Number(spend.vout)]
21444
21534
  )
@@ -21451,11 +21541,15 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21451
21541
  * broadcast happened. Callers MUST use the returned `status` to decide what
21452
21542
  * to offer:
21453
21543
  * - EXECUTABLE → offer Execute (the only status that may).
21454
- * - EXECUTED → auto-clear; `spendingTxid` is the completion proof.
21455
- * - SPENT_MISMATCH → unexecutable; offer cancelPendingWithdrawal.
21544
+ * - EXECUTED → auto-clear; `spendingTxid` is the completion proof
21545
+ * feed it to recoverStaleSpend to clear the reservation.
21546
+ * - SPENT_MISMATCH → unexecutable; recoverStaleSpend with the spending txid
21547
+ * as invalidator clears the reservation.
21456
21548
  * - CORRUPT → authorization contradicts the chain (e.g. declared
21457
- * satoshis ≠ real output value); offer Cancel &
21458
- * re-request Execute can only die at the signer guard.
21549
+ * satoshis ≠ real output value); Execute can only die at
21550
+ * the signer guard, and no borrower-side cancel exists
21551
+ * (audit #62210) — clearing needs the AdminModule
21552
+ * cancelStaleSpendByAdmin operator override.
21459
21553
  * - UNFUNDED → funding tx unknown/unconfirmed; wait.
21460
21554
  *
21461
21555
  * @param opts.esploraBaseUrl Esplora API base (e.g. the api proxy
@@ -21479,18 +21573,13 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21479
21573
  spends.map((spend) => reconcileAuthorizedSpend(spend, esploraBaseUrl))
21480
21574
  );
21481
21575
  }
21482
- /**
21483
- * Cancel a pending BTC withdrawal
21484
- *
21485
- * Removes a stale authorized spend from BTCSpendAuthorizer, unlocking the UTXO
21486
- * and allowing the borrower to retry the withdrawal from scratch.
21487
- * Only the position's borrower may call this requires a connected signer.
21488
- *
21489
- * @param positionId - Position identifier
21490
- * @param txid - Bitcoin UTXO transaction ID (from getPendingWithdrawals)
21491
- * @param vout - Output index (from getPendingWithdrawals)
21492
- * @returns Transaction hash of the cancellation EVM transaction
21493
- */
21576
+ // NOTE: there is deliberately no cancelPendingWithdrawal. Borrower-callable
21577
+ // cleanup was permanently disabled by audit #62210 (markSpentByOwner /
21578
+ // cancelStaleSpendByOwner revert with BorrowerBTCSpendMutationDisabled — a
21579
+ // borrower cancel could be replayed to mint UCD against the same UTXO).
21580
+ // Reservations clear via recoverStaleSpend below (trustless attestation once
21581
+ // the UTXO is provably consumed) or the AdminModule-timelocked
21582
+ // cancelStaleSpendByAdmin operator override.
21494
21583
  /**
21495
21584
  * Recover (clear) a stale BTC reservation via LIT-attested proof.
21496
21585
  *
@@ -21642,6 +21731,256 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21642
21731
  };
21643
21732
  }
21644
21733
  }
21734
+ /**
21735
+ * Sign + broadcast the PKP vault→vault self-spend that invalidates one
21736
+ * stuck authorized outpoint (btc-utxo-invalidator 1.0.0). Once the returned
21737
+ * `invalidatorTxid` reaches >=6 Bitcoin confirmations, `recoverStaleSpend`
21738
+ * clears the on-chain reservation — the trustless replacement for the
21739
+ * AdminModule cancelStaleSpendByAdmin path on unspent stuck/corrupt
21740
+ * reservations (incident 2026-07-22). Requires the borrower (or position
21741
+ * delegate) signer; the signature is scoped to exactly this outpoint.
21742
+ */
21743
+ async invalidateStaleSpend(params) {
21744
+ this.ensureInitialized();
21745
+ const chainId = this.config.chainId ?? this.config.networkOverride?.chainId;
21746
+ if (!chainId) {
21747
+ return {
21748
+ success: false,
21749
+ failedStep: "config",
21750
+ error: "chainId required for invalidateStaleSpend"
21751
+ };
21752
+ }
21753
+ const signer = this.getSignerOrThrow();
21754
+ const envelope = await buildInvalidateStaleSpendEnvelope({
21755
+ positionId: params.positionId,
21756
+ utxoTxid: params.utxoTxid,
21757
+ utxoVout: params.utxoVout,
21758
+ chainId,
21759
+ signer
21760
+ });
21761
+ if (this.config.mode === "service" && this.config.serviceEndpoint) {
21762
+ const headers = {
21763
+ "Content-Type": "application/json",
21764
+ ...await this.getAuthHeader()
21765
+ };
21766
+ const resp = await fetch(
21767
+ `${this.config.serviceEndpoint}/api/lit/stale-spend/invalidate`,
21768
+ {
21769
+ method: "POST",
21770
+ headers,
21771
+ body: JSON.stringify({
21772
+ positionId: this.toBytes32(params.positionId),
21773
+ utxoTxid: params.utxoTxid,
21774
+ utxoVout: params.utxoVout,
21775
+ chainId,
21776
+ auth: {
21777
+ timestamp: envelope.timestamp,
21778
+ action: envelope.action,
21779
+ signature: envelope.signature
21780
+ }
21781
+ })
21782
+ }
21783
+ );
21784
+ const json = await resp.json();
21785
+ return json.data ?? {
21786
+ success: false,
21787
+ error: json.error ?? "stale-spend invalidate request failed"
21788
+ };
21789
+ }
21790
+ const rpcUrl = params.rpcUrl ?? this.config.ethRpcUrl;
21791
+ if (!rpcUrl) {
21792
+ return {
21793
+ success: false,
21794
+ failedStep: "config",
21795
+ error: "ethRpcUrl required for invalidateStaleSpend \u2014 set DiamondHandsSDKConfig.ethRpcUrl or pass rpcUrl"
21796
+ };
21797
+ }
21798
+ const positionDetails = await this.getPositionDetailsView(
21799
+ params.positionId
21800
+ );
21801
+ if (!positionDetails?.pkpId || positionDetails.pkpId === ZeroHash) {
21802
+ return { success: false, error: "Position has no PKP" };
21803
+ }
21804
+ const pkpCache = this.cacheManager.getCache("pkp-data", {
21805
+ maxSize: 500,
21806
+ ttlMs: 24 * 60 * 60 * 1e3
21807
+ });
21808
+ let publicKey = pkpCache.get(params.positionId)?.publicKey;
21809
+ if (!publicKey) {
21810
+ const derived = await this.litOps.deriveDiamondHandsLoanPkpPublicKey({
21811
+ pkpId: positionDetails.pkpId
21812
+ });
21813
+ publicKey = derived.publicKey;
21814
+ }
21815
+ if (!publicKey) {
21816
+ return { success: false, error: "Failed to resolve PKP public key" };
21817
+ }
21818
+ const chipotlePkpAddress = computeAddress(publicKey);
21819
+ const dc = this.config.contractAddresses || {};
21820
+ const chainName = chainId === 1 ? "ethereum" : chainId === 11155111 ? "sepolia" : chainId === 1337 || chainId === 31337 ? "hardhat" : void 0;
21821
+ if (!chainName) {
21822
+ return {
21823
+ success: false,
21824
+ error: `Unsupported chainId ${chainId} for invalidateStaleSpend (supported: 1, 11155111, 1337, 31337)`
21825
+ };
21826
+ }
21827
+ const signResult = await this.litOps.signBtcUtxoInvalidator({
21828
+ auth: {
21829
+ positionId: this.toBytes32(params.positionId),
21830
+ utxoTxid: params.utxoTxid,
21831
+ utxoVout: params.utxoVout,
21832
+ timestamp: envelope.timestamp,
21833
+ chainId,
21834
+ action: envelope.action,
21835
+ signature: envelope.signature
21836
+ },
21837
+ publicKey,
21838
+ pkpId: chipotlePkpAddress,
21839
+ chain: chainName,
21840
+ rpcUrl,
21841
+ ...this.config.bitcoinProviders?.[0]?.url && (chainId === 1337 || chainId === 31337) ? { devBitcoinProviderUrl: this.config.bitcoinProviders[0].url } : {},
21842
+ contractAddresses: {
21843
+ PositionManager: dc.positionManager ?? "",
21844
+ BTCSpendAuthorizer: dc.btcSpendAuthorizer ?? "",
21845
+ ...dc.bitcoinProviderRegistry ? { BitcoinProviderRegistry: dc.bitcoinProviderRegistry } : {},
21846
+ ...dc.positionDelegateRegistry ? { PositionDelegateRegistry: dc.positionDelegateRegistry } : {}
21847
+ }
21848
+ });
21849
+ if (!signResult.success) {
21850
+ return {
21851
+ success: false,
21852
+ error: signResult.error ?? "btc-utxo-invalidator signing failed",
21853
+ failedStep: signResult.failedStep
21854
+ };
21855
+ }
21856
+ if (signResult.alreadySpent && typeof signResult.alreadySpent === "object") {
21857
+ return {
21858
+ success: true,
21859
+ alreadySpent: { spendingTxid: signResult.alreadySpent.spendingTxid }
21860
+ };
21861
+ }
21862
+ if (!signResult.signatures?.length || !signResult.utxo || !signResult.pkpBtcAddress || typeof signResult.invalidatorAmount !== "number") {
21863
+ return {
21864
+ success: false,
21865
+ error: "btc-utxo-invalidator returned incomplete data (signatures/utxo/pkpBtcAddress/invalidatorAmount)"
21866
+ };
21867
+ }
21868
+ const broadcastUrl = this.config.bitcoinProviders?.[0]?.url;
21869
+ if (!broadcastUrl) {
21870
+ return {
21871
+ success: false,
21872
+ error: "invalidateStaleSpend broadcast requires config.bitcoinProviders[0].url (esplora base)"
21873
+ };
21874
+ }
21875
+ const { txid: invalidatorTxid } = await broadcastSignedBitcoinTransaction({
21876
+ signatures: signResult.signatures,
21877
+ utxos: [signResult.utxo],
21878
+ pkpPublicKey: signResult.pkpPublicKey ?? publicKey,
21879
+ destination: signResult.pkpBtcAddress,
21880
+ userReceivesAmount: signResult.invalidatorAmount,
21881
+ changeAmount: 0,
21882
+ pkpBtcAddress: signResult.pkpBtcAddress,
21883
+ bitcoinNetwork: chainId === 1 ? "mainnet" : "regtest",
21884
+ bitcoinRpcUrl: broadcastUrl
21885
+ });
21886
+ return {
21887
+ success: true,
21888
+ invalidatorTxid,
21889
+ invalidatorAmount: signResult.invalidatorAmount,
21890
+ networkFee: signResult.networkFee,
21891
+ valueMismatch: signResult.valueMismatch
21892
+ };
21893
+ }
21894
+ /**
21895
+ * Resumable, stateless clearing state machine for one stuck reservation —
21896
+ * Bitcoin itself is the state store, so every call either advances the flow
21897
+ * one step or reports where it stands (survives reloads and the multi-day
21898
+ * 6-confirmation wait; no internal polling).
21899
+ *
21900
+ * (a) reservation gone → phase "cleared"
21901
+ * (b) outpoint spent, >=6 confs → recoverStaleSpend → "cleared"/"failed"
21902
+ * outpoint spent, <6 confs → "awaiting-confirmations" (x/6)
21903
+ * (c) outpoint unspent → invalidateStaleSpend → "invalidator-broadcast"
21904
+ */
21905
+ async clearStuckReservation(params) {
21906
+ this.ensureInitialized();
21907
+ const REQUIRED_CONFS = 6;
21908
+ const esploraBaseUrl = params.esploraBaseUrl ?? this.config.bitcoinProviders?.[0]?.url;
21909
+ const reconciled = await this.reconcilePendingWithdrawals(
21910
+ params.positionId,
21911
+ esploraBaseUrl ? { esploraBaseUrl } : void 0
21912
+ );
21913
+ const row = reconciled.find(
21914
+ (r) => r.spend.txid.toLowerCase() === params.utxoTxid.toLowerCase() && r.spend.vout === params.utxoVout
21915
+ );
21916
+ if (!row) {
21917
+ return { phase: "cleared" };
21918
+ }
21919
+ if (row.spendingTxid && esploraBaseUrl) {
21920
+ const base = esploraBaseUrl.replace(/\/+$/, "");
21921
+ const txRes = await fetch(`${base}/tx/${row.spendingTxid}`);
21922
+ const tipRes = await fetch(`${base}/blocks/tip/height`);
21923
+ let confirmations = 0;
21924
+ if (txRes.ok && tipRes.ok) {
21925
+ const tx = await txRes.json();
21926
+ const tip = Number(await tipRes.text());
21927
+ if (tx.status?.confirmed && typeof tx.status.block_height === "number" && Number.isFinite(tip)) {
21928
+ confirmations = Math.max(0, tip - tx.status.block_height + 1);
21929
+ }
21930
+ }
21931
+ if (confirmations < REQUIRED_CONFS) {
21932
+ return {
21933
+ phase: "awaiting-confirmations",
21934
+ invalidatorTxid: row.spendingTxid,
21935
+ confirmations,
21936
+ required: REQUIRED_CONFS
21937
+ };
21938
+ }
21939
+ const recovery = await this.recoverStaleSpend({
21940
+ positionId: params.positionId,
21941
+ utxoTxid: params.utxoTxid,
21942
+ utxoVout: params.utxoVout,
21943
+ invalidatorTxid: row.spendingTxid
21944
+ });
21945
+ if (recovery.success) {
21946
+ return {
21947
+ phase: "cleared",
21948
+ invalidatorTxid: row.spendingTxid,
21949
+ transactionHash: recovery.transactionHash,
21950
+ classification: recovery.classification
21951
+ };
21952
+ }
21953
+ return {
21954
+ phase: "failed",
21955
+ invalidatorTxid: row.spendingTxid,
21956
+ error: recovery.error ?? "stale-spend recovery failed"
21957
+ };
21958
+ }
21959
+ const invalidation = await this.invalidateStaleSpend({
21960
+ positionId: params.positionId,
21961
+ utxoTxid: params.utxoTxid,
21962
+ utxoVout: params.utxoVout
21963
+ });
21964
+ if (!invalidation.success) {
21965
+ return {
21966
+ phase: "failed",
21967
+ error: invalidation.error ?? "invalidator signing/broadcast failed"
21968
+ };
21969
+ }
21970
+ if (invalidation.alreadySpent) {
21971
+ return {
21972
+ phase: "awaiting-confirmations",
21973
+ invalidatorTxid: invalidation.alreadySpent.spendingTxid,
21974
+ confirmations: 0,
21975
+ required: REQUIRED_CONFS
21976
+ };
21977
+ }
21978
+ return {
21979
+ phase: "invalidator-broadcast",
21980
+ invalidatorTxid: invalidation.invalidatorTxid,
21981
+ valueMismatch: invalidation.valueMismatch
21982
+ };
21983
+ }
21645
21984
  /**
21646
21985
  * Withdraw BTC and execute transfer (Complete Flow)
21647
21986
  *
@@ -22732,7 +23071,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
22732
23071
  * Convert decimal position ID to bytes32 format
22733
23072
  */
22734
23073
  toBytes32(value) {
22735
- return zeroPadValue4(toBeHex2(BigInt(value)), 32);
23074
+ return zeroPadValue5(toBeHex2(BigInt(value)), 32);
22736
23075
  }
22737
23076
  /**
22738
23077
  * Check if an error indicates a technical failure vs business logic rejection
@@ -22950,8 +23289,8 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
22950
23289
  const MIN_REVEAL_DELAY = 60;
22951
23290
  const MAX_RANDOM_DELAY = 240;
22952
23291
  const entropyInput = positionId + quantumTimestamp.toString();
22953
- const hash = keccak2565(
22954
- toUtf8Bytes5(entropyInput)
23292
+ const hash = keccak2566(
23293
+ toUtf8Bytes6(entropyInput)
22955
23294
  );
22956
23295
  const randomValue = BigInt(hash);
22957
23296
  const randomDelay = Number(randomValue % BigInt(MAX_RANDOM_DELAY));
@@ -22976,12 +23315,12 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
22976
23315
  const rawPositionId = /^0x/i.test(params.positionId.trim()) ? params.positionId.trim().slice(2) : params.positionId.trim();
22977
23316
  const canonicalPositionId = "0x" + rawPositionId.padStart(64, "0").toLowerCase();
22978
23317
  const intentTimestamp = Math.floor(Date.now() / 1e3);
22979
- const intentActionHash = keccak2565(toUtf8Bytes5("liquidate-position"));
22980
- const intentHash = solidityPackedKeccak2564(
23318
+ const intentActionHash = keccak2566(toUtf8Bytes6("liquidate-position"));
23319
+ const intentHash = solidityPackedKeccak2565(
22981
23320
  ["bytes32", "uint256", "uint256", "address", "bytes32"],
22982
23321
  [canonicalPositionId, intentTimestamp, chainId, intentSigner, intentActionHash]
22983
23322
  );
22984
- const intentSignature = await signer.signMessage(getBytes4(intentHash));
23323
+ const intentSignature = await signer.signMessage(getBytes5(intentHash));
22985
23324
  const response = await fetch(endpoint, {
22986
23325
  method: "POST",
22987
23326
  headers: { "Content-Type": "application/json" },
@@ -23033,7 +23372,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
23033
23372
  const vrfSeedRaw = await lmResult.value["vrfSeeds"](positionId);
23034
23373
  const vrfSeed = BigInt(vrfSeedRaw.toString());
23035
23374
  if (vrfSeed !== 0n) {
23036
- const finalEntropy = solidityPackedKeccak2564(
23375
+ const finalEntropy = solidityPackedKeccak2565(
23037
23376
  ["uint256", "uint256", "bytes32"],
23038
23377
  [vrfSeed, BigInt(quantumTimestamp.toString()), positionId]
23039
23378
  );
@@ -23354,6 +23693,7 @@ export {
23354
23693
  PKPManager,
23355
23694
  PaymentType,
23356
23695
  PositionStatus,
23696
+ RECOVERY_INVALIDATE_ACTION,
23357
23697
  SDKError,
23358
23698
  SDK_DEFAULTS,
23359
23699
  SEPOLIA_CONTRACTS,
@@ -23366,6 +23706,8 @@ export {
23366
23706
  assertProtocolNotPaused,
23367
23707
  assertSafeServiceEndpoint,
23368
23708
  buildBtcExecuteEnvelope,
23709
+ buildInvalidateMessageHash,
23710
+ buildInvalidateStaleSpendEnvelope,
23369
23711
  collectFailures,
23370
23712
  collectSuccesses,
23371
23713
  combine,