@gvnrdao/dh-sdk 0.0.323 → 0.0.324
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/index.js +186 -43
- package/dist/index.mjs +186 -43
- package/dist/modules/diamond-hands-sdk.d.ts +18 -0
- package/dist/utils/quantum-revert.utils.d.ts +21 -0
- package/dist/utils/quantum-submission.utils.d.ts +23 -3
- package/dist/utils/quantum-timing.d.ts +49 -2
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1385,30 +1385,30 @@ var require_pkg_src = __commonJS({
|
|
|
1385
1385
|
}
|
|
1386
1386
|
},
|
|
1387
1387
|
btcTransactionSigner: {
|
|
1388
|
-
cid: "
|
|
1388
|
+
cid: "QmakWzTce8NH1Jkgv1xwg83Bg9WmF2UG1Mq76pDYDSwwUp",
|
|
1389
1389
|
authorizedCidHex: cidToHex(
|
|
1390
|
-
"
|
|
1390
|
+
"QmakWzTce8NH1Jkgv1xwg83Bg9WmF2UG1Mq76pDYDSwwUp"
|
|
1391
1391
|
),
|
|
1392
1392
|
name: "Btc Transaction Signer",
|
|
1393
1393
|
description: "Production Btc Transaction Signer",
|
|
1394
1394
|
version: "1.1.1",
|
|
1395
1395
|
deployed: true,
|
|
1396
|
-
deployedAt:
|
|
1397
|
-
size:
|
|
1398
|
-
hash: "
|
|
1396
|
+
deployedAt: 1786113907707,
|
|
1397
|
+
size: 67999,
|
|
1398
|
+
hash: "83f3644c68819215e05ba8db3225571354a009458fc1437f606df6cb9142d3f9"
|
|
1399
1399
|
},
|
|
1400
1400
|
ucdMintValidator: {
|
|
1401
|
-
cid: "
|
|
1401
|
+
cid: "Qmd8jgKRceLvf5aoTtwdXG2JqMoBK5cRJwrvEtQkgvbFbx",
|
|
1402
1402
|
authorizedCidHex: cidToHex(
|
|
1403
|
-
"
|
|
1403
|
+
"Qmd8jgKRceLvf5aoTtwdXG2JqMoBK5cRJwrvEtQkgvbFbx"
|
|
1404
1404
|
),
|
|
1405
1405
|
name: "Ucd Mint Validator",
|
|
1406
1406
|
description: "Production Ucd Mint Validator",
|
|
1407
1407
|
version: "1.2.1",
|
|
1408
1408
|
deployed: true,
|
|
1409
|
-
deployedAt:
|
|
1410
|
-
size:
|
|
1411
|
-
hash: "
|
|
1409
|
+
deployedAt: 1786113908489,
|
|
1410
|
+
size: 82739,
|
|
1411
|
+
hash: "2bdd9aebdaad58a43a4731d5b7284f2531b7a9caaf8caf5719d891c4ed33cb2e",
|
|
1412
1412
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1413
1413
|
pkp: {
|
|
1414
1414
|
publicKey: "0x041ab17cd91fc5c0b761eea6092d032807561b621b82c488826776e04a9158d61ba64d809a4729f0501289d980732cb06d8dfd06999dd9c8efd2f495dad78b31bb",
|
|
@@ -1416,17 +1416,17 @@ var require_pkg_src = __commonJS({
|
|
|
1416
1416
|
}
|
|
1417
1417
|
},
|
|
1418
1418
|
processPaymentValidator: {
|
|
1419
|
-
cid: "
|
|
1419
|
+
cid: "QmNXVdysXDhCaQ1uNE2Vgjbuzy6yUEnnVgHzmDfNKXMU6z",
|
|
1420
1420
|
authorizedCidHex: cidToHex(
|
|
1421
|
-
"
|
|
1421
|
+
"QmNXVdysXDhCaQ1uNE2Vgjbuzy6yUEnnVgHzmDfNKXMU6z"
|
|
1422
1422
|
),
|
|
1423
1423
|
name: "Process Payment Validator",
|
|
1424
1424
|
description: "Production Process Payment Validator",
|
|
1425
1425
|
version: "1.2.1",
|
|
1426
1426
|
deployed: true,
|
|
1427
|
-
deployedAt:
|
|
1428
|
-
size:
|
|
1429
|
-
hash: "
|
|
1427
|
+
deployedAt: 1786113909138,
|
|
1428
|
+
size: 78736,
|
|
1429
|
+
hash: "17191ecc9c009e0fa5ab52c876330beb2a9a75d30cccb92e7c0c04f169df088b",
|
|
1430
1430
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1431
1431
|
pkp: {
|
|
1432
1432
|
publicKey: "0x04cfccb9b6c7addc79469c0e9fafebef4c70801302c62cdb26b610723a9b8527d23f98a40158c7340d83fd0e62d02b5ec74c12f7a86d98e8c6973b4811110f19ad",
|
|
@@ -1434,17 +1434,17 @@ var require_pkg_src = __commonJS({
|
|
|
1434
1434
|
}
|
|
1435
1435
|
},
|
|
1436
1436
|
extendPositionValidator: {
|
|
1437
|
-
cid: "
|
|
1437
|
+
cid: "QmYwBsyrhGfHWyRKx549G4tKHxsBLAuRUHcecigeUCkFav",
|
|
1438
1438
|
authorizedCidHex: cidToHex(
|
|
1439
|
-
"
|
|
1439
|
+
"QmYwBsyrhGfHWyRKx549G4tKHxsBLAuRUHcecigeUCkFav"
|
|
1440
1440
|
),
|
|
1441
1441
|
name: "Extend Position Validator",
|
|
1442
1442
|
description: "Production Extend Position Validator",
|
|
1443
1443
|
version: "1.2.1",
|
|
1444
1444
|
deployed: true,
|
|
1445
|
-
deployedAt:
|
|
1446
|
-
size:
|
|
1447
|
-
hash: "
|
|
1445
|
+
deployedAt: 1786113909872,
|
|
1446
|
+
size: 76515,
|
|
1447
|
+
hash: "1d4e085335af80f194a663a577876902c83015177e95906a0b461fa93bd5da08",
|
|
1448
1448
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1449
1449
|
pkp: {
|
|
1450
1450
|
publicKey: "0x04e52338f6a0c3362800f1a94c4c8f08be5030dde0fa217388c53d1c1a4790f4443c9f2886b2d2d8f4b75a9a156e9b028011b1a1cf7648da0e556aa980ec692836",
|
|
@@ -1452,9 +1452,9 @@ var require_pkg_src = __commonJS({
|
|
|
1452
1452
|
}
|
|
1453
1453
|
},
|
|
1454
1454
|
btcWithdrawal: {
|
|
1455
|
-
cid: "
|
|
1455
|
+
cid: "QmZuR3qSBajutXfRd91ffpyFrRMgmaMh7fDEHRiQQF8Ljp",
|
|
1456
1456
|
authorizedCidHex: cidToHex(
|
|
1457
|
-
"
|
|
1457
|
+
"QmZuR3qSBajutXfRd91ffpyFrRMgmaMh7fDEHRiQQF8Ljp"
|
|
1458
1458
|
),
|
|
1459
1459
|
name: "Btc Withdrawal",
|
|
1460
1460
|
description: "Production Btc Withdrawal",
|
|
@@ -1464,9 +1464,9 @@ var require_pkg_src = __commonJS({
|
|
|
1464
1464
|
// than the one it replaced, which a rotation ceremony would treat as a rollback.
|
|
1465
1465
|
version: "1.2.4",
|
|
1466
1466
|
deployed: true,
|
|
1467
|
-
deployedAt:
|
|
1468
|
-
size:
|
|
1469
|
-
hash: "
|
|
1467
|
+
deployedAt: 1786113910531,
|
|
1468
|
+
size: 95313,
|
|
1469
|
+
hash: "ce12f11e3b5469953bc919586e4f3b8b5d594224b58b799dd9a3b60e3749559c",
|
|
1470
1470
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1471
1471
|
pkp: {
|
|
1472
1472
|
publicKey: "0x043616787c5432415c24378c4ef48de2bcd6bb7b575b837e3cff09171802662da7105d79586c7659677a0ecbaddac4cce06cb2a11f69a16fa0c4d7002ac7d51a4d",
|
|
@@ -1577,6 +1577,21 @@ var require_pkg_src = __commonJS({
|
|
|
1577
1577
|
size: 1046,
|
|
1578
1578
|
hash: "03ee5be239c91c735d2c95db89a0d1b77e602cfc18af8eb64b3281fddcd90a72"
|
|
1579
1579
|
},
|
|
1580
|
+
agentModuleExecutor: {
|
|
1581
|
+
// Bound to the isolated Sepolia agent group 7 ("DH-Agent-Sepolia"), which
|
|
1582
|
+
// holds this action and no PKPs until an agent is minted into it. NOT yet
|
|
1583
|
+
// bound on mainnet — the Base `DH-Agent-Mainnet` group is created at rollout.
|
|
1584
|
+
cid: "QmVgjGXUuxoGf8XyddW8a7btaazT9yEotpKoBgb65oPvcF",
|
|
1585
|
+
authorizedCidHex: cidToHex(
|
|
1586
|
+
"QmVgjGXUuxoGf8XyddW8a7btaazT9yEotpKoBgb65oPvcF"
|
|
1587
|
+
),
|
|
1588
|
+
name: "Agent Module Executor",
|
|
1589
|
+
description: "Capability gate for multi-sig Safe agent delegation: binds the agent PKP to the AgentDelegationRegistry record before signing (authorize | execute modes)",
|
|
1590
|
+
version: "1.0.0",
|
|
1591
|
+
deployed: true,
|
|
1592
|
+
size: 11785,
|
|
1593
|
+
hash: "fa294e164273c2158f9ef11a0ccfd735c343c0e3759fcb4b37e0f8a5d1ab0d29"
|
|
1594
|
+
},
|
|
1580
1595
|
staleSpendRecoveryValidator: {
|
|
1581
1596
|
cid: "QmXQWT8V4wjuvH8BbC7xrkYxsVu5AKfvfWcS7jnYfrexYm",
|
|
1582
1597
|
authorizedCidHex: cidToHex(
|
|
@@ -1596,17 +1611,17 @@ var require_pkg_src = __commonJS({
|
|
|
1596
1611
|
}
|
|
1597
1612
|
},
|
|
1598
1613
|
btcUtxoInvalidator: {
|
|
1599
|
-
cid: "
|
|
1614
|
+
cid: "Qmf4Ze8yhnQhHbnX5RPgJrC7Jw1aA3gbpfpd52kFg7fd1m",
|
|
1600
1615
|
authorizedCidHex: cidToHex(
|
|
1601
|
-
"
|
|
1616
|
+
"Qmf4Ze8yhnQhHbnX5RPgJrC7Jw1aA3gbpfpd52kFg7fd1m"
|
|
1602
1617
|
),
|
|
1603
1618
|
name: "Btc Utxo Invalidator",
|
|
1604
1619
|
description: "Production Btc Utxo Invalidator",
|
|
1605
1620
|
version: "1.0.0",
|
|
1606
1621
|
deployed: true,
|
|
1607
|
-
deployedAt:
|
|
1608
|
-
size:
|
|
1609
|
-
hash: "
|
|
1622
|
+
deployedAt: 1786113911948,
|
|
1623
|
+
size: 62553,
|
|
1624
|
+
hash: "1dcea2b49e545c423195be22e072dc5e3039ed1c95a8490b23505956029f5b33"
|
|
1610
1625
|
}
|
|
1611
1626
|
};
|
|
1612
1627
|
function getDeploymentsForNetwork(network) {
|
|
@@ -6164,7 +6179,10 @@ import { keccak256, toUtf8Bytes, solidityPackedKeccak256, getBytes, Contract } f
|
|
|
6164
6179
|
// src/utils/quantum-timing.ts
|
|
6165
6180
|
var QUANTUM_WINDOW_SECONDS = 60;
|
|
6166
6181
|
var DEAD_ZONE_SECONDS = 8;
|
|
6167
|
-
var
|
|
6182
|
+
var SLOT_SECONDS = 12;
|
|
6183
|
+
var INCLUSION_SLOT_TOLERANCE = 3;
|
|
6184
|
+
var INCLUSION_LATENCY_BUDGET = SLOT_SECONDS * INCLUSION_SLOT_TOLERANCE;
|
|
6185
|
+
var MAX_GATE_POLLS = 12;
|
|
6168
6186
|
var POST_BOUNDARY_SKEW_MARGIN = 3;
|
|
6169
6187
|
function calculateNextQuantumTimestamp() {
|
|
6170
6188
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -6197,8 +6215,17 @@ function validateQuantumTiming(signedTimestamp, _bufferSeconds = 30) {
|
|
|
6197
6215
|
async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
|
|
6198
6216
|
const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
6199
6217
|
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6200
|
-
const nowSec = now();
|
|
6201
6218
|
const signatureQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6219
|
+
if (opts.chainNow) {
|
|
6220
|
+
const chainResult = await awaitSafeSubmissionWindowOnChain(
|
|
6221
|
+
signatureQuantum,
|
|
6222
|
+
opts.chainNow,
|
|
6223
|
+
sleep
|
|
6224
|
+
);
|
|
6225
|
+
if (chainResult)
|
|
6226
|
+
return chainResult;
|
|
6227
|
+
}
|
|
6228
|
+
const nowSec = now();
|
|
6202
6229
|
const currentQuantum = Math.floor(nowSec / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6203
6230
|
const secsToBoundary = QUANTUM_WINDOW_SECONDS - nowSec % QUANTUM_WINDOW_SECONDS;
|
|
6204
6231
|
const isNextQuantumSig = signatureQuantum === currentQuantum + QUANTUM_WINDOW_SECONDS;
|
|
@@ -6215,6 +6242,44 @@ async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
|
|
|
6215
6242
|
}
|
|
6216
6243
|
return { waited: false, waitedSeconds: 0 };
|
|
6217
6244
|
}
|
|
6245
|
+
async function awaitSafeSubmissionWindowOnChain(signatureQuantum, chainNow, sleep) {
|
|
6246
|
+
let waitedSeconds = 0;
|
|
6247
|
+
for (let poll = 0; poll < MAX_GATE_POLLS; poll++) {
|
|
6248
|
+
let head;
|
|
6249
|
+
try {
|
|
6250
|
+
head = await chainNow();
|
|
6251
|
+
} catch {
|
|
6252
|
+
head = null;
|
|
6253
|
+
}
|
|
6254
|
+
if (head === null || !Number.isFinite(head)) {
|
|
6255
|
+
return waitedSeconds > 0 ? { waited: true, waitedSeconds } : null;
|
|
6256
|
+
}
|
|
6257
|
+
const chainQuantum = Math.floor(head / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6258
|
+
if (signatureQuantum <= chainQuantum) {
|
|
6259
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6260
|
+
}
|
|
6261
|
+
const earliestMine = head + SLOT_SECONDS;
|
|
6262
|
+
const latestMine = head + SLOT_SECONDS * INCLUSION_SLOT_TOLERANCE;
|
|
6263
|
+
const safeBeforeDeadZone = latestMine < signatureQuantum - DEAD_ZONE_SECONDS;
|
|
6264
|
+
const safeAfterBoundary = earliestMine >= signatureQuantum;
|
|
6265
|
+
if (safeBeforeDeadZone || safeAfterBoundary) {
|
|
6266
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6267
|
+
}
|
|
6268
|
+
const sleepSeconds = Math.max(
|
|
6269
|
+
1,
|
|
6270
|
+
Math.min(SLOT_SECONDS, signatureQuantum - head)
|
|
6271
|
+
);
|
|
6272
|
+
log.debug(
|
|
6273
|
+
`[Quantum Timing] \u23F3 Deferring send ${sleepSeconds}s to clear the quantum dead zone (sig quantum ${signatureQuantum}, chain head ${head}, insured mine window [${earliestMine}, ${latestMine}])`
|
|
6274
|
+
);
|
|
6275
|
+
await sleep(sleepSeconds * 1e3);
|
|
6276
|
+
waitedSeconds += sleepSeconds;
|
|
6277
|
+
}
|
|
6278
|
+
log.debug(
|
|
6279
|
+
`[Quantum Timing] \u26A0\uFE0F Dead-zone gate exhausted ${MAX_GATE_POLLS} chain polls (sig quantum ${signatureQuantum}) \u2014 proceeding`
|
|
6280
|
+
);
|
|
6281
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6282
|
+
}
|
|
6218
6283
|
|
|
6219
6284
|
// src/utils/mint-authorization.utils.ts
|
|
6220
6285
|
var PKP_NFT_ABI = [
|
|
@@ -7171,6 +7236,20 @@ function decodeQuantumRevert(e) {
|
|
|
7171
7236
|
}
|
|
7172
7237
|
return { data, selector, name };
|
|
7173
7238
|
}
|
|
7239
|
+
var RETRYABLE_QUANTUM_ERROR_NAMES = [
|
|
7240
|
+
"DeadZoneViolation",
|
|
7241
|
+
"QuantumOutsideWindow",
|
|
7242
|
+
"QuantumAlreadyUsed",
|
|
7243
|
+
"QuantumExpired"
|
|
7244
|
+
];
|
|
7245
|
+
function isRetryableQuantumFailure(message) {
|
|
7246
|
+
if (!message)
|
|
7247
|
+
return false;
|
|
7248
|
+
if (message.includes("Transaction timeout") || message.includes("Transaction confirmation failed") || message.includes("Transaction receipt not found")) {
|
|
7249
|
+
return false;
|
|
7250
|
+
}
|
|
7251
|
+
return RETRYABLE_QUANTUM_ERROR_NAMES.some((name) => message.includes(name)) || message.includes("Transaction reverted");
|
|
7252
|
+
}
|
|
7174
7253
|
|
|
7175
7254
|
// src/utils/quantum-submission.utils.ts
|
|
7176
7255
|
var MAX_DEADZONE_RESIMULATIONS = 3;
|
|
@@ -7192,9 +7271,14 @@ async function assertSafeQuantumSubmission(opts) {
|
|
|
7192
7271
|
const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
7193
7272
|
const onDebug = opts.onDebug ?? (() => {
|
|
7194
7273
|
});
|
|
7274
|
+
const chainNow = opts.chainNow ?? (typeof provider.getBlock === "function" ? async () => {
|
|
7275
|
+
const block = await provider.getBlock("latest");
|
|
7276
|
+
return typeof block?.timestamp === "number" ? block.timestamp : null;
|
|
7277
|
+
} : void 0);
|
|
7195
7278
|
const gateOpts = {
|
|
7196
7279
|
now,
|
|
7197
|
-
...opts.sleep ? { sleep: opts.sleep } : {}
|
|
7280
|
+
...opts.sleep ? { sleep: opts.sleep } : {},
|
|
7281
|
+
...chainNow ? { chainNow } : {}
|
|
7198
7282
|
};
|
|
7199
7283
|
await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
|
|
7200
7284
|
const SAME_BLOCK_INTERACTION_SELECTOR = "0x3b53d1db";
|
|
@@ -22218,10 +22302,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22218
22302
|
isRetryablePaymentQuantumFailure(result) {
|
|
22219
22303
|
if (result.success || !result.error)
|
|
22220
22304
|
return false;
|
|
22221
|
-
|
|
22222
|
-
if (e.includes("Transaction timeout"))
|
|
22223
|
-
return false;
|
|
22224
|
-
return e.includes("DeadZoneViolation") || e.includes("QuantumOutsideWindow") || e.includes("QuantumAlreadyUsed") || e.includes("QuantumExpired") || e === "Transaction reverted";
|
|
22305
|
+
return isRetryableQuantumFailure(result.error);
|
|
22225
22306
|
}
|
|
22226
22307
|
/**
|
|
22227
22308
|
* One payment attempt: user auth → Lit Action authorization → dead-zone gate →
|
|
@@ -22860,6 +22941,37 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22860
22941
|
}
|
|
22861
22942
|
return this.withdrawalAddressModule.getEntries(target);
|
|
22862
22943
|
}
|
|
22944
|
+
/**
|
|
22945
|
+
* Recover the revert reason of an already-mined, already-reverted transaction.
|
|
22946
|
+
*
|
|
22947
|
+
* A status-0 receipt carries no revert data, and ethers does not re-fetch it, so
|
|
22948
|
+
* the reason has to be recovered by replaying the exact calldata as an `eth_call`
|
|
22949
|
+
* pinned to the block that mined it — the only block whose `block.timestamp`
|
|
22950
|
+
* reproduces a timing-dependent revert such as `DeadZoneViolation()`.
|
|
22951
|
+
*
|
|
22952
|
+
* Caveat: the replay runs against end-of-block state rather than the state at the
|
|
22953
|
+
* transaction's own index, so for a state-dependent revert the recovered name is
|
|
22954
|
+
* diagnostic, not proof. Timing reverts (the case this exists for) are unaffected —
|
|
22955
|
+
* they depend only on the block timestamp.
|
|
22956
|
+
*
|
|
22957
|
+
* @returns the decoded error name, its raw selector if unknown, or `null` when the
|
|
22958
|
+
* replay yields no revert data at all (never throws — a failed diagnosis
|
|
22959
|
+
* must not mask the underlying failure).
|
|
22960
|
+
*/
|
|
22961
|
+
async decodeMinedRevert(params) {
|
|
22962
|
+
try {
|
|
22963
|
+
await this.getProviderOrThrow().call({
|
|
22964
|
+
to: params.to,
|
|
22965
|
+
from: params.from,
|
|
22966
|
+
data: params.data,
|
|
22967
|
+
blockTag: params.blockNumber
|
|
22968
|
+
});
|
|
22969
|
+
return null;
|
|
22970
|
+
} catch (replayError) {
|
|
22971
|
+
const decoded = decodeQuantumRevert(replayError);
|
|
22972
|
+
return decoded.name ?? decoded.selector ?? null;
|
|
22973
|
+
}
|
|
22974
|
+
}
|
|
22863
22975
|
/**
|
|
22864
22976
|
* Withdraw Bitcoin from a position
|
|
22865
22977
|
*
|
|
@@ -23433,9 +23545,23 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23433
23545
|
txHash: tx.hash
|
|
23434
23546
|
});
|
|
23435
23547
|
}
|
|
23548
|
+
const withdrawProvider = this.getProviderOrThrow();
|
|
23549
|
+
const withdrawTimeoutMs = this.config.transactionTimeoutMs || 3e5;
|
|
23436
23550
|
let receipt;
|
|
23437
23551
|
try {
|
|
23438
|
-
receipt = await
|
|
23552
|
+
receipt = await Promise.race([
|
|
23553
|
+
withdrawProvider.waitForTransaction(tx.hash, 1),
|
|
23554
|
+
new Promise(
|
|
23555
|
+
(_, reject) => setTimeout(
|
|
23556
|
+
() => reject(
|
|
23557
|
+
new Error(
|
|
23558
|
+
`Transaction timeout after ${withdrawTimeoutMs}ms. Hash: ${tx.hash}`
|
|
23559
|
+
)
|
|
23560
|
+
),
|
|
23561
|
+
withdrawTimeoutMs
|
|
23562
|
+
)
|
|
23563
|
+
)
|
|
23564
|
+
]);
|
|
23439
23565
|
} catch (waitError) {
|
|
23440
23566
|
const waitErrorMsg = waitError instanceof Error ? waitError.message : String(waitError);
|
|
23441
23567
|
if (this.config.debug) {
|
|
@@ -23452,7 +23578,26 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23452
23578
|
if (!receipt) {
|
|
23453
23579
|
return {
|
|
23454
23580
|
success: false,
|
|
23455
|
-
error: `Transaction
|
|
23581
|
+
error: `Transaction receipt not found for hash: ${tx.hash}`
|
|
23582
|
+
};
|
|
23583
|
+
}
|
|
23584
|
+
if (receipt.status === 0) {
|
|
23585
|
+
const revertName = await this.decodeMinedRevert({
|
|
23586
|
+
to: positionManagerAddress,
|
|
23587
|
+
from: await withdrawSigner.getAddress(),
|
|
23588
|
+
data: withdrawCalldata,
|
|
23589
|
+
blockNumber: receipt.blockNumber
|
|
23590
|
+
});
|
|
23591
|
+
if (this.config.debug) {
|
|
23592
|
+
log.error("\u274C withdrawBTC reverted on-chain", {
|
|
23593
|
+
txHash: receipt.hash,
|
|
23594
|
+
blockNumber: receipt.blockNumber,
|
|
23595
|
+
revert: revertName ?? "undecodable"
|
|
23596
|
+
});
|
|
23597
|
+
}
|
|
23598
|
+
return {
|
|
23599
|
+
success: false,
|
|
23600
|
+
error: `Transaction reverted: ${revertName ?? "unknown reason"} (tx: ${receipt.hash})`
|
|
23456
23601
|
};
|
|
23457
23602
|
}
|
|
23458
23603
|
if (this.config.debug) {
|
|
@@ -24580,13 +24725,11 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24580
24725
|
if (authResult.success) {
|
|
24581
24726
|
break;
|
|
24582
24727
|
}
|
|
24583
|
-
const isQuantumError = authResult.error
|
|
24584
|
-
"QuantumOutsideWindow"
|
|
24585
|
-
);
|
|
24728
|
+
const isQuantumError = isRetryableQuantumFailure(authResult.error);
|
|
24586
24729
|
if (isQuantumError && wAttempt < MAX_WITHDRAWAL_QUANTUM_RETRIES) {
|
|
24587
24730
|
if (this.config.debug) {
|
|
24588
24731
|
log.warn(
|
|
24589
|
-
`\u26A0\uFE0F Phase 1
|
|
24732
|
+
`\u26A0\uFE0F Phase 1 quantum-timing failure (attempt ${wAttempt}/${MAX_WITHDRAWAL_QUANTUM_RETRIES}): ${authResult.error}. Retrying with a fresh quantum...`
|
|
24590
24733
|
);
|
|
24591
24734
|
}
|
|
24592
24735
|
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
@@ -540,6 +540,24 @@ export declare class DiamondHandsSDK {
|
|
|
540
540
|
* address strings, query the subgraph `WithdrawalAddressBook`.
|
|
541
541
|
*/
|
|
542
542
|
getApprovedWithdrawalAddresses(user?: string): Promise<Result<import("./withdrawal-address/withdrawal-address.module").WithdrawalAddressEntry[], SDKError>>;
|
|
543
|
+
/**
|
|
544
|
+
* Recover the revert reason of an already-mined, already-reverted transaction.
|
|
545
|
+
*
|
|
546
|
+
* A status-0 receipt carries no revert data, and ethers does not re-fetch it, so
|
|
547
|
+
* the reason has to be recovered by replaying the exact calldata as an `eth_call`
|
|
548
|
+
* pinned to the block that mined it — the only block whose `block.timestamp`
|
|
549
|
+
* reproduces a timing-dependent revert such as `DeadZoneViolation()`.
|
|
550
|
+
*
|
|
551
|
+
* Caveat: the replay runs against end-of-block state rather than the state at the
|
|
552
|
+
* transaction's own index, so for a state-dependent revert the recovered name is
|
|
553
|
+
* diagnostic, not proof. Timing reverts (the case this exists for) are unaffected —
|
|
554
|
+
* they depend only on the block timestamp.
|
|
555
|
+
*
|
|
556
|
+
* @returns the decoded error name, its raw selector if unknown, or `null` when the
|
|
557
|
+
* replay yields no revert data at all (never throws — a failed diagnosis
|
|
558
|
+
* must not mask the underlying failure).
|
|
559
|
+
*/
|
|
560
|
+
private decodeMinedRevert;
|
|
543
561
|
/**
|
|
544
562
|
* Withdraw Bitcoin from a position
|
|
545
563
|
*
|
|
@@ -39,3 +39,24 @@ export interface DecodedRevert {
|
|
|
39
39
|
export declare function decodeQuantumRevert(e: any): DecodedRevert;
|
|
40
40
|
/** True iff the error decodes to `DeadZoneViolation()` from any provider nesting. */
|
|
41
41
|
export declare function isDeadZoneViolation(e: any): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether a failed quantum-signed operation can be safely retried with a fresh
|
|
44
|
+
* signature. Shared by the payment and BTC-withdrawal retry loops so the two paths
|
|
45
|
+
* cannot drift apart again (they had: withdrawal recognised only
|
|
46
|
+
* `QuantumOutsideWindow`, and never decoded a mined revert at all, so the mainnet
|
|
47
|
+
* `DeadZoneViolation` of 2026-08-07 surfaced as a hard failure on a flow that
|
|
48
|
+
* payments recover from automatically).
|
|
49
|
+
*
|
|
50
|
+
* **Retryable** — the operation provably did not happen:
|
|
51
|
+
* - a pre-send simulation quantum error (nothing was ever broadcast), or
|
|
52
|
+
* - a mined revert (`status === 0`): atomic, so no funds moved, no authorization
|
|
53
|
+
* was recorded, and the quantum replay lane is untouched. Re-signing is safe.
|
|
54
|
+
*
|
|
55
|
+
* **NOT retryable** — the outcome is unknown, so a resubmit risks double-spending:
|
|
56
|
+
* - a confirmation timeout (the tx may still be pending), or
|
|
57
|
+
* - a confirmation failure with no receipt (transport died mid-wait).
|
|
58
|
+
*
|
|
59
|
+
* Callers MUST therefore only produce a "Transaction reverted" message after
|
|
60
|
+
* observing a receipt with `status === 0`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isRetryableQuantumFailure(message: string | null | undefined): boolean;
|
|
@@ -37,13 +37,21 @@ export declare class QuantumRevertError extends Error {
|
|
|
37
37
|
constructor(errorName: string | null, selector: string | null, data: string | null, cause: unknown);
|
|
38
38
|
}
|
|
39
39
|
export interface SafeQuantumSubmissionOpts {
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Provider used for the `eth_call` simulation and — when it exposes `getBlock`
|
|
42
|
+
* (every ethers provider does) — for the dead-zone gate's chain clock. Without
|
|
43
|
+
* `getBlock` the gate silently degrades to the client's local clock, which on the
|
|
44
|
+
* browser path is exactly the skew we are trying to design out.
|
|
45
|
+
*/
|
|
41
46
|
provider: {
|
|
42
47
|
call(tx: {
|
|
43
48
|
to: string;
|
|
44
49
|
from: string;
|
|
45
50
|
data: string;
|
|
46
51
|
}): Promise<string>;
|
|
52
|
+
getBlock?(blockTag: string): Promise<{
|
|
53
|
+
timestamp: number;
|
|
54
|
+
} | null | undefined>;
|
|
47
55
|
};
|
|
48
56
|
/** Target contract address. */
|
|
49
57
|
to: string;
|
|
@@ -59,6 +67,12 @@ export interface SafeQuantumSubmissionOpts {
|
|
|
59
67
|
now?: () => number;
|
|
60
68
|
/** Sleep seam, forwarded to the gate. */
|
|
61
69
|
sleep?: (ms: number) => Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Chain-clock seam (latest mined block timestamp, whole seconds). Defaults to
|
|
72
|
+
* `provider.getBlock("latest")` when the provider exposes it. Pass explicitly to
|
|
73
|
+
* override in tests, or `null`-resolving to force the local-clock fallback.
|
|
74
|
+
*/
|
|
75
|
+
chainNow?: () => Promise<number | null>;
|
|
62
76
|
/** Debug sink; called with human-readable progress lines. */
|
|
63
77
|
onDebug?: (msg: string) => void;
|
|
64
78
|
/**
|
|
@@ -79,7 +93,9 @@ export interface SafeQuantumSubmissionOpts {
|
|
|
79
93
|
* 1. **Gate** (`awaitSafeSubmissionWindow`): a NEXT-quantum signature mined in
|
|
80
94
|
* the last DEAD_ZONE_SECONDS of the current quantum reverts
|
|
81
95
|
* `DeadZoneViolation()` on-chain, so defer the send across the boundary
|
|
82
|
-
* when inclusion could land there.
|
|
96
|
+
* when inclusion could land there. Anchored on `provider.getBlock("latest")`
|
|
97
|
+
* — the same clock `block.timestamp` comes from — so a skewed client clock
|
|
98
|
+
* cannot wave a doomed send through.
|
|
83
99
|
* 2. **Simulate** (`eth_call`) and route the outcome:
|
|
84
100
|
* - success → safe to broadcast.
|
|
85
101
|
* - `DeadZoneViolation()` → `eth_call` runs against the LATEST block's
|
|
@@ -87,7 +103,11 @@ export interface SafeQuantumSubmissionOpts {
|
|
|
87
103
|
* the signature is already the CURRENT quantum in real time it can never
|
|
88
104
|
* be dead-zoned on-chain — stale-block artifact, proceed. Otherwise the
|
|
89
105
|
* signature is still NEXT near the boundary: re-gate across it and
|
|
90
|
-
* re-simulate (bounded by `maxResimulations`).
|
|
106
|
+
* re-simulate (bounded by `maxResimulations`). This artifact test must use
|
|
107
|
+
* the LOCAL clock, not the chain head: the chain head IS the stale value
|
|
108
|
+
* the simulation reverted against, so anchoring it there would classify
|
|
109
|
+
* every artifact as real. A fast client clock wrongly reaching "artifact"
|
|
110
|
+
* is caught by the chain-anchored final re-gate in step 3.
|
|
91
111
|
* - any other DECODABLE revert → throw `QuantumRevertError` (fail fast: no
|
|
92
112
|
* broadcast, no gas burned on a doomed tx).
|
|
93
113
|
* - UNDECODABLE failure (no revert data: RPC timeout, rate limit, provider
|
|
@@ -28,13 +28,38 @@
|
|
|
28
28
|
*/
|
|
29
29
|
export declare const QUANTUM_WINDOW_SECONDS = 60;
|
|
30
30
|
export declare const DEAD_ZONE_SECONDS = 8;
|
|
31
|
+
/** Ethereum slot cadence. Block timestamps are multiples of 12s from genesis. */
|
|
32
|
+
export declare const SLOT_SECONDS = 12;
|
|
33
|
+
/**
|
|
34
|
+
* Slots of inclusion latency we insure against. A broadcast is NOT guaranteed to
|
|
35
|
+
* land in the very next slot: builders skip low-tip transactions and slots are
|
|
36
|
+
* missed, so 2–3 slot inclusion is routine on mainnet.
|
|
37
|
+
*
|
|
38
|
+
* Sized from a real mainnet failure (tx 0x5933d64d…, 2026-08-07): a NEXT-quantum
|
|
39
|
+
* withdrawal was broadcast with ~25s to the boundary — outside the old 16s budget,
|
|
40
|
+
* so the gate let it through — then sat out one near-empty block (0.0007 gwei tip)
|
|
41
|
+
* and was mined 24s later at second 59, one second inside the trailing dead zone.
|
|
42
|
+
* `DeadZoneViolation()`. 3 slots (36s) covers that skip with a slot to spare.
|
|
43
|
+
*/
|
|
44
|
+
export declare const INCLUSION_SLOT_TOLERANCE = 3;
|
|
31
45
|
/**
|
|
32
46
|
* Seconds of mainnet inclusion latency we insure against BEFORE the quantum
|
|
33
|
-
* boundary.
|
|
47
|
+
* boundary. A NEXT/PAST-quantum send within
|
|
34
48
|
* `DEAD_ZONE_SECONDS + INCLUSION_LATENCY_BUDGET` of the boundary is deferred so it
|
|
35
49
|
* cannot be mined in the trailing dead zone `[boundary - 8, boundary)`.
|
|
50
|
+
*
|
|
51
|
+
* Used by the LOCAL-CLOCK fallback path only. When a chain clock is available the
|
|
52
|
+
* gate reasons in whole slots instead (see `awaitSafeSubmissionWindow`), which is
|
|
53
|
+
* both tighter and immune to client clock skew.
|
|
36
54
|
*/
|
|
37
|
-
export declare const INCLUSION_LATENCY_BUDGET
|
|
55
|
+
export declare const INCLUSION_LATENCY_BUDGET: number;
|
|
56
|
+
/**
|
|
57
|
+
* Cap on chain-clock polls while deferring across a boundary. Each poll sleeps at
|
|
58
|
+
* most one slot, so this bounds a deferral at ~`MAX_GATE_POLLS` slots — comfortably
|
|
59
|
+
* more than the ~44s worst-case wait, while refusing to spin forever on a stuck or
|
|
60
|
+
* lying RPC.
|
|
61
|
+
*/
|
|
62
|
+
export declare const MAX_GATE_POLLS = 12;
|
|
38
63
|
/**
|
|
39
64
|
* Seconds of client-clock-vs-chain skew guard we add AFTER the boundary when
|
|
40
65
|
* deferring. A CURRENT-quantum signature is never dead-zoned, so this only needs to
|
|
@@ -108,6 +133,16 @@ export interface SafeSubmissionOptions {
|
|
|
108
133
|
now?: () => number;
|
|
109
134
|
/** Sleeps for `ms` milliseconds. Defaults to `setTimeout`. */
|
|
110
135
|
sleep?: (ms: number) => Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Latest MINED block timestamp, in whole seconds — the only clock the on-chain
|
|
138
|
+
* dead-zone rule is expressed in. Supply this wherever a provider is available:
|
|
139
|
+
* the local-clock path below cannot see client clock skew, and the primary
|
|
140
|
+
* consumer of this gate is a browser wallet whose clock is not trustworthy.
|
|
141
|
+
*
|
|
142
|
+
* Resolve to `null` (or throw) to fall back to the local clock — a flaky RPC
|
|
143
|
+
* must never block a valid money-path operation.
|
|
144
|
+
*/
|
|
145
|
+
chainNow?: () => Promise<number | null>;
|
|
111
146
|
}
|
|
112
147
|
export interface SafeSubmissionResult {
|
|
113
148
|
/** Whether the send was deferred across the quantum boundary. */
|
|
@@ -136,6 +171,18 @@ export interface SafeSubmissionResult {
|
|
|
136
171
|
* acceptance window (`QuantumOutsideWindow`). They must be handled by re-signing
|
|
137
172
|
* upstream. In practice the SDK only ever signs CURRENT/NEXT.
|
|
138
173
|
*
|
|
174
|
+
* Two implementations, same predicate:
|
|
175
|
+
*
|
|
176
|
+
* - **Chain-anchored** (`opts.chainNow` supplied — always prefer this): reasons in
|
|
177
|
+
* whole slots off the latest mined block timestamp `H`, the same clock the
|
|
178
|
+
* contract's `block.timestamp` comes from. A broadcast issued now can only be
|
|
179
|
+
* mined at `H + 12k`, so it is safe iff EVERY plausible `k` lands clear of the
|
|
180
|
+
* dead zone. No client clock is consulted at all — only sleep *durations*, which
|
|
181
|
+
* are immune to an offset clock.
|
|
182
|
+
* - **Local-clock fallback** (no `chainNow`, or the chain read failed): the original
|
|
183
|
+
* `Date.now()` math with the widened {@link INCLUSION_LATENCY_BUDGET}. Retained so
|
|
184
|
+
* a flaky RPC degrades rather than blocks, but it cannot see client clock skew.
|
|
185
|
+
*
|
|
139
186
|
* @param quantumTimestamp The quantum timestamp embedded in the LIT-signed payload.
|
|
140
187
|
* @returns whether it waited, and for how long.
|
|
141
188
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gvnrdao/dh-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.324",
|
|
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",
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
},
|
|
88
88
|
"sideEffects": false,
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@gvnrdao/dh-lit-actions": "^0.0.
|
|
91
|
-
"@gvnrdao/dh-lit-ops": "^0.0.
|
|
90
|
+
"@gvnrdao/dh-lit-actions": "^0.0.320",
|
|
91
|
+
"@gvnrdao/dh-lit-ops": "^0.0.311",
|
|
92
92
|
"@noble/hashes": "^1.5.0",
|
|
93
93
|
"axios": "^1.17.0",
|
|
94
94
|
"bech32": "^2.0.0",
|