@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.
- package/dist/esm/base.d.ts +59 -3
- package/dist/esm/base.js +77 -17
- package/dist/esm/base.js.map +1 -1
- package/dist/esm/client/base.d.ts +47 -23
- package/dist/esm/client/base.js +127 -110
- package/dist/esm/client/base.js.map +1 -1
- package/dist/esm/client/index.d.ts +18 -4
- package/dist/esm/client/index.js +83 -0
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +36 -0
- package/dist/esm/client/types.js.map +1 -1
- package/dist/esm/session/manager.d.ts +3 -3
- package/dist/esm/signer.d.ts +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/types/base.d.ts +59 -3
- package/dist/types/base.d.ts.map +1 -1
- package/dist/types/client/base.d.ts +47 -23
- package/dist/types/client/base.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +18 -4
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/client/types.d.ts +36 -0
- package/dist/types/client/types.d.ts.map +1 -1
- package/dist/types/session/manager.d.ts +3 -3
- package/dist/types/signer.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +6 -6
- package/src/base.ts +111 -16
- package/src/client/base.ts +159 -138
- package/src/client/index.ts +122 -3
- package/src/client/types.ts +40 -0
- package/src/version.ts +1 -1
package/src/client/base.ts
CHANGED
|
@@ -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<
|
|
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(
|
|
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,
|
|
314
|
-
* important security considerations. In particular, you must not
|
|
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
|
|
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
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
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
|
-
|
|
348
|
-
|
|
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
|
-
|
|
354
|
-
|
|
438
|
+
userPhoneNumber: phone,
|
|
439
|
+
verificationToken,
|
|
355
440
|
},
|
|
356
441
|
});
|
|
357
|
-
await this.request("/v1/update-
|
|
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(
|
|
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> = {
|
package/src/client/index.ts
CHANGED
|
@@ -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 {
|
|
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<
|
|
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
|
/**
|
package/src/client/types.ts
CHANGED
|
@@ -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