@gvnrdao/dh-sdk 0.0.324 → 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.
@@ -329,7 +329,7 @@ var SEPOLIA_DEPLOYMENT = {
329
329
  CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
330
330
  CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
331
331
  AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
332
- AGENT_DELEGATION_REGISTRY_IMPL: "0x80C47350274513688e348D02506477A96B4afCCb",
332
+ AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
333
333
  DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
334
334
  }
335
335
  };
@@ -599,7 +599,7 @@ var MAINNET_CONTRACTS = {
599
599
  var LOCALHOST_DEPLOYMENT = {
600
600
  network: "localhost",
601
601
  chainId: 1337,
602
- timestamp: "2026-07-23T17:13:42.777Z",
602
+ timestamp: "2026-07-20T19:45:58.514Z",
603
603
  deployer: "",
604
604
  contracts: {
605
605
  MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
@@ -619,30 +619,6 @@ var LOCALHOST_DEPLOYMENT = {
619
619
  PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
620
620
  OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
621
621
  PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
622
- },
623
- latestEnv: {
624
- UCD_TOKEN: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
625
- UCD_CONTROLLER: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
626
- POSITION_MANAGER: "0x851356ae760d987E095750cCeb3bC6014560891C",
627
- PKP_VALIDATION_REGISTRY: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A",
628
- PKP_VALIDATION_CID_V1: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
629
- PKP_VALIDATION_CID_V3: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
630
- PKP_ETH_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
631
- OPERATION_AUTHORIZATION_REGISTRY: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
632
- UCD_MINT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
633
- UCD_MINT_VALIDATOR_VERSION: 1,
634
- BTC_WITHDRAWAL_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
635
- BTC_WITHDRAWAL_VALIDATOR_VERSION: 3,
636
- UPDATE_BALANCE_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
637
- UPDATE_BALANCE_VALIDATOR_VERSION: 1,
638
- PROCESS_PAYMENT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
639
- PROCESS_PAYMENT_VALIDATOR_VERSION: 1,
640
- EXTEND_POSITION_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
641
- EXTEND_POSITION_VALIDATOR_VERSION: 1,
642
- POSITION_MANAGER_CORE_MODULE: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
643
- LOAN_OPERATIONS_MANAGER_MODULE: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
644
- TERM_MANAGER_MODULE: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
645
- COMMUNITY_MANAGER_MODULE: ""
646
622
  }
647
623
  };
648
624
  var LOCALHOST_CONTRACTS = {
@@ -296,7 +296,7 @@ var SEPOLIA_DEPLOYMENT = {
296
296
  CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
297
297
  CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
298
298
  AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
299
- AGENT_DELEGATION_REGISTRY_IMPL: "0x80C47350274513688e348D02506477A96B4afCCb",
299
+ AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
300
300
  DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
301
301
  }
302
302
  };
@@ -566,7 +566,7 @@ var MAINNET_CONTRACTS = {
566
566
  var LOCALHOST_DEPLOYMENT = {
567
567
  network: "localhost",
568
568
  chainId: 1337,
569
- timestamp: "2026-07-23T17:13:42.777Z",
569
+ timestamp: "2026-07-20T19:45:58.514Z",
570
570
  deployer: "",
571
571
  contracts: {
572
572
  MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
@@ -586,30 +586,6 @@ var LOCALHOST_DEPLOYMENT = {
586
586
  PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
587
587
  OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
588
588
  PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
589
- },
590
- latestEnv: {
591
- UCD_TOKEN: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
592
- UCD_CONTROLLER: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
593
- POSITION_MANAGER: "0x851356ae760d987E095750cCeb3bC6014560891C",
594
- PKP_VALIDATION_REGISTRY: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A",
595
- PKP_VALIDATION_CID_V1: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
596
- PKP_VALIDATION_CID_V3: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
597
- PKP_ETH_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
598
- OPERATION_AUTHORIZATION_REGISTRY: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
599
- UCD_MINT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
600
- UCD_MINT_VALIDATOR_VERSION: 1,
601
- BTC_WITHDRAWAL_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
602
- BTC_WITHDRAWAL_VALIDATOR_VERSION: 3,
603
- UPDATE_BALANCE_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
604
- UPDATE_BALANCE_VALIDATOR_VERSION: 1,
605
- PROCESS_PAYMENT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
606
- PROCESS_PAYMENT_VALIDATOR_VERSION: 1,
607
- EXTEND_POSITION_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
608
- EXTEND_POSITION_VALIDATOR_VERSION: 1,
609
- POSITION_MANAGER_CORE_MODULE: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
610
- LOAN_OPERATIONS_MANAGER_MODULE: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
611
- TERM_MANAGER_MODULE: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
612
- COMMUNITY_MANAGER_MODULE: ""
613
589
  }
614
590
  };
615
591
  var LOCALHOST_CONTRACTS = {
package/dist/index.d.ts CHANGED
@@ -35,6 +35,7 @@ export type { BitcoinOperationsConfig, BitcoinNetwork, EnrichedBitcoinBalance, }
35
35
  export type { SupportedStablecoinData } from './graphs/diamond-hands';
36
36
  export { validateSDKConfig, validateServiceModeConfig, validateStandaloneModeConfig, isServiceModeConfig, isStandaloneModeConfig, } from './interfaces/chunks/config.i';
37
37
  export { assertSafeServiceEndpoint } from './utils/service-endpoint-policy';
38
+ export type { AuthorizationContext, AuthorizationProvider, } from './utils/authorization-provider.utils';
38
39
  export { buildLoginDomain, buildSignedLoginPayload, LOGIN_TYPES, LOGIN_TYPES_WITH_AUDIENCE, } from './utils/eip712-login';
39
40
  export type { DhServerLoginMessage, DhServerLoginPayload, } from './utils/eip712-login';
40
41
  export { DEFAULT_LIT_NETWORK, VALID_LIT_NETWORKS, SDK_DEFAULTS, } from './constants/chunks/sdk-config';
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: "0x80C47350274513688e348D02506477A96B4afCCb",
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-23T17:13:42.777Z",
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 = {
@@ -5522,8 +5498,14 @@ function getMainnetConfig() {
5522
5498
  agentModuleFactory: MAINNET_CONTRACTS.AgentModuleFactory || ""
5523
5499
  },
5524
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.
5525
5508
  diamondHandsUrl: "https://gateway-arbitrum.network.thegraph.com/api/subgraphs/id/CCTPsdYqco2jChDLLBQTbdJWwoukVoMt1cXeR9ti6r9A"
5526
- // Published Sepolia subgraph on Arbitrum One
5527
5509
  },
5528
5510
  litNetwork: "chipotle",
5529
5511
  debug: false
@@ -6415,7 +6397,7 @@ async function generateMintAuthorization(positionId, amount, chainId, signerOrPr
6415
6397
  signature
6416
6398
  };
6417
6399
  }
6418
- async function generatePaymentAuthorization(positionId, amount, chainId, signer) {
6400
+ async function generatePaymentAuthorization(positionId, amount, chainId, signerOrPrecomputed) {
6419
6401
  if (typeof positionId !== "string") {
6420
6402
  throw new Error(`positionId must be a string, got ${typeof positionId}`);
6421
6403
  }
@@ -6425,6 +6407,18 @@ async function generatePaymentAuthorization(positionId, amount, chainId, signer)
6425
6407
  if (typeof chainId !== "number") {
6426
6408
  throw new Error(`chainId must be a number, got ${typeof chainId}`);
6427
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;
6428
6422
  const timestamp = await calculateNextQuantumTimestamp();
6429
6423
  if (typeof timestamp !== "number") {
6430
6424
  throw new Error(`timestamp must be a number, got ${typeof timestamp}`);
@@ -6666,6 +6660,26 @@ async function generateExtendAuthorization(positionId, newTerm, chainId, signerO
6666
6660
  };
6667
6661
  }
6668
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
+
6669
6683
  // src/utils/eip712-login.ts
6670
6684
  var import_ethers4 = require("ethers");
6671
6685
  function buildLoginDomain(chainId) {
@@ -20077,11 +20091,20 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
20077
20091
  {}
20078
20092
  );
20079
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
+ );
20080
20103
  const auth = await generateMintAuthorization(
20081
20104
  request.positionId,
20082
20105
  BigInt(requestAmountWei.toString()),
20083
20106
  chainId,
20084
- signer2
20107
+ mintAuthInput
20085
20108
  );
20086
20109
  if (this.config.debug) {
20087
20110
  log.info(` Authorization generated (attempt ${attempt})`, {});
@@ -21672,7 +21695,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21672
21695
  * @param _selectedTerm - Extension term in months
21673
21696
  * @returns Transaction receipt
21674
21697
  */
21675
- async extendPosition(_positionId, _selectedTerm, _rpcUrl, _bitcoinRpcUrl) {
21698
+ async extendPosition(_positionId, _selectedTerm, _rpcUrl, _bitcoinRpcUrl, _authorizationProvider) {
21676
21699
  this.ensureInitialized();
21677
21700
  if (typeof _positionId !== "string") {
21678
21701
  throw new Error(`positionId must be a string, got ${typeof _positionId}`);
@@ -21766,11 +21789,23 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21766
21789
  }
21767
21790
  }
21768
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
+ );
21769
21804
  const auth = await generateExtendAuthorization(
21770
21805
  _positionId,
21771
21806
  _selectedTerm,
21772
21807
  chainId,
21773
- signer
21808
+ extendAuthInput
21774
21809
  );
21775
21810
  if (this.config.debug) {
21776
21811
  log.info(" Extend authorization generated", {
@@ -22026,7 +22061,8 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
22026
22061
  positionId,
22027
22062
  selectedTerm,
22028
22063
  request.rpcUrl,
22029
- request.customBitcoinRpcUrl
22064
+ request.customBitcoinRpcUrl,
22065
+ request.authorizationProvider
22030
22066
  );
22031
22067
  let newExpiryAt;
22032
22068
  try {
@@ -22554,11 +22590,20 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
22554
22590
  }
22555
22591
  let auth;
22556
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
+ );
22557
22602
  auth = await generatePaymentAuthorization(
22558
22603
  request.positionId,
22559
22604
  paymentAmountWei,
22560
22605
  chainId,
22561
- signer
22606
+ paymentAuthInput
22562
22607
  );
22563
22608
  } catch (authError) {
22564
22609
  if (authError.message && authError.message.includes("BigNumber")) {
@@ -23070,7 +23115,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
23070
23115
  * @param customBitcoinRpcUrl - Optional custom Bitcoin RPC URL (for local testing)
23071
23116
  * @returns Withdrawal result with transaction details
23072
23117
  */
23073
- async withdrawBTC(positionId, withdrawalAddress, withdrawalAmount) {
23118
+ async withdrawBTC(positionId, withdrawalAddress, withdrawalAmount, options) {
23074
23119
  this.ensureInitialized();
23075
23120
  try {
23076
23121
  if (this.config.debug) {
@@ -23285,11 +23330,21 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
23285
23330
  {}
23286
23331
  );
23287
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
+ );
23288
23343
  const auth = await generateWithdrawAuthorization(
23289
23344
  positionId,
23290
23345
  BigInt(withdrawalAmount),
23291
23346
  chainId,
23292
- signer,
23347
+ withdrawAuthInput,
23293
23348
  withdrawalAddress
23294
23349
  // Pass destination address for message hash
23295
23350
  );
@@ -24363,6 +24418,11 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24363
24418
  * (`MAX_ATTESTATION_AGE = 1 hour`) and replay-protects via
24364
24419
  * `authorizedAt` binding.
24365
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
+ *
24366
24426
  * `invalidatorTxid` is either:
24367
24427
  * - the borrower's own authorized broadcast (classified "consumed-as-authorized"
24368
24428
  * by the LIT validator), once it has ≥6 confirmations on the BTC
@@ -24375,7 +24435,6 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24375
24435
  async recoverStaleSpend(params) {
24376
24436
  this.ensureInitialized();
24377
24437
  try {
24378
- const signer = this.getSignerOrThrow();
24379
24438
  const contractAddresses = this.getContractAddressesOrThrow();
24380
24439
  const btcSpendAuthorizerAddress = contractAddresses.btcSpendAuthorizer;
24381
24440
  if (!btcSpendAuthorizerAddress) {
@@ -24421,6 +24480,28 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24421
24480
  success: false,
24422
24481
  error: json.error ?? "stale-spend recovery request failed"
24423
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
+ };
24424
24505
  } else {
24425
24506
  const rpcUrl = params.rpcUrl ?? this.config.ethRpcUrl;
24426
24507
  if (!rpcUrl) {
@@ -24470,7 +24551,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24470
24551
  [
24471
24552
  "function cancelStaleSpendWithProof(bytes32 positionId, bytes32 utxoKey, uint256 authorizedAt, string calldata invalidatorTxid, uint256 attestationTimestamp, bytes calldata litSignature) external"
24472
24553
  ],
24473
- signer
24554
+ this.getSignerOrThrow()
24474
24555
  );
24475
24556
  const tx = await btcSpendAuthorizer["cancelStaleSpendWithProof"](
24476
24557
  attRes.attestation.positionId,
@@ -25278,10 +25359,22 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
25278
25359
  "getLoanGrant",
25279
25360
  data
25280
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;
25281
25367
  return {
25282
25368
  borrower,
25283
25369
  scopeBits: Number(scopeBits),
25284
- 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
25285
25378
  };
25286
25379
  }
25287
25380
  /**
@@ -25323,10 +25416,14 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
25323
25416
  const mintEnabled = hasGrant && (scopes & 4) !== 0;
25324
25417
  const floor = grant.minCollateralRatioBps;
25325
25418
  const agent = user ? await registry.agentOf(user) : null;
25419
+ const withdrawEnabled = hasGrant && grant.withdrawScopeSupported && (scopes & 8) !== 0;
25326
25420
  return {
25327
25421
  renewEnabled: hasGrant && (scopes & 1) !== 0,
25328
25422
  repayEnabled: hasGrant && (scopes & 2) !== 0,
25329
25423
  mintEnabled,
25424
+ withdrawEnabled,
25425
+ withdrawScopeSupported: grant.withdrawScopeSupported,
25426
+ minWithdrawRatioBps: withdrawEnabled ? grant.minWithdrawRatioBps : 0,
25330
25427
  minCollateralRatioBps: mintEnabled ? floor : 0,
25331
25428
  effectiveMintFloorBps: mintEnabled ? Math.max(floor, protocolMin) : 0,
25332
25429
  protocolMinFloorBps: protocolMin,
@@ -25512,8 +25609,83 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
25512
25609
  const receipt = await tx.wait();
25513
25610
  return { hash: tx.hash, blockNumber: receipt?.blockNumber ?? 0 };
25514
25611
  }
25515
- /** Pre-flight the registry's `FloorBelowProtocolMin` guard with a message a user can act on. */
25516
- async assertMintFloorAtOrAboveProtocolMin(floorBps) {
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") {
25517
25689
  if (!Number.isInteger(floorBps) || floorBps <= 0) {
25518
25690
  throw new SDKError({
25519
25691
  message: `Invalid collateral floor: ${floorBps} bps \u2014 must be a positive integer`,
@@ -25524,7 +25696,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
25524
25696
  const protocolMin = await this.getAgentMintFloorMinBps();
25525
25697
  if (protocolMin === 0) {
25526
25698
  throw new SDKError({
25527
- message: "Agent minting is not configured on this network (agentMintFloorMinBps is 0) \u2014 the registry would reject every mint grant",
25699
+ message: `Agent ${scope === "mint" ? "minting" : "withdrawal"} is not configured on this network (agentMintFloorMinBps is 0) \u2014 the registry would reject every ${scope} grant`,
25528
25700
  category: "CONFIGURATION" /* CONFIGURATION */,
25529
25701
  severity: "HIGH" /* HIGH */
25530
25702
  });