@gvnrdao/dh-sdk 0.0.301 → 0.0.304

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.
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import type { Wallet, Signer, Provider } from "ethers";
5
5
  import { LitNetwork } from "@gvnrdao/dh-lit-ops";
6
+ import type { ServerSessionStore } from "../../utils/server-session-store";
6
7
  export type SDKMode = "standalone" | "service";
7
8
  /**
8
9
  * Contract Addresses Configuration
@@ -55,6 +56,27 @@ interface BaseSDKConfig {
55
56
  * is a SafeModuleSignerAdapter that routes transactions through the module.
56
57
  */
57
58
  authSigner?: Wallet | Signer;
59
+ /**
60
+ * Persistence for the lit-ops-server session (service mode). The signed
61
+ * EIP-712 login envelope silently re-mints 15-minute JWTs for up to 24h, so
62
+ * persisting it means at most one wallet signature per day per device
63
+ * instead of one per 15 minutes / page reload. Defaults to enabled with
64
+ * `localStorage` in browsers; degrades to per-instance memory elsewhere.
65
+ * Note the persisted envelope is a re-mint credential for its window —
66
+ * call `clearServerSession()` on wallet disconnect.
67
+ */
68
+ sessionPersistence?: {
69
+ /** Default true. Set false for a fresh signature per instance + expiry. */
70
+ enabled?: boolean;
71
+ /** Custom store (e.g. a file-backed store for CLI daemons). */
72
+ store?: ServerSessionStore;
73
+ };
74
+ /**
75
+ * Fires immediately before the server-session login requests a wallet
76
+ * signature — never on the silent re-mint paths. Lets UIs show a
77
+ * "check your wallet" prompt.
78
+ */
79
+ onSessionSignaturePrompt?: () => void;
58
80
  ethRpcUrl?: string;
59
81
  chainId?: number;
60
82
  networkOverride?: {
@@ -111,6 +111,19 @@ export declare class DiamondHandsSDK {
111
111
  * reject the call with 401, which is the correct outcome.
112
112
  */
113
113
  private getAuthHeader;
114
+ /**
115
+ * Sign out of the lit-ops-server session for the current auth signer. Call on
116
+ * wallet disconnect or explicit sign-out so the credential does not outlive
117
+ * the wallet connection. No-op in standalone mode.
118
+ *
119
+ * Attempts a server-side revocation first (`ServerSession.logout()` — which
120
+ * tombstones the envelope's 24h re-mint window and invalidates the JWT), then
121
+ * always removes the local credential. If the revocation call fails (offline,
122
+ * server down, wallet already disconnected) we still clear locally so the
123
+ * session cannot silently re-mint after sign-out; `logout()` is best-effort
124
+ * and never prompts the wallet.
125
+ */
126
+ clearServerSession(): Promise<void>;
114
127
  /**
115
128
  * Audit H-9: invalidate the LoanQuery cache so subsequent reads return
116
129
  * the post-write state. We clear the entire loan-query cache (not just
@@ -435,6 +448,24 @@ export declare class DiamondHandsSDK {
435
448
  * @returns Withdrawal result with transaction details
436
449
  */
437
450
  withdrawBTC(positionId: string, withdrawalAddress: string, withdrawalAmount: number): Promise<BTCWithdrawalResult>;
451
+ /**
452
+ * TEMPORARY (multi-UTXO withdrawal fix, Phase A): fetch the vault's confirmed
453
+ * UTXO set from lit-ops-server (`GET /api/lit/vault-utxos`) so the caller can
454
+ * assemble and sign the FULL input set for a multi-UTXO consolidation
455
+ * withdrawal. Phase 1 only returns ONE representative UTXO, so a vault whose
456
+ * balance is spread across several UTXOs otherwise fails the Phase-2 signer
457
+ * with "Insufficient UTXO value ... across 1 input(s)".
458
+ *
459
+ * Returns `[]` on any failure (non-service mode, network error, bad shape) so
460
+ * callers transparently fall back to the single authorized UTXO. Superseded
461
+ * by the btc-withdrawal Lit Action returning its own confirmedUTXOs (Phase B).
462
+ */
463
+ fetchConfirmedVaultUtxos(positionId: string): Promise<Array<{
464
+ txid: string;
465
+ vout: number;
466
+ value: number;
467
+ confirmations: number;
468
+ }>>;
438
469
  /**
439
470
  * Execute Bitcoin withdrawal (Phase 2)
440
471
  *
@@ -456,6 +487,17 @@ export declare class DiamondHandsSDK {
456
487
  * Action can cross-check against the on-chain authorizer record.
457
488
  */
458
489
  targetAmount: number;
490
+ /**
491
+ * FULL input set the phase-2 signer may spend (P6/#8 — bound into the
492
+ * signature via a canonical set hash). Omit for the common single-UTXO
493
+ * withdrawal; multi-UTXO consolidation withdrawals MUST list every input
494
+ * (each with its full sat value), including the authorized UTXO.
495
+ */
496
+ utxos?: Array<{
497
+ txid: string;
498
+ vout: number;
499
+ value: number;
500
+ }>;
459
501
  }): Promise<{
460
502
  success: boolean;
461
503
  txid?: string;
@@ -658,6 +700,13 @@ export declare class DiamondHandsSDK {
658
700
  * Get all events for a loan position from the subgraph
659
701
  */
660
702
  getLoanEvents(positionId: string, filter?: import("../types/event-types").LoanEventsFilter): Promise<Result<import("../types/event-types").LoanEvents, SDKError>>;
703
+ /**
704
+ * Get loan-event history for every position owned by a wallet.
705
+ * Composes getUserPositions + getLoanEvents; returns one LoanEvents per
706
+ * position (each carrying its own positionId). Bitcoin deposits are not on the
707
+ * subgraph — merge those in from an Esplora source on the consumer side.
708
+ */
709
+ getWalletLoanEvents(userAddress: string, filter?: import("../types/event-types").LoanEventsFilter): Promise<Result<import("../types/event-types").LoanEvents[], SDKError>>;
661
710
  /**
662
711
  * Get protocol-wide events across all indexed entity types.
663
712
  * Returns a merged, sorted discriminated-union timeline.
@@ -839,6 +888,22 @@ export declare class DiamondHandsSDK {
839
888
  signature: string;
840
889
  validatorPkp: string;
841
890
  }, SDKError>>;
891
+ /**
892
+ * List a vault address's PENDING (not-yet-confirmed) incoming BTC deposits.
893
+ *
894
+ * A deposit is "pending" until it reaches the protocol's minimum confirmation
895
+ * depth (6) — the confirmed balance endpoints (`getVaultBalance` /
896
+ * `getAddressBalance`) omit anything shallower, so this surfaces in-flight
897
+ * deposits (0–5 confirmations). Server-delegated: lit-ops-server queries its
898
+ * configured premium Esplora provider (`/api/lit/pending-deposits`). Requires
899
+ * service mode.
900
+ */
901
+ getPendingDeposits(vaultAddress: string): Promise<Array<{
902
+ txid: string;
903
+ sats: number;
904
+ confirmations: number;
905
+ minConfirmations: number;
906
+ }>>;
842
907
  /**
843
908
  * Mint mock BTC tokens (test networks only)
844
909
  *
@@ -187,6 +187,29 @@ export declare class LoanQuery {
187
187
  * ```
188
188
  */
189
189
  getLoanEvents(positionId: string, filter?: LoanEventsFilter): Promise<Result<LoanEvents, SDKError>>;
190
+ /**
191
+ * Get loan-event history for every position owned by a wallet.
192
+ *
193
+ * Composes getUserPositions + getLoanEvents (the subgraph has no wallet-scoped
194
+ * event collection). Each returned LoanEvents carries its own positionId, so
195
+ * callers can flatten and merge into a single per-wallet timeline.
196
+ *
197
+ * Bitcoin deposits never appear in the subgraph — merge those in from an
198
+ * Esplora source on the consumer side.
199
+ *
200
+ * @param userAddress - Loan-owner wallet address.
201
+ * @param filter - Optional per-position event filter (same semantics as getLoanEvents).
202
+ * @returns One LoanEvents object per position (empty array if the wallet has none).
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const result = await loanQuery.getWalletLoanEvents('0x830b...');
207
+ * if (result.success) {
208
+ * const allEvents = result.value; // LoanEvents[] — one per position
209
+ * }
210
+ * ```
211
+ */
212
+ getWalletLoanEvents(userAddress: string, filter?: LoanEventsFilter): Promise<Result<LoanEvents[], SDKError>>;
190
213
  /**
191
214
  * Clear loan cache
192
215
  *
@@ -40,8 +40,6 @@ export interface MintUCDAuthParams extends BaseAuthParams {
40
40
  amount: UCD;
41
41
  /** PKP public key (optional - validator PKP is passed separately to authorizeMintUCD) */
42
42
  publicKey?: string;
43
- /** Bitcoin provider URL for balance verification */
44
- bitcoinProviderUrl?: string;
45
43
  /** Selected term in months (override when contract state is zero) */
46
44
  selectedTerm?: number;
47
45
  }
@@ -14,6 +14,24 @@ export interface BtcExecuteSignParams {
14
14
  chainId: number;
15
15
  signer: Signer;
16
16
  }
17
+ /** One Bitcoin input the borrower authorizes the phase-2 signer to spend. */
18
+ export interface BtcExecuteInput {
19
+ txid: string;
20
+ vout: number;
21
+ /** Full output value in sats (feeds the BIP143 sighash). */
22
+ value: number;
23
+ }
24
+ /**
25
+ * Canonical hash of a Bitcoin input set (remediation P6/#8).
26
+ *
27
+ * CANONICALIZATION MUST MATCH
28
+ * `lit-actions/src/modules/bitcoin/utxo-set-hash.ts` byte-for-byte: entries
29
+ * normalized to `${txidLowercase}:${vout}:${value}`, sorted by (txid asc,
30
+ * vout asc), joined with "|", then keccak256 over the UTF-8 bytes. The Lit
31
+ * Action recomputes this over the `utxos[]` it actually signs, so an operator
32
+ * cannot spend inputs beyond the set hashed here.
33
+ */
34
+ export declare function hashBtcInputSet(utxos: BtcExecuteInput[]): string;
17
35
  export interface BtcExecuteSignedEnvelope {
18
36
  positionId: string;
19
37
  txid: string;
@@ -31,6 +49,12 @@ export interface BtcExecuteSignedEnvelope {
31
49
  userSignature: string;
32
50
  /** Address recovered from the signer (borrower). */
33
51
  borrowerAddress: string;
52
+ /**
53
+ * The FULL input set bound into the signature (P6/#8). The server must
54
+ * forward exactly this set to the phase-2 signer — any other set fails
55
+ * signature verification in the Lit Action.
56
+ */
57
+ utxos: BtcExecuteInput[];
34
58
  }
35
59
  /**
36
60
  * Build the quantum-aligned timestamp + EIP-191 signature the
@@ -57,4 +81,11 @@ export declare function buildBtcExecuteEnvelope(params: BtcExecuteSignParams & {
57
81
  satoshis: number;
58
82
  targetAddress: string;
59
83
  targetAmount: number;
84
+ /**
85
+ * FULL input set the phase-2 signer may spend (P6/#8). Omit for the
86
+ * common single-UTXO withdrawal — it defaults to exactly the authorized
87
+ * UTXO, the most restrictive set. Multi-UTXO consolidation withdrawals
88
+ * MUST list every input here; the authorized UTXO must be among them.
89
+ */
90
+ utxos?: BtcExecuteInput[];
60
91
  }): Promise<BtcExecuteSignedEnvelope>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Client-side persistence for the lit-ops-server session.
3
+ *
4
+ * The server accepts re-mints of the SAME signed login envelope until
5
+ * `issuedAt + REAUTH_WINDOW_SECONDS` (see lit-ops-server `login-handler.ts`),
6
+ * so caching the signed payload means an expired 15-minute JWT — or a full
7
+ * page reload — costs one silent API call instead of another wallet popup.
8
+ * Mirrors the frontend's `wallet-login-cache.ts` pattern for the Firebase
9
+ * `DhWebLogin` flow.
10
+ *
11
+ * Browser-safe: the default store auto-detects `localStorage` and degrades to
12
+ * a per-instance in-memory map elsewhere (Node, blocked storage), which keeps
13
+ * exactly the pre-persistence behavior.
14
+ */
15
+ import type { DhServerLoginPayload } from "./eip712-login";
16
+ /** Must match lit-ops-server's REAUTH_WINDOW_SECONDS (`eip712-digest.ts`). */
17
+ export declare const REAUTH_WINDOW_SECONDS: number;
18
+ /** Stop using a cached payload this long before the server would reject it. */
19
+ export declare const SAFETY_MARGIN_SECONDS: number;
20
+ /** One persisted session: the 24h re-mint credential plus the current JWT. */
21
+ export interface PersistedServerSession {
22
+ version: 1;
23
+ /** Lowercased signer address the payload authenticates. */
24
+ address: string;
25
+ chainId: number;
26
+ /** Normalized service endpoint (trailing slashes stripped) — the signed audience. */
27
+ endpoint: string;
28
+ payload: DhServerLoginPayload;
29
+ /** Current JWT, if still live — lets a reload skip even the silent re-mint call. */
30
+ token?: string;
31
+ /** JWT expiry, unix seconds. */
32
+ tokenExpiresAt?: number;
33
+ }
34
+ /**
35
+ * Pluggable persistence. Implementations must be best-effort and never throw
36
+ * out of `save`/`clear`; `load` returns null for absent or malformed entries.
37
+ */
38
+ export interface ServerSessionStore {
39
+ load(key: string): PersistedServerSession | null;
40
+ save(key: string, session: PersistedServerSession): void;
41
+ clear(key: string): void;
42
+ }
43
+ export declare function sessionStoreKey(address: string, chainId: number, endpoint: string): string;
44
+ export declare class LocalStorageSessionStore implements ServerSessionStore {
45
+ load(key: string): PersistedServerSession | null;
46
+ save(key: string, session: PersistedServerSession): void;
47
+ clear(key: string): void;
48
+ }
49
+ export declare class MemorySessionStore implements ServerSessionStore {
50
+ private readonly entries;
51
+ load(key: string): PersistedServerSession | null;
52
+ save(key: string, session: PersistedServerSession): void;
53
+ clear(key: string): void;
54
+ }
55
+ /** `localStorage` when usable (browser, not blocked), else per-instance memory. */
56
+ export declare function createDefaultSessionStore(): ServerSessionStore;
@@ -1,50 +1,132 @@
1
1
  /**
2
- * In-memory session JWT manager for lit-ops-server.
2
+ * Session JWT manager for lit-ops-server.
3
3
  *
4
4
  * The server's `POST /api/auth/login` route accepts an EIP-712-signed
5
- * `DhServerLogin` envelope and returns an HS256 JWT with a 15-minute TTL.
6
- * This helper:
5
+ * `DiamondHands` envelope and returns an HS256 JWT with a 15-minute TTL. The
6
+ * same signed envelope may silently re-mint fresh JWTs until
7
+ * `issuedAt + REAUTH_WINDOW_SECONDS` (24h), so a wallet signature is only
8
+ * needed once per window. This helper:
7
9
  *
8
10
  * - caches the JWT until ~30s before expiry,
11
+ * - persists the signed envelope (+ current JWT) via a pluggable store —
12
+ * `localStorage` in browsers by default — so page reloads and expired
13
+ * JWTs recover WITHOUT another wallet popup,
9
14
  * - coalesces concurrent callers behind a single in-flight Promise so we
10
15
  * don't spam `/api/auth/login` on parallel SDK calls,
11
16
  * - exposes `getValidToken()` returning the live token and
12
17
  * `getAuthHeader()` returning a ready-to-spread `{ Authorization: ... }`
13
18
  * object.
14
19
  *
15
- * Browser-safe — no filesystem touches. The CLI mirrors this shape but
16
- * persists to `~/.diamond-hands/session.json` for cross-invocation reuse.
20
+ * Browser-safe — no filesystem touches; outside browsers the store degrades
21
+ * to per-instance memory. The CLI mirrors this shape but persists to
22
+ * `~/.diamond-hands/session.json` for cross-invocation reuse.
17
23
  */
18
24
  import type { Signer } from "ethers";
25
+ import { type ServerSessionStore } from "./server-session-store";
19
26
  export interface ServerSessionOptions {
20
27
  signer: Signer;
21
28
  serviceEndpoint: string;
22
29
  chainId: number;
30
+ /**
31
+ * Persistence for the signed login envelope + JWT. Defaults to
32
+ * `localStorage` in browsers and per-instance memory elsewhere.
33
+ */
34
+ sessionStore?: ServerSessionStore;
35
+ /** Set false to disable persistence entirely (fresh signature per instance + expiry). */
36
+ persistSession?: boolean;
37
+ /**
38
+ * Fires immediately before a wallet signature is requested — i.e. only when
39
+ * neither the cached JWT nor the persisted envelope could renew the session,
40
+ * never on the silent paths. Lets UIs show a "check your wallet" prompt.
41
+ */
42
+ onSignaturePrompt?: () => void;
23
43
  /** Override for tests. */
24
44
  now?: () => number;
25
45
  /** Override for tests. */
26
46
  fetchImpl?: typeof fetch;
27
47
  }
48
+ /** Login failure carrying the HTTP status so callers can tell rejection from outage. */
49
+ export declare class ServerLoginError extends Error {
50
+ readonly status: number;
51
+ constructor(message: string, status: number);
52
+ }
28
53
  export declare class ServerSession {
29
54
  private readonly signer;
30
55
  private readonly serviceEndpoint;
31
56
  private readonly chainId;
57
+ private readonly store;
58
+ private readonly onSignaturePrompt?;
32
59
  private readonly now;
33
60
  private readonly fetchImpl;
34
61
  private cached;
35
62
  private inFlight;
63
+ /**
64
+ * Bumped by `clearPersisted()` (logout / disconnect). An in-flight
65
+ * `getOrRefresh()` captures this at start and refuses to write its freshly
66
+ * minted token back to `this.cached`/the store if the value changed
67
+ * meanwhile — so a refresh racing a sign-out can't silently "un-clear" the
68
+ * credential it was told to drop.
69
+ */
70
+ private generation;
71
+ /**
72
+ * Store key for the current signer, memoized on first use so the sync
73
+ * `clear()` can drop the persisted token without an async address lookup.
74
+ */
75
+ private lastStoreKey;
36
76
  constructor(opts: ServerSessionOptions);
37
- /** Returns a JWT good for at least `REFRESH_LEEWAY_SECONDS` more seconds. */
38
- getValidToken(): Promise<string>;
77
+ /**
78
+ * Returns a JWT good for at least `REFRESH_LEEWAY_SECONDS` more seconds.
79
+ * Pass `{ silent: true }` to forbid a fresh wallet signature: it resolves ONLY
80
+ * from a live cached token or the persisted re-mint envelope, never prompting
81
+ * and never joining a prompt-capable in-flight refresh (used by `logout()`,
82
+ * which must never pop the wallet during sign-out). Rejects when only a fresh
83
+ * signature could produce a token.
84
+ */
85
+ getValidToken(opts?: {
86
+ silent?: boolean;
87
+ }): Promise<string>;
39
88
  /** Convenience for fetch: spread directly into `headers`. */
40
89
  getAuthHeader(): Promise<Record<string, string>>;
41
- /** Drop the cached token (e.g. on 401 from server). Next call re-logs in. */
90
+ /**
91
+ * Drop the cached token (e.g. on 401 from server). The persisted envelope is
92
+ * kept — the next call re-mints silently — but its stored token copy is
93
+ * dropped too so a dead JWT can't be re-adopted from the store.
94
+ */
42
95
  clear(): void;
43
96
  /**
44
- * Revoke the current session JWT on lit-ops-server (`POST /api/auth/logout`).
45
- * Clears the local cache regardless of server response so the client stops
46
- * presenting the token.
97
+ * Drop the session AND the persisted re-mint envelope (wallet disconnect /
98
+ * explicit sign-out paths anything that must remove the 24h credential
99
+ * from storage).
100
+ */
101
+ clearPersisted(): Promise<void>;
102
+ /**
103
+ * Revoke the current session JWT on lit-ops-server (`POST /api/auth/logout`)
104
+ * — which also tombstones the envelope's re-mint window server-side — and
105
+ * remove the persisted credential locally. Clears the local cache regardless
106
+ * of server response so the client stops presenting the token.
47
107
  */
48
108
  logout(): Promise<void>;
109
+ /**
110
+ * Resolve a session WITHOUT ever prompting for a wallet signature: a live
111
+ * cached token, else the persisted JWT (adopted if still fresh, otherwise
112
+ * silently re-minted from the envelope). Returns `null` only when a fresh
113
+ * wallet signature would be required (dead/absent envelope). Never touches
114
+ * `this.inFlight`, so a silent caller (logout) can resolve independently
115
+ * instead of being dragged into a prompt-capable refresh already in flight.
116
+ *
117
+ * `gen` is the caller's clear-generation snapshot: writes to `this.cached`
118
+ * and the store are skipped if a `clearPersisted()` landed meanwhile.
119
+ */
120
+ private tryResolveSilently;
49
121
  private getOrRefresh;
122
+ private login;
123
+ /**
124
+ * Load the persisted session for the current signer, validating that it
125
+ * belongs to this address/chain/endpoint and that the envelope is still
126
+ * inside the re-auth window (with a safety margin). Invalid or expired
127
+ * entries are evicted.
128
+ */
129
+ private loadPersisted;
130
+ /** Best-effort save of the envelope + live JWT for silent recovery. */
131
+ private persist;
50
132
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.301",
3
+ "version": "0.0.304",
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",
@@ -82,8 +82,8 @@
82
82
  },
83
83
  "sideEffects": false,
84
84
  "dependencies": {
85
- "@gvnrdao/dh-lit-actions": "^0.0.313",
86
- "@gvnrdao/dh-lit-ops": "^0.0.303",
85
+ "@gvnrdao/dh-lit-actions": "^0.0.314",
86
+ "@gvnrdao/dh-lit-ops": "^0.0.304",
87
87
  "@noble/hashes": "^1.5.0",
88
88
  "axios": "^1.17.0",
89
89
  "bech32": "^2.0.0",