@turnkey/core 1.0.0-beta.4 → 1.0.0-beta.6

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 (65) hide show
  1. package/dist/__clients__/core.d.ts +88 -35
  2. package/dist/__clients__/core.d.ts.map +1 -1
  3. package/dist/__clients__/core.js +271 -140
  4. package/dist/__clients__/core.js.map +1 -1
  5. package/dist/__clients__/core.mjs +274 -143
  6. package/dist/__clients__/core.mjs.map +1 -1
  7. package/dist/__generated__/sdk-client-base.d.ts +13 -0
  8. package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
  9. package/dist/__generated__/sdk-client-base.js +198 -8
  10. package/dist/__generated__/sdk-client-base.js.map +1 -1
  11. package/dist/__generated__/sdk-client-base.mjs +198 -8
  12. package/dist/__generated__/sdk-client-base.mjs.map +1 -1
  13. package/dist/__generated__/version.d.ts +1 -1
  14. package/dist/__generated__/version.js +1 -1
  15. package/dist/__generated__/version.mjs +1 -1
  16. package/dist/__inputs__/public_api.types.d.ts +629 -494
  17. package/dist/__inputs__/public_api.types.d.ts.map +1 -1
  18. package/dist/__polyfills__/jest.setup.webcrypto.d.ts +2 -0
  19. package/dist/__polyfills__/jest.setup.webcrypto.d.ts.map +1 -0
  20. package/dist/__stampers__/api/base.d.ts +11 -5
  21. package/dist/__stampers__/api/base.d.ts.map +1 -1
  22. package/dist/__stampers__/api/base.js +32 -10
  23. package/dist/__stampers__/api/base.js.map +1 -1
  24. package/dist/__stampers__/api/base.mjs +32 -10
  25. package/dist/__stampers__/api/base.mjs.map +1 -1
  26. package/dist/__stampers__/api/web/stamper.d.ts.map +1 -1
  27. package/dist/__stampers__/api/web/stamper.js +2 -4
  28. package/dist/__stampers__/api/web/stamper.js.map +1 -1
  29. package/dist/__stampers__/api/web/stamper.mjs +2 -4
  30. package/dist/__stampers__/api/web/stamper.mjs.map +1 -1
  31. package/dist/__types__/base.d.ts +195 -75
  32. package/dist/__types__/base.d.ts.map +1 -1
  33. package/dist/__types__/base.js +14 -13
  34. package/dist/__types__/base.js.map +1 -1
  35. package/dist/__types__/base.mjs +15 -14
  36. package/dist/__types__/base.mjs.map +1 -1
  37. package/dist/__wallet__/stamper.d.ts.map +1 -1
  38. package/dist/__wallet__/stamper.js +8 -7
  39. package/dist/__wallet__/stamper.js.map +1 -1
  40. package/dist/__wallet__/stamper.mjs +9 -8
  41. package/dist/__wallet__/stamper.mjs.map +1 -1
  42. package/dist/__wallet__/wallet-connect/base.d.ts +3 -0
  43. package/dist/__wallet__/wallet-connect/base.d.ts.map +1 -1
  44. package/dist/__wallet__/wallet-connect/base.js +32 -8
  45. package/dist/__wallet__/wallet-connect/base.js.map +1 -1
  46. package/dist/__wallet__/wallet-connect/base.mjs +32 -8
  47. package/dist/__wallet__/wallet-connect/base.mjs.map +1 -1
  48. package/dist/__wallet__/wallet-connect/client.d.ts +28 -3
  49. package/dist/__wallet__/wallet-connect/client.d.ts.map +1 -1
  50. package/dist/__wallet__/wallet-connect/client.js +54 -5
  51. package/dist/__wallet__/wallet-connect/client.js.map +1 -1
  52. package/dist/__wallet__/wallet-connect/client.mjs +54 -5
  53. package/dist/__wallet__/wallet-connect/client.mjs.map +1 -1
  54. package/dist/index.d.ts +2 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +2 -10
  57. package/dist/index.js.map +1 -1
  58. package/dist/index.mjs +2 -2
  59. package/dist/utils.d.ts +61 -13
  60. package/dist/utils.d.ts.map +1 -1
  61. package/dist/utils.js +184 -32
  62. package/dist/utils.js.map +1 -1
  63. package/dist/utils.mjs +179 -33
  64. package/dist/utils.mjs.map +1 -1
  65. package/package.json +10 -9
@@ -1,7 +1,7 @@
1
1
  import { TurnkeySDKClientBase } from '../__generated__/sdk-client-base.mjs';
2
- import { TurnkeyErrorCodes, TurnkeyError, TurnkeyNetworkError } from '@turnkey/sdk-types';
3
- import { SessionKey, DEFAULT_SESSION_EXPIRATION_IN_SECONDS, StamperType, Chain, FilterType, OtpTypeToFilterTypeMap, OtpType, WalletSource, Curve } from '../__types__/base.mjs';
4
- import { withTurnkeyErrorHandling, isWeb, isReactNative, buildSignUpBody, isEthereumWallet, getPublicKeyFromStampHeader, toExternalTimestamp, isSolanaWallet, getWalletAccountMethods, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, googleISS, isWalletAccountArray, generateWalletAccountsFromAddressFormat } from '../utils.mjs';
2
+ import { TurnkeyErrorCodes, TurnkeyError, TurnkeyNetworkError, AuthAction } from '@turnkey/sdk-types';
3
+ import { SessionKey, DEFAULT_SESSION_EXPIRATION_IN_SECONDS, StamperType, WalletSource, Chain, FilterType, OtpTypeToFilterTypeMap, OtpType, Curve, SignIntent } from '../__types__/base.mjs';
4
+ import { withTurnkeyErrorHandling, isValidPasskeyName, isWeb, isReactNative, buildSignUpBody, findWalletProviderFromAddress, addressFromPublicKey, getCurveTypeFromProvider, getPublicKeyFromStampHeader, toExternalTimestamp, getAuthenticatorAddresses, isEthereumProvider, isSolanaProvider, getHashFunction, getEncodingType, getEncodedMessage, splitSignature, broadcastTransaction, 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);
@@ -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,8 +149,14 @@ 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,
@@ -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);
@@ -320,24 +334,34 @@ class TurnkeyClient {
320
334
  });
321
335
  };
322
336
  /**
323
- * Switches the specified wallet provider to a different blockchain chain.
337
+ * Switches the wallet provider associated with a given wallet account
338
+ * to a different chain.
324
339
  *
325
- * - Requires the wallet manager and its connector to be initialized.
326
- * - The wallet provider must have at least one connected address.
327
- * - Does nothing if the wallet provider is already on the desired chain.
340
+ * - Requires the wallet manager and its connector to be initialized
341
+ * - Only works for connected wallet accounts
342
+ * - Looks up the provider for the given account address
343
+ * - Does nothing if the provider is already on the desired chain.
328
344
  *
329
- * @param walletProvider - wallet provider to switch.
330
- * @param chainOrId - target chain as a chain ID string or SwitchableChain object.
345
+ * @param params.walletAccount - The wallet account whose provider should be switched.
346
+ * @param params.chainOrId - The target chain, specified as a chain ID string or a SwitchableChain object.
347
+ * @param params.walletProviders - Optional list of wallet providers to search; falls back to `getWalletProviders()` if omitted.
331
348
  * @returns A promise that resolves once the chain switch is complete.
349
+ *
332
350
  * @throws {TurnkeyError} If the wallet manager is uninitialized, the provider is not connected, or the switch fails.
333
351
  */
334
- this.switchWalletProviderChain = async (walletProvider, chainOrId) => {
352
+ this.switchWalletAccountChain = async (params) => {
353
+ const { walletAccount, chainOrId, walletProviders } = params;
335
354
  return withTurnkeyErrorHandling(async () => {
336
355
  if (!this.walletManager?.connector) {
337
356
  throw new TurnkeyError("Wallet connector is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
338
357
  }
339
- if (walletProvider.connectedAddresses.length === 0) {
340
- throw new TurnkeyError("You can not switch chains for a provider that is not connected", TurnkeyErrorCodes.INVALID_REQUEST);
358
+ if (walletAccount.source === WalletSource.Embedded) {
359
+ throw new TurnkeyError("You can only switch chains for connected wallet accounts", TurnkeyErrorCodes.NOT_FOUND);
360
+ }
361
+ const providers = walletProviders ?? (await this.getWalletProviders());
362
+ const walletProvider = findWalletProviderFromAddress(walletAccount.address, providers);
363
+ if (!walletProvider) {
364
+ throw new TurnkeyError("Wallet provider not found", TurnkeyErrorCodes.SWITCH_WALLET_CHAIN_ERROR);
341
365
  }
342
366
  // if the wallet provider is already on the desired chain, do nothing
343
367
  if (walletProvider.chainInfo.namespace === chainOrId) {
@@ -362,7 +386,9 @@ class TurnkeyClient {
362
386
  * @param params.publicKey - optional public key to associate with the session (generated if not provided).
363
387
  * @param params.sessionKey - optional key to store the session under (defaults to the default session key).
364
388
  * @param params.expirationSeconds - optional session expiration time in seconds (defaults to the configured default).
365
- * @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.
366
392
  * @throws {TurnkeyError} If the wallet stamper is uninitialized, a public key cannot be found or generated, or login fails.
367
393
  */
368
394
  this.loginWithWallet = async (params) => {
@@ -387,14 +413,19 @@ class TurnkeyClient {
387
413
  sessionToken: sessionResponse.session,
388
414
  sessionKey,
389
415
  });
390
- 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
+ };
391
422
  }, {
392
423
  errorMessage: "Unable to log in with the provided wallet",
393
424
  errorCode: TurnkeyErrorCodes.WALLET_LOGIN_AUTH_ERROR,
394
425
  }, {
395
426
  finallyFn: async () => {
396
427
  // Clean up the generated key pair if it wasn't successfully used
397
- this.apiKeyStamper?.clearPublicKeyOverride();
428
+ this.apiKeyStamper?.clearTemporaryPublicKey();
398
429
  if (publicKey) {
399
430
  try {
400
431
  await this.apiKeyStamper?.deleteKeyPair(publicKey);
@@ -419,17 +450,19 @@ class TurnkeyClient {
419
450
  * @param params.createSubOrgParams - parameters for creating a sub-organization (e.g., authenticators, user metadata).
420
451
  * @param params.sessionKey - session key to use for storing the session (defaults to the default session key).
421
452
  * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
422
- * @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.
423
456
  * @throws {TurnkeyError} If there is an error during wallet authentication, sub-organization creation, session storage, or cleanup.
424
457
  */
425
458
  this.signUpWithWallet = async (params) => {
426
459
  const { walletProvider, createSubOrgParams, sessionKey = SessionKey.DefaultSessionkey, expirationSeconds = DEFAULT_SESSION_EXPIRATION_IN_SECONDS, } = params;
427
- let generatedKeyPair = undefined;
460
+ let generatedPublicKey = undefined;
428
461
  return withTurnkeyErrorHandling(async () => {
429
462
  if (!this.walletManager?.stamper) {
430
463
  throw new TurnkeyError("Wallet stamper is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
431
464
  }
432
- generatedKeyPair = await this.apiKeyStamper?.createKeyPair();
465
+ generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
433
466
  this.walletManager.stamper.setProvider(walletProvider.interfaceType, walletProvider);
434
467
  const publicKey = await this.walletManager.stamper.getPublicKey(walletProvider.interfaceType, walletProvider);
435
468
  if (!publicKey) {
@@ -442,13 +475,11 @@ class TurnkeyClient {
442
475
  {
443
476
  apiKeyName: `wallet-auth:${publicKey}`,
444
477
  publicKey: publicKey,
445
- curveType: isEthereumWallet(walletProvider)
446
- ? "API_KEY_CURVE_SECP256K1"
447
- : "API_KEY_CURVE_ED25519",
478
+ curveType: getCurveTypeFromProvider(walletProvider),
448
479
  },
449
480
  {
450
- apiKeyName: `wallet-auth-${generatedKeyPair}`,
451
- publicKey: generatedKeyPair,
481
+ apiKeyName: `wallet-auth-${generatedPublicKey}`,
482
+ publicKey: generatedPublicKey,
452
483
  curveType: "API_KEY_CURVE_P256",
453
484
  expirationSeconds: "60",
454
485
  },
@@ -460,29 +491,34 @@ class TurnkeyClient {
460
491
  throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
461
492
  }
462
493
  const newGeneratedKeyPair = await this.apiKeyStamper?.createKeyPair();
463
- this.apiKeyStamper?.setPublicKeyOverride(generatedKeyPair);
494
+ this.apiKeyStamper?.setTemporaryPublicKey(generatedPublicKey);
464
495
  const sessionResponse = await this.httpClient.stampLogin({
465
496
  publicKey: newGeneratedKeyPair,
466
497
  organizationId: this.config.organizationId,
467
498
  expirationSeconds,
468
499
  });
469
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
500
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
470
501
  await this.storeSession({
471
502
  sessionToken: sessionResponse.session,
472
503
  sessionKey,
473
504
  });
474
- generatedKeyPair = undefined; // Key pair was successfully used, set to null to prevent cleanup
475
- 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
+ };
476
512
  }, {
477
513
  errorMessage: "Failed to sign up with wallet",
478
514
  errorCode: TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR,
479
515
  }, {
480
516
  finallyFn: async () => {
481
517
  // Clean up the generated key pair if it wasn't successfully used
482
- this.apiKeyStamper?.clearPublicKeyOverride();
483
- if (generatedKeyPair) {
518
+ this.apiKeyStamper?.clearTemporaryPublicKey();
519
+ if (generatedPublicKey) {
484
520
  try {
485
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
521
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
486
522
  }
487
523
  catch (cleanupError) {
488
524
  throw new TurnkeyError("Failed to clean up generated key pair", TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
@@ -505,7 +541,10 @@ class TurnkeyClient {
505
541
  * @param params.createSubOrgParams - optional parameters for creating a sub-organization (e.g., authenticators, user metadata).
506
542
  * @param params.sessionKey - session key to use for storing the session (defaults to the default session key).
507
543
  * @param params.expirationSeconds - session expiration time in seconds (defaults to the configured default).
508
- * @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}).
509
548
  * @throws {TurnkeyError} If there is an error during wallet authentication, sub-organization creation, or session storage.
510
549
  */
511
550
  this.loginOrSignupWithWallet = async (params) => {
@@ -513,19 +552,19 @@ class TurnkeyClient {
513
552
  const sessionKey = params.sessionKey || SessionKey.DefaultSessionkey;
514
553
  const walletProvider = params.walletProvider;
515
554
  const expirationSeconds = params.expirationSeconds || DEFAULT_SESSION_EXPIRATION_IN_SECONDS;
516
- let generatedKeyPair = undefined;
555
+ let generatedPublicKey = undefined;
517
556
  return withTurnkeyErrorHandling(async () => {
518
557
  if (!this.walletManager?.stamper) {
519
558
  throw new TurnkeyError("Wallet stamper is not initialized", TurnkeyErrorCodes.WALLET_MANAGER_COMPONENT_NOT_INITIALIZED);
520
559
  }
521
- generatedKeyPair = await this.apiKeyStamper?.createKeyPair();
560
+ generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
522
561
  this.walletManager.stamper.setProvider(walletProvider.interfaceType, walletProvider);
523
- // 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
524
563
  // this is because we need to check if the subOrg exists first, and create one if it doesn't
525
- // 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
526
565
  const signedRequest = await withTurnkeyErrorHandling(async () => {
527
566
  return this.httpClient.stampStampLogin({
528
- publicKey: generatedKeyPair,
567
+ publicKey: generatedPublicKey,
529
568
  organizationId: this.config.organizationId,
530
569
  expirationSeconds,
531
570
  }, StamperType.Wallet);
@@ -562,7 +601,7 @@ class TurnkeyClient {
562
601
  throw new TurnkeyError(`Unsupported interface type: ${walletProvider.interfaceType}`, TurnkeyErrorCodes.INVALID_REQUEST);
563
602
  }
564
603
  // here we check if the subOrg exists and create one
565
- // then we send off the stamped request to the Turnkey
604
+ // then we send off the stamped request to Turnkey
566
605
  const accountRes = await this.httpClient.proxyGetAccount({
567
606
  filterType: FilterType.PublicKey,
568
607
  filterValue: publicKey,
@@ -580,9 +619,7 @@ class TurnkeyClient {
580
619
  {
581
620
  apiKeyName: `wallet-auth:${publicKey}`,
582
621
  publicKey: publicKey,
583
- curveType: isEthereumWallet(walletProvider)
584
- ? "API_KEY_CURVE_SECP256K1"
585
- : "API_KEY_CURVE_ED25519",
622
+ curveType: getCurveTypeFromProvider(walletProvider),
586
623
  },
587
624
  ],
588
625
  },
@@ -592,7 +629,7 @@ class TurnkeyClient {
592
629
  throw new TurnkeyError(`Sign up failed`, TurnkeyErrorCodes.WALLET_SIGNUP_AUTH_ERROR);
593
630
  }
594
631
  }
595
- // now we can send the stamped request to the Turnkey
632
+ // now we can send the stamped request to Turnkey
596
633
  const headers = {
597
634
  "Content-Type": "application/json",
598
635
  [signedRequest.stamp.stampHeaderName]: signedRequest.stamp.stampHeaderValue,
@@ -615,14 +652,19 @@ class TurnkeyClient {
615
652
  sessionToken: sessionToken,
616
653
  sessionKey,
617
654
  });
618
- 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
+ };
619
661
  }, {
620
662
  errorCode: TurnkeyErrorCodes.WALLET_LOGIN_OR_SIGNUP_ERROR,
621
663
  errorMessage: "Failed to log in or sign up with wallet",
622
664
  catchFn: async () => {
623
- if (generatedKeyPair) {
665
+ if (generatedPublicKey) {
624
666
  try {
625
- await this.apiKeyStamper?.deleteKeyPair(generatedKeyPair);
667
+ await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
626
668
  }
627
669
  catch (cleanupError) {
628
670
  throw new TurnkeyError(`Failed to clean up generated key pair`, TurnkeyErrorCodes.KEY_PAIR_CLEANUP_ERROR, cleanupError);
@@ -724,7 +766,8 @@ class TurnkeyClient {
724
766
  * @param params.publicKey - public key to use for authentication. If not provided, a new key pair will be generated.
725
767
  * @param params.invalidateExisting - flag to invalidate existing session for the user.
726
768
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
727
- * @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.
728
771
  * @throws {TurnkeyError} If there is an error during the OTP login process or if key pair cleanup fails.
729
772
  */
730
773
  this.loginWithOtp = async (params) => {
@@ -746,7 +789,9 @@ class TurnkeyClient {
746
789
  sessionToken: loginRes.session,
747
790
  sessionKey,
748
791
  });
749
- return loginRes.session;
792
+ return {
793
+ sessionToken: loginRes.session,
794
+ };
750
795
  }, {
751
796
  errorMessage: "Failed to log in with OTP",
752
797
  errorCode: TurnkeyErrorCodes.OTP_LOGIN_ERROR,
@@ -778,7 +823,8 @@ class TurnkeyClient {
778
823
  * @param params.createSubOrgParams - parameters for creating a sub-organization (e.g., authenticators, user metadata).
779
824
  * @param params.invalidateExisting - flag to invalidate existing session for the user.
780
825
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
781
- * @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.
782
828
  * @throws {TurnkeyError} If there is an error during the OTP sign-up process or session storage.
783
829
  */
784
830
  this.signUpWithOtp = async (params) => {
@@ -793,14 +839,14 @@ class TurnkeyClient {
793
839
  },
794
840
  });
795
841
  return withTurnkeyErrorHandling(async () => {
796
- const generatedKeyPair = await this.apiKeyStamper?.createKeyPair();
842
+ const generatedPublicKey = await this.apiKeyStamper?.createKeyPair();
797
843
  const res = await this.httpClient.proxySignup(signUpBody);
798
844
  if (!res) {
799
845
  throw new TurnkeyError(`Auth proxy OTP sign up failed`, TurnkeyErrorCodes.OTP_SIGNUP_ERROR);
800
846
  }
801
847
  return await this.loginWithOtp({
802
848
  verificationToken,
803
- publicKey: generatedKeyPair,
849
+ publicKey: generatedPublicKey,
804
850
  ...(invalidateExisting && { invalidateExisting }),
805
851
  ...(sessionKey && { sessionKey }),
806
852
  });
@@ -826,7 +872,10 @@ class TurnkeyClient {
826
872
  * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
827
873
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
828
874
  * @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
829
- * @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}).
830
879
  * @throws {TurnkeyError} If there is an error during OTP verification, sign-up, or login.
831
880
  */
832
881
  this.completeOtp = async (params) => {
@@ -842,24 +891,32 @@ class TurnkeyClient {
842
891
  throw new TurnkeyError("No verification token returned from OTP verification", TurnkeyErrorCodes.VERIFY_OTP_ERROR);
843
892
  }
844
893
  if (!subOrganizationId) {
845
- return await this.signUpWithOtp({
894
+ const signUpRes = await this.signUpWithOtp({
846
895
  verificationToken,
847
- contact: contact,
848
- otpType: otpType,
849
- ...(createSubOrgParams && {
850
- createSubOrgParams,
851
- }),
896
+ contact,
897
+ otpType,
898
+ ...(createSubOrgParams && { createSubOrgParams }),
852
899
  ...(invalidateExisting && { invalidateExisting }),
853
900
  ...(sessionKey && { sessionKey }),
854
901
  });
902
+ return {
903
+ ...signUpRes,
904
+ verificationToken,
905
+ action: AuthAction.SIGNUP,
906
+ };
855
907
  }
856
908
  else {
857
- return await this.loginWithOtp({
909
+ const loginRes = await this.loginWithOtp({
858
910
  verificationToken,
859
911
  ...(publicKey && { publicKey }),
860
912
  ...(invalidateExisting && { invalidateExisting }),
861
913
  ...(sessionKey && { sessionKey }),
862
914
  });
915
+ return {
916
+ ...loginRes,
917
+ verificationToken,
918
+ action: AuthAction.LOGIN,
919
+ };
863
920
  }
864
921
  }, {
865
922
  errorMessage: "Failed to complete OTP process",
@@ -876,12 +933,14 @@ class TurnkeyClient {
876
933
  * - Handles session storage and management, and supports invalidating existing sessions if specified.
877
934
  *
878
935
  * @param params.oidcToken - OIDC token received after successful authentication with the OAuth provider.
879
- * @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)`.
880
937
  * @param params.providerName - name of the OAuth provider (defaults to a generated name with a timestamp).
881
938
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
882
939
  * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
883
940
  * @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
884
- * @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}).
885
944
  * @throws {TurnkeyError} If there is an error during the OAuth completion process, such as account lookup, sign-up, or login.
886
945
  */
887
946
  this.completeOauth = async (params) => {
@@ -896,22 +955,31 @@ class TurnkeyClient {
896
955
  }
897
956
  const subOrganizationId = accountRes.organizationId;
898
957
  if (subOrganizationId) {
899
- return this.loginWithOauth({
958
+ const loginRes = await this.loginWithOauth({
900
959
  oidcToken,
901
960
  publicKey,
902
961
  invalidateExisting,
903
962
  sessionKey,
904
963
  });
964
+ return {
965
+ ...loginRes,
966
+ action: AuthAction.LOGIN,
967
+ };
905
968
  }
906
969
  else {
907
- return this.signUpWithOauth({
970
+ const signUpRes = await this.signUpWithOauth({
908
971
  oidcToken,
909
972
  publicKey,
910
973
  providerName,
974
+ sessionKey,
911
975
  ...(createSubOrgParams && {
912
976
  createSubOrgParams,
913
977
  }),
914
978
  });
979
+ return {
980
+ ...signUpRes,
981
+ action: AuthAction.SIGNUP,
982
+ };
915
983
  }
916
984
  }, {
917
985
  errorMessage: "Failed to complete OAuth process",
@@ -930,7 +998,8 @@ class TurnkeyClient {
930
998
  * @param params.publicKey - public key to use for authentication. Must be generated prior to calling this function.
931
999
  * @param params.invalidateExisting - flag to invalidate existing sessions for the user.
932
1000
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
933
- * @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.
934
1003
  * @throws {TurnkeyError} If there is an error during the OAuth login process or if key pair cleanup fails.
935
1004
  */
936
1005
  this.loginWithOauth = async (params) => {
@@ -954,7 +1023,7 @@ class TurnkeyClient {
954
1023
  sessionToken: loginRes.session,
955
1024
  sessionKey,
956
1025
  });
957
- return loginRes.session;
1026
+ return { sessionToken: loginRes.session };
958
1027
  }, {
959
1028
  errorMessage: "Failed to complete OAuth login",
960
1029
  errorCode: TurnkeyErrorCodes.OAUTH_LOGIN_ERROR,
@@ -990,11 +1059,12 @@ class TurnkeyClient {
990
1059
  * @param params.providerName - name of the OAuth provider (e.g., "Google", "Apple").
991
1060
  * @param params.createSubOrgParams - parameters for sub-organization creation (e.g., authenticators, user metadata).
992
1061
  * @param params.sessionKey - session key to use for session creation (defaults to the default session key).
993
- * @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.
994
1064
  * @throws {TurnkeyError} If there is an error during the OAuth sign-up or login process.
995
1065
  */
996
1066
  this.signUpWithOauth = async (params) => {
997
- const { oidcToken, publicKey, providerName, createSubOrgParams } = params;
1067
+ const { oidcToken, publicKey, providerName, createSubOrgParams, sessionKey, } = params;
998
1068
  return withTurnkeyErrorHandling(async () => {
999
1069
  const signUpBody = buildSignUpBody({
1000
1070
  createSubOrgParams: {
@@ -1014,6 +1084,7 @@ class TurnkeyClient {
1014
1084
  return await this.loginWithOauth({
1015
1085
  oidcToken,
1016
1086
  publicKey: publicKey,
1087
+ ...(sessionKey && { sessionKey }),
1017
1088
  });
1018
1089
  }, {
1019
1090
  errorMessage: "Failed to sign up with OAuth",
@@ -1030,11 +1101,12 @@ class TurnkeyClient {
1030
1101
  * - Optionally allows stamping the request with a specific stamper (StamperType.Passkey, StamperType.ApiKey, or StamperType.Wallet).
1031
1102
  *
1032
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.
1033
1105
  * @returns A promise that resolves to an array of `Wallet` objects.
1034
1106
  * @throws {TurnkeyError} If no active session is found or if there is an error fetching wallets.
1035
1107
  */
1036
1108
  this.fetchWallets = async (params) => {
1037
- const { stampWith } = params || {};
1109
+ const { stampWith, walletProviders } = params || {};
1038
1110
  const session = await this.storageManager.getActiveSession();
1039
1111
  if (!session) {
1040
1112
  throw new TurnkeyError("No active session found. Please log in first.", TurnkeyErrorCodes.NO_SESSION_FOUND);
@@ -1060,9 +1132,11 @@ class TurnkeyClient {
1060
1132
  // if wallet connecting is disabled we return only embedded wallets
1061
1133
  if (!this.walletManager?.connector)
1062
1134
  return embedded;
1063
- const providers = await this.getWalletProviders();
1135
+ const providers = walletProviders ?? (await this.getWalletProviders());
1064
1136
  const groupedProviders = new Map();
1065
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"
1066
1140
  const walletId = provider.info?.name?.toLowerCase().replace(/\s+/g, "-") ||
1067
1141
  "unknown";
1068
1142
  const group = groupedProviders.get(walletId) || [];
@@ -1144,31 +1218,69 @@ class TurnkeyClient {
1144
1218
  return [];
1145
1219
  const connected = [];
1146
1220
  const providers = walletProviders ?? (await this.getWalletProviders());
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)
1147
1228
  const matching = providers.filter((p) => p.info?.name?.toLowerCase().replace(/\s+/g, "-") ===
1148
1229
  wallet.walletId && p.connectedAddresses.length > 0);
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);
1149
1235
  for (const provider of matching) {
1150
1236
  const timestamp = toExternalTimestamp();
1151
1237
  for (const address of provider.connectedAddresses) {
1152
- const account = {
1153
- walletAccountId: `${wallet.walletId}-${provider.interfaceType}-${address}`,
1154
- organizationId: session.organizationId,
1155
- walletId: wallet.walletId,
1156
- curve: isEthereumWallet(provider)
1157
- ? Curve.SECP256K1
1158
- : Curve.ED25519,
1159
- pathFormat: "PATH_FORMAT_BIP32",
1160
- path: WalletSource.Connected,
1161
- source: WalletSource.Connected,
1162
- addressFormat: isEthereumWallet(provider)
1163
- ? "ADDRESS_FORMAT_ETHEREUM"
1164
- : "ADDRESS_FORMAT_SOLANA",
1165
- address,
1166
- createdAt: timestamp,
1167
- updatedAt: timestamp,
1168
- ...getWalletAccountMethods(this.walletManager.connector.sign.bind(this.walletManager.connector), provider),
1169
- ...(isSolanaWallet(provider) && { publicKey: address }),
1170
- };
1171
- connected.push(account);
1238
+ if (isEthereumProvider(provider)) {
1239
+ const evmAccount = {
1240
+ walletAccountId: `${wallet.walletId}-${provider.interfaceType}-${address}`,
1241
+ organizationId: session.organizationId,
1242
+ walletId: wallet.walletId,
1243
+ pathFormat: "PATH_FORMAT_BIP32",
1244
+ path: WalletSource.Connected,
1245
+ source: WalletSource.Connected,
1246
+ address,
1247
+ createdAt: timestamp,
1248
+ updatedAt: timestamp,
1249
+ // ethereum specific
1250
+ curve: Curve.SECP256K1,
1251
+ addressFormat: "ADDRESS_FORMAT_ETHEREUM",
1252
+ chainInfo: provider.chainInfo,
1253
+ isAuthenticator: ethereumAddresses.includes(address.toLowerCase()),
1254
+ signMessage: (msg) => sign(msg, provider, SignIntent.SignMessage),
1255
+ signAndSendTransaction: (tx) => sign(tx, provider, SignIntent.SignAndSendTransaction),
1256
+ };
1257
+ connected.push(evmAccount);
1258
+ continue;
1259
+ }
1260
+ if (isSolanaProvider(provider)) {
1261
+ const solAccount = {
1262
+ walletAccountId: `${wallet.walletId}-${provider.interfaceType}-${address}`,
1263
+ organizationId: session.organizationId,
1264
+ walletId: wallet.walletId,
1265
+ pathFormat: "PATH_FORMAT_BIP32",
1266
+ path: WalletSource.Connected,
1267
+ source: WalletSource.Connected,
1268
+ address,
1269
+ createdAt: timestamp,
1270
+ updatedAt: timestamp,
1271
+ // solana specific
1272
+ publicKey: address,
1273
+ curve: Curve.ED25519,
1274
+ addressFormat: "ADDRESS_FORMAT_SOLANA",
1275
+ chainInfo: provider.chainInfo,
1276
+ isAuthenticator: solanaAddresses.includes(address),
1277
+ signMessage: (msg) => sign(msg, provider, SignIntent.SignMessage),
1278
+ signTransaction: (tx) => sign(tx, provider, SignIntent.SignTransaction),
1279
+ };
1280
+ connected.push(solAccount);
1281
+ continue;
1282
+ }
1283
+ throw new Error(`Unsupported wallet chain: ${provider.chainInfo}. Supported chains are Ethereum and Solana.`);
1172
1284
  }
1173
1285
  }
1174
1286
  return connected;
@@ -1177,6 +1289,33 @@ class TurnkeyClient {
1177
1289
  errorCode: TurnkeyErrorCodes.FETCH_WALLET_ACCOUNTS_ERROR,
1178
1290
  });
1179
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
+ };
1180
1319
  /**
1181
1320
  * Signs a message using the specified wallet account.
1182
1321
  *
@@ -1266,15 +1405,16 @@ class TurnkeyClient {
1266
1405
  const { walletAccount, unsignedTransaction, transactionType, stampWith } = params;
1267
1406
  return withTurnkeyErrorHandling(async () => {
1268
1407
  if (walletAccount.source === WalletSource.Connected) {
1269
- // this is a connected wallet account
1270
- if (!walletAccount.signTransaction) {
1271
- const isEthereum = walletAccount.addressFormat === "ADDRESS_FORMAT_ETHEREUM";
1272
- const reason = isEthereum
1273
- ? "Ethereum connected wallets do not support raw transaction signing due to EIP-1193 limitations."
1274
- : "This connected wallet does not support raw transaction signing.";
1275
- throw new TurnkeyError(`Failed to sign transaction: ${reason} ${isEthereum ? "Use signAndSendTransaction instead." : ""}`, TurnkeyErrorCodes.SIGN_TRANSACTION_ERROR);
1408
+ switch (walletAccount.chainInfo.namespace) {
1409
+ case Chain.Ethereum:
1410
+ throw new TurnkeyError("Ethereum connected wallets do not support raw transaction signing. Use signAndSendTransaction instead.", TurnkeyErrorCodes.INVALID_REQUEST);
1411
+ case Chain.Solana:
1412
+ // not sure why typescript isn't inferring the type here
1413
+ // if namespace is Chain.Solana, then it must be a ConnectedSolanaWalletAccount
1414
+ return walletAccount.signTransaction(unsignedTransaction);
1415
+ default:
1416
+ throw new TurnkeyError("Unsupported connected wallet type.", TurnkeyErrorCodes.INVALID_REQUEST);
1276
1417
  }
1277
- return await walletAccount?.signTransaction(unsignedTransaction);
1278
1418
  }
1279
1419
  // this is an embedded wallet account
1280
1420
  const signTransaction = await this.httpClient.signTransaction({
@@ -1313,19 +1453,17 @@ class TurnkeyClient {
1313
1453
  return withTurnkeyErrorHandling(async () => {
1314
1454
  if (walletAccount.source === WalletSource.Connected) {
1315
1455
  // this is a connected wallet account
1316
- switch (transactionType) {
1317
- case "TRANSACTION_TYPE_ETHEREUM":
1318
- if (!walletAccount.signAndSendTransaction) {
1319
- throw new TurnkeyError("This connected wallet does not support signAndSendTransaction.", TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR);
1320
- }
1456
+ switch (walletAccount.chainInfo.namespace) {
1457
+ case Chain.Ethereum:
1458
+ // not sure why typescript isn't inferring the type here
1459
+ // if namespace is Chain.Ethereum, then it must be a ConnectedEthereumWalletAccount
1321
1460
  return await walletAccount.signAndSendTransaction(unsignedTransaction);
1322
- case "TRANSACTION_TYPE_SOLANA":
1461
+ case Chain.Solana:
1323
1462
  if (!rpcUrl) {
1324
1463
  throw new TurnkeyError("Missing rpcUrl: connected Solana wallets require an RPC URL to broadcast transactions.", TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR);
1325
1464
  }
1326
- if (!walletAccount.signTransaction) {
1327
- throw new TurnkeyError("This connected wallet does not support signAndSendTransaction.", TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR);
1328
- }
1465
+ // not sure why typescript isn't inferring the type here
1466
+ // if namespace is Chain.Solana, then it must be a ConnectedSolanaWalletAccount
1329
1467
  const signature = await walletAccount.signTransaction(unsignedTransaction);
1330
1468
  return await broadcastTransaction({
1331
1469
  signedTransaction: signature,
@@ -1710,7 +1848,6 @@ class TurnkeyClient {
1710
1848
  this.addPasskey = async (params) => {
1711
1849
  const { stampWith } = params || {};
1712
1850
  const name = params?.name || `Turnkey Passkey-${Date.now()}`;
1713
- const displayName = params?.displayName || name;
1714
1851
  return withTurnkeyErrorHandling(async () => {
1715
1852
  const session = await this.storageManager.getActiveSession();
1716
1853
  if (!session) {
@@ -1719,7 +1856,6 @@ class TurnkeyClient {
1719
1856
  const userId = params?.userId || session.userId;
1720
1857
  const { encodedChallenge, attestation } = await this.createPasskey({
1721
1858
  name,
1722
- displayName,
1723
1859
  ...(stampWith && { stampWith }),
1724
1860
  });
1725
1861
  if (!attestation || !encodedChallenge) {
@@ -2143,11 +2279,7 @@ class TurnkeyClient {
2143
2279
  if (!sessionToken)
2144
2280
  return;
2145
2281
  withTurnkeyErrorHandling(async () => {
2146
- const sessionToReplace = await this.storageManager.getSession(sessionKey);
2147
2282
  await this.storageManager.storeSession(sessionToken, sessionKey);
2148
- if (sessionToReplace) {
2149
- await this.apiKeyStamper?.deleteKeyPair(sessionToReplace.publicKey);
2150
- }
2151
2283
  }, {
2152
2284
  errorMessage: "Failed to store session",
2153
2285
  errorCode: TurnkeyErrorCodes.STORE_SESSION_ERROR,
@@ -2197,9 +2329,8 @@ class TurnkeyClient {
2197
2329
  this.clearAllSessions = async () => {
2198
2330
  withTurnkeyErrorHandling(async () => {
2199
2331
  const sessionKeys = await this.storageManager.listSessionKeys();
2200
- if (sessionKeys.length === 0) {
2201
- throw new TurnkeyError("No sessions found to clear.", TurnkeyErrorCodes.NO_SESSION_FOUND);
2202
- }
2332
+ if (sessionKeys.length === 0)
2333
+ return;
2203
2334
  for (const sessionKey of sessionKeys) {
2204
2335
  this.clearSession({ sessionKey });
2205
2336
  }
@@ -2417,7 +2548,7 @@ class TurnkeyClient {
2417
2548
  }
2418
2549
  const publicKey = await this.apiKeyStamper.createKeyPair(externalKeyPair ? externalKeyPair : undefined);
2419
2550
  if (storeOverride && publicKey) {
2420
- await this.apiKeyStamper.setPublicKeyOverride(publicKey);
2551
+ await this.apiKeyStamper.setTemporaryPublicKey(publicKey);
2421
2552
  }
2422
2553
  return publicKey;
2423
2554
  }, {