@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.mjs
CHANGED
|
@@ -360,7 +360,7 @@ var init_deployment_addresses = __esm({
|
|
|
360
360
|
CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
|
|
361
361
|
CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
|
|
362
362
|
AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
|
|
363
|
-
AGENT_DELEGATION_REGISTRY_IMPL: "
|
|
363
|
+
AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
|
|
364
364
|
DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
|
|
365
365
|
}
|
|
366
366
|
};
|
|
@@ -630,7 +630,7 @@ var init_deployment_addresses = __esm({
|
|
|
630
630
|
LOCALHOST_DEPLOYMENT = {
|
|
631
631
|
network: "localhost",
|
|
632
632
|
chainId: 1337,
|
|
633
|
-
timestamp: "2026-07-
|
|
633
|
+
timestamp: "2026-07-20T19:45:58.514Z",
|
|
634
634
|
deployer: "",
|
|
635
635
|
contracts: {
|
|
636
636
|
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
@@ -650,30 +650,6 @@ var init_deployment_addresses = __esm({
|
|
|
650
650
|
PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
651
651
|
OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
652
652
|
PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
|
|
653
|
-
},
|
|
654
|
-
latestEnv: {
|
|
655
|
-
UCD_TOKEN: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
656
|
-
UCD_CONTROLLER: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
657
|
-
POSITION_MANAGER: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
658
|
-
PKP_VALIDATION_REGISTRY: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A",
|
|
659
|
-
PKP_VALIDATION_CID_V1: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
660
|
-
PKP_VALIDATION_CID_V3: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
661
|
-
PKP_ETH_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
662
|
-
OPERATION_AUTHORIZATION_REGISTRY: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
663
|
-
UCD_MINT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
664
|
-
UCD_MINT_VALIDATOR_VERSION: 1,
|
|
665
|
-
BTC_WITHDRAWAL_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
666
|
-
BTC_WITHDRAWAL_VALIDATOR_VERSION: 3,
|
|
667
|
-
UPDATE_BALANCE_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
668
|
-
UPDATE_BALANCE_VALIDATOR_VERSION: 1,
|
|
669
|
-
PROCESS_PAYMENT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
670
|
-
PROCESS_PAYMENT_VALIDATOR_VERSION: 1,
|
|
671
|
-
EXTEND_POSITION_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
672
|
-
EXTEND_POSITION_VALIDATOR_VERSION: 1,
|
|
673
|
-
POSITION_MANAGER_CORE_MODULE: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
|
|
674
|
-
LOAN_OPERATIONS_MANAGER_MODULE: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
|
|
675
|
-
TERM_MANAGER_MODULE: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
|
|
676
|
-
COMMUNITY_MANAGER_MODULE: ""
|
|
677
653
|
}
|
|
678
654
|
};
|
|
679
655
|
LOCALHOST_CONTRACTS = {
|
|
@@ -1385,30 +1361,30 @@ var require_pkg_src = __commonJS({
|
|
|
1385
1361
|
}
|
|
1386
1362
|
},
|
|
1387
1363
|
btcTransactionSigner: {
|
|
1388
|
-
cid: "
|
|
1364
|
+
cid: "QmakWzTce8NH1Jkgv1xwg83Bg9WmF2UG1Mq76pDYDSwwUp",
|
|
1389
1365
|
authorizedCidHex: cidToHex(
|
|
1390
|
-
"
|
|
1366
|
+
"QmakWzTce8NH1Jkgv1xwg83Bg9WmF2UG1Mq76pDYDSwwUp"
|
|
1391
1367
|
),
|
|
1392
1368
|
name: "Btc Transaction Signer",
|
|
1393
1369
|
description: "Production Btc Transaction Signer",
|
|
1394
1370
|
version: "1.1.1",
|
|
1395
1371
|
deployed: true,
|
|
1396
|
-
deployedAt:
|
|
1397
|
-
size:
|
|
1398
|
-
hash: "
|
|
1372
|
+
deployedAt: 1786113907707,
|
|
1373
|
+
size: 67999,
|
|
1374
|
+
hash: "83f3644c68819215e05ba8db3225571354a009458fc1437f606df6cb9142d3f9"
|
|
1399
1375
|
},
|
|
1400
1376
|
ucdMintValidator: {
|
|
1401
|
-
cid: "
|
|
1377
|
+
cid: "Qmd8jgKRceLvf5aoTtwdXG2JqMoBK5cRJwrvEtQkgvbFbx",
|
|
1402
1378
|
authorizedCidHex: cidToHex(
|
|
1403
|
-
"
|
|
1379
|
+
"Qmd8jgKRceLvf5aoTtwdXG2JqMoBK5cRJwrvEtQkgvbFbx"
|
|
1404
1380
|
),
|
|
1405
1381
|
name: "Ucd Mint Validator",
|
|
1406
1382
|
description: "Production Ucd Mint Validator",
|
|
1407
1383
|
version: "1.2.1",
|
|
1408
1384
|
deployed: true,
|
|
1409
|
-
deployedAt:
|
|
1410
|
-
size:
|
|
1411
|
-
hash: "
|
|
1385
|
+
deployedAt: 1786113908489,
|
|
1386
|
+
size: 82739,
|
|
1387
|
+
hash: "2bdd9aebdaad58a43a4731d5b7284f2531b7a9caaf8caf5719d891c4ed33cb2e",
|
|
1412
1388
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1413
1389
|
pkp: {
|
|
1414
1390
|
publicKey: "0x041ab17cd91fc5c0b761eea6092d032807561b621b82c488826776e04a9158d61ba64d809a4729f0501289d980732cb06d8dfd06999dd9c8efd2f495dad78b31bb",
|
|
@@ -1416,17 +1392,17 @@ var require_pkg_src = __commonJS({
|
|
|
1416
1392
|
}
|
|
1417
1393
|
},
|
|
1418
1394
|
processPaymentValidator: {
|
|
1419
|
-
cid: "
|
|
1395
|
+
cid: "QmNXVdysXDhCaQ1uNE2Vgjbuzy6yUEnnVgHzmDfNKXMU6z",
|
|
1420
1396
|
authorizedCidHex: cidToHex(
|
|
1421
|
-
"
|
|
1397
|
+
"QmNXVdysXDhCaQ1uNE2Vgjbuzy6yUEnnVgHzmDfNKXMU6z"
|
|
1422
1398
|
),
|
|
1423
1399
|
name: "Process Payment Validator",
|
|
1424
1400
|
description: "Production Process Payment Validator",
|
|
1425
1401
|
version: "1.2.1",
|
|
1426
1402
|
deployed: true,
|
|
1427
|
-
deployedAt:
|
|
1428
|
-
size:
|
|
1429
|
-
hash: "
|
|
1403
|
+
deployedAt: 1786113909138,
|
|
1404
|
+
size: 78736,
|
|
1405
|
+
hash: "17191ecc9c009e0fa5ab52c876330beb2a9a75d30cccb92e7c0c04f169df088b",
|
|
1430
1406
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1431
1407
|
pkp: {
|
|
1432
1408
|
publicKey: "0x04cfccb9b6c7addc79469c0e9fafebef4c70801302c62cdb26b610723a9b8527d23f98a40158c7340d83fd0e62d02b5ec74c12f7a86d98e8c6973b4811110f19ad",
|
|
@@ -1434,17 +1410,17 @@ var require_pkg_src = __commonJS({
|
|
|
1434
1410
|
}
|
|
1435
1411
|
},
|
|
1436
1412
|
extendPositionValidator: {
|
|
1437
|
-
cid: "
|
|
1413
|
+
cid: "QmYwBsyrhGfHWyRKx549G4tKHxsBLAuRUHcecigeUCkFav",
|
|
1438
1414
|
authorizedCidHex: cidToHex(
|
|
1439
|
-
"
|
|
1415
|
+
"QmYwBsyrhGfHWyRKx549G4tKHxsBLAuRUHcecigeUCkFav"
|
|
1440
1416
|
),
|
|
1441
1417
|
name: "Extend Position Validator",
|
|
1442
1418
|
description: "Production Extend Position Validator",
|
|
1443
1419
|
version: "1.2.1",
|
|
1444
1420
|
deployed: true,
|
|
1445
|
-
deployedAt:
|
|
1446
|
-
size:
|
|
1447
|
-
hash: "
|
|
1421
|
+
deployedAt: 1786113909872,
|
|
1422
|
+
size: 76515,
|
|
1423
|
+
hash: "1d4e085335af80f194a663a577876902c83015177e95906a0b461fa93bd5da08",
|
|
1448
1424
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1449
1425
|
pkp: {
|
|
1450
1426
|
publicKey: "0x04e52338f6a0c3362800f1a94c4c8f08be5030dde0fa217388c53d1c1a4790f4443c9f2886b2d2d8f4b75a9a156e9b028011b1a1cf7648da0e556aa980ec692836",
|
|
@@ -1452,9 +1428,9 @@ var require_pkg_src = __commonJS({
|
|
|
1452
1428
|
}
|
|
1453
1429
|
},
|
|
1454
1430
|
btcWithdrawal: {
|
|
1455
|
-
cid: "
|
|
1431
|
+
cid: "QmZuR3qSBajutXfRd91ffpyFrRMgmaMh7fDEHRiQQF8Ljp",
|
|
1456
1432
|
authorizedCidHex: cidToHex(
|
|
1457
|
-
"
|
|
1433
|
+
"QmZuR3qSBajutXfRd91ffpyFrRMgmaMh7fDEHRiQQF8Ljp"
|
|
1458
1434
|
),
|
|
1459
1435
|
name: "Btc Withdrawal",
|
|
1460
1436
|
description: "Production Btc Withdrawal",
|
|
@@ -1464,9 +1440,9 @@ var require_pkg_src = __commonJS({
|
|
|
1464
1440
|
// than the one it replaced, which a rotation ceremony would treat as a rollback.
|
|
1465
1441
|
version: "1.2.4",
|
|
1466
1442
|
deployed: true,
|
|
1467
|
-
deployedAt:
|
|
1468
|
-
size:
|
|
1469
|
-
hash: "
|
|
1443
|
+
deployedAt: 1786113910531,
|
|
1444
|
+
size: 95313,
|
|
1445
|
+
hash: "ce12f11e3b5469953bc919586e4f3b8b5d594224b58b799dd9a3b60e3749559c",
|
|
1470
1446
|
validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
|
|
1471
1447
|
pkp: {
|
|
1472
1448
|
publicKey: "0x043616787c5432415c24378c4ef48de2bcd6bb7b575b837e3cff09171802662da7105d79586c7659677a0ecbaddac4cce06cb2a11f69a16fa0c4d7002ac7d51a4d",
|
|
@@ -1577,6 +1553,21 @@ var require_pkg_src = __commonJS({
|
|
|
1577
1553
|
size: 1046,
|
|
1578
1554
|
hash: "03ee5be239c91c735d2c95db89a0d1b77e602cfc18af8eb64b3281fddcd90a72"
|
|
1579
1555
|
},
|
|
1556
|
+
agentModuleExecutor: {
|
|
1557
|
+
// Bound to the isolated Sepolia agent group 7 ("DH-Agent-Sepolia"), which
|
|
1558
|
+
// holds this action and no PKPs until an agent is minted into it. NOT yet
|
|
1559
|
+
// bound on mainnet — the Base `DH-Agent-Mainnet` group is created at rollout.
|
|
1560
|
+
cid: "QmVgjGXUuxoGf8XyddW8a7btaazT9yEotpKoBgb65oPvcF",
|
|
1561
|
+
authorizedCidHex: cidToHex(
|
|
1562
|
+
"QmVgjGXUuxoGf8XyddW8a7btaazT9yEotpKoBgb65oPvcF"
|
|
1563
|
+
),
|
|
1564
|
+
name: "Agent Module Executor",
|
|
1565
|
+
description: "Capability gate for multi-sig Safe agent delegation: binds the agent PKP to the AgentDelegationRegistry record before signing (authorize | execute modes)",
|
|
1566
|
+
version: "1.0.0",
|
|
1567
|
+
deployed: true,
|
|
1568
|
+
size: 11785,
|
|
1569
|
+
hash: "fa294e164273c2158f9ef11a0ccfd735c343c0e3759fcb4b37e0f8a5d1ab0d29"
|
|
1570
|
+
},
|
|
1580
1571
|
staleSpendRecoveryValidator: {
|
|
1581
1572
|
cid: "QmXQWT8V4wjuvH8BbC7xrkYxsVu5AKfvfWcS7jnYfrexYm",
|
|
1582
1573
|
authorizedCidHex: cidToHex(
|
|
@@ -1596,17 +1587,17 @@ var require_pkg_src = __commonJS({
|
|
|
1596
1587
|
}
|
|
1597
1588
|
},
|
|
1598
1589
|
btcUtxoInvalidator: {
|
|
1599
|
-
cid: "
|
|
1590
|
+
cid: "Qmf4Ze8yhnQhHbnX5RPgJrC7Jw1aA3gbpfpd52kFg7fd1m",
|
|
1600
1591
|
authorizedCidHex: cidToHex(
|
|
1601
|
-
"
|
|
1592
|
+
"Qmf4Ze8yhnQhHbnX5RPgJrC7Jw1aA3gbpfpd52kFg7fd1m"
|
|
1602
1593
|
),
|
|
1603
1594
|
name: "Btc Utxo Invalidator",
|
|
1604
1595
|
description: "Production Btc Utxo Invalidator",
|
|
1605
1596
|
version: "1.0.0",
|
|
1606
1597
|
deployed: true,
|
|
1607
|
-
deployedAt:
|
|
1608
|
-
size:
|
|
1609
|
-
hash: "
|
|
1598
|
+
deployedAt: 1786113911948,
|
|
1599
|
+
size: 62553,
|
|
1600
|
+
hash: "1dcea2b49e545c423195be22e072dc5e3039ed1c95a8490b23505956029f5b33"
|
|
1610
1601
|
}
|
|
1611
1602
|
};
|
|
1612
1603
|
function getDeploymentsForNetwork(network) {
|
|
@@ -5513,8 +5504,14 @@ function getMainnetConfig() {
|
|
|
5513
5504
|
agentModuleFactory: MAINNET_CONTRACTS.AgentModuleFactory || ""
|
|
5514
5505
|
},
|
|
5515
5506
|
subgraphs: {
|
|
5507
|
+
// KNOWN-WRONG placeholder (see GOAL-PLAN step 2 / dogfood F-CLI-4): this is a
|
|
5508
|
+
// SEPOLIA subgraph id in a mainnet (chainId 1) config, and this gateway host
|
|
5509
|
+
// needs an Authorization key the SDK does not (and must not) hold — so the
|
|
5510
|
+
// URL is doubly unusable. The `CCTPsd…` path segment is the subgraph's
|
|
5511
|
+
// PUBLIC ID, not a credential. The fix is a chain-correct SERVER-side proxy
|
|
5512
|
+
// (see mcp/docs/SUBGRAPH-PROXY-PLAN.md) — same rule as rpcUrls above:
|
|
5513
|
+
// never ship an API-keyed URL in a client config.
|
|
5516
5514
|
diamondHandsUrl: "https://gateway-arbitrum.network.thegraph.com/api/subgraphs/id/CCTPsdYqco2jChDLLBQTbdJWwoukVoMt1cXeR9ti6r9A"
|
|
5517
|
-
// Published Sepolia subgraph on Arbitrum One
|
|
5518
5515
|
},
|
|
5519
5516
|
litNetwork: "chipotle",
|
|
5520
5517
|
debug: false
|
|
@@ -6164,7 +6161,10 @@ import { keccak256, toUtf8Bytes, solidityPackedKeccak256, getBytes, Contract } f
|
|
|
6164
6161
|
// src/utils/quantum-timing.ts
|
|
6165
6162
|
var QUANTUM_WINDOW_SECONDS = 60;
|
|
6166
6163
|
var DEAD_ZONE_SECONDS = 8;
|
|
6167
|
-
var
|
|
6164
|
+
var SLOT_SECONDS = 12;
|
|
6165
|
+
var INCLUSION_SLOT_TOLERANCE = 3;
|
|
6166
|
+
var INCLUSION_LATENCY_BUDGET = SLOT_SECONDS * INCLUSION_SLOT_TOLERANCE;
|
|
6167
|
+
var MAX_GATE_POLLS = 12;
|
|
6168
6168
|
var POST_BOUNDARY_SKEW_MARGIN = 3;
|
|
6169
6169
|
function calculateNextQuantumTimestamp() {
|
|
6170
6170
|
const now = Math.floor(Date.now() / 1e3);
|
|
@@ -6197,8 +6197,17 @@ function validateQuantumTiming(signedTimestamp, _bufferSeconds = 30) {
|
|
|
6197
6197
|
async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
|
|
6198
6198
|
const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
6199
6199
|
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
6200
|
-
const nowSec = now();
|
|
6201
6200
|
const signatureQuantum = Math.floor(quantumTimestamp / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6201
|
+
if (opts.chainNow) {
|
|
6202
|
+
const chainResult = await awaitSafeSubmissionWindowOnChain(
|
|
6203
|
+
signatureQuantum,
|
|
6204
|
+
opts.chainNow,
|
|
6205
|
+
sleep
|
|
6206
|
+
);
|
|
6207
|
+
if (chainResult)
|
|
6208
|
+
return chainResult;
|
|
6209
|
+
}
|
|
6210
|
+
const nowSec = now();
|
|
6202
6211
|
const currentQuantum = Math.floor(nowSec / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6203
6212
|
const secsToBoundary = QUANTUM_WINDOW_SECONDS - nowSec % QUANTUM_WINDOW_SECONDS;
|
|
6204
6213
|
const isNextQuantumSig = signatureQuantum === currentQuantum + QUANTUM_WINDOW_SECONDS;
|
|
@@ -6215,6 +6224,44 @@ async function awaitSafeSubmissionWindow(quantumTimestamp, opts = {}) {
|
|
|
6215
6224
|
}
|
|
6216
6225
|
return { waited: false, waitedSeconds: 0 };
|
|
6217
6226
|
}
|
|
6227
|
+
async function awaitSafeSubmissionWindowOnChain(signatureQuantum, chainNow, sleep) {
|
|
6228
|
+
let waitedSeconds = 0;
|
|
6229
|
+
for (let poll = 0; poll < MAX_GATE_POLLS; poll++) {
|
|
6230
|
+
let head;
|
|
6231
|
+
try {
|
|
6232
|
+
head = await chainNow();
|
|
6233
|
+
} catch {
|
|
6234
|
+
head = null;
|
|
6235
|
+
}
|
|
6236
|
+
if (head === null || !Number.isFinite(head)) {
|
|
6237
|
+
return waitedSeconds > 0 ? { waited: true, waitedSeconds } : null;
|
|
6238
|
+
}
|
|
6239
|
+
const chainQuantum = Math.floor(head / QUANTUM_WINDOW_SECONDS) * QUANTUM_WINDOW_SECONDS;
|
|
6240
|
+
if (signatureQuantum <= chainQuantum) {
|
|
6241
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6242
|
+
}
|
|
6243
|
+
const earliestMine = head + SLOT_SECONDS;
|
|
6244
|
+
const latestMine = head + SLOT_SECONDS * INCLUSION_SLOT_TOLERANCE;
|
|
6245
|
+
const safeBeforeDeadZone = latestMine < signatureQuantum - DEAD_ZONE_SECONDS;
|
|
6246
|
+
const safeAfterBoundary = earliestMine >= signatureQuantum;
|
|
6247
|
+
if (safeBeforeDeadZone || safeAfterBoundary) {
|
|
6248
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6249
|
+
}
|
|
6250
|
+
const sleepSeconds = Math.max(
|
|
6251
|
+
1,
|
|
6252
|
+
Math.min(SLOT_SECONDS, signatureQuantum - head)
|
|
6253
|
+
);
|
|
6254
|
+
log.debug(
|
|
6255
|
+
`[Quantum Timing] \u23F3 Deferring send ${sleepSeconds}s to clear the quantum dead zone (sig quantum ${signatureQuantum}, chain head ${head}, insured mine window [${earliestMine}, ${latestMine}])`
|
|
6256
|
+
);
|
|
6257
|
+
await sleep(sleepSeconds * 1e3);
|
|
6258
|
+
waitedSeconds += sleepSeconds;
|
|
6259
|
+
}
|
|
6260
|
+
log.debug(
|
|
6261
|
+
`[Quantum Timing] \u26A0\uFE0F Dead-zone gate exhausted ${MAX_GATE_POLLS} chain polls (sig quantum ${signatureQuantum}) \u2014 proceeding`
|
|
6262
|
+
);
|
|
6263
|
+
return { waited: waitedSeconds > 0, waitedSeconds };
|
|
6264
|
+
}
|
|
6218
6265
|
|
|
6219
6266
|
// src/utils/mint-authorization.utils.ts
|
|
6220
6267
|
var PKP_NFT_ABI = [
|
|
@@ -6261,7 +6308,7 @@ async function generateMintAuthorization(positionId, amount, chainId, signerOrPr
|
|
|
6261
6308
|
signature
|
|
6262
6309
|
};
|
|
6263
6310
|
}
|
|
6264
|
-
async function generatePaymentAuthorization(positionId, amount, chainId,
|
|
6311
|
+
async function generatePaymentAuthorization(positionId, amount, chainId, signerOrPrecomputed) {
|
|
6265
6312
|
if (typeof positionId !== "string") {
|
|
6266
6313
|
throw new Error(`positionId must be a string, got ${typeof positionId}`);
|
|
6267
6314
|
}
|
|
@@ -6271,6 +6318,18 @@ async function generatePaymentAuthorization(positionId, amount, chainId, signer)
|
|
|
6271
6318
|
if (typeof chainId !== "number") {
|
|
6272
6319
|
throw new Error(`chainId must be a number, got ${typeof chainId}`);
|
|
6273
6320
|
}
|
|
6321
|
+
if (typeof signerOrPrecomputed === "object" && "signature" in signerOrPrecomputed && !("signMessage" in signerOrPrecomputed)) {
|
|
6322
|
+
const { timestamp: timestamp2, signature: signature2 } = signerOrPrecomputed;
|
|
6323
|
+
return {
|
|
6324
|
+
positionId,
|
|
6325
|
+
timestamp: timestamp2,
|
|
6326
|
+
chainId,
|
|
6327
|
+
amount: amount.toString(),
|
|
6328
|
+
action: "make-payment",
|
|
6329
|
+
signature: signature2
|
|
6330
|
+
};
|
|
6331
|
+
}
|
|
6332
|
+
const signer = signerOrPrecomputed;
|
|
6274
6333
|
const timestamp = await calculateNextQuantumTimestamp();
|
|
6275
6334
|
if (typeof timestamp !== "number") {
|
|
6276
6335
|
throw new Error(`timestamp must be a number, got ${typeof timestamp}`);
|
|
@@ -6512,6 +6571,26 @@ async function generateExtendAuthorization(positionId, newTerm, chainId, signerO
|
|
|
6512
6571
|
};
|
|
6513
6572
|
}
|
|
6514
6573
|
|
|
6574
|
+
// src/utils/authorization-provider.utils.ts
|
|
6575
|
+
async function resolveAuthorizationInput(provider, signer, ctx) {
|
|
6576
|
+
if (!provider)
|
|
6577
|
+
return signer;
|
|
6578
|
+
const result = await provider(ctx);
|
|
6579
|
+
if (!result || typeof result.signature !== "string" || result.signature.length === 0) {
|
|
6580
|
+
throw new Error(
|
|
6581
|
+
"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"
|
|
6582
|
+
);
|
|
6583
|
+
}
|
|
6584
|
+
if (!Number.isInteger(result.timestamp)) {
|
|
6585
|
+
throw new Error(
|
|
6586
|
+
`authorizationProvider returned a non-integer timestamp (${String(
|
|
6587
|
+
result.timestamp
|
|
6588
|
+
)}) \u2014 the protocol call must carry the same value the envelope was signed over`
|
|
6589
|
+
);
|
|
6590
|
+
}
|
|
6591
|
+
return { timestamp: result.timestamp, signature: result.signature };
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6515
6594
|
// src/utils/eip712-login.ts
|
|
6516
6595
|
import { hexlify, randomBytes } from "ethers";
|
|
6517
6596
|
function buildLoginDomain(chainId) {
|
|
@@ -7171,6 +7250,20 @@ function decodeQuantumRevert(e) {
|
|
|
7171
7250
|
}
|
|
7172
7251
|
return { data, selector, name };
|
|
7173
7252
|
}
|
|
7253
|
+
var RETRYABLE_QUANTUM_ERROR_NAMES = [
|
|
7254
|
+
"DeadZoneViolation",
|
|
7255
|
+
"QuantumOutsideWindow",
|
|
7256
|
+
"QuantumAlreadyUsed",
|
|
7257
|
+
"QuantumExpired"
|
|
7258
|
+
];
|
|
7259
|
+
function isRetryableQuantumFailure(message) {
|
|
7260
|
+
if (!message)
|
|
7261
|
+
return false;
|
|
7262
|
+
if (message.includes("Transaction timeout") || message.includes("Transaction confirmation failed") || message.includes("Transaction receipt not found")) {
|
|
7263
|
+
return false;
|
|
7264
|
+
}
|
|
7265
|
+
return RETRYABLE_QUANTUM_ERROR_NAMES.some((name) => message.includes(name)) || message.includes("Transaction reverted");
|
|
7266
|
+
}
|
|
7174
7267
|
|
|
7175
7268
|
// src/utils/quantum-submission.utils.ts
|
|
7176
7269
|
var MAX_DEADZONE_RESIMULATIONS = 3;
|
|
@@ -7192,9 +7285,14 @@ async function assertSafeQuantumSubmission(opts) {
|
|
|
7192
7285
|
const now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
7193
7286
|
const onDebug = opts.onDebug ?? (() => {
|
|
7194
7287
|
});
|
|
7288
|
+
const chainNow = opts.chainNow ?? (typeof provider.getBlock === "function" ? async () => {
|
|
7289
|
+
const block = await provider.getBlock("latest");
|
|
7290
|
+
return typeof block?.timestamp === "number" ? block.timestamp : null;
|
|
7291
|
+
} : void 0);
|
|
7195
7292
|
const gateOpts = {
|
|
7196
7293
|
now,
|
|
7197
|
-
...opts.sleep ? { sleep: opts.sleep } : {}
|
|
7294
|
+
...opts.sleep ? { sleep: opts.sleep } : {},
|
|
7295
|
+
...chainNow ? { chainNow } : {}
|
|
7198
7296
|
};
|
|
7199
7297
|
await awaitSafeSubmissionWindow(quantumTimestamp, gateOpts);
|
|
7200
7298
|
const SAME_BLOCK_INTERACTION_SELECTOR = "0x3b53d1db";
|
|
@@ -19918,11 +20016,20 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
|
|
|
19918
20016
|
{}
|
|
19919
20017
|
);
|
|
19920
20018
|
}
|
|
20019
|
+
const mintAuthInput = await resolveAuthorizationInput(
|
|
20020
|
+
request.authorizationProvider,
|
|
20021
|
+
signer2,
|
|
20022
|
+
{
|
|
20023
|
+
positionId: request.positionId,
|
|
20024
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
20025
|
+
amount: requestAmountWei.toString()
|
|
20026
|
+
}
|
|
20027
|
+
);
|
|
19921
20028
|
const auth = await generateMintAuthorization(
|
|
19922
20029
|
request.positionId,
|
|
19923
20030
|
BigInt(requestAmountWei.toString()),
|
|
19924
20031
|
chainId,
|
|
19925
|
-
|
|
20032
|
+
mintAuthInput
|
|
19926
20033
|
);
|
|
19927
20034
|
if (this.config.debug) {
|
|
19928
20035
|
log.info(` Authorization generated (attempt ${attempt})`, {});
|
|
@@ -21513,7 +21620,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
21513
21620
|
* @param _selectedTerm - Extension term in months
|
|
21514
21621
|
* @returns Transaction receipt
|
|
21515
21622
|
*/
|
|
21516
|
-
async extendPosition(_positionId, _selectedTerm, _rpcUrl, _bitcoinRpcUrl) {
|
|
21623
|
+
async extendPosition(_positionId, _selectedTerm, _rpcUrl, _bitcoinRpcUrl, _authorizationProvider) {
|
|
21517
21624
|
this.ensureInitialized();
|
|
21518
21625
|
if (typeof _positionId !== "string") {
|
|
21519
21626
|
throw new Error(`positionId must be a string, got ${typeof _positionId}`);
|
|
@@ -21607,11 +21714,23 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
21607
21714
|
}
|
|
21608
21715
|
}
|
|
21609
21716
|
const signer = this.getSignerOrThrow();
|
|
21717
|
+
const extendAuthInput = await resolveAuthorizationInput(
|
|
21718
|
+
_authorizationProvider,
|
|
21719
|
+
signer,
|
|
21720
|
+
{
|
|
21721
|
+
positionId: _positionId,
|
|
21722
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
21723
|
+
// The extend envelope binds the TERM, not an amount. Carried as "0" so
|
|
21724
|
+
// the context shape stays uniform across the four operations.
|
|
21725
|
+
amount: "0",
|
|
21726
|
+
selectedTerm: _selectedTerm
|
|
21727
|
+
}
|
|
21728
|
+
);
|
|
21610
21729
|
const auth = await generateExtendAuthorization(
|
|
21611
21730
|
_positionId,
|
|
21612
21731
|
_selectedTerm,
|
|
21613
21732
|
chainId,
|
|
21614
|
-
|
|
21733
|
+
extendAuthInput
|
|
21615
21734
|
);
|
|
21616
21735
|
if (this.config.debug) {
|
|
21617
21736
|
log.info(" Extend authorization generated", {
|
|
@@ -21867,7 +21986,8 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
21867
21986
|
positionId,
|
|
21868
21987
|
selectedTerm,
|
|
21869
21988
|
request.rpcUrl,
|
|
21870
|
-
request.customBitcoinRpcUrl
|
|
21989
|
+
request.customBitcoinRpcUrl,
|
|
21990
|
+
request.authorizationProvider
|
|
21871
21991
|
);
|
|
21872
21992
|
let newExpiryAt;
|
|
21873
21993
|
try {
|
|
@@ -22218,10 +22338,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22218
22338
|
isRetryablePaymentQuantumFailure(result) {
|
|
22219
22339
|
if (result.success || !result.error)
|
|
22220
22340
|
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";
|
|
22341
|
+
return isRetryableQuantumFailure(result.error);
|
|
22225
22342
|
}
|
|
22226
22343
|
/**
|
|
22227
22344
|
* One payment attempt: user auth → Lit Action authorization → dead-zone gate →
|
|
@@ -22398,11 +22515,20 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22398
22515
|
}
|
|
22399
22516
|
let auth;
|
|
22400
22517
|
try {
|
|
22518
|
+
const paymentAuthInput = await resolveAuthorizationInput(
|
|
22519
|
+
request.authorizationProvider,
|
|
22520
|
+
signer,
|
|
22521
|
+
{
|
|
22522
|
+
positionId: request.positionId,
|
|
22523
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
22524
|
+
amount: paymentAmountWei.toString()
|
|
22525
|
+
}
|
|
22526
|
+
);
|
|
22401
22527
|
auth = await generatePaymentAuthorization(
|
|
22402
22528
|
request.positionId,
|
|
22403
22529
|
paymentAmountWei,
|
|
22404
22530
|
chainId,
|
|
22405
|
-
|
|
22531
|
+
paymentAuthInput
|
|
22406
22532
|
);
|
|
22407
22533
|
} catch (authError) {
|
|
22408
22534
|
if (authError.message && authError.message.includes("BigNumber")) {
|
|
@@ -22860,6 +22986,37 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22860
22986
|
}
|
|
22861
22987
|
return this.withdrawalAddressModule.getEntries(target);
|
|
22862
22988
|
}
|
|
22989
|
+
/**
|
|
22990
|
+
* Recover the revert reason of an already-mined, already-reverted transaction.
|
|
22991
|
+
*
|
|
22992
|
+
* A status-0 receipt carries no revert data, and ethers does not re-fetch it, so
|
|
22993
|
+
* the reason has to be recovered by replaying the exact calldata as an `eth_call`
|
|
22994
|
+
* pinned to the block that mined it — the only block whose `block.timestamp`
|
|
22995
|
+
* reproduces a timing-dependent revert such as `DeadZoneViolation()`.
|
|
22996
|
+
*
|
|
22997
|
+
* Caveat: the replay runs against end-of-block state rather than the state at the
|
|
22998
|
+
* transaction's own index, so for a state-dependent revert the recovered name is
|
|
22999
|
+
* diagnostic, not proof. Timing reverts (the case this exists for) are unaffected —
|
|
23000
|
+
* they depend only on the block timestamp.
|
|
23001
|
+
*
|
|
23002
|
+
* @returns the decoded error name, its raw selector if unknown, or `null` when the
|
|
23003
|
+
* replay yields no revert data at all (never throws — a failed diagnosis
|
|
23004
|
+
* must not mask the underlying failure).
|
|
23005
|
+
*/
|
|
23006
|
+
async decodeMinedRevert(params) {
|
|
23007
|
+
try {
|
|
23008
|
+
await this.getProviderOrThrow().call({
|
|
23009
|
+
to: params.to,
|
|
23010
|
+
from: params.from,
|
|
23011
|
+
data: params.data,
|
|
23012
|
+
blockTag: params.blockNumber
|
|
23013
|
+
});
|
|
23014
|
+
return null;
|
|
23015
|
+
} catch (replayError) {
|
|
23016
|
+
const decoded = decodeQuantumRevert(replayError);
|
|
23017
|
+
return decoded.name ?? decoded.selector ?? null;
|
|
23018
|
+
}
|
|
23019
|
+
}
|
|
22863
23020
|
/**
|
|
22864
23021
|
* Withdraw Bitcoin from a position
|
|
22865
23022
|
*
|
|
@@ -22883,7 +23040,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
22883
23040
|
* @param customBitcoinRpcUrl - Optional custom Bitcoin RPC URL (for local testing)
|
|
22884
23041
|
* @returns Withdrawal result with transaction details
|
|
22885
23042
|
*/
|
|
22886
|
-
async withdrawBTC(positionId, withdrawalAddress, withdrawalAmount) {
|
|
23043
|
+
async withdrawBTC(positionId, withdrawalAddress, withdrawalAmount, options) {
|
|
22887
23044
|
this.ensureInitialized();
|
|
22888
23045
|
try {
|
|
22889
23046
|
if (this.config.debug) {
|
|
@@ -23098,11 +23255,21 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23098
23255
|
{}
|
|
23099
23256
|
);
|
|
23100
23257
|
}
|
|
23258
|
+
const withdrawAuthInput = await resolveAuthorizationInput(
|
|
23259
|
+
options?.authorizationProvider,
|
|
23260
|
+
signer,
|
|
23261
|
+
{
|
|
23262
|
+
positionId,
|
|
23263
|
+
timestamp: calculateNextQuantumTimestamp(),
|
|
23264
|
+
amount: BigInt(withdrawalAmount).toString(),
|
|
23265
|
+
destinationAddress: withdrawalAddress
|
|
23266
|
+
}
|
|
23267
|
+
);
|
|
23101
23268
|
const auth = await generateWithdrawAuthorization(
|
|
23102
23269
|
positionId,
|
|
23103
23270
|
BigInt(withdrawalAmount),
|
|
23104
23271
|
chainId,
|
|
23105
|
-
|
|
23272
|
+
withdrawAuthInput,
|
|
23106
23273
|
withdrawalAddress
|
|
23107
23274
|
// Pass destination address for message hash
|
|
23108
23275
|
);
|
|
@@ -23433,9 +23600,23 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23433
23600
|
txHash: tx.hash
|
|
23434
23601
|
});
|
|
23435
23602
|
}
|
|
23603
|
+
const withdrawProvider = this.getProviderOrThrow();
|
|
23604
|
+
const withdrawTimeoutMs = this.config.transactionTimeoutMs || 3e5;
|
|
23436
23605
|
let receipt;
|
|
23437
23606
|
try {
|
|
23438
|
-
receipt = await
|
|
23607
|
+
receipt = await Promise.race([
|
|
23608
|
+
withdrawProvider.waitForTransaction(tx.hash, 1),
|
|
23609
|
+
new Promise(
|
|
23610
|
+
(_, reject) => setTimeout(
|
|
23611
|
+
() => reject(
|
|
23612
|
+
new Error(
|
|
23613
|
+
`Transaction timeout after ${withdrawTimeoutMs}ms. Hash: ${tx.hash}`
|
|
23614
|
+
)
|
|
23615
|
+
),
|
|
23616
|
+
withdrawTimeoutMs
|
|
23617
|
+
)
|
|
23618
|
+
)
|
|
23619
|
+
]);
|
|
23439
23620
|
} catch (waitError) {
|
|
23440
23621
|
const waitErrorMsg = waitError instanceof Error ? waitError.message : String(waitError);
|
|
23441
23622
|
if (this.config.debug) {
|
|
@@ -23452,7 +23633,26 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
23452
23633
|
if (!receipt) {
|
|
23453
23634
|
return {
|
|
23454
23635
|
success: false,
|
|
23455
|
-
error: `Transaction
|
|
23636
|
+
error: `Transaction receipt not found for hash: ${tx.hash}`
|
|
23637
|
+
};
|
|
23638
|
+
}
|
|
23639
|
+
if (receipt.status === 0) {
|
|
23640
|
+
const revertName = await this.decodeMinedRevert({
|
|
23641
|
+
to: positionManagerAddress,
|
|
23642
|
+
from: await withdrawSigner.getAddress(),
|
|
23643
|
+
data: withdrawCalldata,
|
|
23644
|
+
blockNumber: receipt.blockNumber
|
|
23645
|
+
});
|
|
23646
|
+
if (this.config.debug) {
|
|
23647
|
+
log.error("\u274C withdrawBTC reverted on-chain", {
|
|
23648
|
+
txHash: receipt.hash,
|
|
23649
|
+
blockNumber: receipt.blockNumber,
|
|
23650
|
+
revert: revertName ?? "undecodable"
|
|
23651
|
+
});
|
|
23652
|
+
}
|
|
23653
|
+
return {
|
|
23654
|
+
success: false,
|
|
23655
|
+
error: `Transaction reverted: ${revertName ?? "unknown reason"} (tx: ${receipt.hash})`
|
|
23456
23656
|
};
|
|
23457
23657
|
}
|
|
23458
23658
|
if (this.config.debug) {
|
|
@@ -24143,6 +24343,11 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24143
24343
|
* (`MAX_ATTESTATION_AGE = 1 hour`) and replay-protects via
|
|
24144
24344
|
* `authorizedAt` binding.
|
|
24145
24345
|
*
|
|
24346
|
+
* WHO PAYS: in **service mode** lit-ops-server performs BOTH steps and its
|
|
24347
|
+
* relayer funds the transaction — the borrower is never prompted, because the
|
|
24348
|
+
* Bitcoin withdrawal this cleans up after has already settled. In **direct
|
|
24349
|
+
* mode** (CLI / tests / hardhat) the SDK's own signer submits step 2.
|
|
24350
|
+
*
|
|
24146
24351
|
* `invalidatorTxid` is either:
|
|
24147
24352
|
* - the borrower's own authorized broadcast (classified "consumed-as-authorized"
|
|
24148
24353
|
* by the LIT validator), once it has ≥6 confirmations on the BTC
|
|
@@ -24155,7 +24360,6 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24155
24360
|
async recoverStaleSpend(params) {
|
|
24156
24361
|
this.ensureInitialized();
|
|
24157
24362
|
try {
|
|
24158
|
-
const signer = this.getSignerOrThrow();
|
|
24159
24363
|
const contractAddresses = this.getContractAddressesOrThrow();
|
|
24160
24364
|
const btcSpendAuthorizerAddress = contractAddresses.btcSpendAuthorizer;
|
|
24161
24365
|
if (!btcSpendAuthorizerAddress) {
|
|
@@ -24201,6 +24405,28 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24201
24405
|
success: false,
|
|
24202
24406
|
error: json.error ?? "stale-spend recovery request failed"
|
|
24203
24407
|
};
|
|
24408
|
+
if (!attRes.success || !attRes.attestation) {
|
|
24409
|
+
return {
|
|
24410
|
+
success: false,
|
|
24411
|
+
failedStep: "attestation",
|
|
24412
|
+
error: attRes.error ?? attRes.reason ?? `LIT recovery attestation rejected${attRes.failedStep ? ` at step ${attRes.failedStep}` : ""}`
|
|
24413
|
+
};
|
|
24414
|
+
}
|
|
24415
|
+
if (!attRes.transactionHash && !attRes.alreadyCleared) {
|
|
24416
|
+
return {
|
|
24417
|
+
success: false,
|
|
24418
|
+
failedStep: "submit",
|
|
24419
|
+
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."
|
|
24420
|
+
};
|
|
24421
|
+
}
|
|
24422
|
+
return {
|
|
24423
|
+
success: true,
|
|
24424
|
+
...attRes.transactionHash && {
|
|
24425
|
+
transactionHash: attRes.transactionHash
|
|
24426
|
+
},
|
|
24427
|
+
...attRes.classification && { classification: attRes.classification },
|
|
24428
|
+
attestation: attRes.attestation
|
|
24429
|
+
};
|
|
24204
24430
|
} else {
|
|
24205
24431
|
const rpcUrl = params.rpcUrl ?? this.config.ethRpcUrl;
|
|
24206
24432
|
if (!rpcUrl) {
|
|
@@ -24250,7 +24476,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24250
24476
|
[
|
|
24251
24477
|
"function cancelStaleSpendWithProof(bytes32 positionId, bytes32 utxoKey, uint256 authorizedAt, string calldata invalidatorTxid, uint256 attestationTimestamp, bytes calldata litSignature) external"
|
|
24252
24478
|
],
|
|
24253
|
-
|
|
24479
|
+
this.getSignerOrThrow()
|
|
24254
24480
|
);
|
|
24255
24481
|
const tx = await btcSpendAuthorizer["cancelStaleSpendWithProof"](
|
|
24256
24482
|
attRes.attestation.positionId,
|
|
@@ -24580,13 +24806,11 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
24580
24806
|
if (authResult.success) {
|
|
24581
24807
|
break;
|
|
24582
24808
|
}
|
|
24583
|
-
const isQuantumError = authResult.error
|
|
24584
|
-
"QuantumOutsideWindow"
|
|
24585
|
-
);
|
|
24809
|
+
const isQuantumError = isRetryableQuantumFailure(authResult.error);
|
|
24586
24810
|
if (isQuantumError && wAttempt < MAX_WITHDRAWAL_QUANTUM_RETRIES) {
|
|
24587
24811
|
if (this.config.debug) {
|
|
24588
24812
|
log.warn(
|
|
24589
|
-
`\u26A0\uFE0F Phase 1
|
|
24813
|
+
`\u26A0\uFE0F Phase 1 quantum-timing failure (attempt ${wAttempt}/${MAX_WITHDRAWAL_QUANTUM_RETRIES}): ${authResult.error}. Retrying with a fresh quantum...`
|
|
24590
24814
|
);
|
|
24591
24815
|
}
|
|
24592
24816
|
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
@@ -25060,10 +25284,22 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25060
25284
|
"getLoanGrant",
|
|
25061
25285
|
data
|
|
25062
25286
|
);
|
|
25287
|
+
const raw = getBytes5(data);
|
|
25288
|
+
const withdrawScopeSupported = raw.length >= 128;
|
|
25289
|
+
const minWithdrawRatioBps = withdrawScopeSupported ? Number(
|
|
25290
|
+
AbiCoder2.defaultAbiCoder().decode(["uint32"], raw.slice(96, 128))[0]
|
|
25291
|
+
) : 0;
|
|
25063
25292
|
return {
|
|
25064
25293
|
borrower,
|
|
25065
25294
|
scopeBits: Number(scopeBits),
|
|
25066
|
-
minCollateralRatioBps: Number(minCollateralRatioBps)
|
|
25295
|
+
minCollateralRatioBps: Number(minCollateralRatioBps),
|
|
25296
|
+
minWithdrawRatioBps,
|
|
25297
|
+
/**
|
|
25298
|
+
* True when the deployed registry is v2.1.0+ and therefore honours `SCOPE_WITHDRAW`. The
|
|
25299
|
+
* frontend gates its Withdrawal control on this so the control is absent — not disabled,
|
|
25300
|
+
* not "coming soon" — on a chain whose registry cannot support it.
|
|
25301
|
+
*/
|
|
25302
|
+
withdrawScopeSupported
|
|
25067
25303
|
};
|
|
25068
25304
|
}
|
|
25069
25305
|
/**
|
|
@@ -25105,10 +25341,14 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25105
25341
|
const mintEnabled = hasGrant && (scopes & 4) !== 0;
|
|
25106
25342
|
const floor = grant.minCollateralRatioBps;
|
|
25107
25343
|
const agent = user ? await registry.agentOf(user) : null;
|
|
25344
|
+
const withdrawEnabled = hasGrant && grant.withdrawScopeSupported && (scopes & 8) !== 0;
|
|
25108
25345
|
return {
|
|
25109
25346
|
renewEnabled: hasGrant && (scopes & 1) !== 0,
|
|
25110
25347
|
repayEnabled: hasGrant && (scopes & 2) !== 0,
|
|
25111
25348
|
mintEnabled,
|
|
25349
|
+
withdrawEnabled,
|
|
25350
|
+
withdrawScopeSupported: grant.withdrawScopeSupported,
|
|
25351
|
+
minWithdrawRatioBps: withdrawEnabled ? grant.minWithdrawRatioBps : 0,
|
|
25112
25352
|
minCollateralRatioBps: mintEnabled ? floor : 0,
|
|
25113
25353
|
effectiveMintFloorBps: mintEnabled ? Math.max(floor, protocolMin) : 0,
|
|
25114
25354
|
protocolMinFloorBps: protocolMin,
|
|
@@ -25294,8 +25534,83 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25294
25534
|
const receipt = await tx.wait();
|
|
25295
25535
|
return { hash: tx.hash, blockNumber: receipt?.blockNumber ?? 0 };
|
|
25296
25536
|
}
|
|
25297
|
-
/**
|
|
25298
|
-
|
|
25537
|
+
/**
|
|
25538
|
+
* Grant the agent the WITHDRAW scope for a position (registry v2.1.0+). Borrower-signed.
|
|
25539
|
+
*
|
|
25540
|
+
* CONTRACT WALLETS ONLY, and this is enforced here rather than on-chain. The withdrawal
|
|
25541
|
+
* validator's only non-borrower arm is `checkSafeAgentWithdrawAuthorization`, which requires a
|
|
25542
|
+
* factory-provenanced `AgentModule` whose `safe()` is the borrower — something an EOA cannot
|
|
25543
|
+
* have. The registry deliberately does NOT check wallet type ("Callable by any borrower (this
|
|
25544
|
+
* function does not itself require a Safe)"), so an EOA can record a grant that no code path
|
|
25545
|
+
* will ever satisfy. Refusing here keeps the supported path from writing a dead record.
|
|
25546
|
+
*
|
|
25547
|
+
* That refusal is ergonomics, NOT enforcement — `enableAutoWithdraw` is `external` and reachable
|
|
25548
|
+
* from any wallet. The real reason delegated withdrawal is closed to EOAs is that they can sign
|
|
25549
|
+
* inside the ~44s window themselves, so delegating buys them nothing and only adds risk.
|
|
25550
|
+
*
|
|
25551
|
+
* @param minWithdrawRatioBps Post-withdrawal collateral-ratio floor for AGENT withdrawals.
|
|
25552
|
+
* Clamped by the same governance minimum as the mint floor. **Inert at zero debt** — it
|
|
25553
|
+
* is a ratio, and the post-withdrawal ratio is infinite for any amount once debt is 0.
|
|
25554
|
+
* The operative bound there is the quorum-gated destination allowlist. Do not surface
|
|
25555
|
+
* this to users as a cap on how much an agent may withdraw.
|
|
25556
|
+
* There is no in-place raise: changing it means disable-then-re-enable, deliberately —
|
|
25557
|
+
* on the one scope that moves custodied BTC, incident response can only disable outright.
|
|
25558
|
+
*/
|
|
25559
|
+
async enableAutoWithdraw(positionId, minWithdrawRatioBps, options) {
|
|
25560
|
+
const pid = this.toBytes32(positionId);
|
|
25561
|
+
await this.assertBorrowerIsContractWallet(pid);
|
|
25562
|
+
await this.assertMintFloorAtOrAboveProtocolMin(minWithdrawRatioBps, "withdraw");
|
|
25563
|
+
const { registry, agentAddress } = await this.prepareAgentDelegation(pid, options);
|
|
25564
|
+
const tx = await registry.enableAutoWithdraw(pid, minWithdrawRatioBps);
|
|
25565
|
+
const receipt = await tx.wait();
|
|
25566
|
+
return { hash: tx.hash, blockNumber: receipt?.blockNumber ?? 0, agentAddress };
|
|
25567
|
+
}
|
|
25568
|
+
/**
|
|
25569
|
+
* Revoke the WITHDRAW scope for a position (also zeroes its withdraw floor). Borrower-signed.
|
|
25570
|
+
*
|
|
25571
|
+
* No wallet-type check: revoking is the safe direction, and an EOA that recorded a grant before
|
|
25572
|
+
* this guard existed must be able to clear it.
|
|
25573
|
+
*/
|
|
25574
|
+
async disableAutoWithdraw(positionId) {
|
|
25575
|
+
const addr = this.agentDelegationRegistryOrThrow();
|
|
25576
|
+
const signer = this.getSignerOrThrow();
|
|
25577
|
+
const registry = AgentDelegationRegistry__factory.connect(addr, signer);
|
|
25578
|
+
const pid = this.toBytes32(positionId);
|
|
25579
|
+
const tx = await registry.disableAutoWithdraw(pid);
|
|
25580
|
+
const receipt = await tx.wait();
|
|
25581
|
+
await this.clearGate1IfNoScopesRemain(pid);
|
|
25582
|
+
return { hash: tx.hash, blockNumber: receipt?.blockNumber ?? 0 };
|
|
25583
|
+
}
|
|
25584
|
+
/**
|
|
25585
|
+
* Layer 2 of the withdrawal-delegation gating: refuse to write a grant that could never be
|
|
25586
|
+
* satisfied. See `enableAutoWithdraw` for why this lives here and not in the registry.
|
|
25587
|
+
*/
|
|
25588
|
+
async assertBorrowerIsContractWallet(pid) {
|
|
25589
|
+
const details = await this.getPositionDetailsView(pid);
|
|
25590
|
+
const borrower = details?.borrower;
|
|
25591
|
+
if (!borrower) {
|
|
25592
|
+
throw new SDKError({
|
|
25593
|
+
message: `Could not resolve the borrower for position ${pid} \u2014 refusing to write a delegation grant against an unverified position`,
|
|
25594
|
+
category: "VALIDATION" /* VALIDATION */,
|
|
25595
|
+
severity: "HIGH" /* HIGH */
|
|
25596
|
+
});
|
|
25597
|
+
}
|
|
25598
|
+
const code = await this.getProviderOrThrow().getCode(borrower);
|
|
25599
|
+
if (!code || code === "0x") {
|
|
25600
|
+
throw new SDKError({
|
|
25601
|
+
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.`,
|
|
25602
|
+
category: "VALIDATION" /* VALIDATION */,
|
|
25603
|
+
severity: "MEDIUM" /* MEDIUM */
|
|
25604
|
+
});
|
|
25605
|
+
}
|
|
25606
|
+
}
|
|
25607
|
+
/**
|
|
25608
|
+
* Pre-flight the registry's `FloorBelowProtocolMin` guard with a message a user can act on.
|
|
25609
|
+
*
|
|
25610
|
+
* `agentMintFloorMinBps` is the governance floor-of-floors for BOTH the mint and withdraw
|
|
25611
|
+
* grants — one lever, one invariant — so `scope` only shapes the message.
|
|
25612
|
+
*/
|
|
25613
|
+
async assertMintFloorAtOrAboveProtocolMin(floorBps, scope = "mint") {
|
|
25299
25614
|
if (!Number.isInteger(floorBps) || floorBps <= 0) {
|
|
25300
25615
|
throw new SDKError({
|
|
25301
25616
|
message: `Invalid collateral floor: ${floorBps} bps \u2014 must be a positive integer`,
|
|
@@ -25306,7 +25621,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
|
|
|
25306
25621
|
const protocolMin = await this.getAgentMintFloorMinBps();
|
|
25307
25622
|
if (protocolMin === 0) {
|
|
25308
25623
|
throw new SDKError({
|
|
25309
|
-
message:
|
|
25624
|
+
message: `Agent ${scope === "mint" ? "minting" : "withdrawal"} is not configured on this network (agentMintFloorMinBps is 0) \u2014 the registry would reject every ${scope} grant`,
|
|
25310
25625
|
category: "CONFIGURATION" /* CONFIGURATION */,
|
|
25311
25626
|
severity: "HIGH" /* HIGH */
|
|
25312
25627
|
});
|