@gvnrdao/dh-sdk 0.0.305 → 0.0.306

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
@@ -577,45 +577,47 @@ var init_deployment_addresses = __esm({
577
577
  };
578
578
  LOCALHOST_DEPLOYMENT = {
579
579
  network: "localhost",
580
- chainId: 31337,
581
- timestamp: "2026-06-11T12:30:59.589Z",
580
+ chainId: 1337,
581
+ timestamp: "2026-07-20T19:45:58.514Z",
582
582
  deployer: "",
583
583
  contracts: {
584
- MessageHashBuilder: "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
585
- UpgradeValidator: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
584
+ MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
585
+ LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
586
+ UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
586
587
  UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
587
588
  UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
588
- PositionManagerCoreModule: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
589
- TermManagerModule: "0x9A676e781A523b5d0C0e43731313A708CB607508",
590
- LoanOperationsManagerModule: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
591
- BTCSpendAuthorizer: "0x59b670e9fA9D0A427751Af201D676719a970857b",
592
- CollateralManagerModule: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
593
- LiquidationManagerModule: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
594
- CircuitBreakerModule: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
595
- AdminModule: "0xc5a5C42992dECbae36851359345FE25997F5C42d",
596
- PositionManagerViews: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
597
- PositionManager: "0xf5059a5D33d5853360D16C683c16e67980206f36",
598
- OperationAuthorizationRegistry: "0xF8e31cb472bc70500f08Cd84917E5A1912Ec8397",
599
- PKPValidation: "0xc0F115A19107322cFBf1cDBC7ea011C19EbDB4F8"
589
+ PositionManagerCoreModule: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
590
+ TermManagerModule: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
591
+ LoanOperationsManagerModule: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
592
+ BTCSpendAuthorizer: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d",
593
+ CollateralManagerModule: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
594
+ LiquidationManagerModule: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319",
595
+ CircuitBreakerModule: "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
596
+ AdminModule: "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
597
+ PositionManagerViews: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
598
+ PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
599
+ OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
600
+ PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
600
601
  }
601
602
  };
602
603
  LOCALHOST_CONTRACTS = {
603
- MessageHashBuilder: "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
604
- UpgradeValidator: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
604
+ MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
605
+ LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
606
+ UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
605
607
  UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
606
608
  UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
607
- PositionManagerCoreModule: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
608
- TermManagerModule: "0x9A676e781A523b5d0C0e43731313A708CB607508",
609
- LoanOperationsManagerModule: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
610
- BTCSpendAuthorizer: "0x59b670e9fA9D0A427751Af201D676719a970857b",
611
- CollateralManagerModule: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
612
- LiquidationManagerModule: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
613
- CircuitBreakerModule: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
614
- AdminModule: "0xc5a5C42992dECbae36851359345FE25997F5C42d",
615
- PositionManagerViews: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
616
- PositionManager: "0xf5059a5D33d5853360D16C683c16e67980206f36",
617
- OperationAuthorizationRegistry: "0xF8e31cb472bc70500f08Cd84917E5A1912Ec8397",
618
- PKPValidation: "0xc0F115A19107322cFBf1cDBC7ea011C19EbDB4F8"
609
+ PositionManagerCoreModule: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
610
+ TermManagerModule: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
611
+ LoanOperationsManagerModule: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
612
+ BTCSpendAuthorizer: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d",
613
+ CollateralManagerModule: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
614
+ LiquidationManagerModule: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319",
615
+ CircuitBreakerModule: "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
616
+ AdminModule: "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
617
+ PositionManagerViews: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
618
+ PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
619
+ OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
620
+ PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
619
621
  };
620
622
  ALL_DEPLOYMENTS = {
621
623
  sepolia: SEPOLIA_DEPLOYMENT,
@@ -5534,7 +5536,12 @@ var init_network_configs = __esm({
5534
5536
  NETWORK_CONFIGS = {
5535
5537
  1: getMainnetConfig(),
5536
5538
  11155111: getSepoliaConfig(),
5537
- 1337: getLocalhostConfig()
5539
+ 1337: getLocalhostConfig(),
5540
+ // In-process hardhat default chainId — same localhost deployment as 1337
5541
+ // (`npx hardhat node`), so consumers on either local chain resolve config.
5542
+ // chainId overridden so the resolved config self-describes the chain that
5543
+ // was actually looked up (downstream bind/log/validate reads it).
5544
+ 31337: { ...getLocalhostConfig(), chainId: 31337 }
5538
5545
  };
5539
5546
  DEFAULT_NETWORK_CONFIG = {
5540
5547
  /** Fallback when chain is unknown — production Chipotle. */
@@ -6874,12 +6881,20 @@ async function assertSafeQuantumSubmission(opts) {
6874
6881
  ...opts.sleep ? { sleep: opts.sleep } : {}
6875
6882
  };
6876
6883
  await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
6884
+ const SAME_BLOCK_INTERACTION_SELECTOR = "0x3b53d1db";
6885
+ const laneGuardActive = opts.laneGuardActive === true;
6877
6886
  for (let sim = 1; ; sim++) {
6878
6887
  try {
6879
6888
  await provider.call({ to, from, data });
6880
6889
  break;
6881
6890
  } catch (simError) {
6882
6891
  const decoded = decodeQuantumRevert(simError);
6892
+ if (laneGuardActive && decoded.selector === SAME_BLOCK_INTERACTION_SELECTOR) {
6893
+ onDebug(
6894
+ "\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"
6895
+ );
6896
+ break;
6897
+ }
6883
6898
  if (decoded.selector === DEAD_ZONE_VIOLATION_SELECTOR && sim <= maxResimulations) {
6884
6899
  const sigQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
6885
6900
  const realCurrentQuantum = Math.floor(now() / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
@@ -6915,6 +6930,28 @@ async function assertSafeQuantumSubmission(opts) {
6915
6930
  await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
6916
6931
  }
6917
6932
 
6933
+ // src/utils/lane-guard.utils.ts
6934
+ var HARDHAT_CHAIN_IDS = /* @__PURE__ */ new Set([1337, 31337]);
6935
+ function laneGuardEnvEnabled() {
6936
+ return typeof process !== "undefined" && process.env?.LIT_LANE_GUARD === "1";
6937
+ }
6938
+ async function isLaneGuardActiveOnProvider(provider) {
6939
+ if (!laneGuardEnvEnabled())
6940
+ return false;
6941
+ if (!provider || typeof provider.send !== "function")
6942
+ return false;
6943
+ try {
6944
+ const [chainIdHex, clientVersion] = await Promise.all([
6945
+ provider.send("eth_chainId", []),
6946
+ provider.send("web3_clientVersion", [])
6947
+ ]);
6948
+ const chainId = Number(chainIdHex);
6949
+ return HARDHAT_CHAIN_IDS.has(chainId) && typeof clientVersion === "string" && clientVersion.toLowerCase().includes("hardhat");
6950
+ } catch {
6951
+ return false;
6952
+ }
6953
+ }
6954
+
6918
6955
  // src/utils/btc-withdrawal-message.ts
6919
6956
  import { keccak256 as keccak2563, toUtf8Bytes as toUtf8Bytes3, solidityPackedKeccak256 as solidityPackedKeccak2563, getBytes as getBytes3, zeroPadValue } from "ethers";
6920
6957
  var QUANTUM_SECONDS = 60;
@@ -15776,7 +15813,7 @@ function createMockTokenManager(config) {
15776
15813
 
15777
15814
  // src/modules/diamond-hands-sdk.ts
15778
15815
  var import_dh_lit_actions2 = __toESM(require_pkg_src());
15779
- import { LitOps } from "@gvnrdao/dh-lit-ops";
15816
+ import { LitOps, broadcastSignedBitcoinTransaction } from "@gvnrdao/dh-lit-ops";
15780
15817
 
15781
15818
  // src/utils/telegram-messaging.utils.ts
15782
15819
  var TelegramBot = null;
@@ -16261,6 +16298,10 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16261
16298
  this.litOps = new LitOps({
16262
16299
  mode: config.mode || "standalone",
16263
16300
  network: config.litNetwork || DEFAULT_LIT_NETWORK,
16301
+ // Completes the module's chainId resolution chain (jsParams shapes →
16302
+ // config.chainId) — e.g. the lane validator-override chain binding
16303
+ // reads it when a jsParams shape lacks chainId.
16304
+ chainId: config.chainId,
16264
16305
  signer: config.mode === "standalone" ? config.litOpsSigner : void 0,
16265
16306
  serviceEndpoint: isServiceModeConfig(config) ? config.serviceEndpoint : void 0,
16266
16307
  // Hand lit-ops a session-aware auth getter so every request it makes
@@ -16938,6 +16979,19 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16938
16979
  }
16939
16980
  const MAX_FULL_RETRIES = 3;
16940
16981
  for (let fullRetry = 1; fullRetry <= MAX_FULL_RETRIES; fullRetry++) {
16982
+ if (await this.isLocalLaneGuardActive()) {
16983
+ const phase = Math.floor(Date.now() / 1e3) % 60;
16984
+ if (phase > 30) {
16985
+ const waitMs = (60 - phase + 1) * 1e3;
16986
+ if (this.config.debug) {
16987
+ log.info(
16988
+ `\u23F3 Quantum-phase alignment (lane-only): ${phase}s into the quantum \u2014 waiting ${Math.ceil(waitMs / 1e3)}s for the next boundary before authorization`,
16989
+ {}
16990
+ );
16991
+ }
16992
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
16993
+ }
16994
+ }
16941
16995
  if (fullRetry > 1 && this.config.debug) {
16942
16996
  log.info(
16943
16997
  `\u{1F504} Full mint retry ${fullRetry}/${MAX_FULL_RETRIES} due to timestamp staleness...`,
@@ -17686,6 +17740,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
17686
17740
  from: fromAddress,
17687
17741
  data: mintCalldata,
17688
17742
  quantumTimestamp: Number(validationResponse.timestamp),
17743
+ laneGuardActive: await this.isLocalLaneGuardActive(),
17689
17744
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
17690
17745
  });
17691
17746
  } catch (simError) {
@@ -18981,6 +19036,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
18981
19036
  from: await extendSigner.getAddress(),
18982
19037
  data: extendCalldata,
18983
19038
  quantumTimestamp: Number(quantumTimestamp),
19039
+ laneGuardActive: await this.isLocalLaneGuardActive(),
18984
19040
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
18985
19041
  });
18986
19042
  } catch (simError) {
@@ -19881,6 +19937,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
19881
19937
  from: paymentFrom,
19882
19938
  data: paymentCalldata,
19883
19939
  quantumTimestamp: Number(quantumTimestamp),
19940
+ laneGuardActive: await this.isLocalLaneGuardActive(),
19884
19941
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
19885
19942
  });
19886
19943
  } catch (simError) {
@@ -20620,6 +20677,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20620
20677
  from: await withdrawSigner.getAddress(),
20621
20678
  data: withdrawCalldata,
20622
20679
  quantumTimestamp: Number(withdrawalParams.quantumTimestamp),
20680
+ laneGuardActive: await this.isLocalLaneGuardActive(),
20623
20681
  onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
20624
20682
  });
20625
20683
  tx = await sendEip1559Transaction({
@@ -20774,15 +20832,180 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20774
20832
  * @param request - Execution request with positionId, utxoIdentifier, and networkFee
20775
20833
  * @returns Execution result with Bitcoin txid
20776
20834
  */
20777
- async executeBTCWithdrawal(request) {
20778
- this.ensureInitialized();
20779
- if (this.config.mode !== "service") {
20835
+ /**
20836
+ * STANDALONE Phase 2: sign + broadcast the withdrawal Bitcoin transaction
20837
+ * in-process — the same orchestration lit-ops-server performs, composed
20838
+ * from the same primitives (`litOps.signBTCTransaction`, which has always
20839
+ * had a standalone path, and `broadcastSignedBitcoinTransaction`).
20840
+ * Both modes must support withdrawal execution (Master mandate 2026-07-21).
20841
+ *
20842
+ * The service path's post-broadcast CRIT-2 re-verification exists to catch
20843
+ * a LYING SERVER; here the broadcast happens locally from the signatures we
20844
+ * just obtained, so the esplora-returned txid is already first-hand.
20845
+ */
20846
+ async executeBTCWithdrawalStandalonePhase2(request, envelope, txid, vout, chainId) {
20847
+ const positionDetails = await this.getPositionDetailsView(
20848
+ request.positionId
20849
+ );
20850
+ if (!positionDetails) {
20851
+ return { success: false, error: `Position ${request.positionId} not found` };
20852
+ }
20853
+ const pkpId = positionDetails.pkpId;
20854
+ const vaultAddress = positionDetails.vaultAddress;
20855
+ if (!pkpId || pkpId === ZeroHash) {
20856
+ return { success: false, error: "Position has no PKP" };
20857
+ }
20858
+ if (!vaultAddress) {
20859
+ return { success: false, error: "Position has no vault address" };
20860
+ }
20861
+ const pkpCache = this.cacheManager.getCache("pkp-data", {
20862
+ maxSize: 500,
20863
+ ttlMs: 24 * 60 * 60 * 1e3
20864
+ });
20865
+ let publicKey = pkpCache.get(request.positionId)?.publicKey;
20866
+ if (!publicKey) {
20867
+ const derived = await this.litOps.deriveDiamondHandsLoanPkpPublicKey({
20868
+ pkpId
20869
+ });
20870
+ publicKey = derived.publicKey;
20871
+ }
20872
+ if (!publicKey) {
20873
+ return { success: false, error: "Failed to resolve PKP public key" };
20874
+ }
20875
+ const chipotlePkpAddress = computeAddress(publicKey);
20876
+ const STANDALONE_CHAIN_NAMES = {
20877
+ 1: "ethereum",
20878
+ 11155111: "sepolia",
20879
+ 1337: "hardhat",
20880
+ 31337: "hardhat"
20881
+ };
20882
+ const chainName = STANDALONE_CHAIN_NAMES[chainId];
20883
+ if (!chainName) {
20780
20884
  return {
20781
20885
  success: false,
20782
- error: "BTC withdrawal Phase 2 requires service mode. Set sdkConfig.mode = 'service' and serviceEndpoint to a lit-ops-server."
20886
+ error: `Unsupported chainId ${chainId} for standalone BTC withdrawal (supported: 1, 11155111, 1337, 31337)`
20783
20887
  };
20784
20888
  }
20785
- if (!this.config.serviceEndpoint) {
20889
+ const bitcoinNetworkName = chainId === 1 ? "mainnet" : "regtest";
20890
+ const bitcoin = await import("bitcoinjs-lib");
20891
+ const bitcoinNetwork = chainId === 1 ? bitcoin.networks.bitcoin : chainId === 1337 || chainId === 31337 ? bitcoin.networks.regtest : bitcoin.networks.testnet;
20892
+ let scriptPubKeyBuffer;
20893
+ if (vaultAddress.startsWith("bcrt1q")) {
20894
+ const bech322 = await import("bech32");
20895
+ const decoded = bech322.bech32.decode(vaultAddress);
20896
+ const witnessProgram = Buffer.from(
20897
+ bech322.bech32.fromWords(decoded.words.slice(1))
20898
+ );
20899
+ if (witnessProgram.length !== 20) {
20900
+ return {
20901
+ success: false,
20902
+ error: `Vault address ${vaultAddress} decodes to a ${witnessProgram.length}-byte witness program \u2014 expected 20-byte P2WPKH`
20903
+ };
20904
+ }
20905
+ scriptPubKeyBuffer = Buffer.concat([
20906
+ Buffer.from([0, witnessProgram.length]),
20907
+ witnessProgram
20908
+ ]);
20909
+ } else {
20910
+ scriptPubKeyBuffer = bitcoin.address.toOutputScript(
20911
+ vaultAddress,
20912
+ bitcoinNetwork
20913
+ );
20914
+ }
20915
+ const scriptPubKey = scriptPubKeyBuffer.toString("hex");
20916
+ const executionUtxos = (envelope.utxos && envelope.utxos.length ? envelope.utxos : [{ txid, vout, value: request.utxoSatoshis }]).map((u) => ({ ...u, scriptPubKey }));
20917
+ const dc = this.config.contractAddresses || {};
20918
+ const signerContractAddresses = {
20919
+ LoanOperationsManagerModule: dc.loanOperationsManager ?? "",
20920
+ PositionManager: dc.positionManager ?? "",
20921
+ BTCSpendAuthorizer: dc.btcSpendAuthorizer ?? "",
20922
+ PositionDelegateRegistry: dc.positionDelegateRegistry ?? "",
20923
+ OperationAuthorizationRegistry: dc.operationAuthorizationRegistry ?? "",
20924
+ ...dc.bitcoinProviderRegistry ? { BitcoinProviderRegistry: dc.bitcoinProviderRegistry } : {}
20925
+ };
20926
+ const signResult = await this.litOps.signBTCTransaction({
20927
+ auth: {
20928
+ positionId: envelope.positionId,
20929
+ utxo: { txid, vout, value: request.utxoSatoshis, scriptPubKey },
20930
+ utxos: executionUtxos,
20931
+ networkFee: envelope.networkFee,
20932
+ targetAddress: envelope.targetAddress,
20933
+ targetAmount: envelope.targetAmount,
20934
+ timestamp: envelope.timestamp,
20935
+ signature: envelope.userSignature,
20936
+ chainId
20937
+ },
20938
+ publicKey,
20939
+ pkpId: chipotlePkpAddress,
20940
+ chain: chainName,
20941
+ bitcoinNetwork: bitcoinNetworkName,
20942
+ rpcUrl: this.config.ethRpcUrl,
20943
+ contractAddresses: signerContractAddresses
20944
+ });
20945
+ if (!signResult.success) {
20946
+ return {
20947
+ success: false,
20948
+ error: signResult.error ?? "BTC transaction signing failed"
20949
+ };
20950
+ }
20951
+ if (!signResult.unsignedTxHex || !signResult.signatures?.length) {
20952
+ return {
20953
+ success: false,
20954
+ error: "BTC signing returned incomplete data (missing unsignedTxHex/signatures)"
20955
+ };
20956
+ }
20957
+ if (!signResult.userReceivesAmount) {
20958
+ return { success: false, error: "BTC signing did not return userReceivesAmount" };
20959
+ }
20960
+ const broadcastUrl = this.config.bitcoinProviders?.[0]?.url;
20961
+ if (!broadcastUrl) {
20962
+ return {
20963
+ success: false,
20964
+ error: "Standalone Phase 2 broadcast requires config.bitcoinProviders[0].url (esplora base)"
20965
+ };
20966
+ }
20967
+ try {
20968
+ const { txid: btcTxid } = await broadcastSignedBitcoinTransaction({
20969
+ signatures: signResult.signatures,
20970
+ utxos: executionUtxos,
20971
+ pkpPublicKey: signResult.pkpPublicKey ?? publicKey,
20972
+ destination: envelope.targetAddress,
20973
+ userReceivesAmount: signResult.userReceivesAmount,
20974
+ changeAmount: signResult.changeAmount,
20975
+ pkpBtcAddress: signResult.pkpBtcAddress,
20976
+ bitcoinNetwork: bitcoinNetworkName,
20977
+ bitcoinRpcUrl: broadcastUrl,
20978
+ ...this.config.debug ? {
20979
+ debug: (level, msg, data) => {
20980
+ if (level === "warn")
20981
+ log.warn(msg, { data });
20982
+ else if (level === "error")
20983
+ log.error(msg, { data });
20984
+ else
20985
+ log.info(msg, { data });
20986
+ }
20987
+ } : {}
20988
+ });
20989
+ if (this.config.debug) {
20990
+ log.info("\u2705 Standalone Phase 2 broadcast successful", { txid: btcTxid });
20991
+ }
20992
+ return {
20993
+ success: true,
20994
+ txid: btcTxid,
20995
+ amountSent: signResult.userReceivesAmount,
20996
+ networkFee: signResult.networkFee,
20997
+ destination: envelope.targetAddress
20998
+ };
20999
+ } catch (broadcastError) {
21000
+ return {
21001
+ success: false,
21002
+ error: `BTC broadcast failed: ${broadcastError instanceof Error ? broadcastError.message : String(broadcastError)}`
21003
+ };
21004
+ }
21005
+ }
21006
+ async executeBTCWithdrawal(request) {
21007
+ this.ensureInitialized();
21008
+ if (this.config.mode === "service" && !this.config.serviceEndpoint) {
20786
21009
  return { success: false, error: "serviceEndpoint required for BTC withdrawal" };
20787
21010
  }
20788
21011
  if (!request.positionId) {
@@ -20837,6 +21060,15 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20837
21060
  signer,
20838
21061
  ...request.utxos ? { utxos: request.utxos } : {}
20839
21062
  });
21063
+ if (this.config.mode !== "service") {
21064
+ return await this.executeBTCWithdrawalStandalonePhase2(
21065
+ request,
21066
+ envelope,
21067
+ txid,
21068
+ vout,
21069
+ chainId
21070
+ );
21071
+ }
20840
21072
  const headers = {
20841
21073
  "Content-Type": "application/json",
20842
21074
  ...await this.getAuthHeader()
@@ -22154,6 +22386,25 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
22154
22386
  const chain = this.config.chain?.toLowerCase() || "";
22155
22387
  return chain.includes("mainnet");
22156
22388
  }
22389
+ /**
22390
+ * Local-lane activation, verified against the LIVE node at call time (not
22391
+ * the chainId cached at init — a provider that switched networks after
22392
+ * init fails the fresh probe). Zero RPC cost outside a lane process tree:
22393
+ * without LIT_LANE_GUARD=1 this short-circuits to false before any probe.
22394
+ */
22395
+ async isLocalLaneGuardActive() {
22396
+ if (!laneGuardEnvEnabled())
22397
+ return false;
22398
+ let provider;
22399
+ try {
22400
+ provider = this.config.provider ?? this.getProviderOrThrow();
22401
+ } catch {
22402
+ return false;
22403
+ }
22404
+ return isLaneGuardActiveOnProvider(
22405
+ provider
22406
+ );
22407
+ }
22157
22408
  /**
22158
22409
  * Get Bitcoin network based on chain.
22159
22410
  *
@@ -89,6 +89,18 @@ interface BaseSDKConfig {
89
89
  litActionCid?: string;
90
90
  litNodeConnectTimeoutMs?: number;
91
91
  subgraphUrl?: string;
92
+ /**
93
+ * Standalone/local-lane Bitcoin providers (esplora bases). LIT Actions pull
94
+ * every registered provider from the on-chain BitcoinProviderRegistry and
95
+ * the server's executePendingWithdrawal handles service-mode broadcast —
96
+ * this field never feeds those paths. It drives standalone-mode flows only:
97
+ * the standalone Phase 2 withdrawal broadcast, hardhat-lane per-call
98
+ * overrides, and `network` as a bitcoinNetwork inference fallback.
99
+ */
100
+ bitcoinProviders?: Array<{
101
+ url: string;
102
+ network?: "mainnet" | "testnet" | "regtest";
103
+ }>;
92
104
  priceProviders?: PriceProviderConfig[];
93
105
  /** PKP Ethereum address for signed price oracle (Lit jsParams pkpId); optional */
94
106
  priceOraclePkpId?: string;
@@ -475,6 +475,18 @@ export declare class DiamondHandsSDK {
475
475
  * @param request - Execution request with positionId, utxoIdentifier, and networkFee
476
476
  * @returns Execution result with Bitcoin txid
477
477
  */
478
+ /**
479
+ * STANDALONE Phase 2: sign + broadcast the withdrawal Bitcoin transaction
480
+ * in-process — the same orchestration lit-ops-server performs, composed
481
+ * from the same primitives (`litOps.signBTCTransaction`, which has always
482
+ * had a standalone path, and `broadcastSignedBitcoinTransaction`).
483
+ * Both modes must support withdrawal execution (Master mandate 2026-07-21).
484
+ *
485
+ * The service path's post-broadcast CRIT-2 re-verification exists to catch
486
+ * a LYING SERVER; here the broadcast happens locally from the signatures we
487
+ * just obtained, so the esplora-returned txid is already first-hand.
488
+ */
489
+ private executeBTCWithdrawalStandalonePhase2;
478
490
  executeBTCWithdrawal(request: {
479
491
  positionId: string;
480
492
  utxoIdentifier: string;
@@ -944,6 +956,13 @@ export declare class DiamondHandsSDK {
944
956
  * Check if running on production network
945
957
  */
946
958
  private isProductionNetwork;
959
+ /**
960
+ * Local-lane activation, verified against the LIVE node at call time (not
961
+ * the chainId cached at init — a provider that switched networks after
962
+ * init fails the fresh probe). Zero RPC cost outside a lane process tree:
963
+ * without LIT_LANE_GUARD=1 this short-circuits to false before any probe.
964
+ */
965
+ private isLocalLaneGuardActive;
947
966
  /**
948
967
  * Get Bitcoin network based on chain.
949
968
  *
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Local-verification-lane guard — the ONLY file in the SDK allowed to read
3
+ * the `LIT_LANE_*` environment (enforced by
4
+ * `contracts/tests/unit/lane-guard-env-allowlist.unit.test.ts`).
5
+ *
6
+ * The lane hooks (quantum-phase alignment before mint authorization, the
7
+ * simulated-SameBlockInteraction tolerance) exist solely to absorb local
8
+ * hardhat artifacts: tunnel latency and automine's stale-latest-block
9
+ * simulation. They are never correct on a live network, so activation is
10
+ * TRIPLY bound, verified against the live node at the moment of use:
11
+ *
12
+ * 1. `LIT_LANE_GUARD=1` — exported only by `scripts/lane-env-guard.sh`
13
+ * for the detached local lane's process tree, AND
14
+ * 2. a fresh `eth_chainId` probe (NOT ethers' init-time network cache, so a
15
+ * provider that switched networks after init cannot keep the hooks
16
+ * alive) returns a hardhat chainId (1337 `npx hardhat node`, 31337
17
+ * in-process default), AND
18
+ * 3. the node self-identifies as Hardhat via `web3_clientVersion` — a
19
+ * live/private network that merely reuses chainId 1337/31337 does not.
20
+ *
21
+ * Any probe failure, missing `send` capability, or unexpected answer is
22
+ * treated as NOT the lane — fail closed. The on-chain guards remain fully
23
+ * authoritative in every mode regardless.
24
+ */
25
+ /** Minimal raw-RPC seam — satisfied by ethers v6 JsonRpcApiProvider. */
26
+ export interface RawRpcSender {
27
+ send?(method: string, params: unknown[]): Promise<unknown>;
28
+ }
29
+ /** The env half of the guard: is the lane environment exported at all? */
30
+ export declare function laneGuardEnvEnabled(): boolean;
31
+ /**
32
+ * Full lane-guard check against the LIVE node. Cheap short-circuit (no RPC)
33
+ * when the lane env is not exported — the two probes only ever run inside a
34
+ * lane process tree.
35
+ */
36
+ export declare function isLaneGuardActiveOnProvider(provider: RawRpcSender | undefined | null): Promise<boolean>;
@@ -61,6 +61,14 @@ export interface SafeQuantumSubmissionOpts {
61
61
  sleep?: (ms: number) => Promise<void>;
62
62
  /** Debug sink; called with human-readable progress lines. */
63
63
  onDebug?: (msg: string) => void;
64
+ /**
65
+ * Local-lane activation, computed by the caller at submission time via
66
+ * `isLaneGuardActiveOnProvider` (lane env exported + live node probes as a
67
+ * hardhat chain/client). Only consulted by the local-lane
68
+ * SameBlockInteraction tolerance; omitted or false keeps the fail-fast
69
+ * behavior on every decodable revert.
70
+ */
71
+ laneGuardActive?: boolean;
64
72
  }
65
73
  /**
66
74
  * Gate + pre-send simulation loop. Resolves when it is safe to broadcast;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.305",
3
+ "version": "0.0.306",
4
4
  "description": "TypeScript SDK for Diamond Hands Protocol - Bitcoin-backed lending with LIT Protocol PKPs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -82,8 +82,8 @@
82
82
  },
83
83
  "sideEffects": false,
84
84
  "dependencies": {
85
- "@gvnrdao/dh-lit-actions": "^0.0.315",
86
- "@gvnrdao/dh-lit-ops": "^0.0.305",
85
+ "@gvnrdao/dh-lit-actions": "0.0.315",
86
+ "@gvnrdao/dh-lit-ops": "0.0.306",
87
87
  "@noble/hashes": "^1.5.0",
88
88
  "axios": "^1.17.0",
89
89
  "bech32": "^2.0.0",
@@ -92,7 +92,7 @@
92
92
  "bs58check": "^3.0.1",
93
93
  "crypto-js": "^4.2.0",
94
94
  "dotenv": "^17.4.2",
95
- "ethers": "^6.16.0",
95
+ "ethers": "6.16.0",
96
96
  "valibot": "^1.1.0"
97
97
  },
98
98
  "devDependencies": {