@waaskey/sdk 0.0.1 → 0.1.0

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.cts CHANGED
@@ -1,12 +1,867 @@
1
+ import { PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, AuthenticationResponseJSON } from '@simplewebauthn/browser';
2
+
3
+ /** Thin typed HTTP client over fetch — the single place requests are issued. */
4
+ declare class HttpClient {
5
+ private readonly apiKey;
6
+ private readonly baseUrl;
7
+ private readonly fetchImpl;
8
+ private memberAccessToken?;
9
+ constructor(apiKey: string, baseUrl: string, fetchImpl?: typeof fetch);
10
+ /**
11
+ * Wire a provider of the held org-member access token (from {@link Members}). When it returns a
12
+ * token, {@link requestAsMember} authenticates member calls with `Authorization: Bearer <token>`
13
+ * rather than the ambient dashboard cookie. Injected by the client (not imported) so `Members` and
14
+ * `HttpClient` don't form an import cycle.
15
+ */
16
+ useMemberAccessToken(provider: () => string | undefined): void;
17
+ request<T>(method: string, path: string, body?: unknown, signal?: AbortSignal, bearer?: string): Promise<T>;
18
+ /**
19
+ * `@Public` request: NO `Authorization` header and NO cookie. Used by the org-member Firebase
20
+ * login exchange (`POST /v1/auth/firebase`), which authenticates from the Firebase ID token in the
21
+ * body alone and returns the member bearer tokens in its response.
22
+ */
23
+ requestPublic<T>(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<T>;
24
+ /**
25
+ * Member-session request (#349). Authenticates as the org member in one of two ways, transparent
26
+ * to the caller ({@link Wallets.joinCeremony} / {@link Wallets.joinSignCeremony}):
27
+ *
28
+ * - **Held bearer session** (a {@link memberAccessToken} provider returns a token — e.g. after
29
+ * {@link Members.loginWithFirebase}): sends `Authorization: Bearer <accessToken>`. The backend's
30
+ * `ApiKeyAuthGuard` recognises a non-API-key bearer and falls through to the member-session
31
+ * resolver, so a headless (non-browser) consumer authenticates without a cookie.
32
+ * - **No held session** (the browser dashboard path): sends NO `Authorization` header and
33
+ * `credentials: 'include'`, relying on the caller's own same-site httpOnly member cookie. The
34
+ * guard only falls back to the cookie resolver when the header is absent entirely, so it is
35
+ * omitted here.
36
+ *
37
+ * Never used by the tenant-apiKey ({@link request}) or embedded-end-user paths.
38
+ */
39
+ requestAsMember<T>(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<T>;
40
+ private send;
41
+ }
42
+
43
+ /** Lifecycle events the SDK emits for the tenant dashboard. */
44
+ type AnalyticsEventType = 'wallet.created' | 'wallet.signed' | 'wallet.sent' | 'wallet.recovered' | 'wallet.reshared';
45
+ /**
46
+ * A privacy-respecting SDK analytics event. Carries NO PII, secrets, key shares, addresses, or
47
+ * signing digests — only the action, a timestamp, and tenant-owned non-sensitive identifiers.
48
+ */
49
+ interface AnalyticsEvent {
50
+ type: AnalyticsEventType;
51
+ /** ISO-8601 timestamp. */
52
+ timestamp: string;
53
+ /** Wallet id — an opaque Waaskey identifier owned by the tenant (not PII). */
54
+ walletId?: string;
55
+ /** Chain slug, e.g. "ethereum". */
56
+ chain?: string;
57
+ /** Signing curve, e.g. "secp256k1". */
58
+ curve?: string;
59
+ }
60
+ /**
61
+ * Receives SDK analytics events. The default is an HTTP sink to the Waaskey API; pass your own to
62
+ * forward to a custom pipeline, or `false` in {@link WaaskeyOptions.analytics} to opt out entirely.
63
+ */
64
+ interface AnalyticsSink {
65
+ track(event: AnalyticsEvent): void;
66
+ }
67
+
68
+ /**
69
+ * Emits lifecycle events to a {@link AnalyticsSink}; a no-op when analytics is disabled (no sink).
70
+ * Fire-and-forget and exception-safe — analytics must NEVER break or slow a wallet operation.
71
+ */
72
+ declare class Analytics {
73
+ private readonly sink?;
74
+ constructor(sink?: AnalyticsSink | undefined);
75
+ track(type: AnalyticsEventType, props?: Omit<AnalyticsEvent, 'type' | 'timestamp'>): void;
76
+ }
77
+
78
+ /** Default sink: POST events to the Waaskey API, fire-and-forget — delivery failures are swallowed. */
79
+ declare class HttpAnalyticsSink implements AnalyticsSink {
80
+ private readonly http;
81
+ constructor(http: HttpClient);
82
+ track(event: AnalyticsEvent): void;
83
+ }
84
+
85
+ /**
86
+ * Client-side balance reads.
87
+ *
88
+ * WaaS is non-custodial and the backend does not index chain state — balances are
89
+ * read **directly from a chain provider** (dApp-style), never from the backend. The
90
+ * SDK depends on the {@link ChainProvider} port; the default web implementation is a
91
+ * JSON-RPC provider for EVM chains, and a consumer can plug a custom provider for
92
+ * any chain/transport.
93
+ */
94
+ /** Reads chain state for one chain. Native is always supported; tokens/decimals are EVM-style. */
95
+ interface ChainProvider {
96
+ /** Native asset balance of `address`, in base units (wei / sat / lamports). */
97
+ getNativeBalance(address: string): Promise<bigint>;
98
+ /** Token balance of `owner` for `token`, in the token's base units. */
99
+ getTokenBalance(token: string, owner: string): Promise<bigint>;
100
+ /** Token decimals, if the provider can resolve them (EVM `decimals()`). */
101
+ getTokenDecimals?(token: string): Promise<number>;
102
+ }
103
+ /** Per-chain provider configuration. */
104
+ interface ChainConfig {
105
+ /** JSON-RPC URL for an EVM chain. Ignored if `provider` is set. */
106
+ rpcUrl?: string;
107
+ /** A custom provider (for non-EVM chains or a bespoke transport). Overrides `rpcUrl`. */
108
+ provider?: ChainProvider;
109
+ }
110
+ /** A resolved balance — exact base units plus a human-readable rendering. */
111
+ interface Balance {
112
+ /** Exact balance in base units (no float). */
113
+ raw: bigint;
114
+ /** Decimals of the asset. */
115
+ decimals: number;
116
+ /** `raw` rendered as a decimal string (e.g. `"1.5"`). */
117
+ formatted: string;
118
+ /** Asset symbol, when known. */
119
+ symbol?: string;
120
+ }
121
+ /** Options for a token balance read. */
122
+ interface TokenBalanceOptions {
123
+ /** Token decimals (skips an on-chain `decimals()` lookup). */
124
+ decimals?: number;
125
+ /** Token symbol, for the returned {@link Balance}. */
126
+ symbol?: string;
127
+ }
128
+
129
+ /**
130
+ * Reads balances directly from a chain provider (never the backend). EVM chains work
131
+ * out of the box via a default public RPC; override per chain with `chains` (an RPC
132
+ * URL or a custom {@link ChainProvider}), and supply a provider for non-EVM chains.
133
+ */
134
+ declare class Balances {
135
+ private readonly config;
136
+ private readonly fetchImpl?;
137
+ private readonly providers;
138
+ constructor(config?: Partial<Record<Chain, ChainConfig>>, fetchImpl?: typeof fetch | undefined);
139
+ /** Native (gas-token) balance of an address on a chain. */
140
+ getBalance(chain: Chain, address: string): Promise<Balance>;
141
+ /** Token balance of `owner` for `token` on a chain (ERC-20 on EVM). */
142
+ getTokenBalance(chain: Chain, token: string, owner: string, options?: TokenBalanceOptions): Promise<Balance>;
143
+ /** Resolve (and cache) the provider for a chain: custom → configured RPC → default EVM RPC. */
144
+ private provider;
145
+ }
146
+
147
+ /**
148
+ * {@link ChainProvider} backed by an EVM JSON-RPC endpoint (`eth_getBalance`,
149
+ * `eth_call`). Reads are dApp-style and need no backend. A custom `fetch` can be
150
+ * injected (non-browser runtimes / tests).
151
+ */
152
+ declare class EvmRpcProvider implements ChainProvider {
153
+ private readonly rpcUrl;
154
+ private readonly fetchImpl;
155
+ constructor(rpcUrl: string, fetchImpl?: typeof fetch);
156
+ getNativeBalance(address: string): Promise<bigint>;
157
+ getTokenBalance(token: string, owner: string): Promise<bigint>;
158
+ getTokenDecimals(token: string): Promise<number>;
159
+ private call;
160
+ private toBigInt;
161
+ }
162
+
163
+ /** Render a base-unit integer as a decimal string (e.g. `formatUnits(1500000n, 6)` → `"1.5"`). No float. */
164
+ declare function formatUnits(raw: bigint, decimals: number): string;
165
+
166
+ /**
167
+ * Device-party MPC contract.
168
+ *
169
+ * Waaskey wallets are 2-of-3: the user's **device** holds one key share and runs
170
+ * its half of the cggmp24 ceremony locally (in WASM), talking to the Waaskey
171
+ * `signer` (the server party) over the relay. The private key never exists whole
172
+ * anywhere. This is the device side of that ceremony.
173
+ *
174
+ * The wire shapes mirror the waas-core `client-wasm` exports (`keygen` / `sign`),
175
+ * which mirror the server `party-runner`. The backend tells the SDK the per-party
176
+ * routing (relay url, session id, indices, roles) in the create-wallet response.
177
+ */
178
+ /** Curves the device party can run a ceremony on. */
179
+ type MpcCurve = 'secp256k1';
180
+ /** Relay routing + party identity shared by keygen and sign. */
181
+ interface CeremonyParams {
182
+ curve: MpcCurve;
183
+ /** Relay websocket URL both parties connect to. */
184
+ relayUrl: string;
185
+ /** Ceremony id shared by all parties; derives the execution ids. */
186
+ sessionId: string;
187
+ /** This party's relay routing id. Defaults to `"device"`. */
188
+ role?: string;
189
+ /** The peer (server) party's relay routing id. Defaults to `"server"`. */
190
+ peerRole?: string;
191
+ /** This party's keygen index. */
192
+ partyIndex: number;
193
+ /** The peer (server) party's keygen index. */
194
+ peerPartyIndex: number;
195
+ /**
196
+ * Short-lived relay token (JWT) the device presents to the relay to join this session as
197
+ * `role`. Returned by the backend on the ceremony / sign-session response; present only when
198
+ * relay authentication is enabled. Absent ⇒ the relay accepts an unauthenticated join.
199
+ */
200
+ relayToken?: string;
201
+ }
202
+ /** Parameters for the device half of a keygen ceremony. */
203
+ interface DeviceKeygenParams extends CeremonyParams {
204
+ /** Total parties `n`. */
205
+ parties: number;
206
+ /** Signing threshold `t` (`2 <= t <= n`). */
207
+ threshold: number;
208
+ /**
209
+ * Pre-generated Paillier safe-primes (JSON) for this device, produced ahead of time off the
210
+ * hot path (see {@link MpcCore.pregeneratePrimes} / `PrimePool`). When omitted the core
211
+ * generates them inline — correct, but slow in single-threaded browser WASM. The primes are
212
+ * this device's private aux material and are NEVER sent to the server.
213
+ */
214
+ pregeneratedPrimes?: string;
215
+ }
216
+ /** Result of the device half of keygen — the device's share never leaves the device. */
217
+ interface DeviceKeygenResult {
218
+ /** The device's KeyShare (JSON). Seal + store on the device; never send to the server. */
219
+ keyShare: string;
220
+ /** The ceremony's aux info (JSON). */
221
+ auxInfo: string;
222
+ /** The wallet's shared public key (hex), derived from the share — safe to publish. */
223
+ sharedPublicKey: string;
224
+ }
225
+ /** Parameters for the device half of a sign ceremony. */
226
+ interface DeviceSignParams extends CeremonyParams {
227
+ /**
228
+ * The device's KeyShare as its JSON **string** — the bare crypto material from a prior keygen
229
+ * ({@link DeviceKeygenResult.keyShare}), NOT the on-device storage blob. It rides the sign wire as
230
+ * a parsed JSON OBJECT (the wasm deserializes it with `serde_json::from_value::<KeyShare>`).
231
+ */
232
+ share: string;
233
+ /** Keygen indices signing together (any `t` of `n`). */
234
+ participants: number[];
235
+ /** This party's 0-based position within `participants`. */
236
+ signerPosition: number;
237
+ /** 32-byte hex digest to sign (`0x` prefix optional). */
238
+ digest: string;
239
+ }
240
+ /** Result of the device half of sign. */
241
+ interface DeviceSignResult {
242
+ /** The cggmp24 signature (JSON). */
243
+ signature: string;
244
+ }
245
+ /**
246
+ * Parameters for the device's reshare **assemble** stage ({@link MpcCore.runReshareAssemble}).
247
+ * A structural mirror of the backend `DeviceReshareMaterial` plus the wallet's curve — pure
248
+ * and local, so no relay routing is needed. The fields are opaque JSON produced by the backend
249
+ * reshare; the device only routes them into the core.
250
+ */
251
+ interface DeviceReshareAssembleParams {
252
+ curve: MpcCurve;
253
+ /** This device's 0-based slot within {@link newPreimages}. */
254
+ newPosition: number;
255
+ /** New share preimages `I'` (32-byte big-endian hex scalars), new-holder order. */
256
+ newPreimages: string[];
257
+ /** The new signing threshold `t'`. */
258
+ newThreshold: number;
259
+ /** The unchanged wallet public info (`WalletPublicInfo` JSON, opaque). */
260
+ wallet: unknown;
261
+ /** One Feldman-commitments object per dealer (the broadcast set), opaque JSON. */
262
+ commitments: unknown[];
263
+ /** This device's private sub-share from each dealer, one per dealer, opaque JSON. */
264
+ subShares: unknown[];
265
+ }
266
+ /**
267
+ * Result of the device reshare **assemble** — the bare NEW-epoch core. It cannot sign yet
268
+ * (aux material is generated in {@link MpcCore.runCompleteReshare}), and it is secret: seal it,
269
+ * never log it.
270
+ */
271
+ interface DeviceReshareAssembleResult {
272
+ /** The device's NEW-epoch bare core (`IncompleteKeyShare` JSON). Secret. */
273
+ core: string;
274
+ /** The assembled shared public key (hex) — must equal the wallet's (unchanged across a reshare). */
275
+ sharedPublicKey: string;
276
+ }
277
+ /**
278
+ * Parameters for the device's **complete-reshare** stage ({@link MpcCore.runCompleteReshare}) —
279
+ * the interactive aux-info ceremony over the NEW committee that turns the bare core into a
280
+ * signable share. Relay routing mirrors keygen/sign ({@link CeremonyParams}); the backend uses
281
+ * `<sessionId>/reshare-aux` (kind `reshare-aux`) and drives the server + recovery parties.
282
+ */
283
+ interface DeviceCompleteReshareParams extends CeremonyParams {
284
+ /** The device's bare NEW-epoch core (JSON) from {@link MpcCore.runReshareAssemble}. */
285
+ core: string;
286
+ /** Total parties `n'` in the NEW committee. */
287
+ parties: number;
288
+ /**
289
+ * This device's OWN pre-generated Paillier safe-primes (JSON) for the aux ceremony, off the hot
290
+ * path (see {@link MpcCore.pregeneratePrimes} / `PrimePool`). When omitted the core generates
291
+ * them inline. The primes are the device's private material and are NEVER server-provided — a
292
+ * server-supplied prime pool would collapse the threshold to custodial.
293
+ */
294
+ pregeneratedPrimes?: string;
295
+ }
296
+ /** Result of the device **complete-reshare** — the COMPLETE, signable share. Seal it, never log it. */
297
+ interface DeviceCompleteReshareResult {
298
+ /** The device's complete KeyShare (JSON). Seal + store on the device; never send to the server. */
299
+ keyShare: string;
300
+ /** The wallet's shared public key (hex), preserved across the reshare — verify it is unchanged. */
301
+ sharedPublicKey: string;
302
+ }
303
+ /** Relay connection info shared by an n-party (member-bound) keygen/sign ceremony (#349). */
304
+ interface MemberCeremonyParams {
305
+ curve: MpcCurve;
306
+ /** Relay websocket URL every party connects to. */
307
+ relayUrl: string;
308
+ /** Ceremony id shared by every party; derives the execution ids. */
309
+ sessionId: string;
310
+ /**
311
+ * Short-lived relay token (JWT) this party presents to join the session as its own role in
312
+ * {@link MemberKeygenParams.roles} / {@link MemberSignParams.roles}. Present only when relay
313
+ * authentication is enabled. Absent ⇒ the relay accepts an unauthenticated join.
314
+ */
315
+ relayToken?: string;
316
+ }
317
+ /** Parameters for this party's half of an n-party keygen ceremony (member-bound wallets, #349). */
318
+ interface MemberKeygenParams extends MemberCeremonyParams {
319
+ /** Every party's relay role, in protocol index order — the FULL n-party roster (`roles[i]` is party `i`'s role). */
320
+ roles: string[];
321
+ /** This party's own 0-based index into {@link roles} (its keygen index). */
322
+ partyIndex: number;
323
+ /** Signing threshold `t` (`2 <= t <= roles.length`). */
324
+ threshold: number;
325
+ /**
326
+ * Pre-generated Paillier safe-primes (JSON) for this party, produced ahead of time off the hot
327
+ * path (see {@link MpcCore.pregeneratePrimes}). When omitted the core generates them inline.
328
+ */
329
+ pregeneratedPrimes?: string;
330
+ }
331
+ /** Parameters for this party's half of an n-party sign ceremony (member-bound wallets, #349). */
332
+ interface MemberSignParams extends MemberCeremonyParams {
333
+ /** The FIXED t-of-n quorum's relay roles, in signing order (this ceremony's `PartyRouting`). */
334
+ roles: string[];
335
+ /**
336
+ * This party's KeyShare as its JSON **string** — the bare crypto material from a prior
337
+ * {@link MpcCore.runMemberKeygen} ({@link DeviceKeygenResult.keyShare}), NOT the on-device storage
338
+ * blob. It rides the sign wire as a parsed JSON OBJECT (`serde_json::from_value::<KeyShare>`).
339
+ */
340
+ share: string;
341
+ /** Keygen indices of the parties signing together, parallel to {@link roles}. */
342
+ participants: number[];
343
+ /** This party's 0-based position within {@link roles} / {@link participants}. */
344
+ signerPosition: number;
345
+ /** 32-byte hex digest to sign (`0x` prefix optional). */
346
+ digest: string;
347
+ }
348
+ /** Relay connection info shared by an ed25519 (FROST) keygen/sign ceremony (#110). */
349
+ interface EddsaCeremonyParams {
350
+ /** Relay websocket URL every party connects to. */
351
+ relayUrl: string;
352
+ /** Ceremony id shared by every party; derives the FROST execution ids. */
353
+ sessionId: string;
354
+ /**
355
+ * Short-lived relay token (JWT) this party presents to join the session as its own role in
356
+ * {@link roles}. Present only when relay authentication is enabled; absent ⇒ unauthenticated join.
357
+ */
358
+ relayToken?: string;
359
+ /**
360
+ * Every party's relay role, in protocol-index order (`roles[i]` is party `i`'s role) — for keygen
361
+ * the FULL n-party roster; for sign the FIXED t-of-n quorum in {@link EddsaSignParams.participants}
362
+ * order. MUST match the platform/party-runner order (role ↔ FROST identifier).
363
+ */
364
+ roles: string[];
365
+ /** This party's own 0-based index into {@link roles} (its slot in the ceremony). */
366
+ partyIndex: number;
367
+ }
368
+ /** Parameters for this device's half of an ed25519 (FROST) DKG keygen ceremony (#110). */
369
+ interface EddsaKeygenParams extends EddsaCeremonyParams {
370
+ /** Signing threshold `t` (`2 <= t <= roles.length`, validated by the FROST DKG). */
371
+ threshold: number;
372
+ /**
373
+ * The FROST DKG round-2 encryption roster (#114): one X25519 encryption PUBLIC key (32-byte hex) per
374
+ * party, in `roles`/protocol-index order (`encPubkeys[i]` ↔ FROST identifier `i + 1`). Every party
375
+ * seals its round-2 packages to these keys so no secret share crosses the relay in the clear — the
376
+ * device needs the whole ordered roster (chiefly the server's key). Maps to the wasm `enc_pubkeys`.
377
+ */
378
+ encPubkeys: string[];
379
+ /**
380
+ * This device's OWN X25519 encryption SECRET key (32-byte hex) — opens the round-2 packages sealed to
381
+ * it (the counterpart of this device's entry in {@link encPubkeys}). SECRET: never logged, never sent
382
+ * to the server. Maps to the wasm `enc_secret`.
383
+ */
384
+ encSecret: string;
385
+ }
386
+ /**
387
+ * Result of the device half of an ed25519 keygen — the device's FROST share. The `keyPackage` is
388
+ * **secret** (the device's signing share): seal + store it, never send it to the server. The
389
+ * `publicKeyPackage` is the wallet's group verifying key package (safe to publish). Both are opaque
390
+ * FROST JSON objects (the exact shapes `signEddsa` feeds back), not the cggmp24 KeyShare blob.
391
+ */
392
+ interface EddsaKeygenResult {
393
+ /** The device's FROST `key_package` (JSON object). Secret — seal + store on the device. */
394
+ keyPackage: unknown;
395
+ /** The wallet's shared `public_key_package` (JSON object). Group key — safe to publish. */
396
+ publicKeyPackage: unknown;
397
+ }
398
+ /** Parameters for this device's half of an ed25519 (FROST) two-round sign ceremony (#110). */
399
+ interface EddsaSignParams extends EddsaCeremonyParams {
400
+ /** This device's FROST `key_package` — the secret share from {@link EddsaKeygenResult.keyPackage}, as a JSON object (or its JSON string). */
401
+ keyPackage: unknown;
402
+ /** The wallet's shared `public_key_package` from {@link EddsaKeygenResult.publicKeyPackage}, as a JSON object (or its JSON string). */
403
+ publicKeyPackage: unknown;
404
+ /** The 1-based FROST identifiers of the signing quorum, parallel to {@link EddsaCeremonyParams.roles}. */
405
+ participants: number[];
406
+ /** Hex-encoded raw message bytes to sign (`0x` prefix optional) — the chain adapter's serialized tx message. */
407
+ message: string;
408
+ }
409
+ /** Result of the device half of an ed25519 sign — the RFC 8032 signature. */
410
+ interface EddsaSignResult {
411
+ /** The 64-byte ed25519 signature, hex. */
412
+ signature: string;
413
+ }
414
+ /**
415
+ * The device-party crypto core. Implemented by {@link WasmMpcCore} (web) and, in
416
+ * future, a native core on mobile — the SDK depends on this port, not the engine.
417
+ */
418
+ interface MpcCore {
419
+ runKeygen(params: DeviceKeygenParams): Promise<DeviceKeygenResult>;
420
+ runSign(params: DeviceSignParams): Promise<DeviceSignResult>;
421
+ /**
422
+ * Pre-generate this device's Paillier safe-primes for `curve`, off the keygen hot path
423
+ * (e.g. in a Web Worker during onboarding/idle). Returns opaque serialized primes (JSON) to
424
+ * cache and later pass as {@link DeviceKeygenParams.pregeneratedPrimes}. Optional: a native
425
+ * mobile core generates primes fast inline and need not implement it.
426
+ */
427
+ pregeneratePrimes?(curve: MpcCurve): Promise<string>;
428
+ /**
429
+ * Assemble this device's NEW-epoch bare core from a device-retaining reshare's material (#318).
430
+ * Pure and local — no relay. Optional: only a core built with the reshare capability implements
431
+ * it (the web wasm needs a `--features reshare` build).
432
+ */
433
+ runReshareAssemble?(params: DeviceReshareAssembleParams): Promise<DeviceReshareAssembleResult>;
434
+ /**
435
+ * Complete a reshared bare core into a signable share by running the aux-info ceremony over the
436
+ * NEW committee (#318 / #95). Relay-driven, mirroring keygen's aux phase. Optional: only a core
437
+ * built with the reshare capability implements it (the web wasm needs a `--features reshare` build).
438
+ */
439
+ runCompleteReshare?(params: DeviceCompleteReshareParams): Promise<DeviceCompleteReshareResult>;
440
+ /**
441
+ * Run this party's half of an n-party (member-bound wallet) keygen ceremony (#349) — every
442
+ * member device + the platform join the SAME relay session, addressed by the full
443
+ * {@link MemberKeygenParams.roles} roster rather than a single peer. Optional: only a core built
444
+ * with n-party (member-ceremony) support implements it.
445
+ */
446
+ runMemberKeygen?(params: MemberKeygenParams): Promise<DeviceKeygenResult>;
447
+ /**
448
+ * Run this party's half of an n-party (member-bound wallet) sign ceremony (#349) over the FIXED
449
+ * t-of-n quorum in {@link MemberSignParams.roles}. Optional: only a core built with n-party
450
+ * (member-ceremony) support implements it.
451
+ */
452
+ runMemberSign?(params: MemberSignParams): Promise<DeviceSignResult>;
453
+ /**
454
+ * Run this device's half of an ed25519 (FROST) DKG keygen ceremony (#110) — the device holds one
455
+ * FROST share in a real 2-of-3 wallet, co-generating the group key with the server (+ recovery)
456
+ * parties on the same relay session. Optional: only a core with the ed25519 (FROST) exports
457
+ * (`keygenEddsa`) implements it.
458
+ */
459
+ runEddsaKeygen?(params: EddsaKeygenParams): Promise<EddsaKeygenResult>;
460
+ /**
461
+ * Run this device's half of an ed25519 (FROST) two-round sign ceremony (#110) over the fixed
462
+ * quorum in {@link EddsaSignParams.roles} — co-signing the raw message with the server party.
463
+ * Optional: only a core with the ed25519 (FROST) exports (`signEddsa`) implements it.
464
+ */
465
+ runEddsaSign?(params: EddsaSignParams): Promise<EddsaSignResult>;
466
+ }
467
+
468
+ /** The subset of the client-wasm module the SDK drives. Each export takes one JSON string. */
469
+ interface ClientWasmModule {
470
+ /** Device half of keygen; resolves with `{ aux_info_json, keyshare_json, shared_public_key_json }`. */
471
+ keygen(paramsJson: string): Promise<unknown>;
472
+ /** Device half of sign; resolves with `{ signature_json }`. */
473
+ sign(paramsJson: string): Promise<unknown>;
474
+ /** Pre-generate Paillier safe-primes off the hot path; resolves with `{ primes_json }`. Optional. */
475
+ pregeneratePrimes?(paramsJson: string): Promise<unknown>;
476
+ /**
477
+ * Device reshare **assemble** — compute the NEW-epoch bare core locally; returns
478
+ * `{ core_json, shared_public_key_json }`. Present only in a `--features reshare` wasm build.
479
+ * Synchronous in the core (no relay); the SDK awaits it uniformly.
480
+ */
481
+ reshareAssemble?(paramsJson: string): unknown | Promise<unknown>;
482
+ /**
483
+ * Device **complete-reshare** — run the aux ceremony over the new committee and return the
484
+ * signable share as `{ keyshare_json, shared_public_key_json }`. Present only in a
485
+ * `--features reshare` wasm build.
486
+ */
487
+ completeReshare?(paramsJson: string): Promise<unknown>;
488
+ /**
489
+ * This party's half of an n-party (member-bound wallet) keygen ceremony (#349); resolves with
490
+ * `{ aux_info_json, keyshare_json, shared_public_key_json }`. Present only in a client-wasm
491
+ * build with n-party (member-ceremony) support.
492
+ */
493
+ keygenMember?(paramsJson: string): Promise<unknown>;
494
+ /**
495
+ * This party's half of an n-party (member-bound wallet) sign ceremony over a fixed t-of-n
496
+ * quorum (#349); resolves with `{ signature_json }`. Present only in a client-wasm build with
497
+ * n-party (member-ceremony) support.
498
+ */
499
+ signMember?(paramsJson: string): Promise<unknown>;
500
+ /**
501
+ * This device's half of an ed25519 (FROST) DKG keygen ceremony (#110); resolves with
502
+ * `{ key_package, public_key_package }` (JSON objects). Present only in a client-wasm build with
503
+ * the ed25519 (FROST) exports.
504
+ */
505
+ keygenEddsa?(paramsJson: string): Promise<unknown>;
506
+ /**
507
+ * This device's half of an ed25519 (FROST) two-round sign ceremony (#110); resolves with
508
+ * `{ signature }` (64-byte hex). Present only in a client-wasm build with the ed25519 (FROST) exports.
509
+ */
510
+ signEddsa?(paramsJson: string): Promise<unknown>;
511
+ }
512
+ /** Lazily loads + initializes the client-wasm module (e.g. dynamic `import()` of the wasm-pack pkg). */
513
+ type ClientWasmLoader = () => Promise<ClientWasmModule>;
514
+ /**
515
+ * {@link MpcCore} backed by the waas-core `client-wasm` module (`keygen` / `sign`).
516
+ *
517
+ * The wasm engine is injected via a loader so the SDK stays runtime-agnostic and
518
+ * the core is unit-testable without a real ceremony. The device key share returned
519
+ * by keygen must be sealed and stored on the device — it is never sent to the server.
520
+ */
521
+ declare class WasmMpcCore implements MpcCore {
522
+ private readonly load;
523
+ private modulePromise?;
524
+ constructor(load: ClientWasmLoader);
525
+ private init;
526
+ runKeygen(params: DeviceKeygenParams): Promise<DeviceKeygenResult>;
527
+ runSign(params: DeviceSignParams): Promise<DeviceSignResult>;
528
+ pregeneratePrimes(curve: MpcCurve): Promise<string>;
529
+ runReshareAssemble(params: DeviceReshareAssembleParams): Promise<DeviceReshareAssembleResult>;
530
+ runCompleteReshare(params: DeviceCompleteReshareParams): Promise<DeviceCompleteReshareResult>;
531
+ runMemberKeygen(params: MemberKeygenParams): Promise<DeviceKeygenResult>;
532
+ runMemberSign(params: MemberSignParams): Promise<DeviceSignResult>;
533
+ runEddsaKeygen(params: EddsaKeygenParams): Promise<EddsaKeygenResult>;
534
+ runEddsaSign(params: EddsaSignParams): Promise<EddsaSignResult>;
535
+ /** Build the snake_case params JSON the wasm exports expect from the common routing + extras. */
536
+ private encode;
537
+ /**
538
+ * Build the snake_case params JSON for an n-party (member-bound, #349) member ceremony — the
539
+ * roster-based routing shape (`roles` + `party_index`), not the 2-party `role`/`peer_role` shape
540
+ * {@link encode} builds. The `roles` roster and `party_index` are passed through in `extra` by
541
+ * the caller and land on the wire under those exact keys — what `keygenMember` / `signMember`
542
+ * deserialize.
543
+ */
544
+ private encodeMember;
545
+ /**
546
+ * Build the snake_case params JSON for an ed25519 (FROST, #110) ceremony — the roster-based routing
547
+ * shape (`roles` + `party_index`) the `keygenEddsa` / `signEddsa` exports deserialize. Unlike
548
+ * {@link encodeMember} there is NO `curve` (ed25519-only) and no Paillier primes; the caller passes
549
+ * the ceremony-specific extras (keygen: `threshold`; sign: the FROST packages + participants + message).
550
+ */
551
+ private encodeEddsa;
552
+ }
553
+
554
+ /**
555
+ * The **exact** `@waaskey/client-wasm` version this SDK build is pinned to (issue #40).
556
+ *
557
+ * Pin an exact version — never a floating `^`/`~` range — so a compromised or
558
+ * dependency-confused higher release can't be silently pulled in. Keep this in
559
+ * lockstep with the `@waaskey/client-wasm` entry in `package.json` and with
560
+ * {@link CLIENT_WASM_SHA384}.
561
+ *
562
+ * **`0.2.0` adds the ed25519 (FROST) exports** (`keygenEddsa` / `signEddsa`, waas-core #110) the SDK's
563
+ * ed25519 wallet path drives. It is **published on npm** (`client-wasm-v0.2.0`) and pinned as the SDK's
564
+ * exact, optional peer dependency in `package.json` — so an app that installs `@waaskey/client-wasm`
565
+ * alongside the SDK runs the real ed25519 create/send path end to end.
566
+ */
567
+ declare const CLIENT_WASM_VERSION = "0.2.0";
568
+ /**
569
+ * Verify the integrity of raw wasm bytes against an expected **SHA-384** hash, in
570
+ * Subresource-Integrity (`sha384-<base64>`) form (issue #40).
571
+ *
572
+ * The wasm MPC core handles the plaintext device share, so it must be
573
+ * cryptographically verified **before** `WebAssembly.compile` / `instantiate` — a
574
+ * registry compromise or malicious re-host otherwise silently exfiltrates share-1.
575
+ * Throws (fails closed) on any mismatch.
576
+ */
577
+ declare function verifyWasmIntegrity(bytes: BufferSource, expectedSha384: string): Promise<void>;
578
+ /** Options for {@link createVerifiedClientWasmLoader}. */
579
+ interface VerifiedWasmLoaderOptions {
580
+ /**
581
+ * URL (or path the runtime's `fetch` accepts) of the `@waaskey/client-wasm` `.wasm`
582
+ * binary — vendored/self-hosted so its bytes can be fetched and hashed before use.
583
+ */
584
+ wasmUrl: string | URL;
585
+ /** Expected SHA-384 of the wasm binary, SRI form (`sha384-<base64>`). Pin per {@link CLIENT_WASM_VERSION}. */
586
+ expectedSha384: string;
587
+ /** Custom fetch (non-browser runtimes / tests). Defaults to global `fetch`. */
588
+ fetch?: typeof fetch;
589
+ }
590
+ /**
591
+ * Build an integrity-checked {@link ClientWasmLoader} (issue #40): it fetches the
592
+ * pinned `.wasm` bytes, verifies their SHA-384 against `expectedSha384`, and only
593
+ * **then** compiles + initializes the module — so a tampered binary is rejected
594
+ * before any wasm code runs.
595
+ *
596
+ * Requires a non-auto-initializing (wasm-pack `--target web`) `@waaskey/client-wasm`
597
+ * build, so the verified bytes drive initialization:
598
+ *
599
+ * ```ts
600
+ * const mpc = new WasmMpcCore(
601
+ * createVerifiedClientWasmLoader({ wasmUrl: '/waaskey_client_wasm_bg.wasm', expectedSha384: CLIENT_WASM_SHA384 }),
602
+ * );
603
+ * ```
604
+ */
605
+ declare function createVerifiedClientWasmLoader(options: VerifiedWasmLoaderOptions): ClientWasmLoader;
606
+ /**
607
+ * Convenience loader for the Waaskey client-wasm engine — dynamically imports `@waaskey/client-wasm`
608
+ * (the wasm-pack/bundler build, auto-initialized on import) and returns it as a {@link ClientWasmModule}:
609
+ *
610
+ * ```ts
611
+ * import { Waaskey, WasmMpcCore, loadClientWasm } from '@waaskey/sdk';
612
+ * const waaskey = new Waaskey({ apiKey, mpc: new WasmMpcCore(loadClientWasm), shareStore });
613
+ * ```
614
+ *
615
+ * ⚠️ **No integrity verification (issue #40).** A bundler-target build instantiates the wasm on
616
+ * import, so its bytes cannot be checked first. For production — where a registry compromise or
617
+ * dependency-confusion attack on `@waaskey/client-wasm` could exfiltrate the plaintext device share
618
+ * — use {@link createVerifiedClientWasmLoader} with a vendored `.wasm` and the pinned SHA-384 instead.
619
+ *
620
+ * `@waaskey/client-wasm` is an OPTIONAL peer dependency pinned to {@link CLIENT_WASM_VERSION}: install
621
+ * it alongside the SDK in apps that create wallets. The indirect import specifier keeps it out of
622
+ * static module resolution — so a missing install fails here with an actionable message, not at build.
623
+ */
624
+ declare const loadClientWasm: ClientWasmLoader;
625
+
626
+ /**
627
+ * Client-side pool of pre-generated Paillier safe-primes (#2).
628
+ *
629
+ * Safe-prime generation is the slow part of a device keygen — minutes in single-threaded
630
+ * browser WASM. Primes don't depend on the key, so we generate them **ahead of the hot path**
631
+ * (a Web Worker during onboarding/idle) and cache a small pool; at keygen time we hand one over
632
+ * instantly. The primes are this device's PRIVATE aux material — they are cached locally and
633
+ * NEVER sent to the server (serving them from the API would let the server recover the device's
634
+ * share). Use an encrypted store in production.
635
+ */
636
+ /** Where the pool persists cached primes. Swap in an encrypted IndexedDB store for production. */
637
+ interface PrimePoolStore {
638
+ /** Remove and return one cached prime for `curve`, or undefined if the pool is empty. */
639
+ take(curve: MpcCurve): Promise<string | undefined>;
640
+ /** Add a generated prime to the pool. */
641
+ add(curve: MpcCurve, primes: string): Promise<void>;
642
+ /** Current number of cached primes for `curve`. */
643
+ size(curve: MpcCurve): Promise<number>;
644
+ }
645
+ /** Default in-memory store (lost on reload). Production should persist to an encrypted store. */
646
+ declare class MemoryPrimeStore implements PrimePoolStore {
647
+ private readonly pools;
648
+ take(curve: MpcCurve): Promise<string | undefined>;
649
+ add(curve: MpcCurve, primes: string): Promise<void>;
650
+ size(curve: MpcCurve): Promise<number>;
651
+ }
652
+ /** The minimal core capability the pool needs — just prime generation. */
653
+ interface PrimeGenerator {
654
+ pregeneratePrimes(curve: MpcCurve): Promise<string>;
655
+ }
656
+ interface PrimePoolOptions {
657
+ /** How many spare primes to keep cached per curve. Default 2. */
658
+ targetSize?: number;
659
+ /** Persistence for the pool. Defaults to an in-memory store. */
660
+ store?: PrimePoolStore;
661
+ }
662
+ declare class PrimePool {
663
+ private readonly core;
664
+ private readonly store;
665
+ private readonly targetSize;
666
+ /** Per-curve in-flight refill, so concurrent calls don't over-generate. */
667
+ private readonly refilling;
668
+ constructor(core: PrimeGenerator, options?: PrimePoolOptions);
669
+ /**
670
+ * Top up the pool to the target size — call this OFF the hot path (onboarding/idle, ideally a
671
+ * Web Worker). Deduped per curve, so calling it repeatedly is safe and cheap.
672
+ */
673
+ ensure(curve: MpcCurve): Promise<void>;
674
+ private refill;
675
+ /**
676
+ * Claim a prime for a keygen. Returns a cached one instantly when the pool is warm; otherwise
677
+ * generates one inline (the slow fallback) so keygen never fails on an empty pool. Either way it
678
+ * kicks off a background refill so the next wallet is instant.
679
+ */
680
+ take(curve: MpcCurve): Promise<string>;
681
+ }
682
+
683
+ /**
684
+ * Device key-share storage contract.
685
+ *
686
+ * In a Waaskey 2-of-3 wallet the user's **device** holds one key share (produced by
687
+ * the device-party keygen — see {@link MpcCore}). That share is the user's half of
688
+ * the key and must (a) never be persisted in plaintext, (b) survive a page reload,
689
+ * and (c) be wiped on explicit logout. The SDK depends on the {@link ShareStore}
690
+ * port; the web implementation seals shares with WebCrypto and persists the
691
+ * ciphertext in IndexedDB, and a native Keychain/Keystore core can implement the
692
+ * same port on mobile.
693
+ */
694
+ /** Stores the device's (already-serialized) key share per wallet, encrypted at rest. */
695
+ interface ShareStore {
696
+ /** Seal and persist the share for `walletId` (overwrites any existing one). */
697
+ put(walletId: string, share: string): Promise<void>;
698
+ /** Load and decrypt the share for `walletId`, or `null` if none is stored. */
699
+ get(walletId: string): Promise<string | null>;
700
+ /** Whether a share is stored for `walletId` (does not decrypt it). */
701
+ has(walletId: string): Promise<boolean>;
702
+ /** Remove the stored share for `walletId` (no-op if absent). */
703
+ remove(walletId: string): Promise<void>;
704
+ /** Wipe every stored share — call on explicit logout. */
705
+ clear(): Promise<void>;
706
+ }
707
+ /**
708
+ * Low-level opaque string key/value persistence backing an encrypting
709
+ * {@link ShareStore}. The web adapter is IndexedDB-backed; an in-memory adapter is
710
+ * used for tests / non-persistent (SSR) contexts. It only ever sees ciphertext.
711
+ */
712
+ interface KeyValueStore {
713
+ get(key: string): Promise<string | null>;
714
+ set(key: string, value: string): Promise<void>;
715
+ delete(key: string): Promise<void>;
716
+ /** All stored keys (used to enumerate stored shares). */
717
+ keys(): Promise<string[]>;
718
+ /** Remove every entry. */
719
+ clear(): Promise<void>;
720
+ }
721
+
722
+ /** Options for the {@link EncryptedShareStore} constructor / {@link EncryptedShareStore.browser}. */
723
+ interface EncryptedShareStoreOptions {
724
+ dbName?: string;
725
+ storeName?: string;
726
+ /** Bypass the {@link MIN_SECRET_LENGTH} entropy floor — only when the secret's entropy is guaranteed elsewhere. */
727
+ allowWeakSecret?: boolean;
728
+ }
729
+ /**
730
+ * {@link ShareStore} that seals every share with AES-256-GCM before handing it to a
731
+ * {@link KeyValueStore}, so the backing storage only ever holds ciphertext.
732
+ *
733
+ * The AES key is derived (PBKDF2) from a **secret the app supplies** — from the
734
+ * user's authenticated session, a passkey/PRF, or a device secret — never embedded
735
+ * in the bundle. A random salt is generated once and persisted alongside the data;
736
+ * `clear()` wipes both, so a subsequent login derives a fresh key.
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * const store = EncryptedShareStore.browser(sessionSecret);
741
+ * await store.put(wallet.id, keygen.keyShare); // sealed in IndexedDB
742
+ * const share = await store.get(wallet.id); // decrypted, or null
743
+ * await store.clear(); // on logout
744
+ * ```
745
+ */
746
+ declare class EncryptedShareStore implements ShareStore {
747
+ private readonly kv;
748
+ private readonly secret;
749
+ private keyPromise?;
750
+ constructor(kv: KeyValueStore, secret: string, options?: {
751
+ allowWeakSecret?: boolean;
752
+ });
753
+ /** Default web store: AES-GCM sealing over IndexedDB. */
754
+ static browser(secret: string, options?: EncryptedShareStoreOptions): EncryptedShareStore;
755
+ put(walletId: string, share: string): Promise<void>;
756
+ get(walletId: string): Promise<string | null>;
757
+ has(walletId: string): Promise<boolean>;
758
+ remove(walletId: string): Promise<void>;
759
+ clear(): Promise<void>;
760
+ /** Lazily load (or create) the persisted salt and derive the AES key once. */
761
+ private key;
762
+ private loadKey;
763
+ }
764
+
765
+ /**
766
+ * In-memory {@link KeyValueStore} — non-persistent. Used in tests and as a fallback
767
+ * where IndexedDB is unavailable (e.g. SSR). Shares stored here do NOT survive a
768
+ * reload; for persistent device storage use {@link IndexedDbKeyValueStore}.
769
+ */
770
+ declare class MemoryKeyValueStore implements KeyValueStore {
771
+ private readonly map;
772
+ get(key: string): Promise<string | null>;
773
+ set(key: string, value: string): Promise<void>;
774
+ delete(key: string): Promise<void>;
775
+ keys(): Promise<string[]>;
776
+ clear(): Promise<void>;
777
+ }
778
+
779
+ /**
780
+ * IndexedDB-backed {@link KeyValueStore} — the default persistent backend on web.
781
+ * A single object store holds opaque ciphertext records keyed by string, so device
782
+ * shares survive a reload. Stores only sealed data (see EncryptedShareStore).
783
+ */
784
+ declare class IndexedDbKeyValueStore implements KeyValueStore {
785
+ private readonly dbName;
786
+ private readonly storeName;
787
+ private dbPromise?;
788
+ constructor(dbName?: string, storeName?: string);
789
+ private db;
790
+ private run;
791
+ get(key: string): Promise<string | null>;
792
+ set(key: string, value: string): Promise<void>;
793
+ delete(key: string): Promise<void>;
794
+ keys(): Promise<string[]>;
795
+ clear(): Promise<void>;
796
+ }
797
+
798
+ /**
799
+ * Structured error model for the SDK.
800
+ *
801
+ * Every failure surfaces as a {@link WaaskeyError} carrying a typed {@link WaaskeyErrorCode}
802
+ * — never a bare string — so callers can branch on `error.code` instead of matching
803
+ * message text. Transport/HTTP failures map the API's error envelope
804
+ * (`{ statusCode, message, error }`) to a code; orchestration failures (device keygen,
805
+ * missing share, cancellation) use the SDK-side codes.
806
+ */
807
+ type WaaskeyErrorCode = 'unauthorized' | 'forbidden' | 'not_found' | 'validation' | 'conflict' | 'rate_limited' | 'server_error' | 'network' | 'unsupported_chain' | 'provider_error' | 'device_core_required' | 'keygen_failed' | 'sign_failed' | 'reshare_failed' | 'reshare_pubkey_mismatch' | 'share_not_found' | 'recovery_failed' | 'invalid_recovery_code' | 'backup_failed' | 'wallet_activation_timeout' | 'sign_ceremony_timeout' | 'unauthenticated' | 'firebase_not_enabled' | 'mfa_required' | 'no_member' | 'unsupported' | 'aborted' | 'unknown';
808
+ interface WaaskeyErrorOptions {
809
+ /** HTTP status, when the error originates from an API response. */
810
+ status?: number;
811
+ /** Raw error payload from the API (or other context), for debugging. */
812
+ details?: unknown;
813
+ /** The underlying error that triggered this one. */
814
+ cause?: unknown;
815
+ }
816
+ /** The single error type thrown by the SDK. */
817
+ declare class WaaskeyError extends Error {
818
+ readonly code: WaaskeyErrorCode;
819
+ readonly status?: number;
820
+ readonly details?: unknown;
821
+ constructor(message: string, code: WaaskeyErrorCode, options?: WaaskeyErrorOptions);
822
+ }
823
+
1
824
  /**
2
825
  * Public contract for the Waaskey SDK.
3
826
  *
4
- * These types describe the wire shapes the SDK exchanges with the Waaskey API.
5
- * They will be replaced by types generated from the backend OpenAPI spec once the
6
- * API contract is stable; until then this file is the single source of truth.
827
+ * These types mirror the wire shapes the Waaskey API exchanges (the backend
828
+ * `@waas/types` request/response contracts). They will be replaced by types
829
+ * generated from the backend OpenAPI spec once it is published; until then this
830
+ * file is the single source of truth and must track the API exactly — a drift
831
+ * here is a contract bug.
7
832
  */
833
+
8
834
  /** Chains a wallet can be created on. */
9
835
  type Chain = 'ethereum' | 'polygon' | 'arbitrum' | 'base' | 'optimism' | 'bitcoin' | 'solana';
836
+ /**
837
+ * Threshold-signing curve a wallet uses (backend `WalletCurve`). `ed25519` is threshold EdDSA
838
+ * (FROST, RFC 8032/9591) — the ed25519 chains (Solana, NEAR, Aptos, Sui); the others are cggmp24
839
+ * ECDSA. An `ed25519` wallet's device share and sign flow are the FROST path, distinct from the
840
+ * cggmp24 KeyShare blob (see the `mpc` Eddsa* types).
841
+ */
842
+ type WalletCurve = 'secp256k1' | 'ed25519';
843
+ /**
844
+ * Lifecycle state of a wallet — tracks keygen progress (backend `WalletStatus`).
845
+ * `pending_keygen` is a member-bound wallet (#342/#344) whose keygen roster is still open —
846
+ * waiting for every {@link WalletShareholder} to {@link Wallets.joinCeremony} before the
847
+ * multi-device keygen ceremony runs. Not fundable (no `publicKey`/`address`) until `active`.
848
+ */
849
+ type WalletStatus = 'pending' | 'pending_keygen' | 'active' | 'failed';
850
+ /**
851
+ * Custody role of a single MPC key share, independent of its party-role label
852
+ * (backend `CustodyKind`, #293). Drives the wallet's custody attestation:
853
+ * `platform_signer` / `platform_recovery` are shares WaaS itself holds; the other
854
+ * kinds are shares only the user or a third party holds.
855
+ */
856
+ type CustodyKind = 'user_device' | 'user_backup' | 'platform_signer' | 'platform_recovery' | 'external_party';
857
+ /**
858
+ * The custody posture a wallet's topology attests to (backend `CustodyType`, #293),
859
+ * derived from its {@link CustodyKind} shares vs the threshold `t`:
860
+ * `embedded` when the platform alone holds `>= t` shares (custodial-capable — the
861
+ * default 2-of-3), `self_custody` when the platform holds none, `shared` otherwise
862
+ * (neither party alone meets `t` — a true multi-party quorum).
863
+ */
864
+ type CustodyType = 'embedded' | 'shared' | 'self_custody';
10
865
  /** Options accepted by `new Waaskey(...)`. */
11
866
  interface WaaskeyOptions {
12
867
  /** Publishable API key issued from the Waaskey dashboard. */
@@ -15,81 +870,1812 @@ interface WaaskeyOptions {
15
870
  baseUrl?: string;
16
871
  /** Custom fetch implementation (e.g. for non-browser runtimes). Defaults to global `fetch`. */
17
872
  fetch?: typeof fetch;
873
+ /**
874
+ * Device-party MPC core (e.g. `WasmMpcCore`). Required to create a wallet — the
875
+ * device runs its half of the keygen ceremony with this.
876
+ */
877
+ mpc?: MpcCore;
878
+ /**
879
+ * Where the device key share is sealed and persisted (e.g.
880
+ * `EncryptedShareStore.browser(secret)`). Required to create a wallet.
881
+ */
882
+ shareStore?: ShareStore;
883
+ /**
884
+ * Per-chain provider config for client-side balance reads (RPC URL or a custom
885
+ * provider). EVM chains have built-in public-RPC defaults; override them here.
886
+ */
887
+ chains?: Partial<Record<Chain, ChainConfig>>;
888
+ /**
889
+ * Analytics for the tenant dashboard (wallet created/signed/recovered — no PII or secrets).
890
+ * Defaults to an HTTP sink to the Waaskey API; pass a custom {@link AnalyticsSink}, or `false`
891
+ * to opt out entirely.
892
+ */
893
+ analytics?: AnalyticsSink | false;
18
894
  }
19
895
  /** Parameters for creating a wallet. */
20
896
  interface CreateWalletParams {
21
- /** Your end-user's id in your own system; binds the wallet to that user. */
22
- userId?: string;
23
- /** Chain the wallet is created on. */
897
+ /** Chain the wallet is created on (determines the signing curve). */
24
898
  chain: Chain;
899
+ /** Human label for the wallet. Defaults to the chain name. */
900
+ label?: string;
901
+ /**
902
+ * Signing threshold `t` for this wallet's `t`-of-`n` MPC key. Optional — omit the whole
903
+ * custody policy for the deployment default (embedded 2-of-3). When a topology is supplied
904
+ * it must satisfy `2 <= threshold <= parties.length` (validated client-side before the request).
905
+ */
906
+ threshold?: number;
907
+ /**
908
+ * Ordered party roles of the keygen topology, length `n` (e.g. `['device','server','recovery']`).
909
+ * Optional. When supplied it must be at least 2 distinct, non-empty role strings.
910
+ */
911
+ parties?: string[];
912
+ /**
913
+ * Per-party custody kind, parallel to {@link parties} (same length, same order) (#293). Optional —
914
+ * when omitted the backend derives the kind from each party's role name. When supplied it must
915
+ * have exactly one entry per party.
916
+ */
917
+ custodyKinds?: CustodyKind[];
918
+ /**
919
+ * Requested custody posture (#293) — the backend enforces it against the resolved
920
+ * `(threshold, custodyKinds)` and refuses the create (400) when the topology does not attest to it
921
+ * (e.g. asking for `self_custody` but the shares are platform-heavy enough to be `embedded`).
922
+ */
923
+ custodyType?: CustodyType;
924
+ }
925
+ /** Options for `wallets.create(...)`. */
926
+ interface CreateWalletOptions {
927
+ /** Cancel the create (request + ceremony + activation wait). */
928
+ signal?: AbortSignal;
929
+ /** Wait until the wallet is ACTIVE (keygen finished) before resolving. Default `true`. */
930
+ waitForActive?: boolean;
931
+ /** Max time to wait for activation, ms. Default 60000. */
932
+ activationTimeoutMs?: number;
933
+ /** Poll interval while waiting for activation, ms. Default 1000. */
934
+ pollIntervalMs?: number;
935
+ /**
936
+ * Recovery-backup material for a **non-custodial** `[device, server, user_backup]` create (#351/#78):
937
+ * REQUIRED when the returned ceremony carries a `user_backup` party ({@link WalletCeremony.additionalParties}).
938
+ * In that topology the device runs a SECOND client party (`user_backup`) whose share replaces the
939
+ * platform's recovery share — so it must be sealed with the user's recovery code and registered as an
940
+ * opaque ciphertext server-side at keygen time (the same mechanism as `recovery.register`), or the
941
+ * wallet would be unrecoverable on device loss. Absent/ignored for a single-client-party create
942
+ * (`[device, server]`, custodial `[device, server, recovery]`, ed25519).
943
+ */
944
+ backup?: WalletBackupParams;
945
+ }
946
+ /**
947
+ * Recovery-backup factors sealing + enrolling the client-held `user_backup` share on a non-custodial
948
+ * create ({@link CreateWalletOptions.backup}, #351/#78) — the `recovery.register` material minus the
949
+ * `share` (the SDK supplies the freshly-generated user_backup share itself). The `recoveryCode` is
950
+ * **caller-provided** (unlike `recovery.register`, which may generate one): `create` returns the
951
+ * {@link Wallet}, so it cannot hand a generated code back — the caller mints one up front (e.g. with
952
+ * `generateRecoveryCode()`), shows it to the user, and passes it here. It is a client-side sealing
953
+ * secret and never leaves the device (only its SHA-256 is enrolled), so a lost code is an
954
+ * unrecoverable backup.
955
+ */
956
+ interface WalletBackupParams {
957
+ /** High-entropy recovery code that seals the user_backup share. Show it to the user once — it is the only key to the backup and never leaves the client. */
958
+ recoveryCode: string;
959
+ /** Base32 TOTP secret to enrol the authenticator factor. */
960
+ totpSecret: string;
961
+ /** Email address to enrol the email-OTP factor. */
962
+ email: string;
963
+ /** Extra factor enrolments beyond the standard three. */
964
+ extraFactors?: FactorEnrollment[];
965
+ }
966
+ /** Options for `wallets.joinCeremony(...)` (#349). */
967
+ interface JoinCeremonyOptions {
968
+ /** Cancel the join (roster ack + the relay keygen ceremony). */
969
+ signal?: AbortSignal;
970
+ }
971
+ /** Options for `wallets.joinSignCeremony(...)` (#349). */
972
+ interface JoinSignCeremonyOptions {
973
+ /** Cancel the join (the approve vote + waiting for the quorum + the relay sign ceremony). */
974
+ signal?: AbortSignal;
975
+ /** Max time to wait for the t-of-n quorum to fix and select this member, ms. Default 60000. */
976
+ readyTimeoutMs?: number;
977
+ /** Poll interval while waiting for the quorum, ms. Default 1000. */
978
+ pollIntervalMs?: number;
979
+ }
980
+ /**
981
+ * A WebAuthn assertion attached for passkey step-up (issue #21/#39) — the structural
982
+ * shape of `@simplewebauthn/browser`'s `AuthenticationResponseJSON`. Declared here (not
983
+ * imported) so the field is typed without a hard dependency on the optional passkey peer.
984
+ */
985
+ interface PasskeyAssertionJSON {
986
+ id: string;
987
+ rawId: string;
988
+ type: string;
989
+ response: {
990
+ clientDataJSON: string;
991
+ authenticatorData: string;
992
+ signature: string;
993
+ userHandle?: string;
994
+ };
995
+ authenticatorAttachment?: unknown;
996
+ clientExtensionResults?: unknown;
25
997
  }
26
- /** A wallet as returned by the API. */
998
+ /** Options for signing. */
999
+ interface SignOptions {
1000
+ /** Cancel the signing request. */
1001
+ signal?: AbortSignal;
1002
+ /**
1003
+ * When `true`, run a WebAuthn assertion before submitting (passkey step-up,
1004
+ * Pattern B / issue #21/#39). The SDK fetches a server-issued one-time challenge,
1005
+ * prompts the user's authenticator over it, and attaches the resulting
1006
+ * `passkeyAssertion` (+ its `passkeyChallengeId`) to the POST body.
1007
+ *
1008
+ * Requires WebAuthn / `@simplewebauthn/browser` to be available. Throws
1009
+ * `WaaskeyError('unsupported')` when passkeys are unavailable, and
1010
+ * `WaaskeyError('aborted')` when the user cancels the prompt.
1011
+ *
1012
+ * Alternatively pass a pre-built assertion directly via `passkeyAssertion`.
1013
+ */
1014
+ requirePasskey?: boolean;
1015
+ /**
1016
+ * A pre-built WebAuthn `AuthenticationResponseJSON` to attach as
1017
+ * `passkeyAssertion` on the sign request. Takes precedence over
1018
+ * `requirePasskey` when supplied.
1019
+ */
1020
+ passkeyAssertion?: PasskeyAssertionJSON;
1021
+ /**
1022
+ * Id of the server-issued step-up challenge the pre-built `passkeyAssertion` was
1023
+ * produced against (from {@link StepUpChallengeResponse}). The server verifies the
1024
+ * assertion against this one-time challenge and burns it. Required only when you
1025
+ * supply `passkeyAssertion` yourself; with `requirePasskey` the SDK fetches and
1026
+ * echoes it for you.
1027
+ */
1028
+ passkeyChallengeId?: string;
1029
+ /**
1030
+ * Credential id to restrict the passkey assertion to (used with `requirePasskey`).
1031
+ * When omitted the browser presents all resident credentials for the RP.
1032
+ */
1033
+ passkeyCredentialId?: string;
1034
+ }
1035
+ /** Options for sending a transaction. */
1036
+ interface SendOptions {
1037
+ /** Cancel the send request. */
1038
+ signal?: AbortSignal;
1039
+ /**
1040
+ * When `true`, run a WebAuthn assertion before submitting (passkey step-up,
1041
+ * Pattern B / issue #21/#39), over a server-issued one-time challenge.
1042
+ */
1043
+ requirePasskey?: boolean;
1044
+ /**
1045
+ * A pre-built WebAuthn `AuthenticationResponseJSON` to attach as
1046
+ * `passkeyAssertion` on the send request. Takes precedence over
1047
+ * `requirePasskey` when supplied.
1048
+ */
1049
+ passkeyAssertion?: PasskeyAssertionJSON;
1050
+ /**
1051
+ * Id of the server-issued step-up challenge the pre-built `passkeyAssertion` was
1052
+ * produced against (from {@link StepUpChallengeResponse}). Required only when you
1053
+ * supply `passkeyAssertion` yourself; with `requirePasskey` the SDK handles it.
1054
+ */
1055
+ passkeyChallengeId?: string;
1056
+ /** Credential id to restrict the passkey assertion to (used with `requirePasskey`). */
1057
+ passkeyCredentialId?: string;
1058
+ }
1059
+ /** The step-up operation a passkey challenge is minted for (issue #39). */
1060
+ type StepUpOperation = 'sign' | 'send';
1061
+ /**
1062
+ * A server-issued **one-time** WebAuthn step-up challenge (issue #39). The SDK
1063
+ * fetches this before running a passkey assertion so the challenge is a fresh
1064
+ * server nonce — not derived from the request payload — which the server verifies
1065
+ * and **burns** on use, making a captured assertion non-replayable.
1066
+ */
1067
+ interface StepUpChallengeResponse {
1068
+ /** Opaque id the client echoes back (`passkeyChallengeId`) so the server can verify + burn the challenge. */
1069
+ challengeId: string;
1070
+ /** base64url one-time challenge nonce the authenticator signs over. */
1071
+ challenge: string;
1072
+ }
1073
+ /** Per-party relay coordination returned on create so the device can join the keygen ceremony (backend `IWalletCeremony`). */
1074
+ interface WalletCeremony {
1075
+ relayUrl: string;
1076
+ sessionId: string;
1077
+ curve: WalletCurve;
1078
+ role: string;
1079
+ peerRole: string;
1080
+ partyIndex: number;
1081
+ peerPartyIndex: number;
1082
+ parties: number;
1083
+ threshold: number;
1084
+ /** Short-lived relay token (JWT) the device presents to join this keygen session; present only when relay auth is enabled. */
1085
+ relayToken?: string;
1086
+ /**
1087
+ * The FROST DKG round-2 encryption roster — **ed25519 keygen only** (#114, backend `IWalletCeremony`).
1088
+ * One X25519 encryption PUBLIC key (32-byte hex) per party, in `parties`/protocol-index order, so
1089
+ * `encPubkeys[i]` is the key of FROST participant `i + 1` (e.g. `[deviceEncPubkey, serverEncPubkey]`
1090
+ * for `[device, server]`). The device seals its round-2 packages to these keys (chiefly the server's).
1091
+ * The device's own entry is the `deviceEncPubkey` it supplied on create, echoed back so it builds the
1092
+ * exact same ordered roster the signer uses. Absent for secp keygen and on a refresh fetch.
1093
+ */
1094
+ encPubkeys?: string[];
1095
+ /**
1096
+ * Additional CLIENT-run party descriptors the SAME caller must ALSO drive for this keygen ceremony,
1097
+ * beyond the primary `device` party this object describes (backend `IWalletCeremony`, #396/#351/#78).
1098
+ * Present for the non-custodial default `[device, server, user_backup]` (secp256k1): ONE entry — the
1099
+ * client-held `user_backup` party (`role: 'user_backup'`, its own `partyIndex`, `peerRole: 'server'`,
1100
+ * and its OWN role-scoped {@link relayToken}) — because the platform signer drives ONLY the `server`
1101
+ * share, so the one device must run BOTH its `device` party (this object) AND the `user_backup` party.
1102
+ * Each entry is a full, self-contained {@link WalletCeremony} joining the SAME relay `sessionId`.
1103
+ *
1104
+ * ADDITIVE: absent for single-client-party rosters (`[device, server]`, custodial
1105
+ * `[device, server, recovery]`) and for ed25519 — a client that reads only `ceremony` and ignores
1106
+ * this field keeps running exactly one party. Each entry's own `additionalParties` is always absent
1107
+ * (the list is flat, never recursive).
1108
+ */
1109
+ additionalParties?: WalletCeremony[];
1110
+ }
1111
+ /** A wallet as returned by the API (backend `WalletResponse`). Dates are ISO strings on the wire. */
27
1112
  interface WalletData {
28
- /** Waaskey wallet id, e.g. `wlt_...`. */
29
1113
  id: string;
30
- /** On-chain address. */
31
- address: string;
32
- /** Chain the wallet belongs to. */
33
- chain: Chain;
1114
+ tenantId: string;
1115
+ label: string;
1116
+ curve: WalletCurve;
1117
+ status: WalletStatus;
1118
+ /** Compressed public key (hex). Set once keygen completes. */
1119
+ publicKey?: string;
1120
+ /** On-chain address derived from the public key. Set once keygen completes. */
1121
+ address?: string;
1122
+ /** When the key shares were last proactively rotated. */
1123
+ keyRefreshedAt?: string;
1124
+ /** Effective signing threshold `t` of the wallet's `t`-of-`n` key (the persisted value, or the deployment default). */
1125
+ threshold: number;
1126
+ /** Effective ordered party roles of the wallet's keygen topology, length `n`. */
1127
+ parties: string[];
1128
+ /** Effective per-party custody kind, parallel to {@link parties} (same length, same order). */
1129
+ custodyKinds: CustodyKind[];
1130
+ /** Count of {@link custodyKinds} entries the platform itself holds (`platform_signer` + `platform_recovery`). */
1131
+ platformShareCount: number;
1132
+ /**
1133
+ * The wallet's custody attestation (#293): `embedded` when `platformShareCount >= threshold`
1134
+ * (platform alone is custodial-capable — the default 2-of-3), `self_custody` when
1135
+ * `platformShareCount === 0`, else `shared`. See {@link isNonCustodial}.
1136
+ */
1137
+ custodyType: CustodyType;
1138
+ createdAt: string;
1139
+ /**
1140
+ * Keygen ceremony params — present on create and on `GET` while the wallet is `pending`
1141
+ * (backend `WalletResponse.ceremony`). **Absent** for a member-bound wallet (#342): it is
1142
+ * provisioned `pending_keygen` with no single ceremony to join here — each
1143
+ * {@link WalletShareholder} instead runs {@link Wallets.joinCeremony} for its own party.
1144
+ */
1145
+ ceremony?: WalletCeremony;
34
1146
  }
35
- /** Error thrown for any non-2xx API response. */
36
- declare class WaaskeyError extends Error {
37
- readonly status: number;
38
- readonly code?: string | undefined;
39
- constructor(message: string, status: number, code?: string | undefined);
1147
+ /**
1148
+ * Response to creating a wallet (backend `CreateWalletResponse`) — structurally identical to
1149
+ * {@link WalletData} (`ceremony` lives there now, since `GET` returns it too while pending).
1150
+ */
1151
+ type CreateWalletResponse = WalletData;
1152
+ /** Parameters for `wallets.createWallet(...)` — the member-bound create (#342). */
1153
+ interface CreateMemberWalletParams {
1154
+ /** Human label for the wallet. */
1155
+ label: string;
1156
+ /** Elliptic curve for threshold signing. Defaults to secp256k1 (the backend default) when omitted. */
1157
+ curve?: WalletCurve;
1158
+ /**
1159
+ * The `N` org membership ids that each hold one share of this wallet's key. The backend derives
1160
+ * the full topology from them — `n = N+1` parties (the N members + exactly one platform share),
1161
+ * `parties = ['member:<membershipId>' × N, 'platform']`. Every id must be a membership of the
1162
+ * caller's tenant with the `canHoldShare` capability, and distinct.
1163
+ */
1164
+ shareholderMembershipIds: string[];
1165
+ /** Signing threshold `t` (`2 <= t <= N+1`). Defaults to 2 when omitted. */
1166
+ threshold?: number;
1167
+ }
1168
+ /**
1169
+ * A single org-member share-holder of a member-bound wallet's `t`-of-`n` key (#342, backend
1170
+ * `IWalletShareholder`). The N member share-holders plus exactly one (non-row) platform share make
1171
+ * up the wallet's N+1 topology.
1172
+ */
1173
+ interface WalletShareholder {
1174
+ id: string;
1175
+ tenantId: string;
1176
+ walletId: string;
1177
+ /** The membership that holds this share — must have `canHoldShare`. */
1178
+ membershipId: string;
1179
+ /** 0-based index of this party in the wallet's topology (the protocol party index). */
1180
+ partyIndex: number;
1181
+ /** Deterministic protocol role string for this party — `member:<membershipId>`. */
1182
+ role: string;
1183
+ /** Custody kind of this share (a member share-holder is always `user_device`). */
1184
+ custodyKind: CustodyKind;
1185
+ /** The member's device this share is bound to, once enrolled. Absent before it binds. */
1186
+ deviceId?: string;
1187
+ /** When this member's device joined/ack'd the keygen roster ({@link Wallets.joinCeremony}). Absent until it acks. */
1188
+ joinedAt?: string;
1189
+ createdAt: string;
1190
+ }
1191
+ /**
1192
+ * The CALLING member's own party of a member-bound wallet's multi-device keygen ceremony (#344,
1193
+ * backend `IMemberCeremony`), returned by {@link Wallets.joinCeremony}'s internal `ceremony/mine`
1194
+ * fetch. Carries only the caller's own party params — never another party's. Unlike
1195
+ * {@link WalletCeremony} (the 2-party device/server keygen), an n-party member ceremony has no
1196
+ * single peer: every other member + the platform party join the SAME relay session, so the caller
1197
+ * only needs its own `partyIndex`, the total party count `parties`, and a relay token bound to its
1198
+ * `{sessionId, role, sub=membershipId}`.
1199
+ */
1200
+ interface MemberCeremony {
1201
+ /** Relay websocket URL the member device connects to. */
1202
+ relayUrl: string;
1203
+ /** Ceremony id shared by every party on the relay — the wallet id (registered verbatim). */
1204
+ sessionId: string;
1205
+ curve: WalletCurve;
1206
+ /** The caller's own relay routing role — `member:<membershipId>` (registered byte-for-byte). */
1207
+ role: string;
1208
+ /** The caller's own 0-based protocol party index in the wallet's topology. */
1209
+ partyIndex: number;
1210
+ /** Total parties `n` (= N members + the single platform party). */
1211
+ parties: number;
1212
+ /** Signing threshold `t`. */
1213
+ threshold: number;
1214
+ /**
1215
+ * Short-lived relay token (JWT) the device presents to join this session as {@link role}. Bound
1216
+ * to `{sessionId, role, sub=membershipId}`; present only when relay authentication is enabled.
1217
+ */
1218
+ relayToken?: string;
1219
+ }
1220
+ /**
1221
+ * Result of a member device acking the keygen roster (#344, backend `MemberCeremonyJoinResponse`) —
1222
+ * reports roster progress so the caller can render "3 of 4 devices ready" and learn when the
1223
+ * multi-device keygen has started ({@link rosterComplete} flips true on the last member's join).
1224
+ */
1225
+ interface MemberCeremonyJoinResponse {
1226
+ walletId: string;
1227
+ /** The wallet status after the ack — `pending_keygen` while filling / running, `active` once complete. */
1228
+ status: WalletStatus;
1229
+ /** How many of the wallet's member share-holders have joined/ack'd so far. */
1230
+ joined: number;
1231
+ /** The total member share-holders `N` that must join before keygen runs. */
1232
+ total: number;
1233
+ /** True once every member has joined — the multi-device keygen ceremony has been triggered. */
1234
+ rosterComplete: boolean;
1235
+ }
1236
+ /** Response to signing (backend `SignMessageResponse`). */
1237
+ interface SignMessageResponse {
1238
+ walletId: string;
1239
+ /** Hex-encoded signature produced by the MPC protocol. */
1240
+ signature: string;
1241
+ }
1242
+ /**
1243
+ * Lifecycle of a send/sweep signing activity (backend `TxStatus`). WaaS is
1244
+ * **sign-only** — a send is built (`pending`), MPC-signed (`signed`, terminal:
1245
+ * the signed raw tx is returned for the client to broadcast), or its ceremony
1246
+ * fails (`failed`). WaaS never broadcasts, so there is no on-chain/broadcast state.
1247
+ */
1248
+ type TxStatus = 'pending' | 'signed' | 'failed';
1249
+ /** Parameters to send a transaction (backend `SendTxRequest`). */
1250
+ interface SendParams {
1251
+ /** Target chain id, e.g. `evm:1`, `evm:11155111`. */
1252
+ chainId: string;
1253
+ /** Destination address. */
1254
+ to: string;
1255
+ /** Transfer amount in the chain's base unit (wei), as a numeric string. */
1256
+ value?: string;
1257
+ /** Arbitrary call data (hex) for contract interactions. */
1258
+ data?: string;
1259
+ }
1260
+ /**
1261
+ * Result of a send (backend `SendTxResponse`). WaaS is **sign-only**: it builds
1262
+ * and MPC-signs the transaction and returns the signed raw tx — it does **not**
1263
+ * broadcast it. Submit `signedTx` from your own node/provider (see
1264
+ * {@link Waaskey.broadcast} or your own submitter).
1265
+ */
1266
+ interface SendResult {
1267
+ walletId: string;
1268
+ chainId: string;
1269
+ /**
1270
+ * The signed raw transaction — the canonical broadcast payload the **client**
1271
+ * submits to its own node (an EVM raw RLP tx hex, a Bitcoin tx hex, …). WaaS
1272
+ * never broadcasts this.
1273
+ */
1274
+ signedTx: string;
1275
+ /**
1276
+ * The transaction id, computed offline as a deterministic hash of the signed
1277
+ * tx — for reference/tracking only. It is **not** fetched from a node and is
1278
+ * not proof of broadcast or confirmation.
1279
+ */
1280
+ txHash: string;
1281
+ /** The 32-byte digest the MPC signer signed. */
1282
+ digest: string;
1283
+ }
1284
+ /**
1285
+ * The relay coordination + raw message the device co-signs for an ed25519 send (backend
1286
+ * `EddsaSendSessionResponse`, #110). Returned by the START phase (`POST …/send-session`). Unlike the
1287
+ * secp {@link SignSessionResponse} (a 32-byte digest + 2-party peer roles), this carries the FROST
1288
+ * signing quorum roster (`roles` in `participants` order) and the WHOLE `message` bytes (ed25519 signs
1289
+ * the message, not a digest).
1290
+ */
1291
+ interface EddsaSendSession {
1292
+ /** The pending signing-activity row id — echoed back to the ASSEMBLE phase to finalize the signed tx. */
1293
+ txId: string;
1294
+ /** Relay websocket URL the device connects to. */
1295
+ relayUrl: string;
1296
+ /** Relay session id shared by the device + server parties. */
1297
+ sessionId: string;
1298
+ /** The FROST signing quorum's relay roles, in `participants` order (`roles[i]` ↔ `participants[i]`), e.g. `['device','server']`. */
1299
+ roles: string[];
1300
+ /** The 1-based FROST identifiers of the quorum, parallel to {@link roles} (e.g. `[1, 2]`). */
1301
+ participants: number[];
1302
+ /** This device's own 0-based slot into {@link roles} (its position in the quorum). */
1303
+ signerPosition: number;
1304
+ /** The raw message bytes to sign, hex (`0x` prefix optional) — the chain adapter's serialized tx message. */
1305
+ message: string;
1306
+ /** Short-lived relay token (JWT) the device presents to join this session; present only when relay auth is enabled. */
1307
+ relayToken?: string;
1308
+ }
1309
+ /** Body of the ASSEMBLE phase (`POST …/send-session/:txId/assemble`) — the aggregated ed25519 signature the backend embeds into the wire tx (backend `AssembleEddsaTxRequest`). */
1310
+ interface EddsaAssembleRequest {
1311
+ /** The 64-byte RFC 8032 ed25519 signature (hex) the device + server co-produced. */
1312
+ signature: string;
1313
+ }
1314
+ /** How a signature was produced (backend `SignatureKind`). */
1315
+ type SignatureKind = 'message' | 'personal_sign' | 'typed_data' | 'session' | 'transaction';
1316
+ /**
1317
+ * A record in the wallet's unified **signing activity** (backend `SignatureResponse`,
1318
+ * #289). A raw sign is `kind` + `digest` + `signature`; a send/sweep (`kind =
1319
+ * 'transaction'`) additionally carries the tx fields, including the `signedTx` the
1320
+ * client broadcasts. Dates are ISO strings on the wire.
1321
+ */
1322
+ interface Signature {
1323
+ id: string;
1324
+ tenantId: string;
1325
+ walletId: string;
1326
+ kind: SignatureKind;
1327
+ /** 32-byte hex digest that was signed. */
1328
+ digest?: string;
1329
+ /** Hex-encoded signature from the MPC protocol. */
1330
+ signature?: string;
1331
+ /** Chain id, e.g. `evm:1` (send/sweep only). */
1332
+ chainId?: string;
1333
+ /** Destination address (send/sweep only). */
1334
+ to?: string;
1335
+ /** Amount in the chain's base unit, numeric string (send/sweep value transfer). */
1336
+ value?: string;
1337
+ /** The signed raw transaction returned to the client to broadcast (send/sweep, once signed). */
1338
+ signedTx?: string;
1339
+ /** Transaction id computed offline from the signed tx (send/sweep, once signed). */
1340
+ txHash?: string;
1341
+ /** Send/sweep lifecycle status (absent for a plain sign). */
1342
+ status?: TxStatus;
1343
+ createdAt: string;
1344
+ }
1345
+ /** A wallet operation gated behind approval before its MPC ceremony runs (backend `WalletActionType`). */
1346
+ type WalletActionType = 'sign' | 'send';
1347
+ /**
1348
+ * Lifecycle of an asynchronous, approval-gated signing request (backend `SigningRequestStatus`).
1349
+ * `requested` is the single device-approval flow (#229, back-compat); `pending_approval` is the
1350
+ * M-of-N approver-quorum flow (#309) — distinct so a caller can tell the two apart. `approved`
1351
+ * means the quorum settled and the MPC ceremony started; it flips to `signed` once the ceremony
1352
+ * completes, or `declined`/`expired`/`failed` otherwise.
1353
+ */
1354
+ type SigningRequestStatus = 'requested' | 'pending_approval' | 'approved' | 'signed' | 'declined' | 'expired' | 'failed';
1355
+ /**
1356
+ * Relay coordination to run an approved request's MPC ceremony (backend `SignSessionResponse`).
1357
+ * Returned only from `approveSignRequest` of a `sign` action, once the approval requirement is
1358
+ * met — the device runs its half of the ceremony with these params.
1359
+ */
1360
+ interface SignSessionResponse {
1361
+ relayUrl: string;
1362
+ sessionId: string;
1363
+ curve: WalletCurve;
1364
+ /** The device party's relay routing id (e.g. `"device"`). */
1365
+ role: string;
1366
+ /** The server party's relay routing id (e.g. `"server"`). */
1367
+ peerRole: string;
1368
+ /** This device's signer slot — its keygen index among the participants. */
1369
+ signerPosition: number;
1370
+ /** Keygen indices of the signing quorum, in protocol order (e.g. `[0, 1]`). */
1371
+ participants: number[];
1372
+ /** Short-lived relay token (JWT) the device presents to join this session; present only when relay auth is enabled. */
1373
+ relayToken?: string;
1374
+ /** Additive BIP32 child tweak (32-byte hex) applied when signing under a derived deposit address. */
1375
+ tweak?: string;
1376
+ /** The 32-byte hex digest to sign, present only when the server built the transaction server-side. */
1377
+ digest?: string;
1378
+ }
1379
+ /**
1380
+ * An asynchronous wallet action (sign/send) awaiting approval — either the device owner
1381
+ * (single-approval, #229) or an M-of-N approver quorum (#309) — (backend `SigningRequestResponse`).
1382
+ */
1383
+ interface SigningRequestResponse {
1384
+ id: string;
1385
+ walletId: string;
1386
+ /** The operation being approved. */
1387
+ action: WalletActionType;
1388
+ status: SigningRequestStatus;
1389
+ /** 32-byte hex digest being signed (the message for `sign`; the tx digest for `send`, once built). */
1390
+ digest?: string;
1391
+ /** Present once a `sign` action is approved and signed. */
1392
+ signature?: string;
1393
+ /** Transaction id computed offline from the signed tx. Present once a `send` action is signed. */
1394
+ txHash?: string;
1395
+ createdAt: string;
1396
+ /** ISO time after which an un-approved request expires. */
1397
+ expiresAt: string;
1398
+ /**
1399
+ * Returned only from `approveSignRequest` of a `sign` action: the relay coordination params
1400
+ * the approving device runs its half of the MPC ceremony with — immediately for a non-quorum
1401
+ * request, or once an approver quorum settles APPROVED. Absent on `get` and on `send` approvals.
1402
+ */
1403
+ session?: SignSessionResponse;
1404
+ /**
1405
+ * Under an M-of-N approver quorum (#309): the number of further approvals still needed before
1406
+ * the MPC ceremony runs. `0` once the quorum is reached (the ceremony has started and a
1407
+ * `session` is returned). Absent for a non-quorum (single device-approval) request.
1408
+ */
1409
+ approvalsRemaining?: number;
1410
+ }
1411
+ /**
1412
+ * The CALLING member's own party of a member-bound sign-request's multi-device SIGN ceremony
1413
+ * (#347, backend `IMemberSignCeremony`), returned by {@link Wallets.joinSignCeremony}'s internal
1414
+ * `sign-requests/:reqId/ceremony/mine` fetch. The signing analogue of {@link MemberCeremony}
1415
+ * (keygen): only the caller's own params, never another party's. Because a sign is a t-of-n
1416
+ * SELECTION (only `t` of the N+1 parties sign), the participant set is not known until `t-1`
1417
+ * members have approved and the quorum is FIXED — so the quorum fields appear only once
1418
+ * {@link ready}: `false` while the roster is still collecting approvals, or when the caller was
1419
+ * not selected into the fixed quorum.
1420
+ */
1421
+ interface MemberSignCeremony {
1422
+ /** Relay websocket URL the member device connects to. */
1423
+ relayUrl: string;
1424
+ /** The sign ceremony's relay session id (unique per sign-request) — registered verbatim. */
1425
+ sessionId: string;
1426
+ curve: WalletCurve;
1427
+ /** The caller's own relay routing role — `member:<membershipId>` (registered byte-for-byte). */
1428
+ role: string;
1429
+ /**
1430
+ * `true` once the quorum is fixed AND the caller is one of its `t` signing parties — only then
1431
+ * are the quorum fields below present and the device can run its half.
1432
+ */
1433
+ ready: boolean;
1434
+ /** The fixed quorum's relay roles in signing order (this ceremony's `PartyRouting`). Present only when {@link ready}. */
1435
+ quorumRoles?: string[];
1436
+ /** The caller's 0-based signer position within {@link quorumRoles}/{@link participants}. Present only when {@link ready}. */
1437
+ signerPosition?: number;
1438
+ /** Keygen indices of the fixed quorum, in signing order. Present only when {@link ready}. */
1439
+ participants?: number[];
1440
+ /** The 32-byte hex digest to sign. Present only when {@link ready}. */
1441
+ digest?: string;
1442
+ /**
1443
+ * Short-lived relay token (JWT) bound to `{sessionId, role, sub=membershipId}`. Present only
1444
+ * when relay authentication is enabled.
1445
+ */
1446
+ relayToken?: string;
1447
+ }
1448
+ /** Options for the optional client-side broadcast helper ({@link Waaskey.broadcast}). */
1449
+ interface BroadcastOptions {
1450
+ /** JSON-RPC endpoint of **your** node/provider to submit the signed tx to. */
1451
+ rpcUrl: string;
1452
+ /**
1453
+ * Chain id (e.g. `evm:1`) — reserved for future per-chain routing. The helper
1454
+ * assumes an EVM raw tx (`eth_sendRawTransaction`) today.
1455
+ */
1456
+ chainId?: string;
1457
+ /** Custom fetch implementation (non-browser runtimes / tests). Defaults to global `fetch`. */
1458
+ fetch?: typeof fetch;
1459
+ /** Cancel the broadcast request. */
1460
+ signal?: AbortSignal;
1461
+ }
1462
+ /** Result of a client-side broadcast — the hash the node returned for the submitted tx. */
1463
+ interface BroadcastResult {
1464
+ /** Transaction hash the node returned when it accepted the raw tx. */
1465
+ txHash: string;
1466
+ }
1467
+ /** A page of results (backend `Pagination<T>`). */
1468
+ interface Page<T> {
1469
+ items: T[];
1470
+ /** Total matching records across all pages. */
1471
+ total: number;
1472
+ /** 1-based page number returned. */
1473
+ page: number;
1474
+ /** Page size applied. */
1475
+ limit: number;
1476
+ }
1477
+ /** Query for a paginated list. */
1478
+ interface PageQuery {
1479
+ page?: number;
1480
+ limit?: number;
1481
+ }
1482
+ /**
1483
+ * Query for a wallet's signing requests — {@link PageQuery} plus an optional status filter
1484
+ * (e.g. `pending_approval` to build an approver queue, #329). Backed by the API's
1485
+ * `status` query param on `GET /v1/wallets/{id}/sign-requests` (#332), which paginates the
1486
+ * filtered set correctly (unlike a client-side filter over an unfiltered page).
1487
+ */
1488
+ interface SignRequestsQuery extends PageQuery {
1489
+ status?: SigningRequestStatus;
1490
+ }
1491
+ /** Parameters for an on-ramp widget URL (backend `GetWidgetUrlRequest`). */
1492
+ interface OnrampWidgetParams {
1493
+ /** Wallet address the purchased crypto is delivered to. */
1494
+ walletAddress: string;
1495
+ /** Crypto to buy, e.g. `ETH`. */
1496
+ cryptoCurrency: string;
1497
+ /** Chain id, e.g. `evm:1`. */
1498
+ chainId: string;
1499
+ /** Fiat the user pays with (defaults to USD server-side). */
1500
+ fiatCurrency?: string;
1501
+ /** Pre-fill the fiat amount. */
1502
+ fiatAmount?: number;
1503
+ }
1504
+ /** A provider on-ramp widget URL (backend `OnrampWidgetUrl`). */
1505
+ interface OnrampWidgetUrl {
1506
+ /** Open this URL to launch the on-ramp widget. */
1507
+ url: string;
1508
+ /** Provider that generated the URL, e.g. `transak`. */
1509
+ provider: string;
1510
+ /** ISO timestamp when the signed URL expires, if applicable. */
1511
+ expiresAt?: string;
1512
+ }
1513
+ /** The recovery factors a wallet enrols (backend `RecoveryFactor`). */
1514
+ type RecoveryFactor = 'recovery_code' | 'totp' | 'email_otp';
1515
+ /**
1516
+ * Enrolment of one factor at register time (backend `FactorEnrollment`).
1517
+ *
1518
+ * Contract A: the recovery code is a client-side **sealing secret** and must never
1519
+ * reach the server, so the `recovery_code` factor enrols `credentialHash` (the
1520
+ * lowercase-hex SHA-256 of the code), never the code itself. `totp` / `email_otp`
1521
+ * enrol their non-sealing `credential` (base32 secret / email) as before.
1522
+ */
1523
+ interface FactorEnrollment {
1524
+ type: RecoveryFactor;
1525
+ /** totp → base32 secret; email_otp → email address. Omitted for `recovery_code`. */
1526
+ credential?: string;
1527
+ /** recovery_code → lowercase-hex SHA-256 of the recovery code (the raw code never leaves the client). */
1528
+ credentialHash?: string;
1529
+ }
1530
+ /**
1531
+ * One factor's proof at recovery time (backend `FactorVerification`).
1532
+ *
1533
+ * Contract A: `recovery_code` proves possession with `credentialHash` (the same
1534
+ * SHA-256 the server stored), never the plaintext code — the server compares the
1535
+ * hash in constant time and can never derive the code to unseal the ciphertext.
1536
+ */
1537
+ interface FactorVerification {
1538
+ type: RecoveryFactor;
1539
+ /** totp → current 6-digit OTP; email_otp → the emailed OTP. Omitted for `recovery_code`. */
1540
+ token?: string;
1541
+ /** recovery_code → lowercase-hex SHA-256 of the code (matches the enrolled hash). */
1542
+ credentialHash?: string;
1543
+ }
1544
+ /** A registered recovery record's metadata (backend `RecoveryShareResponse`). */
1545
+ interface RecoveryShareInfo {
1546
+ id: string;
1547
+ walletId: string;
1548
+ factors: RecoveryFactor[];
1549
+ createdAt: string;
1550
+ }
1551
+ /** Response to initiating a recovery session (backend `RecoveryChallengeResponse`). */
1552
+ interface RecoveryChallengeResponse {
1553
+ challengeId: string;
1554
+ requiredFactors: RecoveryFactor[];
1555
+ }
1556
+ /** Encrypted share returned after verifying factors (backend `RecoveryRetrieveResponse`). */
1557
+ interface RecoveryRetrieveResponse {
1558
+ id: string;
1559
+ ciphertext: string;
1560
+ }
1561
+ /** Result of device-loss recovery — factors verified + shares rotated (backend `RecoverWalletResponse`). */
1562
+ interface RecoverWalletResponse {
1563
+ recovered: boolean;
1564
+ id: string;
1565
+ ciphertext: string;
1566
+ refreshedAt: string;
1567
+ }
1568
+ /** Parameters for `recovery.register(...)`. */
1569
+ interface RegisterRecoveryParams {
1570
+ /** The device key share to back up (e.g. `await shareStore.get(walletId)`). */
1571
+ share: string;
1572
+ /** High-entropy recovery code used to encrypt the backup. Generated and returned if omitted. */
1573
+ recoveryCode?: string;
1574
+ /** Base32 TOTP secret to enrol the authenticator factor. */
1575
+ totpSecret: string;
1576
+ /** Email address to enrol the email-OTP factor. */
1577
+ email: string;
1578
+ /** Extra factor enrolments beyond the standard three. */
1579
+ extraFactors?: FactorEnrollment[];
1580
+ }
1581
+ /** Parameters for `recovery.recover(...)` / `recovery.retrieveShare(...)`. */
1582
+ interface RecoverParams {
1583
+ /** challengeId from `recovery.challenge(...)`. */
1584
+ challengeId: string;
1585
+ /** One verification per enrolled factor. */
1586
+ verifications: FactorVerification[];
1587
+ /** The recovery code — decrypts the retrieved share client-side. */
1588
+ recoveryCode: string;
1589
+ }
1590
+ /**
1591
+ * Parameters for `wallets.recoverSign(...)` — the device-loss RECOVERY CO-SIGN of a non-custodial
1592
+ * `[device, server, user_backup]` secp256k1 wallet (#351/#78). Extends the recovery gate ({@link RecoverParams})
1593
+ * that releases the sealed `user_backup` ciphertext with the digest to co-sign. The `recoveryCode` opens
1594
+ * the backup CLIENT-SIDE (Contract A: it never reaches the server); the restored `user_backup` share then
1595
+ * co-signs 2-party with the platform's `server` party. Passkey step-up (when the wallet requires it) rides
1596
+ * the call's options, exactly like a normal sign.
1597
+ */
1598
+ interface RecoverSignParams extends RecoverParams {
1599
+ /** 32-byte hex digest to co-sign (a leading `0x` is accepted and stripped). */
1600
+ digest: string;
1601
+ /** Target chain (e.g. `"evm:1"`) — forwarded to the recover-sign session for plan/chain gating, parity with `sign-session`. */
1602
+ chainId?: string;
1603
+ }
1604
+ /**
1605
+ * Assemble material the backend hands a retained USER_DEVICE new holder so it can finish its OWN
1606
+ * reshared share client-side — the platform never learns the device's share (backend
1607
+ * `DeviceReshareMaterial`, #83/#318). Present on {@link ReshareWalletResponse.deviceMaterial} only
1608
+ * when a reshare keeps a device holder. The values are opaque JSON the device routes into the core.
1609
+ */
1610
+ interface DeviceReshareMaterial {
1611
+ curve: WalletCurve;
1612
+ /** The device holder's 0-based position within {@link newPreimages}. */
1613
+ newPosition: number;
1614
+ /** New share preimages (32-byte hex), one per new holder. */
1615
+ newPreimages: string[];
1616
+ /** The new signing threshold `t'`. */
1617
+ newThreshold: number;
1618
+ /** The unchanged `WalletPublicInfo` JSON. */
1619
+ wallet: unknown;
1620
+ /** One Feldman-commitments object per dealer (the broadcast set). */
1621
+ commitments: unknown[];
1622
+ /** The device holder's private sub-share from each dealer. */
1623
+ subShares: unknown[];
1624
+ }
1625
+ /**
1626
+ * Relay coordination for the device to join the post-reshare **aux-completion** ceremony (#318 /
1627
+ * #95) — the interactive `aux_info_gen` over the NEW committee that the backend signer drives for
1628
+ * the server + recovery parties. Mirrors the keygen {@link WalletCeremony} / sign-session relay
1629
+ * fields; the backend uses `<sessionId>/reshare-aux` (kind `reshare-aux`), with `sessionId` the
1630
+ * wallet id. `curve` is taken from {@link DeviceReshareMaterial}, so it is not repeated here.
1631
+ */
1632
+ interface ReshareCompletionCeremony {
1633
+ /** Relay websocket URL the device connects to. */
1634
+ relayUrl: string;
1635
+ /** Ceremony id shared by every committee party (the wallet id). */
1636
+ sessionId: string;
1637
+ /** This device's relay routing id in the NEW committee, e.g. `device`. */
1638
+ role: string;
1639
+ /** The peer (server) party's relay routing id, e.g. `server`. */
1640
+ peerRole: string;
1641
+ /** This device's 0-based party index in the NEW committee. */
1642
+ partyIndex: number;
1643
+ /** The peer (server) party's party index in the NEW committee. */
1644
+ peerPartyIndex: number;
1645
+ /** Total parties `n'` in the NEW committee. */
1646
+ parties: number;
1647
+ /** Short-lived relay token (JWT) the device presents to join the reshare-aux session; present only when relay auth is enabled. */
1648
+ relayToken?: string;
1649
+ }
1650
+ /** Parameters for `reshare.complete(...)` — everything the device needs to finish its NEW-epoch share. */
1651
+ interface ReshareCompletionParams {
1652
+ /** Assemble material from the reshare response ({@link ReshareWalletResponse.deviceMaterial}). */
1653
+ material: DeviceReshareMaterial;
1654
+ /** Relay coordination for the `<sessionId>/reshare-aux` ceremony the device joins. */
1655
+ ceremony: ReshareCompletionCeremony;
1656
+ /** The wallet's NEW key epoch ({@link ReshareWalletResponse.keyEpoch}) — the completed share is stored under it. */
1657
+ keyEpoch: number;
1658
+ /** This device's OWN pre-generated Paillier safe-primes for the aux ceremony (never server-provided). Absent ⇒ generated inline. */
1659
+ pregeneratedPrimes?: string;
1660
+ }
1661
+ /** Result of `reshare.complete(...)` — the wallet is now signable on this device under the new epoch. */
1662
+ interface ReshareCompletionResult {
1663
+ walletId: string;
1664
+ /** The key epoch the completed share was stored under (the wallet's new epoch). */
1665
+ keyEpoch: number;
1666
+ /** The wallet's shared public key (hex) — verified unchanged across the reshare. */
1667
+ sharedPublicKey: string;
1668
+ }
1669
+ /**
1670
+ * Result of a wallet reshare (backend `ReshareWalletResponse`, #295): the wallet's NEW topology +
1671
+ * custody attestation and the bumped `keyEpoch` (the public key/address are UNCHANGED). When the
1672
+ * new committee retains a USER_DEVICE holder, {@link deviceMaterial} carries the material that
1673
+ * device completes client-side (see `reshare.complete`).
1674
+ */
1675
+ interface ReshareWalletResponse {
1676
+ walletId: string;
1677
+ /** The new signing threshold `t'`. */
1678
+ threshold: number;
1679
+ /** The new ordered party roles, length `n'`. */
1680
+ parties: string[];
1681
+ /** The new per-party custody kinds. */
1682
+ custodyKinds: CustodyKind[];
1683
+ /** Count of {@link custodyKinds} entries the platform itself holds. */
1684
+ platformShareCount: number;
1685
+ /** The wallet's new custody attestation. */
1686
+ custodyType: CustodyType;
1687
+ /** The wallet's new key epoch (bumped). */
1688
+ keyEpoch: number;
1689
+ /** True: the new committee cannot sign until its aux material is (re)generated (device completion clears it). */
1690
+ auxPending: boolean;
1691
+ /** ISO timestamp the reshare completed at. */
1692
+ resharedAt: string;
1693
+ /** Assemble material for a retained USER_DEVICE new holder. Present only when the new committee keeps a device holder. */
1694
+ deviceMaterial?: DeviceReshareMaterial;
1695
+ }
1696
+ /** An embedded-wallet end-user (authenticates through the app, distinct from a dashboard member). */
1697
+ interface EndUser {
1698
+ id: string;
1699
+ tenantId: string;
1700
+ /** Email identifier (email-OTP / social). Absent when signed up by phone or passkey. */
1701
+ email?: string;
1702
+ /** Phone identifier (phone-OTP, E.164). Absent unless a verified phone is linked. */
1703
+ phone?: string;
1704
+ createdAt: string;
1705
+ }
1706
+ /** Result of starting an OTP login (email or phone). */
1707
+ interface EmailStartResult {
1708
+ sent: boolean;
1709
+ /** TEST-ONLY: the code, present only when the backend runs with `EMBEDDED_OTP_DEBUG` (non-prod). */
1710
+ debugCode?: string;
1711
+ }
1712
+ /** An authenticated end-user session. */
1713
+ interface EmbeddedSession {
1714
+ /** End-user session token (sent on subsequent end-user-scoped calls). */
1715
+ token: string;
1716
+ endUser: EndUser;
1717
+ }
1718
+ /** Body of a Firebase Auth login — the client-obtained Firebase ID token (backend `FirebaseAuthDto`). */
1719
+ interface FirebaseAuthRequest {
1720
+ /** Firebase ID token obtained on the client via the Firebase Auth SDK. */
1721
+ idToken: string;
1722
+ }
1723
+ /** A dashboard member's role within a tenant (backend `MemberRole`). */
1724
+ type MemberRole = 'owner' | 'admin' | 'member';
1725
+ /** Which environments a member may operate in (backend `MembershipScope`). */
1726
+ type MembershipScope = 'all' | 'production' | 'sandbox';
1727
+ /**
1728
+ * A dashboard org member — a human who logs into the tenant console (distinct from an embedded
1729
+ * {@link EndUser}, and from an API key). Mirrors the backend `IMember`; `createdAt` is an ISO string
1730
+ * on the wire.
1731
+ */
1732
+ interface Member {
1733
+ id: string;
1734
+ tenantId: string;
1735
+ email: string;
1736
+ role: MemberRole;
1737
+ /** Which environments this member may operate in (production / sandbox / all). */
1738
+ environmentScope: MembershipScope;
1739
+ /** Whether the member has completed TOTP 2FA enrolment. */
1740
+ totpEnabled: boolean;
1741
+ createdAt: string;
1742
+ }
1743
+ /**
1744
+ * A held org-member bearer session (mirrors the backend `FirebaseAuthResponse`). Returned by
1745
+ * {@link Members.loginWithFirebase} and held by the client so member-scoped calls authenticate with
1746
+ * `Authorization: Bearer <accessToken>` instead of the ambient dashboard cookie.
1747
+ */
1748
+ interface MemberSession {
1749
+ /** Member access token (JWT) — presented as `Authorization: Bearer …` on member calls. */
1750
+ accessToken: string;
1751
+ /**
1752
+ * Member refresh token (JWT). The SDK holds it but does NOT yet auto-refresh (out of scope) — once
1753
+ * the access token expires a member call surfaces a 401 error; a future revision exchanges it at
1754
+ * `/v1/auth/refresh`.
1755
+ */
1756
+ refreshToken: string;
1757
+ /** The authenticated member. */
1758
+ member: Member;
40
1759
  }
41
1760
 
42
- /** Thin typed HTTP client over fetch — the single place requests are issued. */
43
- declare class HttpClient {
44
- private readonly apiKey;
45
- private readonly baseUrl;
46
- private readonly fetchImpl;
47
- constructor(apiKey: string, baseUrl: string, fetchImpl?: typeof fetch);
48
- request<T>(method: string, path: string, body?: unknown): Promise<T>;
1761
+ /**
1762
+ * Embedded end-user authentication (#6) — Privy-style login that maps an end-user to a
1763
+ * non-custodial wallet, distinct from the dashboard member auth. The developer's API key
1764
+ * authorizes the flow; a successful login yields an end-user **session token** the SDK
1765
+ * holds and sends on end-user-scoped calls.
1766
+ *
1767
+ * Methods (email-OTP, phone-OTP, ) live behind the same `auth` surface and all establish
1768
+ * the same session; social / passkey are added the same way.
1769
+ */
1770
+ declare class Auth {
1771
+ private readonly http;
1772
+ private current?;
1773
+ constructor(http: HttpClient);
1774
+ /** Email one-time-code login. */
1775
+ readonly email: {
1776
+ /** Send a login code to `email`. */
1777
+ start: (email: string, signal?: AbortSignal) => Promise<EmailStartResult>;
1778
+ /** Verify the code; on success the end-user is provisioned and the session is established. */
1779
+ verify: (email: string, code: string, signal?: AbortSignal) => Promise<EmbeddedSession>;
1780
+ };
1781
+ /** Phone one-time-code (SMS) login. `phone` is E.164, e.g. `+14155550123`. */
1782
+ readonly phone: {
1783
+ /** Send a login code to `phone`. */
1784
+ start: (phone: string, signal?: AbortSignal) => Promise<EmailStartResult>;
1785
+ /** Verify the code; on success the end-user is provisioned and the session is established. */
1786
+ verify: (phone: string, code: string, signal?: AbortSignal) => Promise<EmbeddedSession>;
1787
+ };
1788
+ /** Social / federated-provider login. */
1789
+ readonly social: {
1790
+ /**
1791
+ * Exchange a Google ID token (obtained on the client via Google Identity Services) for a
1792
+ * session. Requires the tenant's Google client id to be configured server-side.
1793
+ */
1794
+ google: (idToken: string, signal?: AbortSignal) => Promise<EmbeddedSession>;
1795
+ /**
1796
+ * Exchange a Firebase ID token (obtained on the client via the Firebase Auth SDK) for a session
1797
+ * — the Firebase analogue of {@link social.google}. Requires the tenant to have opted into
1798
+ * embedded Firebase sign-in (`firebaseAuthEnabled`); when it hasn't, the API replies 412 and the
1799
+ * SDK surfaces a typed `firebase_not_enabled` {@link WaaskeyError}.
1800
+ */
1801
+ firebase: (idToken: string, signal?: AbortSignal) => Promise<EmbeddedSession>;
1802
+ };
1803
+ /**
1804
+ * Passkey (WebAuthn) login. `register` adds a passkey to the **currently logged-in** end-user;
1805
+ * `login` is usernameless (a passkey assertion resolves the user and establishes a session).
1806
+ *
1807
+ * The browser ceremony uses `@simplewebauthn/browser` (an optional peer dependency, loaded on
1808
+ * demand). Pass a `ceremony` to override it — e.g. on React Native with a native authenticator.
1809
+ */
1810
+ readonly passkey: {
1811
+ /** Add a passkey to the logged-in end-user. Requires an active session. */
1812
+ register: (ceremony?: PasskeyCeremony) => Promise<void>;
1813
+ /** Usernameless passkey login — establishes the session on success. */
1814
+ login: (ceremony?: PasskeyCeremony) => Promise<EmbeddedSession>;
1815
+ };
1816
+ /** The active session, or `undefined` when not logged in. */
1817
+ get session(): EmbeddedSession | undefined;
1818
+ /** The end-user session token, or `undefined` when not logged in. */
1819
+ get token(): string | undefined;
1820
+ /** Whether an end-user is currently logged in. */
1821
+ get isAuthenticated(): boolean;
1822
+ /** The logged-in end-user (re-fetched from the session token). Rejects if not logged in. */
1823
+ me(signal?: AbortSignal): Promise<EndUser>;
1824
+ /** Restore a session from a previously stored token (e.g. across reloads). */
1825
+ restore(session: EmbeddedSession): void;
1826
+ /** Clear the session (sign out). */
1827
+ logout(): void;
1828
+ /** POST a verify request, store the resulting session, and return it. */
1829
+ private establish;
1830
+ private requireToken;
1831
+ }
1832
+ /**
1833
+ * The browser WebAuthn ceremony. Defaults to `@simplewebauthn/browser`; override for non-DOM
1834
+ * runtimes (e.g. a React Native native-passkey module). `create` runs registration
1835
+ * (navigator.credentials.create), `get` runs authentication (navigator.credentials.get).
1836
+ */
1837
+ interface PasskeyCeremony {
1838
+ create(options: unknown): Promise<unknown>;
1839
+ get(options: unknown): Promise<unknown>;
49
1840
  }
50
1841
 
51
1842
  /**
52
- * A handle to a single wallet. Returned by `waaskey.wallets.create(...)`.
1843
+ * Org-member (dashboard "plane B") authentication — a bearer member login for a NON-browser
1844
+ * consumer (a Node app, or the browser extension via the SDK) that cannot carry the ambient
1845
+ * same-site dashboard session cookie the browser console relies on.
1846
+ *
1847
+ * {@link loginWithFirebase} exchanges a Firebase ID token at the `@Public` `POST /v1/auth/firebase`
1848
+ * (no API key, no cookie) for a member session `{ accessToken, refreshToken, member }`, which the
1849
+ * client HOLDS. Once held, member-scoped calls ({@link Wallets.joinCeremony} /
1850
+ * {@link Wallets.joinSignCeremony}, which go through {@link HttpClient.requestAsMember}) send
1851
+ * `Authorization: Bearer <accessToken>` rather than relying on the cookie — so a headless consumer
1852
+ * can drive the member-bound ceremonies.
53
1853
  *
54
- * Signing is an interactive threshold-MPC protocol; from the caller's point of
55
- * view it is a single awaited call that returns the signature.
1854
+ * **Refresh is out of scope for now:** the SDK holds `refreshToken` but does not yet auto-refresh.
1855
+ * Once the access token expires a member call surfaces a 401 (`unauthorized`) error; a future
1856
+ * revision will exchange the refresh token at `/v1/auth/refresh` from here.
56
1857
  */
57
- declare class Wallet implements WalletData {
1858
+ declare class Members {
58
1859
  private readonly http;
1860
+ private held?;
1861
+ constructor(http: HttpClient);
1862
+ /**
1863
+ * Exchange a Firebase ID token for an org-member session and hold it.
1864
+ *
1865
+ * The token must belong to an ALREADY-INVITED member — Firebase never self-provisions a member, so
1866
+ * an unknown email is rejected (`no_member`). A member with 2FA enrolled must present a Firebase
1867
+ * token that itself passed a second factor, or the login is refused (`mfa_required`).
1868
+ *
1869
+ * @throws {WaaskeyError} `no_member` — no org member exists for the token's Firebase identity (401).
1870
+ * @throws {WaaskeyError} `mfa_required` — the member needs a second factor the token didn't carry (401).
1871
+ */
1872
+ loginWithFirebase(idToken: string, signal?: AbortSignal): Promise<MemberSession>;
1873
+ /** The current member, or `undefined` when not logged in. */
1874
+ get member(): Member | undefined;
1875
+ /** The held member session (access + refresh tokens + member), or `undefined` when not logged in. */
1876
+ get session(): MemberSession | undefined;
1877
+ /** The held member access token — the value the client wires into {@link HttpClient.requestAsMember}. */
1878
+ get accessToken(): string | undefined;
1879
+ /** Whether an org member is currently logged in over a held bearer session. */
1880
+ get isAuthenticated(): boolean;
1881
+ /** Restore a previously stored member session (e.g. across process restarts). */
1882
+ restore(session: MemberSession): void;
1883
+ /** Clear the held member session (sign out); member calls fall back to the cookie path afterwards. */
1884
+ memberSignOut(): void;
1885
+ }
1886
+
1887
+ /**
1888
+ * The `onramp` resource — fund an embedded wallet with fiat (card/bank) via a provider
1889
+ * (e.g. Transak). The SDK returns a provider widget URL to open; the purchase settles on
1890
+ * chain to the wallet address and the on-chain balance reflects it once the provider delivers.
1891
+ */
1892
+ declare class Onramp {
1893
+ private readonly http;
1894
+ constructor(http: HttpClient);
1895
+ /** Get a provider widget URL to buy `cryptoCurrency` on `chainId` for `walletAddress`. */
1896
+ widgetUrl(params: OnrampWidgetParams, signal?: AbortSignal): Promise<OnrampWidgetUrl>;
1897
+ }
1898
+
1899
+ /** Dependencies the {@link Recovery} resource needs. */
1900
+ interface RecoveryDeps {
1901
+ shareStore?: ShareStore;
1902
+ /** Optional analytics emitter for the wallet.recovered event. */
1903
+ analytics?: Analytics;
1904
+ }
1905
+ /**
1906
+ * Multi-factor wallet recovery.
1907
+ *
1908
+ * The recovery share is encrypted **client-side** with the user's recovery code
1909
+ * (the server stores only the opaque ciphertext) and its release is gated behind
1910
+ * ≥3 factors (recovery code + TOTP + email OTP). Device-loss recovery verifies the
1911
+ * factors, has the server rotate the key shares (invalidating the lost device
1912
+ * share), then decrypts the backup and restores it on the new device.
1913
+ */
1914
+ declare class Recovery {
1915
+ private readonly http;
1916
+ private readonly deps;
1917
+ constructor(http: HttpClient, deps?: RecoveryDeps);
1918
+ /**
1919
+ * Back up a wallet's device share: encrypt it with the recovery code and enrol the
1920
+ * factors. Returns the (possibly generated) recovery code — show it to the user
1921
+ * once; it is the only key to the backup and is never recoverable from the server.
1922
+ */
1923
+ register(walletId: string, params: RegisterRecoveryParams, options?: {
1924
+ signal?: AbortSignal;
1925
+ }): Promise<{
1926
+ recoveryCode: string;
1927
+ share: RecoveryShareInfo;
1928
+ }>;
1929
+ /** The registered recovery factors for a wallet (no secrets). */
1930
+ getInfo(walletId: string, options?: {
1931
+ signal?: AbortSignal;
1932
+ }): Promise<RecoveryShareInfo>;
1933
+ /** Start a recovery session — returns the challengeId + the factors the user must verify. */
1934
+ challenge(walletId: string, options?: {
1935
+ signal?: AbortSignal;
1936
+ }): Promise<RecoveryChallengeResponse>;
1937
+ /**
1938
+ * Device-loss recovery: verify factors, have the server rotate the key shares,
1939
+ * then decrypt the backup with the recovery code and restore it to the share store
1940
+ * (when one is configured). Returns when the share is restored.
1941
+ */
1942
+ recover(walletId: string, params: RecoverParams, options?: {
1943
+ signal?: AbortSignal;
1944
+ }): Promise<{
1945
+ share: string;
1946
+ refreshedAt: string;
1947
+ }>;
1948
+ /**
1949
+ * Verify factors and decrypt the backed-up share **without** rotating keys — for a
1950
+ * read-only restore. Use {@link recover} for true device-loss (which re-keys).
1951
+ *
1952
+ * NOTE (issue #41, LOW): because this path does not rotate the (possibly lost) device
1953
+ * share, the server `/verify` endpoint must enforce that **all ≥3 factors** were
1954
+ * satisfied before releasing the ciphertext; prefer {@link recover} (always-rotate) for
1955
+ * device-loss so a leaked backup can't be replayed against a still-valid old share.
1956
+ */
1957
+ retrieveShare(walletId: string, params: RecoverParams, options?: {
1958
+ signal?: AbortSignal;
1959
+ }): Promise<string>;
1960
+ private decrypt;
1961
+ }
1962
+ /** A high-entropy (128-bit) recovery code, grouped for readability — e.g. `7F3A-9C21-...`. */
1963
+ declare function generateRecoveryCode(): string;
1964
+
1965
+ /** Dependencies the {@link Reshare} resource needs for the device-side completion ceremony. */
1966
+ interface ReshareDeps {
1967
+ mpc?: MpcCore;
1968
+ shareStore?: ShareStore;
1969
+ /** Optional analytics emitter for the wallet.reshared event. */
1970
+ analytics?: Analytics;
1971
+ }
1972
+ /**
1973
+ * Device-side completion of a **device-retaining** reshare (#318 phase 2b).
1974
+ *
1975
+ * A backend reshare that keeps a USER_DEVICE holder commits a NEW epoch whose shares are CORE-ONLY
1976
+ * (no aux) — the wallet is `reshareAuxPending` and cannot sign until the aux material is generated
1977
+ * over the new committee. The platform signer drives its server + recovery parties of that aux
1978
+ * ceremony; the DEVICE must join it or the ceremony can't complete. This resource is the device's
1979
+ * half: it assembles its NEW-epoch bare core locally from the reshare's {@link DeviceReshareMaterial},
1980
+ * runs the `<sessionId>/reshare-aux` aux ceremony over the relay alongside the platform parties, and
1981
+ * seals the resulting COMPLETE share on the device under the new epoch.
1982
+ *
1983
+ * The device is never left worse off: nothing is stored until the completed share's public key is
1984
+ * verified equal to the wallet's (unchanged across a reshare — the funds-safety invariant), and the
1985
+ * OLD-epoch share is kept intact, so an interrupted completion can simply be retried.
1986
+ */
1987
+ declare class Reshare {
1988
+ private readonly http;
1989
+ private readonly deps;
1990
+ constructor(http: HttpClient, deps?: ReshareDeps);
1991
+ /**
1992
+ * Complete this device's share for a device-retaining reshare and make the wallet signable on the
1993
+ * device under the new epoch.
1994
+ *
1995
+ * 1. Fetch the wallet to learn its (unchanged) public key — the invariant the completion is checked
1996
+ * against, taken from the server's truth rather than the caller.
1997
+ * 2. Assemble the NEW-epoch bare core locally from {@link ReshareCompletionParams.material} (no relay).
1998
+ * 3. Run the aux-completion ceremony over the relay ({@link ReshareCompletionParams.ceremony}),
1999
+ * alongside the platform server + recovery parties, to obtain the COMPLETE signable share.
2000
+ * 4. Verify the completed share's public key equals the wallet's (fail closed on any mismatch).
2001
+ * 5. Seal + persist the completed share under `(walletId, keyEpoch)`, keeping the old-epoch share.
2002
+ *
2003
+ * Requires `mpc` (built with the reshare capability) + `shareStore`. Idempotent/recoverable: safe to
2004
+ * retry, since nothing is overwritten until the new share is assembled, verified, and stored.
2005
+ */
2006
+ complete(walletId: string, params: ReshareCompletionParams, options?: {
2007
+ signal?: AbortSignal;
2008
+ }): Promise<ReshareCompletionResult>;
2009
+ }
2010
+ /**
2011
+ * Compose the epoch-keyed device-share storage key `(walletId, keyEpoch)`, mirroring the backend's
2012
+ * epoch-keyed share model (#295). Epoch 1 (a fresh keygen) keeps the bare `walletId` key for
2013
+ * back-compat with existing stored shares; later epochs (reshares) use a distinct suffixed key, so a
2014
+ * completed reshare never overwrites the old-epoch share until cutover is confirmed.
2015
+ */
2016
+ declare function epochShareKey(walletId: string, keyEpoch: number): string;
2017
+
2018
+ /** Device-party dependencies a {@link Wallet} needs to co-sign an ed25519 (FROST) transaction locally. */
2019
+ interface WalletDeviceDeps {
2020
+ mpc?: MpcCore;
2021
+ shareStore?: ShareStore;
2022
+ }
2023
+ /**
2024
+ * A handle to a single wallet. Returned by `waaskey.wallets.create(...)` /
2025
+ * `waaskey.wallets.get(...)`.
2026
+ */
2027
+ declare class Wallet {
2028
+ private readonly http;
2029
+ private readonly analytics?;
2030
+ /** Device core + share store, threaded from {@link Wallets}, so an ed25519 wallet can co-sign locally. */
2031
+ private readonly device;
2032
+ /** Waaskey wallet id, e.g. `wlt_…`. */
59
2033
  readonly id: string;
60
- readonly address: string;
61
- readonly chain: Chain;
62
- constructor(http: HttpClient, data: WalletData);
63
- /** Sign an arbitrary message with this wallet's key (2-of-3 threshold MPC). */
64
- signMessage(message: string): Promise<string>;
2034
+ /** The full wallet record as returned by the API. */
2035
+ readonly data: WalletData;
2036
+ constructor(http: HttpClient, data: WalletData, analytics?: Analytics | undefined,
2037
+ /** Device core + share store, threaded from {@link Wallets}, so an ed25519 wallet can co-sign locally. */
2038
+ device?: WalletDeviceDeps);
2039
+ /** On-chain address (set once keygen completes). */
2040
+ get address(): string | undefined;
2041
+ /** Lifecycle state (`pending` until keygen completes, then `active`). */
2042
+ get status(): WalletStatus;
2043
+ /** Signing curve of the wallet. */
2044
+ get curve(): WalletCurve;
2045
+ /** Signing threshold `t` of the wallet's `t`-of-`n` MPC key. */
2046
+ get threshold(): number;
2047
+ /** Ordered party roles of the wallet's keygen topology, length `n`. */
2048
+ get parties(): string[];
2049
+ /** Per-party custody kind, parallel to {@link parties}. */
2050
+ get custodyKinds(): CustodyKind[];
2051
+ /** How many of the wallet's shares the platform itself holds. */
2052
+ get platformShareCount(): number;
2053
+ /**
2054
+ * The wallet's custody attestation (`embedded` / `shared` / `self_custody`) — display
2055
+ * it to prove the custody guarantee to the end-user (see {@link isNonCustodial}).
2056
+ */
2057
+ get custodyType(): CustodyType;
2058
+ /**
2059
+ * Whether this wallet is **non-custodial** — the platform's shares alone do not meet
2060
+ * the threshold (`platformShareCount < threshold`, i.e. custody type `shared` or
2061
+ * `self_custody`), so WaaS cannot sign without the user/external party.
2062
+ */
2063
+ get isNonCustodial(): boolean;
2064
+ /**
2065
+ * Sign a 32-byte message digest with this wallet's key (2-of-3 threshold MPC).
2066
+ *
2067
+ * `digest` is a 32-byte hash as hex (the `0x` prefix is optional) — e.g. the
2068
+ * keccak-256 of an EVM transaction. Hashing a higher-level message/transaction
2069
+ * into a digest is the caller's (or a chain helper's) responsibility.
2070
+ *
2071
+ * **Passkey step-up (Pattern B / issue #21, #39):** pass `{ requirePasskey: true }` to run a
2072
+ * WebAuthn assertion before signing. The SDK fetches a **server-issued one-time challenge**,
2073
+ * prompts the user's authenticator over it, and attaches `passkeyAssertion` + its
2074
+ * `passkeyChallengeId` to the POST body; the backend verifies both the MPC signature and the
2075
+ * assertion, then burns the challenge (so it cannot be replayed). Alternatively supply a
2076
+ * pre-built assertion via `options.passkeyAssertion` (with its `options.passkeyChallengeId`).
2077
+ */
2078
+ sign(digest: string, options?: SignOptions): Promise<string>;
2079
+ /**
2080
+ * Send a transaction from this wallet. The platform builds the chain-specific transaction
2081
+ * and co-signs it with the 2-of-3 MPC quorum, returning the **signed raw transaction**.
2082
+ * `value` is in the chain's base unit (wei) as a numeric string.
2083
+ *
2084
+ * **WaaS is sign-only — it never broadcasts.** The result's {@link SendResult.signedTx} is
2085
+ * the signed raw tx the *client* submits to its own node/provider (see
2086
+ * {@link Waaskey.broadcast} or your own submitter); {@link SendResult.txHash} is a
2087
+ * deterministic offline id for reference, not proof of broadcast.
2088
+ *
2089
+ * **Passkey step-up (Pattern B / issue #21, #39):** pass `{ requirePasskey: true }` to run a
2090
+ * WebAuthn assertion before sending, over a server-issued one-time challenge (verified + burned
2091
+ * server-side). Alternatively supply a pre-built assertion via `options.passkeyAssertion`.
2092
+ */
2093
+ send(params: SendParams, options?: SendOptions): Promise<SendResult>;
2094
+ /**
2095
+ * Device-co-signed send for an ed25519 (FROST) wallet (#110) — the browser holds the device FROST
2096
+ * share and co-signs 2-party with the backend `server` party over the relay:
2097
+ *
2098
+ * 1. **START** (`POST …/send-session`): the backend builds the unsigned tx (chain adapter), starts
2099
+ * its server FROST party on the relay in the background, and returns the raw `message` bytes to
2100
+ * sign + the relay coordination ({@link EddsaSendSession}).
2101
+ * 2. **CO-SIGN**: the device runs `signEddsa` over the relay with its stored `{keyPackage,
2102
+ * publicKeyPackage}` share; the two parties aggregate the RFC 8032 signature (returned locally).
2103
+ * 3. **ASSEMBLE** (`POST …/send-session/:txId/assemble`): the backend embeds the aggregated
2104
+ * signature into the wire tx (chain adapter) and returns the signed raw tx + offline `txHash`.
2105
+ *
2106
+ * WaaS stays sign-only: the returned {@link SendResult.signedTx} is what the client broadcasts.
2107
+ */
2108
+ private sendEd25519;
2109
+ /**
2110
+ * Attach a passkey step-up assertion to `body` when the caller requests one (issue #39).
2111
+ *
2112
+ * The challenge is a **server-issued one-time nonce**, never derived from the request
2113
+ * payload: the SDK fetches it from the step-up challenge endpoint, runs the WebAuthn
2114
+ * assertion over it, and echoes its `challengeId` so the server can verify and burn it —
2115
+ * making a captured assertion non-replayable. A pre-built `passkeyAssertion` (with its
2116
+ * `passkeyChallengeId`) is attached as-is and takes precedence over `requirePasskey`.
2117
+ */
2118
+ private attachStepUp;
2119
+ /**
2120
+ * List this wallet's **signing activity**, newest first (paginated) — the unified audit
2121
+ * trail of what this key signed (raw signs and send/sweep signed txs, #289). A send/sweep
2122
+ * record carries the `signedTx` the client broadcasts.
2123
+ */
2124
+ signatures(query?: PageQuery, options?: SendOptions): Promise<Page<Signature>>;
65
2125
  }
66
2126
 
67
- /** The `wallets` resource: create and load wallets. */
2127
+ /** Dependencies a {@link Wallets} resource needs for the non-custodial ceremony. */
2128
+ interface WalletsDeps {
2129
+ mpc?: MpcCore;
2130
+ shareStore?: ShareStore;
2131
+ /** Optional pool of pre-generated primes; when present, keygen claims one instead of generating inline. */
2132
+ primePool?: PrimePool;
2133
+ /** Optional analytics emitter for wallet lifecycle events. */
2134
+ analytics?: Analytics;
2135
+ }
2136
+ /**
2137
+ * The `wallets` resource: create and load wallets, plus the approver actions
2138
+ * ({@link Wallets.approveSignRequest}/{@link Wallets.declineSignRequest}) and the approver
2139
+ * queue listing ({@link Wallets.listSignRequests}/{@link Wallets.listPendingApprovals}) on
2140
+ * their async signing requests (#229, #309, #329).
2141
+ */
68
2142
  declare class Wallets {
69
2143
  private readonly http;
70
- constructor(http: HttpClient);
71
- /** Create a new MPC wallet on the given chain. */
72
- create(params: CreateWalletParams): Promise<Wallet>;
2144
+ private readonly deps;
2145
+ constructor(http: HttpClient, deps?: WalletsDeps);
2146
+ /**
2147
+ * Create a new MPC wallet on the given chain. The keygen is a `t`-of-`n` ceremony:
2148
+ *
2149
+ * 1. ask the API to start a wallet + the server party (returns the ceremony params),
2150
+ * 2. run the **device** half of keygen locally (in WASM) against the relay,
2151
+ * 3. seal + persist the device share (it never leaves the device),
2152
+ * 4. wait until the wallet is ACTIVE (server party finished) and return it.
2153
+ *
2154
+ * By default (no custody policy) this is the embedded **2-of-3** topology. Supply an
2155
+ * explicit `{ threshold, parties, custodyKinds }` and/or a requested `custodyType` to
2156
+ * change the topology / custody posture — the SDK validates it client-side (see
2157
+ * {@link validateCustodyPolicy}) and the backend enforces the attested invariant. The
2158
+ * device runs whatever `t`-of-`n` the returned ceremony describes (its `parties` /
2159
+ * `threshold`), so the ceremony is not pinned to 2-of-3.
2160
+ *
2161
+ * **Non-custodial `[device, server, user_backup]` (#351/#78).** When the returned ceremony carries a
2162
+ * `user_backup` party ({@link WalletCeremony.additionalParties}), the platform signer drives ONLY the
2163
+ * `server` share, so this ONE device runs BOTH client parties in the SAME keygen ceremony — the
2164
+ * `device` party AND the client-held `user_backup` party — joining the same relay session with each
2165
+ * party's own relay token. It then persists the `device` share locally (as always) and, because there
2166
+ * is no platform recovery share, seals the `user_backup` share with the caller's recovery code and
2167
+ * registers the ciphertext server-side (reusing the recovery mechanism), so device-loss can never lock
2168
+ * funds. This path therefore REQUIRES `options.backup`. The sealed backup is persisted to a local
2169
+ * pending slot BEFORE the network call, so if registration fails the (non-re-derivable) share is not
2170
+ * lost — `create` throws `backup_failed` and {@link retryBackup} re-registers it (no re-keygen).
2171
+ *
2172
+ * Requires `mpc` + `shareStore` on the client. The device share is the user's half
2173
+ * of the key; without storing it the wallet would be unrecoverable.
2174
+ */
2175
+ create(params: CreateWalletParams, options?: CreateWalletOptions): Promise<Wallet>;
2176
+ /**
2177
+ * Create a **member-bound** wallet (#342-#349): an N+1 threshold topology where `N` specific ORG
2178
+ * MEMBERSHIPS (`shareholderMembershipIds`) each hold one share and the platform holds exactly
2179
+ * one (a derived `shared` custody topology — never `embedded`). Unlike {@link create} (the
2180
+ * embedded device+server(+recovery) flow, which runs the device's keygen inline and waits for
2181
+ * ACTIVE), this wallet is provisioned `pending_keygen` with **no ceremony to join here** — it
2182
+ * has no `mpc`/`shareStore` dependency and no custody-policy validation (that machinery is for
2183
+ * the raw `parties`/`custodyKinds` embedded topology, mutually exclusive with
2184
+ * `shareholderMembershipIds` server-side). Each invited member later runs {@link joinCeremony}
2185
+ * from their OWN device/session; the wallet only activates once every member has joined.
2186
+ */
2187
+ createWallet(params: CreateMemberWalletParams, options?: {
2188
+ signal?: AbortSignal;
2189
+ }): Promise<WalletData>;
2190
+ /**
2191
+ * Warm the prime pool for a chain's curve OFF the hot path — call during onboarding/idle (ideally
2192
+ * from a Web Worker) so the next {@link create} on that chain doesn't pay the safe-prime cost.
2193
+ * No-op when no prime pool is configured.
2194
+ */
2195
+ prewarm(chain: CreateWalletParams['chain']): Promise<void>;
73
2196
  /** Load an existing wallet by id. */
74
- get(id: string): Promise<Wallet>;
2197
+ get(id: string, options?: {
2198
+ signal?: AbortSignal;
2199
+ }): Promise<Wallet>;
2200
+ /** The device core + share store a {@link Wallet} needs to co-sign an ed25519 (FROST) tx locally. */
2201
+ private walletDeviceDeps;
2202
+ /**
2203
+ * Join a **member-bound** wallet's multi-device keygen ceremony (#344, #349) as an invited org
2204
+ * member — call this from the member's OWN device/session (never a tenant API key: every HTTP
2205
+ * call here is member-session-authenticated, see {@link HttpClient.requestAsMember}). Unlike
2206
+ * {@link create}, every member (N of them) + the platform join the SAME relay session, so:
2207
+ *
2208
+ * 1. fetch this member's own party ({@link MemberCeremony}) plus the wallet's other
2209
+ * share-holders (to derive the full n-party relay roster the ceremony needs),
2210
+ * 2. ack readiness (`POST .../ceremony/join`) — the backend starts its platform party's own
2211
+ * relay connection only once EVERY member has acked, so this must happen before the ceremony
2212
+ * can complete,
2213
+ * 3. run this device's half of the n-party keygen over the relay (blocks until every party,
2214
+ * including the platform, is present and the ceremony completes),
2215
+ * 4. seal + persist the device's ONE share, keyed by `(walletId, membershipId)` — never by
2216
+ * `walletId` alone, since several members' shares for the SAME wallet may live in one
2217
+ * `shareStore` (e.g. a shared device, or a test harness).
2218
+ *
2219
+ * Safe to retry: a failed ceremony reopens the roster server-side, and re-acking/re-running is
2220
+ * idempotent from the caller's perspective.
2221
+ */
2222
+ joinCeremony(walletId: string, options?: JoinCeremonyOptions): Promise<MemberCeremonyJoinResponse>;
2223
+ /**
2224
+ * Join a **member-bound** wallet's multi-device SIGN ceremony (#347, #349) as an invited org
2225
+ * member — the signing analogue of {@link joinCeremony}, called from the member's OWN
2226
+ * device/session. A sign is a `t`-of-`n` SELECTION (only `t` of the N+1 parties actually sign),
2227
+ * so the flow is:
2228
+ *
2229
+ * 1. cast this member's APPROVE vote (`POST .../sign-requests/:reqId/approve`) — a no-op if
2230
+ * already cast; once `t-1` members have approved, the backend fixes the signing quorum,
2231
+ * 2. poll this member's own sign-ceremony party (`GET .../sign-requests/:reqId/ceremony/mine`)
2232
+ * until the quorum is fixed AND this member was selected into it ({@link MemberSignCeremony.ready}),
2233
+ * 3. load this member's OWN stored share (keyed by `(walletId, membershipId)`, from
2234
+ * {@link joinCeremony}) and run this device's half of the fixed-quorum sign ceremony —
2235
+ * the interactive protocol's public output IS the completed signature.
2236
+ *
2237
+ * A member who is not selected into the fixed quorum, or whose approval never gets it there,
2238
+ * simply times out (`sign_ceremony_timeout`) rather than running anything.
2239
+ */
2240
+ joinSignCeremony(walletId: string, reqId: string, options?: JoinSignCeremonyOptions): Promise<DeviceSignResult>;
2241
+ /**
2242
+ * Approve a pending async signing request (#229) — either completing a single
2243
+ * device-approval request outright, or casting one APPROVE vote in an M-of-N approver
2244
+ * quorum (#309) configured on the wallet (via the tenant dashboard's `PUT .../quorum`). A
2245
+ * non-quorum request starts the MPC ceremony immediately; a quorum request only starts
2246
+ * it once enough approvers vote, and until then the response reports how many
2247
+ * approvals are still needed via {@link SigningRequestResponse.approvalsRemaining}.
2248
+ *
2249
+ * Takes `walletId` + `reqId` directly (rather than a {@link Wallet} instance) since an
2250
+ * approver typically learns of a pending request out-of-band — e.g. a
2251
+ * `sign_request.created` webhook or push notification — without first loading the wallet.
2252
+ */
2253
+ approveSignRequest(walletId: string, reqId: string, options?: {
2254
+ signal?: AbortSignal;
2255
+ }): Promise<SigningRequestResponse>;
2256
+ /**
2257
+ * Decline a pending async signing request (#229) — either rejecting a single
2258
+ * device-approval request outright, or casting a REJECT vote in an M-of-N approver
2259
+ * quorum (#309), which declines the request as soon as one approver rejects it.
2260
+ */
2261
+ declineSignRequest(walletId: string, reqId: string, options?: {
2262
+ signal?: AbortSignal;
2263
+ }): Promise<SigningRequestResponse>;
2264
+ /**
2265
+ * List a wallet's async signing requests, newest first (paginated) — optionally filtered to
2266
+ * a single lifecycle {@link SignRequestsQuery.status} (#332). The API paginates the
2267
+ * *filtered* set, so `total`/`page` always describe what was actually matched.
2268
+ */
2269
+ listSignRequests(walletId: string, query?: SignRequestsQuery, options?: {
2270
+ signal?: AbortSignal;
2271
+ }): Promise<Page<SigningRequestResponse>>;
2272
+ /**
2273
+ * List a wallet's **pending approver queue** — the ergonomic entry point over
2274
+ * {@link listSignRequests} pinned to `status: 'pending_approval'` (#309, #329): the
2275
+ * requests currently awaiting an approver's {@link approveSignRequest}/
2276
+ * {@link declineSignRequest} vote, either a single device-approval request or one still
2277
+ * short of its M-of-N quorum (see {@link SigningRequestResponse.approvalsRemaining}).
2278
+ */
2279
+ listPendingApprovals(walletId: string, query?: PageQuery, options?: {
2280
+ signal?: AbortSignal;
2281
+ }): Promise<Page<SigningRequestResponse>>;
2282
+ /**
2283
+ * Run the cggmp24 (secp256k1) device keygen and persist the resulting share(s) (#351/#78). Two shapes,
2284
+ * chosen by whether the ceremony carries client-held extra parties ({@link WalletCeremony.additionalParties}):
2285
+ *
2286
+ * - **Single client party** (today's path — `[device, server]` or custodial `[device, server, recovery]`,
2287
+ * where the platform drives every non-device party): run just the `device` party and seal its share
2288
+ * under the wallet id, exactly as before.
2289
+ * - **Non-custodial `[device, server, user_backup]`** (a `user_backup` extra party): run BOTH the
2290
+ * `device` and `user_backup` parties CONCURRENTLY in the SAME relay session (each with its own
2291
+ * role-scoped relay token + its OWN Paillier primes), then persist the `device` share locally and
2292
+ * seal + register the `user_backup` share as the non-custodial backup (requires `backup`).
2293
+ *
2294
+ * Any additional party the client cannot drive (a non-`user_backup` role) is refused up front — leaving
2295
+ * it unjoined would hang the whole ceremony, so failing loud beats silently deadlocking.
2296
+ */
2297
+ private runSecpKeygen;
2298
+ /**
2299
+ * Register a sealed user_backup {@link RecoveryRegisterPayload} server-side, with a small bounded retry
2300
+ * on TRANSIENT failures (network / 5xx / rate-limit) — a flaky moment must not cost the backup. A
2301
+ * non-transient failure (4xx) fails fast. On abort, the abort propagates unchanged. If it ultimately
2302
+ * cannot register, throws a distinct, actionable {@link WaaskeyError} `backup_failed` (NOT `keygen_failed`)
2303
+ * — the caller's cue that the wallet was created but its backup is only local, and pointing at
2304
+ * {@link retryBackup}. The pending slot is intentionally NOT touched here, so a failure leaves the
2305
+ * sealed share intact for the retry.
2306
+ */
2307
+ private registerBackup;
2308
+ /**
2309
+ * Re-register a non-custodial wallet's user_backup backup that a previous {@link create} sealed locally
2310
+ * but could not register (a `backup_failed` create) (#351/#78). Reads the LOCAL pending-backup slot's
2311
+ * sealed ciphertext and re-POSTs it — **no re-keygen** (the share is not re-derivable) — with the same
2312
+ * bounded transient retry, then clears the slot on success. Idempotent-ish: a no-op `share_not_found`
2313
+ * when nothing is pending (already registered, or never created here). Requires a share store.
2314
+ */
2315
+ retryBackup(walletId: string, options?: {
2316
+ signal?: AbortSignal;
2317
+ }): Promise<RecoveryShareInfo>;
2318
+ /**
2319
+ * Device-loss RECOVERY CO-SIGN for a non-custodial `[device, server, user_backup]` secp256k1 wallet
2320
+ * (#351/#78). When the device is lost, the user restores their client-held `user_backup` share from the
2321
+ * sealed server-side backup and co-signs `digest` with the platform's `server` party — the 2-party
2322
+ * `{server, user_backup}` quorum. This is NOT the platform-only custodial `{server, recovery}` recoverSign
2323
+ * (`recovery.recoverSign` / `POST …/recovery/recover-sign`): here the platform CANNOT sign alone; the
2324
+ * user's restored share is the co-signing factor.
2325
+ *
2326
+ * 1. **RESTORE** — retrieve the sealed `user_backup` ciphertext (the multi-factor recovery gate,
2327
+ * {@link RecoverParams}, releases it) and open it with the recovery code CLIENT-SIDE (Contract A: the
2328
+ * raw code never leaves the device), yielding the cggmp24 `user_backup` KeyShare. No registered backup
2329
+ * fails `share_not_found`; a wrong recovery code fails `invalid_recovery_code` — either BEFORE any
2330
+ * ceremony starts, so a bad restore never signs (and never hangs).
2331
+ * 2. **START** — `POST /v1/wallets/:id/recover-sign-session` with the digest (+ passkey step-up when the
2332
+ * wallet requires it, threaded exactly like a normal sign via `options`) → the `user_backup` party's
2333
+ * ceremony descriptor: its role, the `{server, user_backup}` keygen `participants`, this party's
2334
+ * `signerPosition`, and a `user_backup`-role relay token.
2335
+ * 3. **CO-SIGN** — run the `user_backup` MPC party ({@link MpcCore.runSign}) over the relay with the
2336
+ * restored share + that descriptor; the platform `server` party co-signs server-side. Returns the
2337
+ * resulting signature.
2338
+ *
2339
+ * The ONLY differences from a normal `{device, server}` device sign are the SHARE (the restored
2340
+ * `user_backup`, not the local device share) and the DESCRIPTOR (from the recover-sign session, not the
2341
+ * normal sign session) — the same {@link MpcCore.runSign} relay machinery drives both.
2342
+ *
2343
+ * Requires `mpc` on the client (the co-signing core). No share store is needed: the restored share is
2344
+ * held only in memory for the ceremony and never persisted (the device is lost/new). secp256k1 only —
2345
+ * re-provisioning a fresh device share (reshare back to a full 2-of-3) is a separate follow-up.
2346
+ */
2347
+ recoverSign(walletId: string, params: RecoverSignParams, options?: SignOptions): Promise<string>;
2348
+ /**
2349
+ * Retrieve the sealed `user_backup` ciphertext via the recovery gate ({@link fetchRecoveryCiphertext}),
2350
+ * mapping a "no recovery share registered" (404) to the actionable `share_not_found` — this wallet has no
2351
+ * client-held `user_backup` backup to co-sign with (its device-loss recovery is the custodial path instead).
2352
+ */
2353
+ private fetchUserBackupCiphertext;
2354
+ /**
2355
+ * Run one or more device keygen parties (each `mpc.runKeygen`), concurrently, mapping any failure to a
2356
+ * single `keygen_failed`. Used for both the single `device` party and the two-party non-custodial
2357
+ * `[device, user_backup]` ceremony — one place owns the error contract so both paths stay identical.
2358
+ */
2359
+ private runKeygenParties;
2360
+ /**
2361
+ * Run the device half of an ed25519 (FROST) keygen (#110) and seal the resulting `{keyPackage,
2362
+ * publicKeyPackage}` share — the EdDSA counterpart of the cggmp24 `runKeygen` branch in {@link create}.
2363
+ * The device co-generates the group key with the backend `server` party over the relay; the FROST DKG
2364
+ * is the 2-party {device, server} quorum the ceremony names (M6 scope), roster-addressed like the
2365
+ * member ceremony rather than the cggmp24 single-peer shape.
2366
+ */
2367
+ private runEddsaKeygen;
2368
+ /** Poll the wallet until keygen completes (ACTIVE), or throw on failure/timeout. */
2369
+ private waitUntilActive;
2370
+ /** Poll the member's own sign ceremony until the t-of-n quorum is fixed AND this member is selected, or throw on timeout. */
2371
+ private waitUntilReady;
75
2372
  }
2373
+ /**
2374
+ * Storage key for a member-bound wallet's per-member share (#349) — distinct from the embedded
2375
+ * flow's bare `walletId` key ({@link Wallet}'s single device share), since several members' shares
2376
+ * for the SAME wallet may live in one {@link ShareStore} (e.g. a shared device, or a test harness).
2377
+ */
2378
+ declare function memberShareKey(walletId: string, membershipId: string): string;
2379
+ /**
2380
+ * Storage key for a non-custodial wallet's PENDING user_backup backup (#351/#78) — the sealed
2381
+ * {@link RecoveryRegisterPayload} ciphertext {@link Wallets.create} persists before registering it
2382
+ * server-side, so a failed/interrupted registration never loses the (non-re-derivable) user_backup share.
2383
+ * Distinct from the device share's bare `walletId` key; cleared by {@link Wallets.retryBackup} on success.
2384
+ * The value is opaque without the recovery code, so a local copy is a safe retry buffer.
2385
+ */
2386
+ declare function userBackupPendingKey(walletId: string): string;
76
2387
 
77
2388
  /**
78
2389
  * The Waaskey client — entry point of the SDK.
79
2390
  *
80
2391
  * @example
81
2392
  * ```ts
82
- * import { Waaskey } from '@waaskey/sdk';
2393
+ * import { Waaskey, WasmMpcCore, EncryptedShareStore } from '@waaskey/sdk';
83
2394
  *
84
- * const waaskey = new Waaskey({ apiKey: process.env.WAASKEY_PUBLISHABLE_KEY! });
85
- * const wallet = await waaskey.wallets.create({ userId: user.id, chain: 'ethereum' });
86
- * const signature = await wallet.signMessage('Hello Waaskey');
2395
+ * const waaskey = new Waaskey({
2396
+ * apiKey: process.env.WAASKEY_PUBLISHABLE_KEY!,
2397
+ * mpc: new WasmMpcCore(loadClientWasm),
2398
+ * shareStore: EncryptedShareStore.browser(sessionSecret),
2399
+ * });
2400
+ * const wallet = await waaskey.wallets.create({ chain: 'ethereum' });
2401
+ * const signature = await wallet.sign(digestHex);
87
2402
  * ```
88
2403
  */
89
2404
  declare class Waaskey {
90
2405
  /** The `wallets` resource. */
91
2406
  readonly wallets: Wallets;
2407
+ /** The `recovery` resource — multi-factor, client-encrypted wallet recovery. */
2408
+ readonly recovery: Recovery;
2409
+ /** The `reshare` resource — device-side completion of a device-retaining reshare (#318). */
2410
+ readonly reshare: Reshare;
2411
+ /** The `balances` resource — client-side balance reads from a chain provider (no backend). */
2412
+ readonly balances: Balances;
2413
+ /** The `auth` resource — embedded end-user login (email-OTP, …) → non-custodial wallet. */
2414
+ readonly auth: Auth;
2415
+ /** The `members` resource — org-member (dashboard "plane B") bearer login for headless consumers. */
2416
+ readonly members: Members;
2417
+ /** The `onramp` resource — fund the wallet with fiat via a provider on-ramp. */
2418
+ readonly onramp: Onramp;
2419
+ /** Default fetch used by the optional {@link broadcast} helper (from `WaaskeyOptions.fetch`). */
2420
+ private readonly defaultFetch?;
92
2421
  constructor(options: WaaskeyOptions);
2422
+ /**
2423
+ * **Optional** client-side broadcast of a signed raw tx from {@link Wallet.send}.
2424
+ *
2425
+ * WaaS is a **signing service** — `wallet.send(...)` returns `signedTx` and WaaS never
2426
+ * submits it. This is a thin, best-effort convenience to broadcast from **your own**
2427
+ * node/provider (one JSON-RPC call, no status polling / no retries — tracking is your
2428
+ * concern). Most integrators broadcast with their own infra.
2429
+ *
2430
+ * @example
2431
+ * ```ts
2432
+ * const { signedTx } = await wallet.send({ chainId: 'evm:1', to, value });
2433
+ * const { txHash } = await waaskey.broadcast(signedTx, { rpcUrl: 'https://your-rpc' });
2434
+ * ```
2435
+ */
2436
+ broadcast(signedTx: string, options: BroadcastOptions): Promise<BroadcastResult>;
93
2437
  }
94
2438
 
95
- export { type Chain, type CreateWalletParams, Waaskey, WaaskeyError, type WaaskeyOptions, Wallet, type WalletData, Wallets };
2439
+ /**
2440
+ * Whether a wallet is **non-custodial** — the platform alone cannot produce a
2441
+ * signature because the shares it holds do not meet the threshold
2442
+ * (`platformShareCount < threshold`, i.e. custody type `shared` or `self_custody`).
2443
+ *
2444
+ * This mirrors the server-attested invariant `platformShares < t ⟺ non-custodial`,
2445
+ * so an app can display the custody guarantee straight off the returned wallet.
2446
+ */
2447
+ declare function isNonCustodial(wallet: Pick<WalletData, 'platformShareCount' | 'threshold'>): boolean;
2448
+ /**
2449
+ * Validate a create-wallet custody policy client-side, before the request — clear
2450
+ * errors instead of a round-trip to a 400. A no-op when no policy is supplied
2451
+ * (the default embedded 2-of-3 topology).
2452
+ *
2453
+ * The rules mirror the backend contract: `parties` (when supplied) are ≥2 distinct,
2454
+ * non-empty roles that define `n`; `threshold` is an integer in `[2, n]`; and
2455
+ * `custodyKinds` has exactly one known kind per party.
2456
+ */
2457
+ declare function validateCustodyPolicy(params: Pick<CreateWalletParams, 'threshold' | 'parties' | 'custodyKinds' | 'custodyType'>): void;
2458
+
2459
+ /**
2460
+ * **Optional** client-side broadcast helper.
2461
+ *
2462
+ * WaaS is a **signing service**, not a broadcaster: `wallet.send(...)` returns the
2463
+ * signed raw transaction (`signedTx`) and WaaS never submits it. Broadcasting,
2464
+ * nonce/mempool handling and status-tracking are the integrator's concern — that
2465
+ * is what non-custodial means. This helper is a thin convenience so you can submit
2466
+ * a signed tx from **your own** node/provider; it is deliberately best-effort:
2467
+ * one JSON-RPC call, **no status polling and no retries**. For production you will
2468
+ * usually broadcast (and track) with your own infra.
2469
+ *
2470
+ * Today it submits an EVM raw transaction via `eth_sendRawTransaction`. The node's
2471
+ * returned hash is passed through as {@link BroadcastResult.txHash}.
2472
+ */
2473
+ declare function broadcast(signedTx: string, options: BroadcastOptions): Promise<BroadcastResult>;
2474
+
2475
+ /**
2476
+ * PasskeyPrfSecretProvider — derives a stable AES-key secret from a passkey's
2477
+ * PRF extension output, so the device share in {@link EncryptedShareStore} is
2478
+ * sealed under a biometric-gated passkey rather than a password.
2479
+ *
2480
+ * ### How the secret stays stable
2481
+ * The PRF extension evaluates a pseudo-random function keyed by the credential
2482
+ * (stored in the authenticator) over a caller-supplied `salt`. `enroll()` mints a
2483
+ * **random per-user salt** (issue #41) and returns it alongside `credentialId`;
2484
+ * persist both (they are non-secret) and pass the salt back to `unlock()`. Because
2485
+ * the credential and the salt are the same, the PRF output — and therefore the
2486
+ * derived `secret` — is identical on every `unlock()`. (A random per-user salt,
2487
+ * rather than one global constant, prevents cross-context PRF correlation.)
2488
+ *
2489
+ * ### Wiring to EncryptedShareStore
2490
+ * ```ts
2491
+ * import { PasskeyPrfSecretProvider } from '@waaskey/sdk';
2492
+ * import { EncryptedShareStore } from '@waaskey/sdk';
2493
+ *
2494
+ * const prf = new PasskeyPrfSecretProvider();
2495
+ * const { credentialId, secret } = await prf.enroll({ rpName: 'My App', userName: user.email });
2496
+ * // persist credentialId (non-secret); keep secret in memory for this session
2497
+ * const store = EncryptedShareStore.browser(secret);
2498
+ *
2499
+ * // On a subsequent session:
2500
+ * const { secret } = await prf.unlock(credentialId);
2501
+ * const store = EncryptedShareStore.browser(secret);
2502
+ *
2503
+ * // @waaskey/react: usePrfStore() wraps the above + stores credentialId in localStorage.
2504
+ * // react-native: use a native PRF authenticator, supply a custom PrfCeremony.
2505
+ * ```
2506
+ */
2507
+
2508
+ /**
2509
+ * Override the underlying WebAuthn ceremony calls. Useful for React Native
2510
+ * (pass a native PRF authenticator module) or tests (mock the PRF output).
2511
+ */
2512
+ interface PrfCeremony {
2513
+ register(options: PublicKeyCredentialCreationOptionsJSON): Promise<{
2514
+ credentialId: string;
2515
+ prfResult: ArrayBuffer | null;
2516
+ }>;
2517
+ authenticate(credentialId: string, options: PublicKeyCredentialRequestOptionsJSON): Promise<{
2518
+ prfResult: ArrayBuffer | null;
2519
+ }>;
2520
+ }
2521
+ /** Options accepted by `PasskeyPrfSecretProvider.enroll(...)`. */
2522
+ interface PasskeyPrfEnrollOptions {
2523
+ /**
2524
+ * Human-readable name for the relying party (your app). Used in the
2525
+ * authenticator's registration ceremony UI.
2526
+ */
2527
+ rpName?: string;
2528
+ /** Display name shown in the authenticator UI for the user. */
2529
+ userName?: string;
2530
+ /**
2531
+ * Fully qualified domain name of the relying party. Defaults to the current
2532
+ * origin's hostname in the browser. Must match the value used for `unlock()`.
2533
+ */
2534
+ rpId?: string;
2535
+ /** Custom ceremony (for non-browser runtimes or tests). */
2536
+ ceremony?: PrfCeremony;
2537
+ }
2538
+ /** Result of a successful `enroll()` or `unlock()`. */
2539
+ interface PasskeyPrfResult {
2540
+ /** Credential id (Base64URL). Persist this — pass it to `unlock()` later. */
2541
+ credentialId: string;
2542
+ /**
2543
+ * Base64-encoded per-user PRF evaluation salt (issue #41). Non-secret — persist it
2544
+ * alongside `credentialId` and pass it back to `unlock({ salt })` so the same secret
2545
+ * is re-derived. Omitting it on `unlock` falls back to the legacy constant salt.
2546
+ */
2547
+ salt: string;
2548
+ /**
2549
+ * Base64-encoded 32-byte PRF output. Use as the `secret` argument to
2550
+ * `EncryptedShareStore.browser(secret)`. Keep in memory; never persist it.
2551
+ */
2552
+ secret: string;
2553
+ }
2554
+ /**
2555
+ * Returns `true` if the current runtime has WebAuthn available at all.
2556
+ * A `false` result means passkeys are completely unavailable; fall back to
2557
+ * password or device-secret share sealing.
2558
+ */
2559
+ declare function isPasskeySupported(): boolean;
2560
+ /**
2561
+ * Returns `true` if the current platform authenticator supports the PRF
2562
+ * extension (needed for passkey share sealing).
2563
+ *
2564
+ * This is a best-effort probe — not all browsers expose `credentials.get` in a
2565
+ * way that lets us query PRF support without a full ceremony. For definitive
2566
+ * detection, attempt `enroll()` and handle the `unsupported` error.
2567
+ *
2568
+ * In practice: Chrome 116+ / Edge 116+ on a platform authenticator support PRF;
2569
+ * Safari / iOS 17 do not yet.
2570
+ */
2571
+ declare function isPrfSupported(): Promise<boolean>;
2572
+ /**
2573
+ * Derives a stable AES-key `secret` from a passkey's PRF extension for use with
2574
+ * {@link EncryptedShareStore}. The secret is deterministic: same credential +
2575
+ * same fixed salt → same 32 bytes every time.
2576
+ */
2577
+ declare class PasskeyPrfSecretProvider {
2578
+ /**
2579
+ * Register a new passkey that supports PRF and derive the initial secret from it.
2580
+ *
2581
+ * @returns `{ credentialId, secret }` — persist `credentialId`; use `secret`
2582
+ * to construct `EncryptedShareStore.browser(secret)` for this session only.
2583
+ *
2584
+ * @throws `WaaskeyError('unsupported')` when PRF isn't available in this runtime.
2585
+ * @throws `WaaskeyError('aborted')` when the user cancels the authenticator dialog.
2586
+ */
2587
+ enroll(opts?: PasskeyPrfEnrollOptions): Promise<PasskeyPrfResult>;
2588
+ /**
2589
+ * Authenticate with an existing passkey and re-derive the same stable secret.
2590
+ *
2591
+ * @param credentialId — the id returned by `enroll()`.
2592
+ * @param opts.salt — the per-user salt `enroll()` returned (issue #41). Pass it to
2593
+ * re-derive the same secret; omit only for legacy credentials enrolled before
2594
+ * per-user salts (falls back to the constant salt).
2595
+ * @returns `{ credentialId, salt, secret }` — reconstruct `EncryptedShareStore.browser(secret)`.
2596
+ *
2597
+ * @throws `WaaskeyError('unsupported')` when PRF isn't available.
2598
+ * @throws `WaaskeyError('aborted')` when the user cancels.
2599
+ */
2600
+ unlock(credentialId: string, opts?: {
2601
+ rpId?: string;
2602
+ ceremony?: PrfCeremony;
2603
+ salt?: string;
2604
+ }): Promise<PasskeyPrfResult>;
2605
+ }
2606
+
2607
+ /**
2608
+ * Passkey step-up signing assertion (Pattern B).
2609
+ *
2610
+ * The backend accepts an optional `passkeyAssertion` (`AuthenticationResponseJSON`)
2611
+ * on sign/send/recover DTOs and verifies it server-side. This module runs the
2612
+ * WebAuthn assertion over a **server-issued one-time challenge** and returns the
2613
+ * typed `AuthenticationResponseJSON` ready to be included in the POST body.
2614
+ *
2615
+ * ### Challenge (issue #39 — replay-safe)
2616
+ * The challenge is a random nonce the **server** mints (via a step-up challenge
2617
+ * endpoint), NOT a value derived from the request payload. `Wallet.sign`/`send`
2618
+ * fetch it, run the assertion over it, and echo its `challengeId` on the request;
2619
+ * the server verifies the assertion against the stored challenge and **burns** it,
2620
+ * so a captured assertion cannot be replayed for a later identical payload.
2621
+ *
2622
+ * ### Wiring in Wallet
2623
+ * ```ts
2624
+ * // In wallet.sign() (handled by Wallet.resolveStepUp):
2625
+ * const { challengeId, challenge } = await http.request('POST', '/v1/wallets/${id}/stepup/challenge', { operation: 'sign' });
2626
+ * const assertion = await getSigningAssertion(challenge, { credentialId });
2627
+ * await http.request('POST', '/v1/wallets/${id}/sign', { message, passkeyAssertion: assertion, passkeyChallengeId: challengeId });
2628
+ *
2629
+ * // Caller opts in:
2630
+ * await wallet.sign(digest, { requirePasskey: true });
2631
+ * ```
2632
+ */
2633
+
2634
+ /**
2635
+ * Returns `true` if the current runtime has WebAuthn available — i.e. the SDK
2636
+ * can attempt a passkey assertion. Use this as a fast pre-flight before calling
2637
+ * `getSigningAssertion()` when you want to show/hide a "sign with passkey" button.
2638
+ */
2639
+ declare function isPasskeyAssertionSupported(): boolean;
2640
+ /**
2641
+ * Override the WebAuthn `credentials.get` call. Useful for React Native (native
2642
+ * passkey module) or unit tests (mock the assertion response).
2643
+ */
2644
+ interface SigningAssertionCeremony {
2645
+ get(options: PublicKeyCredentialRequestOptionsJSON): Promise<AuthenticationResponseJSON>;
2646
+ }
2647
+ /** Options for `getSigningAssertion()`. */
2648
+ interface SigningAssertionOptions {
2649
+ /**
2650
+ * Restrict the assertion to a specific credential. Pass the `credentialId`
2651
+ * that was enrolled (e.g. from `PasskeyPrfSecretProvider.enroll()`). When
2652
+ * omitted the browser presents all resident credentials for the RP.
2653
+ */
2654
+ credentialId?: string;
2655
+ /**
2656
+ * Fully qualified domain name of the relying party. Defaults to the current
2657
+ * origin's hostname in the browser.
2658
+ */
2659
+ rpId?: string;
2660
+ /**
2661
+ * Custom ceremony implementation (for React Native or tests).
2662
+ */
2663
+ ceremony?: SigningAssertionCeremony;
2664
+ }
2665
+ /**
2666
+ * Run a WebAuthn assertion over a **server-issued one-time `challenge`** and return
2667
+ * the typed `AuthenticationResponseJSON` to attach as `passkeyAssertion` in the
2668
+ * request body.
2669
+ *
2670
+ * @param challenge — the base64url one-time challenge nonce from the server's
2671
+ * step-up challenge endpoint ({@link StepUpChallengeResponse.challenge}). It is
2672
+ * passed to the authenticator verbatim; the server verifies the assertion against
2673
+ * the stored challenge and burns it, so the assertion cannot be replayed.
2674
+ * @param options — optional ceremony override and credential hint.
2675
+ *
2676
+ * @throws `WaaskeyError('unsupported')` when WebAuthn is not available in this runtime.
2677
+ * @throws `WaaskeyError('aborted')` when the user cancels the authenticator prompt.
2678
+ */
2679
+ declare function getSigningAssertion(challenge: string, options?: SigningAssertionOptions): Promise<AuthenticationResponseJSON>;
2680
+
2681
+ export { Analytics, type AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, Auth, type Balance, Balances, type BroadcastOptions, type BroadcastResult, CLIENT_WASM_VERSION, type CeremonyParams, type Chain, type ChainConfig, type ChainProvider, type ClientWasmLoader, type ClientWasmModule, type CreateMemberWalletParams, type CreateWalletOptions, type CreateWalletParams, type CreateWalletResponse, type CustodyKind, type CustodyType, type DeviceCompleteReshareParams, type DeviceCompleteReshareResult, type DeviceKeygenParams, type DeviceKeygenResult, type DeviceReshareAssembleParams, type DeviceReshareAssembleResult, type DeviceReshareMaterial, type DeviceSignParams, type DeviceSignResult, type EddsaAssembleRequest, type EddsaCeremonyParams, type EddsaKeygenParams, type EddsaKeygenResult, type EddsaSendSession, type EddsaSignParams, type EddsaSignResult, type EmailStartResult, type EmbeddedSession, EncryptedShareStore, type EndUser, EvmRpcProvider, type FactorEnrollment, type FactorVerification, type FirebaseAuthRequest, HttpAnalyticsSink, IndexedDbKeyValueStore, type JoinCeremonyOptions, type JoinSignCeremonyOptions, type KeyValueStore, type Member, type MemberCeremony, type MemberCeremonyJoinResponse, type MemberCeremonyParams, type MemberKeygenParams, type MemberRole, type MemberSession, type MemberSignCeremony, type MemberSignParams, Members, type MembershipScope, MemoryKeyValueStore, MemoryPrimeStore, type MpcCore, type MpcCurve, Onramp, type OnrampWidgetParams, type OnrampWidgetUrl, type Page, type PageQuery, type PasskeyAssertionJSON, type PasskeyCeremony, type PasskeyPrfEnrollOptions, type PasskeyPrfResult, PasskeyPrfSecretProvider, type PrfCeremony, PrimePool, type PrimePoolOptions, type PrimePoolStore, type RecoverParams, type RecoverSignParams, type RecoverWalletResponse, Recovery, type RecoveryChallengeResponse, type RecoveryFactor, type RecoveryRetrieveResponse, type RecoveryShareInfo, type RegisterRecoveryParams, Reshare, type ReshareCompletionCeremony, type ReshareCompletionParams, type ReshareCompletionResult, type ReshareWalletResponse, type SendOptions, type SendParams, type SendResult, type ShareStore, type SignMessageResponse, type SignOptions, type SignRequestsQuery, type SignSessionResponse, type Signature, type SignatureKind, type SigningAssertionCeremony, type SigningAssertionOptions, type SigningRequestResponse, type SigningRequestStatus, type StepUpChallengeResponse, type StepUpOperation, type TokenBalanceOptions, type TxStatus, type VerifiedWasmLoaderOptions, Waaskey, WaaskeyError, type WaaskeyErrorCode, type WaaskeyErrorOptions, type WaaskeyOptions, Wallet, type WalletActionType, type WalletBackupParams, type WalletCeremony, type WalletCurve, type WalletData, type WalletShareholder, type WalletStatus, Wallets, WasmMpcCore, broadcast, createVerifiedClientWasmLoader, epochShareKey, formatUnits, generateRecoveryCode, getSigningAssertion, isNonCustodial, isPasskeyAssertionSupported, isPasskeySupported, isPrfSupported, loadClientWasm, memberShareKey, userBackupPendingKey, validateCustodyPolicy, verifyWasmIntegrity };