@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/browser/dist/browser.js +1 -1
- package/dist/deployments.js +32 -30
- package/dist/deployments.mjs +32 -30
- package/dist/index.js +287 -36
- package/dist/index.mjs +288 -37
- package/dist/interfaces/chunks/config.i.d.ts +12 -0
- package/dist/modules/diamond-hands-sdk.d.ts +19 -0
- package/dist/utils/lane-guard.utils.d.ts +36 -0
- package/dist/utils/quantum-submission.utils.d.ts +8 -0
- package/package.json +4 -4
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:
|
|
581
|
-
timestamp: "2026-
|
|
580
|
+
chainId: 1337,
|
|
581
|
+
timestamp: "2026-07-20T19:45:58.514Z",
|
|
582
582
|
deployer: "",
|
|
583
583
|
contracts: {
|
|
584
|
-
MessageHashBuilder: "
|
|
585
|
-
|
|
584
|
+
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
585
|
+
LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
|
|
586
|
+
UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
|
586
587
|
UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
587
588
|
UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
588
|
-
PositionManagerCoreModule: "
|
|
589
|
-
TermManagerModule: "
|
|
590
|
-
LoanOperationsManagerModule: "
|
|
591
|
-
BTCSpendAuthorizer: "
|
|
592
|
-
CollateralManagerModule: "
|
|
593
|
-
LiquidationManagerModule: "
|
|
594
|
-
CircuitBreakerModule: "
|
|
595
|
-
AdminModule: "
|
|
596
|
-
PositionManagerViews: "
|
|
597
|
-
PositionManager: "
|
|
598
|
-
OperationAuthorizationRegistry: "
|
|
599
|
-
PKPValidation: "
|
|
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: "
|
|
604
|
-
|
|
604
|
+
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
605
|
+
LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
|
|
606
|
+
UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
|
605
607
|
UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
606
608
|
UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
607
|
-
PositionManagerCoreModule: "
|
|
608
|
-
TermManagerModule: "
|
|
609
|
-
LoanOperationsManagerModule: "
|
|
610
|
-
BTCSpendAuthorizer: "
|
|
611
|
-
CollateralManagerModule: "
|
|
612
|
-
LiquidationManagerModule: "
|
|
613
|
-
CircuitBreakerModule: "
|
|
614
|
-
AdminModule: "
|
|
615
|
-
PositionManagerViews: "
|
|
616
|
-
PositionManager: "
|
|
617
|
-
OperationAuthorizationRegistry: "
|
|
618
|
-
PKPValidation: "
|
|
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
|
-
|
|
20778
|
-
|
|
20779
|
-
|
|
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:
|
|
20886
|
+
error: `Unsupported chainId ${chainId} for standalone BTC withdrawal (supported: 1, 11155111, 1337, 31337)`
|
|
20783
20887
|
};
|
|
20784
20888
|
}
|
|
20785
|
-
|
|
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.
|
|
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": "
|
|
86
|
-
"@gvnrdao/dh-lit-ops": "
|
|
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": "
|
|
95
|
+
"ethers": "6.16.0",
|
|
96
96
|
"valibot": "^1.1.0"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|