@gvnrdao/dh-sdk 0.0.311 → 0.0.312

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.d.ts CHANGED
@@ -35,6 +35,8 @@ 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 { buildLoginDomain, buildSignedLoginPayload, LOGIN_TYPES, LOGIN_TYPES_WITH_AUDIENCE, } from './utils/eip712-login';
39
+ export type { DhServerLoginMessage, DhServerLoginPayload, } from './utils/eip712-login';
38
40
  export { DEFAULT_LIT_NETWORK, VALID_LIT_NETWORKS, SDK_DEFAULTS, } from './constants/chunks/sdk-config';
39
41
  export { ALL_CONTRACTS, ALL_DEPLOYMENTS, getContractsByNetwork, getDeploymentByNetwork, LOCALHOST_CONTRACTS, SEPOLIA_CONTRACTS, } from './constants/chunks/deployment-addresses';
40
42
  export type { DeploymentContracts, DeploymentData, DeploymentLatestEnv, } from './constants/chunks/deployment-addresses';
package/dist/index.js CHANGED
@@ -5574,6 +5574,8 @@ __export(src_exports, {
5574
5574
  ErrorSeverity: () => ErrorSeverity,
5575
5575
  EventHelpers: () => EventHelpers,
5576
5576
  LOCALHOST_CONTRACTS: () => LOCALHOST_CONTRACTS,
5577
+ LOGIN_TYPES: () => LOGIN_TYPES,
5578
+ LOGIN_TYPES_WITH_AUDIENCE: () => LOGIN_TYPES_WITH_AUDIENCE,
5577
5579
  LRUCache: () => LRUCache,
5578
5580
  LoanCreator: () => LoanCreator,
5579
5581
  LoanQuery: () => LoanQuery,
@@ -5596,6 +5598,8 @@ __export(src_exports, {
5596
5598
  buildBtcExecuteEnvelope: () => buildBtcExecuteEnvelope,
5597
5599
  buildInvalidateMessageHash: () => buildInvalidateMessageHash,
5598
5600
  buildInvalidateStaleSpendEnvelope: () => buildInvalidateStaleSpendEnvelope,
5601
+ buildLoginDomain: () => buildLoginDomain,
5602
+ buildSignedLoginPayload: () => buildSignedLoginPayload,
5599
5603
  collectFailures: () => collectFailures,
5600
5604
  collectSuccesses: () => collectSuccesses,
5601
5605
  combine: () => combine,
@@ -6700,6 +6704,7 @@ function isLoginRejection(error) {
6700
6704
  var ServerSession = class {
6701
6705
  signer;
6702
6706
  serviceEndpoint;
6707
+ loginAudience;
6703
6708
  chainId;
6704
6709
  store;
6705
6710
  onSignaturePrompt;
@@ -6730,6 +6735,10 @@ var ServerSession = class {
6730
6735
  assertSafeServiceEndpoint(opts.serviceEndpoint);
6731
6736
  this.signer = opts.signer;
6732
6737
  this.serviceEndpoint = opts.serviceEndpoint.replace(/\/+$/, "");
6738
+ this.loginAudience = (opts.loginAudience ?? this.serviceEndpoint).replace(
6739
+ /\/+$/,
6740
+ ""
6741
+ );
6733
6742
  this.chainId = opts.chainId;
6734
6743
  this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
6735
6744
  this.onSignaturePrompt = opts.onSignaturePrompt;
@@ -6829,6 +6838,53 @@ var ServerSession = class {
6829
6838
  );
6830
6839
  }
6831
6840
  }
6841
+ /**
6842
+ * Adopt a login envelope signed ELSEWHERE and exchange it for a session now.
6843
+ *
6844
+ * The browser frontend collects one wallet signature and spends it at both the
6845
+ * api and this service, so the envelope is minted outside the SDK. Without this
6846
+ * seam the SDK would mint its own — a second wallet prompt, which for a
6847
+ * multi-sig Safe is another full propose-and-confirm ceremony across owners.
6848
+ *
6849
+ * Must be called PROMPTLY after signing. Each server pins the nonce on FIRST
6850
+ * use and requires that first use to be fresh (±120s EOA, ±600s contract
6851
+ * wallet); the stores are independent, so a late hand-off is rejected as
6852
+ * `stale_first_use` even though the other service already accepted it. After a
6853
+ * successful first use, silent re-mint covers the next 24h.
6854
+ *
6855
+ * No-ops the prompt path entirely: on success the session is cached and
6856
+ * persisted exactly as a self-minted login would be.
6857
+ */
6858
+ adoptLoginPayload(payload) {
6859
+ const gen = this.generation;
6860
+ const notCleared = () => gen === this.generation;
6861
+ const attempt = (async () => {
6862
+ if (payload?.chainId !== this.chainId) {
6863
+ throw new Error(
6864
+ `ServerSession: cannot adopt a login for chain ${payload?.chainId} \u2014 this session is on ${this.chainId}`
6865
+ );
6866
+ }
6867
+ const payloadAddress = payload.message?.address?.toLowerCase();
6868
+ const signerAddress = (await this.signer.getAddress()).toLowerCase();
6869
+ if (!payloadAddress || payloadAddress !== signerAddress) {
6870
+ throw new Error(
6871
+ "ServerSession: cannot adopt a login signed by a different address"
6872
+ );
6873
+ }
6874
+ const session = await this.login(payload);
6875
+ if (notCleared()) {
6876
+ this.persist(payload, session);
6877
+ this.cached = session;
6878
+ this.promptBackoff = null;
6879
+ }
6880
+ return session;
6881
+ })();
6882
+ this.inFlight = attempt;
6883
+ return attempt.then(() => void 0).finally(() => {
6884
+ if (notCleared())
6885
+ this.inFlight = null;
6886
+ });
6887
+ }
6832
6888
  /**
6833
6889
  * Resolve a session WITHOUT ever prompting for a wallet signature: a live
6834
6890
  * cached token, else the persisted JWT (adopted if still fresh, otherwise
@@ -6901,7 +6957,7 @@ var ServerSession = class {
6901
6957
  const payload = await buildSignedLoginPayload(
6902
6958
  this.signer,
6903
6959
  this.chainId,
6904
- this.serviceEndpoint,
6960
+ this.loginAudience,
6905
6961
  reuse
6906
6962
  );
6907
6963
  if (storeKey && !reuse) {
@@ -16545,6 +16601,32 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16545
16601
  await this.serverSession.clearPersisted();
16546
16602
  }
16547
16603
  }
16604
+ /**
16605
+ * Establish the lit-ops-server session from a login envelope the CALLER already
16606
+ * had signed, instead of prompting the wallet for one.
16607
+ *
16608
+ * For a host that authenticates several services from one wallet signature (the
16609
+ * browser frontend signs once and spends the same envelope at the api and here),
16610
+ * this is what turns two wallet prompts into one — and for a multi-sig Safe each
16611
+ * prompt saved is a whole propose-and-confirm ceremony across owners.
16612
+ *
16613
+ * Call it PROMPTLY after signing: each server pins the nonce on first use and
16614
+ * requires that first use to be fresh (±120s EOA, ±600s contract wallet), and
16615
+ * the two servers' replay stores are independent, so a late hand-off is rejected
16616
+ * as stale even though the other service already accepted the same envelope.
16617
+ * Once accepted, silent re-mint covers the next 24h with no further prompts.
16618
+ *
16619
+ * Requires `loginAudience` to be configured to a value BOTH servers accept —
16620
+ * otherwise this service rejects the envelope as `audience_mismatch`.
16621
+ *
16622
+ * Throws if the exchange fails, so a caller can fall back to the prompt path.
16623
+ * No-op in standalone mode (no server session to prime).
16624
+ */
16625
+ async primeServerSession(payload) {
16626
+ if (!this.serverSession)
16627
+ return;
16628
+ await this.serverSession.adoptLoginPayload(payload);
16629
+ }
16548
16630
  /**
16549
16631
  * Audit H-9: invalidate the LoanQuery cache so subsequent reads return
16550
16632
  * the post-write state. We clear the entire loan-query cache (not just
@@ -16656,6 +16738,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16656
16738
  this.serverSession = new ServerSession({
16657
16739
  signer: config.authSigner ?? config.contractSigner,
16658
16740
  serviceEndpoint: config.serviceEndpoint,
16741
+ loginAudience: config.loginAudience,
16659
16742
  chainId: config.chainId,
16660
16743
  persistSession: config.sessionPersistence?.enabled,
16661
16744
  sessionStore: config.sessionPersistence?.store,
@@ -23866,6 +23949,8 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
23866
23949
  ErrorSeverity,
23867
23950
  EventHelpers,
23868
23951
  LOCALHOST_CONTRACTS,
23952
+ LOGIN_TYPES,
23953
+ LOGIN_TYPES_WITH_AUDIENCE,
23869
23954
  LRUCache,
23870
23955
  LoanCreator,
23871
23956
  LoanQuery,
@@ -23888,6 +23973,8 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
23888
23973
  buildBtcExecuteEnvelope,
23889
23974
  buildInvalidateMessageHash,
23890
23975
  buildInvalidateStaleSpendEnvelope,
23976
+ buildLoginDomain,
23977
+ buildSignedLoginPayload,
23891
23978
  collectFailures,
23892
23979
  collectSuccesses,
23893
23980
  combine,
package/dist/index.mjs CHANGED
@@ -6619,6 +6619,7 @@ function isLoginRejection(error) {
6619
6619
  var ServerSession = class {
6620
6620
  signer;
6621
6621
  serviceEndpoint;
6622
+ loginAudience;
6622
6623
  chainId;
6623
6624
  store;
6624
6625
  onSignaturePrompt;
@@ -6649,6 +6650,10 @@ var ServerSession = class {
6649
6650
  assertSafeServiceEndpoint(opts.serviceEndpoint);
6650
6651
  this.signer = opts.signer;
6651
6652
  this.serviceEndpoint = opts.serviceEndpoint.replace(/\/+$/, "");
6653
+ this.loginAudience = (opts.loginAudience ?? this.serviceEndpoint).replace(
6654
+ /\/+$/,
6655
+ ""
6656
+ );
6652
6657
  this.chainId = opts.chainId;
6653
6658
  this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
6654
6659
  this.onSignaturePrompt = opts.onSignaturePrompt;
@@ -6748,6 +6753,53 @@ var ServerSession = class {
6748
6753
  );
6749
6754
  }
6750
6755
  }
6756
+ /**
6757
+ * Adopt a login envelope signed ELSEWHERE and exchange it for a session now.
6758
+ *
6759
+ * The browser frontend collects one wallet signature and spends it at both the
6760
+ * api and this service, so the envelope is minted outside the SDK. Without this
6761
+ * seam the SDK would mint its own — a second wallet prompt, which for a
6762
+ * multi-sig Safe is another full propose-and-confirm ceremony across owners.
6763
+ *
6764
+ * Must be called PROMPTLY after signing. Each server pins the nonce on FIRST
6765
+ * use and requires that first use to be fresh (±120s EOA, ±600s contract
6766
+ * wallet); the stores are independent, so a late hand-off is rejected as
6767
+ * `stale_first_use` even though the other service already accepted it. After a
6768
+ * successful first use, silent re-mint covers the next 24h.
6769
+ *
6770
+ * No-ops the prompt path entirely: on success the session is cached and
6771
+ * persisted exactly as a self-minted login would be.
6772
+ */
6773
+ adoptLoginPayload(payload) {
6774
+ const gen = this.generation;
6775
+ const notCleared = () => gen === this.generation;
6776
+ const attempt = (async () => {
6777
+ if (payload?.chainId !== this.chainId) {
6778
+ throw new Error(
6779
+ `ServerSession: cannot adopt a login for chain ${payload?.chainId} \u2014 this session is on ${this.chainId}`
6780
+ );
6781
+ }
6782
+ const payloadAddress = payload.message?.address?.toLowerCase();
6783
+ const signerAddress = (await this.signer.getAddress()).toLowerCase();
6784
+ if (!payloadAddress || payloadAddress !== signerAddress) {
6785
+ throw new Error(
6786
+ "ServerSession: cannot adopt a login signed by a different address"
6787
+ );
6788
+ }
6789
+ const session = await this.login(payload);
6790
+ if (notCleared()) {
6791
+ this.persist(payload, session);
6792
+ this.cached = session;
6793
+ this.promptBackoff = null;
6794
+ }
6795
+ return session;
6796
+ })();
6797
+ this.inFlight = attempt;
6798
+ return attempt.then(() => void 0).finally(() => {
6799
+ if (notCleared())
6800
+ this.inFlight = null;
6801
+ });
6802
+ }
6751
6803
  /**
6752
6804
  * Resolve a session WITHOUT ever prompting for a wallet signature: a live
6753
6805
  * cached token, else the persisted JWT (adopted if still fresh, otherwise
@@ -6820,7 +6872,7 @@ var ServerSession = class {
6820
6872
  const payload = await buildSignedLoginPayload(
6821
6873
  this.signer,
6822
6874
  this.chainId,
6823
- this.serviceEndpoint,
6875
+ this.loginAudience,
6824
6876
  reuse
6825
6877
  );
6826
6878
  if (storeKey && !reuse) {
@@ -16474,6 +16526,32 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16474
16526
  await this.serverSession.clearPersisted();
16475
16527
  }
16476
16528
  }
16529
+ /**
16530
+ * Establish the lit-ops-server session from a login envelope the CALLER already
16531
+ * had signed, instead of prompting the wallet for one.
16532
+ *
16533
+ * For a host that authenticates several services from one wallet signature (the
16534
+ * browser frontend signs once and spends the same envelope at the api and here),
16535
+ * this is what turns two wallet prompts into one — and for a multi-sig Safe each
16536
+ * prompt saved is a whole propose-and-confirm ceremony across owners.
16537
+ *
16538
+ * Call it PROMPTLY after signing: each server pins the nonce on first use and
16539
+ * requires that first use to be fresh (±120s EOA, ±600s contract wallet), and
16540
+ * the two servers' replay stores are independent, so a late hand-off is rejected
16541
+ * as stale even though the other service already accepted the same envelope.
16542
+ * Once accepted, silent re-mint covers the next 24h with no further prompts.
16543
+ *
16544
+ * Requires `loginAudience` to be configured to a value BOTH servers accept —
16545
+ * otherwise this service rejects the envelope as `audience_mismatch`.
16546
+ *
16547
+ * Throws if the exchange fails, so a caller can fall back to the prompt path.
16548
+ * No-op in standalone mode (no server session to prime).
16549
+ */
16550
+ async primeServerSession(payload) {
16551
+ if (!this.serverSession)
16552
+ return;
16553
+ await this.serverSession.adoptLoginPayload(payload);
16554
+ }
16477
16555
  /**
16478
16556
  * Audit H-9: invalidate the LoanQuery cache so subsequent reads return
16479
16557
  * the post-write state. We clear the entire loan-query cache (not just
@@ -16585,6 +16663,7 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16585
16663
  this.serverSession = new ServerSession({
16586
16664
  signer: config.authSigner ?? config.contractSigner,
16587
16665
  serviceEndpoint: config.serviceEndpoint,
16666
+ loginAudience: config.loginAudience,
16588
16667
  chainId: config.chainId,
16589
16668
  persistSession: config.sessionPersistence?.enabled,
16590
16669
  sessionStore: config.sessionPersistence?.store,
@@ -23794,6 +23873,8 @@ export {
23794
23873
  ErrorSeverity,
23795
23874
  EventHelpers,
23796
23875
  LOCALHOST_CONTRACTS,
23876
+ LOGIN_TYPES,
23877
+ LOGIN_TYPES_WITH_AUDIENCE,
23797
23878
  LRUCache,
23798
23879
  LoanCreator,
23799
23880
  LoanQuery,
@@ -23816,6 +23897,8 @@ export {
23816
23897
  buildBtcExecuteEnvelope,
23817
23898
  buildInvalidateMessageHash,
23818
23899
  buildInvalidateStaleSpendEnvelope,
23900
+ buildLoginDomain,
23901
+ buildSignedLoginPayload,
23819
23902
  collectFailures,
23820
23903
  collectSuccesses,
23821
23904
  combine,
@@ -71,6 +71,19 @@ interface BaseSDKConfig {
71
71
  /** Custom store (e.g. a file-backed store for CLI daemons). */
72
72
  store?: ServerSessionStore;
73
73
  };
74
+ /**
75
+ * Audit M-8 audience the server-session login binds to. Defaults to
76
+ * `serviceEndpoint`.
77
+ *
78
+ * Set this only when ONE signature must authenticate several services — the
79
+ * browser frontend signs a single envelope and spends it at both
80
+ * lit-ops-server and the api, and a per-endpoint audience cannot name two
81
+ * endpoints. Every server the envelope is presented to must list this value as
82
+ * an accepted audience, so changing it requires a server-side config change
83
+ * FIRST. Node callers (CLI, MCP, cr-monitor) omit it and keep binding to the
84
+ * endpoint they call.
85
+ */
86
+ loginAudience?: string;
74
87
  /**
75
88
  * Fires immediately before the server-session login requests a wallet
76
89
  * signature — never on the silent re-mint paths. Lets UIs show a
@@ -18,6 +18,7 @@ import { SDKError } from "../utils/error-handler";
18
18
  import type { CreateLoanRequest, CreateLoanResult, LoanDataDetail, UCDMintRequest, UCDMintResult, PartialPaymentRequest, PartialPaymentResult, BTCWithdrawalResult, RenewPositionRequest, RenewPositionResult, LiquidationRequest, LiquidationResult, ConfirmBalanceRequest, ConfirmBalanceResult, TermsWithFeesResult } from "../interfaces/chunks/loan-operations.i";
19
19
  import type { DiamondHandsSDKConfig } from "../interfaces/chunks/config.i";
20
20
  import type { PKPData } from "../interfaces/chunks/pkp-integration.i";
21
+ import type { DhServerLoginPayload } from "../utils/eip712-login";
21
22
  import { type ReconciledWithdrawal } from "../utils/withdrawal-reconciliation.utils";
22
23
  import { ContractManager } from "./contract/contract-manager.module";
23
24
  import { WithdrawalAddressModule } from "./withdrawal-address/withdrawal-address.module";
@@ -125,6 +126,28 @@ export declare class DiamondHandsSDK {
125
126
  * and never prompts the wallet.
126
127
  */
127
128
  clearServerSession(): Promise<void>;
129
+ /**
130
+ * Establish the lit-ops-server session from a login envelope the CALLER already
131
+ * had signed, instead of prompting the wallet for one.
132
+ *
133
+ * For a host that authenticates several services from one wallet signature (the
134
+ * browser frontend signs once and spends the same envelope at the api and here),
135
+ * this is what turns two wallet prompts into one — and for a multi-sig Safe each
136
+ * prompt saved is a whole propose-and-confirm ceremony across owners.
137
+ *
138
+ * Call it PROMPTLY after signing: each server pins the nonce on first use and
139
+ * requires that first use to be fresh (±120s EOA, ±600s contract wallet), and
140
+ * the two servers' replay stores are independent, so a late hand-off is rejected
141
+ * as stale even though the other service already accepted the same envelope.
142
+ * Once accepted, silent re-mint covers the next 24h with no further prompts.
143
+ *
144
+ * Requires `loginAudience` to be configured to a value BOTH servers accept —
145
+ * otherwise this service rejects the envelope as `audience_mismatch`.
146
+ *
147
+ * Throws if the exchange fails, so a caller can fall back to the prompt path.
148
+ * No-op in standalone mode (no server session to prime).
149
+ */
150
+ primeServerSession(payload: DhServerLoginPayload): Promise<void>;
128
151
  /**
129
152
  * Audit H-9: invalidate the LoanQuery cache so subsequent reads return
130
153
  * the post-write state. We clear the entire loan-query cache (not just
@@ -22,6 +22,7 @@
22
22
  * `~/.diamond-hands/session.json` for cross-invocation reuse.
23
23
  */
24
24
  import type { Signer } from "ethers";
25
+ import { type DhServerLoginPayload } from "./eip712-login";
25
26
  import { type ServerSessionStore } from "./server-session-store";
26
27
  export interface ServerSessionOptions {
27
28
  signer: Signer;
@@ -34,6 +35,16 @@ export interface ServerSessionOptions {
34
35
  sessionStore?: ServerSessionStore;
35
36
  /** Set false to disable persistence entirely (fresh signature per instance + expiry). */
36
37
  persistSession?: boolean;
38
+ /**
39
+ * Audit M-8 audience to bind the login to. Defaults to `serviceEndpoint`.
40
+ *
41
+ * A caller that shares ONE signature across several services (the browser
42
+ * frontend, which authenticates both lit-ops-server and the api from a single
43
+ * wallet prompt) cannot bind to a single endpoint, so it passes the app audience
44
+ * instead — and every server it talks to must accept that value. Node callers
45
+ * (CLI, MCP, cr-monitor) omit it and keep the endpoint binding unchanged.
46
+ */
47
+ loginAudience?: string;
37
48
  /**
38
49
  * Fires immediately before a wallet signature is requested — i.e. only when
39
50
  * neither the cached JWT nor the persisted envelope could renew the session,
@@ -59,6 +70,7 @@ export declare class ServerLoginError extends Error {
59
70
  export declare class ServerSession {
60
71
  private readonly signer;
61
72
  private readonly serviceEndpoint;
73
+ private readonly loginAudience;
62
74
  private readonly chainId;
63
75
  private readonly store;
64
76
  private readonly onSignaturePrompt?;
@@ -118,6 +130,24 @@ export declare class ServerSession {
118
130
  * of server response so the client stops presenting the token.
119
131
  */
120
132
  logout(): Promise<void>;
133
+ /**
134
+ * Adopt a login envelope signed ELSEWHERE and exchange it for a session now.
135
+ *
136
+ * The browser frontend collects one wallet signature and spends it at both the
137
+ * api and this service, so the envelope is minted outside the SDK. Without this
138
+ * seam the SDK would mint its own — a second wallet prompt, which for a
139
+ * multi-sig Safe is another full propose-and-confirm ceremony across owners.
140
+ *
141
+ * Must be called PROMPTLY after signing. Each server pins the nonce on FIRST
142
+ * use and requires that first use to be fresh (±120s EOA, ±600s contract
143
+ * wallet); the stores are independent, so a late hand-off is rejected as
144
+ * `stale_first_use` even though the other service already accepted it. After a
145
+ * successful first use, silent re-mint covers the next 24h.
146
+ *
147
+ * No-ops the prompt path entirely: on success the session is cached and
148
+ * persisted exactly as a self-minted login would be.
149
+ */
150
+ adoptLoginPayload(payload: DhServerLoginPayload): Promise<void>;
121
151
  /**
122
152
  * Resolve a session WITHOUT ever prompting for a wallet signature: a live
123
153
  * cached token, else the persisted JWT (adopted if still fresh, otherwise
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.311",
3
+ "version": "0.0.312",
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",