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