@gvnrdao/dh-sdk 0.0.318 → 0.0.320

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
@@ -412,7 +412,7 @@ var init_deployment_addresses = __esm({
412
412
  LiquidationManagerModule: "0x74B28695352E7bf736D6c9F76ccb7B4439477De9",
413
413
  CircuitBreakerModule: "0xF7b545d3a4Ee056D06909AB367E2F7bFFE3cB1BC",
414
414
  AdminModule: "0x0023B71B586795eE5fa4E83E384345C1EC8Bb946",
415
- PositionManagerViews: "0x9225647a30bbACA4Acc0491B786869036914242e",
415
+ PositionManagerViews: "0x8B57954eA338c6A6bfF1457f71080e25baFF3937",
416
416
  PositionManager: "0x12A1836e56139ccaD8722B89519d84233efc224B",
417
417
  OperationAuthorizationRegistry: "0xD4505645b724754f5170a2027804a4265e860d44",
418
418
  ContractVersionRegistry: "0x5e18f77d01940dA5B1D986D373D9A6ad0767008d",
@@ -490,13 +490,13 @@ var init_deployment_addresses = __esm({
490
490
  block: 25472957
491
491
  },
492
492
  PositionManager: {
493
- previousImplementation: "0x9f52BaBB40FbFC5863e45B1A4014cB433950c684",
494
- newImplementation: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
495
- upgradedAt: "2026-07-06T08:42:47.000Z",
496
- upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0x2002827d34A781883Ec4c83c36ECC140c3911F2B in tx 0xa1fbe31cb71736514a868e681573dff22c9fab91d69cec157a24a30e29eadbe6 (block 25472368)",
497
- reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
498
- executeTx: "0xa1fbe31cb71736514a868e681573dff22c9fab91d69cec157a24a30e29eadbe6",
499
- block: 25472368
493
+ previousImplementation: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
494
+ newImplementation: "0x2e7170d6Fb15A876dB726732e72e63109bcB916d",
495
+ upgradedAt: "2026-08-03T08:34:23.000Z",
496
+ upgradedBy: "AdminModule generic-op 0x69b79a906ed2 (3-of-3 signatures, 24h timelock; proposed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f, executed in the release batch onchain-auth/.safe-batches/mainnet-release-execute-B.json, submitted by EOA 0x2002827d34A781883Ec4c83c36ECC140c3911F2B, tx 0xf1f8d5fc13f45d7a3f932bf1a2d6e29381fdd41cd31298ad785bf7688df07fe6, block 25673200)",
497
+ reason: "2026-08 agent-stack release, Execute B: agent-delegation digest rebind. makePayment/mintUCD/extendPosition now bind their EIP-712 digest to the STORED borrower rather than msg.sender, so a scoped AgentDelegationRegistry grant can submit; withdrawBTC still binds msg.sender and stays non-delegable. Also adds setViews(address), which the same batch used to repoint PM at the replacement PositionManagerViews. NOTE: VERSION reads 1.5.4 on BOTH sides of this upgrade \u2014 verify by implementation ADDRESS, never by VERSION(). NOTE: not the July audit rollout batch \u2014 reconcile-deployment-record.ts --write templates every row with that text; this hand-authored provenance is the accurate one, restore it if --write flattens it again.",
498
+ executeTx: "0xf1f8d5fc13f45d7a3f932bf1a2d6e29381fdd41cd31298ad785bf7688df07fe6",
499
+ block: 25673200
500
500
  },
501
501
  LoanOperationsManagerModule: {
502
502
  previousImplementation: "0x56671D671e492Dae625413d463D47605Cf54edf6",
@@ -540,6 +540,16 @@ var init_deployment_addresses = __esm({
540
540
  reason: "2026-08 agent-stack release: PositionDelegateRegistry VERSION 1.1.0 -> 2.0.0 via upgradeToAndCall + initializeV2(AdminModule, registrar 0x5D54E0BE\u2026, pauser 0xC205Ae99\u2026) \u2014 kill-switch, deny-only registrar, permanent delegate burn. NOTE: not the July audit rollout batch \u2014 reconcile-deployment-record.ts --write templates every row with that text; this hand-authored provenance is the accurate one, restore it if --write flattens it again.",
541
541
  executeTx: "0xf0f237f6c4e958c37de556571908283c59c3fba6ebf9fa3cefb956f37e315a10",
542
542
  block: 25669024
543
+ },
544
+ PositionManagerViews: {
545
+ previousImplementation: "0x9225647a30bbACA4Acc0491B786869036914242e",
546
+ newImplementation: "0xE68DC096A65Ecddbc6A9b5c6e5f878e2CE143Bfe",
547
+ proxy: "0x8B57954eA338c6A6bfF1457f71080e25baFF3937",
548
+ upgradedAt: "2026-08-03T08:34:23.000Z",
549
+ upgradedBy: "REPLACED, not upgraded. Fresh ERC1967Proxy (atomically initialized) + AdminModule generic-op 0x848a97186f0d PositionManager.setViews(proxy) \u2014 3-of-3, 24h timelock, executed in onchain-auth/.safe-batches/mainnet-release-execute-B.json",
550
+ reason: "2026-08 agent-stack release. The mainnet PositionManagerViews at 0x9225647a was deployed as a BARE implementation (EIP-1967 slot 0x00), so upgradeToAndCall reverts UUPSUnauthorizedCallContext \u2014 it could never be upgraded. PositionManager.setViews (added in the same Execute B batch) removed the repoint blocker, so Views was replaced behind a new proxy rather than upgraded. This makes the Pashov F3 renewal debt-ceiling check REACHABLE for the first time: ExceedsMaxLoan (0x499e7985) is present in 0xE68DC096 and absent from 0x9225647a. Views holds no protocol state (8 wiring slots + roles + pause), which is what made replacement safe. The old contract is ABANDONED, not decommissioned \u2014 it stays live and callable as the rollback target for setViews. NOTE: VERSION reads 1.1.1 on both sides; verify by address.",
551
+ executeTx: "0xf1f8d5fc13f45d7a3f932bf1a2d6e29381fdd41cd31298ad785bf7688df07fe6",
552
+ block: 25673200
543
553
  }
544
554
  },
545
555
  latestEnv: {
@@ -573,10 +583,10 @@ var init_deployment_addresses = __esm({
573
583
  ADMIN_MODULE_IMPL: "0xb4C5C7E051A1e672F014B25CB5D55B225Aa0D512",
574
584
  BTC_SPEND_AUTHORIZER: "0xfBbF108486fa07F1B06a811C5b66ed68DD9c508a",
575
585
  BTC_SPEND_AUTHORIZER_IMPL: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
576
- POSITION_MANAGER_VIEWS: "0x9225647a30bbACA4Acc0491B786869036914242e",
577
- POSITION_MANAGER_VIEWS_IMPL: "0x9225647a30bbACA4Acc0491B786869036914242e",
586
+ POSITION_MANAGER_VIEWS: "0x8B57954eA338c6A6bfF1457f71080e25baFF3937",
587
+ POSITION_MANAGER_VIEWS_IMPL: "0xE68DC096A65Ecddbc6A9b5c6e5f878e2CE143Bfe",
578
588
  POSITION_MANAGER: "0x12A1836e56139ccaD8722B89519d84233efc224B",
579
- POSITION_MANAGER_IMPL: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
589
+ POSITION_MANAGER_IMPL: "0x2e7170d6Fb15A876dB726732e72e63109bcB916d",
580
590
  LOAN_OPERATIONS_MANAGER_MODULE: "0x2561974A40F1AEed1251d0A090A83cF8536FB9E9",
581
591
  LOAN_OPERATIONS_MANAGER_MODULE_IMPL: "0x7d90aAB4466f59BA9aC9C7a9BF14dC53734336e7",
582
592
  UPGRADE_VALIDATOR: "0xC46Ae71F22e3741A6CF46E399aE914C4868fEbed",
@@ -602,7 +612,7 @@ var init_deployment_addresses = __esm({
602
612
  LiquidationManagerModule: "0x74B28695352E7bf736D6c9F76ccb7B4439477De9",
603
613
  CircuitBreakerModule: "0xF7b545d3a4Ee056D06909AB367E2F7bFFE3cB1BC",
604
614
  AdminModule: "0x0023B71B586795eE5fa4E83E384345C1EC8Bb946",
605
- PositionManagerViews: "0x9225647a30bbACA4Acc0491B786869036914242e",
615
+ PositionManagerViews: "0x8B57954eA338c6A6bfF1457f71080e25baFF3937",
606
616
  PositionManager: "0x12A1836e56139ccaD8722B89519d84233efc224B",
607
617
  OperationAuthorizationRegistry: "0xD4505645b724754f5170a2027804a4265e860d44",
608
618
  ContractVersionRegistry: "0x5e18f77d01940dA5B1D986D373D9A6ad0767008d",
@@ -6521,6 +6531,7 @@ function sessionStoreKey(address, chainId, endpoint) {
6521
6531
  return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
6522
6532
  }
6523
6533
  var PENDING_ENVELOPE_TTL_SECONDS = 8 * 60;
6534
+ var EOA_PENDING_ENVELOPE_TTL_SECONDS = 60;
6524
6535
  function pendingEnvelopeKey(sessionKey) {
6525
6536
  return sessionKey + ":pending";
6526
6537
  }
@@ -6568,7 +6579,7 @@ var LocalStorageSessionStore = class {
6568
6579
  } catch {
6569
6580
  }
6570
6581
  }
6571
- loadPending(key) {
6582
+ loadPending(key, maxAgeSeconds = PENDING_ENVELOPE_TTL_SECONDS) {
6572
6583
  let raw;
6573
6584
  try {
6574
6585
  raw = window.localStorage.getItem(pendingEnvelopeKey(key));
@@ -6585,7 +6596,7 @@ var LocalStorageSessionStore = class {
6585
6596
  return null;
6586
6597
  }
6587
6598
  const nowSec = Math.floor(Date.now() / 1e3);
6588
- if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
6599
+ if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + maxAgeSeconds) {
6589
6600
  this.clearPending(key);
6590
6601
  return null;
6591
6602
  }
@@ -6619,12 +6630,12 @@ var MemorySessionStore = class {
6619
6630
  clear(key) {
6620
6631
  this.entries.delete(key);
6621
6632
  }
6622
- loadPending(key) {
6633
+ loadPending(key, maxAgeSeconds = PENDING_ENVELOPE_TTL_SECONDS) {
6623
6634
  const envelope = this.pending.get(key);
6624
6635
  if (!envelope)
6625
6636
  return null;
6626
6637
  const nowSec = Math.floor(Date.now() / 1e3);
6627
- if (nowSec >= envelope.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
6638
+ if (nowSec >= envelope.issuedAt + maxAgeSeconds) {
6628
6639
  this.pending.delete(key);
6629
6640
  return null;
6630
6641
  }
@@ -6658,15 +6669,23 @@ var REFRESH_LEEWAY_SECONDS = 30;
6658
6669
  var LOGIN_RETRY_BASE_MS = 3e4;
6659
6670
  var LOGIN_RETRY_MAX_MS = 5 * 6e4;
6660
6671
  var ServerLoginError = class extends Error {
6661
- constructor(message, status) {
6672
+ constructor(message, status, reason) {
6662
6673
  super(message);
6663
6674
  this.status = status;
6675
+ this.reason = reason;
6664
6676
  this.name = "ServerLoginError";
6665
6677
  }
6666
6678
  };
6667
6679
  function isLoginRejection(error) {
6668
6680
  return error instanceof ServerLoginError && error.status >= 400 && error.status < 500 && error.status !== 429;
6669
6681
  }
6682
+ var SPENT_ENVELOPE_REASONS = /* @__PURE__ */ new Set(["replayed_nonce", "expired_issuedAt"]);
6683
+ function isSpentEnvelope(error) {
6684
+ if (!isLoginRejection(error))
6685
+ return false;
6686
+ const { reason, status } = error;
6687
+ return reason ? SPENT_ENVELOPE_REASONS.has(reason) : status === 409;
6688
+ }
6670
6689
  var ServerSession = class {
6671
6690
  signer;
6672
6691
  serviceEndpoint;
@@ -6697,6 +6716,12 @@ var ServerSession = class {
6697
6716
  * `clear()` can drop the persisted token without an async address lookup.
6698
6717
  */
6699
6718
  lastStoreKey = null;
6719
+ /**
6720
+ * Whether the signer is a smart-contract wallet, memoized after the first
6721
+ * `eth_getCode`. Decides which of the server's two first-use windows bounds
6722
+ * envelope reuse; a signer cannot change kind for the life of the session.
6723
+ */
6724
+ contractSigner = null;
6700
6725
  constructor(opts) {
6701
6726
  assertSafeServiceEndpoint(opts.serviceEndpoint);
6702
6727
  this.signer = opts.signer;
@@ -6895,6 +6920,34 @@ var ServerSession = class {
6895
6920
  return null;
6896
6921
  }
6897
6922
  }
6923
+ /**
6924
+ * How long a pending envelope for THIS signer may be reused, in seconds.
6925
+ *
6926
+ * The server grants an EOA 120s to make an envelope's first use and a contract
6927
+ * wallet 600s, so one local TTL is necessarily wrong for one of them. Deployed
6928
+ * code has bytecode; an EOA does not — one `eth_getCode`, memoized, settles it.
6929
+ *
6930
+ * Reports the CONTRACT window when it cannot tell, which is the behavior that
6931
+ * shipped before the split. The asymmetry is intended: guessing "EOA" for a
6932
+ * Safe hands it a new message and orphans the owner signatures already
6933
+ * collected, while guessing "contract" for an EOA now costs one rejected
6934
+ * attempt, because a rejection clears the envelope.
6935
+ */
6936
+ async pendingEnvelopeTtl() {
6937
+ if (this.contractSigner === null) {
6938
+ try {
6939
+ const provider = this.signer.provider;
6940
+ if (!provider)
6941
+ return PENDING_ENVELOPE_TTL_SECONDS;
6942
+ const address = await this.signer.getAddress();
6943
+ const code = await provider.getCode(address);
6944
+ this.contractSigner = typeof code === "string" && code !== "0x";
6945
+ } catch {
6946
+ return PENDING_ENVELOPE_TTL_SECONDS;
6947
+ }
6948
+ }
6949
+ return this.contractSigner ? PENDING_ENVELOPE_TTL_SECONDS : EOA_PENDING_ENVELOPE_TTL_SECONDS;
6950
+ }
6898
6951
  async getOrRefresh() {
6899
6952
  const nowSec = this.now();
6900
6953
  if (this.cached && this.cached.expiresAt - REFRESH_LEEWAY_SECONDS > nowSec) {
@@ -6916,10 +6969,10 @@ var ServerSession = class {
6916
6969
  }
6917
6970
  this.onSignaturePrompt?.();
6918
6971
  let session;
6972
+ const store = this.store;
6973
+ const storeKey = store ? this.lastStoreKey : null;
6919
6974
  try {
6920
- const store = this.store;
6921
- const storeKey = store ? this.lastStoreKey : null;
6922
- const reuse = storeKey ? store.loadPending?.(storeKey) ?? void 0 : void 0;
6975
+ const reuse = storeKey ? store.loadPending?.(storeKey, await this.pendingEnvelopeTtl()) ?? void 0 : void 0;
6923
6976
  const payload = await buildSignedLoginPayload(
6924
6977
  this.signer,
6925
6978
  this.chainId,
@@ -6942,6 +6995,9 @@ var ServerSession = class {
6942
6995
  this.onSignatureResolved?.(true);
6943
6996
  } catch (error) {
6944
6997
  this.onSignatureResolved?.(false);
6998
+ if (storeKey && isSpentEnvelope(error)) {
6999
+ store.clearPending?.(storeKey);
7000
+ }
6945
7001
  const failures = (this.promptBackoff?.failures ?? 0) + 1;
6946
7002
  this.promptBackoff = {
6947
7003
  failures,
@@ -6970,9 +7026,17 @@ var ServerSession = class {
6970
7026
  });
6971
7027
  if (!res.ok) {
6972
7028
  const detail = await res.text().catch(() => "");
7029
+ let reason;
7030
+ try {
7031
+ const parsed = JSON.parse(detail);
7032
+ if (typeof parsed?.reason === "string")
7033
+ reason = parsed.reason;
7034
+ } catch {
7035
+ }
6973
7036
  throw new ServerLoginError(
6974
7037
  `lit-ops-server login failed: HTTP ${res.status} ${detail.slice(0, 200)}`,
6975
- res.status
7038
+ res.status,
7039
+ reason
6976
7040
  );
6977
7041
  }
6978
7042
  const body = await res.json();
@@ -12627,7 +12691,7 @@ var DiamondHandsGraph = class {
12627
12691
  async getPSMConfiguration() {
12628
12692
  const query = `
12629
12693
  query GetPSMConfiguration {
12630
- psmConfiguration(id: "psm") {
12694
+ psmConfiguration: psmconfiguration(id: "psm") {
12631
12695
  id
12632
12696
  version
12633
12697
  redeemEnabled
@@ -12749,7 +12813,7 @@ var DiamondHandsGraph = class {
12749
12813
  const whereClause = whereConditions.length > 0 ? `where: { ${whereConditions.join(", ")} }` : "";
12750
12814
  const query = `
12751
12815
  query GetPSMTransactions($first: Int!, $skip: Int!) {
12752
- psmTransactions(${whereClause}, first: $first, skip: $skip, orderBy: timestamp, orderDirection: desc) {
12816
+ psmTransactions: psmtransactions(${whereClause}, first: $first, skip: $skip, orderBy: timestamp, orderDirection: desc) {
12753
12817
  id
12754
12818
  type
12755
12819
  user
@@ -12784,7 +12848,7 @@ var DiamondHandsGraph = class {
12784
12848
  const whereClause = whereConditions.length > 0 ? `where: { ${whereConditions.join(", ")} }` : "";
12785
12849
  const query = `
12786
12850
  query GetPSMDailyMetrics($first: Int!) {
12787
- psmDailyMetrics(${whereClause}, first: $first, orderBy: date, orderDirection: desc) {
12851
+ psmDailyMetrics: psmdailyMetrics(${whereClause}, first: $first, orderBy: date, orderDirection: desc) {
12788
12852
  id
12789
12853
  date
12790
12854
  totalSwapVolume
@@ -13508,7 +13572,7 @@ var DiamondHandsGraph = class {
13508
13572
  }` : ""}
13509
13573
 
13510
13574
  ${includeKind("psm") ? `
13511
- psmTransactions(
13575
+ psmTransactions: psmtransactions(
13512
13576
  ${buildTimeWhereClause()}
13513
13577
  first: $limit, orderBy: timestamp, orderDirection: $orderDirection
13514
13578
  ) {
@@ -24571,22 +24635,28 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24571
24635
  * @throws if timeout is reached before subgraph catches up
24572
24636
  */
24573
24637
  // ============================================================================
24574
- // HIGH-LEVEL API - Agent Delegation (auto-renew) — SEPOLIA ONLY
24638
+ // HIGH-LEVEL API - Agent Delegation (auto-renew)
24575
24639
  // ============================================================================
24576
- /** Guard: the AgentDelegationRegistry MVP is deployed on Sepolia only. */
24577
- async assertAgentDelegationSepolia() {
24578
- const chainId = Number((await this.getProviderOrThrow().getNetwork()).chainId);
24579
- if (chainId !== 11155111) {
24580
- throw new SDKError({
24581
- message: `Agent auto-renew delegation is Sepolia-only (chainId 11155111); got ${chainId}`,
24582
- category: "NETWORK" /* NETWORK */,
24583
- severity: "HIGH" /* HIGH */
24584
- });
24585
- }
24640
+ /**
24641
+ * Guard: resolve the AgentDelegationRegistry for the configured network.
24642
+ *
24643
+ * Deployment presence IS the availability test — there is deliberately no chainId
24644
+ * allow-list here. The registry is deployed per network (Sepolia 2026-07, mainnet
24645
+ * 2026-08 agent-stack release) and `getNetworkConfig` populates the address from the
24646
+ * generated deployment table, so a chain without a deployment resolves to `""` and
24647
+ * fails closed below. Adding a chain therefore needs no SDK code change — which is
24648
+ * exactly how the previous hardcoded `chainId !== 11155111` gate went stale and kept
24649
+ * the mainnet toggle dark after the registry shipped.
24650
+ *
24651
+ * Callers that need the availability answer WITHOUT throwing (e.g. to decide whether to
24652
+ * render a toggle at all) should ask the deployment table directly rather than calling this
24653
+ * and catching — `ALL_DEPLOYMENTS` is published on the `@gvnrdao/dh-sdk/deployments` subpath.
24654
+ */
24655
+ agentDelegationRegistryOrThrow() {
24586
24656
  const addr = this.config.contractAddresses?.agentDelegationRegistry;
24587
24657
  if (!addr) {
24588
24658
  throw new SDKError({
24589
- message: "AgentDelegationRegistry address not configured \u2014 provide contractAddresses.agentDelegationRegistry",
24659
+ message: "AgentDelegationRegistry is not available on this network \u2014 no deployment address is configured (contractAddresses.agentDelegationRegistry)",
24590
24660
  category: "CONFIGURATION" /* CONFIGURATION */,
24591
24661
  severity: "HIGH" /* HIGH */
24592
24662
  });
@@ -24598,7 +24668,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24598
24668
  * Pure view — no signer required.
24599
24669
  */
24600
24670
  async getAutoRenewStatus(positionId, user) {
24601
- const addr = await this.assertAgentDelegationSepolia();
24671
+ const addr = this.agentDelegationRegistryOrThrow();
24602
24672
  const registry = AgentDelegationRegistry__factory.connect(addr, this.getProviderOrThrow());
24603
24673
  const pid = this.toBytes32(positionId);
24604
24674
  const grant = await registry.getLoanGrant(pid);
@@ -24618,7 +24688,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24618
24688
  * registers it, then records the position's renew grant. Borrower-signed.
24619
24689
  */
24620
24690
  async enableAutoRenew(positionId, options) {
24621
- const addr = await this.assertAgentDelegationSepolia();
24691
+ const addr = this.agentDelegationRegistryOrThrow();
24622
24692
  const pdrAddr = this.config.contractAddresses?.positionDelegateRegistry;
24623
24693
  if (!pdrAddr) {
24624
24694
  throw new SDKError({
@@ -24658,7 +24728,7 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
24658
24728
  }
24659
24729
  /** Disable auto-renew delegation for a position (clears the grant). Borrower-signed. */
24660
24730
  async disableAutoRenew(positionId) {
24661
- const addr = await this.assertAgentDelegationSepolia();
24731
+ const addr = this.agentDelegationRegistryOrThrow();
24662
24732
  const signer = this.getSignerOrThrow();
24663
24733
  const user = await signer.getAddress();
24664
24734
  const registry = AgentDelegationRegistry__factory.connect(addr, signer);
@@ -887,8 +887,22 @@ export declare class DiamondHandsSDK {
887
887
  * @param options - Override timeout/interval for this call
888
888
  * @throws if timeout is reached before subgraph catches up
889
889
  */
890
- /** Guard: the AgentDelegationRegistry MVP is deployed on Sepolia only. */
891
- private assertAgentDelegationSepolia;
890
+ /**
891
+ * Guard: resolve the AgentDelegationRegistry for the configured network.
892
+ *
893
+ * Deployment presence IS the availability test — there is deliberately no chainId
894
+ * allow-list here. The registry is deployed per network (Sepolia 2026-07, mainnet
895
+ * 2026-08 agent-stack release) and `getNetworkConfig` populates the address from the
896
+ * generated deployment table, so a chain without a deployment resolves to `""` and
897
+ * fails closed below. Adding a chain therefore needs no SDK code change — which is
898
+ * exactly how the previous hardcoded `chainId !== 11155111` gate went stale and kept
899
+ * the mainnet toggle dark after the registry shipped.
900
+ *
901
+ * Callers that need the availability answer WITHOUT throwing (e.g. to decide whether to
902
+ * render a toggle at all) should ask the deployment table directly rather than calling this
903
+ * and catching — `ALL_DEPLOYMENTS` is published on the `@gvnrdao/dh-sdk/deployments` subpath.
904
+ */
905
+ private agentDelegationRegistryOrThrow;
892
906
  /**
893
907
  * Read a position's auto-renew delegation state (used to drive the Enable/Disable toggle).
894
908
  * Pure view — no signer required.
@@ -44,8 +44,12 @@ export interface ServerSessionStore {
44
44
  * so a custom store written before this existed still satisfies the interface;
45
45
  * without it the session simply mints a fresh envelope each attempt, which is
46
46
  * the pre-existing behavior and only costs a multi-owner Safe a restart.
47
+ *
48
+ * `maxAgeSeconds` is supplied by the caller, which knows the signer kind and
49
+ * therefore which of the server's two first-use windows applies. A store that
50
+ * ignores it keeps the old single-TTL behavior.
47
51
  */
48
- loadPending?(key: string): PendingLoginEnvelope | null;
52
+ loadPending?(key: string, maxAgeSeconds?: number): PendingLoginEnvelope | null;
49
53
  savePending?(key: string, envelope: PendingLoginEnvelope): void;
50
54
  clearPending?(key: string): void;
51
55
  }
@@ -69,12 +73,26 @@ export interface PendingLoginEnvelope {
69
73
  nonce: string;
70
74
  }
71
75
  /**
72
- * How long an unsigned envelope stays reusable. Must stay UNDER the server's
73
- * contract-wallet first-use skew (`CONTRACT_ISSUED_AT_SKEW_SECONDS`, 10 min):
74
- * beyond it the first use is rejected as stale, so reuse would guarantee failure
75
- * instead of preventing a re-sign.
76
+ * How long an unsigned envelope stays reusable FOR A CONTRACT WALLET. Must stay
77
+ * UNDER the server's contract-wallet first-use skew
78
+ * (`CONTRACT_ISSUED_AT_SKEW_SECONDS`, 10 min): beyond it the first use is
79
+ * rejected as stale, so reuse would guarantee failure instead of preventing a
80
+ * re-sign.
76
81
  */
77
82
  export declare const PENDING_ENVELOPE_TTL_SECONDS: number;
83
+ /**
84
+ * The same, for an EOA — which the server gives only `ISSUED_AT_SKEW_SECONDS`
85
+ * (120s) to make its FIRST use.
86
+ *
87
+ * Between 120s and 8 minutes a reused EOA envelope is still "live" locally and
88
+ * certain to be refused by the server, so the contract-wallet TTL is not a safe
89
+ * over-estimate for a key-holding wallet — it is a window in which every retry
90
+ * re-signs a message that cannot be accepted. 60s leaves a full minute of the
91
+ * server's window for the prompt and the round trip, and an EOA gives up
92
+ * nothing by re-minting: it signs in one click, so it has no equivalent of the
93
+ * cross-session ceremony this reuse exists for.
94
+ */
95
+ export declare const EOA_PENDING_ENVELOPE_TTL_SECONDS = 60;
78
96
  /** Storage key for the pending envelope matching a session key. */
79
97
  export declare function pendingEnvelopeKey(sessionKey: string): string;
80
98
  export declare function isPendingEnvelopeShape(value: any): value is PendingLoginEnvelope;
@@ -82,7 +100,7 @@ export declare class LocalStorageSessionStore implements ServerSessionStore {
82
100
  load(key: string): PersistedServerSession | null;
83
101
  save(key: string, session: PersistedServerSession): void;
84
102
  clear(key: string): void;
85
- loadPending(key: string): PendingLoginEnvelope | null;
103
+ loadPending(key: string, maxAgeSeconds?: number): PendingLoginEnvelope | null;
86
104
  savePending(key: string, envelope: PendingLoginEnvelope): void;
87
105
  clearPending(key: string): void;
88
106
  }
@@ -92,7 +110,7 @@ export declare class MemorySessionStore implements ServerSessionStore {
92
110
  load(key: string): PersistedServerSession | null;
93
111
  save(key: string, session: PersistedServerSession): void;
94
112
  clear(key: string): void;
95
- loadPending(key: string): PendingLoginEnvelope | null;
113
+ loadPending(key: string, maxAgeSeconds?: number): PendingLoginEnvelope | null;
96
114
  savePending(key: string, envelope: PendingLoginEnvelope): void;
97
115
  clearPending(key: string): void;
98
116
  }
@@ -62,10 +62,15 @@ export interface ServerSessionOptions {
62
62
  /** Override for tests. */
63
63
  fetchImpl?: typeof fetch;
64
64
  }
65
- /** Login failure carrying the HTTP status so callers can tell rejection from outage. */
65
+ /**
66
+ * Login failure carrying the HTTP status — and the server's machine-readable
67
+ * `reason` where it sent one — so callers can tell rejection from outage, and
68
+ * one kind of rejection from another.
69
+ */
66
70
  export declare class ServerLoginError extends Error {
67
71
  readonly status: number;
68
- constructor(message: string, status: number);
72
+ readonly reason?: string | undefined;
73
+ constructor(message: string, status: number, reason?: string | undefined);
69
74
  }
70
75
  export declare class ServerSession {
71
76
  private readonly signer;
@@ -97,6 +102,12 @@ export declare class ServerSession {
97
102
  * `clear()` can drop the persisted token without an async address lookup.
98
103
  */
99
104
  private lastStoreKey;
105
+ /**
106
+ * Whether the signer is a smart-contract wallet, memoized after the first
107
+ * `eth_getCode`. Decides which of the server's two first-use windows bounds
108
+ * envelope reuse; a signer cannot change kind for the life of the session.
109
+ */
110
+ private contractSigner;
100
111
  constructor(opts: ServerSessionOptions);
101
112
  /**
102
113
  * Returns a JWT good for at least `REFRESH_LEEWAY_SECONDS` more seconds.
@@ -160,6 +171,20 @@ export declare class ServerSession {
160
171
  * and the store are skipped if a `clearPersisted()` landed meanwhile.
161
172
  */
162
173
  private tryResolveSilently;
174
+ /**
175
+ * How long a pending envelope for THIS signer may be reused, in seconds.
176
+ *
177
+ * The server grants an EOA 120s to make an envelope's first use and a contract
178
+ * wallet 600s, so one local TTL is necessarily wrong for one of them. Deployed
179
+ * code has bytecode; an EOA does not — one `eth_getCode`, memoized, settles it.
180
+ *
181
+ * Reports the CONTRACT window when it cannot tell, which is the behavior that
182
+ * shipped before the split. The asymmetry is intended: guessing "EOA" for a
183
+ * Safe hands it a new message and orphans the owner signatures already
184
+ * collected, while guessing "contract" for an EOA now costs one rejected
185
+ * attempt, because a rejection clears the envelope.
186
+ */
187
+ private pendingEnvelopeTtl;
163
188
  private getOrRefresh;
164
189
  private login;
165
190
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.318",
3
+ "version": "0.0.320",
4
4
  "description": "TypeScript SDK for Diamond Hands Protocol - Bitcoin-backed lending with LIT Protocol PKPs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",