@turnkey/core 1.0.0-beta.5 → 1.0.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.
Files changed (61) hide show
  1. package/README.MD +3 -1
  2. package/dist/__clients__/core.d.ts +118 -30
  3. package/dist/__clients__/core.d.ts.map +1 -1
  4. package/dist/__clients__/core.js +362 -102
  5. package/dist/__clients__/core.js.map +1 -1
  6. package/dist/__clients__/core.mjs +364 -104
  7. package/dist/__clients__/core.mjs.map +1 -1
  8. package/dist/__generated__/sdk-client-base.d.ts +17 -0
  9. package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
  10. package/dist/__generated__/sdk-client-base.js +256 -5
  11. package/dist/__generated__/sdk-client-base.js.map +1 -1
  12. package/dist/__generated__/sdk-client-base.mjs +256 -5
  13. package/dist/__generated__/sdk-client-base.mjs.map +1 -1
  14. package/dist/__generated__/version.d.ts +1 -1
  15. package/dist/__generated__/version.d.ts.map +1 -1
  16. package/dist/__generated__/version.js +1 -1
  17. package/dist/__generated__/version.mjs +1 -1
  18. package/dist/__inputs__/public_api.types.d.ts +373 -23
  19. package/dist/__inputs__/public_api.types.d.ts.map +1 -1
  20. package/dist/__polyfills__/jest.setup.webcrypto.d.ts +2 -0
  21. package/dist/__polyfills__/jest.setup.webcrypto.d.ts.map +1 -0
  22. package/dist/__stampers__/api/base.d.ts +11 -5
  23. package/dist/__stampers__/api/base.d.ts.map +1 -1
  24. package/dist/__stampers__/api/base.js +32 -10
  25. package/dist/__stampers__/api/base.js.map +1 -1
  26. package/dist/__stampers__/api/base.mjs +32 -10
  27. package/dist/__stampers__/api/base.mjs.map +1 -1
  28. package/dist/__stampers__/api/web/stamper.d.ts.map +1 -1
  29. package/dist/__stampers__/api/web/stamper.js +2 -4
  30. package/dist/__stampers__/api/web/stamper.js.map +1 -1
  31. package/dist/__stampers__/api/web/stamper.mjs +2 -4
  32. package/dist/__stampers__/api/web/stamper.mjs.map +1 -1
  33. package/dist/__types__/base.d.ts +3 -1
  34. package/dist/__types__/base.d.ts.map +1 -1
  35. package/dist/__types__/base.js.map +1 -1
  36. package/dist/__types__/base.mjs.map +1 -1
  37. package/dist/__wallet__/base.d.ts.map +1 -1
  38. package/dist/__wallet__/base.js +6 -6
  39. package/dist/__wallet__/base.js.map +1 -1
  40. package/dist/__wallet__/base.mjs +2 -2
  41. package/dist/__wallet__/base.mjs.map +1 -1
  42. package/dist/__wallet__/stamper.d.ts.map +1 -1
  43. package/dist/__wallet__/stamper.js +7 -6
  44. package/dist/__wallet__/stamper.js.map +1 -1
  45. package/dist/__wallet__/stamper.mjs +8 -7
  46. package/dist/__wallet__/stamper.mjs.map +1 -1
  47. package/dist/__wallet__/wallet-connect/base.js +5 -6
  48. package/dist/__wallet__/wallet-connect/base.js.map +1 -1
  49. package/dist/__wallet__/wallet-connect/base.mjs +1 -2
  50. package/dist/__wallet__/wallet-connect/base.mjs.map +1 -1
  51. package/dist/__wallet__/web/native/solana.js +1 -2
  52. package/dist/__wallet__/web/native/solana.js.map +1 -1
  53. package/dist/__wallet__/web/native/solana.mjs +1 -2
  54. package/dist/__wallet__/web/native/solana.mjs.map +1 -1
  55. package/dist/utils.d.ts +33 -6
  56. package/dist/utils.d.ts.map +1 -1
  57. package/dist/utils.js +160 -18
  58. package/dist/utils.js.map +1 -1
  59. package/dist/utils.mjs +156 -21
  60. package/dist/utils.mjs.map +1 -1
  61. package/package.json +10 -10
@@ -1,7 +1,7 @@
1
1
  import { TurnkeySDKClientBase } from '../__generated__/sdk-client-base.mjs';
2
- import { TurnkeyErrorCodes, TurnkeyError, TurnkeyNetworkError } from '@turnkey/sdk-types';
2
+ import { TurnkeyErrorCodes, TurnkeyError, TurnkeyNetworkError, AuthAction } from '@turnkey/sdk-types';
3
3
  import { SessionKey, DEFAULT_SESSION_EXPIRATION_IN_SECONDS, StamperType, WalletSource, Chain, FilterType, OtpTypeToFilterTypeMap, OtpType, Curve, SignIntent } from '../__types__/base.mjs';
4
- import { withTurnkeyErrorHandling, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, isEthereumProvider, getPublicKeyFromStampHeader, toExternalTimestamp, isSolanaProvider, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
4
+ import { withTurnkeyErrorHandling, isValidPasskeyName, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, addressFromPublicKey, getCurveTypeFromProvider, getPublicKeyFromStampHeader, toExternalTimestamp, getAuthenticatorAddresses, isEthereumProvider, isSolanaProvider, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, getPolicySignature, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
5
5
  import { createStorageManager } from '../__storage__/base.mjs';
6
6
  import { CrossPlatformApiKeyStamper } from '../__stampers__/api/base.mjs';
7
7
  import { CrossPlatformPasskeyStamper } from '../__stampers__/passkey/base.mjs';
@@ -21,9 +21,9 @@ class TurnkeyClient {
21
21
  * - Handles both web and React Native environments, automatically selecting the appropriate passkey creation flow.
22
22
  * - The resulting attestation and challenge can be used to register the passkey with Turnkey.
23
23
  *
24
- * @param params.name - name of the passkey. If not provided, defaults to "A Passkey".
25
- * @param params.displayName - display name for the passkey. If not provided, defaults to "A Passkey".
24
+ * @param params.name - display name for the passkey (defaults to a generated name based on the current timestamp).
26
25
  * @param params.stampWith - parameter to stamp the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
26
+ * @param params.challenge - challenge string to use for passkey registration. If not provided, a new challenge will be generated.
27
27
  * @returns A promise that resolves to an object containing:
28
28
  * - attestation: attestation object returned from the passkey creation process.
29
29
  * - encodedChallenge: encoded challenge string used for passkey registration.
@@ -31,16 +31,16 @@ class TurnkeyClient {
31
31
  */
32
32
  this.createPasskey = async (params) => {
33
33
  return withTurnkeyErrorHandling(async () => {
34
- const name = params?.name || "A Passkey";
35
- const displayName = params?.displayName || "A Passkey";
34
+ const name = isValidPasskeyName(params?.name || `passkey-${Date.now()}`);
36
35
  let passkey;
37
36
  if (isWeb()) {
38
37
  const res = await this.passkeyStamper?.createWebPasskey({
39
38
  publicKey: {
40
39
  user: {
41
40
  name,
42
- displayName,
41
+ displayName: name,
43
42
  },
43
+ ...(params?.challenge && { challenge: params.challenge }),
44
44
  },
45
45
  });
46
46
  if (!res) {
@@ -54,7 +54,7 @@ class TurnkeyClient {
54
54
  else if (isReactNative()) {
55
55
  const res = await this.passkeyStamper?.createReactNativePasskey({
56
56
  name,
57
- displayName,
57
+ displayName: name,
58
58
  });
59
59
  if (!res) {
60
60
  throw new TurnkeyError("Failed to create React Native passkey", TurnkeyErrorCodes.INTERNAL_ERROR);
@@ -71,7 +71,7 @@ class TurnkeyClient {
71
71
  }, {
72
72
  errorMessage: "Failed to create passkey",
73
73
  errorCode: TurnkeyErrorCodes.CREATE_PASSKEY_ERROR,
74
- customMessageByMessages: {
74
+ customErrorsByMessages: {
75
75
  "timed out or was not allowed": {
76
76
  message: "Passkey creation was cancelled by the user.",
77
77
  code: TurnkeyErrorCodes.SELECT_PASSKEY_CANCELLED,
@@ -125,21 +125,23 @@ class TurnkeyClient {
125
125
  * @param params.publicKey - public key to use for authentication. If not provided, a new key pair will be generated.
126
126
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
127
127
  * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
128
- * @returns A promise that resolves to a signed JWT session token.
128
+ * @returns A promise that resolves to a {@link PasskeyAuthResult}, which includes:
129
+ * - `sessionToken`: the signed JWT session token.
130
+ * - `credentialId`: an empty string.
129
131
  * @throws {TurnkeyError} If there is an error during the passkey login process or if the user cancels the passkey prompt.
130
132
  */
131
133
  this.loginWithPasskey = async (params) => {
132
- let generatedKeyPair = undefined;
134
+ let generatedPublicKey = undefined;
133
135
  return await withTurnkeyErrorHandling(async () => {
134
- generatedKeyPair =
136
+ generatedPublicKey =
135
137
  params?.publicKey || (await this.apiKeyStamper?.createKeyPair());
136
138
  const sessionKey = params?.sessionKey || SessionKey.DefaultSessionkey;
137
139
  const expirationSeconds = params?.expirationSeconds || DEFAULT_SESSION_EXPIRATION_IN_SECONDS;
138
- if (!generatedKeyPair) {
140
+ if (!generatedPublicKey) {
139
141
  throw new TurnkeyError("A publickey could not be found or generated.", TurnkeyErrorCodes.INTERNAL_ERROR);
140
142
  }
141
143
  const sessionResponse = await this.httpClient.stampLogin({
142
- publicKey: generatedKeyPair,
144
+ publicKey: generatedPublicKey,
143
145
  organizationId: this.config.organizationId,
144
146
  expirationSeconds,
145
147
  }, StamperType.Passkey);
@@ -147,12 +149,18 @@ class TurnkeyClient {
147
149
  sessionToken: sessionResponse.session,
148
150
  sessionKey,
149
151
  });
150
- generatedKeyPair = undefined; // Key pair was successfully used, set to null to prevent cleanup
151
- return sessionResponse.session;
152
+ generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
153
+ return {
154
+ sessionToken: sessionResponse.session,
155
+ // TODO: can we return the credentialId here?
156
+ // from a quick glance this is going to be difficult
157
+ // for now we return an empty string
158
+ credentialId: "",
159
+ };
152
160
  }, {
153
161
  errorMessage: "Unable to log in with the provided passkey",
154
162
  errorCode: TurnkeyErrorCodes.PASSKEY_LOGIN_AUTH_ERROR,
155
- customMessageByMessages: {
163
+ customErrorsByMessages: {
156
164
  "timed out or was not allowed": {
157
165
  message: "Passkey login was cancelled by the user.",
158
166
  code: TurnkeyErrorCodes.SELECT_PASSKEY_CANCELLED,
@@ -160,9 +168,9 @@ class TurnkeyClient {
160
168
  },
161
169
  }, {
162
170
  finallyFn: async () => {
163
- if (generatedKeyPair) {
171
+ if (generatedPublicKey) {
164
172
  try {
165
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
173
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
166
174
  }
167
175
  catch (cleanupError) {
168
176
  throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
@@ -180,23 +188,26 @@ class TurnkeyClient {
180
188
  * - Automatically generates a new API key pair for authentication and session management.
181
189
  * - Stores the resulting session token and manages cleanup of unused key pairs.
182
190
  *
191
+ * @param params.passkeyDisplayName - display name for the passkey (defaults to a generated name based on the current timestamp).
183
192
  * @param params.createSubOrgParams - parameters for creating a sub-organization (e.g., authenticators, user metadata).
184
193
  * @param params.sessionKey - session key to use for storing the session (defaults to the default session key).
185
- * @param params.passkeyDisplayName - display name for the passkey (defaults to a generated name based on the current timestamp).
186
194
  * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
187
- * @returns A promise that resolves to a signed JWT session token for the new sub-organization.
195
+ * @param params.challenge - challenge string to use for passkey registration. If not provided, a new challenge will be generated.
196
+ * @returns A promise that resolves to a {@link PasskeyAuthResult}, which includes:
197
+ * - `sessionToken`: the signed JWT session token.
198
+ * - `credentialId`: the credential ID associated with the passkey created.
188
199
  * @throws {TurnkeyError} If there is an error during passkey creation, sub-organization creation, or session storage.
189
200
  */
190
201
  this.signUpWithPasskey = async (params) => {
191
202
  const { createSubOrgParams, passkeyDisplayName, sessionKey = SessionKey.DefaultSessionkey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params || {};
192
- let generatedKeyPair = undefined;
203
+ let generatedPublicKey = undefined;
193
204
  return withTurnkeyErrorHandling(async () => {
194
- generatedKeyPair = await this.apiKeyStamper?.createKeyPair();
205
+ generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
195
206
  const passkeyName = passkeyDisplayName || `passkey-${Date.now()}`;
196
207
  // A passkey will be created automatically when you call this function. The name is passed in
197
208
  const passkey = await this.createPasskey({
198
209
  name: passkeyName,
199
- displayName: passkeyName,
210
+ ...(params?.challenge && { challenge: params.challenge }),
200
211
  });
201
212
  if (!passkey) {
202
213
  throw new TurnkeyError("Failed to create passkey: encoded challenge or attestation is missing", TurnkeyErrorCodes.INTERNAL_ERROR);
@@ -215,8 +226,8 @@ class TurnkeyClient {
215
226
  ],
216
227
  apiKeys: [
217
228
  {
218
- apiKeyName: `passkey-auth-${generatedKeyPair}`,
219
- publicKey: generatedKeyPair,
229
+ apiKeyName: `passkey-auth-${generatedPublicKey}`,
230
+ publicKey: generatedPublicKey,
220
231
  curveType: "API_KEY_CURVE_P256",
221
232
  expirationSeconds: "60",
222
233
  },
@@ -228,28 +239,31 @@ class TurnkeyClient {
228
239
  throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.PASSKEY_SIGNUP_AUTH_ERROR);
229
240
  }
230
241
  const newGeneratedKeyPair = await this.apiKeyStamper?.createKeyPair();
231
- this.apiKeyStamper?.setPublicKeyOverride(generatedKeyPair);
242
+ this.apiKeyStamper?.setTemporaryPublicKey(generatedPublicKey);
232
243
  const sessionResponse = await this.httpClient.stampLogin({
233
244
  publicKey: newGeneratedKeyPair,
234
245
  organizationId: this.config.organizationId,
235
246
  expirationSeconds,
236
247
  });
237
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
248
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
238
249
  await this.storeSession({
239
250
  sessionToken: sessionResponse.session,
240
251
  sessionKey,
241
252
  });
242
- generatedKeyPair = undefined; // Key pair was successfully used, set to null to prevent cleanup
243
- return sessionResponse.session;
253
+ generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
254
+ return {
255
+ sessionToken: sessionResponse.session,
256
+ credentialId: passkey.attestation.credentialId,
257
+ };
244
258
  }, {
245
259
  errorCode: TurnkeyErrorCodes.PASSKEY_SIGNUP_AUTH_ERROR,
246
260
  errorMessage: "Failed to sign up with passkey",
247
261
  }, {
248
262
  finallyFn: async () => {
249
- this.apiKeyStamper?.clearPublicKeyOverride();
250
- if (generatedKeyPair) {
263
+ this.apiKeyStamper?.clearTemporaryPublicKey();
264
+ if (generatedPublicKey) {
251
265
  try {
252
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
266
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
253
267
  }
254
268
  catch (cleanupError) {
255
269
  throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
@@ -268,7 +282,7 @@ class TurnkeyClient {
268
282
  * @returns A promise that resolves to an array of wallet providers.
269
283
  * @throws {TurnkeyError} If the wallet manager is uninitialized or provider retrieval fails.
270
284
  */
271
- this.getWalletProviders = async (chain) => {
285
+ this.fetchWalletProviders = async (chain) => {
272
286
  return withTurnkeyErrorHandling(async () => {
273
287
  if (!this.walletManager) {
274
288
  throw new TurnkeyError("Wallet manager is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
@@ -330,7 +344,7 @@ class TurnkeyClient {
330
344
  *
331
345
  * @param params.walletAccount - The wallet account whose provider should be switched.
332
346
  * @param params.chainOrId - The target chain, specified as a chain ID string or a SwitchableChain object.
333
- * @param params.walletProviders - Optional list of wallet providers to search; falls back to `getWalletProviders()` if omitted.
347
+ * @param params.walletProviders - Optional list of wallet providers to search; falls back to `fetchWalletProviders()` if omitted.
334
348
  * @returns A promise that resolves once the chain switch is complete.
335
349
  *
336
350
  * @throws {TurnkeyError} If the wallet manager is uninitialized, the provider is not connected, or the switch fails.
@@ -344,7 +358,7 @@ class TurnkeyClient {
344
358
  if (walletAccount.source === WalletSource.Embedded) {
345
359
  throw new TurnkeyError("You can only switch chains for connected wallet accounts", TurnkeyErrorCodes.NOT_FOUND);
346
360
  }
347
- const providers = walletProviders ?? (await this.getWalletProviders());
361
+ const providers = walletProviders ?? (await this.fetchWalletProviders());
348
362
  const walletProvider = findWalletProviderFromAddress(walletAccount.address, providers);
349
363
  if (!walletProvider) {
350
364
  throw new TurnkeyError("Wallet provider not found", TurnkeyErrorCodes.SWITCH_WALLET_CHAIN_ERROR);
@@ -372,7 +386,9 @@ class TurnkeyClient {
372
386
  * @param params.publicKey - optional public key to associate with the session (generated if not provided).
373
387
  * @param params.sessionKey - optional key to store the session under (defaults to the default session key).
374
388
  * @param params.expirationSeconds - optional session expiration time in seconds (defaults to the configured default).
375
- * @returns A promise that resolves to the created session token.
389
+ * @returns A promise that resolves to a {@link WalletAuthResult}, which includes:
390
+ * - `sessionToken`: the signed JWT session token.
391
+ * - `address`: the authenticated wallet address.
376
392
  * @throws {TurnkeyError} If the wallet stamper is uninitialized, a public key cannot be found or generated, or login fails.
377
393
  */
378
394
  this.loginWithWallet = async (params) => {
@@ -397,14 +413,19 @@ class TurnkeyClient {
397
413
  sessionToken: sessionResponse.session,
398
414
  sessionKey,
399
415
  });
400
- return sessionResponse.session;
416
+ // TODO (Moe): What happens if a user connects to MetaMask on Ethereum,
417
+ // then switches to a Solana account within MetaMask? Will this flow break?
418
+ return {
419
+ sessionToken: sessionResponse.session,
420
+ address: addressFromPublicKey(walletProvider.chainInfo.namespace, publicKey),
421
+ };
401
422
  }, {
402
423
  errorMessage: "Unable to log in with the provided wallet",
403
424
  errorCode: TurnkeyErrorCodes.WALLET_LOGIN_AUTH_ERROR,
404
425
  }, {
405
426
  finallyFn: async () => {
406
427
  // Clean up the generated key pair if it wasn't successfully used
407
- this.apiKeyStamper?.clearPublicKeyOverride();
428
+ this.apiKeyStamper?.clearTemporaryPublicKey();
408
429
  if (publicKey) {
409
430
  try {
410
431
  await this.apiKeyStamper?.deleteKeyPair(publicKey);
@@ -429,17 +450,19 @@ class TurnkeyClient {
429
450
  * @param params.createSubOrgParams - parameters for creating a sub-organization (e.g., authenticators, user metadata).
430
451
  * @param params.sessionKey - session key to use for storing the session (defaults to the default session key).
431
452
  * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
432
- * @returns A promise that resolves to a signed JWT session token for the new sub-organization.
453
+ * @returns A promise that resolves to a {@link WalletAuthResult}, which includes:
454
+ * - `sessionToken`: the signed JWT session token.
455
+ * - `address`: the authenticated wallet address.
433
456
  * @throws {TurnkeyError} If there is an error during wallet authentication, sub-organization creation, session storage, or cleanup.
434
457
  */
435
458
  this.signUpWithWallet = async (params) => {
436
459
  const { walletProvider, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params;
437
- let generatedKeyPair = undefined;
460
+ let generatedPublicKey = undefined;
438
461
  return withTurnkeyErrorHandling(async () => {
439
462
  if (!this.walletManager?.stamper) {
440
463
  throw new TurnkeyError("Wallet stamper is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
441
464
  }
442
- generatedKeyPair = await this.apiKeyStamper?.createKeyPair();
465
+ generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
443
466
  this.walletManager.stamper.setProvider(walletProvider.interfaceType, walletProvider);
444
467
  const publicKey = await this.walletManager.stamper.getPublicKey(walletProvider.interfaceType, walletProvider);
445
468
  if (!publicKey) {
@@ -452,13 +475,11 @@ class TurnkeyClient {
452
475
  {
453
476
  apiKeyName: `wallet-auth:${publicKey}`,
454
477
  publicKey: publicKey,
455
- curveType: isEthereumProvider(walletProvider)
456
- ? "API_KEY_CURVE_SECP256K1"
457
- : "API_KEY_CURVE_ED25519",
478
+ curveType: getCurveTypeFromProvider(walletProvider),
458
479
  },
459
480
  {
460
- apiKeyName: `wallet-auth-${generatedKeyPair}`,
461
- publicKey: generatedKeyPair,
481
+ apiKeyName: `wallet-auth-${generatedPublicKey}`,
482
+ publicKey: generatedPublicKey,
462
483
  curveType: "API_KEY_CURVE_P256",
463
484
  expirationSeconds: "60",
464
485
  },
@@ -470,29 +491,34 @@ class TurnkeyClient {
470
491
  throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
471
492
  }
472
493
  const newGeneratedKeyPair = await this.apiKeyStamper?.createKeyPair();
473
- this.apiKeyStamper?.setPublicKeyOverride(generatedKeyPair);
494
+ this.apiKeyStamper?.setTemporaryPublicKey(generatedPublicKey);
474
495
  const sessionResponse = await this.httpClient.stampLogin({
475
496
  publicKey: newGeneratedKeyPair,
476
497
  organizationId: this.config.organizationId,
477
498
  expirationSeconds,
478
499
  });
479
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
500
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
480
501
  await this.storeSession({
481
502
  sessionToken: sessionResponse.session,
482
503
  sessionKey,
483
504
  });
484
- generatedKeyPair = undefined; // Key pair was successfully used, set to null to prevent cleanup
485
- return sessionResponse.session;
505
+ generatedPublicKey = undefined; // Key pair was successfully used, set to null to prevent cleanup
506
+ // TODO (Moe): What happens if a user connects to MetaMask on Ethereum,
507
+ // then switches to a Solana account within MetaMask? Will this flow break?
508
+ return {
509
+ sessionToken: sessionResponse.session,
510
+ address: addressFromPublicKey(walletProvider.chainInfo.namespace, publicKey),
511
+ };
486
512
  }, {
487
513
  errorMessage: "Failed to sign up with wallet",
488
514
  errorCode: TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR,
489
515
  }, {
490
516
  finallyFn: async () => {
491
517
  // Clean up the generated key pair if it wasn't successfully used
492
- this.apiKeyStamper?.clearPublicKeyOverride();
493
- if (generatedKeyPair) {
518
+ this.apiKeyStamper?.clearTemporaryPublicKey();
519
+ if (generatedPublicKey) {
494
520
  try {
495
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
521
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
496
522
  }
497
523
  catch (cleanupError) {
498
524
  throw new TurnkeyError("Failed to clean up generated key pair", TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
@@ -515,7 +541,10 @@ class TurnkeyClient {
515
541
  * @param params.createSubOrgParams - optional parameters for creating a sub-organization (e.g., authenticators, user metadata).
516
542
  * @param params.sessionKey - session key to use for storing the session (defaults to the default session key).
517
543
  * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
518
- * @returns A promise that resolves to a signed JWT session token for the sub-organization (new or existing).
544
+ * @returns A promise that resolves to an object containing:
545
+ * - `sessionToken`: the signed JWT session token.
546
+ * - `address`: the authenticated wallet address.
547
+ * - `action`: whether the flow resulted in a login or signup ({@link AuthAction}).
519
548
  * @throws {TurnkeyError} If there is an error during wallet authentication, sub-organization creation, or session storage.
520
549
  */
521
550
  this.loginOrSignupWithWallet = async (params) => {
@@ -523,26 +552,26 @@ class TurnkeyClient {
523
552
  const sessionKey = params.sessionKey || SessionKey.DefaultSessionkey;
524
553
  const walletProvider = params.walletProvider;
525
554
  const expirationSeconds = params.expirationSeconds || DEFAULT_SESSION_EXPIRATION_IN_SECONDS;
526
- let generatedKeyPair = undefined;
555
+ let generatedPublicKey = undefined;
527
556
  return withTurnkeyErrorHandling(async () => {
528
557
  if (!this.walletManager?.stamper) {
529
558
  throw new TurnkeyError("Wallet stamper is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
530
559
  }
531
- generatedKeyPair = await this.apiKeyStamper?.createKeyPair();
560
+ generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
532
561
  this.walletManager.stamper.setProvider(walletProvider.interfaceType, walletProvider);
533
- // here we sign the request with the wallet, but we don't send it to the Turnkey yet
562
+ // here we sign the request with the wallet, but we don't send it to Turnkey yet
534
563
  // this is because we need to check if the subOrg exists first, and create one if it doesn't
535
- // once we have the subOrg for the publicKey, we then can send the request to the Turnkey
564
+ // once we have the subOrg for the publicKey, we then can send the request to Turnkey
536
565
  const signedRequest = await withTurnkeyErrorHandling(async () => {
537
566
  return this.httpClient.stampStampLogin({
538
- publicKey: generatedKeyPair,
567
+ publicKey: generatedPublicKey,
539
568
  organizationId: this.config.organizationId,
540
569
  expirationSeconds,
541
570
  }, StamperType.Wallet);
542
571
  }, {
543
572
  errorMessage: "Failed to create stamped request for wallet login",
544
573
  errorCode: TurnkeyErrorCodes.WALLET_LOGIN_OR_SIGNUP_ERROR,
545
- customMessageByMessages: {
574
+ customErrorsByMessages: {
546
575
  "Failed to sign the message": {
547
576
  message: "Wallet auth was cancelled by the user.",
548
577
  code: TurnkeyErrorCodes.CONNECT_WALLET_CANCELLED,
@@ -572,7 +601,7 @@ class TurnkeyClient {
572
601
  throw new TurnkeyError(`Unsupported interface type: ${walletProvider.interfaceType}`, TurnkeyErrorCodes.INVALID_REQUEST);
573
602
  }
574
603
  // here we check if the subOrg exists and create one
575
- // then we send off the stamped request to the Turnkey
604
+ // then we send off the stamped request to Turnkey
576
605
  const accountRes = await this.httpClient.proxyGetAccount({
577
606
  filterType: FilterType.PublicKey,
578
607
  filterValue: publicKey,
@@ -590,9 +619,7 @@ class TurnkeyClient {
590
619
  {
591
620
  apiKeyName: `wallet-auth:${publicKey}`,
592
621
  publicKey: publicKey,
593
- curveType: isEthereumProvider(walletProvider)
594
- ? "API_KEY_CURVE_SECP256K1"
595
- : "API_KEY_CURVE_ED25519",
622
+ curveType: getCurveTypeFromProvider(walletProvider),
596
623
  },
597
624
  ],
598
625
  },
@@ -602,7 +629,7 @@ class TurnkeyClient {
602
629
  throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
603
630
  }
604
631
  }
605
- // now we can send the stamped request to the Turnkey
632
+ // now we can send the stamped request to Turnkey
606
633
  const headers = {
607
634
  "Content-Type": "application/json",
608
635
  [signedRequest.stamp.stampHeaderName]: signedRequest.stamp.stampHeaderValue,
@@ -625,14 +652,19 @@ class TurnkeyClient {
625
652
  sessionToken: sessionToken,
626
653
  sessionKey,
627
654
  });
628
- return sessionToken;
655
+ return {
656
+ sessionToken: sessionToken,
657
+ address: addressFromPublicKey(walletProvider.chainInfo.namespace, publicKey),
658
+ // if the subOrganizationId exists, it means the user is logging in
659
+ action: subOrganizationId ? AuthAction.LOGIN : AuthAction.SIGNUP,
660
+ };
629
661
  }, {
630
662
  errorCode: TurnkeyErrorCodes.WALLET_LOGIN_OR_SIGNUP_ERROR,
631
663
  errorMessage: "Failed to log in or sign up with wallet",
632
664
  catchFn: async () => {
633
- if (generatedKeyPair) {
665
+ if (generatedPublicKey) {
634
666
  try {
635
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
667
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
636
668
  }
637
669
  catch (cleanupError) {
638
670
  throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
@@ -663,7 +695,7 @@ class TurnkeyClient {
663
695
  }, {
664
696
  errorMessage: "Failed to initialize OTP",
665
697
  errorCode: TurnkeyErrorCodes.INIT_OTP_ERROR,
666
- customMessageByMessages: {
698
+ customErrorsByMessages: {
667
699
  "Max number of OTPs have been initiated": {
668
700
  message: "Maximum number of OTPs has been reached for this contact.",
669
701
  code: TurnkeyErrorCodes.MAX_OTP_INITIATED_ERROR,
@@ -713,7 +745,7 @@ class TurnkeyClient {
713
745
  }, {
714
746
  errorMessage: "Failed to verify OTP",
715
747
  errorCode: TurnkeyErrorCodes.VERIFY_OTP_ERROR,
716
- customMessageByMessages: {
748
+ customErrorsByMessages: {
717
749
  "Invalid OTP code": {
718
750
  message: "The provided OTP code is invalid.",
719
751
  code: TurnkeyErrorCodes.INVALID_OTP_CODE,
@@ -734,7 +766,8 @@ class TurnkeyClient {
734
766
  * @param params.publicKey - public key to use for authentication. If not provided, a new key pair will be generated.
735
767
  * @param params.invalidateExisting - flag to invalidate existing session for the user.
736
768
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
737
- * @returns A promise that resolves to a signed JWT session token.
769
+ * @returns A promise that resolves to a {@link BaseAuthResult}, which includes:
770
+ * - `sessionToken`: the signed JWT session token.
738
771
  * @throws {TurnkeyError} If there is an error during the OTP login process or if key pair cleanup fails.
739
772
  */
740
773
  this.loginWithOtp = async (params) => {
@@ -756,7 +789,9 @@ class TurnkeyClient {
756
789
  sessionToken: loginRes.session,
757
790
  sessionKey,
758
791
  });
759
- return loginRes.session;
792
+ return {
793
+ sessionToken: loginRes.session,
794
+ };
760
795
  }, {
761
796
  errorMessage: "Failed to log in with OTP",
762
797
  errorCode: TurnkeyErrorCodes.OTP_LOGIN_ERROR,
@@ -788,7 +823,8 @@ class TurnkeyClient {
788
823
  * @param params.createSubOrgParams - parameters for creating a sub-organization (e.g., authenticators, user metadata).
789
824
  * @param params.invalidateExisting - flag to invalidate existing session for the user.
790
825
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
791
- * @returns A promise that resolves to a signed JWT session token for the new sub-organization.
826
+ * @returns A promise that resolves to a {@link BaseAuthResult}, which includes:
827
+ * - `sessionToken`: the signed JWT session token.
792
828
  * @throws {TurnkeyError} If there is an error during the OTP sign-up process or session storage.
793
829
  */
794
830
  this.signUpWithOtp = async (params) => {
@@ -803,14 +839,14 @@ class TurnkeyClient {
803
839
  },
804
840
  });
805
841
  return withTurnkeyErrorHandling(async () => {
806
- const generatedKeyPair = await this.apiKeyStamper?.createKeyPair();
842
+ const generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
807
843
  const res = await this.httpClient.proxySignup(signUpBody);
808
844
  if (!res) {
809
845
  throw new TurnkeyError(`Auth proxy OTP sign up failed`, TurnkeyErrorCodes.OTP_SIGNUP_ERROR);
810
846
  }
811
847
  return await this.loginWithOtp({
812
848
  verificationToken,
813
- publicKey: generatedKeyPair,
849
+ publicKey: generatedPublicKey,
814
850
  ...(invalidateExisting && { invalidateExisting }),
815
851
  ...(sessionKey && { sessionKey }),
816
852
  });
@@ -836,7 +872,10 @@ class TurnkeyClient {
836
872
  * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
837
873
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
838
874
  * @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
839
- * @returns A promise that resolves to a signed JWT session token for the user.
875
+ * @returns A promise that resolves to an object containing:
876
+ * - `sessionToken`: the signed JWT session token.
877
+ * - `verificationToken`: the OTP verification token.
878
+ * - `action`: whether the flow resulted in a login or signup ({@link AuthAction}).
840
879
  * @throws {TurnkeyError} If there is an error during OTP verification, sign-up, or login.
841
880
  */
842
881
  this.completeOtp = async (params) => {
@@ -852,24 +891,32 @@ class TurnkeyClient {
852
891
  throw new TurnkeyError("No verification token returned from OTP verification", TurnkeyErrorCodes.VERIFY_OTP_ERROR);
853
892
  }
854
893
  if (!subOrganizationId) {
855
- return await this.signUpWithOtp({
894
+ const signUpRes = await this.signUpWithOtp({
856
895
  verificationToken,
857
- contact: contact,
858
- otpType: otpType,
859
- ...(createSubOrgParams && {
860
- createSubOrgParams,
861
- }),
896
+ contact,
897
+ otpType,
898
+ ...(createSubOrgParams && { createSubOrgParams }),
862
899
  ...(invalidateExisting && { invalidateExisting }),
863
900
  ...(sessionKey && { sessionKey }),
864
901
  });
902
+ return {
903
+ ...signUpRes,
904
+ verificationToken,
905
+ action: AuthAction.SIGNUP,
906
+ };
865
907
  }
866
908
  else {
867
- return await this.loginWithOtp({
909
+ const loginRes = await this.loginWithOtp({
868
910
  verificationToken,
869
911
  ...(publicKey && { publicKey }),
870
912
  ...(invalidateExisting && { invalidateExisting }),
871
913
  ...(sessionKey && { sessionKey }),
872
914
  });
915
+ return {
916
+ ...loginRes,
917
+ verificationToken,
918
+ action: AuthAction.LOGIN,
919
+ };
873
920
  }
874
921
  }, {
875
922
  errorMessage: "Failed to complete OTP process",
@@ -886,16 +933,18 @@ class TurnkeyClient {
886
933
  * - Handles session storage and management, and supports invalidating existing sessions if specified.
887
934
  *
888
935
  * @param params.oidcToken - OIDC token received after successful authentication with the OAuth provider.
889
- * @param params.publicKey - public key to use for authentication. Must be generated prior to calling this function.
936
+ * @param params.publicKey - public key to use for authentication. Must be generated prior to calling this function, this is because the OIDC nonce has to be set to `sha256(publicKey)`.
890
937
  * @param params.providerName - name of the OAuth provider (defaults to a generated name with a timestamp).
891
938
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
892
939
  * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
893
940
  * @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
894
- * @returns A promise that resolves to a signed JWT session token for the user.
941
+ * @returns A promise that resolves to an object containing:
942
+ * - `sessionToken`: the signed JWT session token.
943
+ * - `action`: whether the flow resulted in a login or signup ({@link AuthAction}).
895
944
  * @throws {TurnkeyError} If there is an error during the OAuth completion process, such as account lookup, sign-up, or login.
896
945
  */
897
946
  this.completeOauth = async (params) => {
898
- const { oidcToken, publicKey, createSubOrgParams, providerName = "OpenID Connect Provider" + Date.now(), sessionKey = SessionKey.DefaultSessionkey, invalidateExisting = false, } = params;
947
+ const { oidcToken, publicKey, createSubOrgParams, providerName = "OpenID Connect Provider" + " " + Date.now(), sessionKey = SessionKey.DefaultSessionkey, invalidateExisting = false, } = params;
899
948
  return withTurnkeyErrorHandling(async () => {
900
949
  const accountRes = await this.httpClient.proxyGetAccount({
901
950
  filterType: "OIDC_TOKEN",
@@ -906,22 +955,31 @@ class TurnkeyClient {
906
955
  }
907
956
  const subOrganizationId = accountRes.organizationId;
908
957
  if (subOrganizationId) {
909
- return this.loginWithOauth({
958
+ const loginRes = await this.loginWithOauth({
910
959
  oidcToken,
911
960
  publicKey,
912
961
  invalidateExisting,
913
962
  sessionKey,
914
963
  });
964
+ return {
965
+ ...loginRes,
966
+ action: AuthAction.LOGIN,
967
+ };
915
968
  }
916
969
  else {
917
- return this.signUpWithOauth({
970
+ const signUpRes = await this.signUpWithOauth({
918
971
  oidcToken,
919
972
  publicKey,
920
973
  providerName,
974
+ sessionKey,
921
975
  ...(createSubOrgParams && {
922
976
  createSubOrgParams,
923
977
  }),
924
978
  });
979
+ return {
980
+ ...signUpRes,
981
+ action: AuthAction.SIGNUP,
982
+ };
925
983
  }
926
984
  }, {
927
985
  errorMessage: "Failed to complete OAuth process",
@@ -940,7 +998,8 @@ class TurnkeyClient {
940
998
  * @param params.publicKey - public key to use for authentication. Must be generated prior to calling this function.
941
999
  * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
942
1000
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
943
- * @returns A promise that resolves to a signed JWT session token.
1001
+ * @returns A promise that resolves to a {@link BaseAuthResult}, which includes:
1002
+ * - `sessionToken`: the signed JWT session token.
944
1003
  * @throws {TurnkeyError} If there is an error during the OAuth login process or if key pair cleanup fails.
945
1004
  */
946
1005
  this.loginWithOauth = async (params) => {
@@ -964,11 +1023,11 @@ class TurnkeyClient {
964
1023
  sessionToken: loginRes.session,
965
1024
  sessionKey,
966
1025
  });
967
- return loginRes.session;
1026
+ return { sessionToken: loginRes.session };
968
1027
  }, {
969
1028
  errorMessage: "Failed to complete OAuth login",
970
1029
  errorCode: TurnkeyErrorCodes.OAUTH_LOGIN_ERROR,
971
- customMessageByMessages: {
1030
+ customErrorsByMessages: {
972
1031
  "OAUTH disallowed": {
973
1032
  message: "OAuth is disabled on the dashboard for this organization.",
974
1033
  code: TurnkeyErrorCodes.AUTH_METHOD_NOT_ENABLED,
@@ -1000,11 +1059,12 @@ class TurnkeyClient {
1000
1059
  * @param params.providerName - name of the OAuth provider (e.g., "Google", "Apple").
1001
1060
  * @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
1002
1061
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
1003
- * @returns A promise that resolves to a signed JWT session token for the new sub-organization.
1062
+ * @returns A promise that resolves to a {@link BaseAuthResult}, which includes:
1063
+ * - `sessionToken`: the signed JWT session token.
1004
1064
  * @throws {TurnkeyError} If there is an error during the OAuth sign-up or login process.
1005
1065
  */
1006
1066
  this.signUpWithOauth = async (params) => {
1007
- const { oidcToken, publicKey, providerName, createSubOrgParams } = params;
1067
+ const { oidcToken, publicKey, providerName, createSubOrgParams, sessionKey, } = params;
1008
1068
  return withTurnkeyErrorHandling(async () => {
1009
1069
  const signUpBody = buildSignUpBody({
1010
1070
  createSubOrgParams: {
@@ -1024,6 +1084,7 @@ class TurnkeyClient {
1024
1084
  return await this.loginWithOauth({
1025
1085
  oidcToken,
1026
1086
  publicKey: publicKey,
1087
+ ...(sessionKey && { sessionKey }),
1027
1088
  });
1028
1089
  }, {
1029
1090
  errorMessage: "Failed to sign up with OAuth",
@@ -1040,6 +1101,7 @@ class TurnkeyClient {
1040
1101
  * - Optionally allows stamping the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
1041
1102
  *
1042
1103
  * @param params.stampWith - parameter to stamp the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
1104
+ * @param params.walletProviders - array of wallet providers to use for fetching wallets.
1043
1105
  * @returns A promise that resolves to an array of `Wallet` objects.
1044
1106
  * @throws {TurnkeyError} If no active session is found or if there is an error fetching wallets.
1045
1107
  */
@@ -1070,9 +1132,11 @@ class TurnkeyClient {
1070
1132
  // if wallet connecting is disabled we return only embedded wallets
1071
1133
  if (!this.walletManager?.connector)
1072
1134
  return embedded;
1073
- const providers = walletProviders ?? (await this.getWalletProviders());
1135
+ const providers = walletProviders ?? (await this.fetchWalletProviders());
1074
1136
  const groupedProviders = new Map();
1075
1137
  for (const provider of providers) {
1138
+ // connected wallets don't all have some uuid we can use for the walletId
1139
+ // so what we do is we use a normalized version of the name for the wallet, like "metamask" or "phantom-wallet"
1076
1140
  const walletId = provider.info?.name?.toLowerCase().replace(/\s+/g, "-") ||
1077
1141
  "unknown";
1078
1142
  const group = groupedProviders.get(walletId) || [];
@@ -1153,10 +1217,21 @@ class TurnkeyClient {
1153
1217
  if (!this.walletManager?.connector)
1154
1218
  return [];
1155
1219
  const connected = [];
1156
- const providers = walletProviders ?? (await this.getWalletProviders());
1220
+ const providers = walletProviders ?? (await this.fetchWalletProviders());
1221
+ // Context: connected wallets don't all have some uuid we can use for the walletId so what
1222
+ // we do is we use a normalized version of the name for the wallet, like "metamask"
1223
+ // or "phantom-wallet"
1224
+ //
1225
+ // when fetching accounts, we select all providers with this normalized walletId.
1226
+ // A single wallet can map to multiple providers if it supports multiple chains
1227
+ // (e.g. MetaMask for Ethereum and MetaMask for Solana)
1157
1228
  const matching = providers.filter((p) => p.info?.name?.toLowerCase().replace(/\s+/g, "-") ===
1158
1229
  wallet.walletId && p.connectedAddresses.length > 0);
1159
1230
  const sign = this.walletManager.connector.sign.bind(this.walletManager.connector);
1231
+ const user = await this.fetchUser({
1232
+ stampWith,
1233
+ });
1234
+ const { ethereum: ethereumAddresses, solana: solanaAddresses } = getAuthenticatorAddresses(user);
1160
1235
  for (const provider of matching) {
1161
1236
  const timestamp = toExternalTimestamp();
1162
1237
  for (const address of provider.connectedAddresses) {
@@ -1175,6 +1250,7 @@ class TurnkeyClient {
1175
1250
  curve: Curve.SECP256K1,
1176
1251
  addressFormat: "ADDRESS_FORMAT_ETHEREUM",
1177
1252
  chainInfo: provider.chainInfo,
1253
+ isAuthenticator: ethereumAddresses.includes(address.toLowerCase()),
1178
1254
  signMessage: (msg) => sign(msg, provider, SignIntent.SignMessage),
1179
1255
  signAndSendTransaction: (tx) => sign(tx, provider, SignIntent.SignAndSendTransaction),
1180
1256
  };
@@ -1197,6 +1273,7 @@ class TurnkeyClient {
1197
1273
  curve: Curve.ED25519,
1198
1274
  addressFormat: "ADDRESS_FORMAT_SOLANA",
1199
1275
  chainInfo: provider.chainInfo,
1276
+ isAuthenticator: solanaAddresses.includes(address),
1200
1277
  signMessage: (msg) => sign(msg, provider, SignIntent.SignMessage),
1201
1278
  signTransaction: (tx) => sign(tx, provider, SignIntent.SignTransaction),
1202
1279
  };
@@ -1212,6 +1289,33 @@ class TurnkeyClient {
1212
1289
  errorCode: TurnkeyErrorCodes.FETCH_WALLET_ACCOUNTS_ERROR,
1213
1290
  });
1214
1291
  };
1292
+ /**
1293
+ * Fetches all private keys for the current user.
1294
+ *
1295
+ * - Retrieves private keys from the Turnkey API.
1296
+ * - Supports stamping the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
1297
+ *
1298
+ * @param params.stampWith - parameter to stamp the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
1299
+ * @returns A promise that resolves to an array of `v1PrivateKey` objects.
1300
+ * @throws {TurnkeyError} If no active session is found or if there is an error fetching private keys.
1301
+ */
1302
+ this.fetchPrivateKeys = async (params) => {
1303
+ const { stampWith } = params || {};
1304
+ const session = await this.storageManager.getActiveSession();
1305
+ if (!session) {
1306
+ throw new TurnkeyError("No active session found. Please log in first.", TurnkeyErrorCodes.NO_SESSION_FOUND);
1307
+ }
1308
+ return withTurnkeyErrorHandling(async () => {
1309
+ const res = await this.httpClient.getPrivateKeys({ organizationId: session.organizationId }, stampWith);
1310
+ if (!res) {
1311
+ throw new TurnkeyError("Failed to fetch private keys", TurnkeyErrorCodes.BAD_RESPONSE);
1312
+ }
1313
+ return res.privateKeys;
1314
+ }, {
1315
+ errorMessage: "Failed to fetch private keys",
1316
+ errorCode: TurnkeyErrorCodes.FETCH_PRIVATE_KEYS_ERROR,
1317
+ });
1318
+ };
1215
1319
  /**
1216
1320
  * Signs a message using the specified wallet account.
1217
1321
  *
@@ -1430,6 +1534,165 @@ class TurnkeyClient {
1430
1534
  errorCode: TurnkeyErrorCodes.FETCH_USER_ERROR,
1431
1535
  });
1432
1536
  };
1537
+ /**
1538
+ * Fetches an existing user by P-256 API key public key, or creates a new one if none exists.
1539
+ *
1540
+ * - This function is idempotent: multiple calls with the same `publicKey` will always return the same user.
1541
+ * - Attempts to find a user whose API keys include the given P-256 public key.
1542
+ * - If a matching user is found, it is returned as-is.
1543
+ * - If no matching user is found, a new user is created with the given public key as a P-256 API key.
1544
+ *
1545
+ * @param params.publicKey - the P-256 public key to use for lookup and creation.
1546
+ * @param params.createParams.userName - optional username to assign if creating a new user (defaults to `"Public Key User"`).
1547
+ * @param params.createParams.apiKeyName - optional API key name to assign if creating a new API key (defaults to `public-key-user-${publicKey}`).
1548
+ * @returns A promise that resolves to the existing or newly created {@link v1User}.
1549
+ * @throws {TurnkeyError} If there is no active session, if the input is invalid, if user retrieval fails, or if user creation fails.
1550
+ */
1551
+ this.fetchOrCreateP256ApiKeyUser = async (params) => {
1552
+ const { publicKey, createParams } = params;
1553
+ return withTurnkeyErrorHandling(async () => {
1554
+ const session = await this.storageManager.getActiveSession();
1555
+ if (!session) {
1556
+ throw new TurnkeyError("No active session found. Please log in first.", TurnkeyErrorCodes.NO_SESSION_FOUND);
1557
+ }
1558
+ const organizationId = session.organizationId;
1559
+ // we validate their input
1560
+ if (!publicKey?.trim()) {
1561
+ throw new TurnkeyError("'publicKey' is required and cannot be empty.", TurnkeyErrorCodes.INVALID_REQUEST);
1562
+ }
1563
+ const usersResponse = await this.httpClient.getUsers({
1564
+ organizationId,
1565
+ });
1566
+ if (!usersResponse || !usersResponse.users) {
1567
+ throw new TurnkeyError("No users found in the response", TurnkeyErrorCodes.BAD_RESPONSE);
1568
+ }
1569
+ const userWithPublicKey = usersResponse.users.find((user) => user.apiKeys.some((apiKey) => apiKey.credential.publicKey === publicKey &&
1570
+ apiKey.credential.type === "CREDENTIAL_TYPE_API_KEY_P256"));
1571
+ // the user already exists, so we return it
1572
+ if (userWithPublicKey) {
1573
+ return userWithPublicKey;
1574
+ }
1575
+ // at this point we know the user doesn't exist, so we create it
1576
+ const userName = createParams?.userName?.trim() || "Public Key User";
1577
+ const apiKeyName = createParams?.apiKeyName?.trim() || `public-key-user-${publicKey}`;
1578
+ const createUserResp = await this.httpClient.createUsers({
1579
+ organizationId,
1580
+ users: [
1581
+ {
1582
+ userName: userName,
1583
+ userTags: [],
1584
+ apiKeys: [
1585
+ {
1586
+ apiKeyName: apiKeyName,
1587
+ curveType: "API_KEY_CURVE_P256",
1588
+ publicKey,
1589
+ },
1590
+ ],
1591
+ authenticators: [],
1592
+ oauthProviders: [],
1593
+ },
1594
+ ],
1595
+ });
1596
+ if (!createUserResp?.userIds ||
1597
+ createUserResp.userIds.length === 0 ||
1598
+ !createUserResp.userIds[0]) {
1599
+ throw new TurnkeyError("Failed to create P-256 API key user", TurnkeyErrorCodes.CREATE_USERS_ERROR);
1600
+ }
1601
+ const newUserId = createUserResp.userIds[0];
1602
+ return await this.fetchUser({
1603
+ organizationId,
1604
+ userId: newUserId,
1605
+ });
1606
+ }, {
1607
+ errorMessage: "Failed to get or create P-256 API key user",
1608
+ errorCode: TurnkeyErrorCodes.CREATE_USERS_ERROR,
1609
+ });
1610
+ };
1611
+ /**
1612
+ * Fetches each requested policy if it exists, or creates it if it does not.
1613
+ *
1614
+ * - This function is idempotent: multiple calls with the same policies will not create duplicates.
1615
+ * - For every policy in the request:
1616
+ * - If it already exists, it is returned with its `policyId`.
1617
+ * - If it does not exist, it is created and returned with its new `policyId`.
1618
+ *
1619
+ * @param params.policies - the list of policies to fetch or create.
1620
+ * @returns A promise that resolves to an array of objects, each containing:
1621
+ * - `policyId`: the unique identifier of the policy.
1622
+ * - `policyName`: human-readable name of the policy.
1623
+ * - `effect`: the instruction to DENY or ALLOW an activity.
1624
+ * - `condition`: (optional) the condition expression that triggers the effect.
1625
+ * - `consensus`: (optional) the consensus expression that triggers the effect.
1626
+ * - `notes`: (optional) developer notes or description for the policy.
1627
+ * @throws {TurnkeyError} If there is no active session, if the input is invalid,
1628
+ * if fetching policies fails, or if creating policies fails.
1629
+ */
1630
+ this.fetchOrCreatePolicies = async (params) => {
1631
+ const { policies } = params;
1632
+ return await withTurnkeyErrorHandling(async () => {
1633
+ const session = await this.storageManager.getActiveSession();
1634
+ if (!session) {
1635
+ throw new TurnkeyError("No active session found. Please log in first.", TurnkeyErrorCodes.NO_SESSION_FOUND);
1636
+ }
1637
+ if (!Array.isArray(policies) || policies.length === 0) {
1638
+ throw new TurnkeyError("'policies' must be a non-empty array of policy definitions.", TurnkeyErrorCodes.INVALID_REQUEST);
1639
+ }
1640
+ const organizationId = session.organizationId;
1641
+ // we first fetch existing policies
1642
+ const existingPoliciesResponse = await this.httpClient.getPolicies({
1643
+ organizationId,
1644
+ });
1645
+ const existingPolicies = existingPoliciesResponse.policies || [];
1646
+ // we create a map of existing policies by their signature
1647
+ // where the policySignature maps to its policyId
1648
+ const existingPoliciesSignatureMap = {};
1649
+ for (const existingPolicy of existingPolicies) {
1650
+ const signature = getPolicySignature(existingPolicy);
1651
+ existingPoliciesSignatureMap[signature] = existingPolicy.policyId;
1652
+ }
1653
+ // we go through each requested policy and check if it already exists
1654
+ // if it exists, we add it to the alreadyExistingPolicies list
1655
+ // if it doesn't exist, we add it to the missingPolicies list
1656
+ const alreadyExistingPolicies = [];
1657
+ const missingPolicies = [];
1658
+ for (const policy of policies) {
1659
+ const existingId = existingPoliciesSignatureMap[getPolicySignature(policy)];
1660
+ if (existingId) {
1661
+ alreadyExistingPolicies.push({ ...policy, policyId: existingId });
1662
+ }
1663
+ else {
1664
+ missingPolicies.push(policy);
1665
+ }
1666
+ }
1667
+ // if there are no missing policies, that means we're done
1668
+ // so we return them with their respective IDs
1669
+ if (missingPolicies.length === 0) {
1670
+ return alreadyExistingPolicies;
1671
+ }
1672
+ // at this point we know there is at least one missing policy.
1673
+ // so we create the missing policies and then return the full list
1674
+ const createPoliciesResponse = await this.httpClient.createPolicies({
1675
+ organizationId,
1676
+ policies: missingPolicies,
1677
+ });
1678
+ // assign returned IDs back to the missing ones in order
1679
+ if (!createPoliciesResponse || !createPoliciesResponse.policyIds) {
1680
+ throw new TurnkeyError("Failed to create missing delegated access policies", TurnkeyErrorCodes.CREATE_POLICY_ERROR);
1681
+ }
1682
+ const newlyCreatedPolicies = missingPolicies.map((p, idx) => ({
1683
+ ...p,
1684
+ // we can safely assert the ID exists because we know Turnkey's api
1685
+ // will return one ID for each created policy or throw an error
1686
+ policyId: createPoliciesResponse.policyIds[idx],
1687
+ }));
1688
+ // we return the full list of policies, both existing and the newly created
1689
+ // which includes each of their respective IDs
1690
+ return [...alreadyExistingPolicies, ...newlyCreatedPolicies];
1691
+ }, {
1692
+ errorMessage: "Failed to get or create delegated access policies",
1693
+ errorCode: TurnkeyErrorCodes.CREATE_USERS_ERROR,
1694
+ });
1695
+ };
1433
1696
  /**
1434
1697
  * Updates the user's email address.
1435
1698
  *
@@ -1744,7 +2007,6 @@ class TurnkeyClient {
1744
2007
  this.addPasskey = async (params) => {
1745
2008
  const { stampWith } = params || {};
1746
2009
  const name = params?.name || `Turnkey Passkey-${Date.now()}`;
1747
- const displayName = params?.displayName || name;
1748
2010
  return withTurnkeyErrorHandling(async () => {
1749
2011
  const session = await this.storageManager.getActiveSession();
1750
2012
  if (!session) {
@@ -1753,7 +2015,6 @@ class TurnkeyClient {
1753
2015
  const userId = params?.userId || session.userId;
1754
2016
  const { encodedChallenge, attestation } = await this.createPasskey({
1755
2017
  name,
1756
- displayName,
1757
2018
  ...(stampWith && { stampWith }),
1758
2019
  });
1759
2020
  if (!attestation || !encodedChallenge) {
@@ -2073,7 +2334,7 @@ class TurnkeyClient {
2073
2334
  }, {
2074
2335
  errorMessage: "Failed to import wallet",
2075
2336
  errorCode: TurnkeyErrorCodes.IMPORT_WALLET_ERROR,
2076
- customMessageByMessages: {
2337
+ customErrorsByMessages: {
2077
2338
  "invalid mnemonic": {
2078
2339
  message: "Invalid mnemonic input",
2079
2340
  code: TurnkeyErrorCodes.BAD_REQUEST,
@@ -2123,7 +2384,7 @@ class TurnkeyClient {
2123
2384
  }, {
2124
2385
  errorMessage: "Failed to import wallet",
2125
2386
  errorCode: TurnkeyErrorCodes.IMPORT_WALLET_ERROR,
2126
- customMessageByMessages: {
2387
+ customErrorsByMessages: {
2127
2388
  "invalid mnemonic": {
2128
2389
  message: "Invalid mnemonic input",
2129
2390
  code: TurnkeyErrorCodes.BAD_REQUEST,
@@ -2227,9 +2488,8 @@ class TurnkeyClient {
2227
2488
  this.clearAllSessions = async () => {
2228
2489
  withTurnkeyErrorHandling(async () => {
2229
2490
  const sessionKeys = await this.storageManager.listSessionKeys();
2230
- if (sessionKeys.length === 0) {
2231
- throw new TurnkeyError("No sessions found to clear.", TurnkeyErrorCodes.NO_SESSION_FOUND);
2232
- }
2491
+ if (sessionKeys.length === 0)
2492
+ return;
2233
2493
  for (const sessionKey of sessionKeys) {
2234
2494
  this.clearSession({ sessionKey });
2235
2495
  }
@@ -2447,7 +2707,7 @@ class TurnkeyClient {
2447
2707
  }
2448
2708
  const publicKey = await this.apiKeyStamper.createKeyPair(externalKeyPair ? externalKeyPair : undefined);
2449
2709
  if (storeOverride && publicKey) {
2450
- await this.apiKeyStamper.setPublicKeyOverride(publicKey);
2710
+ await this.apiKeyStamper.setTemporaryPublicKey(publicKey);
2451
2711
  }
2452
2712
  return publicKey;
2453
2713
  }, {