@gvnrdao/dh-sdk 0.0.323 → 0.0.327
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 +2 -26
- package/dist/deployments.mjs +2 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +399 -84
- package/dist/index.mjs +399 -84
- package/dist/interfaces/chunks/loan-operations.i.d.ts +37 -0
- package/dist/modules/diamond-hands-sdk.d.ts +93 -2
- package/dist/safe-delegation.js +2 -26
- package/dist/safe-delegation.mjs +2 -26
- package/dist/utils/authorization-provider.utils.d.ts +65 -0
- package/dist/utils/mint-authorization.utils.d.ts +10 -16
- 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/browser/dist/397.browser.js +0 -2
- package/browser/dist/397.browser.js.LICENSE.txt +0 -1
- package/browser/dist/833.browser.js +0 -2
- package/browser/dist/833.browser.js.LICENSE.txt +0 -1
- package/browser/dist/index.d.ts +0 -8
- package/browser/dist/index.d.ts.map +0 -1
- package/browser/dist/index.js +0 -25
package/dist/index.js
CHANGED
|
@@ -354,7 +354,7 @@ var init_deployment_addresses = __esm({
|
|
|
354
354
|
CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
|
|
355
355
|
CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
|
|
356
356
|
AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
|
|
357
|
-
AGENT_DELEGATION_REGISTRY_IMPL: "
|
|
357
|
+
AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
|
|
358
358
|
DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
|
|
359
359
|
}
|
|
360
360
|
};
|
|
@@ -624,7 +624,7 @@ var init_deployment_addresses = __esm({
|
|
|
624
624
|
LOCALHOST_DEPLOYMENT = {
|
|
625
625
|
network: "localhost",
|
|
626
626
|
chainId: 1337,
|
|
627
|
-
timestamp: "2026-07-
|
|
627
|
+
timestamp: "2026-07-20T19:45:58.514Z",
|
|
628
628
|
deployer: "",
|
|
629
629
|
contracts: {
|
|
630
630
|
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
@@ -644,30 +644,6 @@ var init_deployment_addresses = __esm({
|
|
|
644
644
|
PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
645
645
|
OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
646
646
|
PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
|
|
647
|
-
},
|
|
648
|
-
latestEnv: {
|
|
649
|
-
UCD_TOKEN: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
650
|
-
UCD_CONTROLLER: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
651
|
-
POSITION_MANAGER: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
652
|
-
PKP_VALIDATION_REGISTRY: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A",
|
|
653
|
-
PKP_VALIDATION_CID_V1: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
654
|
-
PKP_VALIDATION_CID_V3: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
655
|
-
PKP_ETH_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
656
|
-
OPERATION_AUTHORIZATION_REGISTRY: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
657
|
-
UCD_MINT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
658
|
-
UCD_MINT_VALIDATOR_VERSION: 1,
|
|
659
|
-
BTC_WITHDRAWAL_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
660
|
-
BTC_WITHDRAWAL_VALIDATOR_VERSION: 3,
|
|
661
|
-
UPDATE_BALANCE_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
662
|
-
UPDATE_BALANCE_VALIDATOR_VERSION: 1,
|
|
663
|
-
PROCESS_PAYMENT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
664
|
-
PROCESS_PAYMENT_VALIDATOR_VERSION: 1,
|
|
665
|
-
EXTEND_POSITION_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
666
|
-
EXTEND_POSITION_VALIDATOR_VERSION: 1,
|
|
667
|
-
POSITION_MANAGER_CORE_MODULE: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
|
|
668
|
-
LOAN_OPERATIONS_MANAGER_MODULE: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
|
|
669
|
-
TERM_MANAGER_MODULE: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
|
|
670
|
-
COMMUNITY_MANAGER_MODULE: ""
|
|
671
647
|
}
|
|
672
648
|
};
|
|
673
649
|
LOCALHOST_CONTRACTS = {
|
|
@@ -1379,30 +1355,30 @@ var require_pkg_src = __commonJS({
|
|
|
1379
1355
|
}
|
|
1380
1356
|
},
|
|
1381
1357
|
btcTransactionSigner: {
|
|
1382
|
-
cid: "
|
|
1358
|
+
cid: "QmakWzTce8NH1Jkgv1xwg83Bg9WmF2UG1Mq76pDYDSwwUp",
|
|
1383
1359
|
authorizedCidHex: cidToHex(
|
|
1384
|
-
"
|
|
1360
|
+
"QmakWzTce8NH1Jkgv1xwg83Bg9WmF2UG1Mq76pDYDSwwUp"
|
|
1385
1361
|
),
|
|
1386
1362
|
name: "Btc Transaction Signer",
|
|
1387
1363
|
description: "Production Btc Transaction Signer",
|
|
1388
1364
|
version: "1.1.1",
|
|
1389
1365
|
deployed: true,
|
|
1390
|
-
deployedAt:
|
|
1391
|
-
size:
|
|
1392
|
-
hash: "
|
|
1366
|
+
deployedAt: 1786113907707,
|
|
1367
|
+
size: 67999,
|
|
1368
|
+
hash: "83f3644c68819215e05ba8db3225571354a009458fc1437f606df6cb9142d3f9"
|
|
1393
1369
|
},
|
|
1394
1370
|
ucdMintValidator: {
|
|
1395
|
-
cid: "
|
|
1371
|
+
cid: "Qmd8jgKRceLvf5aoTtwdXG2JqMoBK5cRJwrvEtQkgvbFbx",
|
|
1396
1372
|
authorizedCidHex: cidToHex(
|
|
1397
|
-
"
|
|
1373
|
+
"Qmd8jgKRceLvf5aoTtwdXG2JqMoBK5cRJwrvEtQkgvbFbx"
|
|
1398
1374
|
),
|
|
1399
1375
|
name: "Ucd Mint Validator",
|
|
1400
1376
|
description: "Production Ucd Mint Validator",
|
|
1401
1377
|
version: "1.2.1",
|
|
1402
1378
|
deployed: true,
|
|
1403
|
-
deployedAt:
|
|
1404
|
-
size:
|
|
1405
|
-
hash: "
|
|
1379
|
+
deployedAt: 1786113908489,
|
|
1380
|
+
size: 82739,
|
|
1381
|
+
hash: "2bdd9aebdaad58a43a4731d5b7284f2531b7a9caaf8caf5719d891c4ed33cb2e",
|
|
1406
1382
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1407
1383
|
pkp: {
|
|
1408
1384
|
publicKey: "0x041ab17cd91fc5c0b761eea6092d032807561b621b82c488826776e04a9158d61ba64d809a4729f0501289d980732cb06d8dfd06999dd9c8efd2f495dad78b31bb",
|
|
@@ -1410,17 +1386,17 @@ var require_pkg_src = __commonJS({
|
|
|
1410
1386
|
}
|
|
1411
1387
|
},
|
|
1412
1388
|
processPaymentValidator: {
|
|
1413
|
-
cid: "
|
|
1389
|
+
cid: "QmNXVdysXDhCaQ1uNE2Vgjbuzy6yUEnnVgHzmDfNKXMU6z",
|
|
1414
1390
|
authorizedCidHex: cidToHex(
|
|
1415
|
-
"
|
|
1391
|
+
"QmNXVdysXDhCaQ1uNE2Vgjbuzy6yUEnnVgHzmDfNKXMU6z"
|
|
1416
1392
|
),
|
|
1417
1393
|
name: "Process Payment Validator",
|
|
1418
1394
|
description: "Production Process Payment Validator",
|
|
1419
1395
|
version: "1.2.1",
|
|
1420
1396
|
deployed: true,
|
|
1421
|
-
deployedAt:
|
|
1422
|
-
size:
|
|
1423
|
-
hash: "
|
|
1397
|
+
deployedAt: 1786113909138,
|
|
1398
|
+
size: 78736,
|
|
1399
|
+
hash: "17191ecc9c009e0fa5ab52c876330beb2a9a75d30cccb92e7c0c04f169df088b",
|
|
1424
1400
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1425
1401
|
pkp: {
|
|
1426
1402
|
publicKey: "0x04cfccb9b6c7addc79469c0e9fafebef4c70801302c62cdb26b610723a9b8527d23f98a40158c7340d83fd0e62d02b5ec74c12f7a86d98e8c6973b4811110f19ad",
|
|
@@ -1428,17 +1404,17 @@ var require_pkg_src = __commonJS({
|
|
|
1428
1404
|
}
|
|
1429
1405
|
},
|
|
1430
1406
|
extendPositionValidator: {
|
|
1431
|
-
cid: "
|
|
1407
|
+
cid: "QmYwBsyrhGfHWyRKx549G4tKHxsBLAuRUHcecigeUCkFav",
|
|
1432
1408
|
authorizedCidHex: cidToHex(
|
|
1433
|
-
"
|
|
1409
|
+
"QmYwBsyrhGfHWyRKx549G4tKHxsBLAuRUHcecigeUCkFav"
|
|
1434
1410
|
),
|
|
1435
1411
|
name: "Extend Position Validator",
|
|
1436
1412
|
description: "Production Extend Position Validator",
|
|
1437
1413
|
version: "1.2.1",
|
|
1438
1414
|
deployed: true,
|
|
1439
|
-
deployedAt:
|
|
1440
|
-
size:
|
|
1441
|
-
hash: "
|
|
1415
|
+
deployedAt: 1786113909872,
|
|
1416
|
+
size: 76515,
|
|
1417
|
+
hash: "1d4e085335af80f194a663a577876902c83015177e95906a0b461fa93bd5da08",
|
|
1442
1418
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1443
1419
|
pkp: {
|
|
1444
1420
|
publicKey: "0x04e52338f6a0c3362800f1a94c4c8f08be5030dde0fa217388c53d1c1a4790f4443c9f2886b2d2d8f4b75a9a156e9b028011b1a1cf7648da0e556aa980ec692836",
|
|
@@ -1446,9 +1422,9 @@ var require_pkg_src = __commonJS({
|
|
|
1446
1422
|
}
|
|
1447
1423
|
},
|
|
1448
1424
|
btcWithdrawal: {
|
|
1449
|
-
cid: "
|
|
1425
|
+
cid: "QmZuR3qSBajutXfRd91ffpyFrRMgmaMh7fDEHRiQQF8Ljp",
|
|
1450
1426
|
authorizedCidHex: cidToHex(
|
|
1451
|
-
"
|
|
1427
|
+
"QmZuR3qSBajutXfRd91ffpyFrRMgmaMh7fDEHRiQQF8Ljp"
|
|
1452
1428
|
),
|
|
1453
1429
|
name: "Btc Withdrawal",
|
|
1454
1430
|
description: "Production Btc Withdrawal",
|
|
@@ -1458,9 +1434,9 @@ var require_pkg_src = __commonJS({
|
|
|
1458
1434
|
// than the one it replaced, which a rotation ceremony would treat as a rollback.
|
|
1459
1435
|
version: "1.2.4",
|
|
1460
1436
|
deployed: true,
|
|
1461
|
-
deployedAt:
|
|
1462
|
-
size:
|
|
1463
|
-
hash: "
|
|
1437
|
+
deployedAt: 1786113910531,
|
|
1438
|
+
size: 95313,
|
|
1439
|
+
hash: "ce12f11e3b5469953bc919586e4f3b8b5d594224b58b799dd9a3b60e3749559c",
|
|
1464
1440
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1465
1441
|
pkp: {
|
|
1466
1442
|
publicKey: "0x043616787c5432415c24378c4ef48de2bcd6bb7b575b837e3cff09171802662da7105d79586c7659677a0ecbaddac4cce06cb2a11f69a16fa0c4d7002ac7d51a4d",
|
|
@@ -1571,6 +1547,21 @@ var require_pkg_src = __commonJS({
|
|
|
1571
1547
|
size: 1046,
|
|
1572
1548
|
hash: "03ee5be239c91c735d2c95db89a0d1b77e602cfc18af8eb64b3281fddcd90a72"
|
|
1573
1549
|
},
|
|
1550
|
+
agentModuleExecutor: {
|
|
1551
|
+
// Bound to the isolated Sepolia agent group 7 ("DH-Agent-Sepolia"), which
|
|
1552
|
+
// holds this action and no PKPs until an agent is minted into it. NOT yet
|
|
1553
|
+
// bound on mainnet — the Base `DH-Agent-Mainnet` group is created at rollout.
|
|
1554
|
+
cid: "QmVgjGXUuxoGf8XyddW8a7btaazT9yEotpKoBgb65oPvcF",
|
|
1555
|
+
authorizedCidHex: cidToHex(
|
|
1556
|
+
"QmVgjGXUuxoGf8XyddW8a7btaazT9yEotpKoBgb65oPvcF"
|
|
1557
|
+
),
|
|
1558
|
+
name: "Agent Module Executor",
|
|
1559
|
+
description: "Capability gate for multi-sig Safe agent delegation: binds the agent PKP to the AgentDelegationRegistry record before signing (authorize | execute modes)",
|
|
1560
|
+
version: "1.0.0",
|
|
1561
|
+
deployed: true,
|
|
1562
|
+
size: 11785,
|
|
1563
|
+
hash: "fa294e164273c2158f9ef11a0ccfd735c343c0e3759fcb4b37e0f8a5d1ab0d29"
|
|
1564
|
+
},
|
|
1574
1565
|
staleSpendRecoveryValidator: {
|
|
1575
1566
|
cid: "QmXQWT8V4wjuvH8BbC7xrkYxsVu5AKfvfWcS7jnYfrexYm",
|
|
1576
1567
|
authorizedCidHex: cidToHex(
|
|
@@ -1590,17 +1581,17 @@ var require_pkg_src = __commonJS({
|
|
|
1590
1581
|
}
|
|
1591
1582
|
},
|
|
1592
1583
|
btcUtxoInvalidator: {
|
|
1593
|
-
cid: "
|
|
1584
|
+
cid: "Qmf4Ze8yhnQhHbnX5RPgJrC7Jw1aA3gbpfpd52kFg7fd1m",
|
|
1594
1585
|
authorizedCidHex: cidToHex(
|
|
1595
|
-
"
|
|
1586
|
+
"Qmf4Ze8yhnQhHbnX5RPgJrC7Jw1aA3gbpfpd52kFg7fd1m"
|
|
1596
1587
|
),
|
|
1597
1588
|
name: "Btc Utxo Invalidator",
|
|
1598
1589
|
description: "Production Btc Utxo Invalidator",
|
|
1599
1590
|
version: "1.0.0",
|
|
1600
1591
|
deployed: true,
|
|
1601
|
-
deployedAt:
|
|
1602
|
-
size:
|
|
1603
|
-
hash: "
|
|
1592
|
+
deployedAt: 1786113911948,
|
|
1593
|
+
size: 62553,
|
|
1594
|
+
hash: "1dcea2b49e545c423195be22e072dc5e3039ed1c95a8490b23505956029f5b33"
|
|
1604
1595
|
}
|
|
1605
1596
|
};
|
|
1606
1597
|
function getDeploymentsForNetwork(network) {
|
|
@@ -5507,8 +5498,14 @@ function getMainnetConfig() {
|
|
|
5507
5498
|
agentModuleFactory: MAINNET_CONTRACTS.AgentModuleFactory || ""
|
|
5508
5499
|
},
|
|
5509
5500
|
subgraphs: {
|
|
5501
|
+
// KNOWN-WRONG placeholder (see GOAL-PLAN step 2 / dogfood F-CLI-4): this is a
|
|
5502
|
+
// SEPOLIA subgraph id in a mainnet (chainId 1) config, and this gateway host
|
|
5503
|
+
// needs an Authorization key the SDK does not (and must not) hold — so the
|
|
5504
|
+
// URL is doubly unusable. The `CCTPsd…` path segment is the subgraph's
|
|
5505
|
+
// PUBLIC ID, not a credential. The fix is a chain-correct SERVER-side proxy
|
|
5506
|
+
// (see mcp/docs/SUBGRAPH-PROXY-PLAN.md) — same rule as rpcUrls above:
|
|
5507
|
+
// never ship an API-keyed URL in a client config.
|
|
5510
5508
|
diamondHandsUrl: "https://gateway-arbitrum.network.thegraph.com/api/subgraphs/id/CCTPsdYqco2jChDLLBQTbdJWwoukVoMt1cXeR9ti6r9A"
|
|
5511
|
-
// Published Sepolia subgraph on Arbitrum One
|
|
5512
5509
|
},
|
|
5513
5510
|
litNetwork: "chipotle",
|
|
5514
5511
|
debug: false
|
|
@@ -6253,7 +6250,10 @@ var import_ethers2 = require("ethers");
|
|
|
6253
6250
|
// src/utils/quantum-timing.ts
|
|
6254
6251
|
var QUANTUM_WINDOW_SECONDS = 60;
|
|
6255
6252
|
var DEAD_ZONE_SECONDS = 8;
|
|
6256
|
-
var
|
|
6253
|
+
var SLOT_SECONDS = 12;
|
|
6254
|
+
var INCLUSION_SLOT_TOLERANCE = 3;
|
|
6255
|
+
var INCLUSION_LATENCY_BUDGET = SLOT_SECONDS * INCLUSION_SLOT_TOLERANCE;
|
|
6256
|
+
var MAX_GATE_POLLS = 12;
|
|
6257
6257
|
var POST_BOUNDARY_SKEW_MARGIN = 3;
|
|
6258
6258
|
function calculateNextQuantumTimestamp() {
|
|
6259
6259
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -6286,8 +6286,17 @@ function validateQuantumTiming(signedTimestamp, _bufferSeconds = 30) {
|
|
|
6286
6286
|
async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
|
|
6287
6287
|
const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
6288
6288
|
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6289
|
-
const nowSec = now();
|
|
6290
6289
|
const signatureQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6290
|
+
if (opts.chainNow) {
|
|
6291
|
+
const chainResult = await awaitSafeSubmissionWindowOnChain(
|
|
6292
|
+
signatureQuantum,
|
|
6293
|
+
opts.chainNow,
|
|
6294
|
+
sleep
|
|
6295
|
+
);
|
|
6296
|
+
if (chainResult)
|
|
6297
|
+
return chainResult;
|
|
6298
|
+
}
|
|
6299
|
+
const nowSec = now();
|
|
6291
6300
|
const currentQuantum = Math.floor(nowSec / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6292
6301
|
const secsToBoundary = QUANTUM_WINDOW_SECONDS - nowSec % QUANTUM_WINDOW_SECONDS;
|
|
6293
6302
|
const isNextQuantumSig = signatureQuantum === currentQuantum + QUANTUM_WINDOW_SECONDS;
|
|
@@ -6304,6 +6313,44 @@ async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
|
|
|
6304
6313
|
}
|
|
6305
6314
|
return { waited: false, waitedSeconds: 0 };
|
|
6306
6315
|
}
|
|
6316
|
+
async function awaitSafeSubmissionWindowOnChain(signatureQuantum, chainNow, sleep) {
|
|
6317
|
+
let waitedSeconds = 0;
|
|
6318
|
+
for (let poll = 0; poll < MAX_GATE_POLLS; poll++) {
|
|
6319
|
+
let head;
|
|
6320
|
+
try {
|
|
6321
|
+
head = await chainNow();
|
|
6322
|
+
} catch {
|
|
6323
|
+
head = null;
|
|
6324
|
+
}
|
|
6325
|
+
if (head === null || !Number.isFinite(head)) {
|
|
6326
|
+
return waitedSeconds > 0 ? { waited: true, waitedSeconds } : null;
|
|
6327
|
+
}
|
|
6328
|
+
const chainQuantum = Math.floor(head / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6329
|
+
if (signatureQuantum <= chainQuantum) {
|
|
6330
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6331
|
+
}
|
|
6332
|
+
const earliestMine = head + SLOT_SECONDS;
|
|
6333
|
+
const latestMine = head + SLOT_SECONDS * INCLUSION_SLOT_TOLERANCE;
|
|
6334
|
+
const safeBeforeDeadZone = latestMine < signatureQuantum - DEAD_ZONE_SECONDS;
|
|
6335
|
+
const safeAfterBoundary = earliestMine >= signatureQuantum;
|
|
6336
|
+
if (safeBeforeDeadZone || safeAfterBoundary) {
|
|
6337
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6338
|
+
}
|
|
6339
|
+
const sleepSeconds = Math.max(
|
|
6340
|
+
1,
|
|
6341
|
+
Math.min(SLOT_SECONDS, signatureQuantum - head)
|
|
6342
|
+
);
|
|
6343
|
+
log.debug(
|
|
6344
|
+
`[Quantum Timing] \u23F3 Deferring send ${sleepSeconds}s to clear the quantum dead zone (sig quantum ${signatureQuantum}, chain head ${head}, insured mine window [${earliestMine}, ${latestMine}])`
|
|
6345
|
+
);
|
|
6346
|
+
await sleep(sleepSeconds * 1e3);
|
|
6347
|
+
waitedSeconds += sleepSeconds;
|
|
6348
|
+
}
|
|
6349
|
+
log.debug(
|
|
6350
|
+
`[Quantum Timing] \u26A0\uFE0F Dead-zone gate exhausted ${MAX_GATE_POLLS} chain polls (sig quantum ${signatureQuantum}) \u2014 proceeding`
|
|
6351
|
+
);
|
|
6352
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6353
|
+
}
|
|
6307
6354
|
|
|
6308
6355
|
// src/utils/mint-authorization.utils.ts
|
|
6309
6356
|
var PKP_NFT_ABI = [
|
|
@@ -6350,7 +6397,7 @@ async function generateMintAuthorization(positionId, amount, chainId, signerOrPr
|
|
|
6350
6397
|
signature
|
|
6351
6398
|
};
|
|
6352
6399
|
}
|
|
6353
|
-
async function generatePaymentAuthorization(positionId, amount, chainId,
|
|
6400
|
+
async function generatePaymentAuthorization(positionId, amount, chainId, signerOrPrecomputed) {
|
|
6354
6401
|
if (typeof positionId !== "string") {
|
|
6355
6402
|
throw new Error(`positionId must be a string, got ${typeof positionId}`);
|
|
6356
6403
|
}
|
|
@@ -6360,6 +6407,18 @@ async function generatePaymentAuthorization(positionId, amount, chainId, signer)
|
|
|
6360
6407
|
if (typeof chainId !== "number") {
|
|
6361
6408
|
throw new Error(`chainId must be a number, got ${typeof chainId}`);
|
|
6362
6409
|
}
|
|
6410
|
+
if (typeof signerOrPrecomputed === "object" && "signature" in signerOrPrecomputed && !("signMessage" in signerOrPrecomputed)) {
|
|
6411
|
+
const { timestamp: timestamp2, signature: signature2 } = signerOrPrecomputed;
|
|
6412
|
+
return {
|
|
6413
|
+
positionId,
|
|
6414
|
+
timestamp: timestamp2,
|
|
6415
|
+
chainId,
|
|
6416
|
+
amount: amount.toString(),
|
|
6417
|
+
action: "make-payment",
|
|
6418
|
+
signature: signature2
|
|
6419
|
+
};
|
|
6420
|
+
}
|
|
6421
|
+
const signer = signerOrPrecomputed;
|
|
6363
6422
|
const timestamp = await calculateNextQuantumTimestamp();
|
|
6364
6423
|
if (typeof timestamp !== "number") {
|
|
6365
6424
|
throw new Error(`timestamp must be a number, got ${typeof timestamp}`);
|
|
@@ -6601,6 +6660,26 @@ async function generateExtendAuthorization(positionId, newTerm, chainId, signerO
|
|
|
6601
6660
|
};
|
|
6602
6661
|
}
|
|
6603
6662
|
|
|
6663
|
+
// src/utils/authorization-provider.utils.ts
|
|
6664
|
+
async function resolveAuthorizationInput(provider, signer, ctx) {
|
|
6665
|
+
if (!provider)
|
|
6666
|
+
return signer;
|
|
6667
|
+
const result = await provider(ctx);
|
|
6668
|
+
if (!result || typeof result.signature !== "string" || result.signature.length === 0) {
|
|
6669
|
+
throw new Error(
|
|
6670
|
+
"authorizationProvider returned no signature \u2014 refusing to fall back to the connected signer, which would prompt for the owner quorum this provider exists to avoid"
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
if (!Number.isInteger(result.timestamp)) {
|
|
6674
|
+
throw new Error(
|
|
6675
|
+
`authorizationProvider returned a non-integer timestamp (${String(
|
|
6676
|
+
result.timestamp
|
|
6677
|
+
)}) \u2014 the protocol call must carry the same value the envelope was signed over`
|
|
6678
|
+
);
|
|
6679
|
+
}
|
|
6680
|
+
return { timestamp: result.timestamp, signature: result.signature };
|
|
6681
|
+
}
|
|
6682
|
+
|
|
6604
6683
|
// src/utils/eip712-login.ts
|
|
6605
6684
|
var import_ethers4 = require("ethers");
|
|
6606
6685
|
function buildLoginDomain(chainId) {
|
|
@@ -7260,6 +7339,20 @@ function decodeQuantumRevert(e) {
|
|
|
7260
7339
|
}
|
|
7261
7340
|
return { data, selector, name };
|
|
7262
7341
|
}
|
|
7342
|
+
var RETRYABLE_QUANTUM_ERROR_NAMES = [
|
|
7343
|
+
"DeadZoneViolation",
|
|
7344
|
+
"QuantumOutsideWindow",
|
|
7345
|
+
"QuantumAlreadyUsed",
|
|
7346
|
+
"QuantumExpired"
|
|
7347
|
+
];
|
|
7348
|
+
function isRetryableQuantumFailure(message) {
|
|
7349
|
+
if (!message)
|
|
7350
|
+
return false;
|
|
7351
|
+
if (message.includes("Transaction timeout") || message.includes("Transaction confirmation failed") || message.includes("Transaction receipt not found")) {
|
|
7352
|
+
return false;
|
|
7353
|
+
}
|
|
7354
|
+
return RETRYABLE_QUANTUM_ERROR_NAMES.some((name) => message.includes(name)) || message.includes("Transaction reverted");
|
|
7355
|
+
}
|
|
7263
7356
|
|
|
7264
7357
|
// src/utils/quantum-submission.utils.ts
|
|
7265
7358
|
var MAX_DEADZONE_RESIMULATIONS = 3;
|
|
@@ -7281,9 +7374,14 @@ async function assertSafeQuantumSubmission(opts) {
|
|
|
7281
7374
|
const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
7282
7375
|
const onDebug = opts.onDebug ?? (() => {
|
|
7283
7376
|
});
|
|
7377
|
+
const chainNow = opts.chainNow ?? (typeof provider.getBlock === "function" ? async () => {
|
|
7378
|
+
const block = await provider.getBlock("latest");
|
|
7379
|
+
return typeof block?.timestamp === "number" ? block.timestamp : null;
|
|
7380
|
+
} : void 0);
|
|
7284
7381
|
const gateOpts = {
|
|
7285
7382
|
now,
|
|
7286
|
-
...opts.sleep ? { sleep: opts.sleep } : {}
|
|
7383
|
+
...opts.sleep ? { sleep: opts.sleep } : {},
|
|
7384
|
+
...chainNow ? { chainNow } : {}
|
|
7287
7385
|
};
|
|
7288
7386
|
await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
|
|
7289
7387
|
const SAME_BLOCK_INTERACTION_SELECTOR = "0x3b53d1db";
|
|
@@ -19993,11 +20091,20 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
19993
20091
|
{}
|
|
19994
20092
|
);
|
|
19995
20093
|
}
|
|
20094
|
+
const mintAuthInput = await resolveAuthorizationInput(
|
|
20095
|
+
request.authorizationProvider,
|
|
20096
|
+
signer2,
|
|
20097
|
+
{
|
|
20098
|
+
positionId: request.positionId,
|
|
20099
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
20100
|
+
amount: requestAmountWei.toString()
|
|
20101
|
+
}
|
|
20102
|
+
);
|
|
19996
20103
|
const auth = await generateMintAuthorization(
|
|
19997
20104
|
request.positionId,
|
|
19998
20105
|
BigInt(requestAmountWei.toString()),
|
|
19999
20106
|
chainId,
|
|
20000
|
-
|
|
20107
|
+
mintAuthInput
|
|
20001
20108
|
);
|
|
20002
20109
|
if (this.config.debug) {
|
|
20003
20110
|
log.info(` Authorization generated (attempt ${attempt})`, {});
|
|
@@ -21588,7 +21695,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
21588
21695
|
* @param _selectedTerm - Extension term in months
|
|
21589
21696
|
* @returns Transaction receipt
|
|
21590
21697
|
*/
|
|
21591
|
-
async extendPosition(_positionId, _selectedTerm, _rpcUrl, _bitcoinRpcUrl) {
|
|
21698
|
+
async extendPosition(_positionId, _selectedTerm, _rpcUrl, _bitcoinRpcUrl, _authorizationProvider) {
|
|
21592
21699
|
this.ensureInitialized();
|
|
21593
21700
|
if (typeof _positionId !== "string") {
|
|
21594
21701
|
throw new Error(`positionId must be a string, got ${typeof _positionId}`);
|
|
@@ -21682,11 +21789,23 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
21682
21789
|
}
|
|
21683
21790
|
}
|
|
21684
21791
|
const signer = this.getSignerOrThrow();
|
|
21792
|
+
const extendAuthInput = await resolveAuthorizationInput(
|
|
21793
|
+
_authorizationProvider,
|
|
21794
|
+
signer,
|
|
21795
|
+
{
|
|
21796
|
+
positionId: _positionId,
|
|
21797
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
21798
|
+
// The extend envelope binds the TERM, not an amount. Carried as "0" so
|
|
21799
|
+
// the context shape stays uniform across the four operations.
|
|
21800
|
+
amount: "0",
|
|
21801
|
+
selectedTerm: _selectedTerm
|
|
21802
|
+
}
|
|
21803
|
+
);
|
|
21685
21804
|
const auth = await generateExtendAuthorization(
|
|
21686
21805
|
_positionId,
|
|
21687
21806
|
_selectedTerm,
|
|
21688
21807
|
chainId,
|
|
21689
|
-
|
|
21808
|
+
extendAuthInput
|
|
21690
21809
|
);
|
|
21691
21810
|
if (this.config.debug) {
|
|
21692
21811
|
log.info(" Extend authorization generated", {
|
|
@@ -21942,7 +22061,8 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
21942
22061
|
positionId,
|
|
21943
22062
|
selectedTerm,
|
|
21944
22063
|
request.rpcUrl,
|
|
21945
|
-
request.customBitcoinRpcUrl
|
|
22064
|
+
request.customBitcoinRpcUrl,
|
|
22065
|
+
request.authorizationProvider
|
|
21946
22066
|
);
|
|
21947
22067
|
let newExpiryAt;
|
|
21948
22068
|
try {
|
|
@@ -22293,10 +22413,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22293
22413
|
isRetryablePaymentQuantumFailure(result) {
|
|
22294
22414
|
if (result.success || !result.error)
|
|
22295
22415
|
return false;
|
|
22296
|
-
|
|
22297
|
-
if (e.includes("Transaction timeout"))
|
|
22298
|
-
return false;
|
|
22299
|
-
return e.includes("DeadZoneViolation") || e.includes("QuantumOutsideWindow") || e.includes("QuantumAlreadyUsed") || e.includes("QuantumExpired") || e === "Transaction reverted";
|
|
22416
|
+
return isRetryableQuantumFailure(result.error);
|
|
22300
22417
|
}
|
|
22301
22418
|
/**
|
|
22302
22419
|
* One payment attempt: user auth → Lit Action authorization → dead-zone gate →
|
|
@@ -22473,11 +22590,20 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22473
22590
|
}
|
|
22474
22591
|
let auth;
|
|
22475
22592
|
try {
|
|
22593
|
+
const paymentAuthInput = await resolveAuthorizationInput(
|
|
22594
|
+
request.authorizationProvider,
|
|
22595
|
+
signer,
|
|
22596
|
+
{
|
|
22597
|
+
positionId: request.positionId,
|
|
22598
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
22599
|
+
amount: paymentAmountWei.toString()
|
|
22600
|
+
}
|
|
22601
|
+
);
|
|
22476
22602
|
auth = await generatePaymentAuthorization(
|
|
22477
22603
|
request.positionId,
|
|
22478
22604
|
paymentAmountWei,
|
|
22479
22605
|
chainId,
|
|
22480
|
-
|
|
22606
|
+
paymentAuthInput
|
|
22481
22607
|
);
|
|
22482
22608
|
} catch (authError) {
|
|
22483
22609
|
if (authError.message && authError.message.includes("BigNumber")) {
|
|
@@ -22935,6 +23061,37 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22935
23061
|
}
|
|
22936
23062
|
return this.withdrawalAddressModule.getEntries(target);
|
|
22937
23063
|
}
|
|
23064
|
+
/**
|
|
23065
|
+
* Recover the revert reason of an already-mined, already-reverted transaction.
|
|
23066
|
+
*
|
|
23067
|
+
* A status-0 receipt carries no revert data, and ethers does not re-fetch it, so
|
|
23068
|
+
* the reason has to be recovered by replaying the exact calldata as an `eth_call`
|
|
23069
|
+
* pinned to the block that mined it — the only block whose `block.timestamp`
|
|
23070
|
+
* reproduces a timing-dependent revert such as `DeadZoneViolation()`.
|
|
23071
|
+
*
|
|
23072
|
+
* Caveat: the replay runs against end-of-block state rather than the state at the
|
|
23073
|
+
* transaction's own index, so for a state-dependent revert the recovered name is
|
|
23074
|
+
* diagnostic, not proof. Timing reverts (the case this exists for) are unaffected —
|
|
23075
|
+
* they depend only on the block timestamp.
|
|
23076
|
+
*
|
|
23077
|
+
* @returns the decoded error name, its raw selector if unknown, or `null` when the
|
|
23078
|
+
* replay yields no revert data at all (never throws — a failed diagnosis
|
|
23079
|
+
* must not mask the underlying failure).
|
|
23080
|
+
*/
|
|
23081
|
+
async decodeMinedRevert(params) {
|
|
23082
|
+
try {
|
|
23083
|
+
await this.getProviderOrThrow().call({
|
|
23084
|
+
to: params.to,
|
|
23085
|
+
from: params.from,
|
|
23086
|
+
data: params.data,
|
|
23087
|
+
blockTag: params.blockNumber
|
|
23088
|
+
});
|
|
23089
|
+
return null;
|
|
23090
|
+
} catch (replayError) {
|
|
23091
|
+
const decoded = decodeQuantumRevert(replayError);
|
|
23092
|
+
return decoded.name ?? decoded.selector ?? null;
|
|
23093
|
+
}
|
|
23094
|
+
}
|
|
22938
23095
|
/**
|
|
22939
23096
|
* Withdraw Bitcoin from a position
|
|
22940
23097
|
*
|
|
@@ -22958,7 +23115,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22958
23115
|
* @param customBitcoinRpcUrl - Optional custom Bitcoin RPC URL (for local testing)
|
|
22959
23116
|
* @returns Withdrawal result with transaction details
|
|
22960
23117
|
*/
|
|
22961
|
-
async withdrawBTC(positionId, withdrawalAddress, withdrawalAmount) {
|
|
23118
|
+
async withdrawBTC(positionId, withdrawalAddress, withdrawalAmount, options) {
|
|
22962
23119
|
this.ensureInitialized();
|
|
22963
23120
|
try {
|
|
22964
23121
|
if (this.config.debug) {
|
|
@@ -23173,11 +23330,21 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23173
23330
|
{}
|
|
23174
23331
|
);
|
|
23175
23332
|
}
|
|
23333
|
+
const withdrawAuthInput = await resolveAuthorizationInput(
|
|
23334
|
+
options?.authorizationProvider,
|
|
23335
|
+
signer,
|
|
23336
|
+
{
|
|
23337
|
+
positionId,
|
|
23338
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
23339
|
+
amount: BigInt(withdrawalAmount).toString(),
|
|
23340
|
+
destinationAddress: withdrawalAddress
|
|
23341
|
+
}
|
|
23342
|
+
);
|
|
23176
23343
|
const auth = await generateWithdrawAuthorization(
|
|
23177
23344
|
positionId,
|
|
23178
23345
|
BigInt(withdrawalAmount),
|
|
23179
23346
|
chainId,
|
|
23180
|
-
|
|
23347
|
+
withdrawAuthInput,
|
|
23181
23348
|
withdrawalAddress
|
|
23182
23349
|
// Pass destination address for message hash
|
|
23183
23350
|
);
|
|
@@ -23508,9 +23675,23 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23508
23675
|
txHash: tx.hash
|
|
23509
23676
|
});
|
|
23510
23677
|
}
|
|
23678
|
+
const withdrawProvider = this.getProviderOrThrow();
|
|
23679
|
+
const withdrawTimeoutMs = this.config.transactionTimeoutMs || 3e5;
|
|
23511
23680
|
let receipt;
|
|
23512
23681
|
try {
|
|
23513
|
-
receipt = await
|
|
23682
|
+
receipt = await Promise.race([
|
|
23683
|
+
withdrawProvider.waitForTransaction(tx.hash, 1),
|
|
23684
|
+
new Promise(
|
|
23685
|
+
(_, reject) => setTimeout(
|
|
23686
|
+
() => reject(
|
|
23687
|
+
new Error(
|
|
23688
|
+
`Transaction timeout after ${withdrawTimeoutMs}ms. Hash: ${tx.hash}`
|
|
23689
|
+
)
|
|
23690
|
+
),
|
|
23691
|
+
withdrawTimeoutMs
|
|
23692
|
+
)
|
|
23693
|
+
)
|
|
23694
|
+
]);
|
|
23514
23695
|
} catch (waitError) {
|
|
23515
23696
|
const waitErrorMsg = waitError instanceof Error ? waitError.message : String(waitError);
|
|
23516
23697
|
if (this.config.debug) {
|
|
@@ -23527,7 +23708,26 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23527
23708
|
if (!receipt) {
|
|
23528
23709
|
return {
|
|
23529
23710
|
success: false,
|
|
23530
|
-
error: `Transaction
|
|
23711
|
+
error: `Transaction receipt not found for hash: ${tx.hash}`
|
|
23712
|
+
};
|
|
23713
|
+
}
|
|
23714
|
+
if (receipt.status === 0) {
|
|
23715
|
+
const revertName = await this.decodeMinedRevert({
|
|
23716
|
+
to: positionManagerAddress,
|
|
23717
|
+
from: await withdrawSigner.getAddress(),
|
|
23718
|
+
data: withdrawCalldata,
|
|
23719
|
+
blockNumber: receipt.blockNumber
|
|
23720
|
+
});
|
|
23721
|
+
if (this.config.debug) {
|
|
23722
|
+
log.error("\u274C withdrawBTC reverted on-chain", {
|
|
23723
|
+
txHash: receipt.hash,
|
|
23724
|
+
blockNumber: receipt.blockNumber,
|
|
23725
|
+
revert: revertName ?? "undecodable"
|
|
23726
|
+
});
|
|
23727
|
+
}
|
|
23728
|
+
return {
|
|
23729
|
+
success: false,
|
|
23730
|
+
error: `Transaction reverted: ${revertName ?? "unknown reason"} (tx: ${receipt.hash})`
|
|
23531
23731
|
};
|
|
23532
23732
|
}
|
|
23533
23733
|
if (this.config.debug) {
|
|
@@ -24218,6 +24418,11 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24218
24418
|
* (`MAX_ATTESTATION_AGE = 1 hour`) and replay-protects via
|
|
24219
24419
|
* `authorizedAt` binding.
|
|
24220
24420
|
*
|
|
24421
|
+
* WHO PAYS: in **service mode** lit-ops-server performs BOTH steps and its
|
|
24422
|
+
* relayer funds the transaction — the borrower is never prompted, because the
|
|
24423
|
+
* Bitcoin withdrawal this cleans up after has already settled. In **direct
|
|
24424
|
+
* mode** (CLI / tests / hardhat) the SDK's own signer submits step 2.
|
|
24425
|
+
*
|
|
24221
24426
|
* `invalidatorTxid` is either:
|
|
24222
24427
|
* - the borrower's own authorized broadcast (classified "consumed-as-authorized"
|
|
24223
24428
|
* by the LIT validator), once it has ≥6 confirmations on the BTC
|
|
@@ -24230,7 +24435,6 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24230
24435
|
async recoverStaleSpend(params) {
|
|
24231
24436
|
this.ensureInitialized();
|
|
24232
24437
|
try {
|
|
24233
|
-
const signer = this.getSignerOrThrow();
|
|
24234
24438
|
const contractAddresses = this.getContractAddressesOrThrow();
|
|
24235
24439
|
const btcSpendAuthorizerAddress = contractAddresses.btcSpendAuthorizer;
|
|
24236
24440
|
if (!btcSpendAuthorizerAddress) {
|
|
@@ -24276,6 +24480,28 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24276
24480
|
success: false,
|
|
24277
24481
|
error: json.error ?? "stale-spend recovery request failed"
|
|
24278
24482
|
};
|
|
24483
|
+
if (!attRes.success || !attRes.attestation) {
|
|
24484
|
+
return {
|
|
24485
|
+
success: false,
|
|
24486
|
+
failedStep: "attestation",
|
|
24487
|
+
error: attRes.error ?? attRes.reason ?? `LIT recovery attestation rejected${attRes.failedStep ? ` at step ${attRes.failedStep}` : ""}`
|
|
24488
|
+
};
|
|
24489
|
+
}
|
|
24490
|
+
if (!attRes.transactionHash && !attRes.alreadyCleared) {
|
|
24491
|
+
return {
|
|
24492
|
+
success: false,
|
|
24493
|
+
failedStep: "submit",
|
|
24494
|
+
error: "lit-ops-server returned a stale-spend attestation but did not submit it. The server is on a build without the recovery relayer, or its STALE_SPEND_RELAYER_PRIVATE_KEY is unset."
|
|
24495
|
+
};
|
|
24496
|
+
}
|
|
24497
|
+
return {
|
|
24498
|
+
success: true,
|
|
24499
|
+
...attRes.transactionHash && {
|
|
24500
|
+
transactionHash: attRes.transactionHash
|
|
24501
|
+
},
|
|
24502
|
+
...attRes.classification && { classification: attRes.classification },
|
|
24503
|
+
attestation: attRes.attestation
|
|
24504
|
+
};
|
|
24279
24505
|
} else {
|
|
24280
24506
|
const rpcUrl = params.rpcUrl ?? this.config.ethRpcUrl;
|
|
24281
24507
|
if (!rpcUrl) {
|
|
@@ -24325,7 +24551,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24325
24551
|
[
|
|
24326
24552
|
"function cancelStaleSpendWithProof(bytes32 positionId, bytes32 utxoKey, uint256 authorizedAt, string calldata invalidatorTxid, uint256 attestationTimestamp, bytes calldata litSignature) external"
|
|
24327
24553
|
],
|
|
24328
|
-
|
|
24554
|
+
this.getSignerOrThrow()
|
|
24329
24555
|
);
|
|
24330
24556
|
const tx = await btcSpendAuthorizer["cancelStaleSpendWithProof"](
|
|
24331
24557
|
attRes.attestation.positionId,
|
|
@@ -24655,13 +24881,11 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24655
24881
|
if (authResult.success) {
|
|
24656
24882
|
break;
|
|
24657
24883
|
}
|
|
24658
|
-
const isQuantumError = authResult.error
|
|
24659
|
-
"QuantumOutsideWindow"
|
|
24660
|
-
);
|
|
24884
|
+
const isQuantumError = isRetryableQuantumFailure(authResult.error);
|
|
24661
24885
|
if (isQuantumError && wAttempt < MAX_WITHDRAWAL_QUANTUM_RETRIES) {
|
|
24662
24886
|
if (this.config.debug) {
|
|
24663
24887
|
log.warn(
|
|
24664
|
-
`\u26A0\uFE0F Phase 1
|
|
24888
|
+
`\u26A0\uFE0F Phase 1 quantum-timing failure (attempt ${wAttempt}/${MAX_WITHDRAWAL_QUANTUM_RETRIES}): ${authResult.error}. Retrying with a fresh quantum...`
|
|
24665
24889
|
);
|
|
24666
24890
|
}
|
|
24667
24891
|
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
@@ -25135,10 +25359,22 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25135
25359
|
"getLoanGrant",
|
|
25136
25360
|
data
|
|
25137
25361
|
);
|
|
25362
|
+
const raw = (0, import_ethers17.getBytes)(data);
|
|
25363
|
+
const withdrawScopeSupported = raw.length >= 128;
|
|
25364
|
+
const minWithdrawRatioBps = withdrawScopeSupported ? Number(
|
|
25365
|
+
import_ethers17.AbiCoder.defaultAbiCoder().decode(["uint32"], raw.slice(96, 128))[0]
|
|
25366
|
+
) : 0;
|
|
25138
25367
|
return {
|
|
25139
25368
|
borrower,
|
|
25140
25369
|
scopeBits: Number(scopeBits),
|
|
25141
|
-
minCollateralRatioBps: Number(minCollateralRatioBps)
|
|
25370
|
+
minCollateralRatioBps: Number(minCollateralRatioBps),
|
|
25371
|
+
minWithdrawRatioBps,
|
|
25372
|
+
/**
|
|
25373
|
+
* True when the deployed registry is v2.1.0+ and therefore honours `SCOPE_WITHDRAW`. The
|
|
25374
|
+
* frontend gates its Withdrawal control on this so the control is absent — not disabled,
|
|
25375
|
+
* not "coming soon" — on a chain whose registry cannot support it.
|
|
25376
|
+
*/
|
|
25377
|
+
withdrawScopeSupported
|
|
25142
25378
|
};
|
|
25143
25379
|
}
|
|
25144
25380
|
/**
|
|
@@ -25180,10 +25416,14 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25180
25416
|
const mintEnabled = hasGrant && (scopes & 4) !== 0;
|
|
25181
25417
|
const floor = grant.minCollateralRatioBps;
|
|
25182
25418
|
const agent = user ? await registry.agentOf(user) : null;
|
|
25419
|
+
const withdrawEnabled = hasGrant && grant.withdrawScopeSupported && (scopes & 8) !== 0;
|
|
25183
25420
|
return {
|
|
25184
25421
|
renewEnabled: hasGrant && (scopes & 1) !== 0,
|
|
25185
25422
|
repayEnabled: hasGrant && (scopes & 2) !== 0,
|
|
25186
25423
|
mintEnabled,
|
|
25424
|
+
withdrawEnabled,
|
|
25425
|
+
withdrawScopeSupported: grant.withdrawScopeSupported,
|
|
25426
|
+
minWithdrawRatioBps: withdrawEnabled ? grant.minWithdrawRatioBps : 0,
|
|
25187
25427
|
minCollateralRatioBps: mintEnabled ? floor : 0,
|
|
25188
25428
|
effectiveMintFloorBps: mintEnabled ? Math.max(floor, protocolMin) : 0,
|
|
25189
25429
|
protocolMinFloorBps: protocolMin,
|
|
@@ -25369,8 +25609,83 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25369
25609
|
const receipt = await tx.wait();
|
|
25370
25610
|
return { hash: tx.hash, blockNumber: receipt?.blockNumber ?? 0 };
|
|
25371
25611
|
}
|
|
25372
|
-
/**
|
|
25373
|
-
|
|
25612
|
+
/**
|
|
25613
|
+
* Grant the agent the WITHDRAW scope for a position (registry v2.1.0+). Borrower-signed.
|
|
25614
|
+
*
|
|
25615
|
+
* CONTRACT WALLETS ONLY, and this is enforced here rather than on-chain. The withdrawal
|
|
25616
|
+
* validator's only non-borrower arm is `checkSafeAgentWithdrawAuthorization`, which requires a
|
|
25617
|
+
* factory-provenanced `AgentModule` whose `safe()` is the borrower — something an EOA cannot
|
|
25618
|
+
* have. The registry deliberately does NOT check wallet type ("Callable by any borrower (this
|
|
25619
|
+
* function does not itself require a Safe)"), so an EOA can record a grant that no code path
|
|
25620
|
+
* will ever satisfy. Refusing here keeps the supported path from writing a dead record.
|
|
25621
|
+
*
|
|
25622
|
+
* That refusal is ergonomics, NOT enforcement — `enableAutoWithdraw` is `external` and reachable
|
|
25623
|
+
* from any wallet. The real reason delegated withdrawal is closed to EOAs is that they can sign
|
|
25624
|
+
* inside the ~44s window themselves, so delegating buys them nothing and only adds risk.
|
|
25625
|
+
*
|
|
25626
|
+
* @param minWithdrawRatioBps Post-withdrawal collateral-ratio floor for AGENT withdrawals.
|
|
25627
|
+
* Clamped by the same governance minimum as the mint floor. **Inert at zero debt** — it
|
|
25628
|
+
* is a ratio, and the post-withdrawal ratio is infinite for any amount once debt is 0.
|
|
25629
|
+
* The operative bound there is the quorum-gated destination allowlist. Do not surface
|
|
25630
|
+
* this to users as a cap on how much an agent may withdraw.
|
|
25631
|
+
* There is no in-place raise: changing it means disable-then-re-enable, deliberately —
|
|
25632
|
+
* on the one scope that moves custodied BTC, incident response can only disable outright.
|
|
25633
|
+
*/
|
|
25634
|
+
async enableAutoWithdraw(positionId, minWithdrawRatioBps, options) {
|
|
25635
|
+
const pid = this.toBytes32(positionId);
|
|
25636
|
+
await this.assertBorrowerIsContractWallet(pid);
|
|
25637
|
+
await this.assertMintFloorAtOrAboveProtocolMin(minWithdrawRatioBps, "withdraw");
|
|
25638
|
+
const { registry, agentAddress } = await this.prepareAgentDelegation(pid, options);
|
|
25639
|
+
const tx = await registry.enableAutoWithdraw(pid, minWithdrawRatioBps);
|
|
25640
|
+
const receipt = await tx.wait();
|
|
25641
|
+
return { hash: tx.hash, blockNumber: receipt?.blockNumber ?? 0, agentAddress };
|
|
25642
|
+
}
|
|
25643
|
+
/**
|
|
25644
|
+
* Revoke the WITHDRAW scope for a position (also zeroes its withdraw floor). Borrower-signed.
|
|
25645
|
+
*
|
|
25646
|
+
* No wallet-type check: revoking is the safe direction, and an EOA that recorded a grant before
|
|
25647
|
+
* this guard existed must be able to clear it.
|
|
25648
|
+
*/
|
|
25649
|
+
async disableAutoWithdraw(positionId) {
|
|
25650
|
+
const addr = this.agentDelegationRegistryOrThrow();
|
|
25651
|
+
const signer = this.getSignerOrThrow();
|
|
25652
|
+
const registry = AgentDelegationRegistry__factory.connect(addr, signer);
|
|
25653
|
+
const pid = this.toBytes32(positionId);
|
|
25654
|
+
const tx = await registry.disableAutoWithdraw(pid);
|
|
25655
|
+
const receipt = await tx.wait();
|
|
25656
|
+
await this.clearGate1IfNoScopesRemain(pid);
|
|
25657
|
+
return { hash: tx.hash, blockNumber: receipt?.blockNumber ?? 0 };
|
|
25658
|
+
}
|
|
25659
|
+
/**
|
|
25660
|
+
* Layer 2 of the withdrawal-delegation gating: refuse to write a grant that could never be
|
|
25661
|
+
* satisfied. See `enableAutoWithdraw` for why this lives here and not in the registry.
|
|
25662
|
+
*/
|
|
25663
|
+
async assertBorrowerIsContractWallet(pid) {
|
|
25664
|
+
const details = await this.getPositionDetailsView(pid);
|
|
25665
|
+
const borrower = details?.borrower;
|
|
25666
|
+
if (!borrower) {
|
|
25667
|
+
throw new SDKError({
|
|
25668
|
+
message: `Could not resolve the borrower for position ${pid} \u2014 refusing to write a delegation grant against an unverified position`,
|
|
25669
|
+
category: "VALIDATION" /* VALIDATION */,
|
|
25670
|
+
severity: "HIGH" /* HIGH */
|
|
25671
|
+
});
|
|
25672
|
+
}
|
|
25673
|
+
const code = await this.getProviderOrThrow().getCode(borrower);
|
|
25674
|
+
if (!code || code === "0x") {
|
|
25675
|
+
throw new SDKError({
|
|
25676
|
+
message: `Withdrawal delegation is only available to contract wallets. Borrower ${borrower} is an EOA, and the withdrawal validator has no delegate arm for one \u2014 an EOA can sign inside the withdrawal quantum window itself, so the grant would be recorded on-chain and never honoured.`,
|
|
25677
|
+
category: "VALIDATION" /* VALIDATION */,
|
|
25678
|
+
severity: "MEDIUM" /* MEDIUM */
|
|
25679
|
+
});
|
|
25680
|
+
}
|
|
25681
|
+
}
|
|
25682
|
+
/**
|
|
25683
|
+
* Pre-flight the registry's `FloorBelowProtocolMin` guard with a message a user can act on.
|
|
25684
|
+
*
|
|
25685
|
+
* `agentMintFloorMinBps` is the governance floor-of-floors for BOTH the mint and withdraw
|
|
25686
|
+
* grants — one lever, one invariant — so `scope` only shapes the message.
|
|
25687
|
+
*/
|
|
25688
|
+
async assertMintFloorAtOrAboveProtocolMin(floorBps, scope = "mint") {
|
|
25374
25689
|
if (!Number.isInteger(floorBps) || floorBps <= 0) {
|
|
25375
25690
|
throw new SDKError({
|
|
25376
25691
|
message: `Invalid collateral floor: ${floorBps} bps \u2014 must be a positive integer`,
|
|
@@ -25381,7 +25696,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25381
25696
|
const protocolMin = await this.getAgentMintFloorMinBps();
|
|
25382
25697
|
if (protocolMin === 0) {
|
|
25383
25698
|
throw new SDKError({
|
|
25384
|
-
message:
|
|
25699
|
+
message: `Agent ${scope === "mint" ? "minting" : "withdrawal"} is not configured on this network (agentMintFloorMinBps is 0) \u2014 the registry would reject every ${scope} grant`,
|
|
25385
25700
|
category: "CONFIGURATION" /* CONFIGURATION */,
|
|
25386
25701
|
severity: "HIGH" /* HIGH */
|
|
25387
25702
|
});
|