@account-kit/signer 4.21.0 → 4.23.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 (62) hide show
  1. package/dist/esm/base.d.ts +183 -2
  2. package/dist/esm/base.js +352 -55
  3. package/dist/esm/base.js.map +1 -1
  4. package/dist/esm/client/base.d.ts +46 -5
  5. package/dist/esm/client/base.js.map +1 -1
  6. package/dist/esm/client/index.d.ts +51 -4
  7. package/dist/esm/client/index.js +201 -9
  8. package/dist/esm/client/index.js.map +1 -1
  9. package/dist/esm/client/types.d.ts +102 -1
  10. package/dist/esm/client/types.js.map +1 -1
  11. package/dist/esm/errors.d.ts +6 -0
  12. package/dist/esm/errors.js +18 -0
  13. package/dist/esm/errors.js.map +1 -1
  14. package/dist/esm/index.d.ts +1 -1
  15. package/dist/esm/index.js +1 -1
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/session/manager.d.ts +2 -0
  18. package/dist/esm/session/manager.js.map +1 -1
  19. package/dist/esm/signer.d.ts +4 -2
  20. package/dist/esm/signer.js.map +1 -1
  21. package/dist/esm/types.d.ts +15 -1
  22. package/dist/esm/types.js +6 -0
  23. package/dist/esm/types.js.map +1 -1
  24. package/dist/esm/utils/parseMfaError.d.ts +2 -0
  25. package/dist/esm/utils/parseMfaError.js +15 -0
  26. package/dist/esm/utils/parseMfaError.js.map +1 -0
  27. package/dist/esm/version.d.ts +1 -1
  28. package/dist/esm/version.js +1 -1
  29. package/dist/esm/version.js.map +1 -1
  30. package/dist/types/base.d.ts +183 -2
  31. package/dist/types/base.d.ts.map +1 -1
  32. package/dist/types/client/base.d.ts +46 -5
  33. package/dist/types/client/base.d.ts.map +1 -1
  34. package/dist/types/client/index.d.ts +51 -4
  35. package/dist/types/client/index.d.ts.map +1 -1
  36. package/dist/types/client/types.d.ts +102 -1
  37. package/dist/types/client/types.d.ts.map +1 -1
  38. package/dist/types/errors.d.ts +6 -0
  39. package/dist/types/errors.d.ts.map +1 -1
  40. package/dist/types/index.d.ts +1 -1
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/dist/types/session/manager.d.ts +2 -0
  43. package/dist/types/session/manager.d.ts.map +1 -1
  44. package/dist/types/signer.d.ts +4 -2
  45. package/dist/types/signer.d.ts.map +1 -1
  46. package/dist/types/types.d.ts +15 -1
  47. package/dist/types/types.d.ts.map +1 -1
  48. package/dist/types/utils/parseMfaError.d.ts +3 -0
  49. package/dist/types/utils/parseMfaError.d.ts.map +1 -0
  50. package/dist/types/version.d.ts +1 -1
  51. package/package.json +4 -4
  52. package/src/base.ts +421 -62
  53. package/src/client/base.ts +56 -2
  54. package/src/client/index.ts +225 -10
  55. package/src/client/types.ts +112 -1
  56. package/src/errors.ts +11 -1
  57. package/src/index.ts +5 -1
  58. package/src/session/manager.ts +6 -1
  59. package/src/signer.ts +6 -1
  60. package/src/types.ts +16 -0
  61. package/src/utils/parseMfaError.ts +15 -0
  62. package/src/version.ts +1 -1
package/src/base.ts CHANGED
@@ -21,7 +21,17 @@ 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 { OauthConfig, OauthParams, User } from "./client/types";
24
+ import type {
25
+ EmailType,
26
+ MfaFactor,
27
+ OauthConfig,
28
+ OauthParams,
29
+ User,
30
+ VerifyMfaParams,
31
+ EnableMfaParams,
32
+ EnableMfaResult,
33
+ RemoveMfaParams,
34
+ } from "./client/types";
25
35
  import { NotAuthenticatedError } from "./errors.js";
26
36
  import { SignerLogger } from "./metrics.js";
27
37
  import {
@@ -36,6 +46,7 @@ import {
36
46
  type AlchemySignerEvent,
37
47
  type AlchemySignerEvents,
38
48
  type ErrorInfo,
49
+ type ValidateMultiFactorsArgs,
39
50
  } from "./types.js";
40
51
  import { assertNever } from "./utils/typeAssertions.js";
41
52
 
@@ -51,6 +62,11 @@ type AlchemySignerStore = {
51
62
  error: ErrorInfo | null;
52
63
  otpId?: string;
53
64
  isNewUser?: boolean;
65
+ mfaStatus: {
66
+ mfaRequired: boolean;
67
+ mfaFactorId?: string;
68
+ encryptedPayload?: string;
69
+ };
54
70
  };
55
71
 
56
72
  type UnpackedSignature = {
@@ -108,6 +124,10 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
108
124
  user: null,
109
125
  status: AlchemySignerStatus.INITIALIZING,
110
126
  error: initialError ?? null,
127
+ mfaStatus: {
128
+ mfaRequired: false,
129
+ mfaFactorId: undefined,
130
+ },
111
131
  } satisfies AlchemySignerStore)
112
132
  )
113
133
  );
@@ -181,6 +201,13 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
181
201
  },
182
202
  { fireImmediately: true }
183
203
  );
204
+ case "mfaStatusChanged":
205
+ return this.store.subscribe(
206
+ ({ mfaStatus }) => mfaStatus,
207
+ (mfaStatus) =>
208
+ (listener as AlchemySignerEvents["mfaStatusChanged"])(mfaStatus),
209
+ { fireImmediately: true }
210
+ );
184
211
  default:
185
212
  assertNever(event, `Unknown event type ${event}`);
186
213
  }
@@ -590,6 +617,39 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
590
617
  }
591
618
  );
592
619
 
620
+ /**
621
+ * Gets the current MFA status
622
+ *
623
+ * @example
624
+ * ```ts
625
+ * import { AlchemyWebSigner } from "@account-kit/signer";
626
+ *
627
+ * const signer = new AlchemyWebSigner({
628
+ * client: {
629
+ * connection: {
630
+ * rpcUrl: "/api/rpc",
631
+ * },
632
+ * iframeConfig: {
633
+ * iframeContainerId: "alchemy-signer-iframe-container",
634
+ * },
635
+ * },
636
+ * });
637
+ *
638
+ * const mfaStatus = signer.getMfaStatus();
639
+ * if (mfaStatus === AlchemyMfaStatus.REQUIRED) {
640
+ * // Handle MFA requirement
641
+ * }
642
+ * ```
643
+ *
644
+ * @returns {{ mfaRequired: boolean; mfaFactorId?: string }} The current MFA status
645
+ */
646
+ getMfaStatus = (): {
647
+ mfaRequired: boolean;
648
+ mfaFactorId?: string;
649
+ } => {
650
+ return this.store.getState().mfaStatus;
651
+ };
652
+
593
653
  private unpackSignRawMessageBytes = (
594
654
  hex: `0x${string}`
595
655
  ): UnpackedSignature => {
@@ -784,70 +844,36 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
784
844
  private authenticateWithEmail = async (
785
845
  params: Extract<AuthParams, { type: "email" }>
786
846
  ): Promise<User> => {
787
- if ("email" in params) {
788
- const existingUser = await this.getUser(params.email);
789
- const expirationSeconds = this.getExpirationSeconds();
790
-
791
- const { orgId, otpId } = existingUser
792
- ? await this.inner.initEmailAuth({
793
- email: params.email,
794
- emailMode: params.emailMode,
795
- expirationSeconds,
796
- redirectParams: params.redirectParams,
797
- })
798
- : await this.inner.createAccount({
799
- type: "email",
800
- email: params.email,
801
- emailMode: params.emailMode,
802
- expirationSeconds,
803
- redirectParams: params.redirectParams,
804
- });
847
+ if ("bundle" in params) {
848
+ return this.completeEmailAuth(params);
849
+ }
805
850
 
806
- this.sessionManager.setTemporarySession({
807
- orgId,
808
- isNewUser: !existingUser,
809
- });
810
- this.store.setState({
811
- status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
812
- otpId,
813
- error: null,
814
- });
851
+ const { orgId, otpId, isNewUser } = await this.initOrCreateEmailUser(
852
+ params.email,
853
+ params.emailMode,
854
+ params.multiFactors,
855
+ params.redirectParams
856
+ );
815
857
 
816
- // We wait for the session manager to emit a connected event if
817
- // cross tab sessions are permitted
818
- return new Promise<User>((resolve) => {
819
- const removeListener = this.sessionManager.on(
820
- "connected",
821
- (session) => {
822
- resolve(session.user);
823
- removeListener();
824
- }
825
- );
826
- });
827
- } else {
828
- const temporarySession = params.orgId
829
- ? { orgId: params.orgId }
830
- : this.sessionManager.getTemporarySession();
858
+ this.sessionManager.setTemporarySession({
859
+ orgId,
860
+ isNewUser,
861
+ });
831
862
 
832
- if (!temporarySession) {
833
- this.store.setState({
834
- status: AlchemySignerStatus.DISCONNECTED,
835
- });
836
- throw new Error("Could not find email auth init session!");
837
- }
863
+ this.store.setState({
864
+ status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
865
+ otpId,
866
+ error: null,
867
+ });
838
868
 
839
- const user = await this.inner.completeAuthWithBundle({
840
- bundle: params.bundle,
841
- orgId: temporarySession.orgId,
842
- connectedEventName: "connectedEmail",
843
- authenticatingType: "email",
869
+ // We wait for the session manager to emit a connected event if
870
+ // 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();
844
875
  });
845
-
846
- // fire new user event
847
- this.emitNewUserEvent(params.isNewUser);
848
-
849
- return user;
850
- }
876
+ });
851
877
  };
852
878
 
853
879
  private authenticateWithPasskey = async (
@@ -916,14 +942,31 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
916
942
  if (!otpId) {
917
943
  throw new Error("otpId not found in session");
918
944
  }
919
- const { bundle } = await this.inner.submitOtpCode({
945
+
946
+ const response = await this.inner.submitOtpCode({
920
947
  orgId,
921
948
  otpId,
922
949
  otpCode: args.otpCode,
923
950
  expirationSeconds: this.getExpirationSeconds(),
951
+ multiFactors: args.multiFactors,
924
952
  });
953
+
954
+ if (response.mfaRequired) {
955
+ 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
+ });
966
+ }
967
+
925
968
  const user = await this.inner.completeAuthWithBundle({
926
- bundle,
969
+ bundle: response.bundle,
927
970
  orgId,
928
971
  connectedEventName: "connectedOtp",
929
972
  authenticatingType: "otp",
@@ -959,6 +1002,31 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
959
1002
  return user;
960
1003
  };
961
1004
 
1005
+ private handleMfaRequired(
1006
+ encryptedPayload: string,
1007
+ multiFactors: MfaFactor[]
1008
+ ) {
1009
+ // Store complete MFA context in the temporary session
1010
+ const tempSession = this.sessionManager.getTemporarySession();
1011
+ if (tempSession) {
1012
+ this.sessionManager.setTemporarySession({
1013
+ ...tempSession,
1014
+ encryptedPayload,
1015
+ mfaFactorId: multiFactors?.[0]?.multiFactorId,
1016
+ });
1017
+ }
1018
+
1019
+ // Keep minimal state in the store for UI updates
1020
+ this.store.setState({
1021
+ status: AlchemySignerStatus.AWAITING_MFA_AUTH,
1022
+ error: null,
1023
+ mfaStatus: {
1024
+ mfaRequired: true,
1025
+ mfaFactorId: multiFactors?.[0]?.multiFactorId,
1026
+ },
1027
+ });
1028
+ }
1029
+
962
1030
  private getExpirationSeconds = () =>
963
1031
  Math.floor(this.sessionManager.expirationTimeMs / 1000);
964
1032
 
@@ -1027,6 +1095,297 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
1027
1095
  if (isNewUser) this.store.setState({ isNewUser });
1028
1096
  };
1029
1097
 
1098
+ private async initOrCreateEmailUser(
1099
+ email: string,
1100
+ emailMode?: EmailType,
1101
+ multiFactors?: VerifyMfaParams[],
1102
+ redirectParams?: URLSearchParams
1103
+ ): Promise<{
1104
+ orgId: string;
1105
+ otpId?: string;
1106
+ isNewUser: boolean;
1107
+ }> {
1108
+ const existingUser = await this.getUser(email);
1109
+ const expirationSeconds = this.getExpirationSeconds();
1110
+
1111
+ if (existingUser) {
1112
+ const { orgId, otpId } = await this.inner.initEmailAuth({
1113
+ email: email,
1114
+ emailMode: emailMode,
1115
+ expirationSeconds,
1116
+ redirectParams: redirectParams,
1117
+ multiFactors,
1118
+ });
1119
+ return {
1120
+ orgId,
1121
+ otpId,
1122
+ isNewUser: false,
1123
+ };
1124
+ }
1125
+
1126
+ const { orgId, otpId } = await this.inner.createAccount({
1127
+ type: "email",
1128
+ email,
1129
+ emailMode,
1130
+ expirationSeconds,
1131
+ redirectParams,
1132
+ });
1133
+ return {
1134
+ orgId,
1135
+ otpId,
1136
+ isNewUser: true,
1137
+ };
1138
+ }
1139
+
1140
+ private async completeEmailAuth(
1141
+ params: Extract<AuthParams, { type: "email"; bundle: string }>
1142
+ ): Promise<User> {
1143
+ const temporarySession = params.orgId
1144
+ ? { orgId: params.orgId }
1145
+ : this.sessionManager.getTemporarySession();
1146
+
1147
+ if (!temporarySession) {
1148
+ this.store.setState({ status: AlchemySignerStatus.DISCONNECTED });
1149
+ throw new Error("Could not find email auth init session!");
1150
+ }
1151
+
1152
+ const user = await this.inner.completeAuthWithBundle({
1153
+ bundle: params.bundle,
1154
+ orgId: temporarySession.orgId,
1155
+ connectedEventName: "connectedEmail",
1156
+ authenticatingType: "email",
1157
+ });
1158
+
1159
+ // fire new user event
1160
+ this.emitNewUserEvent(params.isNewUser);
1161
+
1162
+ return user;
1163
+ }
1164
+
1165
+ /**
1166
+ * Retrieves the list of MFA factors configured for the current user.
1167
+ *
1168
+ * @example
1169
+ * ```ts
1170
+ * import { AlchemyWebSigner } from "@account-kit/signer";
1171
+ *
1172
+ * const signer = new AlchemyWebSigner({
1173
+ * client: {
1174
+ * connection: {
1175
+ * rpcUrl: "/api/rpc",
1176
+ * },
1177
+ * iframeConfig: {
1178
+ * iframeContainerId: "alchemy-signer-iframe-container",
1179
+ * },
1180
+ * },
1181
+ * });
1182
+ *
1183
+ * const { multiFactors } = await signer.getMfaFactors();
1184
+ * ```
1185
+ *
1186
+ * @throws {NotAuthenticatedError} If no user is authenticated
1187
+ * @returns {Promise<{ multiFactors: Array<MfaFactor> }>} A promise that resolves to an array of configured MFA factors
1188
+ */
1189
+ getMfaFactors: () => Promise<{ multiFactors: MfaFactor[] }> =
1190
+ SignerLogger.profiled("BaseAlchemySigner.getMfaFactors", async () => {
1191
+ return this.inner.getMfaFactors();
1192
+ });
1193
+
1194
+ /**
1195
+ * Initiates the setup of a new MFA factor for the current user.
1196
+ * The factor will need to be verified using verifyMfa before it becomes active.
1197
+ *
1198
+ * @example
1199
+ * ```ts
1200
+ * import { AlchemyWebSigner } from "@account-kit/signer";
1201
+ *
1202
+ * const signer = new AlchemyWebSigner({
1203
+ * client: {
1204
+ * connection: {
1205
+ * rpcUrl: "/api/rpc",
1206
+ * },
1207
+ * iframeConfig: {
1208
+ * iframeContainerId: "alchemy-signer-iframe-container",
1209
+ * },
1210
+ * },
1211
+ * });
1212
+ *
1213
+ * const result = await signer.addMfa({ multiFactorType: "totp" });
1214
+ * // Result contains multiFactorTotpUrl to display as QR code
1215
+ * ```
1216
+ *
1217
+ * @param {EnableMfaParams} params The parameters required to enable a new MFA factor
1218
+ * @throws {NotAuthenticatedError} If no user is authenticated
1219
+ * @returns {Promise<EnableMfaResult>} A promise that resolves to the factor setup information
1220
+ */
1221
+ addMfa: (params: EnableMfaParams) => Promise<EnableMfaResult> =
1222
+ SignerLogger.profiled("BaseAlchemySigner.addMfa", async (params) => {
1223
+ return this.inner.addMfa(params);
1224
+ });
1225
+
1226
+ /**
1227
+ * Verifies a newly created MFA factor to complete the setup process.
1228
+ *
1229
+ * @example
1230
+ * ```ts
1231
+ * import { AlchemyWebSigner } from "@account-kit/signer";
1232
+ *
1233
+ * const signer = new AlchemyWebSigner({
1234
+ * client: {
1235
+ * connection: {
1236
+ * rpcUrl: "/api/rpc",
1237
+ * },
1238
+ * iframeConfig: {
1239
+ * iframeContainerId: "alchemy-signer-iframe-container",
1240
+ * },
1241
+ * },
1242
+ * });
1243
+ *
1244
+ * const result = await signer.verifyMfa({
1245
+ * multiFactorId: "factor-id",
1246
+ * multiFactorCode: "123456" // 6-digit code from authenticator app
1247
+ * });
1248
+ * ```
1249
+ *
1250
+ * @param {VerifyMfaParams} params The parameters required to verify the MFA factor
1251
+ * @throws {NotAuthenticatedError} If no user is authenticated
1252
+ * @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
1253
+ */
1254
+ verifyMfa: (
1255
+ params: VerifyMfaParams
1256
+ ) => Promise<{ multiFactors: MfaFactor[] }> = SignerLogger.profiled(
1257
+ "BaseAlchemySigner.verifyMfa",
1258
+ async (params) => {
1259
+ return this.inner.verifyMfa(params);
1260
+ }
1261
+ );
1262
+
1263
+ /**
1264
+ * Removes existing MFA factors by their IDs.
1265
+ *
1266
+ * @example
1267
+ * ```ts
1268
+ * import { AlchemyWebSigner } from "@account-kit/signer";
1269
+ *
1270
+ * const signer = new AlchemyWebSigner({
1271
+ * client: {
1272
+ * connection: {
1273
+ * rpcUrl: "/api/rpc",
1274
+ * },
1275
+ * iframeConfig: {
1276
+ * iframeContainerId: "alchemy-signer-iframe-container",
1277
+ * },
1278
+ * },
1279
+ * });
1280
+ *
1281
+ * const result = await signer.removeMfa({
1282
+ * multiFactorIds: ["factor-id-1", "factor-id-2"]
1283
+ * });
1284
+ * ```
1285
+ *
1286
+ * @param {RemoveMfaParams} params The parameters specifying which factors to disable
1287
+ * @throws {NotAuthenticatedError} If no user is authenticated
1288
+ * @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
1289
+ */
1290
+ removeMfa: (
1291
+ params: RemoveMfaParams
1292
+ ) => Promise<{ multiFactors: MfaFactor[] }> = SignerLogger.profiled(
1293
+ "BaseAlchemySigner.removeMfa",
1294
+ async (params) => {
1295
+ return this.inner.removeMfa(params);
1296
+ }
1297
+ );
1298
+
1299
+ /**
1300
+ * Validates MFA factors that were required during authentication.
1301
+ * This function should be called after MFA is required and the user has provided their MFA code.
1302
+ * It completes the authentication process by validating the MFA factors and completing the auth bundle.
1303
+ *
1304
+ * @example
1305
+ * ```ts
1306
+ * import { AlchemyWebSigner } from "@account-kit/signer";
1307
+ *
1308
+ * const signer = new AlchemyWebSigner({
1309
+ * client: {
1310
+ * connection: {
1311
+ * rpcUrl: "/api/rpc",
1312
+ * },
1313
+ * iframeConfig: {
1314
+ * iframeContainerId: "alchemy-signer-iframe-container",
1315
+ * },
1316
+ * },
1317
+ * });
1318
+ *
1319
+ * // After MFA is required and user provides code
1320
+ * const user = await signer.validateMultiFactors({
1321
+ * multiFactorCode: "123456", // 6-digit code from authenticator app
1322
+ * multiFactorId: "factor-id"
1323
+ * });
1324
+ * ```
1325
+ *
1326
+ * @param {ValidateMultiFactorsArgs} params - Parameters for validating MFA factors
1327
+ * @throws {Error} If there is no pending MFA context or if orgId is not found
1328
+ * @returns {Promise<User>} A promise that resolves to the authenticated user
1329
+ */
1330
+ public async validateMultiFactors(
1331
+ params: ValidateMultiFactorsArgs
1332
+ ): Promise<User> {
1333
+ // Get MFA context from temporary session
1334
+ const tempSession = this.sessionManager.getTemporarySession();
1335
+ if (
1336
+ !tempSession?.orgId ||
1337
+ !tempSession.encryptedPayload ||
1338
+ !tempSession.mfaFactorId
1339
+ ) {
1340
+ throw new Error(
1341
+ "No pending MFA context found. Call submitOtpCode() first."
1342
+ );
1343
+ }
1344
+
1345
+ if (
1346
+ params.multiFactorId &&
1347
+ tempSession.mfaFactorId !== params.multiFactorId
1348
+ ) {
1349
+ throw new Error("MFA factor ID mismatch");
1350
+ }
1351
+
1352
+ // Call the low-level client
1353
+ const { bundle } = await this.inner.validateMultiFactors({
1354
+ encryptedPayload: tempSession.encryptedPayload,
1355
+ multiFactors: [
1356
+ {
1357
+ multiFactorId: tempSession.mfaFactorId,
1358
+ multiFactorCode: params.multiFactorCode,
1359
+ },
1360
+ ],
1361
+ });
1362
+
1363
+ // Complete the authentication
1364
+ const user = await this.inner.completeAuthWithBundle({
1365
+ bundle,
1366
+ orgId: tempSession.orgId,
1367
+ connectedEventName: "connectedOtp",
1368
+ authenticatingType: "otp",
1369
+ });
1370
+
1371
+ // Remove MFA data from temporary session
1372
+ this.sessionManager.setTemporarySession({
1373
+ ...tempSession,
1374
+ encryptedPayload: undefined,
1375
+ mfaFactorId: undefined,
1376
+ });
1377
+
1378
+ // Update UI state
1379
+ this.store.setState({
1380
+ mfaStatus: {
1381
+ mfaRequired: false,
1382
+ mfaFactorId: undefined,
1383
+ },
1384
+ });
1385
+
1386
+ return user;
1387
+ }
1388
+
1030
1389
  protected initConfig = async (): Promise<SignerConfig> => {
1031
1390
  this.config = this.fetchConfig();
1032
1391
  return this.config;
@@ -14,10 +14,14 @@ import type {
14
14
  AlchemySignerClientEvents,
15
15
  AuthenticatingEventMetadata,
16
16
  CreateAccountParams,
17
+ RemoveMfaParams,
17
18
  EmailAuthParams,
19
+ EnableMfaParams,
20
+ EnableMfaResult,
18
21
  experimental_CreateApiKeyParams,
19
22
  GetOauthProviderUrlArgs,
20
23
  GetWebAuthnAttestationResult,
24
+ MfaFactor,
21
25
  OauthConfig,
22
26
  OauthParams,
23
27
  OauthState,
@@ -27,6 +31,9 @@ import type {
27
31
  SignerRoutes,
28
32
  SignupResponse,
29
33
  User,
34
+ VerifyMfaParams,
35
+ SubmitOtpCodeResponse,
36
+ ValidateMultiFactorsParams,
30
37
  } from "./types.js";
31
38
  import { VERSION } from "../version.js";
32
39
 
@@ -133,7 +140,54 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
133
140
 
134
141
  public abstract initEmailAuth(
135
142
  params: Omit<EmailAuthParams, "targetPublicKey">
136
- ): Promise<{ orgId: string; otpId?: string }>;
143
+ ): Promise<{ orgId: string; otpId?: string; multiFactors?: MfaFactor[] }>;
144
+
145
+ /**
146
+ * Retrieves the list of MFA factors configured for the current user.
147
+ *
148
+ * @returns {Promise<{ multiFactors: Array<MfaFactor> }>} A promise that resolves to an array of configured MFA factors
149
+ */
150
+ public abstract getMfaFactors(): Promise<{
151
+ multiFactors: MfaFactor[];
152
+ }>;
153
+
154
+ /**
155
+ * Initiates the setup of a new MFA factor for the current user. Mfa will need to be verified before it is active.
156
+ *
157
+ * @param {EnableMfaParams} params The parameters required to enable a new MFA factor
158
+ * @returns {Promise<EnableMfaResult>} A promise that resolves to the factor setup information
159
+ */
160
+ public abstract addMfa(params: EnableMfaParams): Promise<EnableMfaResult>;
161
+
162
+ /**
163
+ * Verifies a newly created MFA factor to complete the setup process.
164
+ *
165
+ * @param {VerifyMfaParams} params The parameters required to verify the MFA factor
166
+ * @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
167
+ */
168
+ public abstract verifyMfa(params: VerifyMfaParams): Promise<{
169
+ multiFactors: MfaFactor[];
170
+ }>;
171
+
172
+ /**
173
+ * Removes existing MFA factors by ID or factor type.
174
+ *
175
+ * @param {RemoveMfaParams} params The parameters specifying which factors to disable
176
+ * @returns {Promise<{ multiFactors: MfaFactor[] }>} A promise that resolves to the updated list of MFA factors
177
+ */
178
+ public abstract removeMfa(params: RemoveMfaParams): Promise<{
179
+ multiFactors: MfaFactor[];
180
+ }>;
181
+
182
+ /**
183
+ * Validates multiple MFA factors using the provided encrypted payload and MFA codes.
184
+ *
185
+ * @param {ValidateMultiFactorsParams} params The validation parameters
186
+ * @returns {Promise<{ bundle: string }>} A promise that resolves to an object containing the credential bundle
187
+ */
188
+ public abstract validateMultiFactors(
189
+ params: ValidateMultiFactorsParams
190
+ ): Promise<{ bundle: string }>;
137
191
 
138
192
  public abstract completeAuthWithBundle(params: {
139
193
  bundle: string;
@@ -153,7 +207,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
153
207
 
154
208
  public abstract submitOtpCode(
155
209
  args: Omit<OtpParams, "targetPublicKey">
156
- ): Promise<{ bundle: string }>;
210
+ ): Promise<SubmitOtpCodeResponse>;
157
211
 
158
212
  public abstract disconnect(): Promise<void>;
159
213