@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.
@@ -7,7 +7,6 @@ import { NotAuthenticatedError, OAuthProvidersError } from "../errors.js";
7
7
  import { getDefaultProviderCustomization } from "../oauth.js";
8
8
  import { base64UrlEncode } from "../utils/base64UrlEncode.js";
9
9
  import { resolveRelativeUrl } from "../utils/resolveRelativeUrl.js";
10
- import { assertNever } from "../utils/typeAssertions.js";
11
10
  import { VERSION } from "../version.js";
12
11
  import { secp256k1 } from "@noble/curves/secp256k1";
13
12
  import { Point } from "@noble/secp256k1";
@@ -98,66 +97,140 @@ export class BaseSignerClient {
98
97
  }
99
98
  });
100
99
  /**
101
- * Sets the email for the authenticated user, allowing them to login with that
102
- * email.
103
- *
104
- * You must contact Alchemy to enable this feature for your team, as there are
105
- * important security considerations. In particular, you must not call this
106
- * without first validating that the user owns this email account.
100
+ * Removes the email for the authenticated user, disallowing them from login with that email.
107
101
  *
108
- * @param {string} email The email to set for the user
109
- * @returns {Promise<void>} A promise that resolves when the email is set
102
+ * @returns {Promise<string>} A promise that resolves when the email is removed
110
103
  * @throws {NotAuthenticatedError} If the user is not authenticated
111
104
  */
112
- Object.defineProperty(this, "setEmail", {
105
+ Object.defineProperty(this, "removeEmail", {
113
106
  enumerable: true,
114
107
  configurable: true,
115
108
  writable: true,
116
- value: async (email) => {
117
- if (!email) {
118
- throw new Error("Email must not be empty. Use removeEmail() to remove email auth.");
109
+ value: async () => {
110
+ await this.updateEmail("");
111
+ }
112
+ });
113
+ Object.defineProperty(this, "updateEmail", {
114
+ enumerable: true,
115
+ configurable: true,
116
+ writable: true,
117
+ value: async (email, verificationToken) => {
118
+ if (!this.user) {
119
+ throw new NotAuthenticatedError();
119
120
  }
120
- await this.updateEmail(email);
121
+ // Unverified use is legacy & requires team flag.
122
+ const isUnverified = email && !verificationToken;
123
+ const stampedRequest = isUnverified
124
+ ? await this.turnkeyClient.stampUpdateUser({
125
+ type: "ACTIVITY_TYPE_UPDATE_USER",
126
+ timestampMs: Date.now().toString(),
127
+ organizationId: this.user.orgId,
128
+ parameters: {
129
+ userId: this.user.userId,
130
+ userEmail: email,
131
+ },
132
+ })
133
+ : await this.turnkeyClient.stampUpdateUserEmail({
134
+ type: "ACTIVITY_TYPE_UPDATE_USER_EMAIL",
135
+ timestampMs: Date.now().toString(),
136
+ organizationId: this.user.orgId,
137
+ parameters: {
138
+ userId: this.user.userId,
139
+ userEmail: email,
140
+ verificationToken,
141
+ },
142
+ });
143
+ await this.request("/v1/update-email-auth", {
144
+ stampedRequest,
145
+ });
146
+ this.user = {
147
+ ...this.user,
148
+ email: email || undefined,
149
+ };
121
150
  }
122
151
  });
123
152
  /**
124
- * Removes the email for the authenticated user, disallowing them from login with that email.
153
+ * Updates the phone number for the authenticated user, allowing them to login with that
154
+ * phone number. Must be called after calling `initOtp` with the phone number.
125
155
  *
126
- * @returns {Promise<void>} A promise that resolves when the email is removed
156
+ * @param {VerificationOtp} otp The OTP object including the OTP ID and OTP code
157
+ * @returns {Promise<void>} A promise that resolves when the phone number is set
127
158
  * @throws {NotAuthenticatedError} If the user is not authenticated
128
159
  */
129
- Object.defineProperty(this, "removeEmail", {
160
+ Object.defineProperty(this, "setPhoneNumber", {
161
+ enumerable: true,
162
+ configurable: true,
163
+ writable: true,
164
+ value: async (otp) => {
165
+ const { verificationToken } = await this.request("/v1/verify-otp", {
166
+ otpId: otp.id,
167
+ otpCode: otp.code,
168
+ });
169
+ const { contact } = jwtDecode(verificationToken);
170
+ await this.updatePhoneNumber(contact, verificationToken);
171
+ }
172
+ });
173
+ /**
174
+ * Removes the phone number for the authenticated user, disallowing them from login with that phone number.
175
+ *
176
+ * @returns {Promise<void>} A promise that resolves when the phone number is removed
177
+ * @throws {NotAuthenticatedError} If the user is not authenticated
178
+ */
179
+ Object.defineProperty(this, "removePhoneNumber", {
130
180
  enumerable: true,
131
181
  configurable: true,
132
182
  writable: true,
133
183
  value: async () => {
134
- // This is a hack to remove the email for the user. Turnkey does not
135
- // support clearing the email once set, so we set it to a known
136
- // inaccessible address instead.
137
- await this.updateEmail("not.enabled@example.invalid");
184
+ await this.updatePhoneNumber("");
138
185
  }
139
186
  });
140
- Object.defineProperty(this, "updateEmail", {
187
+ Object.defineProperty(this, "updatePhoneNumber", {
141
188
  enumerable: true,
142
189
  configurable: true,
143
190
  writable: true,
144
- value: async (email) => {
191
+ value: async (phone, verificationToken) => {
145
192
  if (!this.user) {
146
193
  throw new NotAuthenticatedError();
147
194
  }
148
- const stampedRequest = await this.turnkeyClient.stampUpdateUser({
149
- type: "ACTIVITY_TYPE_UPDATE_USER",
195
+ if (phone.trim() && !verificationToken) {
196
+ throw new Error("Verification token is required to change phone number.");
197
+ }
198
+ const stampedRequest = await this.turnkeyClient.stampUpdateUserPhoneNumber({
199
+ type: "ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER",
150
200
  timestampMs: Date.now().toString(),
151
201
  organizationId: this.user.orgId,
152
202
  parameters: {
153
203
  userId: this.user.userId,
154
- userEmail: email,
155
- userTagIds: [],
204
+ userPhoneNumber: phone,
205
+ verificationToken,
156
206
  },
157
207
  });
158
- await this.request("/v1/update-email-auth", {
208
+ await this.request("/v1/update-phone-auth", {
159
209
  stampedRequest,
160
210
  });
211
+ this.user = {
212
+ ...this.user,
213
+ phone: phone || undefined,
214
+ };
215
+ }
216
+ });
217
+ /**
218
+ * Initiates an OTP (One-Time Password) verification process for a user contact.
219
+ *
220
+ * @param {("email" | "sms")} type - The type of OTP to send, either "email" or "sms"
221
+ * @param {string} contact - The email address or phone number to send the OTP to
222
+ * @returns {Promise<{ otpId: string }>} A promise that resolves to an object containing the OTP ID
223
+ * @throws {NotAuthenticatedError} When no user is currently authenticated
224
+ */
225
+ Object.defineProperty(this, "initOtp", {
226
+ enumerable: true,
227
+ configurable: true,
228
+ writable: true,
229
+ value: async (type, contact) => {
230
+ return await this.request("/v1/init-otp", {
231
+ otpType: type === "email" ? "OTP_TYPE_EMAIL" : "OTP_TYPE_SMS",
232
+ contact,
233
+ });
161
234
  }
162
235
  });
163
236
  /**
@@ -587,7 +660,7 @@ export class BaseSignerClient {
587
660
  };
588
661
  return {
589
662
  stampHeaderName: "X-Stamp",
590
- stampHeaderValue: base64UrlEncode(Buffer.from(JSON.stringify(stamp))),
663
+ stampHeaderValue: base64UrlEncode(Buffer.from(JSON.stringify(stamp)).buffer),
591
664
  };
592
665
  },
593
666
  })
@@ -923,67 +996,6 @@ export class BaseSignerClient {
923
996
  });
924
997
  // #endregion
925
998
  // #region PRIVATE METHODS
926
- Object.defineProperty(this, "exportAsSeedPhrase", {
927
- enumerable: true,
928
- configurable: true,
929
- writable: true,
930
- value: async (stamper) => {
931
- if (!this.user) {
932
- throw new NotAuthenticatedError();
933
- }
934
- const { wallets } = await this.turnkeyClient.getWallets({
935
- organizationId: this.user.orgId,
936
- });
937
- const walletAccounts = await Promise.all(wallets.map(({ walletId }) => this.turnkeyClient.getWalletAccounts({
938
- organizationId: this.user.orgId,
939
- walletId,
940
- }))).then((x) => x.flatMap((x) => x.accounts));
941
- const walletAccount = walletAccounts.find((x) => x.address === this.user.address);
942
- if (!walletAccount) {
943
- throw new Error(`Could not find wallet associated with ${this.user.address}`);
944
- }
945
- const { activity } = await this.turnkeyClient.exportWallet({
946
- organizationId: this.user.orgId,
947
- type: "ACTIVITY_TYPE_EXPORT_WALLET",
948
- timestampMs: Date.now().toString(),
949
- parameters: {
950
- walletId: walletAccount.walletId,
951
- targetPublicKey: stamper.publicKey(),
952
- },
953
- });
954
- const { exportBundle } = await this.pollActivityCompletion(activity, this.user.orgId, "exportWalletResult");
955
- const result = await stamper.injectWalletExportBundle(exportBundle, this.user.orgId);
956
- if (!result) {
957
- throw new Error("Failed to inject wallet export bundle");
958
- }
959
- return result;
960
- }
961
- });
962
- Object.defineProperty(this, "exportAsPrivateKey", {
963
- enumerable: true,
964
- configurable: true,
965
- writable: true,
966
- value: async (stamper) => {
967
- if (!this.user) {
968
- throw new NotAuthenticatedError();
969
- }
970
- const { activity } = await this.turnkeyClient.exportWalletAccount({
971
- organizationId: this.user.orgId,
972
- type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT",
973
- timestampMs: Date.now().toString(),
974
- parameters: {
975
- address: this.user.address,
976
- targetPublicKey: stamper.publicKey(),
977
- },
978
- });
979
- const { exportBundle } = await this.pollActivityCompletion(activity, this.user.orgId, "exportWalletAccountResult");
980
- const result = await stamper.injectKeyExportBundle(exportBundle, this.user.orgId);
981
- if (!result) {
982
- throw new Error("Failed to inject wallet export bundle");
983
- }
984
- return result;
985
- }
986
- });
987
999
  /**
988
1000
  * Returns the authentication url for the selected OAuth Proivder
989
1001
  *
@@ -1061,7 +1073,7 @@ export class BaseSignerClient {
1061
1073
  openerOrigin: mode === "popup" ? window.location.origin : undefined,
1062
1074
  fetchIdTokenOnly: oauthParams.fetchIdTokenOnly,
1063
1075
  };
1064
- const state = base64UrlEncode(new TextEncoder().encode(JSON.stringify(stateObject)));
1076
+ const state = base64UrlEncode(new TextEncoder().encode(JSON.stringify(stateObject)).buffer);
1065
1077
  const authUrl = new URL(authEndpoint);
1066
1078
  const params = {
1067
1079
  redirect_uri: oauthCallbackUrl,
@@ -1172,25 +1184,6 @@ export class BaseSignerClient {
1172
1184
  setStamper(stamper) {
1173
1185
  this.turnkeyClient.stamper = stamper;
1174
1186
  }
1175
- /**
1176
- * Exports wallet credentials based on the specified type, either as a SEED_PHRASE or PRIVATE_KEY.
1177
- *
1178
- * @param {object} params The parameters for exporting the wallet
1179
- * @param {ExportWalletStamper} params.exportStamper The stamper used for exporting the wallet
1180
- * @param {"SEED_PHRASE" | "PRIVATE_KEY"} params.exportAs Specifies the format for exporting the wallet, either as a SEED_PHRASE or PRIVATE_KEY
1181
- * @returns {Promise<boolean>} A promise that resolves to true if the export is successful
1182
- */
1183
- exportWalletInner(params) {
1184
- const { exportAs } = params;
1185
- switch (exportAs) {
1186
- case "PRIVATE_KEY":
1187
- return this.exportAsPrivateKey(params.exportStamper);
1188
- case "SEED_PHRASE":
1189
- return this.exportAsSeedPhrase(params.exportStamper);
1190
- default:
1191
- assertNever(exportAs, `Unknown export mode: ${exportAs}`);
1192
- }
1193
- }
1194
1187
  /**
1195
1188
  * Authenticates the user by either email or passkey account creation flow. Emits events during the process.
1196
1189
  *
@@ -1244,5 +1237,29 @@ export class BaseSignerClient {
1244
1237
  this.eventEmitter.emit("connectedPasskey", this.user);
1245
1238
  return result;
1246
1239
  }
1240
+ /**
1241
+ * Implementation for setEmail method with optional OTP verification.
1242
+ *
1243
+ * @param {string | VerificationOtp} params An OTP object containing the OTP ID & OTP code (or an email address for legacy usage)
1244
+ * @returns {Promise<void>} A promise that resolves to the updated email address
1245
+ */
1246
+ async setEmail(params) {
1247
+ if (typeof params === "string") {
1248
+ // Legacy use, requires team flag.
1249
+ const contact = params;
1250
+ if (!contact) {
1251
+ throw new Error("Email must not be empty. Use removeEmail() to remove email auth.");
1252
+ }
1253
+ await this.updateEmail(contact);
1254
+ return contact;
1255
+ }
1256
+ const { verificationToken } = await this.request("/v1/verify-otp", {
1257
+ otpId: params.id,
1258
+ otpCode: params.code,
1259
+ });
1260
+ const { contact } = jwtDecode(verificationToken);
1261
+ await this.updateEmail(contact, verificationToken);
1262
+ return contact;
1263
+ }
1247
1264
  }
1248
1265
  //# sourceMappingURL=base.js.map