@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/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: "0x80C47350274513688e348D02506477A96B4afCCb",
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-23T17:13:42.777Z",
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: "QmPncBUbxN9giWntJcvBMFW9GDAgA3DwgAkUAHxSsY67ic",
1364
+ cid: "QmakWzTce8NH1Jkgv1xwg83Bg9WmF2UG1Mq76pDYDSwwUp",
1389
1365
  authorizedCidHex: cidToHex(
1390
- "QmPncBUbxN9giWntJcvBMFW9GDAgA3DwgAkUAHxSsY67ic"
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: 1785226367707,
1397
- size: 66624,
1398
- hash: "9e0d9909519d5133bab03a9491566870b08d8b42c80e2d768bd24f02946cc781"
1372
+ deployedAt: 1786113907707,
1373
+ size: 67999,
1374
+ hash: "83f3644c68819215e05ba8db3225571354a009458fc1437f606df6cb9142d3f9"
1399
1375
  },
1400
1376
  ucdMintValidator: {
1401
- cid: "QmPRLCfseSWCAS4PpvQWt9aEd6rXqWYG4JyGTn26KYk6xc",
1377
+ cid: "Qmd8jgKRceLvf5aoTtwdXG2JqMoBK5cRJwrvEtQkgvbFbx",
1402
1378
  authorizedCidHex: cidToHex(
1403
- "QmPRLCfseSWCAS4PpvQWt9aEd6rXqWYG4JyGTn26KYk6xc"
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: 1785226381631,
1410
- size: 81658,
1411
- hash: "d8ea21622885c3d26fdb64e2ba9d2f9460753a5cd7934b2928858899537443eb",
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: "QmNUzmHQHsaKThGkfZiD3xZrN64qZfn4P6J4MqWgvkeEsX",
1395
+ cid: "QmNXVdysXDhCaQ1uNE2Vgjbuzy6yUEnnVgHzmDfNKXMU6z",
1420
1396
  authorizedCidHex: cidToHex(
1421
- "QmNUzmHQHsaKThGkfZiD3xZrN64qZfn4P6J4MqWgvkeEsX"
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: 1785226385509,
1428
- size: 77655,
1429
- hash: "3c2af3c3e62e8e606d35f81fd7e6f4943f8516a9bef74f4150c8c1767cbd316f",
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: "QmY2t41w1maaoDmdRPx6wTfciHPQAydSpLSKwKVFSS2DPM",
1413
+ cid: "QmYwBsyrhGfHWyRKx549G4tKHxsBLAuRUHcecigeUCkFav",
1438
1414
  authorizedCidHex: cidToHex(
1439
- "QmY2t41w1maaoDmdRPx6wTfciHPQAydSpLSKwKVFSS2DPM"
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: 1785226398291,
1446
- size: 75434,
1447
- hash: "ba55532fbeaf6e65d180b869fd69ee06393d574daa0ace524a9e831d846539a5",
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: "QmcBg8vPXsCmiKDk28bJVTqDpTrwCzSYAGu5V6ABVVPuK5",
1431
+ cid: "QmZuR3qSBajutXfRd91ffpyFrRMgmaMh7fDEHRiQQF8Ljp",
1456
1432
  authorizedCidHex: cidToHex(
1457
- "QmcBg8vPXsCmiKDk28bJVTqDpTrwCzSYAGu5V6ABVVPuK5"
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: 1785226412397,
1468
- size: 92754,
1469
- hash: "d3a8ec62e0b1e6d0566a154a884bf7ee4e25ef6dc2dd84fde98211d25c1dede5",
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: "QmZE422cZsEjYN8pE6AmpkgFoZCK4UAfdDuzoVRRsMJbwm",
1590
+ cid: "Qmf4Ze8yhnQhHbnX5RPgJrC7Jw1aA3gbpfpd52kFg7fd1m",
1600
1591
  authorizedCidHex: cidToHex(
1601
- "QmZE422cZsEjYN8pE6AmpkgFoZCK4UAfdDuzoVRRsMJbwm"
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: 1785226423791,
1608
- size: 61472,
1609
- hash: "75eca528d8ac30ae8f21caaae206ccab162a83a048d622868e60f645f0c475e2"
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 INCLUSION_LATENCY_BUDGET = 16;
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, signer) {
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
- signer2
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
- signer
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
- const e = result.error;
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
- signer
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
- signer,
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 tx.wait();
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 confirmation returned no receipt. Hash: ${tx.hash}`
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
- signer
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?.includes(
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 QuantumOutsideWindow (attempt ${wAttempt}/${MAX_WITHDRAWAL_QUANTUM_RETRIES}). LIT action took too long. Retrying with fresh quantum...`
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
- /** Pre-flight the registry's `FloorBelowProtocolMin` guard with a message a user can act on. */
25298
- async assertMintFloorAtOrAboveProtocolMin(floorBps) {
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: "Agent minting is not configured on this network (agentMintFloorMinBps is 0) \u2014 the registry would reject every mint grant",
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
  });