@waaskey/sdk 0.4.2 → 0.6.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/README.md +113 -3
- package/dist/index.cjs +570 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +653 -3
- package/dist/index.d.ts +653 -3
- package/dist/index.js +562 -22
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.d.cts
CHANGED
|
@@ -699,9 +699,46 @@ declare class MemoryPrimeStore implements PrimePoolStore {
|
|
|
699
699
|
interface PrimeGenerator {
|
|
700
700
|
pregeneratePrimes(curve: MpcCurve): Promise<string>;
|
|
701
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* A claim on pooled primes (#83).
|
|
704
|
+
*
|
|
705
|
+
* A keygen that fails — a server timeout, a dropped relay — used to take its primes out of the
|
|
706
|
+
* pool and never give them back, so every retry paid another full safe-prime generation: 5–10
|
|
707
|
+
* minutes of CPU in single-threaded WASM, right after something had already gone wrong. A lease
|
|
708
|
+
* makes the ownership explicit: primes leave the pool for good only when a ceremony actually
|
|
709
|
+
* derived key material from them.
|
|
710
|
+
*/
|
|
711
|
+
interface PrimeLease {
|
|
712
|
+
/** The primes to hand the ceremony. */
|
|
713
|
+
readonly primes: string;
|
|
714
|
+
/**
|
|
715
|
+
* The ceremony derived key material from these — they are spent and must never be reused.
|
|
716
|
+
*/
|
|
717
|
+
consume(): void;
|
|
718
|
+
/**
|
|
719
|
+
* The ceremony did not complete: put them back for the retry.
|
|
720
|
+
*
|
|
721
|
+
* Returning primes a failed ceremony may have already published an `N` for is a deliberate
|
|
722
|
+
* call, not an oversight. The private halves never left the device; a party that re-runs with
|
|
723
|
+
* the same Paillier key is in the same position as one that simply has not refreshed its aux
|
|
724
|
+
* info yet, which is the normal state between refreshes. The alternative — burning another
|
|
725
|
+
* full generation on every retry — is a cost users actually pay.
|
|
726
|
+
*/
|
|
727
|
+
release(): Promise<void>;
|
|
728
|
+
}
|
|
702
729
|
interface PrimePoolOptions {
|
|
703
730
|
/** How many spare primes to keep cached per curve. Default 2. */
|
|
704
731
|
targetSize?: number;
|
|
732
|
+
/**
|
|
733
|
+
* How many primes to generate at once when refilling. Default 1.
|
|
734
|
+
*
|
|
735
|
+
* Nothing in one prime search depends on another, so a worker-backed core can fill the pool in
|
|
736
|
+
* parallel and turn `targetSize` full generations of wall-clock into roughly one (#109). The
|
|
737
|
+
* default stays 1 because on a core that generates INLINE there are no cores to spare: the
|
|
738
|
+
* searches share the calling thread, so raising this would not finish sooner and would only
|
|
739
|
+
* delay the first prime becoming usable. Raise it when the core is worker-backed.
|
|
740
|
+
*/
|
|
741
|
+
concurrency?: number;
|
|
705
742
|
/** Persistence for the pool. Defaults to an in-memory store. */
|
|
706
743
|
store?: PrimePoolStore;
|
|
707
744
|
/**
|
|
@@ -719,6 +756,7 @@ declare class PrimePool {
|
|
|
719
756
|
private readonly store;
|
|
720
757
|
private readonly targetSize;
|
|
721
758
|
private readonly autoRefill;
|
|
759
|
+
private readonly concurrency;
|
|
722
760
|
/** Per-curve in-flight refill, so concurrent calls don't over-generate. */
|
|
723
761
|
private readonly refilling;
|
|
724
762
|
constructor(core: PrimeGenerator, options?: PrimePoolOptions);
|
|
@@ -735,6 +773,14 @@ declare class PrimePool {
|
|
|
735
773
|
* is worker-backed).
|
|
736
774
|
*/
|
|
737
775
|
take(curve: MpcCurve): Promise<string>;
|
|
776
|
+
/**
|
|
777
|
+
* Claim primes as a LEASE, so a failed ceremony gives them back (#83).
|
|
778
|
+
*
|
|
779
|
+
* Prefer this over {@link take} anywhere the primes feed a ceremony that can fail: `take` hands
|
|
780
|
+
* them over unconditionally, and a keygen that dies on a relay timeout then costs the next
|
|
781
|
+
* attempt a full generation it did not need to pay.
|
|
782
|
+
*/
|
|
783
|
+
borrow(curve: MpcCurve): Promise<PrimeLease>;
|
|
738
784
|
}
|
|
739
785
|
|
|
740
786
|
/**
|
|
@@ -861,7 +907,7 @@ declare class IndexedDbKeyValueStore implements KeyValueStore {
|
|
|
861
907
|
* (`{ statusCode, message, error }`) to a code; orchestration failures (device keygen,
|
|
862
908
|
* missing share, cancellation) use the SDK-side codes.
|
|
863
909
|
*/
|
|
864
|
-
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';
|
|
910
|
+
type WaaskeyErrorCode = 'unauthorized' | 'forbidden' | 'not_found' | 'validation' | 'conflict' | 'rate_limited' | 'server_error' | 'network' | 'unsupported_chain' | 'provider_error' | 'device_core_required' | 'device_store_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' | 'permission_expired' | 'permission_revoked' | 'permission_scope' | 'permission_value_exceeded' | 'permission_exhausted' | 'permission_rate_limited' | 'permission_request_timeout' | 'permission_declined' | 'aborted' | 'unknown';
|
|
865
911
|
interface WaaskeyErrorOptions {
|
|
866
912
|
/** HTTP status, when the error originates from an API response. */
|
|
867
913
|
status?: number;
|
|
@@ -935,6 +981,14 @@ interface WaaskeyOptions {
|
|
|
935
981
|
baseUrl: string;
|
|
936
982
|
/** Custom fetch implementation (e.g. for non-browser runtimes). Defaults to global `fetch`. */
|
|
937
983
|
fetch?: typeof fetch;
|
|
984
|
+
/**
|
|
985
|
+
* Where a permission request sends the user to answer it — the wallet that holds their key (#113).
|
|
986
|
+
*
|
|
987
|
+
* Required only by `sessionKeys.request`, and deliberately not defaulted: which wallet is "the"
|
|
988
|
+
* wallet is a product decision (your own app, waaskey mobile, the extension), and guessing it
|
|
989
|
+
* would send users somewhere that cannot answer.
|
|
990
|
+
*/
|
|
991
|
+
walletUrl?: string;
|
|
938
992
|
/**
|
|
939
993
|
* Device-party MPC core (e.g. `WasmMpcCore`). Required to create a wallet — the
|
|
940
994
|
* device runs its half of the keygen ceremony with this.
|
|
@@ -945,6 +999,15 @@ interface WaaskeyOptions {
|
|
|
945
999
|
* `EncryptedShareStore.browser(secret)`). Required to create a wallet.
|
|
946
1000
|
*/
|
|
947
1001
|
shareStore?: ShareStore;
|
|
1002
|
+
/**
|
|
1003
|
+
* Where this client's stable **device id** is persisted (e.g. `new IndexedDbKeyValueStore()`).
|
|
1004
|
+
* Required to act as a member DEVICE — registering, and joining a member-bound wallet's
|
|
1005
|
+
* ceremonies as `member:<membershipId>:<deviceId>`.
|
|
1006
|
+
*
|
|
1007
|
+
* Deliberately NOT the sealed share store: the device id is public (it is in every party role),
|
|
1008
|
+
* and sealing it would make it unavailable before unlock — exactly when registration needs it.
|
|
1009
|
+
*/
|
|
1010
|
+
deviceStore?: KeyValueStore;
|
|
948
1011
|
/**
|
|
949
1012
|
* Optional pool of pre-generated Paillier primes (`new PrimePool(mpc)`). When set,
|
|
950
1013
|
* `wallets.prewarm(chain)` fills it off the hot path and `wallets.create` claims from
|
|
@@ -1003,6 +1066,15 @@ interface CreateWalletOptions {
|
|
|
1003
1066
|
activationTimeoutMs?: number;
|
|
1004
1067
|
/** Poll interval while waiting for activation, ms. Default 1000. */
|
|
1005
1068
|
pollIntervalMs?: number;
|
|
1069
|
+
/**
|
|
1070
|
+
* What the backup ended up enrolled with (#105) — called once, before `create` resolves.
|
|
1071
|
+
*
|
|
1072
|
+
* The app needs this to say the right thing: a passkey-guarded wallet must NOT be shown a
|
|
1073
|
+
* "write this code down" screen for a code nobody will ever ask for, and a code-guarded one must.
|
|
1074
|
+
* Guessing from device capability is not enough, because enrolment can fall back for reasons the
|
|
1075
|
+
* app cannot see.
|
|
1076
|
+
*/
|
|
1077
|
+
onRecoveryEnrolled?: (enrolment: RecoveryEnrolment) => void;
|
|
1006
1078
|
/**
|
|
1007
1079
|
* Recovery-backup material for a **non-custodial** `[device, server, user_backup]` create (#351/#78):
|
|
1008
1080
|
* REQUIRED when the returned ceremony carries a `user_backup` party ({@link WalletCeremony.additionalParties}).
|
|
@@ -1024,6 +1096,28 @@ interface CreateWalletOptions {
|
|
|
1024
1096
|
* secret and never leaves the device (only its SHA-256 is enrolled), so a lost code is an
|
|
1025
1097
|
* unrecoverable backup.
|
|
1026
1098
|
*/
|
|
1099
|
+
/**
|
|
1100
|
+
* Produces a passkey PRF credential for recovery enrolment, or `undefined` when this device cannot
|
|
1101
|
+
* (no platform authenticator, no PRF extension, the user declined).
|
|
1102
|
+
*
|
|
1103
|
+
* A function rather than a class so a React Native or a test caller can supply one without the
|
|
1104
|
+
* browser WebAuthn surface; `PasskeyPrfSecretProvider.enroll` fits it directly.
|
|
1105
|
+
*/
|
|
1106
|
+
type PasskeyEnroller = () => Promise<{
|
|
1107
|
+
secret: string;
|
|
1108
|
+
salt: string;
|
|
1109
|
+
credentialId: string;
|
|
1110
|
+
} | undefined>;
|
|
1111
|
+
/** Which factor ended up guarding the backup, and why (#105). */
|
|
1112
|
+
interface RecoveryEnrolment {
|
|
1113
|
+
/**
|
|
1114
|
+
* The strong factor that was enrolled. `recovery_code` means the user MUST be shown their code;
|
|
1115
|
+
* `passkey` means they must not be told to write down a code they will never be asked for.
|
|
1116
|
+
*/
|
|
1117
|
+
strongFactor: 'passkey' | 'recovery_code';
|
|
1118
|
+
/** Why the passkey was not used. Absent when it was. */
|
|
1119
|
+
fallbackReason?: 'no_passkey_enroller' | 'device_cannot_prf' | 'enrolment_failed';
|
|
1120
|
+
}
|
|
1027
1121
|
interface WalletBackupParams {
|
|
1028
1122
|
/** 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. */
|
|
1029
1123
|
recoveryCode: string;
|
|
@@ -1033,6 +1127,17 @@ interface WalletBackupParams {
|
|
|
1033
1127
|
email: string;
|
|
1034
1128
|
/** Extra factor enrolments beyond the standard three. */
|
|
1035
1129
|
extraFactors?: FactorEnrollment[];
|
|
1130
|
+
/**
|
|
1131
|
+
* Enrol a passkey INSTEAD of the recovery code when this device can produce a PRF — the #511
|
|
1132
|
+
* default (waas-backend#511), made automatic here rather than left as an opt-in (#105).
|
|
1133
|
+
*
|
|
1134
|
+
* Called once during `create`. Return the credential to enrol it as the strong factor; return
|
|
1135
|
+
* `undefined` (or throw) and the recovery code is used instead. A device without PRF is a
|
|
1136
|
+
* permanent property of that device, not a user error, so the fallback is silent — but which of
|
|
1137
|
+
* the two was enrolled is reported through `onRecoveryEnrolled`, because it decides whether the
|
|
1138
|
+
* app must tell the user to write a code down.
|
|
1139
|
+
*/
|
|
1140
|
+
passkeyEnroller?: PasskeyEnroller;
|
|
1036
1141
|
/**
|
|
1037
1142
|
* A passkey PRF secret to ALSO wrap the backup key with (#510) — pass what
|
|
1038
1143
|
* `PasskeyPrfSecretProvider.enroll()` returned. The backup then opens with either the passkey or the
|
|
@@ -1066,6 +1171,42 @@ interface WalletBackupParams {
|
|
|
1066
1171
|
interface JoinCeremonyOptions {
|
|
1067
1172
|
/** Cancel the join (roster ack + the relay keygen ceremony). */
|
|
1068
1173
|
signal?: AbortSignal;
|
|
1174
|
+
/**
|
|
1175
|
+
* Which of the member's devices is joining (#54). Required when the member holds several — the
|
|
1176
|
+
* backend cannot guess which one, and the share is sealed under this device's own key.
|
|
1177
|
+
* Defaults to the client's stored device id when a `deviceStore` is configured.
|
|
1178
|
+
*/
|
|
1179
|
+
deviceId?: string;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* One of a member's registered devices (#53). Mirrors the backend `IMemberDevice`.
|
|
1183
|
+
*/
|
|
1184
|
+
interface Device {
|
|
1185
|
+
/** Registry id — what {@link Devices.revoke} takes (NOT the client-generated `deviceId`). */
|
|
1186
|
+
id: string;
|
|
1187
|
+
/** The owning member seat. */
|
|
1188
|
+
membershipId: string;
|
|
1189
|
+
/** The client-generated stable id this device is known by in party roles. */
|
|
1190
|
+
deviceId: string;
|
|
1191
|
+
/** Human-friendly label, e.g. "Chrome on MacBook Pro". */
|
|
1192
|
+
label: string;
|
|
1193
|
+
createdAt: string;
|
|
1194
|
+
/** Last re-registration; null until the device re-registers after creation. */
|
|
1195
|
+
lastSeenAt: string | null;
|
|
1196
|
+
/** The device's X25519 encryption public key (32-byte hex), when it registered one. */
|
|
1197
|
+
encryptionPublicKey?: string | null;
|
|
1198
|
+
}
|
|
1199
|
+
/** What {@link Devices.register} sends. */
|
|
1200
|
+
interface DeviceRegistration {
|
|
1201
|
+
/** Human-friendly label shown in device pickers and the dashboard. */
|
|
1202
|
+
label: string;
|
|
1203
|
+
/** Override the stored id — for a caller with no `deviceStore` of its own. */
|
|
1204
|
+
deviceId?: string;
|
|
1205
|
+
/**
|
|
1206
|
+
* This device's X25519 encryption public key (32-byte non-zero hex). Dealers seal reshare
|
|
1207
|
+
* sub-shares and FROST round-2 packages to it, so a device that will hold a share needs one.
|
|
1208
|
+
*/
|
|
1209
|
+
encryptionPublicKey?: string;
|
|
1069
1210
|
}
|
|
1070
1211
|
/** Options for `wallets.joinSignCeremony(...)` (#349). */
|
|
1071
1212
|
interface JoinSignCeremonyOptions {
|
|
@@ -1075,6 +1216,11 @@ interface JoinSignCeremonyOptions {
|
|
|
1075
1216
|
readyTimeoutMs?: number;
|
|
1076
1217
|
/** Poll interval while waiting for the quorum, ms. Default 1000. */
|
|
1077
1218
|
pollIntervalMs?: number;
|
|
1219
|
+
/**
|
|
1220
|
+
* Which of the member's devices is signing (#54) — it must be the device whose share this
|
|
1221
|
+
* client holds. Defaults to the client's stored device id when a `deviceStore` is configured.
|
|
1222
|
+
*/
|
|
1223
|
+
deviceId?: string;
|
|
1078
1224
|
}
|
|
1079
1225
|
/**
|
|
1080
1226
|
* A WebAuthn assertion attached for passkey step-up (issue #21/#39) — the structural
|
|
@@ -1760,6 +1906,17 @@ interface RegisterRecoveryParams {
|
|
|
1760
1906
|
email: string;
|
|
1761
1907
|
/** Extra factor enrolments beyond the standard three. */
|
|
1762
1908
|
extraFactors?: FactorEnrollment[];
|
|
1909
|
+
/**
|
|
1910
|
+
* Enrol a passkey INSTEAD of the recovery code when this device can produce a PRF — the #511
|
|
1911
|
+
* default (waas-backend#511), made automatic here rather than left as an opt-in (#105).
|
|
1912
|
+
*
|
|
1913
|
+
* Called once during `create`. Return the credential to enrol it as the strong factor; return
|
|
1914
|
+
* `undefined` (or throw) and the recovery code is used instead. A device without PRF is a
|
|
1915
|
+
* permanent property of that device, not a user error, so the fallback is silent — but which of
|
|
1916
|
+
* the two was enrolled is reported through `onRecoveryEnrolled`, because it decides whether the
|
|
1917
|
+
* app must tell the user to write a code down.
|
|
1918
|
+
*/
|
|
1919
|
+
passkeyEnroller?: PasskeyEnroller;
|
|
1763
1920
|
/**
|
|
1764
1921
|
* A passkey PRF secret to ALSO wrap the backup key with (#510) — pass what
|
|
1765
1922
|
* `PasskeyPrfSecretProvider.enroll()` returned. The backup then opens with either the passkey or the
|
|
@@ -2051,6 +2208,18 @@ interface Member {
|
|
|
2051
2208
|
totpEnabled: boolean;
|
|
2052
2209
|
createdAt: string;
|
|
2053
2210
|
}
|
|
2211
|
+
/**
|
|
2212
|
+
* A team member as the members roster returns them (#57) — the login identity plus the
|
|
2213
|
+
* per-membership capability a creator picks share-holders by.
|
|
2214
|
+
*/
|
|
2215
|
+
interface TeamMember extends Member {
|
|
2216
|
+
/**
|
|
2217
|
+
* Whether this membership may hold a wallet key share (#342). Defaults from the role
|
|
2218
|
+
* (owner/admin true, member false) and is togglable by a manager — so it is NOT derivable from
|
|
2219
|
+
* `role`, and guessing it in a picker offers users a choice the backend will refuse.
|
|
2220
|
+
*/
|
|
2221
|
+
canHoldShare: boolean;
|
|
2222
|
+
}
|
|
2054
2223
|
/**
|
|
2055
2224
|
* A held org-member bearer session (mirrors the backend `FirebaseAuthResponse`). Returned by
|
|
2056
2225
|
* {@link Members.loginWithFirebase} and held by the client so member-scoped calls authenticate with
|
|
@@ -2069,6 +2238,169 @@ interface MemberSession {
|
|
|
2069
2238
|
member: Member;
|
|
2070
2239
|
}
|
|
2071
2240
|
|
|
2241
|
+
/** A granted session key, as the API lists it (#112). */
|
|
2242
|
+
interface SessionKey {
|
|
2243
|
+
id: string;
|
|
2244
|
+
walletId: string;
|
|
2245
|
+
/** Compressed secp256k1 public key of the key; the private half never leaves the grantee. */
|
|
2246
|
+
publicKey: string;
|
|
2247
|
+
permissions: {
|
|
2248
|
+
allowedContracts?: string[];
|
|
2249
|
+
allowedSelectors?: string[];
|
|
2250
|
+
/** Per-call value cap (hex wei). */
|
|
2251
|
+
maxValueWei?: string;
|
|
2252
|
+
/** Lifetime call budget — how much the grant is worth in total. */
|
|
2253
|
+
maxCalls?: number;
|
|
2254
|
+
allowedChains?: string[];
|
|
2255
|
+
/** Length of the rate window in seconds; set whenever a per-period cap is (#538). */
|
|
2256
|
+
periodSeconds?: number;
|
|
2257
|
+
/** Calls allowed within one period — a rate, on top of the lifetime `maxCalls`. */
|
|
2258
|
+
maxCallsPerPeriod?: number;
|
|
2259
|
+
/** Total value allowed within one period (hex wei). */
|
|
2260
|
+
maxValueWeiPerPeriod?: string;
|
|
2261
|
+
};
|
|
2262
|
+
expiresAt: string;
|
|
2263
|
+
/**
|
|
2264
|
+
* What this permission is for, in the granter's words (#534).
|
|
2265
|
+
*
|
|
2266
|
+
* Metadata only — it takes no part in any authorization decision. Without it a client can list a
|
|
2267
|
+
* permission by its public key alone, which tells a user nothing about which grant they are
|
|
2268
|
+
* about to revoke.
|
|
2269
|
+
*/
|
|
2270
|
+
label?: string | null;
|
|
2271
|
+
/**
|
|
2272
|
+
* The registered app that asked for this permission, when one did (#539).
|
|
2273
|
+
*
|
|
2274
|
+
* An id, not a name: resolve it through `waaskey.sessionKeys.requester(id)` and read its
|
|
2275
|
+
* `status` — only a `verified` requester may be presented to a user as identity.
|
|
2276
|
+
*/
|
|
2277
|
+
requesterId?: string | null;
|
|
2278
|
+
/** The sponsorship policy funding this permission's gas (#539). */
|
|
2279
|
+
paymasterPolicyId?: string | null;
|
|
2280
|
+
/** Whether the wallet's owner has answered the request for this permission (#553). */
|
|
2281
|
+
grantStatus?: PermissionGrantStatus;
|
|
2282
|
+
/** When they approved it; null while pending or after a refusal. */
|
|
2283
|
+
approvedAt?: string | null;
|
|
2284
|
+
callsUsed: number;
|
|
2285
|
+
/** Start of the rate window the usage below is counted in; null until the first call. */
|
|
2286
|
+
periodStartedAt?: string | null;
|
|
2287
|
+
/** Calls spent in the current period — stale once that period has lapsed. */
|
|
2288
|
+
periodCallsUsed?: number;
|
|
2289
|
+
/** Value spent in the current period, decimal wei — stale once that period has lapsed. */
|
|
2290
|
+
periodValueWeiUsed?: string;
|
|
2291
|
+
active: boolean;
|
|
2292
|
+
createdAt: string;
|
|
2293
|
+
}
|
|
2294
|
+
/** What a session key needs to act: the permission, the account, and the call. */
|
|
2295
|
+
interface SessionKeySendParams {
|
|
2296
|
+
/** Id of the granted session key (the API knows its public half). */
|
|
2297
|
+
sessionKeyId: string;
|
|
2298
|
+
/** The session key's PRIVATE key (hex). Never sent anywhere — it signs locally. */
|
|
2299
|
+
privateKey: string;
|
|
2300
|
+
/** Smart account the operation is for. */
|
|
2301
|
+
sender: string;
|
|
2302
|
+
/** waas chain id, e.g. `evm:1`. */
|
|
2303
|
+
chainId: string;
|
|
2304
|
+
/** ABI-encoded `execute` calldata of the call. */
|
|
2305
|
+
callData: string;
|
|
2306
|
+
/** Target contract — checked against the key's allowlist. */
|
|
2307
|
+
contract: string;
|
|
2308
|
+
/** 4-byte selector — checked against the key's allowlist. */
|
|
2309
|
+
selector: string;
|
|
2310
|
+
/** Call value in wei (hex) — checked against the key's cap. */
|
|
2311
|
+
valueWei: string;
|
|
2312
|
+
/** Account nonce; the server resolves the current one when omitted. */
|
|
2313
|
+
nonce?: string;
|
|
2314
|
+
}
|
|
2315
|
+
/** What the bundler accepted. */
|
|
2316
|
+
interface SessionKeySendResult {
|
|
2317
|
+
userOpHash: string;
|
|
2318
|
+
}
|
|
2319
|
+
/** The scope an app asks a user for (#113). Every bound is optional; omitting one asks for no limit. */
|
|
2320
|
+
interface PermissionScope {
|
|
2321
|
+
/** Contracts the key may call; omitted = any. */
|
|
2322
|
+
allowedContracts?: string[];
|
|
2323
|
+
/** 4-byte selectors it may call; omitted = any. */
|
|
2324
|
+
allowedSelectors?: string[];
|
|
2325
|
+
/** Chains it may act on (waas ids, e.g. `evm:1`); omitted = any. */
|
|
2326
|
+
allowedChains?: string[];
|
|
2327
|
+
/** Per-call value cap (hex wei). */
|
|
2328
|
+
maxValueWei?: string;
|
|
2329
|
+
/** Lifetime call budget — how much the grant is worth in total. */
|
|
2330
|
+
maxCalls?: number;
|
|
2331
|
+
/** Length of the rate window in seconds; required with either per-period cap. */
|
|
2332
|
+
periodSeconds?: number;
|
|
2333
|
+
/** Calls allowed within one period — a rate, on top of `maxCalls`. */
|
|
2334
|
+
maxCallsPerPeriod?: number;
|
|
2335
|
+
/** Total value allowed within one period (hex wei). */
|
|
2336
|
+
maxValueWeiPerPeriod?: string;
|
|
2337
|
+
}
|
|
2338
|
+
/** What an app asks for when it requests a permission (#113). */
|
|
2339
|
+
interface PermissionRequestParams {
|
|
2340
|
+
/** The wallet being asked. */
|
|
2341
|
+
walletId: string;
|
|
2342
|
+
/** The scope wanted. The user may narrow it; they can never widen it. */
|
|
2343
|
+
scope: PermissionScope;
|
|
2344
|
+
/** When it should expire. */
|
|
2345
|
+
expiresAt: Date | string;
|
|
2346
|
+
/** What it is for, in words the user will read. */
|
|
2347
|
+
label?: string;
|
|
2348
|
+
/** The registered app doing the asking — the wallet shows its proven domain. */
|
|
2349
|
+
requesterId?: string;
|
|
2350
|
+
/** Sponsorship policy funding its gas, when the app pays. */
|
|
2351
|
+
paymasterPolicyId?: string;
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* A permission that has been asked for and not yet answered.
|
|
2355
|
+
*
|
|
2356
|
+
* `privateKey` never leaves the caller: the SDK generated the pair locally and registered only the
|
|
2357
|
+
* public half, so nothing secret is on the wire, in the link, or in the wallet.
|
|
2358
|
+
*/
|
|
2359
|
+
interface PermissionRequest {
|
|
2360
|
+
/** Id of the pending grant — poll it, and use it to sign once approved. */
|
|
2361
|
+
sessionKeyId: string;
|
|
2362
|
+
/** The private half, held only here. Store it as you would any signing key. */
|
|
2363
|
+
privateKey: string;
|
|
2364
|
+
/** Its public half, which is what the API was told. */
|
|
2365
|
+
publicKey: string;
|
|
2366
|
+
/** Where to send the user to answer: open it, or render it as a QR code. */
|
|
2367
|
+
url: string;
|
|
2368
|
+
/** Whether an answer is still outstanding — `approved` already for a wallet with no owner to ask. */
|
|
2369
|
+
status: PermissionGrantStatus;
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* An app that may ask a user for a permission (#539).
|
|
2373
|
+
*
|
|
2374
|
+
* `name` and `iconUrl` are what the app CLAIMS about itself and are never verified; `origin` is the
|
|
2375
|
+
* only part that can carry trust, and only when `status` is `verified` — meaning that domain
|
|
2376
|
+
* published a document naming the tenant. A consent screen that shows an unproven domain as proven
|
|
2377
|
+
* is worse than one that shows nothing.
|
|
2378
|
+
*/
|
|
2379
|
+
interface AppRequester {
|
|
2380
|
+
id: string;
|
|
2381
|
+
/** The https origin that identifies the app. */
|
|
2382
|
+
origin: string;
|
|
2383
|
+
/** Claimed by the app; never verified. */
|
|
2384
|
+
name: string;
|
|
2385
|
+
/** Claimed by the app; never verified. */
|
|
2386
|
+
iconUrl?: string | null;
|
|
2387
|
+
status: 'pending' | 'verified' | 'failed';
|
|
2388
|
+
/** When the domain last proved the claim; null until it has. */
|
|
2389
|
+
verifiedAt?: string | null;
|
|
2390
|
+
/** Why the last check failed — what the app's operator has to fix. */
|
|
2391
|
+
lastFailure?: string | null;
|
|
2392
|
+
}
|
|
2393
|
+
/** Whether the wallet's owner has answered a permission request. */
|
|
2394
|
+
type PermissionGrantStatus = 'pending' | 'approved' | 'declined';
|
|
2395
|
+
/** The answer to a permission request, and what was actually granted (#113). */
|
|
2396
|
+
interface PermissionDecision {
|
|
2397
|
+
status: PermissionGrantStatus;
|
|
2398
|
+
/** The permission as it now stands — the scope the user accepted, which may be narrower than asked. */
|
|
2399
|
+
key: SessionKey;
|
|
2400
|
+
/** True when the user accepted less than was asked for, so an app can adapt instead of failing opaquely. */
|
|
2401
|
+
narrowed: boolean;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2072
2404
|
/**
|
|
2073
2405
|
* Embedded end-user authentication (#6) — Privy-style login that maps an end-user to a
|
|
2074
2406
|
* non-custodial wallet, distinct from the dashboard member auth. The developer's API key
|
|
@@ -2150,6 +2482,86 @@ interface PasskeyCeremony {
|
|
|
2150
2482
|
get(options: unknown): Promise<unknown>;
|
|
2151
2483
|
}
|
|
2152
2484
|
|
|
2485
|
+
/**
|
|
2486
|
+
* This browser (or Node process) as a **member device** (#53) — the client half of the
|
|
2487
|
+
* per-membership device registry the multi-device wallet model is built on.
|
|
2488
|
+
*
|
|
2489
|
+
* A member-bound wallet gives each device its OWN share, and the parties are named
|
|
2490
|
+
* `member:<membershipId>:<deviceId>`. That only works if a device can say which one it is,
|
|
2491
|
+
* consistently, across reloads — so the id is minted once and persisted, never derived from
|
|
2492
|
+
* anything the browser may change (a user agent, an install id, a session).
|
|
2493
|
+
*
|
|
2494
|
+
* The id is **not a secret and not a credential**: it appears verbatim in the protocol roles every
|
|
2495
|
+
* other party sees, and holding it grants nothing. What proves the device is the share it holds and
|
|
2496
|
+
* the member session it registered under. Which is why it is kept in a plain
|
|
2497
|
+
* {@link KeyValueStore} rather than the sealed share store — sealing a public identifier would only
|
|
2498
|
+
* make it unavailable before unlock, when registration needs it most.
|
|
2499
|
+
*
|
|
2500
|
+
* Every call here is MEMBER-scoped (`Authorization: Bearer <member access token>`, or the ambient
|
|
2501
|
+
* dashboard cookie): a device belongs to a member seat, not to a tenant API key.
|
|
2502
|
+
*/
|
|
2503
|
+
declare class Devices {
|
|
2504
|
+
private readonly http;
|
|
2505
|
+
private readonly store?;
|
|
2506
|
+
constructor(http: HttpClient, store?: KeyValueStore | undefined);
|
|
2507
|
+
/**
|
|
2508
|
+
* This device's stable id, minting and persisting one on first use.
|
|
2509
|
+
*
|
|
2510
|
+
* A caller with no configured store must pass its own id to {@link register} — an id that is
|
|
2511
|
+
* regenerated per call is worse than none: the registry would fill with orphan devices and none
|
|
2512
|
+
* of them would match the share this browser actually holds.
|
|
2513
|
+
*
|
|
2514
|
+
* @throws {WaaskeyError} `device_store_required` — no store configured to persist the id.
|
|
2515
|
+
*/
|
|
2516
|
+
deviceId(): Promise<string>;
|
|
2517
|
+
/**
|
|
2518
|
+
* The persisted device id, or `undefined` when this client has never minted one.
|
|
2519
|
+
*
|
|
2520
|
+
* Read-only on purpose: it is how other resources DEFAULT to this device without minting an
|
|
2521
|
+
* identity as a side effect of joining a ceremony. A silently minted id would be one this
|
|
2522
|
+
* device never registered and holds no share for.
|
|
2523
|
+
*/
|
|
2524
|
+
stored(): Promise<string | undefined>;
|
|
2525
|
+
/**
|
|
2526
|
+
* Register (or re-register) this device under the caller's member seat.
|
|
2527
|
+
*
|
|
2528
|
+
* Idempotent by `deviceId`: the backend upserts, so calling it on every start refreshes
|
|
2529
|
+
* `lastSeenAt` and rotates the encryption public key without creating a second device. That
|
|
2530
|
+
* matters — a duplicate device would appear in the picker as a share-holder candidate that holds
|
|
2531
|
+
* nothing.
|
|
2532
|
+
*
|
|
2533
|
+
* `encryptionPublicKey` is this device's X25519 key; dealers seal reshare sub-shares and FROST
|
|
2534
|
+
* round-2 packages to it, so a device that will hold a share should register one.
|
|
2535
|
+
*/
|
|
2536
|
+
register(registration: DeviceRegistration, signal?: AbortSignal): Promise<Device>;
|
|
2537
|
+
/** The caller's own registered devices — this one and their others. */
|
|
2538
|
+
list(signal?: AbortSignal): Promise<Device[]>;
|
|
2539
|
+
/**
|
|
2540
|
+
* The caller's devices, each marked whether it is THIS client (#57).
|
|
2541
|
+
*
|
|
2542
|
+
* A device-management UI cannot work this out on its own: the registry returns a member's
|
|
2543
|
+
* devices with no notion of who is asking, and the only thing that distinguishes this browser is
|
|
2544
|
+
* the id stored here. Getting it wrong is not cosmetic — "revoke" next to the wrong row revokes
|
|
2545
|
+
* the device the user is standing on, and the backend will happily do it.
|
|
2546
|
+
*
|
|
2547
|
+
* Every device reads as "not this one" when this client has no stored id, which is the honest
|
|
2548
|
+
* answer: it has no identity to match against.
|
|
2549
|
+
*/
|
|
2550
|
+
mine(signal?: AbortSignal): Promise<Array<Device & {
|
|
2551
|
+
isThisDevice: boolean;
|
|
2552
|
+
}>>;
|
|
2553
|
+
/**
|
|
2554
|
+
* Revoke one of the caller's own devices by its registry id (not its `deviceId`).
|
|
2555
|
+
*
|
|
2556
|
+
* The backend tombstones any live shares it holds, and REFUSES (409) when that would strand a
|
|
2557
|
+
* wallet mid-keygen or drop an active wallet below its signing threshold — losing the ability to
|
|
2558
|
+
* sign is not something a device-management action gets to do quietly.
|
|
2559
|
+
*/
|
|
2560
|
+
revoke(id: string, signal?: AbortSignal): Promise<void>;
|
|
2561
|
+
/** Forget this browser's device id, so the next {@link deviceId} mints a new one. */
|
|
2562
|
+
forget(): Promise<void>;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2153
2565
|
/**
|
|
2154
2566
|
* Org-member (dashboard "plane B") authentication — a bearer member login for a NON-browser
|
|
2155
2567
|
* consumer (a Node app, or the browser extension via the SDK) that cannot carry the ambient
|
|
@@ -2193,6 +2605,31 @@ declare class Members {
|
|
|
2193
2605
|
restore(session: MemberSession): void;
|
|
2194
2606
|
/** Clear the held member session (sign out); member calls fall back to the cookie path afterwards. */
|
|
2195
2607
|
memberSignOut(): void;
|
|
2608
|
+
/**
|
|
2609
|
+
* The tenant's team members, optionally narrowed to the share-holder-eligible roster (#57) —
|
|
2610
|
+
* what a wallet creator picks share-holders from.
|
|
2611
|
+
*
|
|
2612
|
+
* `canHoldShare` is a per-membership capability, not part of the login identity, so it is only
|
|
2613
|
+
* on this view: the session's own `member` does not carry it (see {@link eligibility}).
|
|
2614
|
+
*/
|
|
2615
|
+
list(options?: {
|
|
2616
|
+
canHoldShare?: boolean;
|
|
2617
|
+
}, signal?: AbortSignal): Promise<TeamMember[]>;
|
|
2618
|
+
/**
|
|
2619
|
+
* Whether the logged-in member may hold a wallet key share, and who else may — one call, because
|
|
2620
|
+
* a device-picker UI needs both at once.
|
|
2621
|
+
*
|
|
2622
|
+
* It reads the roster to answer a question about the caller because the member session does not
|
|
2623
|
+
* carry `canHoldShare` (the backend's session view is the login identity, `IMember`). Worth
|
|
2624
|
+
* knowing rather than hiding: a UI that offers "hold a share on this device" to an ineligible
|
|
2625
|
+
* member produces a create that the backend refuses at binding time, after the user has chosen.
|
|
2626
|
+
*
|
|
2627
|
+
* @throws {WaaskeyError} `unauthorized` — no member session is held.
|
|
2628
|
+
*/
|
|
2629
|
+
eligibility(signal?: AbortSignal): Promise<{
|
|
2630
|
+
canHoldShare: boolean;
|
|
2631
|
+
eligible: TeamMember[];
|
|
2632
|
+
}>;
|
|
2196
2633
|
}
|
|
2197
2634
|
|
|
2198
2635
|
/**
|
|
@@ -2356,6 +2793,108 @@ declare class Reshare {
|
|
|
2356
2793
|
*/
|
|
2357
2794
|
declare function epochShareKey(walletId: string, keyEpoch: number): string;
|
|
2358
2795
|
|
|
2796
|
+
/**
|
|
2797
|
+
* The `sessionKeys` resource — exercise a delegated permission (#112).
|
|
2798
|
+
*
|
|
2799
|
+
* A session key is a scoped, time-limited signer the wallet granted: it may call certain contracts,
|
|
2800
|
+
* certain selectors, up to a value, a bounded number of times. Creating and revoking one has been
|
|
2801
|
+
* possible for a while; USING one had no client at all, which is what this closes.
|
|
2802
|
+
*
|
|
2803
|
+
* Two credentials are involved and they are not interchangeable — the surprise everyone hits once:
|
|
2804
|
+
*
|
|
2805
|
+
* * the **API key** on the client, which must carry the `SIGN` scope: the session private key
|
|
2806
|
+
* alone does not reach the API;
|
|
2807
|
+
* * the **session private key**, which never leaves the caller and is what actually authorizes the
|
|
2808
|
+
* operation on-chain.
|
|
2809
|
+
*/
|
|
2810
|
+
declare class SessionKeys {
|
|
2811
|
+
private readonly http;
|
|
2812
|
+
private readonly walletUrl?;
|
|
2813
|
+
constructor(http: HttpClient, walletUrl?: string | undefined);
|
|
2814
|
+
/**
|
|
2815
|
+
* Ask a user for a permission, and get back a link to send them to (#113).
|
|
2816
|
+
*
|
|
2817
|
+
* The keypair is generated HERE and only its public half is registered, so nothing secret ever
|
|
2818
|
+
* travels: not in the request, not in the link, not through the wallet. The private key in the
|
|
2819
|
+
* result is the app's to keep — there is no way to retrieve it later, by design.
|
|
2820
|
+
*
|
|
2821
|
+
* The link is where the user answers. Open it, or render it as a QR code for a wallet on another
|
|
2822
|
+
* device. Then wait for the answer with {@link waitForDecision}.
|
|
2823
|
+
*
|
|
2824
|
+
* @example
|
|
2825
|
+
* ```ts
|
|
2826
|
+
* const req = await waaskey.sessionKeys.request({
|
|
2827
|
+
* walletId, label: 'Dungeon Quest — one battle',
|
|
2828
|
+
* scope: { allowedContracts: [game], maxCalls: 50, periodSeconds: 3600, maxCallsPerPeriod: 10 },
|
|
2829
|
+
* expiresAt: new Date(Date.now() + 24 * 3600_000),
|
|
2830
|
+
* });
|
|
2831
|
+
* showQrCode(req.url);
|
|
2832
|
+
* const { status, key, narrowed } = await waaskey.sessionKeys.waitForDecision(req.sessionKeyId);
|
|
2833
|
+
* ```
|
|
2834
|
+
*/
|
|
2835
|
+
request(params: PermissionRequestParams, signal?: AbortSignal): Promise<PermissionRequest>;
|
|
2836
|
+
/** One permission as it now stands — including whether its owner has answered yet. */
|
|
2837
|
+
get(sessionKeyId: string, signal?: AbortSignal): Promise<SessionKey>;
|
|
2838
|
+
/**
|
|
2839
|
+
* The app that asked for a permission — who it says it is, and whether its domain proved it.
|
|
2840
|
+
*
|
|
2841
|
+
* A wallet rendering a consent screen needs this, and hand-rolling the call is how a client ends
|
|
2842
|
+
* up showing `name` without `status` beside it — which reads as verified whether or not it is.
|
|
2843
|
+
*/
|
|
2844
|
+
requester(requesterId: string, signal?: AbortSignal): Promise<AppRequester>;
|
|
2845
|
+
/**
|
|
2846
|
+
* Wait for the user to answer a permission request (#113).
|
|
2847
|
+
*
|
|
2848
|
+
* Polls, rather than waiting on a redirect back: a user who answers on their phone, or closes the
|
|
2849
|
+
* tab, or takes a minute to read the screen would otherwise strand the app with no answer at all.
|
|
2850
|
+
*
|
|
2851
|
+
* Returns as soon as the wallet's owner has decided — `declined` is an answer, not an error, and
|
|
2852
|
+
* is returned rather than thrown so the app can say something useful. A request that is never
|
|
2853
|
+
* answered times out, which IS an error: it is indistinguishable from a user who walked away.
|
|
2854
|
+
*
|
|
2855
|
+
* `narrowed` says the user accepted less than was asked (they may narrow, never widen), so an app
|
|
2856
|
+
* can adapt to the smaller scope instead of failing opaquely on the first call outside it.
|
|
2857
|
+
*/
|
|
2858
|
+
waitForDecision(sessionKeyId: string, options?: WaitForDecisionOptions): Promise<PermissionDecision>;
|
|
2859
|
+
/** The tenant's active session keys, newest first (optionally for one wallet). */
|
|
2860
|
+
list(walletId?: string, signal?: AbortSignal): Promise<SessionKey[]>;
|
|
2861
|
+
/**
|
|
2862
|
+
* Sign a call with a session key and submit it as a UserOperation.
|
|
2863
|
+
*
|
|
2864
|
+
* The op is prepared server-side first, because its gas and paymaster fields are part of what the
|
|
2865
|
+
* signature covers: signing a locally-guessed op would produce a signature for an operation the
|
|
2866
|
+
* bundler never sees. Those exact fields are then sent back with the signature, so what was
|
|
2867
|
+
* signed is what is submitted.
|
|
2868
|
+
*
|
|
2869
|
+
* @example
|
|
2870
|
+
* ```ts
|
|
2871
|
+
* const { userOpHash } = await waaskey.sessionKeys.send({
|
|
2872
|
+
* sessionKeyId, privateKey, // the granted permission
|
|
2873
|
+
* sender, chainId: 'evm:1', // the smart account it acts for
|
|
2874
|
+
* callData, contract, selector, valueWei: '0x0',
|
|
2875
|
+
* });
|
|
2876
|
+
* ```
|
|
2877
|
+
*/
|
|
2878
|
+
send(params: SessionKeySendParams, signal?: AbortSignal): Promise<SessionKeySendResult>;
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Sign a UserOperation hash the way a SimpleAccount verifies it: over the EIP-191 personal-sign
|
|
2882
|
+
* form, not the raw hash. The account contract recovers from
|
|
2883
|
+
* `userOpHash.toEthSignedMessageHash()`, so anything else is a signature the chain rejects — and
|
|
2884
|
+
* the API now rejects it earlier, which is the same answer sooner.
|
|
2885
|
+
*/
|
|
2886
|
+
declare function signUserOpHash(userOpHash: string, privateKey: string): string;
|
|
2887
|
+
/** How long to wait for an answer, and how often to look. */
|
|
2888
|
+
interface WaitForDecisionOptions {
|
|
2889
|
+
/** Give up after this long. Default 5 minutes. */
|
|
2890
|
+
timeoutMs?: number;
|
|
2891
|
+
/** How often to ask. Default every 2 seconds. */
|
|
2892
|
+
pollMs?: number;
|
|
2893
|
+
signal?: AbortSignal;
|
|
2894
|
+
/** The scope that was asked for; supply it to learn whether the user narrowed it. */
|
|
2895
|
+
asked?: PermissionScope;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2359
2898
|
/** Device-party dependencies a {@link Wallet} needs to co-sign an ed25519 (FROST) transaction locally. */
|
|
2360
2899
|
interface WalletDeviceDeps {
|
|
2361
2900
|
mpc?: MpcCore;
|
|
@@ -2524,6 +3063,8 @@ interface WalletsDeps {
|
|
|
2524
3063
|
primePool?: PrimePool;
|
|
2525
3064
|
/** Optional analytics emitter for wallet lifecycle events. */
|
|
2526
3065
|
analytics?: Analytics;
|
|
3066
|
+
/** This client's device identity (#53) — supplies the default `deviceId` for member ceremonies. */
|
|
3067
|
+
devices?: Devices;
|
|
2527
3068
|
}
|
|
2528
3069
|
/**
|
|
2529
3070
|
* The `wallets` resource: create and load wallets, plus the approver actions
|
|
@@ -2694,6 +3235,8 @@ declare class Wallets {
|
|
|
2694
3235
|
* it unjoined would hang the whole ceremony, so failing loud beats silently deadlocking.
|
|
2695
3236
|
*/
|
|
2696
3237
|
private runSecpKeygen;
|
|
3238
|
+
/** The `[device, server, user_backup]` ceremony itself — see {@link runSecpKeygen} for why the primes are leased. */
|
|
3239
|
+
private runNonCustodialKeygen;
|
|
2697
3240
|
/**
|
|
2698
3241
|
* Register a sealed user_backup {@link RecoveryRegisterPayload} server-side, with a small bounded retry
|
|
2699
3242
|
* on TRANSIENT failures (network / 5xx / rate-limit) — a flaky moment must not cost the backup. A
|
|
@@ -2776,14 +3319,30 @@ declare class Wallets {
|
|
|
2776
3319
|
/** Poll the wallet until keygen completes (ACTIVE), or throw on failure/timeout. */
|
|
2777
3320
|
private waitUntilActive;
|
|
2778
3321
|
/** Poll the member's own sign ceremony until the t-of-n quorum is fixed AND this member is selected, or throw on timeout. */
|
|
3322
|
+
/**
|
|
3323
|
+
* The device id to act as: an explicit one, else the client's stored id, else none.
|
|
3324
|
+
*
|
|
3325
|
+
* `undefined` is a legitimate answer, not a failure — a single-device member and every existing
|
|
3326
|
+
* consumer built before per-device shares work exactly as they did, on the un-suffixed key and
|
|
3327
|
+
* the un-parameterised endpoints.
|
|
3328
|
+
*/
|
|
3329
|
+
private resolveDeviceId;
|
|
2779
3330
|
private waitUntilReady;
|
|
2780
3331
|
}
|
|
2781
3332
|
/**
|
|
2782
3333
|
* Storage key for a member-bound wallet's per-member share (#349) — distinct from the embedded
|
|
2783
3334
|
* flow's bare `walletId` key ({@link Wallet}'s single device share), since several members' shares
|
|
2784
3335
|
* for the SAME wallet may live in one {@link ShareStore} (e.g. a shared device, or a test harness).
|
|
3336
|
+
*
|
|
3337
|
+
* With `deviceId` (#54) the key is per DEVICE as well: one member may hold DISTINCT shares of the
|
|
3338
|
+
* same wallet across their own devices (browser, phone), and a device must only ever read its own.
|
|
3339
|
+
*
|
|
3340
|
+
* Without it the key keeps its original shape, and that is not a convenience — a share sealed under
|
|
3341
|
+
* the old key by an earlier SDK version is still in the user's browser, and a key change that could
|
|
3342
|
+
* not find it would look exactly like a lost wallet. Same reasoning as `epochShareKey` keeping
|
|
3343
|
+
* epoch 1 on the bare wallet id.
|
|
2785
3344
|
*/
|
|
2786
|
-
declare function memberShareKey(walletId: string, membershipId: string): string;
|
|
3345
|
+
declare function memberShareKey(walletId: string, membershipId: string, deviceId?: string): string;
|
|
2787
3346
|
/**
|
|
2788
3347
|
* Storage key for a non-custodial wallet's PENDING user_backup backup (#351/#78) — the sealed
|
|
2789
3348
|
* {@link RecoveryRegisterPayload} ciphertext {@link Wallets.create} persists before registering it
|
|
@@ -2823,8 +3382,12 @@ declare class Waaskey {
|
|
|
2823
3382
|
readonly auth: Auth;
|
|
2824
3383
|
/** The `members` resource — org-member (dashboard "plane B") bearer login for headless consumers. */
|
|
2825
3384
|
readonly members: Members;
|
|
3385
|
+
/** The `devices` resource — this client as a member DEVICE that can hold an MPC share (#53). */
|
|
3386
|
+
readonly devices: Devices;
|
|
2826
3387
|
/** The `onramp` resource — fund the wallet with fiat via a provider on-ramp. */
|
|
2827
3388
|
readonly onramp: Onramp;
|
|
3389
|
+
/** The `sessionKeys` resource — act with a delegated, scoped permission (#112). */
|
|
3390
|
+
readonly sessionKeys: SessionKeys;
|
|
2828
3391
|
/** Default fetch used by the optional {@link broadcast} helper (from `WaaskeyOptions.fetch`). */
|
|
2829
3392
|
private readonly defaultFetch?;
|
|
2830
3393
|
constructor(options: WaaskeyOptions);
|
|
@@ -3034,6 +3597,93 @@ declare function passkeyFactorEnrollment(credentialId: string): FactorEnrollment
|
|
|
3034
3597
|
*/
|
|
3035
3598
|
declare function passkeyFactorVerification(challenge: string, options?: SigningAssertionOptions): Promise<FactorVerification>;
|
|
3036
3599
|
|
|
3600
|
+
/** How an envelope's secret is wrapped. Both may be present — either one opens it. */
|
|
3601
|
+
type UnlockMethod = 'passkey_prf' | 'passphrase';
|
|
3602
|
+
/** One wrapped copy of the secret. Every field here is NON-secret and safe to store beside the data. */
|
|
3603
|
+
interface UnlockKeyWrap {
|
|
3604
|
+
method: UnlockMethod;
|
|
3605
|
+
/** The secret, sealed under the key this method derives. */
|
|
3606
|
+
wrapped: string;
|
|
3607
|
+
/** Base64 salt: the PBKDF2 salt for a passphrase, the PRF evaluation salt for a passkey. */
|
|
3608
|
+
salt: string;
|
|
3609
|
+
/** Which passkey to authenticate with (`passkey_prf` only). */
|
|
3610
|
+
credentialId?: string;
|
|
3611
|
+
}
|
|
3612
|
+
/** A secret that several methods can unlock. */
|
|
3613
|
+
interface SecretEnvelope {
|
|
3614
|
+
wraps: UnlockKeyWrap[];
|
|
3615
|
+
}
|
|
3616
|
+
/** A passkey PRF secret — the exact shape `PasskeyPrfSecretProvider` returns. */
|
|
3617
|
+
interface PasskeyUnlockKey {
|
|
3618
|
+
/** Base64 PRF output. Never persisted. */
|
|
3619
|
+
secret: string;
|
|
3620
|
+
/** Base64 PRF evaluation salt — non-secret, stored with the wrap so it can be replayed. */
|
|
3621
|
+
salt: string;
|
|
3622
|
+
credentialId: string;
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* A local **unlock envelope**: one secret, wrapped once per method the user enrolled (#52 B4).
|
|
3626
|
+
*
|
|
3627
|
+
* A browser member device seals its key share at rest under a secret, and that secret has to come
|
|
3628
|
+
* from the user on every unlock — never from storage, or a stolen browser profile carries the key to
|
|
3629
|
+
* its own ciphertext. Two things can supply it, and neither is good enough alone:
|
|
3630
|
+
*
|
|
3631
|
+
* - a **passkey** (WebAuthn PRF) — nothing to remember, but not every browser implements PRF, and a
|
|
3632
|
+
* lost device takes the only key with it;
|
|
3633
|
+
* - a **passphrase** — works everywhere and moves between browsers, but is one more thing to know.
|
|
3634
|
+
*
|
|
3635
|
+
* So the secret is not derived from either. It is random, and each method wraps a copy of it. Any
|
|
3636
|
+
* enrolled method opens the envelope, a method can be added later without re-sealing anything the
|
|
3637
|
+
* secret protects, and losing one method is not losing the wallet — which is the whole reason the
|
|
3638
|
+
* same shape already backs the recovery backup (`recovery-envelope.ts`).
|
|
3639
|
+
*
|
|
3640
|
+
* Nothing here is secret at rest: the wraps, salts and credential id yield nothing without the
|
|
3641
|
+
* authenticator or the passphrase.
|
|
3642
|
+
*/
|
|
3643
|
+
declare function newUnlockSecret(): string;
|
|
3644
|
+
/**
|
|
3645
|
+
* Wrap `secret` for the given methods. At least one is required — an envelope nothing can open is
|
|
3646
|
+
* a wallet nobody can use.
|
|
3647
|
+
*
|
|
3648
|
+
* @throws {WaaskeyError} `validation` — no method supplied.
|
|
3649
|
+
*/
|
|
3650
|
+
declare function sealSecret(secret: string, methods: {
|
|
3651
|
+
passphrase?: string;
|
|
3652
|
+
passkey?: PasskeyUnlockKey;
|
|
3653
|
+
}): Promise<SecretEnvelope>;
|
|
3654
|
+
/**
|
|
3655
|
+
* Add (or replace) one method on an existing envelope, so a user can enrol a passphrase on a
|
|
3656
|
+
* device that already unlocks by passkey — or the reverse — without touching what the secret seals.
|
|
3657
|
+
*
|
|
3658
|
+
* Replacing rather than appending is deliberate: two wraps of the same method are two passphrases
|
|
3659
|
+
* that both work, one of which the user does not know they set.
|
|
3660
|
+
*/
|
|
3661
|
+
declare function addMethod(envelope: SecretEnvelope, secret: string, method: {
|
|
3662
|
+
passphrase?: string;
|
|
3663
|
+
passkey?: PasskeyUnlockKey;
|
|
3664
|
+
}): Promise<SecretEnvelope>;
|
|
3665
|
+
/**
|
|
3666
|
+
* Drop a method. Refuses to remove the last one — an envelope with no wraps is a share nobody,
|
|
3667
|
+
* including its owner, can ever open again.
|
|
3668
|
+
*
|
|
3669
|
+
* @throws {WaaskeyError} `validation` — removing this method would leave nothing.
|
|
3670
|
+
*/
|
|
3671
|
+
declare function removeMethod(envelope: SecretEnvelope, method: UnlockMethod): SecretEnvelope;
|
|
3672
|
+
/**
|
|
3673
|
+
* Open the envelope with whatever the user has.
|
|
3674
|
+
*
|
|
3675
|
+
* A passkey is preferred when both are available and enrolled: it is the path with nothing to type.
|
|
3676
|
+
*
|
|
3677
|
+
* @throws {WaaskeyError} `unauthorized` — nothing supplied matches an enrolled method, or the
|
|
3678
|
+
* passphrase is wrong.
|
|
3679
|
+
*/
|
|
3680
|
+
declare function openSecret(envelope: SecretEnvelope, opener: {
|
|
3681
|
+
passphrase?: string;
|
|
3682
|
+
passkeySecret?: string;
|
|
3683
|
+
}): Promise<string>;
|
|
3684
|
+
/** Which methods this envelope accepts — what an unlock screen offers. */
|
|
3685
|
+
declare function enrolledMethods(envelope: SecretEnvelope): UnlockMethod[];
|
|
3686
|
+
|
|
3037
3687
|
/**
|
|
3038
3688
|
* **Optional** client-side broadcast helper.
|
|
3039
3689
|
*
|
|
@@ -3182,4 +3832,4 @@ declare class PasskeyPrfSecretProvider {
|
|
|
3182
3832
|
}): Promise<PasskeyPrfResult>;
|
|
3183
3833
|
}
|
|
3184
3834
|
|
|
3185
|
-
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 PasskeyBackupKey, type PasskeyCeremony, type PasskeyPrfEnrollOptions, type PasskeyPrfResult, PasskeyPrfSecretProvider, type PrfCeremony, PrimePool, type PrimePoolOptions, type PrimePoolStore, type RecoverParams, type RecoverSignParams, type RecoverWalletResponse, Recovery, type RecoveryBackupEnvelope, type RecoveryChallengeResponse, type RecoveryFactor, type RecoveryKeyWrap, type RecoveryRetrieveResponse, type RecoveryShareInfo, type RegisterRecoveryParams, Reshare, type ReshareAssemblyMaterial, type ReshareCeremony, type ReshareCompletionCeremony, type ReshareCompletionParams, type ReshareCompletionResult, type ReshareRotateParams, type ReshareRotateResult, 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, openRecoveryBackup, passkeyFactorEnrollment, passkeyFactorVerification, sealRecoveryBackup, userBackupPendingKey, validateCustodyPolicy, verifyWasmIntegrity };
|
|
3835
|
+
export { Analytics, type AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AppRequester, 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 Device, type DeviceCompleteReshareParams, type DeviceCompleteReshareResult, type DeviceKeygenParams, type DeviceKeygenResult, type DeviceRegistration, type DeviceReshareAssembleParams, type DeviceReshareAssembleResult, type DeviceReshareMaterial, type DeviceSignParams, type DeviceSignResult, Devices, 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 PasskeyBackupKey, type PasskeyCeremony, type PasskeyEnroller, type PasskeyPrfEnrollOptions, type PasskeyPrfResult, PasskeyPrfSecretProvider, type PasskeyUnlockKey, type PermissionDecision, type PermissionGrantStatus, type PermissionRequest, type PermissionRequestParams, type PermissionScope, type PrfCeremony, type PrimeLease, PrimePool, type PrimePoolOptions, type PrimePoolStore, type RecoverParams, type RecoverSignParams, type RecoverWalletResponse, Recovery, type RecoveryBackupEnvelope, type RecoveryChallengeResponse, type RecoveryEnrolment, type RecoveryFactor, type RecoveryKeyWrap, type RecoveryRetrieveResponse, type RecoveryShareInfo, type RegisterRecoveryParams, Reshare, type ReshareAssemblyMaterial, type ReshareCeremony, type ReshareCompletionCeremony, type ReshareCompletionParams, type ReshareCompletionResult, type ReshareRotateParams, type ReshareRotateResult, type ReshareWalletResponse, type SecretEnvelope, type SendOptions, type SendParams, type SendResult, SessionKeys, 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 TeamMember, type TokenBalanceOptions, type TxStatus, type UnlockKeyWrap, type UnlockMethod, type VerifiedWasmLoaderOptions, Waaskey, WaaskeyError, type WaaskeyErrorCode, type WaaskeyErrorOptions, type WaaskeyOptions, type WaitForDecisionOptions, Wallet, type WalletActionType, type WalletBackupParams, type WalletCeremony, type WalletCurve, type WalletData, type WalletShareholder, type WalletStatus, Wallets, WasmMpcCore, addMethod, broadcast, createVerifiedClientWasmLoader, enrolledMethods, epochShareKey, formatUnits, generateRecoveryCode, getSigningAssertion, isNonCustodial, isPasskeyAssertionSupported, isPasskeySupported, isPrfSupported, loadClientWasm, memberShareKey, newUnlockSecret, openRecoveryBackup, openSecret, passkeyFactorEnrollment, passkeyFactorVerification, removeMethod, sealRecoveryBackup, sealSecret, signUserOpHash, userBackupPendingKey, validateCustodyPolicy, verifyWasmIntegrity };
|