@gvnrdao/dh-sdk 0.0.308 → 0.0.311

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.js CHANGED
@@ -6507,7 +6507,7 @@ async function generateExtendAuthorization(positionId, newTerm, chainId, signerO
6507
6507
  var import_ethers4 = require("ethers");
6508
6508
  function buildLoginDomain(chainId) {
6509
6509
  return {
6510
- name: "Diamond Hands lit-ops-server",
6510
+ name: "Diamond Hands",
6511
6511
  version: "1",
6512
6512
  chainId
6513
6513
  };
@@ -6527,12 +6527,12 @@ var LOGIN_TYPES_WITH_AUDIENCE = {
6527
6527
  { name: "audience", type: "string" }
6528
6528
  ]
6529
6529
  };
6530
- async function buildSignedLoginPayload(signer, chainId, audience) {
6530
+ async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
6531
6531
  const address = await signer.getAddress();
6532
6532
  const message = {
6533
6533
  address,
6534
- issuedAt: Math.floor(Date.now() / 1e3),
6535
- nonce: (0, import_ethers4.hexlify)((0, import_ethers4.randomBytes)(32)),
6534
+ issuedAt: reuse?.issuedAt ?? Math.floor(Date.now() / 1e3),
6535
+ nonce: reuse?.nonce ?? (0, import_ethers4.hexlify)((0, import_ethers4.randomBytes)(32)),
6536
6536
  ...audience ? { audience } : {}
6537
6537
  };
6538
6538
  const signature = await signer.signTypedData(
@@ -6550,6 +6550,13 @@ var STORE_PREFIX = "dh-server-session:";
6550
6550
  function sessionStoreKey(address, chainId, endpoint) {
6551
6551
  return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
6552
6552
  }
6553
+ var PENDING_ENVELOPE_TTL_SECONDS = 8 * 60;
6554
+ function pendingEnvelopeKey(sessionKey) {
6555
+ return sessionKey + ":pending";
6556
+ }
6557
+ function isPendingEnvelopeShape(value) {
6558
+ return !!value && typeof value === "object" && typeof value.issuedAt === "number" && typeof value.nonce === "string";
6559
+ }
6553
6560
  function isPayloadShape(value) {
6554
6561
  return !!value && typeof value === "object" && typeof value.chainId === "number" && typeof value.signature === "string" && !!value.message && typeof value.message.address === "string" && typeof value.message.issuedAt === "number" && typeof value.message.nonce === "string";
6555
6562
  }
@@ -6591,9 +6598,48 @@ var LocalStorageSessionStore = class {
6591
6598
  } catch {
6592
6599
  }
6593
6600
  }
6601
+ loadPending(key) {
6602
+ let raw;
6603
+ try {
6604
+ raw = window.localStorage.getItem(pendingEnvelopeKey(key));
6605
+ } catch {
6606
+ return null;
6607
+ }
6608
+ if (!raw)
6609
+ return null;
6610
+ let parsed;
6611
+ try {
6612
+ parsed = JSON.parse(raw);
6613
+ } catch {
6614
+ this.clearPending(key);
6615
+ return null;
6616
+ }
6617
+ const nowSec = Math.floor(Date.now() / 1e3);
6618
+ if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
6619
+ this.clearPending(key);
6620
+ return null;
6621
+ }
6622
+ return parsed;
6623
+ }
6624
+ savePending(key, envelope) {
6625
+ try {
6626
+ window.localStorage.setItem(
6627
+ pendingEnvelopeKey(key),
6628
+ JSON.stringify(envelope)
6629
+ );
6630
+ } catch {
6631
+ }
6632
+ }
6633
+ clearPending(key) {
6634
+ try {
6635
+ window.localStorage.removeItem(pendingEnvelopeKey(key));
6636
+ } catch {
6637
+ }
6638
+ }
6594
6639
  };
6595
6640
  var MemorySessionStore = class {
6596
6641
  entries = /* @__PURE__ */ new Map();
6642
+ pending = /* @__PURE__ */ new Map();
6597
6643
  load(key) {
6598
6644
  return this.entries.get(key) ?? null;
6599
6645
  }
@@ -6603,6 +6649,23 @@ var MemorySessionStore = class {
6603
6649
  clear(key) {
6604
6650
  this.entries.delete(key);
6605
6651
  }
6652
+ loadPending(key) {
6653
+ const envelope = this.pending.get(key);
6654
+ if (!envelope)
6655
+ return null;
6656
+ const nowSec = Math.floor(Date.now() / 1e3);
6657
+ if (nowSec >= envelope.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
6658
+ this.pending.delete(key);
6659
+ return null;
6660
+ }
6661
+ return envelope;
6662
+ }
6663
+ savePending(key, envelope) {
6664
+ this.pending.set(key, envelope);
6665
+ }
6666
+ clearPending(key) {
6667
+ this.pending.delete(key);
6668
+ }
6606
6669
  };
6607
6670
  function localStorageUsable() {
6608
6671
  try {
@@ -6622,6 +6685,8 @@ function createDefaultSessionStore() {
6622
6685
 
6623
6686
  // src/utils/server-session.ts
6624
6687
  var REFRESH_LEEWAY_SECONDS = 30;
6688
+ var LOGIN_RETRY_BASE_MS = 3e4;
6689
+ var LOGIN_RETRY_MAX_MS = 5 * 6e4;
6625
6690
  var ServerLoginError = class extends Error {
6626
6691
  constructor(message, status) {
6627
6692
  super(message);
@@ -6638,10 +6703,16 @@ var ServerSession = class {
6638
6703
  chainId;
6639
6704
  store;
6640
6705
  onSignaturePrompt;
6706
+ onSignatureResolved;
6641
6707
  now;
6642
6708
  fetchImpl;
6643
6709
  cached = null;
6644
6710
  inFlight = null;
6711
+ /**
6712
+ * Set when a prompted login failed; suppresses further prompts until `until`.
6713
+ * `error` is replayed to callers so they still see the real failure reason.
6714
+ */
6715
+ promptBackoff = null;
6645
6716
  /**
6646
6717
  * Bumped by `clearPersisted()` (logout / disconnect). An in-flight
6647
6718
  * `getOrRefresh()` captures this at start and refuses to write its freshly
@@ -6662,6 +6733,7 @@ var ServerSession = class {
6662
6733
  this.chainId = opts.chainId;
6663
6734
  this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
6664
6735
  this.onSignaturePrompt = opts.onSignaturePrompt;
6736
+ this.onSignatureResolved = opts.onSignatureResolved;
6665
6737
  this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
6666
6738
  this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
6667
6739
  }
@@ -6716,6 +6788,7 @@ var ServerSession = class {
6716
6788
  this.generation += 1;
6717
6789
  this.cached = null;
6718
6790
  this.inFlight = null;
6791
+ this.promptBackoff = null;
6719
6792
  if (!this.store)
6720
6793
  return;
6721
6794
  try {
@@ -6812,19 +6885,53 @@ var ServerSession = class {
6812
6885
  this.inFlight = (async () => {
6813
6886
  try {
6814
6887
  const existing = await this.tryResolveSilently(gen);
6815
- if (existing)
6888
+ if (existing) {
6889
+ this.promptBackoff = null;
6816
6890
  return existing;
6891
+ }
6892
+ if (this.promptBackoff && this.now() * 1e3 < this.promptBackoff.until) {
6893
+ throw this.promptBackoff.error;
6894
+ }
6817
6895
  this.onSignaturePrompt?.();
6818
- const payload = await buildSignedLoginPayload(
6819
- this.signer,
6820
- this.chainId,
6821
- this.serviceEndpoint
6822
- );
6823
- const session = await this.login(payload);
6824
- if (notCleared()) {
6825
- this.persist(payload, session);
6826
- this.cached = session;
6896
+ let session;
6897
+ try {
6898
+ const store = this.store;
6899
+ const storeKey = store ? this.lastStoreKey : null;
6900
+ const reuse = storeKey ? store.loadPending?.(storeKey) ?? void 0 : void 0;
6901
+ const payload = await buildSignedLoginPayload(
6902
+ this.signer,
6903
+ this.chainId,
6904
+ this.serviceEndpoint,
6905
+ reuse
6906
+ );
6907
+ if (storeKey && !reuse) {
6908
+ store.savePending?.(storeKey, {
6909
+ issuedAt: payload.message.issuedAt,
6910
+ nonce: payload.message.nonce
6911
+ });
6912
+ }
6913
+ session = await this.login(payload);
6914
+ if (notCleared()) {
6915
+ this.persist(payload, session);
6916
+ this.cached = session;
6917
+ }
6918
+ if (storeKey)
6919
+ store.clearPending?.(storeKey);
6920
+ this.onSignatureResolved?.(true);
6921
+ } catch (error) {
6922
+ this.onSignatureResolved?.(false);
6923
+ const failures = (this.promptBackoff?.failures ?? 0) + 1;
6924
+ this.promptBackoff = {
6925
+ failures,
6926
+ error,
6927
+ until: this.now() * 1e3 + Math.min(
6928
+ LOGIN_RETRY_BASE_MS * 2 ** (failures - 1),
6929
+ LOGIN_RETRY_MAX_MS
6930
+ )
6931
+ };
6932
+ throw error;
6827
6933
  }
6934
+ this.promptBackoff = null;
6828
6935
  return session;
6829
6936
  } finally {
6830
6937
  if (notCleared())
@@ -16552,7 +16659,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16552
16659
  chainId: config.chainId,
16553
16660
  persistSession: config.sessionPersistence?.enabled,
16554
16661
  sessionStore: config.sessionPersistence?.store,
16555
- onSignaturePrompt: config.onSessionSignaturePrompt
16662
+ onSignaturePrompt: config.onSessionSignaturePrompt,
16663
+ onSignatureResolved: config.onSessionSignatureResolved
16556
16664
  });
16557
16665
  }
16558
16666
  const contractManagerResult = createContractManager({
package/dist/index.mjs CHANGED
@@ -6426,7 +6426,7 @@ async function generateExtendAuthorization(positionId, newTerm, chainId, signerO
6426
6426
  import { hexlify, randomBytes } from "ethers";
6427
6427
  function buildLoginDomain(chainId) {
6428
6428
  return {
6429
- name: "Diamond Hands lit-ops-server",
6429
+ name: "Diamond Hands",
6430
6430
  version: "1",
6431
6431
  chainId
6432
6432
  };
@@ -6446,12 +6446,12 @@ var LOGIN_TYPES_WITH_AUDIENCE = {
6446
6446
  { name: "audience", type: "string" }
6447
6447
  ]
6448
6448
  };
6449
- async function buildSignedLoginPayload(signer, chainId, audience) {
6449
+ async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
6450
6450
  const address = await signer.getAddress();
6451
6451
  const message = {
6452
6452
  address,
6453
- issuedAt: Math.floor(Date.now() / 1e3),
6454
- nonce: hexlify(randomBytes(32)),
6453
+ issuedAt: reuse?.issuedAt ?? Math.floor(Date.now() / 1e3),
6454
+ nonce: reuse?.nonce ?? hexlify(randomBytes(32)),
6455
6455
  ...audience ? { audience } : {}
6456
6456
  };
6457
6457
  const signature = await signer.signTypedData(
@@ -6469,6 +6469,13 @@ var STORE_PREFIX = "dh-server-session:";
6469
6469
  function sessionStoreKey(address, chainId, endpoint) {
6470
6470
  return STORE_PREFIX + `${address.trim().toLowerCase()}:${chainId}:${endpoint.replace(/\/+$/, "")}`;
6471
6471
  }
6472
+ var PENDING_ENVELOPE_TTL_SECONDS = 8 * 60;
6473
+ function pendingEnvelopeKey(sessionKey) {
6474
+ return sessionKey + ":pending";
6475
+ }
6476
+ function isPendingEnvelopeShape(value) {
6477
+ return !!value && typeof value === "object" && typeof value.issuedAt === "number" && typeof value.nonce === "string";
6478
+ }
6472
6479
  function isPayloadShape(value) {
6473
6480
  return !!value && typeof value === "object" && typeof value.chainId === "number" && typeof value.signature === "string" && !!value.message && typeof value.message.address === "string" && typeof value.message.issuedAt === "number" && typeof value.message.nonce === "string";
6474
6481
  }
@@ -6510,9 +6517,48 @@ var LocalStorageSessionStore = class {
6510
6517
  } catch {
6511
6518
  }
6512
6519
  }
6520
+ loadPending(key) {
6521
+ let raw;
6522
+ try {
6523
+ raw = window.localStorage.getItem(pendingEnvelopeKey(key));
6524
+ } catch {
6525
+ return null;
6526
+ }
6527
+ if (!raw)
6528
+ return null;
6529
+ let parsed;
6530
+ try {
6531
+ parsed = JSON.parse(raw);
6532
+ } catch {
6533
+ this.clearPending(key);
6534
+ return null;
6535
+ }
6536
+ const nowSec = Math.floor(Date.now() / 1e3);
6537
+ if (!isPendingEnvelopeShape(parsed) || nowSec >= parsed.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
6538
+ this.clearPending(key);
6539
+ return null;
6540
+ }
6541
+ return parsed;
6542
+ }
6543
+ savePending(key, envelope) {
6544
+ try {
6545
+ window.localStorage.setItem(
6546
+ pendingEnvelopeKey(key),
6547
+ JSON.stringify(envelope)
6548
+ );
6549
+ } catch {
6550
+ }
6551
+ }
6552
+ clearPending(key) {
6553
+ try {
6554
+ window.localStorage.removeItem(pendingEnvelopeKey(key));
6555
+ } catch {
6556
+ }
6557
+ }
6513
6558
  };
6514
6559
  var MemorySessionStore = class {
6515
6560
  entries = /* @__PURE__ */ new Map();
6561
+ pending = /* @__PURE__ */ new Map();
6516
6562
  load(key) {
6517
6563
  return this.entries.get(key) ?? null;
6518
6564
  }
@@ -6522,6 +6568,23 @@ var MemorySessionStore = class {
6522
6568
  clear(key) {
6523
6569
  this.entries.delete(key);
6524
6570
  }
6571
+ loadPending(key) {
6572
+ const envelope = this.pending.get(key);
6573
+ if (!envelope)
6574
+ return null;
6575
+ const nowSec = Math.floor(Date.now() / 1e3);
6576
+ if (nowSec >= envelope.issuedAt + PENDING_ENVELOPE_TTL_SECONDS) {
6577
+ this.pending.delete(key);
6578
+ return null;
6579
+ }
6580
+ return envelope;
6581
+ }
6582
+ savePending(key, envelope) {
6583
+ this.pending.set(key, envelope);
6584
+ }
6585
+ clearPending(key) {
6586
+ this.pending.delete(key);
6587
+ }
6525
6588
  };
6526
6589
  function localStorageUsable() {
6527
6590
  try {
@@ -6541,6 +6604,8 @@ function createDefaultSessionStore() {
6541
6604
 
6542
6605
  // src/utils/server-session.ts
6543
6606
  var REFRESH_LEEWAY_SECONDS = 30;
6607
+ var LOGIN_RETRY_BASE_MS = 3e4;
6608
+ var LOGIN_RETRY_MAX_MS = 5 * 6e4;
6544
6609
  var ServerLoginError = class extends Error {
6545
6610
  constructor(message, status) {
6546
6611
  super(message);
@@ -6557,10 +6622,16 @@ var ServerSession = class {
6557
6622
  chainId;
6558
6623
  store;
6559
6624
  onSignaturePrompt;
6625
+ onSignatureResolved;
6560
6626
  now;
6561
6627
  fetchImpl;
6562
6628
  cached = null;
6563
6629
  inFlight = null;
6630
+ /**
6631
+ * Set when a prompted login failed; suppresses further prompts until `until`.
6632
+ * `error` is replayed to callers so they still see the real failure reason.
6633
+ */
6634
+ promptBackoff = null;
6564
6635
  /**
6565
6636
  * Bumped by `clearPersisted()` (logout / disconnect). An in-flight
6566
6637
  * `getOrRefresh()` captures this at start and refuses to write its freshly
@@ -6581,6 +6652,7 @@ var ServerSession = class {
6581
6652
  this.chainId = opts.chainId;
6582
6653
  this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
6583
6654
  this.onSignaturePrompt = opts.onSignaturePrompt;
6655
+ this.onSignatureResolved = opts.onSignatureResolved;
6584
6656
  this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
6585
6657
  this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
6586
6658
  }
@@ -6635,6 +6707,7 @@ var ServerSession = class {
6635
6707
  this.generation += 1;
6636
6708
  this.cached = null;
6637
6709
  this.inFlight = null;
6710
+ this.promptBackoff = null;
6638
6711
  if (!this.store)
6639
6712
  return;
6640
6713
  try {
@@ -6731,19 +6804,53 @@ var ServerSession = class {
6731
6804
  this.inFlight = (async () => {
6732
6805
  try {
6733
6806
  const existing = await this.tryResolveSilently(gen);
6734
- if (existing)
6807
+ if (existing) {
6808
+ this.promptBackoff = null;
6735
6809
  return existing;
6810
+ }
6811
+ if (this.promptBackoff && this.now() * 1e3 < this.promptBackoff.until) {
6812
+ throw this.promptBackoff.error;
6813
+ }
6736
6814
  this.onSignaturePrompt?.();
6737
- const payload = await buildSignedLoginPayload(
6738
- this.signer,
6739
- this.chainId,
6740
- this.serviceEndpoint
6741
- );
6742
- const session = await this.login(payload);
6743
- if (notCleared()) {
6744
- this.persist(payload, session);
6745
- this.cached = session;
6815
+ let session;
6816
+ try {
6817
+ const store = this.store;
6818
+ const storeKey = store ? this.lastStoreKey : null;
6819
+ const reuse = storeKey ? store.loadPending?.(storeKey) ?? void 0 : void 0;
6820
+ const payload = await buildSignedLoginPayload(
6821
+ this.signer,
6822
+ this.chainId,
6823
+ this.serviceEndpoint,
6824
+ reuse
6825
+ );
6826
+ if (storeKey && !reuse) {
6827
+ store.savePending?.(storeKey, {
6828
+ issuedAt: payload.message.issuedAt,
6829
+ nonce: payload.message.nonce
6830
+ });
6831
+ }
6832
+ session = await this.login(payload);
6833
+ if (notCleared()) {
6834
+ this.persist(payload, session);
6835
+ this.cached = session;
6836
+ }
6837
+ if (storeKey)
6838
+ store.clearPending?.(storeKey);
6839
+ this.onSignatureResolved?.(true);
6840
+ } catch (error) {
6841
+ this.onSignatureResolved?.(false);
6842
+ const failures = (this.promptBackoff?.failures ?? 0) + 1;
6843
+ this.promptBackoff = {
6844
+ failures,
6845
+ error,
6846
+ until: this.now() * 1e3 + Math.min(
6847
+ LOGIN_RETRY_BASE_MS * 2 ** (failures - 1),
6848
+ LOGIN_RETRY_MAX_MS
6849
+ )
6850
+ };
6851
+ throw error;
6746
6852
  }
6853
+ this.promptBackoff = null;
6747
6854
  return session;
6748
6855
  } finally {
6749
6856
  if (notCleared())
@@ -16481,7 +16588,8 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16481
16588
  chainId: config.chainId,
16482
16589
  persistSession: config.sessionPersistence?.enabled,
16483
16590
  sessionStore: config.sessionPersistence?.store,
16484
- onSignaturePrompt: config.onSessionSignaturePrompt
16591
+ onSignaturePrompt: config.onSessionSignaturePrompt,
16592
+ onSignatureResolved: config.onSessionSignatureResolved
16485
16593
  });
16486
16594
  }
16487
16595
  const contractManagerResult = createContractManager({
@@ -77,6 +77,14 @@ interface BaseSDKConfig {
77
77
  * "check your wallet" prompt.
78
78
  */
79
79
  onSessionSignaturePrompt?: () => void;
80
+ /**
81
+ * Fires once that signature request has concluded — `true` when a session was
82
+ * established, `false` when it failed or was rejected. Pairs with
83
+ * `onSessionSignaturePrompt`, which otherwise has no ending: a UI that shows
84
+ * "check your wallet" on the prompt has no way to stop showing it, and a
85
+ * contract wallet can leave that request open for minutes.
86
+ */
87
+ onSessionSignatureResolved?: (ok: boolean) => void;
80
88
  ethRpcUrl?: string;
81
89
  chainId?: number;
82
90
  networkOverride?: {
@@ -31,4 +31,16 @@ export declare const LOGIN_TYPES_WITH_AUDIENCE: Record<string, TypedDataField[]>
31
31
  * Audit M-8: pass `audience` (the service endpoint URL being logged into) to bind the login to
32
32
  * one service. Omit it for legacy compatibility during the migration window.
33
33
  */
34
- export declare function buildSignedLoginPayload(signer: Signer, chainId: number, audience?: string): Promise<DhServerLoginPayload>;
34
+ export declare function buildSignedLoginPayload(signer: Signer, chainId: number, audience?: string,
35
+ /**
36
+ * Reuse the envelope of a login already collecting signatures instead of
37
+ * minting a fresh one. Every field is hashed into the EIP-712 message, so a
38
+ * new nonce produces a new hash — which a multi-owner Safe shows as a new
39
+ * message needing its FIRST signature, discarding the ones already collected.
40
+ * Passing the pending envelope back reproduces the same hash, so the next
41
+ * owner adds to it. See `server-session-store.ts`.
42
+ */
43
+ reuse?: {
44
+ issuedAt: number;
45
+ nonce: string;
46
+ }): Promise<DhServerLoginPayload>;
@@ -39,18 +39,62 @@ export interface ServerSessionStore {
39
39
  load(key: string): PersistedServerSession | null;
40
40
  save(key: string, session: PersistedServerSession): void;
41
41
  clear(key: string): void;
42
+ /**
43
+ * Optional pending-envelope persistence (see `PendingLoginEnvelope`). Optional
44
+ * so a custom store written before this existed still satisfies the interface;
45
+ * without it the session simply mints a fresh envelope each attempt, which is
46
+ * the pre-existing behavior and only costs a multi-owner Safe a restart.
47
+ */
48
+ loadPending?(key: string): PendingLoginEnvelope | null;
49
+ savePending?(key: string, envelope: PendingLoginEnvelope): void;
50
+ clearPending?(key: string): void;
42
51
  }
43
52
  export declare function sessionStoreKey(address: string, chainId: number, endpoint: string): string;
53
+ /**
54
+ * The UNSIGNED envelope of a login already collecting signatures.
55
+ *
56
+ * A multi-owner Safe signs across sessions — owner 1 now, owner 2 later — and
57
+ * identifies the pending message solely by its EIP-712 hash. Every field is
58
+ * hashed, so minting a fresh `nonce`/`issuedAt` for the next attempt yields a
59
+ * DIFFERENT hash, which Safe presents as a new message needing its FIRST
60
+ * signature. Whatever owner 1 signed is orphaned, and a 2-of-N Safe can never
61
+ * reach threshold: every retry resets the count to zero.
62
+ *
63
+ * Reusing the envelope makes the re-request hash identically, so Safe shows the
64
+ * same pending message and the next owner ADDS to it. Invisible for an EOA,
65
+ * which signs immediately and spends the envelope.
66
+ */
67
+ export interface PendingLoginEnvelope {
68
+ issuedAt: number;
69
+ nonce: string;
70
+ }
71
+ /**
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
+ */
77
+ export declare const PENDING_ENVELOPE_TTL_SECONDS: number;
78
+ /** Storage key for the pending envelope matching a session key. */
79
+ export declare function pendingEnvelopeKey(sessionKey: string): string;
80
+ export declare function isPendingEnvelopeShape(value: any): value is PendingLoginEnvelope;
44
81
  export declare class LocalStorageSessionStore implements ServerSessionStore {
45
82
  load(key: string): PersistedServerSession | null;
46
83
  save(key: string, session: PersistedServerSession): void;
47
84
  clear(key: string): void;
85
+ loadPending(key: string): PendingLoginEnvelope | null;
86
+ savePending(key: string, envelope: PendingLoginEnvelope): void;
87
+ clearPending(key: string): void;
48
88
  }
49
89
  export declare class MemorySessionStore implements ServerSessionStore {
50
90
  private readonly entries;
91
+ private readonly pending;
51
92
  load(key: string): PersistedServerSession | null;
52
93
  save(key: string, session: PersistedServerSession): void;
53
94
  clear(key: string): void;
95
+ loadPending(key: string): PendingLoginEnvelope | null;
96
+ savePending(key: string, envelope: PendingLoginEnvelope): void;
97
+ clearPending(key: string): void;
54
98
  }
55
99
  /** `localStorage` when usable (browser, not blocked), else per-instance memory. */
56
100
  export declare function createDefaultSessionStore(): ServerSessionStore;
@@ -40,6 +40,12 @@ export interface ServerSessionOptions {
40
40
  * never on the silent paths. Lets UIs show a "check your wallet" prompt.
41
41
  */
42
42
  onSignaturePrompt?: () => void;
43
+ /**
44
+ * Fires once the prompted signature has concluded, with whether a session was
45
+ * established. Without it `onSignaturePrompt` has no ending, so a UI showing
46
+ * "check your wallet" can never take it down.
47
+ */
48
+ onSignatureResolved?: (ok: boolean) => void;
43
49
  /** Override for tests. */
44
50
  now?: () => number;
45
51
  /** Override for tests. */
@@ -56,10 +62,16 @@ export declare class ServerSession {
56
62
  private readonly chainId;
57
63
  private readonly store;
58
64
  private readonly onSignaturePrompt?;
65
+ private readonly onSignatureResolved?;
59
66
  private readonly now;
60
67
  private readonly fetchImpl;
61
68
  private cached;
62
69
  private inFlight;
70
+ /**
71
+ * Set when a prompted login failed; suppresses further prompts until `until`.
72
+ * `error` is replayed to callers so they still see the real failure reason.
73
+ */
74
+ private promptBackoff;
63
75
  /**
64
76
  * Bumped by `clearPersisted()` (logout / disconnect). An in-flight
65
77
  * `getOrRefresh()` captures this at start and refuses to write its freshly
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.308",
3
+ "version": "0.0.311",
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",