@account-kit/signer 4.31.2 → 4.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/esm/base.d.ts +6 -2
  2. package/dist/esm/base.js +131 -33
  3. package/dist/esm/base.js.map +1 -1
  4. package/dist/esm/client/base.d.ts +132 -9
  5. package/dist/esm/client/base.js +34 -4
  6. package/dist/esm/client/base.js.map +1 -1
  7. package/dist/esm/client/index.d.ts +36 -14
  8. package/dist/esm/client/index.js +36 -18
  9. package/dist/esm/client/index.js.map +1 -1
  10. package/dist/esm/client/types.d.ts +19 -0
  11. package/dist/esm/client/types.js.map +1 -1
  12. package/dist/esm/signer.d.ts +88 -33
  13. package/dist/esm/signer.js +28 -3
  14. package/dist/esm/signer.js.map +1 -1
  15. package/dist/esm/solanaSigner.d.ts +3 -3
  16. package/dist/esm/solanaSigner.js +1 -1
  17. package/dist/esm/solanaSigner.js.map +1 -1
  18. package/dist/esm/types.d.ts +1 -0
  19. package/dist/esm/types.js.map +1 -1
  20. package/dist/esm/version.d.ts +1 -1
  21. package/dist/esm/version.js +1 -1
  22. package/dist/esm/version.js.map +1 -1
  23. package/dist/types/base.d.ts +6 -2
  24. package/dist/types/base.d.ts.map +1 -1
  25. package/dist/types/client/base.d.ts +132 -9
  26. package/dist/types/client/base.d.ts.map +1 -1
  27. package/dist/types/client/index.d.ts +36 -14
  28. package/dist/types/client/index.d.ts.map +1 -1
  29. package/dist/types/client/types.d.ts +19 -0
  30. package/dist/types/client/types.d.ts.map +1 -1
  31. package/dist/types/signer.d.ts +88 -33
  32. package/dist/types/signer.d.ts.map +1 -1
  33. package/dist/types/solanaSigner.d.ts +3 -3
  34. package/dist/types/solanaSigner.d.ts.map +1 -1
  35. package/dist/types/types.d.ts +1 -0
  36. package/dist/types/types.d.ts.map +1 -1
  37. package/dist/types/version.d.ts +1 -1
  38. package/package.json +5 -6
  39. package/src/base.ts +172 -56
  40. package/src/client/base.ts +36 -7
  41. package/src/client/index.ts +41 -18
  42. package/src/client/types.ts +21 -0
  43. package/src/signer.ts +36 -3
  44. package/src/solanaSigner.ts +4 -4
  45. package/src/types.ts +1 -0
  46. package/src/version.ts +1 -1
package/src/base.ts CHANGED
@@ -21,17 +21,18 @@ import type { Mutate, StoreApi } from "zustand";
21
21
  import { subscribeWithSelector } from "zustand/middleware";
22
22
  import { createStore } from "zustand/vanilla";
23
23
  import type { BaseSignerClient } from "./client/base";
24
- import type {
25
- EmailType,
26
- MfaFactor,
27
- OauthConfig,
28
- OauthParams,
29
- User,
30
- VerifyMfaParams,
31
- AddMfaParams,
32
- AddMfaResult,
33
- RemoveMfaParams,
34
- } from "./client/types";
24
+ import {
25
+ type EmailType,
26
+ type MfaFactor,
27
+ type OauthConfig,
28
+ type OauthParams,
29
+ type User,
30
+ type VerifyMfaParams,
31
+ type AddMfaParams,
32
+ type AddMfaResult,
33
+ type RemoveMfaParams,
34
+ type AuthLinkingPrompt,
35
+ } from "./client/types.js";
35
36
  import { NotAuthenticatedError } from "./errors.js";
36
37
  import { SignerLogger } from "./metrics.js";
37
38
  import {
@@ -54,6 +55,7 @@ export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
54
55
  client: TClient;
55
56
  sessionConfig?: Omit<SessionManagerParams, "client">;
56
57
  initialError?: ErrorInfo;
58
+ initialAuthLinkingPrompt?: AuthLinkingPrompt;
57
59
  }
58
60
 
59
61
  type AlchemySignerStore = {
@@ -67,6 +69,11 @@ type AlchemySignerStore = {
67
69
  mfaFactorId?: string;
68
70
  encryptedPayload?: string;
69
71
  };
72
+ authLinkingStatus?: {
73
+ email: string;
74
+ providerName: string;
75
+ idToken: string;
76
+ };
70
77
  };
71
78
 
72
79
  type UnpackedSignature = {
@@ -115,6 +122,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
115
122
  client,
116
123
  sessionConfig,
117
124
  initialError,
125
+ initialAuthLinkingPrompt,
118
126
  }: BaseAlchemySignerParams<TClient>) {
119
127
  this.inner = client;
120
128
  this.store = createStore(
@@ -143,6 +151,9 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
143
151
  // then initialize so that we can catch those events
144
152
  this.sessionManager.initialize();
145
153
  this.config = this.fetchConfig();
154
+ if (initialAuthLinkingPrompt) {
155
+ this.setAuthLinkingPrompt(initialAuthLinkingPrompt);
156
+ }
146
157
  }
147
158
 
148
159
  /**
@@ -161,52 +172,64 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
161
172
  // is fired. In the Client and SessionManager we use EventEmitter because it's easier to handle internally
162
173
  switch (event) {
163
174
  case "connected":
164
- return this.store.subscribe(
175
+ return subscribeWithDelayedFireImmediately(
176
+ this.store,
165
177
  ({ status }) => status,
166
178
  (status) =>
167
179
  status === AlchemySignerStatus.CONNECTED &&
168
180
  (listener as AlchemySignerEvents["connected"])(
169
181
  this.store.getState().user!
170
- ),
171
- { fireImmediately: true }
182
+ )
172
183
  );
173
184
  case "disconnected":
174
- return this.store.subscribe(
185
+ return subscribeWithDelayedFireImmediately(
186
+ this.store,
175
187
  ({ status }) => status,
176
188
  (status) =>
177
189
  status === AlchemySignerStatus.DISCONNECTED &&
178
- (listener as AlchemySignerEvents["disconnected"])(),
179
- { fireImmediately: true }
190
+ (listener as AlchemySignerEvents["disconnected"])()
180
191
  );
181
192
  case "statusChanged":
182
- return this.store.subscribe(
193
+ return subscribeWithDelayedFireImmediately(
194
+ this.store,
183
195
  ({ status }) => status,
184
- listener as AlchemySignerEvents["statusChanged"],
185
- { fireImmediately: true }
196
+ listener as AlchemySignerEvents["statusChanged"]
186
197
  );
187
198
  case "errorChanged":
188
- return this.store.subscribe(
199
+ return subscribeWithDelayedFireImmediately(
200
+ this.store,
189
201
  ({ error }) => error,
190
202
  (error) =>
191
203
  (listener as AlchemySignerEvents["errorChanged"])(
192
204
  error ?? undefined
193
- ),
194
- { fireImmediately: true }
205
+ )
195
206
  );
196
207
  case "newUserSignup":
197
- return this.store.subscribe(
208
+ return subscribeWithDelayedFireImmediately(
209
+ this.store,
198
210
  ({ isNewUser }) => isNewUser,
199
211
  (isNewUser) => {
200
212
  if (isNewUser) (listener as AlchemySignerEvents["newUserSignup"])();
201
- },
202
- { fireImmediately: true }
213
+ }
203
214
  );
204
215
  case "mfaStatusChanged":
205
- return this.store.subscribe(
216
+ return subscribeWithDelayedFireImmediately(
217
+ this.store,
206
218
  ({ mfaStatus }) => mfaStatus,
207
219
  (mfaStatus) =>
208
- (listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus),
209
- { fireImmediately: true }
220
+ (listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus)
221
+ );
222
+ case "emailAuthLinkingRequired":
223
+ return subscribeWithDelayedFireImmediately(
224
+ this.store,
225
+ ({ authLinkingStatus }) => authLinkingStatus,
226
+ (authLinkingStatus) => {
227
+ if (authLinkingStatus) {
228
+ (listener as AlchemySignerEvents["emailAuthLinkingRequired"])(
229
+ authLinkingStatus.email
230
+ );
231
+ }
232
+ }
210
233
  );
211
234
  default:
212
235
  assertNever(event, `Unknown event type ${event}`);
@@ -855,25 +878,19 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
855
878
  params.redirectParams
856
879
  );
857
880
 
858
- this.sessionManager.setTemporarySession({
859
- orgId,
860
- isNewUser,
861
- });
881
+ this.setAwaitingEmailAuth({ orgId, otpId, isNewUser });
862
882
 
863
- this.store.setState({
864
- status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
865
- otpId,
866
- error: null,
867
- });
883
+ // Clear the auth linking status if the email has changed. This would mean
884
+ // that the previously initiated social login is not associated with the
885
+ // email which is now being used to login.
886
+ const { authLinkingStatus } = this.store.getState();
887
+ if (authLinkingStatus && authLinkingStatus.email !== params.email) {
888
+ this.store.setState({ authLinkingStatus: undefined });
889
+ }
868
890
 
869
891
  // We wait for the session manager to emit a connected event if
870
892
  // cross tab sessions are permitted
871
- return new Promise<User>((resolve) => {
872
- const removeListener = this.sessionManager.on("connected", (session) => {
873
- resolve(session.user);
874
- removeListener();
875
- });
876
- });
893
+ return this.waitForConnected();
877
894
  };
878
895
 
879
896
  private authenticateWithPasskey = async (
@@ -919,15 +936,20 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
919
936
  private authenticateWithOauth = async (
920
937
  args: Extract<AuthParams, { type: "oauth" }>
921
938
  ): Promise<User> => {
939
+ this.store.setState({ authLinkingStatus: undefined });
922
940
  const params: OauthParams = {
923
941
  ...args,
924
942
  expirationSeconds: this.getExpirationSeconds(),
925
943
  };
926
944
  if (params.mode === "redirect") {
927
945
  return this.inner.oauthWithRedirect(params);
928
- } else {
929
- return this.inner.oauthWithPopup(params);
930
946
  }
947
+ const result = await this.inner.oauthWithPopup(params);
948
+ if (!isAuthLinkingPrompt(result)) {
949
+ return result;
950
+ }
951
+ this.setAuthLinkingPrompt(result);
952
+ return this.waitForConnected();
931
953
  };
932
954
 
933
955
  private authenticateWithOtp = async (
@@ -953,16 +975,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
953
975
 
954
976
  if (response.mfaRequired) {
955
977
  this.handleMfaRequired(response.encryptedPayload, response.multiFactors);
956
-
957
- return new Promise<User>((resolve) => {
958
- const removeListener = this.sessionManager.on(
959
- "connected",
960
- (session) => {
961
- resolve(session.user);
962
- removeListener();
963
- }
964
- );
965
- });
978
+ return this.waitForConnected();
966
979
  }
967
980
 
968
981
  const user = await this.inner.completeAuthWithBundle({
@@ -980,9 +993,39 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
980
993
  });
981
994
  }
982
995
 
996
+ const { authLinkingStatus } = this.store.getState();
997
+ if (authLinkingStatus) {
998
+ (async () => {
999
+ this.inner.addOauthProvider({
1000
+ providerName: authLinkingStatus.providerName,
1001
+ oidcToken: authLinkingStatus.idToken,
1002
+ });
1003
+ })();
1004
+ }
1005
+
983
1006
  return user;
984
1007
  };
985
1008
 
1009
+ private setAwaitingEmailAuth = ({
1010
+ orgId,
1011
+ otpId,
1012
+ isNewUser,
1013
+ }: {
1014
+ orgId: string;
1015
+ otpId?: string;
1016
+ isNewUser?: boolean;
1017
+ }): void => {
1018
+ this.sessionManager.setTemporarySession({
1019
+ orgId,
1020
+ isNewUser,
1021
+ });
1022
+ this.store.setState({
1023
+ status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
1024
+ otpId,
1025
+ error: null,
1026
+ });
1027
+ };
1028
+
986
1029
  private handleOauthReturn = ({
987
1030
  bundle,
988
1031
  orgId,
@@ -1407,6 +1450,30 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
1407
1450
  protected fetchConfig = async (): Promise<SignerConfig> => {
1408
1451
  return this.inner.request("/v1/signer-config", {});
1409
1452
  };
1453
+
1454
+ private setAuthLinkingPrompt = (prompt: AuthLinkingPrompt) => {
1455
+ this.setAwaitingEmailAuth({
1456
+ orgId: prompt.orgId,
1457
+ otpId: prompt.otpId,
1458
+ isNewUser: false,
1459
+ });
1460
+ this.store.setState({
1461
+ authLinkingStatus: {
1462
+ email: prompt.email,
1463
+ providerName: prompt.providerName,
1464
+ idToken: prompt.idToken,
1465
+ },
1466
+ });
1467
+ };
1468
+
1469
+ private waitForConnected = (): Promise<User> => {
1470
+ return new Promise<User>((resolve) => {
1471
+ const removeListener = this.sessionManager.on("connected", (session) => {
1472
+ resolve(session.user);
1473
+ removeListener();
1474
+ });
1475
+ });
1476
+ };
1410
1477
  }
1411
1478
 
1412
1479
  function toErrorInfo(error: unknown): ErrorInfo {
@@ -1414,3 +1481,52 @@ function toErrorInfo(error: unknown): ErrorInfo {
1414
1481
  ? { name: error.name, message: error.message }
1415
1482
  : { name: "Error", message: "Unknown error" };
1416
1483
  }
1484
+
1485
+ // eslint-disable-next-line jsdoc/require-param, jsdoc/require-returns
1486
+ /**
1487
+ * Zustand's `fireImmediately` option calls the listener before
1488
+ * `store.subscribe` has returned, which breaks listeners which call
1489
+ * unsubscribe, e.g.
1490
+ *
1491
+ * ```ts
1492
+ * const unsubscribe = store.subscribe(
1493
+ * selector,
1494
+ * (update) => {
1495
+ * handleUpdate(update);
1496
+ * unsubscribe();
1497
+ * },
1498
+ * { fireImmediately: true },
1499
+ * )
1500
+ * ```
1501
+ *
1502
+ * since `unsubscribe` is still undefined at the time the listener is called. To
1503
+ * prevent this, if the listener triggers before `subscribe` has returned, delay
1504
+ * the callback to a later run of the event loop.
1505
+ */
1506
+ function subscribeWithDelayedFireImmediately<T>(
1507
+ store: InternalStore,
1508
+ selector: (state: AlchemySignerStore) => T,
1509
+ listener: (selectedState: T, previousSelectedState: T) => void
1510
+ ): () => void {
1511
+ let subscribeHasReturned = false;
1512
+ const unsubscribe = store.subscribe(
1513
+ selector,
1514
+ (...args) => {
1515
+ if (subscribeHasReturned) {
1516
+ listener(...args);
1517
+ } else {
1518
+ setTimeout(() => listener(...args), 0);
1519
+ }
1520
+ },
1521
+ { fireImmediately: true }
1522
+ );
1523
+ subscribeHasReturned = true;
1524
+ return unsubscribe;
1525
+ }
1526
+
1527
+ function isAuthLinkingPrompt(result: unknown): result is AuthLinkingPrompt {
1528
+ return (
1529
+ (result as AuthLinkingPrompt)?.status ===
1530
+ "ACCOUNT_LINKING_CONFIRMATION_REQUIRED"
1531
+ );
1532
+ }
@@ -34,6 +34,8 @@ import type {
34
34
  VerifyMfaParams,
35
35
  SubmitOtpCodeResponse,
36
36
  ValidateMultiFactorsParams,
37
+ AuthLinkingPrompt,
38
+ AddOauthProviderParams,
37
39
  } from "./types.js";
38
40
  import { VERSION } from "../version.js";
39
41
 
@@ -99,13 +101,13 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
99
101
  }
100
102
 
101
103
  protected set user(user: User | undefined) {
102
- if (user && !this._user) {
104
+ const previousUser = this._user;
105
+ this._user = user;
106
+ if (user && !previousUser) {
103
107
  this.eventEmitter.emit("connected", user);
104
- } else if (!user && this._user) {
108
+ } else if (!user && previousUser) {
105
109
  this.eventEmitter.emit("disconnected");
106
110
  }
107
-
108
- this._user = user;
109
111
  }
110
112
 
111
113
  /**
@@ -160,11 +162,11 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
160
162
 
161
163
  public abstract oauthWithRedirect(
162
164
  args: Extract<OauthParams, { mode: "redirect" }>
163
- ): Promise<User | never>;
165
+ ): Promise<User>;
164
166
 
165
167
  public abstract oauthWithPopup(
166
168
  args: Extract<OauthParams, { mode: "popup" }>
167
- ): Promise<User>;
169
+ ): Promise<User | AuthLinkingPrompt>;
168
170
 
169
171
  public abstract submitOtpCode(
170
172
  args: Omit<OtpParams, "targetPublicKey">
@@ -266,6 +268,32 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
266
268
  };
267
269
  };
268
270
 
271
+ /**
272
+ * Adds an OAuth provider for the authenticated user using the provided parameters. Throws an error if the user is not authenticated.
273
+ *
274
+ * @param {AddOauthProviderParams} params The parameters for adding an OAuth provider, including `providerName` and `oidcToken`.
275
+ * @throws {NotAuthenticatedError} Throws if the user is not authenticated.
276
+ * @returns {Promise<void>} A Promise that resolves when the OAuth provider is added.
277
+ */
278
+ public addOauthProvider = async (
279
+ params: AddOauthProviderParams
280
+ ): Promise<void> => {
281
+ if (!this.user) {
282
+ throw new NotAuthenticatedError();
283
+ }
284
+ const { providerName, oidcToken } = params;
285
+ const stampedRequest = await this.turnkeyClient.stampCreateOauthProviders({
286
+ type: "ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS",
287
+ timestampMs: Date.now().toString(),
288
+ organizationId: this.user.orgId,
289
+ parameters: {
290
+ userId: this.user.userId,
291
+ oauthProviders: [{ providerName, oidcToken }],
292
+ },
293
+ });
294
+ await this.request("/v1/add-oauth-provider", { stampedRequest });
295
+ };
296
+
269
297
  /**
270
298
  * Retrieves the current user or fetches the user information if not already available.
271
299
  *
@@ -374,7 +402,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
374
402
  throw new Error("User must be authenticated to create api key");
375
403
  }
376
404
  const resp = await this.turnkeyClient.createApiKeys({
377
- type: "ACTIVITY_TYPE_CREATE_API_KEYS",
405
+ type: "ACTIVITY_TYPE_CREATE_API_KEYS_V2",
378
406
  timestampMs: new Date().getTime().toString(),
379
407
  organizationId: this.user.orgId,
380
408
  parameters: {
@@ -382,6 +410,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
382
410
  {
383
411
  apiKeyName: params.name,
384
412
  publicKey: params.publicKey,
413
+ curveType: "API_KEY_CURVE_P256",
385
414
  expirationSeconds: params.expirationSec.toString(),
386
415
  },
387
416
  ],
@@ -18,6 +18,7 @@ import type {
18
18
  OtpParams,
19
19
  User,
20
20
  SubmitOtpCodeResponse,
21
+ AuthLinkingPrompt,
21
22
  } from "./types.js";
22
23
  import { MfaRequiredError } from "../errors.js";
23
24
  import { parseMfaError } from "../utils/parseMfaError.js";
@@ -531,7 +532,7 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
531
532
  */
532
533
  public override oauthWithPopup = async (
533
534
  args: Extract<AuthParams, { type: "oauth"; mode: "popup" }>
534
- ): Promise<User> => {
535
+ ): Promise<User | AuthLinkingPrompt> => {
535
536
  const turnkeyPublicKey = await this.initIframeStamper();
536
537
  const oauthParams = args;
537
538
  const providerUrl = await this.getOauthProviderUrl({
@@ -551,33 +552,55 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
551
552
  return;
552
553
  }
553
554
  const {
555
+ alchemyStatus: status,
554
556
  alchemyBundle: bundle,
555
557
  alchemyOrgId: orgId,
556
558
  alchemyIdToken: idToken,
557
559
  alchemyIsSignup: isSignup,
558
560
  alchemyError,
561
+ alchemyOtpId: otpId,
562
+ alchemyEmail: email,
563
+ alchemyAuthProvider: providerName,
559
564
  } = event.data;
560
- if (bundle && orgId && idToken) {
561
- cleanup();
562
- popup?.close();
563
- this.completeAuthWithBundle({
564
- bundle,
565
- orgId,
566
- connectedEventName: "connectedOauth",
567
- idToken,
568
- authenticatingType: "oauth",
569
- }).then((user) => {
570
- if (isSignup) {
571
- eventEmitter.emit("newUserSignup");
572
- }
573
-
574
- resolve(user);
575
- }, reject);
576
- } else if (alchemyError) {
565
+ if (alchemyError) {
577
566
  cleanup();
578
567
  popup?.close();
579
568
  reject(new OauthFailedError(alchemyError));
580
569
  }
570
+ if (!status) {
571
+ // This message isn't meant for us.
572
+ return;
573
+ }
574
+ cleanup();
575
+ popup?.close();
576
+ switch (status) {
577
+ case "SUCCESS":
578
+ this.completeAuthWithBundle({
579
+ bundle,
580
+ orgId,
581
+ connectedEventName: "connectedOauth",
582
+ idToken,
583
+ authenticatingType: "oauth",
584
+ }).then((user) => {
585
+ if (isSignup) {
586
+ eventEmitter.emit("newUserSignup");
587
+ }
588
+ resolve(user);
589
+ }, reject);
590
+ break;
591
+ case "ACCOUNT_LINKING_CONFIRMATION_REQUIRED":
592
+ resolve({
593
+ status,
594
+ idToken,
595
+ email,
596
+ providerName,
597
+ otpId,
598
+ orgId,
599
+ } satisfies AuthLinkingPrompt);
600
+ break;
601
+ default:
602
+ reject(new Error(`Unknown status: ${status}`));
603
+ }
581
604
  };
582
605
 
583
606
  window.addEventListener("message", handleMessage);
@@ -171,6 +171,13 @@ export type SignerEndpoints = [
171
171
  signature: Hex;
172
172
  };
173
173
  },
174
+ {
175
+ Route: "/v1/add-oauth-provider";
176
+ Body: {
177
+ stampedRequest: TSignedRequest;
178
+ };
179
+ Response: void;
180
+ },
174
181
  {
175
182
  Route: "/v1/prepare-oauth";
176
183
  Body: {
@@ -262,6 +269,15 @@ export type GetWebAuthnAttestationResult = {
262
269
  authenticatorUserId: ArrayBuffer;
263
270
  };
264
271
 
272
+ export type AuthLinkingPrompt = {
273
+ status: "ACCOUNT_LINKING_CONFIRMATION_REQUIRED";
274
+ idToken: string;
275
+ email: string;
276
+ providerName: string;
277
+ otpId: string;
278
+ orgId: string;
279
+ };
280
+
265
281
  export type OauthState = {
266
282
  authProviderId: string;
267
283
  isCustomProvider?: boolean;
@@ -324,6 +340,11 @@ export type SubmitOtpCodeResponse =
324
340
  | { bundle: string; mfaRequired: false }
325
341
  | { mfaRequired: true; encryptedPayload: string; multiFactors: MfaFactor[] };
326
342
 
343
+ export type AddOauthProviderParams = {
344
+ providerName: string;
345
+ oidcToken: string;
346
+ };
347
+
327
348
  export type experimental_CreateApiKeyParams = {
328
349
  name: string;
329
350
  publicKey: string;
package/src/signer.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  AlchemySignerWebClient,
6
6
  } from "./client/index.js";
7
7
  import type {
8
+ AuthLinkingPrompt,
8
9
  CredentialCreationOptionOverrides,
9
10
  VerifyMfaParams,
10
11
  } from "./client/types.js";
@@ -141,20 +142,28 @@ export class AlchemyWebSigner extends BaseAlchemySigner<AlchemySignerWebClient>
141
142
  emailBundle: "bundle",
142
143
  // We don't need this, but we still want to remove it from the URL.
143
144
  emailOrgId: "orgId",
145
+ status: "alchemy-status",
144
146
  oauthBundle: "alchemy-bundle",
145
147
  oauthOrgId: "alchemy-org-id",
146
- oauthError: "alchemy-error",
147
148
  idToken: "alchemy-id-token",
148
149
  isSignup: "aa-is-signup",
150
+ otpId: "alchemy-otp-id",
151
+ email: "alchemy-email",
152
+ authProvider: "alchemy-auth-provider",
153
+ oauthError: "alchemy-error",
149
154
  };
150
155
 
151
156
  const {
152
157
  emailBundle,
158
+ status,
153
159
  oauthBundle,
154
160
  oauthOrgId,
155
- oauthError,
156
161
  idToken,
157
162
  isSignup,
163
+ otpId,
164
+ email,
165
+ authProvider,
166
+ oauthError,
158
167
  } = getAndRemoveQueryParams(qpStructure);
159
168
 
160
169
  if (!AlchemyWebSigner.replaceStateFilterInstalled) {
@@ -167,7 +176,31 @@ export class AlchemyWebSigner extends BaseAlchemySigner<AlchemySignerWebClient>
167
176
  ? { name: "OauthError", message: oauthError }
168
177
  : undefined;
169
178
 
170
- super({ client, sessionConfig, initialError });
179
+ const initialAuthLinkingPrompt: AuthLinkingPrompt | undefined = (() => {
180
+ if (status !== "ACCOUNT_LINKING_CONFIRMATION_REQUIRED") {
181
+ return undefined;
182
+ }
183
+ if (
184
+ idToken == null ||
185
+ email == null ||
186
+ authProvider == null ||
187
+ otpId == null ||
188
+ oauthOrgId == null
189
+ ) {
190
+ console.error("Missing required query params for auth linking prompt");
191
+ return undefined;
192
+ }
193
+ return {
194
+ status,
195
+ idToken,
196
+ email,
197
+ providerName: authProvider,
198
+ otpId,
199
+ orgId: oauthOrgId,
200
+ };
201
+ })();
202
+
203
+ super({ client, sessionConfig, initialError, initialAuthLinkingPrompt });
171
204
 
172
205
  const isNewUser = isSignup === "true";
173
206
 
@@ -88,17 +88,17 @@ export class SolanaSigner {
88
88
  return toBytes(this.formatSignatureForSolana(signature));
89
89
  }
90
90
 
91
- async createTransfer(
91
+ async createTransaction(
92
92
  instructions: TransactionInstruction[],
93
93
  connection: Connection,
94
94
  version?: "versioned"
95
95
  ): Promise<VersionedTransaction>;
96
- async createTransfer(
96
+ async createTransaction(
97
97
  instructions: TransactionInstruction[],
98
98
  connection: Connection,
99
99
  version?: "legacy"
100
100
  ): Promise<Transaction>;
101
- async createTransfer(
101
+ async createTransaction(
102
102
  instructions: TransactionInstruction[],
103
103
  connection: Connection
104
104
  ): Promise<VersionedTransaction>;
@@ -111,7 +111,7 @@ export class SolanaSigner {
111
111
  * @param {"versioned" | "legacy"} [version] - The version of the transaction
112
112
  * @returns {Promise<Transaction | VersionedTransaction>} The transfer transaction
113
113
  */
114
- async createTransfer(
114
+ async createTransaction(
115
115
  instructions: TransactionInstruction[],
116
116
  connection: Connection,
117
117
  version?: string
package/src/types.ts CHANGED
@@ -11,6 +11,7 @@ export type AlchemySignerEvents = {
11
11
  mfaFactorId?: string;
12
12
  encryptedPayload?: string;
13
13
  }): void;
14
+ emailAuthLinkingRequired(email: string): void;
14
15
  };
15
16
 
16
17
  export type AlchemySignerEvent = keyof AlchemySignerEvents;
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is autogenerated by inject-version.ts. Any changes will be
2
2
  // overwritten on commit!
3
- export const VERSION = "4.31.2";
3
+ export const VERSION = "4.32.0";