@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/browser/dist/browser.js +1 -1
- package/dist/deployments.js +32 -30
- package/dist/deployments.mjs +32 -30
- package/dist/index.d.ts +2 -0
- package/dist/index.js +442 -36
- package/dist/index.mjs +441 -37
- package/dist/interfaces/chunks/config.i.d.ts +12 -0
- package/dist/modules/diamond-hands-sdk.d.ts +41 -0
- package/dist/utils/lane-guard.utils.d.ts +36 -0
- package/dist/utils/quantum-submission.utils.d.ts +8 -0
- package/dist/utils/withdrawal-reconciliation.utils.d.ts +64 -0
- package/package.json +4 -4
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:
|
|
575
|
-
timestamp: "2026-
|
|
574
|
+
chainId: 1337,
|
|
575
|
+
timestamp: "2026-07-20T19:45:58.514Z",
|
|
576
576
|
deployer: "",
|
|
577
577
|
contracts: {
|
|
578
|
-
MessageHashBuilder: "
|
|
579
|
-
|
|
578
|
+
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
579
|
+
LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
|
|
580
|
+
UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
|
580
581
|
UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
581
582
|
UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
582
|
-
PositionManagerCoreModule: "
|
|
583
|
-
TermManagerModule: "
|
|
584
|
-
LoanOperationsManagerModule: "
|
|
585
|
-
BTCSpendAuthorizer: "
|
|
586
|
-
CollateralManagerModule: "
|
|
587
|
-
LiquidationManagerModule: "
|
|
588
|
-
CircuitBreakerModule: "
|
|
589
|
-
AdminModule: "
|
|
590
|
-
PositionManagerViews: "
|
|
591
|
-
PositionManager: "
|
|
592
|
-
OperationAuthorizationRegistry: "
|
|
593
|
-
PKPValidation: "
|
|
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: "
|
|
598
|
-
|
|
598
|
+
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
599
|
+
LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
|
|
600
|
+
UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
|
599
601
|
UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
600
602
|
UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
601
|
-
PositionManagerCoreModule: "
|
|
602
|
-
TermManagerModule: "
|
|
603
|
-
LoanOperationsManagerModule: "
|
|
604
|
-
BTCSpendAuthorizer: "
|
|
605
|
-
CollateralManagerModule: "
|
|
606
|
-
LiquidationManagerModule: "
|
|
607
|
-
CircuitBreakerModule: "
|
|
608
|
-
AdminModule: "
|
|
609
|
-
PositionManagerViews: "
|
|
610
|
-
PositionManager: "
|
|
611
|
-
OperationAuthorizationRegistry: "
|
|
612
|
-
PKPValidation: "
|
|
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
|
-
|
|
20850
|
-
|
|
20851
|
-
|
|
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:
|
|
21076
|
+
error: `Unsupported chainId ${chainId} for standalone BTC withdrawal (supported: 1, 11155111, 1337, 31337)`
|
|
20855
21077
|
};
|
|
20856
21078
|
}
|
|
20857
|
-
|
|
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,
|