@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.
- package/dist/__clients__/core.d.ts +88 -35
- package/dist/__clients__/core.d.ts.map +1 -1
- package/dist/__clients__/core.js +271 -140
- package/dist/__clients__/core.js.map +1 -1
- package/dist/__clients__/core.mjs +274 -143
- package/dist/__clients__/core.mjs.map +1 -1
- package/dist/__generated__/sdk-client-base.d.ts +13 -0
- package/dist/__generated__/sdk-client-base.d.ts.map +1 -1
- package/dist/__generated__/sdk-client-base.js +198 -8
- package/dist/__generated__/sdk-client-base.js.map +1 -1
- package/dist/__generated__/sdk-client-base.mjs +198 -8
- package/dist/__generated__/sdk-client-base.mjs.map +1 -1
- package/dist/__generated__/version.d.ts +1 -1
- package/dist/__generated__/version.js +1 -1
- package/dist/__generated__/version.mjs +1 -1
- package/dist/__inputs__/public_api.types.d.ts +629 -494
- package/dist/__inputs__/public_api.types.d.ts.map +1 -1
- package/dist/__polyfills__/jest.setup.webcrypto.d.ts +2 -0
- package/dist/__polyfills__/jest.setup.webcrypto.d.ts.map +1 -0
- package/dist/__stampers__/api/base.d.ts +11 -5
- package/dist/__stampers__/api/base.d.ts.map +1 -1
- package/dist/__stampers__/api/base.js +32 -10
- package/dist/__stampers__/api/base.js.map +1 -1
- package/dist/__stampers__/api/base.mjs +32 -10
- package/dist/__stampers__/api/base.mjs.map +1 -1
- package/dist/__stampers__/api/web/stamper.d.ts.map +1 -1
- package/dist/__stampers__/api/web/stamper.js +2 -4
- package/dist/__stampers__/api/web/stamper.js.map +1 -1
- package/dist/__stampers__/api/web/stamper.mjs +2 -4
- package/dist/__stampers__/api/web/stamper.mjs.map +1 -1
- package/dist/__types__/base.d.ts +195 -75
- package/dist/__types__/base.d.ts.map +1 -1
- package/dist/__types__/base.js +14 -13
- package/dist/__types__/base.js.map +1 -1
- package/dist/__types__/base.mjs +15 -14
- package/dist/__types__/base.mjs.map +1 -1
- package/dist/__wallet__/stamper.d.ts.map +1 -1
- package/dist/__wallet__/stamper.js +8 -7
- package/dist/__wallet__/stamper.js.map +1 -1
- package/dist/__wallet__/stamper.mjs +9 -8
- package/dist/__wallet__/stamper.mjs.map +1 -1
- package/dist/__wallet__/wallet-connect/base.d.ts +3 -0
- package/dist/__wallet__/wallet-connect/base.d.ts.map +1 -1
- package/dist/__wallet__/wallet-connect/base.js +32 -8
- package/dist/__wallet__/wallet-connect/base.js.map +1 -1
- package/dist/__wallet__/wallet-connect/base.mjs +32 -8
- package/dist/__wallet__/wallet-connect/base.mjs.map +1 -1
- package/dist/__wallet__/wallet-connect/client.d.ts +28 -3
- package/dist/__wallet__/wallet-connect/client.d.ts.map +1 -1
- package/dist/__wallet__/wallet-connect/client.js +54 -5
- package/dist/__wallet__/wallet-connect/client.js.map +1 -1
- package/dist/__wallet__/wallet-connect/client.mjs +54 -5
- package/dist/__wallet__/wallet-connect/client.mjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/utils.d.ts +61 -13
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +184 -32
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +179 -33
- package/dist/utils.mjs.map +1 -1
- 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,
|
|
4
|
-
import { withTurnkeyErrorHandling, isWeb, isReactNative, buildSignUpBody,
|
|
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
|
|
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 ||
|
|
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
|
|
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
|
|
134
|
+
let generatedPublicKey = undefined;
|
|
133
135
|
return await withTurnkeyErrorHandling(async () => {
|
|
134
|
-
|
|
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 (!
|
|
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:
|
|
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
|
-
|
|
151
|
-
return
|
|
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 (
|
|
171
|
+
if (generatedPublicKey) {
|
|
164
172
|
try {
|
|
165
|
-
await this.apiKeyStamper?.deleteKeyPair(
|
|
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
|
-
* @
|
|
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
|
|
203
|
+
let generatedPublicKey = undefined;
|
|
193
204
|
return withTurnkeyErrorHandling(async () => {
|
|
194
|
-
|
|
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
|
-
|
|
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-${
|
|
219
|
-
publicKey:
|
|
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?.
|
|
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(
|
|
248
|
+
await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
|
|
238
249
|
await this.storeSession({
|
|
239
250
|
sessionToken: sessionResponse.session,
|
|
240
251
|
sessionKey,
|
|
241
252
|
});
|
|
242
|
-
|
|
243
|
-
return
|
|
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?.
|
|
250
|
-
if (
|
|
263
|
+
this.apiKeyStamper?.clearTemporaryPublicKey();
|
|
264
|
+
if (generatedPublicKey) {
|
|
251
265
|
try {
|
|
252
|
-
await this.apiKeyStamper?.deleteKeyPair(
|
|
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
|
|
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
|
-
* -
|
|
327
|
-
* -
|
|
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
|
|
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.
|
|
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 (
|
|
340
|
-
throw new TurnkeyError("You can
|
|
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
|
|
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
|
-
|
|
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?.
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
446
|
-
? "API_KEY_CURVE_SECP256K1"
|
|
447
|
-
: "API_KEY_CURVE_ED25519",
|
|
478
|
+
curveType: getCurveTypeFromProvider(walletProvider),
|
|
448
479
|
},
|
|
449
480
|
{
|
|
450
|
-
apiKeyName: `wallet-auth-${
|
|
451
|
-
publicKey:
|
|
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?.
|
|
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(
|
|
500
|
+
await this.apiKeyStamper?.deleteKeyPair(generatedPublicKey);
|
|
470
501
|
await this.storeSession({
|
|
471
502
|
sessionToken: sessionResponse.session,
|
|
472
503
|
sessionKey,
|
|
473
504
|
});
|
|
474
|
-
|
|
475
|
-
|
|
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?.
|
|
483
|
-
if (
|
|
518
|
+
this.apiKeyStamper?.clearTemporaryPublicKey();
|
|
519
|
+
if (generatedPublicKey) {
|
|
484
520
|
try {
|
|
485
|
-
await this.apiKeyStamper?.deleteKeyPair(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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 (
|
|
665
|
+
if (generatedPublicKey) {
|
|
624
666
|
try {
|
|
625
|
-
await this.apiKeyStamper?.deleteKeyPair(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
894
|
+
const signUpRes = await this.signUpWithOtp({
|
|
846
895
|
verificationToken,
|
|
847
|
-
contact
|
|
848
|
-
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
:
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
:
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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 (
|
|
1317
|
-
case
|
|
1318
|
-
|
|
1319
|
-
|
|
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
|
|
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
|
-
|
|
1327
|
-
|
|
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
|
-
|
|
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.
|
|
2551
|
+
await this.apiKeyStamper.setTemporaryPublicKey(publicKey);
|
|
2421
2552
|
}
|
|
2422
2553
|
return publicKey;
|
|
2423
2554
|
}, {
|