@account-kit/signer 4.60.1 → 4.62.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.
@@ -15,7 +15,6 @@ import { getDefaultProviderCustomization } from "../oauth.js";
15
15
  import type { OauthMode } from "../signer.js";
16
16
  import { base64UrlEncode } from "../utils/base64UrlEncode.js";
17
17
  import { resolveRelativeUrl } from "../utils/resolveRelativeUrl.js";
18
- import { assertNever } from "../utils/typeAssertions.js";
19
18
  import type {
20
19
  AlchemySignerClientEvent,
21
20
  AlchemySignerClientEvents,
@@ -50,6 +49,7 @@ import type {
50
49
  IdTokenOnly,
51
50
  AuthMethods,
52
51
  SmsAuthParams,
52
+ VerificationOtp,
53
53
  } from "./types.js";
54
54
  import { VERSION } from "../version.js";
55
55
  import { secp256k1 } from "@noble/curves/secp256k1";
@@ -81,7 +81,10 @@ const withHexPrefix = (hex: string) => `0x${hex}` as const;
81
81
  /**
82
82
  * Base class for all Alchemy Signer clients
83
83
  */
84
- export abstract class BaseSignerClient<TExportWalletParams = unknown> {
84
+ export abstract class BaseSignerClient<
85
+ TExportWalletParams = unknown,
86
+ TExportWalletOutput = unknown,
87
+ > {
85
88
  private _user: User | undefined;
86
89
  private connectionConfig: ConnectionConfig;
87
90
  protected turnkeyClient: TurnkeyClient;
@@ -137,29 +140,6 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
137
140
  this.turnkeyClient.stamper = stamper;
138
141
  }
139
142
 
140
- /**
141
- * Exports wallet credentials based on the specified type, either as a SEED_PHRASE or PRIVATE_KEY.
142
- *
143
- * @param {object} params The parameters for exporting the wallet
144
- * @param {ExportWalletStamper} params.exportStamper The stamper used for exporting the wallet
145
- * @param {"SEED_PHRASE" | "PRIVATE_KEY"} params.exportAs Specifies the format for exporting the wallet, either as a SEED_PHRASE or PRIVATE_KEY
146
- * @returns {Promise<boolean>} A promise that resolves to true if the export is successful
147
- */
148
- protected exportWalletInner(params: {
149
- exportStamper: ExportWalletStamper;
150
- exportAs: "SEED_PHRASE" | "PRIVATE_KEY";
151
- }): Promise<boolean> {
152
- const { exportAs } = params;
153
- switch (exportAs) {
154
- case "PRIVATE_KEY":
155
- return this.exportAsPrivateKey(params.exportStamper);
156
- case "SEED_PHRASE":
157
- return this.exportAsSeedPhrase(params.exportStamper);
158
- default:
159
- assertNever(exportAs, `Unknown export mode: ${exportAs}`);
160
- }
161
- }
162
-
163
143
  /**
164
144
  * Authenticates the user by either email or passkey account creation flow. Emits events during the process.
165
145
  *
@@ -265,7 +245,9 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
265
245
 
266
246
  public abstract disconnect(): Promise<void>;
267
247
 
268
- public abstract exportWallet(params: TExportWalletParams): Promise<boolean>;
248
+ public abstract exportWallet(
249
+ params: TExportWalletParams,
250
+ ): Promise<TExportWalletOutput>;
269
251
 
270
252
  public abstract targetPublicKey(): Promise<string>;
271
253
 
@@ -310,53 +292,178 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
310
292
  * Sets the email for the authenticated user, allowing them to login with that
311
293
  * email.
312
294
  *
313
- * You must contact Alchemy to enable this feature for your team, as there are
314
- * important security considerations. In particular, you must not call this
315
- * without first validating that the user owns this email account.
295
+ * @deprecated You must contact Alchemy to enable this feature for your team,
296
+ * as there are important security considerations. In particular, you must not
297
+ * call this without first validating that the user owns this email account.
298
+ * Recommended to use the email verification flow instead.
316
299
  *
317
300
  * @param {string} email The email to set for the user
318
- * @returns {Promise<void>} A promise that resolves when the email is set
301
+ * @returns {Promise<void>} A promise that resolves to the updated email
319
302
  * @throws {NotAuthenticatedError} If the user is not authenticated
320
303
  */
321
- public setEmail = async (email: string): Promise<void> => {
322
- if (!email) {
323
- throw new Error(
324
- "Email must not be empty. Use removeEmail() to remove email auth.",
325
- );
304
+ public setEmail(email: string): Promise<string>;
305
+
306
+ /**
307
+ * Sets the email for the authenticated user, allowing them to login with that
308
+ * email. Must be called after calling `initOtp` with the email.
309
+ *
310
+ * @param {VerificationOtp} otp The OTP verification object including the OTP ID and OTP code
311
+ * @returns {Promise<void>} A promise that resolves to the updated email
312
+ * @throws {NotAuthenticatedError} If the user is not authenticated
313
+ */
314
+ public setEmail(otp: VerificationOtp): Promise<string>;
315
+
316
+ /**
317
+ * Implementation for setEmail method with optional OTP verification.
318
+ *
319
+ * @param {string | VerificationOtp} params An OTP object containing the OTP ID & OTP code (or an email address for legacy usage)
320
+ * @returns {Promise<void>} A promise that resolves to the updated email address
321
+ */
322
+ public async setEmail(params: string | VerificationOtp): Promise<string> {
323
+ if (typeof params === "string") {
324
+ // Legacy use, requires team flag.
325
+ const contact = params;
326
+ if (!contact) {
327
+ throw new Error(
328
+ "Email must not be empty. Use removeEmail() to remove email auth.",
329
+ );
330
+ }
331
+ await this.updateEmail(contact);
332
+ return contact;
326
333
  }
327
- await this.updateEmail(email);
328
- };
334
+
335
+ const { verificationToken } = await this.request("/v1/verify-otp", {
336
+ otpId: params.id,
337
+ otpCode: params.code,
338
+ });
339
+ const { contact } = jwtDecode<{ contact: string }>(verificationToken);
340
+ await this.updateEmail(contact, verificationToken);
341
+ return contact;
342
+ }
329
343
 
330
344
  /**
331
345
  * Removes the email for the authenticated user, disallowing them from login with that email.
332
346
  *
333
- * @returns {Promise<void>} A promise that resolves when the email is removed
347
+ * @returns {Promise<string>} A promise that resolves when the email is removed
334
348
  * @throws {NotAuthenticatedError} If the user is not authenticated
335
349
  */
336
350
  public removeEmail = async (): Promise<void> => {
337
- // This is a hack to remove the email for the user. Turnkey does not
338
- // support clearing the email once set, so we set it to a known
339
- // inaccessible address instead.
340
- await this.updateEmail("not.enabled@example.invalid");
351
+ await this.updateEmail("");
352
+ };
353
+
354
+ private updateEmail = async (
355
+ email: string,
356
+ verificationToken?: string,
357
+ ): Promise<void> => {
358
+ if (!this.user) {
359
+ throw new NotAuthenticatedError();
360
+ }
361
+
362
+ // Unverified use is legacy & requires team flag.
363
+ const isUnverified = email && !verificationToken;
364
+
365
+ const stampedRequest = isUnverified
366
+ ? await this.turnkeyClient.stampUpdateUser({
367
+ type: "ACTIVITY_TYPE_UPDATE_USER",
368
+ timestampMs: Date.now().toString(),
369
+ organizationId: this.user.orgId,
370
+ parameters: {
371
+ userId: this.user.userId,
372
+ userEmail: email,
373
+ },
374
+ })
375
+ : await this.turnkeyClient.stampUpdateUserEmail({
376
+ type: "ACTIVITY_TYPE_UPDATE_USER_EMAIL",
377
+ timestampMs: Date.now().toString(),
378
+ organizationId: this.user.orgId,
379
+ parameters: {
380
+ userId: this.user.userId,
381
+ userEmail: email,
382
+ verificationToken,
383
+ },
384
+ });
385
+
386
+ await this.request("/v1/update-email-auth", {
387
+ stampedRequest,
388
+ });
389
+ this.user = {
390
+ ...this.user,
391
+ email: email || undefined,
392
+ };
393
+ };
394
+
395
+ /**
396
+ * Updates the phone number for the authenticated user, allowing them to login with that
397
+ * phone number. Must be called after calling `initOtp` with the phone number.
398
+ *
399
+ * @param {VerificationOtp} otp The OTP object including the OTP ID and OTP code
400
+ * @returns {Promise<void>} A promise that resolves when the phone number is set
401
+ * @throws {NotAuthenticatedError} If the user is not authenticated
402
+ */
403
+ public setPhoneNumber = async (otp: VerificationOtp): Promise<void> => {
404
+ const { verificationToken } = await this.request("/v1/verify-otp", {
405
+ otpId: otp.id,
406
+ otpCode: otp.code,
407
+ });
408
+ const { contact } = jwtDecode<{ contact: string }>(verificationToken);
409
+ await this.updatePhoneNumber(contact, verificationToken);
341
410
  };
342
411
 
343
- private updateEmail = async (email: string): Promise<void> => {
412
+ /**
413
+ * Removes the phone number for the authenticated user, disallowing them from login with that phone number.
414
+ *
415
+ * @returns {Promise<void>} A promise that resolves when the phone number is removed
416
+ * @throws {NotAuthenticatedError} If the user is not authenticated
417
+ */
418
+ public removePhoneNumber = async (): Promise<void> => {
419
+ await this.updatePhoneNumber("");
420
+ };
421
+
422
+ private updatePhoneNumber = async (
423
+ phone: string,
424
+ verificationToken?: string,
425
+ ): Promise<void> => {
344
426
  if (!this.user) {
345
427
  throw new NotAuthenticatedError();
346
428
  }
347
- const stampedRequest = await this.turnkeyClient.stampUpdateUser({
348
- type: "ACTIVITY_TYPE_UPDATE_USER",
429
+ if (phone.trim() && !verificationToken) {
430
+ throw new Error("Verification token is required to change phone number.");
431
+ }
432
+ const stampedRequest = await this.turnkeyClient.stampUpdateUserPhoneNumber({
433
+ type: "ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER",
349
434
  timestampMs: Date.now().toString(),
350
435
  organizationId: this.user.orgId,
351
436
  parameters: {
352
437
  userId: this.user.userId,
353
- userEmail: email,
354
- userTagIds: [],
438
+ userPhoneNumber: phone,
439
+ verificationToken,
355
440
  },
356
441
  });
357
- await this.request("/v1/update-email-auth", {
442
+ await this.request("/v1/update-phone-auth", {
358
443
  stampedRequest,
359
444
  });
445
+ this.user = {
446
+ ...this.user,
447
+ phone: phone || undefined,
448
+ };
449
+ };
450
+
451
+ /**
452
+ * Initiates an OTP (One-Time Password) verification process for a user contact.
453
+ *
454
+ * @param {("email" | "sms")} type - The type of OTP to send, either "email" or "sms"
455
+ * @param {string} contact - The email address or phone number to send the OTP to
456
+ * @returns {Promise<{ otpId: string }>} A promise that resolves to an object containing the OTP ID
457
+ * @throws {NotAuthenticatedError} When no user is currently authenticated
458
+ */
459
+ public initOtp = async (
460
+ type: "email" | "sms",
461
+ contact: string,
462
+ ): Promise<{ otpId: string }> => {
463
+ return await this.request("/v1/init-otp", {
464
+ otpType: type === "email" ? "OTP_TYPE_EMAIL" : "OTP_TYPE_SMS",
465
+ contact,
466
+ });
360
467
  };
361
468
 
362
469
  /**
@@ -780,7 +887,9 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
780
887
 
781
888
  return {
782
889
  stampHeaderName: "X-Stamp",
783
- stampHeaderValue: base64UrlEncode(Buffer.from(JSON.stringify(stamp))),
890
+ stampHeaderValue: base64UrlEncode(
891
+ Buffer.from(JSON.stringify(stamp)).buffer,
892
+ ),
784
893
  };
785
894
  },
786
895
  });
@@ -1141,94 +1250,6 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
1141
1250
  // #endregion
1142
1251
 
1143
1252
  // #region PRIVATE METHODS
1144
- private exportAsSeedPhrase = async (stamper: ExportWalletStamper) => {
1145
- if (!this.user) {
1146
- throw new NotAuthenticatedError();
1147
- }
1148
-
1149
- const { wallets } = await this.turnkeyClient.getWallets({
1150
- organizationId: this.user.orgId,
1151
- });
1152
-
1153
- const walletAccounts = await Promise.all(
1154
- wallets.map(({ walletId }) =>
1155
- this.turnkeyClient.getWalletAccounts({
1156
- organizationId: this.user!.orgId,
1157
- walletId,
1158
- }),
1159
- ),
1160
- ).then((x) => x.flatMap((x) => x.accounts));
1161
-
1162
- const walletAccount = walletAccounts.find(
1163
- (x) => x.address === this.user!.address,
1164
- );
1165
-
1166
- if (!walletAccount) {
1167
- throw new Error(
1168
- `Could not find wallet associated with ${this.user.address}`,
1169
- );
1170
- }
1171
-
1172
- const { activity } = await this.turnkeyClient.exportWallet({
1173
- organizationId: this.user.orgId,
1174
- type: "ACTIVITY_TYPE_EXPORT_WALLET",
1175
- timestampMs: Date.now().toString(),
1176
- parameters: {
1177
- walletId: walletAccount!.walletId,
1178
- targetPublicKey: stamper.publicKey()!,
1179
- },
1180
- });
1181
-
1182
- const { exportBundle } = await this.pollActivityCompletion(
1183
- activity,
1184
- this.user.orgId,
1185
- "exportWalletResult",
1186
- );
1187
-
1188
- const result = await stamper.injectWalletExportBundle(
1189
- exportBundle,
1190
- this.user.orgId,
1191
- );
1192
-
1193
- if (!result) {
1194
- throw new Error("Failed to inject wallet export bundle");
1195
- }
1196
-
1197
- return result;
1198
- };
1199
-
1200
- private exportAsPrivateKey = async (stamper: ExportWalletStamper) => {
1201
- if (!this.user) {
1202
- throw new NotAuthenticatedError();
1203
- }
1204
-
1205
- const { activity } = await this.turnkeyClient.exportWalletAccount({
1206
- organizationId: this.user.orgId,
1207
- type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT",
1208
- timestampMs: Date.now().toString(),
1209
- parameters: {
1210
- address: this.user.address,
1211
- targetPublicKey: stamper.publicKey()!,
1212
- },
1213
- });
1214
-
1215
- const { exportBundle } = await this.pollActivityCompletion(
1216
- activity,
1217
- this.user.orgId,
1218
- "exportWalletAccountResult",
1219
- );
1220
-
1221
- const result = await stamper.injectKeyExportBundle(
1222
- exportBundle,
1223
- this.user.orgId,
1224
- );
1225
-
1226
- if (!result) {
1227
- throw new Error("Failed to inject wallet export bundle");
1228
- }
1229
-
1230
- return result;
1231
- };
1232
1253
 
1233
1254
  /**
1234
1255
  * Returns the authentication url for the selected OAuth Proivder
@@ -1337,7 +1358,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
1337
1358
  fetchIdTokenOnly: oauthParams.fetchIdTokenOnly,
1338
1359
  };
1339
1360
  const state = base64UrlEncode(
1340
- new TextEncoder().encode(JSON.stringify(stateObject)),
1361
+ new TextEncoder().encode(JSON.stringify(stateObject)).buffer,
1341
1362
  );
1342
1363
  const authUrl = new URL(authEndpoint);
1343
1364
  const params: Record<string, string> = {
@@ -5,7 +5,9 @@ import { WebauthnStamper } from "@turnkey/webauthn-stamper";
5
5
  import { z } from "zod";
6
6
  import type { AuthParams } from "../signer.js";
7
7
  import { generateRandomBuffer } from "../utils/generateRandomBuffer.js";
8
- import { BaseSignerClient } from "./base.js";
8
+ import { assertNever } from "../utils/typeAssertions.js";
9
+ import { BaseSignerClient, type ExportWalletStamper } from "./base.js";
10
+ import { NotAuthenticatedError } from "../errors.js";
9
11
  import type {
10
12
  AlchemySignerClientEvents,
11
13
  AuthenticatingEventMetadata,
@@ -22,6 +24,7 @@ import type {
22
24
  GetWebAuthnAttestationResult,
23
25
  IdTokenOnly,
24
26
  SmsAuthParams,
27
+ ExportWalletOutput,
25
28
  } from "./types.js";
26
29
  import { MfaRequiredError } from "../errors.js";
27
30
  import { parseMfaError } from "../utils/parseMfaError.js";
@@ -54,7 +57,10 @@ export type AlchemySignerClientParams = z.input<
54
57
  * A lower level client used by the AlchemySigner used to communicate with
55
58
  * Alchemy's signer service.
56
59
  */
57
- export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams> {
60
+ export class AlchemySignerWebClient extends BaseSignerClient<
61
+ ExportWalletParams,
62
+ ExportWalletOutput
63
+ > {
58
64
  private iframeStamper: IframeStamper;
59
65
  private webauthnStamper: WebauthnStamper;
60
66
  oauthCallbackUrl: string;
@@ -389,7 +395,7 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
389
395
  public override exportWallet = async ({
390
396
  iframeContainerId,
391
397
  iframeElementId = "turnkey-export-iframe",
392
- }: ExportWalletParams) => {
398
+ }: ExportWalletParams): Promise<ExportWalletOutput> => {
393
399
  const exportWalletIframeStamper = new IframeStamper({
394
400
  iframeContainer: document.getElementById(iframeContainerId),
395
401
  iframeElementId: iframeElementId,
@@ -410,6 +416,29 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
410
416
  });
411
417
  };
412
418
 
419
+ /**
420
+ * Exports wallet credentials based on the specified type, either as a SEED_PHRASE or PRIVATE_KEY.
421
+ *
422
+ * @param {object} params The parameters for exporting the wallet
423
+ * @param {ExportWalletStamper} params.exportStamper The stamper used for exporting the wallet
424
+ * @param {"SEED_PHRASE" | "PRIVATE_KEY"} params.exportAs Specifies the format for exporting the wallet, either as a SEED_PHRASE or PRIVATE_KEY
425
+ * @returns {Promise<boolean>} A promise that resolves to true if the export is successful
426
+ */
427
+ protected exportWalletInner(params: {
428
+ exportStamper: ExportWalletStamper;
429
+ exportAs: "SEED_PHRASE" | "PRIVATE_KEY";
430
+ }): Promise<ExportWalletOutput> {
431
+ const { exportAs } = params;
432
+ switch (exportAs) {
433
+ case "PRIVATE_KEY":
434
+ return this.exportAsPrivateKey(params.exportStamper);
435
+ case "SEED_PHRASE":
436
+ return this.exportAsSeedPhrase(params.exportStamper);
437
+ default:
438
+ assertNever(exportAs, `Unknown export mode: ${exportAs}`);
439
+ }
440
+ }
441
+
413
442
  /**
414
443
  * Asynchronous function that clears the user and resets the iframe stamper.
415
444
  *
@@ -748,6 +777,96 @@ export class AlchemySignerWebClient extends BaseSignerClient<ExportWalletParams>
748
777
  ];
749
778
  }
750
779
  }
780
+
781
+ private exportAsSeedPhrase = async (stamper: ExportWalletStamper) => {
782
+ if (!this.user) {
783
+ throw new NotAuthenticatedError();
784
+ }
785
+
786
+ const { wallets } = await this.turnkeyClient.getWallets({
787
+ organizationId: this.user.orgId,
788
+ });
789
+
790
+ const walletAccountResponses = await Promise.all(
791
+ wallets.map(({ walletId }) =>
792
+ this.turnkeyClient.getWalletAccounts({
793
+ organizationId: this.user!.orgId,
794
+ walletId,
795
+ }),
796
+ ),
797
+ );
798
+ const walletAccounts = walletAccountResponses.flatMap((x) => x.accounts);
799
+
800
+ const walletAccount = walletAccounts.find(
801
+ (x) => x.address.toLowerCase() === this.user!.address.toLowerCase(),
802
+ );
803
+
804
+ if (!walletAccount) {
805
+ throw new Error(
806
+ `Could not find wallet associated with ${this.user.address}`,
807
+ );
808
+ }
809
+
810
+ const { activity } = await this.turnkeyClient.exportWallet({
811
+ organizationId: this.user.orgId,
812
+ type: "ACTIVITY_TYPE_EXPORT_WALLET",
813
+ timestampMs: Date.now().toString(),
814
+ parameters: {
815
+ walletId: walletAccount!.walletId,
816
+ targetPublicKey: stamper.publicKey()!,
817
+ },
818
+ });
819
+
820
+ const { exportBundle } = await this.pollActivityCompletion(
821
+ activity,
822
+ this.user.orgId,
823
+ "exportWalletResult",
824
+ );
825
+
826
+ const result = await stamper.injectWalletExportBundle(
827
+ exportBundle,
828
+ this.user.orgId,
829
+ );
830
+
831
+ if (!result) {
832
+ throw new Error("Failed to inject wallet export bundle");
833
+ }
834
+
835
+ return result;
836
+ };
837
+
838
+ private exportAsPrivateKey = async (stamper: ExportWalletStamper) => {
839
+ if (!this.user) {
840
+ throw new NotAuthenticatedError();
841
+ }
842
+
843
+ const { activity } = await this.turnkeyClient.exportWalletAccount({
844
+ organizationId: this.user.orgId,
845
+ type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT",
846
+ timestampMs: Date.now().toString(),
847
+ parameters: {
848
+ address: this.user.address,
849
+ targetPublicKey: stamper.publicKey()!,
850
+ },
851
+ });
852
+
853
+ const { exportBundle } = await this.pollActivityCompletion(
854
+ activity,
855
+ this.user.orgId,
856
+ "exportWalletAccountResult",
857
+ );
858
+
859
+ const result = await stamper.injectKeyExportBundle(
860
+ exportBundle,
861
+ this.user.orgId,
862
+ );
863
+
864
+ if (!result) {
865
+ throw new Error("Failed to inject wallet export bundle");
866
+ }
867
+
868
+ return result;
869
+ };
751
870
  }
752
871
 
753
872
  /**
@@ -7,6 +7,15 @@ import type {
7
7
  import type { Hex } from "viem";
8
8
  import type { AuthParams } from "../signer";
9
9
 
10
+ // [!region VerificationOtp]
11
+ export type VerificationOtp = {
12
+ /** The OTP ID returned from initOtp */
13
+ id: string;
14
+ /** The OTP code received by the user */
15
+ code: string;
16
+ };
17
+ // [!endregion VerificationOtp]
18
+
10
19
  export type CredentialCreationOptionOverrides = {
11
20
  publicKey?: Partial<CredentialCreationOptions["publicKey"]>;
12
21
  } & Pick<CredentialCreationOptions, "signal">;
@@ -14,6 +23,7 @@ export type CredentialCreationOptionOverrides = {
14
23
  // [!region User]
15
24
  export type User = {
16
25
  email?: string;
26
+ phone?: string;
17
27
  orgId: string;
18
28
  userId: string;
19
29
  address: Address;
@@ -30,6 +40,8 @@ export type ExportWalletParams = {
30
40
  iframeElementId?: string;
31
41
  };
32
42
 
43
+ export type ExportWalletOutput = boolean;
44
+
33
45
  export type CreateAccountParams =
34
46
  | {
35
47
  type: "email";
@@ -195,6 +207,26 @@ export type SignerEndpoints = [
195
207
  orgId: string | null;
196
208
  };
197
209
  },
210
+ {
211
+ Route: "/v1/init-otp";
212
+ Body: {
213
+ contact: string;
214
+ otpType: "OTP_TYPE_SMS" | "OTP_TYPE_EMAIL";
215
+ };
216
+ Response: {
217
+ otpId: string;
218
+ };
219
+ },
220
+ {
221
+ Route: "/v1/verify-otp";
222
+ Body: {
223
+ otpId: string;
224
+ otpCode: string;
225
+ };
226
+ Response: {
227
+ verificationToken: string;
228
+ };
229
+ },
198
230
  {
199
231
  Route: "/v1/sign-payload";
200
232
  Body: {
@@ -211,6 +243,13 @@ export type SignerEndpoints = [
211
243
  };
212
244
  Response: void;
213
245
  },
246
+ {
247
+ Route: "/v1/update-phone-auth";
248
+ Body: {
249
+ stampedRequest: TSignedRequest;
250
+ };
251
+ Response: void;
252
+ },
214
253
  {
215
254
  Route: "/v1/add-oauth-provider";
216
255
  Body: {
@@ -510,6 +549,7 @@ export type AddOauthProviderParams = {
510
549
 
511
550
  export type AuthMethods = {
512
551
  email?: string;
552
+ phone?: string;
513
553
  oauthProviders: OauthProviderInfo[];
514
554
  passkeys: PasskeyInfo[];
515
555
  };
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.60.1";
3
+ export const VERSION = "4.62.0";