@gvnrdao/dh-sdk 0.0.304 → 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 +38 -32
- package/dist/deployments.mjs +38 -32
- package/dist/index.js +326 -53
- package/dist/index.mjs +327 -54
- package/dist/interfaces/chunks/config.i.d.ts +12 -0
- package/dist/interfaces/chunks/loan-operations.i.d.ts +10 -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
|
@@ -444,9 +444,13 @@ var init_deployment_addresses = __esm({
|
|
|
444
444
|
reason: "Validator gas fix (#420): BitcoinAddressValidator packed-table lookups; VERSION 1.0.0 -> 1.0.1"
|
|
445
445
|
},
|
|
446
446
|
UCDController: {
|
|
447
|
+
previousImplementation: "0x16f94AcC78cE87bEeBCfF90552091B3EfC071E48",
|
|
447
448
|
newImplementation: "0x28B8cAB89D89B3F83581FD2290de2Ce84535bd0A",
|
|
448
|
-
|
|
449
|
-
|
|
449
|
+
upgradedAt: "2026-07-06T10:41:59.000Z",
|
|
450
|
+
upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7 (block 25472962)",
|
|
451
|
+
reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
|
|
452
|
+
executeTx: "0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7",
|
|
453
|
+
block: 25472962
|
|
450
454
|
},
|
|
451
455
|
PositionManagerCoreModule: {
|
|
452
456
|
previousImplementation: "0x6C8b0FB93F8d0239254aefaC24d4264cA2F8AAeB",
|
|
@@ -573,45 +577,47 @@ var init_deployment_addresses = __esm({
|
|
|
573
577
|
};
|
|
574
578
|
LOCALHOST_DEPLOYMENT = {
|
|
575
579
|
network: "localhost",
|
|
576
|
-
chainId:
|
|
577
|
-
timestamp: "2026-
|
|
580
|
+
chainId: 1337,
|
|
581
|
+
timestamp: "2026-07-20T19:45:58.514Z",
|
|
578
582
|
deployer: "",
|
|
579
583
|
contracts: {
|
|
580
|
-
MessageHashBuilder: "
|
|
581
|
-
|
|
584
|
+
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
585
|
+
LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
|
|
586
|
+
UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
|
582
587
|
UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
583
588
|
UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
584
|
-
PositionManagerCoreModule: "
|
|
585
|
-
TermManagerModule: "
|
|
586
|
-
LoanOperationsManagerModule: "
|
|
587
|
-
BTCSpendAuthorizer: "
|
|
588
|
-
CollateralManagerModule: "
|
|
589
|
-
LiquidationManagerModule: "
|
|
590
|
-
CircuitBreakerModule: "
|
|
591
|
-
AdminModule: "
|
|
592
|
-
PositionManagerViews: "
|
|
593
|
-
PositionManager: "
|
|
594
|
-
OperationAuthorizationRegistry: "
|
|
595
|
-
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"
|
|
596
601
|
}
|
|
597
602
|
};
|
|
598
603
|
LOCALHOST_CONTRACTS = {
|
|
599
|
-
MessageHashBuilder: "
|
|
600
|
-
|
|
604
|
+
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
605
|
+
LITActionValidator: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
|
|
606
|
+
UpgradeValidator: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
|
601
607
|
UCDToken: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
602
608
|
UCDController: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
603
|
-
PositionManagerCoreModule: "
|
|
604
|
-
TermManagerModule: "
|
|
605
|
-
LoanOperationsManagerModule: "
|
|
606
|
-
BTCSpendAuthorizer: "
|
|
607
|
-
CollateralManagerModule: "
|
|
608
|
-
LiquidationManagerModule: "
|
|
609
|
-
CircuitBreakerModule: "
|
|
610
|
-
AdminModule: "
|
|
611
|
-
PositionManagerViews: "
|
|
612
|
-
PositionManager: "
|
|
613
|
-
OperationAuthorizationRegistry: "
|
|
614
|
-
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"
|
|
615
621
|
};
|
|
616
622
|
ALL_DEPLOYMENTS = {
|
|
617
623
|
sepolia: SEPOLIA_DEPLOYMENT,
|
|
@@ -1293,7 +1299,7 @@ var require_pkg_src = __commonJS({
|
|
|
1293
1299
|
description: "Production Pkp Validator",
|
|
1294
1300
|
version: "1.0.0",
|
|
1295
1301
|
deployed: true,
|
|
1296
|
-
deployedAt:
|
|
1302
|
+
deployedAt: 1784569687492,
|
|
1297
1303
|
size: 4140,
|
|
1298
1304
|
hash: "4c0d65bf1f33cdac22a78fd8c4764319cc3fe00650e0fe7fa8a6090d9a318a7b",
|
|
1299
1305
|
validatorWalletAddress: "0x82db9f515957aa380b596c6ea2bd30cef181f236",
|
|
@@ -1370,17 +1376,17 @@ var require_pkg_src = __commonJS({
|
|
|
1370
1376
|
}
|
|
1371
1377
|
},
|
|
1372
1378
|
btcWithdrawal: {
|
|
1373
|
-
cid: "
|
|
1379
|
+
cid: "QmexvpfHPacjsVcqNHHLyJhSnnAPLgbWmWd9N3NDLDvYds",
|
|
1374
1380
|
authorizedCidHex: cidToHex(
|
|
1375
|
-
"
|
|
1381
|
+
"QmexvpfHPacjsVcqNHHLyJhSnnAPLgbWmWd9N3NDLDvYds"
|
|
1376
1382
|
),
|
|
1377
1383
|
name: "Btc Withdrawal",
|
|
1378
1384
|
description: "Production Btc Withdrawal",
|
|
1379
|
-
version: "1.2.
|
|
1385
|
+
version: "1.2.3",
|
|
1380
1386
|
deployed: true,
|
|
1381
|
-
deployedAt:
|
|
1382
|
-
size:
|
|
1383
|
-
hash: "
|
|
1387
|
+
deployedAt: 1784585746901,
|
|
1388
|
+
size: 92027,
|
|
1389
|
+
hash: "7b4f4eab1f0c08b8b5b1c9cdb1064dd403ec13f57765919f62dec67fb8ba3c69",
|
|
1384
1390
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1385
1391
|
pkp: {
|
|
1386
1392
|
publicKey: "0x043616787c5432415c24378c4ef48de2bcd6bb7b575b837e3cff09171802662da7105d79586c7659677a0ecbaddac4cce06cb2a11f69a16fa0c4d7002ac7d51a4d",
|
|
@@ -3752,12 +3758,23 @@ ${auth.clientId}`;
|
|
|
3752
3758
|
if (!data.vout || !Array.isArray(data.vout)) {
|
|
3753
3759
|
return null;
|
|
3754
3760
|
}
|
|
3755
|
-
return data.vout.map((output, index) =>
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
+
return data.vout.map((output, index) => {
|
|
3762
|
+
if (output.value === void 0) {
|
|
3763
|
+
throw new Error(
|
|
3764
|
+
`getTransactionOutputs(${txid}): output ${index} is missing its value \u2014 malformed provider response, refusing to return outputs that cannot be value-verified`
|
|
3765
|
+
);
|
|
3766
|
+
}
|
|
3767
|
+
assertSaneSatoshis(
|
|
3768
|
+
output.value,
|
|
3769
|
+
`getTransactionOutputs(${txid}) output ${index}`
|
|
3770
|
+
);
|
|
3771
|
+
return {
|
|
3772
|
+
index,
|
|
3773
|
+
scriptpubkey: output.scriptpubkey,
|
|
3774
|
+
scriptpubkey_address: output.scriptpubkey_address,
|
|
3775
|
+
value: output.value
|
|
3776
|
+
};
|
|
3777
|
+
});
|
|
3761
3778
|
} catch (error) {
|
|
3762
3779
|
clearTimeout(timeoutId);
|
|
3763
3780
|
if (isAbortError(error)) {
|
|
@@ -5519,7 +5536,12 @@ var init_network_configs = __esm({
|
|
|
5519
5536
|
NETWORK_CONFIGS = {
|
|
5520
5537
|
1: getMainnetConfig(),
|
|
5521
5538
|
11155111: getSepoliaConfig(),
|
|
5522
|
-
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 }
|
|
5523
5545
|
};
|
|
5524
5546
|
DEFAULT_NETWORK_CONFIG = {
|
|
5525
5547
|
/** Fallback when chain is unknown — production Chipotle. */
|
|
@@ -6859,12 +6881,20 @@ async function assertSafeQuantumSubmission(opts) {
|
|
|
6859
6881
|
...opts.sleep ? { sleep: opts.sleep } : {}
|
|
6860
6882
|
};
|
|
6861
6883
|
await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
|
|
6884
|
+
const SAME_BLOCK_INTERACTION_SELECTOR = "0x3b53d1db";
|
|
6885
|
+
const laneGuardActive = opts.laneGuardActive === true;
|
|
6862
6886
|
for (let sim = 1; ; sim++) {
|
|
6863
6887
|
try {
|
|
6864
6888
|
await provider.call({ to, from, data });
|
|
6865
6889
|
break;
|
|
6866
6890
|
} catch (simError) {
|
|
6867
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
|
+
}
|
|
6868
6898
|
if (decoded.selector === DEAD_ZONE_VIOLATION_SELECTOR && sim <= maxResimulations) {
|
|
6869
6899
|
const sigQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6870
6900
|
const realCurrentQuantum = Math.floor(now() / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
@@ -6900,6 +6930,28 @@ async function assertSafeQuantumSubmission(opts) {
|
|
|
6900
6930
|
await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
|
|
6901
6931
|
}
|
|
6902
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
|
+
|
|
6903
6955
|
// src/utils/btc-withdrawal-message.ts
|
|
6904
6956
|
import { keccak256 as keccak2563, toUtf8Bytes as toUtf8Bytes3, solidityPackedKeccak256 as solidityPackedKeccak2563, getBytes as getBytes3, zeroPadValue } from "ethers";
|
|
6905
6957
|
var QUANTUM_SECONDS = 60;
|
|
@@ -15761,7 +15813,7 @@ function createMockTokenManager(config) {
|
|
|
15761
15813
|
|
|
15762
15814
|
// src/modules/diamond-hands-sdk.ts
|
|
15763
15815
|
var import_dh_lit_actions2 = __toESM(require_pkg_src());
|
|
15764
|
-
import { LitOps } from "@gvnrdao/dh-lit-ops";
|
|
15816
|
+
import { LitOps, broadcastSignedBitcoinTransaction } from "@gvnrdao/dh-lit-ops";
|
|
15765
15817
|
|
|
15766
15818
|
// src/utils/telegram-messaging.utils.ts
|
|
15767
15819
|
var TelegramBot = null;
|
|
@@ -16246,6 +16298,10 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16246
16298
|
this.litOps = new LitOps({
|
|
16247
16299
|
mode: config.mode || "standalone",
|
|
16248
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,
|
|
16249
16305
|
signer: config.mode === "standalone" ? config.litOpsSigner : void 0,
|
|
16250
16306
|
serviceEndpoint: isServiceModeConfig(config) ? config.serviceEndpoint : void 0,
|
|
16251
16307
|
// Hand lit-ops a session-aware auth getter so every request it makes
|
|
@@ -16923,6 +16979,19 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
16923
16979
|
}
|
|
16924
16980
|
const MAX_FULL_RETRIES = 3;
|
|
16925
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
|
+
}
|
|
16926
16995
|
if (fullRetry > 1 && this.config.debug) {
|
|
16927
16996
|
log.info(
|
|
16928
16997
|
`\u{1F504} Full mint retry ${fullRetry}/${MAX_FULL_RETRIES} due to timestamp staleness...`,
|
|
@@ -17671,6 +17740,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
17671
17740
|
from: fromAddress,
|
|
17672
17741
|
data: mintCalldata,
|
|
17673
17742
|
quantumTimestamp: Number(validationResponse.timestamp),
|
|
17743
|
+
laneGuardActive: await this.isLocalLaneGuardActive(),
|
|
17674
17744
|
onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
|
|
17675
17745
|
});
|
|
17676
17746
|
} catch (simError) {
|
|
@@ -18966,6 +19036,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
18966
19036
|
from: await extendSigner.getAddress(),
|
|
18967
19037
|
data: extendCalldata,
|
|
18968
19038
|
quantumTimestamp: Number(quantumTimestamp),
|
|
19039
|
+
laneGuardActive: await this.isLocalLaneGuardActive(),
|
|
18969
19040
|
onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
|
|
18970
19041
|
});
|
|
18971
19042
|
} catch (simError) {
|
|
@@ -19866,6 +19937,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
19866
19937
|
from: paymentFrom,
|
|
19867
19938
|
data: paymentCalldata,
|
|
19868
19939
|
quantumTimestamp: Number(quantumTimestamp),
|
|
19940
|
+
laneGuardActive: await this.isLocalLaneGuardActive(),
|
|
19869
19941
|
onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
|
|
19870
19942
|
});
|
|
19871
19943
|
} catch (simError) {
|
|
@@ -20605,6 +20677,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
20605
20677
|
from: await withdrawSigner.getAddress(),
|
|
20606
20678
|
data: withdrawCalldata,
|
|
20607
20679
|
quantumTimestamp: Number(withdrawalParams.quantumTimestamp),
|
|
20680
|
+
laneGuardActive: await this.isLocalLaneGuardActive(),
|
|
20608
20681
|
onDebug: this.config.debug ? (msg) => log.info(msg, {}) : void 0
|
|
20609
20682
|
});
|
|
20610
20683
|
tx = await sendEip1559Transaction({
|
|
@@ -20682,7 +20755,9 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
20682
20755
|
gasUsed: receipt.gasUsed?.toString() || "unknown",
|
|
20683
20756
|
utxoTxid: validationResponse.utxoTxid,
|
|
20684
20757
|
utxoVout: validationResponse.utxoVout,
|
|
20685
|
-
utxoSatoshis: validationResponse.utxoSatoshis
|
|
20758
|
+
utxoSatoshis: validationResponse.utxoSatoshis,
|
|
20759
|
+
// Phase B: full consensus-validated spend set (absent on older actions).
|
|
20760
|
+
confirmedUtxos: validationResponse.confirmedUtxos
|
|
20686
20761
|
};
|
|
20687
20762
|
} catch (error) {
|
|
20688
20763
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -20757,15 +20832,180 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
20757
20832
|
* @param request - Execution request with positionId, utxoIdentifier, and networkFee
|
|
20758
20833
|
* @returns Execution result with Bitcoin txid
|
|
20759
20834
|
*/
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
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) {
|
|
20763
20884
|
return {
|
|
20764
20885
|
success: false,
|
|
20765
|
-
error:
|
|
20886
|
+
error: `Unsupported chainId ${chainId} for standalone BTC withdrawal (supported: 1, 11155111, 1337, 31337)`
|
|
20766
20887
|
};
|
|
20767
20888
|
}
|
|
20768
|
-
|
|
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) {
|
|
20769
21009
|
return { success: false, error: "serviceEndpoint required for BTC withdrawal" };
|
|
20770
21010
|
}
|
|
20771
21011
|
if (!request.positionId) {
|
|
@@ -20820,6 +21060,15 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
20820
21060
|
signer,
|
|
20821
21061
|
...request.utxos ? { utxos: request.utxos } : {}
|
|
20822
21062
|
});
|
|
21063
|
+
if (this.config.mode !== "service") {
|
|
21064
|
+
return await this.executeBTCWithdrawalStandalonePhase2(
|
|
21065
|
+
request,
|
|
21066
|
+
envelope,
|
|
21067
|
+
txid,
|
|
21068
|
+
vout,
|
|
21069
|
+
chainId
|
|
21070
|
+
);
|
|
21071
|
+
}
|
|
20823
21072
|
const headers = {
|
|
20824
21073
|
"Content-Type": "application/json",
|
|
20825
21074
|
...await this.getAuthHeader()
|
|
@@ -21365,7 +21614,12 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
21365
21614
|
log.info("\u{1F50D} Found vault UTXO for execution", { utxoIdentifier });
|
|
21366
21615
|
}
|
|
21367
21616
|
let executionUtxos;
|
|
21368
|
-
const gatheredUtxos =
|
|
21617
|
+
const gatheredUtxos = authResult.confirmedUtxos && authResult.confirmedUtxos.length > 0 ? authResult.confirmedUtxos.map((u) => ({
|
|
21618
|
+
txid: u.txid,
|
|
21619
|
+
vout: Number(u.vout),
|
|
21620
|
+
value: Number(u.value),
|
|
21621
|
+
confirmations: 0
|
|
21622
|
+
})) : await this.fetchConfirmedVaultUtxos(positionId);
|
|
21369
21623
|
if (gatheredUtxos.length > 0) {
|
|
21370
21624
|
const normTxid = (t) => String(t).toLowerCase().replace(/^0x/, "");
|
|
21371
21625
|
const byOutpoint = /* @__PURE__ */ new Map();
|
|
@@ -22132,6 +22386,25 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22132
22386
|
const chain = this.config.chain?.toLowerCase() || "";
|
|
22133
22387
|
return chain.includes("mainnet");
|
|
22134
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
|
+
}
|
|
22135
22408
|
/**
|
|
22136
22409
|
* Get Bitcoin network based on chain.
|
|
22137
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;
|
|
@@ -233,6 +233,16 @@ export interface BTCWithdrawalResult {
|
|
|
233
233
|
utxoVout?: number;
|
|
234
234
|
/** Full satoshi value of the authorized UTXO; needed by Phase 2 broadcast. */
|
|
235
235
|
utxoSatoshis?: string;
|
|
236
|
+
/**
|
|
237
|
+
* Phase B: the FULL consensus-validated confirmed UTXO set the Lit Action
|
|
238
|
+
* spends, so a multi-UTXO consolidation withdrawal can sign the whole set
|
|
239
|
+
* without the temporary /vault-utxos endpoint. Absent on older Lit Actions.
|
|
240
|
+
*/
|
|
241
|
+
confirmedUtxos?: Array<{
|
|
242
|
+
txid: string;
|
|
243
|
+
vout: number;
|
|
244
|
+
value: string;
|
|
245
|
+
}>;
|
|
236
246
|
error?: string;
|
|
237
247
|
}
|
|
238
248
|
/**
|
|
@@ -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;
|