@gvnrdao/dh-sdk 0.0.305 → 0.0.307

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.js CHANGED
@@ -571,45 +571,47 @@ var init_deployment_addresses = __esm({
571
571
  };
572
572
  LOCALHOST_DEPLOYMENT = {
573
573
  network: "localhost",
574
- chainId: 31337,
575
- timestamp: "2026-06-11T12:30:59.589Z",
574
+ chainId: 1337,
575
+ timestamp: "2026-07-20T19:45:58.514Z",
576
576
  deployer: "",
577
577
  contracts: {
578
- MessageHashBuilder: "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
579
- UpgradeValidator: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
578
+ MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
579
+ LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
580
+ UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
580
581
  UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
581
582
  UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
582
- PositionManagerCoreModule: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
583
- TermManagerModule: "0x9A676e781A523b5d0C0e43731313A708CB607508",
584
- LoanOperationsManagerModule: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
585
- BTCSpendAuthorizer: "0x59b670e9fA9D0A427751Af201D676719a970857b",
586
- CollateralManagerModule: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
587
- LiquidationManagerModule: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
588
- CircuitBreakerModule: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
589
- AdminModule: "0xc5a5C42992dECbae36851359345FE25997F5C42d",
590
- PositionManagerViews: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
591
- PositionManager: "0xf5059a5D33d5853360D16C683c16e67980206f36",
592
- OperationAuthorizationRegistry: "0xF8e31cb472bc70500f08Cd84917E5A1912Ec8397",
593
- PKPValidation: "0xc0F115A19107322cFBf1cDBC7ea011C19EbDB4F8"
583
+ PositionManagerCoreModule: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
584
+ TermManagerModule: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
585
+ LoanOperationsManagerModule: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
586
+ BTCSpendAuthorizer: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d",
587
+ CollateralManagerModule: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
588
+ LiquidationManagerModule: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319",
589
+ CircuitBreakerModule: "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
590
+ AdminModule: "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
591
+ PositionManagerViews: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
592
+ PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
593
+ OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
594
+ PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
594
595
  }
595
596
  };
596
597
  LOCALHOST_CONTRACTS = {
597
- MessageHashBuilder: "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
598
- UpgradeValidator: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
598
+ MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
599
+ LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
600
+ UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
599
601
  UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
600
602
  UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
601
- PositionManagerCoreModule: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
602
- TermManagerModule: "0x9A676e781A523b5d0C0e43731313A708CB607508",
603
- LoanOperationsManagerModule: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
604
- BTCSpendAuthorizer: "0x59b670e9fA9D0A427751Af201D676719a970857b",
605
- CollateralManagerModule: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
606
- LiquidationManagerModule: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
607
- CircuitBreakerModule: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
608
- AdminModule: "0xc5a5C42992dECbae36851359345FE25997F5C42d",
609
- PositionManagerViews: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
610
- PositionManager: "0xf5059a5D33d5853360D16C683c16e67980206f36",
611
- OperationAuthorizationRegistry: "0xF8e31cb472bc70500f08Cd84917E5A1912Ec8397",
612
- PKPValidation: "0xc0F115A19107322cFBf1cDBC7ea011C19EbDB4F8"
603
+ PositionManagerCoreModule: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
604
+ TermManagerModule: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
605
+ LoanOperationsManagerModule: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
606
+ BTCSpendAuthorizer: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d",
607
+ CollateralManagerModule: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
608
+ LiquidationManagerModule: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319",
609
+ CircuitBreakerModule: "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
610
+ AdminModule: "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
611
+ PositionManagerViews: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
612
+ PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
613
+ OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
614
+ PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
613
615
  };
614
616
  ALL_DEPLOYMENTS = {
615
617
  sepolia: SEPOLIA_DEPLOYMENT,
@@ -5528,7 +5530,12 @@ var init_network_configs = __esm({
5528
5530
  NETWORK_CONFIGS = {
5529
5531
  1: getMainnetConfig(),
5530
5532
  11155111: getSepoliaConfig(),
5531
- 1337: getLocalhostConfig()
5533
+ 1337: getLocalhostConfig(),
5534
+ // In-process hardhat default chainId — same localhost deployment as 1337
5535
+ // (`npx hardhat node`), so consumers on either local chain resolve config.
5536
+ // chainId overridden so the resolved config self-describes the chain that
5537
+ // was actually looked up (downstream bind/log/validate reads it).
5538
+ 31337: { ...getLocalhostConfig(), chainId: 31337 }
5532
5539
  };
5533
5540
  DEFAULT_NETWORK_CONFIG = {
5534
5541
  /** Fallback when chain is unknown — production Chipotle. */
@@ -5584,6 +5591,7 @@ __export(src_exports, {
5584
5591
  createPKPManager: () => createPKPManager,
5585
5592
  createWithdrawalAddressModule: () => createWithdrawalAddressModule,
5586
5593
  default: () => DiamondHandsSDK,
5594
+ defaultHttpGetJson: () => defaultHttpGetJson,
5587
5595
  failure: () => failure,
5588
5596
  fetchProtocolPauseStatus: () => fetchProtocolPauseStatus,
5589
5597
  firstSuccess: () => firstSuccess,
@@ -5607,6 +5615,7 @@ __export(src_exports, {
5607
5615
  mapError: () => mapError,
5608
5616
  match: () => match,
5609
5617
  numericToPositionStatus: () => numericToPositionStatus,
5618
+ reconcileAuthorizedSpend: () => reconcileAuthorizedSpend,
5610
5619
  setPositionDelegate: () => setPositionDelegate,
5611
5620
  success: () => success,
5612
5621
  toPromise: () => toPromise,
@@ -6950,12 +6959,20 @@ async function assertSafeQuantumSubmission(opts) {
6950
6959
  ...opts.sleep ? { sleep: opts.sleep } : {}
6951
6960
  };
6952
6961
  await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
6962
+ const SAME_BLOCK_INTERACTION_SELECTOR = "0x3b53d1db";
6963
+ const laneGuardActive = opts.laneGuardActive === true;
6953
6964
  for (let sim = 1; ; sim++) {
6954
6965
  try {
6955
6966
  await provider.call({ to, from, data });
6956
6967
  break;
6957
6968
  } catch (simError) {
6958
6969
  const decoded = decodeQuantumRevert(simError);
6970
+ if (laneGuardActive && decoded.selector === SAME_BLOCK_INTERACTION_SELECTOR) {
6971
+ onDebug(
6972
+ "\u21AA\uFE0F [lane-only] Simulated SameBlockInteraction is a stale-latest-block artifact (the simulated block contains this flow's own previous tx) \u2014 proceeding to broadcast; the on-chain guard stays authoritative"
6973
+ );
6974
+ break;
6975
+ }
6959
6976
  if (decoded.selector === DEAD_ZONE_VIOLATION_SELECTOR && sim <= maxResimulations) {
6960
6977
  const sigQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
6961
6978
  const realCurrentQuantum = Math.floor(now() / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
@@ -6991,6 +7008,144 @@ async function assertSafeQuantumSubmission(opts) {
6991
7008
  await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
6992
7009
  }
6993
7010
 
7011
+ // src/utils/lane-guard.utils.ts
7012
+ var HARDHAT_CHAIN_IDS = /* @__PURE__ */ new Set([1337, 31337]);
7013
+ function laneGuardEnvEnabled() {
7014
+ return typeof process !== "undefined" && process.env?.LIT_LANE_GUARD === "1";
7015
+ }
7016
+ async function isLaneGuardActiveOnProvider(provider) {
7017
+ if (!laneGuardEnvEnabled())
7018
+ return false;
7019
+ if (!provider || typeof provider.send !== "function")
7020
+ return false;
7021
+ try {
7022
+ const [chainIdHex, clientVersion] = await Promise.all([
7023
+ provider.send("eth_chainId", []),
7024
+ provider.send("web3_clientVersion", [])
7025
+ ]);
7026
+ const chainId = Number(chainIdHex);
7027
+ return HARDHAT_CHAIN_IDS.has(chainId) && typeof clientVersion === "string" && clientVersion.toLowerCase().includes("hardhat");
7028
+ } catch {
7029
+ return false;
7030
+ }
7031
+ }
7032
+
7033
+ // src/utils/withdrawal-reconciliation.utils.ts
7034
+ var defaultHttpGetJson = async (url) => {
7035
+ const res = await fetch(url, { headers: { accept: "application/json" } });
7036
+ let body = null;
7037
+ try {
7038
+ body = await res.json();
7039
+ } catch {
7040
+ body = null;
7041
+ }
7042
+ return { status: res.status, body };
7043
+ };
7044
+ async function reconcileAuthorizedSpend(spend, esploraBaseUrl, httpGetJson = defaultHttpGetJson) {
7045
+ const base = esploraBaseUrl.replace(/\/+$/, "");
7046
+ const fundingRes = await httpGetJson(`${base}/tx/${spend.txid}`);
7047
+ if (fundingRes.status === 404) {
7048
+ return {
7049
+ spend,
7050
+ status: "UNFUNDED",
7051
+ reason: `Funding tx ${spend.txid} not found on the network`
7052
+ };
7053
+ }
7054
+ if (fundingRes.status !== 200) {
7055
+ throw new Error(
7056
+ `Withdrawal reconciliation: esplora /tx/${spend.txid} returned HTTP ${fundingRes.status}`
7057
+ );
7058
+ }
7059
+ const funding = fundingRes.body;
7060
+ if (!funding?.status?.confirmed) {
7061
+ return {
7062
+ spend,
7063
+ status: "UNFUNDED",
7064
+ reason: `Funding tx ${spend.txid} is not confirmed yet`
7065
+ };
7066
+ }
7067
+ const output = funding.vout?.[spend.vout];
7068
+ if (!output || typeof output.value !== "number") {
7069
+ return {
7070
+ spend,
7071
+ status: "CORRUPT",
7072
+ reason: `Authorized vout ${spend.vout} does not exist on tx ${spend.txid} (${funding.vout?.length ?? 0} outputs)`
7073
+ };
7074
+ }
7075
+ const onChainOutputValue = output.value;
7076
+ if (onChainOutputValue !== spend.satoshis) {
7077
+ return {
7078
+ spend,
7079
+ status: "CORRUPT",
7080
+ onChainOutputValue,
7081
+ reason: `Authorization declares ${spend.satoshis} sats for ${spend.txid}:${spend.vout} but the on-chain output is ${onChainOutputValue} sats`
7082
+ };
7083
+ }
7084
+ if (spend.targetAmount > onChainOutputValue) {
7085
+ return {
7086
+ spend,
7087
+ status: "CORRUPT",
7088
+ onChainOutputValue,
7089
+ reason: `Authorized targetAmount ${spend.targetAmount} sats exceeds the ${onChainOutputValue}-sat output it is authorized against`
7090
+ };
7091
+ }
7092
+ const outspendRes = await httpGetJson(
7093
+ `${base}/tx/${spend.txid}/outspend/${spend.vout}`
7094
+ );
7095
+ if (outspendRes.status !== 200) {
7096
+ throw new Error(
7097
+ `Withdrawal reconciliation: esplora outspend for ${spend.txid}:${spend.vout} returned HTTP ${outspendRes.status}`
7098
+ );
7099
+ }
7100
+ const outspend = outspendRes.body;
7101
+ if (outspend?.spent) {
7102
+ const spendingTxid = typeof outspend.txid === "string" && outspend.txid ? outspend.txid : null;
7103
+ if (spendingTxid) {
7104
+ const spendingRes = await httpGetJson(`${base}/tx/${spendingTxid}`);
7105
+ if (spendingRes.status === 200) {
7106
+ const spendingTx = spendingRes.body;
7107
+ const includesOutpoint = (spendingTx.vin ?? []).some(
7108
+ (v) => v.txid === spend.txid && Number(v.vout) === spend.vout
7109
+ );
7110
+ if (includesOutpoint) {
7111
+ const paidToTargetSats = (spendingTx.vout ?? []).filter(
7112
+ (o) => (o.scriptpubkey_address ?? "").toLowerCase() === spend.targetAddress.toLowerCase()
7113
+ ).reduce((sum, o) => sum + (o.value ?? 0), 0);
7114
+ if (paidToTargetSats > 0) {
7115
+ return {
7116
+ spend,
7117
+ status: "EXECUTED",
7118
+ spendingTxid,
7119
+ paidToTargetSats,
7120
+ onChainOutputValue,
7121
+ reason: `Outpoint spent by ${spendingTxid}, paying ${paidToTargetSats} sats to the authorized target \u2014 withdrawal complete`
7122
+ };
7123
+ }
7124
+ return {
7125
+ spend,
7126
+ status: "SPENT_MISMATCH",
7127
+ spendingTxid,
7128
+ onChainOutputValue,
7129
+ reason: `Outpoint spent by ${spendingTxid} which pays the authorized target nothing \u2014 authorization is unexecutable`
7130
+ };
7131
+ }
7132
+ }
7133
+ }
7134
+ return {
7135
+ spend,
7136
+ status: "EXECUTABLE",
7137
+ onChainOutputValue,
7138
+ reason: "Esplora claims the outpoint is spent but no spending tx provably includes it (known regtest-faucet artifact) \u2014 treated as unspent"
7139
+ };
7140
+ }
7141
+ return {
7142
+ spend,
7143
+ status: "EXECUTABLE",
7144
+ onChainOutputValue,
7145
+ reason: "Outpoint confirmed, unspent, and coherent with the authorization"
7146
+ };
7147
+ }
7148
+
6994
7149
  // src/utils/btc-withdrawal-message.ts
6995
7150
  var import_ethers6 = require("ethers");
6996
7151
  var QUANTUM_SECONDS = 60;
@@ -16333,6 +16488,10 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16333
16488
  this.litOps = new import_dh_lit_ops.LitOps({
16334
16489
  mode: config.mode || "standalone",
16335
16490
  network: config.litNetwork || DEFAULT_LIT_NETWORK,
16491
+ // Completes the module's chainId resolution chain (jsParams shapes →
16492
+ // config.chainId) — e.g. the lane validator-override chain binding
16493
+ // reads it when a jsParams shape lacks chainId.
16494
+ chainId: config.chainId,
16336
16495
  signer: config.mode === "standalone" ? config.litOpsSigner : void 0,
16337
16496
  serviceEndpoint: isServiceModeConfig(config) ? config.serviceEndpoint : void 0,
16338
16497
  // Hand lit-ops a session-aware auth getter so every request it makes
@@ -17010,6 +17169,19 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
17010
17169
  }
17011
17170
  const MAX_FULL_RETRIES = 3;
17012
17171
  for (let fullRetry = 1; fullRetry <= MAX_FULL_RETRIES; fullRetry++) {
17172
+ if (await this.isLocalLaneGuardActive()) {
17173
+ const phase = Math.floor(Date.now() / 1e3) % 60;
17174
+ if (phase > 30) {
17175
+ const waitMs = (60 - phase + 1) * 1e3;
17176
+ if (this.config.debug) {
17177
+ log.info(
17178
+ `\u23F3 Quantum-phase alignment (lane-only): ${phase}s into the quantum \u2014 waiting ${Math.ceil(waitMs / 1e3)}s for the next boundary before authorization`,
17179
+ {}
17180
+ );
17181
+ }
17182
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
17183
+ }
17184
+ }
17013
17185
  if (fullRetry > 1 && this.config.debug) {
17014
17186
  log.info(
17015
17187
  `\u{1F504} Full mint retry ${fullRetry}/${MAX_FULL_RETRIES} due to timestamp staleness...`,
@@ -17758,6 +17930,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
17758
17930
  from: fromAddress,
17759
17931
  data: mintCalldata,
17760
17932
  quantumTimestamp: Number(validationResponse.timestamp),
17933
+ laneGuardActive: await this.isLocalLaneGuardActive(),
17761
17934
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
17762
17935
  });
17763
17936
  } catch (simError) {
@@ -19053,6 +19226,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
19053
19226
  from: await extendSigner.getAddress(),
19054
19227
  data: extendCalldata,
19055
19228
  quantumTimestamp: Number(quantumTimestamp),
19229
+ laneGuardActive: await this.isLocalLaneGuardActive(),
19056
19230
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
19057
19231
  });
19058
19232
  } catch (simError) {
@@ -19953,6 +20127,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
19953
20127
  from: paymentFrom,
19954
20128
  data: paymentCalldata,
19955
20129
  quantumTimestamp: Number(quantumTimestamp),
20130
+ laneGuardActive: await this.isLocalLaneGuardActive(),
19956
20131
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
19957
20132
  });
19958
20133
  } catch (simError) {
@@ -20692,6 +20867,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20692
20867
  from: await withdrawSigner.getAddress(),
20693
20868
  data: withdrawCalldata,
20694
20869
  quantumTimestamp: Number(withdrawalParams.quantumTimestamp),
20870
+ laneGuardActive: await this.isLocalLaneGuardActive(),
20695
20871
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
20696
20872
  });
20697
20873
  tx = await sendEip1559Transaction({
@@ -20846,15 +21022,180 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20846
21022
  * @param request - Execution request with positionId, utxoIdentifier, and networkFee
20847
21023
  * @returns Execution result with Bitcoin txid
20848
21024
  */
20849
- async executeBTCWithdrawal(request) {
20850
- this.ensureInitialized();
20851
- if (this.config.mode !== "service") {
21025
+ /**
21026
+ * STANDALONE Phase 2: sign + broadcast the withdrawal Bitcoin transaction
21027
+ * in-process — the same orchestration lit-ops-server performs, composed
21028
+ * from the same primitives (`litOps.signBTCTransaction`, which has always
21029
+ * had a standalone path, and `broadcastSignedBitcoinTransaction`).
21030
+ * Both modes must support withdrawal execution (Master mandate 2026-07-21).
21031
+ *
21032
+ * The service path's post-broadcast CRIT-2 re-verification exists to catch
21033
+ * a LYING SERVER; here the broadcast happens locally from the signatures we
21034
+ * just obtained, so the esplora-returned txid is already first-hand.
21035
+ */
21036
+ async executeBTCWithdrawalStandalonePhase2(request, envelope, txid, vout, chainId) {
21037
+ const positionDetails = await this.getPositionDetailsView(
21038
+ request.positionId
21039
+ );
21040
+ if (!positionDetails) {
21041
+ return { success: false, error: `Position ${request.positionId} not found` };
21042
+ }
21043
+ const pkpId = positionDetails.pkpId;
21044
+ const vaultAddress = positionDetails.vaultAddress;
21045
+ if (!pkpId || pkpId === import_ethers14.ZeroHash) {
21046
+ return { success: false, error: "Position has no PKP" };
21047
+ }
21048
+ if (!vaultAddress) {
21049
+ return { success: false, error: "Position has no vault address" };
21050
+ }
21051
+ const pkpCache = this.cacheManager.getCache("pkp-data", {
21052
+ maxSize: 500,
21053
+ ttlMs: 24 * 60 * 60 * 1e3
21054
+ });
21055
+ let publicKey = pkpCache.get(request.positionId)?.publicKey;
21056
+ if (!publicKey) {
21057
+ const derived = await this.litOps.deriveDiamondHandsLoanPkpPublicKey({
21058
+ pkpId
21059
+ });
21060
+ publicKey = derived.publicKey;
21061
+ }
21062
+ if (!publicKey) {
21063
+ return { success: false, error: "Failed to resolve PKP public key" };
21064
+ }
21065
+ const chipotlePkpAddress = (0, import_ethers14.computeAddress)(publicKey);
21066
+ const STANDALONE_CHAIN_NAMES = {
21067
+ 1: "ethereum",
21068
+ 11155111: "sepolia",
21069
+ 1337: "hardhat",
21070
+ 31337: "hardhat"
21071
+ };
21072
+ const chainName = STANDALONE_CHAIN_NAMES[chainId];
21073
+ if (!chainName) {
20852
21074
  return {
20853
21075
  success: false,
20854
- error: "BTC withdrawal Phase 2 requires service mode. Set sdkConfig.mode = 'service' and serviceEndpoint to a lit-ops-server."
21076
+ error: `Unsupported chainId ${chainId} for standalone BTC withdrawal (supported: 1, 11155111, 1337, 31337)`
20855
21077
  };
20856
21078
  }
20857
- if (!this.config.serviceEndpoint) {
21079
+ const bitcoinNetworkName = chainId === 1 ? "mainnet" : "regtest";
21080
+ const bitcoin = await import("bitcoinjs-lib");
21081
+ const bitcoinNetwork = chainId === 1 ? bitcoin.networks.bitcoin : chainId === 1337 || chainId === 31337 ? bitcoin.networks.regtest : bitcoin.networks.testnet;
21082
+ let scriptPubKeyBuffer;
21083
+ if (vaultAddress.startsWith("bcrt1q")) {
21084
+ const bech322 = await import("bech32");
21085
+ const decoded = bech322.bech32.decode(vaultAddress);
21086
+ const witnessProgram = Buffer.from(
21087
+ bech322.bech32.fromWords(decoded.words.slice(1))
21088
+ );
21089
+ if (witnessProgram.length !== 20) {
21090
+ return {
21091
+ success: false,
21092
+ error: `Vault address ${vaultAddress} decodes to a ${witnessProgram.length}-byte witness program \u2014 expected 20-byte P2WPKH`
21093
+ };
21094
+ }
21095
+ scriptPubKeyBuffer = Buffer.concat([
21096
+ Buffer.from([0, witnessProgram.length]),
21097
+ witnessProgram
21098
+ ]);
21099
+ } else {
21100
+ scriptPubKeyBuffer = bitcoin.address.toOutputScript(
21101
+ vaultAddress,
21102
+ bitcoinNetwork
21103
+ );
21104
+ }
21105
+ const scriptPubKey = scriptPubKeyBuffer.toString("hex");
21106
+ const executionUtxos = (envelope.utxos && envelope.utxos.length ? envelope.utxos : [{ txid, vout, value: request.utxoSatoshis }]).map((u) => ({ ...u, scriptPubKey }));
21107
+ const dc = this.config.contractAddresses || {};
21108
+ const signerContractAddresses = {
21109
+ LoanOperationsManagerModule: dc.loanOperationsManager ?? "",
21110
+ PositionManager: dc.positionManager ?? "",
21111
+ BTCSpendAuthorizer: dc.btcSpendAuthorizer ?? "",
21112
+ PositionDelegateRegistry: dc.positionDelegateRegistry ?? "",
21113
+ OperationAuthorizationRegistry: dc.operationAuthorizationRegistry ?? "",
21114
+ ...dc.bitcoinProviderRegistry ? { BitcoinProviderRegistry: dc.bitcoinProviderRegistry } : {}
21115
+ };
21116
+ const signResult = await this.litOps.signBTCTransaction({
21117
+ auth: {
21118
+ positionId: envelope.positionId,
21119
+ utxo: { txid, vout, value: request.utxoSatoshis, scriptPubKey },
21120
+ utxos: executionUtxos,
21121
+ networkFee: envelope.networkFee,
21122
+ targetAddress: envelope.targetAddress,
21123
+ targetAmount: envelope.targetAmount,
21124
+ timestamp: envelope.timestamp,
21125
+ signature: envelope.userSignature,
21126
+ chainId
21127
+ },
21128
+ publicKey,
21129
+ pkpId: chipotlePkpAddress,
21130
+ chain: chainName,
21131
+ bitcoinNetwork: bitcoinNetworkName,
21132
+ rpcUrl: this.config.ethRpcUrl,
21133
+ contractAddresses: signerContractAddresses
21134
+ });
21135
+ if (!signResult.success) {
21136
+ return {
21137
+ success: false,
21138
+ error: signResult.error ?? "BTC transaction signing failed"
21139
+ };
21140
+ }
21141
+ if (!signResult.unsignedTxHex || !signResult.signatures?.length) {
21142
+ return {
21143
+ success: false,
21144
+ error: "BTC signing returned incomplete data (missing unsignedTxHex/signatures)"
21145
+ };
21146
+ }
21147
+ if (!signResult.userReceivesAmount) {
21148
+ return { success: false, error: "BTC signing did not return userReceivesAmount" };
21149
+ }
21150
+ const broadcastUrl = this.config.bitcoinProviders?.[0]?.url;
21151
+ if (!broadcastUrl) {
21152
+ return {
21153
+ success: false,
21154
+ error: "Standalone Phase 2 broadcast requires config.bitcoinProviders[0].url (esplora base)"
21155
+ };
21156
+ }
21157
+ try {
21158
+ const { txid: btcTxid } = await (0, import_dh_lit_ops.broadcastSignedBitcoinTransaction)({
21159
+ signatures: signResult.signatures,
21160
+ utxos: executionUtxos,
21161
+ pkpPublicKey: signResult.pkpPublicKey ?? publicKey,
21162
+ destination: envelope.targetAddress,
21163
+ userReceivesAmount: signResult.userReceivesAmount,
21164
+ changeAmount: signResult.changeAmount,
21165
+ pkpBtcAddress: signResult.pkpBtcAddress,
21166
+ bitcoinNetwork: bitcoinNetworkName,
21167
+ bitcoinRpcUrl: broadcastUrl,
21168
+ ...this.config.debug ? {
21169
+ debug: (level, msg, data) => {
21170
+ if (level === "warn")
21171
+ log.warn(msg, { data });
21172
+ else if (level === "error")
21173
+ log.error(msg, { data });
21174
+ else
21175
+ log.info(msg, { data });
21176
+ }
21177
+ } : {}
21178
+ });
21179
+ if (this.config.debug) {
21180
+ log.info("\u2705 Standalone Phase 2 broadcast successful", { txid: btcTxid });
21181
+ }
21182
+ return {
21183
+ success: true,
21184
+ txid: btcTxid,
21185
+ amountSent: signResult.userReceivesAmount,
21186
+ networkFee: signResult.networkFee,
21187
+ destination: envelope.targetAddress
21188
+ };
21189
+ } catch (broadcastError) {
21190
+ return {
21191
+ success: false,
21192
+ error: `BTC broadcast failed: ${broadcastError instanceof Error ? broadcastError.message : String(broadcastError)}`
21193
+ };
21194
+ }
21195
+ }
21196
+ async executeBTCWithdrawal(request) {
21197
+ this.ensureInitialized();
21198
+ if (this.config.mode === "service" && !this.config.serviceEndpoint) {
20858
21199
  return { success: false, error: "serviceEndpoint required for BTC withdrawal" };
20859
21200
  }
20860
21201
  if (!request.positionId) {
@@ -20909,6 +21250,15 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20909
21250
  signer,
20910
21251
  ...request.utxos ? { utxos: request.utxos } : {}
20911
21252
  });
21253
+ if (this.config.mode !== "service") {
21254
+ return await this.executeBTCWithdrawalStandalonePhase2(
21255
+ request,
21256
+ envelope,
21257
+ txid,
21258
+ vout,
21259
+ chainId
21260
+ );
21261
+ }
20912
21262
  const headers = {
20913
21263
  "Content-Type": "application/json",
20914
21264
  ...await this.getAuthHeader()
@@ -21168,6 +21518,41 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21168
21518
  )
21169
21519
  }));
21170
21520
  }
21521
+ /**
21522
+ * Reconcile every pending withdrawal against BITCOIN truth (incident
21523
+ * 2026-07-22): `getPendingWithdrawals` only reflects on-chain
21524
+ * authorizations, and the contract can never know whether the Phase-2 BTC
21525
+ * broadcast happened. Callers MUST use the returned `status` to decide what
21526
+ * to offer:
21527
+ * - EXECUTABLE → offer Execute (the only status that may).
21528
+ * - EXECUTED → auto-clear; `spendingTxid` is the completion proof.
21529
+ * - SPENT_MISMATCH → unexecutable; offer cancelPendingWithdrawal.
21530
+ * - CORRUPT → authorization contradicts the chain (e.g. declared
21531
+ * satoshis ≠ real output value); offer Cancel &
21532
+ * re-request — Execute can only die at the signer guard.
21533
+ * - UNFUNDED → funding tx unknown/unconfirmed; wait.
21534
+ *
21535
+ * @param opts.esploraBaseUrl Esplora API base (e.g. the api proxy
21536
+ * `/v1/proxy/esplora/<network>`). Falls back to
21537
+ * `config.bitcoinProviders[0].url`; throws when neither is configured.
21538
+ */
21539
+ async reconcilePendingWithdrawals(positionId, opts) {
21540
+ this.ensureInitialized();
21541
+ const esploraBaseUrl = opts?.esploraBaseUrl ?? this.config.bitcoinProviders?.[0]?.url;
21542
+ if (!esploraBaseUrl) {
21543
+ throw new SDKError({
21544
+ code: "SDK_ESPLORA_NOT_CONFIGURED",
21545
+ message: "reconcilePendingWithdrawals requires opts.esploraBaseUrl or config.bitcoinProviders[0].url",
21546
+ category: "CONFIGURATION" /* CONFIGURATION */,
21547
+ severity: "HIGH" /* HIGH */,
21548
+ originalError: new Error("esplora base URL not configured")
21549
+ });
21550
+ }
21551
+ const spends = await this.getPendingWithdrawals(positionId);
21552
+ return Promise.all(
21553
+ spends.map((spend) => reconcileAuthorizedSpend(spend, esploraBaseUrl))
21554
+ );
21555
+ }
21171
21556
  /**
21172
21557
  * Cancel a pending BTC withdrawal
21173
21558
  *
@@ -22226,6 +22611,25 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
22226
22611
  const chain = this.config.chain?.toLowerCase() || "";
22227
22612
  return chain.includes("mainnet");
22228
22613
  }
22614
+ /**
22615
+ * Local-lane activation, verified against the LIVE node at call time (not
22616
+ * the chainId cached at init — a provider that switched networks after
22617
+ * init fails the fresh probe). Zero RPC cost outside a lane process tree:
22618
+ * without LIT_LANE_GUARD=1 this short-circuits to false before any probe.
22619
+ */
22620
+ async isLocalLaneGuardActive() {
22621
+ if (!laneGuardEnvEnabled())
22622
+ return false;
22623
+ let provider;
22624
+ try {
22625
+ provider = this.config.provider ?? this.getProviderOrThrow();
22626
+ } catch {
22627
+ return false;
22628
+ }
22629
+ return isLaneGuardActiveOnProvider(
22630
+ provider
22631
+ );
22632
+ }
22229
22633
  /**
22230
22634
  * Get Bitcoin network based on chain.
22231
22635
  *
@@ -23047,6 +23451,7 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
23047
23451
  createLoanQuery,
23048
23452
  createPKPManager,
23049
23453
  createWithdrawalAddressModule,
23454
+ defaultHttpGetJson,
23050
23455
  failure,
23051
23456
  fetchProtocolPauseStatus,
23052
23457
  firstSuccess,
@@ -23070,6 +23475,7 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
23070
23475
  mapError,
23071
23476
  match,
23072
23477
  numericToPositionStatus,
23478
+ reconcileAuthorizedSpend,
23073
23479
  setPositionDelegate,
23074
23480
  success,
23075
23481
  toPromise,