@account-kit/signer 4.0.0-beta.0 → 4.0.0-beta.10
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 +43 -8
- package/dist/esm/base.js +186 -29
- package/dist/esm/base.js.map +1 -1
- package/dist/esm/client/base.d.ts +22 -4
- package/dist/esm/client/base.js +36 -2
- package/dist/esm/client/base.js.map +1 -1
- package/dist/esm/client/index.d.ts +108 -7
- package/dist/esm/client/index.js +282 -14
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/types.d.ts +31 -1
- package/dist/esm/client/types.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/metrics.d.ts +17 -0
- package/dist/esm/metrics.js +7 -0
- package/dist/esm/metrics.js.map +1 -0
- package/dist/esm/oauth.d.ts +19 -0
- package/dist/esm/oauth.js +26 -0
- package/dist/esm/oauth.js.map +1 -0
- package/dist/esm/session/manager.d.ts +3 -2
- package/dist/esm/session/manager.js +29 -15
- package/dist/esm/session/manager.js.map +1 -1
- package/dist/esm/session/types.d.ts +1 -1
- package/dist/esm/session/types.js.map +1 -1
- package/dist/esm/signer.d.ts +52 -7
- package/dist/esm/signer.js +46 -3
- package/dist/esm/signer.js.map +1 -1
- package/dist/esm/types.d.ts +8 -1
- package/dist/esm/types.js +3 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/utils/typeAssertions.d.ts +1 -0
- package/dist/esm/utils/typeAssertions.js +4 -0
- package/dist/esm/utils/typeAssertions.js.map +1 -0
- 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 +43 -8
- package/dist/types/base.d.ts.map +1 -1
- package/dist/types/client/base.d.ts +22 -4
- package/dist/types/client/base.d.ts.map +1 -1
- package/dist/types/client/index.d.ts +108 -7
- package/dist/types/client/index.d.ts.map +1 -1
- package/dist/types/client/types.d.ts +31 -1
- package/dist/types/client/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/metrics.d.ts +18 -0
- package/dist/types/metrics.d.ts.map +1 -0
- package/dist/types/oauth.d.ts +20 -0
- package/dist/types/oauth.d.ts.map +1 -0
- package/dist/types/session/manager.d.ts +3 -2
- package/dist/types/session/manager.d.ts.map +1 -1
- package/dist/types/session/types.d.ts +1 -1
- package/dist/types/session/types.d.ts.map +1 -1
- package/dist/types/signer.d.ts +52 -7
- package/dist/types/signer.d.ts.map +1 -1
- package/dist/types/types.d.ts +8 -1
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/typeAssertions.d.ts +2 -0
- package/dist/types/utils/typeAssertions.d.ts.map +1 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types/version.d.ts.map +1 -1
- package/package.json +7 -10
- package/src/base.ts +261 -66
- package/src/client/base.ts +49 -4
- package/src/client/index.ts +317 -20
- package/src/client/types.ts +33 -1
- package/src/index.ts +5 -1
- package/src/metrics.ts +23 -0
- package/src/oauth.ts +36 -0
- package/src/session/manager.ts +46 -19
- package/src/session/types.ts +1 -1
- package/src/signer.ts +91 -4
- package/src/types.ts +9 -1
- package/src/utils/typeAssertions.ts +3 -0
- package/src/version.ts +1 -1
package/src/base.ts
CHANGED
|
@@ -4,10 +4,14 @@ import {
|
|
|
4
4
|
hashTypedData,
|
|
5
5
|
keccak256,
|
|
6
6
|
serializeTransaction,
|
|
7
|
-
type
|
|
7
|
+
type GetTransactionType,
|
|
8
8
|
type Hex,
|
|
9
|
+
type IsNarrowable,
|
|
9
10
|
type LocalAccount,
|
|
11
|
+
type SerializeTransactionFn,
|
|
10
12
|
type SignableMessage,
|
|
13
|
+
type TransactionSerializable,
|
|
14
|
+
type TransactionSerialized,
|
|
11
15
|
type TypedData,
|
|
12
16
|
type TypedDataDefinition,
|
|
13
17
|
} from "viem";
|
|
@@ -16,8 +20,9 @@ import type { Mutate, StoreApi } from "zustand";
|
|
|
16
20
|
import { subscribeWithSelector } from "zustand/middleware";
|
|
17
21
|
import { createStore } from "zustand/vanilla";
|
|
18
22
|
import type { BaseSignerClient } from "./client/base";
|
|
19
|
-
import type { User } from "./client/types";
|
|
23
|
+
import type { OauthConfig, OauthParams, User } from "./client/types";
|
|
20
24
|
import { NotAuthenticatedError } from "./errors.js";
|
|
25
|
+
import { SignerLogger } from "./metrics.js";
|
|
21
26
|
import {
|
|
22
27
|
SessionManager,
|
|
23
28
|
type SessionManagerParams,
|
|
@@ -27,16 +32,20 @@ import {
|
|
|
27
32
|
AlchemySignerStatus,
|
|
28
33
|
type AlchemySignerEvent,
|
|
29
34
|
type AlchemySignerEvents,
|
|
35
|
+
type ErrorInfo,
|
|
30
36
|
} from "./types.js";
|
|
37
|
+
import { assertNever } from "./utils/typeAssertions.js";
|
|
31
38
|
|
|
32
39
|
export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
|
|
33
40
|
client: TClient;
|
|
34
41
|
sessionConfig?: Omit<SessionManagerParams, "client">;
|
|
42
|
+
initialError?: ErrorInfo;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
type AlchemySignerStore = {
|
|
38
46
|
user: User | null;
|
|
39
47
|
status: AlchemySignerStatus;
|
|
48
|
+
error: ErrorInfo | null;
|
|
40
49
|
};
|
|
41
50
|
|
|
42
51
|
type InternalStore = Mutate<
|
|
@@ -64,8 +73,13 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
64
73
|
* @param {BaseAlchemySignerParams<TClient>} param0 Object containing the client and session configuration
|
|
65
74
|
* @param {TClient} param0.client The client instance to be used internally
|
|
66
75
|
* @param {SessionConfig} param0.sessionConfig Configuration for managing sessions
|
|
76
|
+
* @param {ErrorInfo | undefined} param0.initialError Error already present on the signer when initialized, if any
|
|
67
77
|
*/
|
|
68
|
-
constructor({
|
|
78
|
+
constructor({
|
|
79
|
+
client,
|
|
80
|
+
sessionConfig,
|
|
81
|
+
initialError,
|
|
82
|
+
}: BaseAlchemySignerParams<TClient>) {
|
|
69
83
|
this.inner = client;
|
|
70
84
|
this.store = createStore(
|
|
71
85
|
subscribeWithSelector(
|
|
@@ -73,6 +87,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
73
87
|
({
|
|
74
88
|
user: null,
|
|
75
89
|
status: AlchemySignerStatus.INITIALIZING,
|
|
90
|
+
error: initialError ?? null,
|
|
76
91
|
} satisfies AlchemySignerStore)
|
|
77
92
|
)
|
|
78
93
|
);
|
|
@@ -83,15 +98,6 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
83
98
|
...sessionConfig,
|
|
84
99
|
client: this.inner,
|
|
85
100
|
});
|
|
86
|
-
this.store = createStore(
|
|
87
|
-
subscribeWithSelector(
|
|
88
|
-
() =>
|
|
89
|
-
({
|
|
90
|
-
user: null,
|
|
91
|
-
status: AlchemySignerStatus.INITIALIZING,
|
|
92
|
-
} satisfies AlchemySignerStore)
|
|
93
|
-
)
|
|
94
|
-
);
|
|
95
101
|
// register listeners first
|
|
96
102
|
this.registerListeners();
|
|
97
103
|
// then initialize so that we can catch those events
|
|
@@ -137,11 +143,51 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
137
143
|
listener as AlchemySignerEvents["statusChanged"],
|
|
138
144
|
{ fireImmediately: true }
|
|
139
145
|
);
|
|
146
|
+
case "errorChanged":
|
|
147
|
+
return this.store.subscribe(
|
|
148
|
+
({ error }) => error,
|
|
149
|
+
(error) =>
|
|
150
|
+
(listener as AlchemySignerEvents["errorChanged"])(
|
|
151
|
+
error ?? undefined
|
|
152
|
+
),
|
|
153
|
+
{ fireImmediately: true }
|
|
154
|
+
);
|
|
140
155
|
default:
|
|
141
|
-
|
|
156
|
+
assertNever(event, `Unknown event type ${event}`);
|
|
142
157
|
}
|
|
143
158
|
};
|
|
144
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Prepares the config needed to use popup-based OAuth login. This must be
|
|
162
|
+
* called before calling `.authenticate` with params `{ type: "oauth", mode:
|
|
163
|
+
* "popup" }`, and is recommended to be called on page load.
|
|
164
|
+
*
|
|
165
|
+
* This method exists because browsers may prevent popups from opening unless
|
|
166
|
+
* triggered by user interaction, and so the OAuth config must already have
|
|
167
|
+
* been fetched at the time a user clicks a social login button.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* import { AlchemyWebSigner } from "@account-kit/signer";
|
|
172
|
+
*
|
|
173
|
+
* const signer = new AlchemyWebSigner({
|
|
174
|
+
* client: {
|
|
175
|
+
* connection: {
|
|
176
|
+
* rpcUrl: "/api/rpc",
|
|
177
|
+
* },
|
|
178
|
+
* iframeConfig: {
|
|
179
|
+
* iframeContainerId: "alchemy-signer-iframe-container",
|
|
180
|
+
* },
|
|
181
|
+
* },
|
|
182
|
+
* });
|
|
183
|
+
*
|
|
184
|
+
* await signer.preparePopupOauth();
|
|
185
|
+
* ```
|
|
186
|
+
* @returns {Promise<OauthConfig>} the config which must be loaded before
|
|
187
|
+
* using popup-based OAuth
|
|
188
|
+
*/
|
|
189
|
+
preparePopupOauth = (): Promise<OauthConfig> => this.inner.initOauth();
|
|
190
|
+
|
|
145
191
|
/**
|
|
146
192
|
* Authenticate a user with either an email or a passkey and create a session for that user
|
|
147
193
|
*
|
|
@@ -169,12 +215,70 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
169
215
|
* @param {AuthParams} params - undefined if passkey login, otherwise an object with email and bundle to resolve
|
|
170
216
|
* @returns {Promise<User>} the user that was authenticated
|
|
171
217
|
*/
|
|
172
|
-
authenticate: (params: AuthParams) => Promise<User> =
|
|
173
|
-
|
|
174
|
-
|
|
218
|
+
authenticate: (params: AuthParams) => Promise<User> = SignerLogger.profiled(
|
|
219
|
+
"BaseAlchemySigner.authenticate",
|
|
220
|
+
async (params) => {
|
|
221
|
+
const { type } = params;
|
|
222
|
+
const result = (() => {
|
|
223
|
+
switch (type) {
|
|
224
|
+
case "email":
|
|
225
|
+
return this.authenticateWithEmail(params);
|
|
226
|
+
case "passkey":
|
|
227
|
+
return this.authenticateWithPasskey(params);
|
|
228
|
+
case "oauth":
|
|
229
|
+
return this.authenticateWithOauth(params);
|
|
230
|
+
case "oauthReturn":
|
|
231
|
+
return this.handleOauthReturn(params);
|
|
232
|
+
default:
|
|
233
|
+
assertNever(type, `Unknown auth type: ${type}`);
|
|
234
|
+
}
|
|
235
|
+
})();
|
|
236
|
+
|
|
237
|
+
this.trackAuthenticateType(params);
|
|
238
|
+
|
|
239
|
+
return result.catch((error) => {
|
|
240
|
+
this.store.setState({ error: toErrorInfo(error) });
|
|
241
|
+
throw error;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
private trackAuthenticateType = (params: AuthParams) => {
|
|
247
|
+
const { type } = params;
|
|
248
|
+
switch (type) {
|
|
249
|
+
case "email": {
|
|
250
|
+
// we just want to track the start of email auth
|
|
251
|
+
if ("bundle" in params) return;
|
|
252
|
+
SignerLogger.trackEvent({
|
|
253
|
+
name: "signer_authnticate",
|
|
254
|
+
data: { authType: "email" },
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
case "passkey": {
|
|
259
|
+
const isAnon = !("email" in params) && params.createNew == null;
|
|
260
|
+
SignerLogger.trackEvent({
|
|
261
|
+
name: "signer_authnticate",
|
|
262
|
+
data: {
|
|
263
|
+
authType: isAnon ? "passkey_anon" : "passkey_email",
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
case "oauth":
|
|
269
|
+
SignerLogger.trackEvent({
|
|
270
|
+
name: "signer_authnticate",
|
|
271
|
+
data: {
|
|
272
|
+
authType: "oauth",
|
|
273
|
+
provider: params.authProviderId,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
break;
|
|
277
|
+
case "oauthReturn":
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
assertNever(type, `Unknown auth type: ${type}`);
|
|
175
281
|
}
|
|
176
|
-
|
|
177
|
-
return this.authenticateWithPasskey(params);
|
|
178
282
|
};
|
|
179
283
|
|
|
180
284
|
/**
|
|
@@ -245,11 +349,14 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
245
349
|
*
|
|
246
350
|
* @returns {Promise<string>} A promise that resolves to the address of the current user.
|
|
247
351
|
*/
|
|
248
|
-
getAddress: () => Promise<`0x${string}`> =
|
|
249
|
-
|
|
352
|
+
getAddress: () => Promise<`0x${string}`> = SignerLogger.profiled(
|
|
353
|
+
"BaseAlchemySigner.getAddress",
|
|
354
|
+
async () => {
|
|
355
|
+
const { address } = await this.inner.whoami();
|
|
250
356
|
|
|
251
|
-
|
|
252
|
-
|
|
357
|
+
return address;
|
|
358
|
+
}
|
|
359
|
+
);
|
|
253
360
|
|
|
254
361
|
/**
|
|
255
362
|
* Signs a raw message after hashing it.
|
|
@@ -275,13 +382,18 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
275
382
|
* @param {string} msg the message to be hashed and then signed
|
|
276
383
|
* @returns {Promise<string>} a promise that resolves to the signed message
|
|
277
384
|
*/
|
|
278
|
-
signMessage: (msg: SignableMessage) => Promise<`0x${string}`> =
|
|
279
|
-
msg
|
|
280
|
-
|
|
281
|
-
const messageHash = hashMessage(msg);
|
|
385
|
+
signMessage: (msg: SignableMessage) => Promise<`0x${string}`> =
|
|
386
|
+
SignerLogger.profiled("BaseAlchemySigner.signMessage", async (msg) => {
|
|
387
|
+
const messageHash = hashMessage(msg);
|
|
282
388
|
|
|
283
|
-
|
|
284
|
-
|
|
389
|
+
const result = await this.inner.signRawMessage(messageHash);
|
|
390
|
+
|
|
391
|
+
SignerLogger.trackEvent({
|
|
392
|
+
name: "signer_sign_message",
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return result;
|
|
396
|
+
});
|
|
285
397
|
|
|
286
398
|
/**
|
|
287
399
|
* Signs a typed message by first hashing it and then signing the hashed message using the `signRawMessage` method.
|
|
@@ -313,15 +425,18 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
313
425
|
* @returns {Promise<any>} A promise that resolves to the signed message
|
|
314
426
|
*/
|
|
315
427
|
signTypedData: <
|
|
316
|
-
const TTypedData extends TypedData |
|
|
428
|
+
const TTypedData extends TypedData | Record<string, unknown>,
|
|
317
429
|
TPrimaryType extends keyof TTypedData | "EIP712Domain" = keyof TTypedData
|
|
318
430
|
>(
|
|
319
431
|
params: TypedDataDefinition<TTypedData, TPrimaryType>
|
|
320
|
-
) => Promise<Hex> =
|
|
321
|
-
|
|
432
|
+
) => Promise<Hex> = SignerLogger.profiled(
|
|
433
|
+
"BaseAlchemySigner.signTypedData",
|
|
434
|
+
async (params) => {
|
|
435
|
+
const messageHash = hashTypedData(params);
|
|
322
436
|
|
|
323
|
-
|
|
324
|
-
|
|
437
|
+
return this.inner.signRawMessage(messageHash);
|
|
438
|
+
}
|
|
439
|
+
);
|
|
325
440
|
|
|
326
441
|
/**
|
|
327
442
|
* Serializes a transaction, signs it with a raw message, and then returns the serialized transaction with the signature.
|
|
@@ -353,21 +468,41 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
353
468
|
* @param {() => Hex} [args.serializer] an optional serializer function. If not provided, the default `serializeTransaction` function will be used
|
|
354
469
|
* @returns {Promise<string>} a promise that resolves to the serialized transaction with the signature
|
|
355
470
|
*/
|
|
356
|
-
signTransaction:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
471
|
+
signTransaction: <
|
|
472
|
+
serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>,
|
|
473
|
+
transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]
|
|
474
|
+
>(
|
|
475
|
+
transaction: transaction,
|
|
476
|
+
options?:
|
|
477
|
+
| {
|
|
478
|
+
serializer?: serializer | undefined;
|
|
479
|
+
}
|
|
480
|
+
| undefined
|
|
481
|
+
) => Promise<
|
|
482
|
+
IsNarrowable<
|
|
483
|
+
TransactionSerialized<GetTransactionType<transaction>>,
|
|
484
|
+
Hex
|
|
485
|
+
> extends true
|
|
486
|
+
? TransactionSerialized<GetTransactionType<transaction>>
|
|
487
|
+
: Hex
|
|
488
|
+
> = SignerLogger.profiled(
|
|
489
|
+
"BaseAlchemySigner.signTransaction",
|
|
490
|
+
async (tx, args) => {
|
|
491
|
+
const serializeFn = args?.serializer ?? serializeTransaction;
|
|
492
|
+
const serializedTx = serializeFn(tx);
|
|
493
|
+
const signatureHex = await this.inner.signRawMessage(
|
|
494
|
+
keccak256(serializedTx)
|
|
495
|
+
);
|
|
362
496
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
497
|
+
const signature = {
|
|
498
|
+
r: takeBytes(signatureHex, { count: 32 }),
|
|
499
|
+
s: takeBytes(signatureHex, { count: 32, offset: 32 }),
|
|
500
|
+
v: BigInt(takeBytes(signatureHex, { count: 1, offset: 64 })),
|
|
501
|
+
};
|
|
368
502
|
|
|
369
|
-
|
|
370
|
-
|
|
503
|
+
return serializeFn(tx, signature);
|
|
504
|
+
}
|
|
505
|
+
);
|
|
371
506
|
|
|
372
507
|
/**
|
|
373
508
|
* Unauthenticated call to look up a user's organizationId by email
|
|
@@ -393,19 +528,18 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
393
528
|
* @param {string} email the email to lookup
|
|
394
529
|
* @returns {Promise<{orgId: string}>} the organization id for the user if they exist
|
|
395
530
|
*/
|
|
396
|
-
getUser: (email: string) => Promise<{ orgId: string } | null> =
|
|
397
|
-
email
|
|
398
|
-
|
|
399
|
-
const result = await this.inner.lookupUserByEmail(email);
|
|
531
|
+
getUser: (email: string) => Promise<{ orgId: string } | null> =
|
|
532
|
+
SignerLogger.profiled("BaseAlchemySigner.getUser", async (email) => {
|
|
533
|
+
const result = await this.inner.lookupUserByEmail(email);
|
|
400
534
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
535
|
+
if (result.orgId == null) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
404
538
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
539
|
+
return {
|
|
540
|
+
orgId: result.orgId,
|
|
541
|
+
};
|
|
542
|
+
});
|
|
409
543
|
|
|
410
544
|
/**
|
|
411
545
|
* Adds a passkey to the user's account
|
|
@@ -432,9 +566,9 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
432
566
|
* @returns {Promise<string[]>} an array of the authenticator ids added to the user
|
|
433
567
|
*/
|
|
434
568
|
addPasskey: (params?: CredentialCreationOptions) => Promise<string[]> =
|
|
435
|
-
async (params) => {
|
|
569
|
+
SignerLogger.profiled("BaseAlchemySigner.addPasskey", async (params) => {
|
|
436
570
|
return this.inner.addPasskey(params ?? {});
|
|
437
|
-
};
|
|
571
|
+
});
|
|
438
572
|
|
|
439
573
|
/**
|
|
440
574
|
* Used to export the wallet for a given user
|
|
@@ -519,22 +653,28 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
519
653
|
): Promise<User> => {
|
|
520
654
|
if ("email" in params) {
|
|
521
655
|
const existingUser = await this.getUser(params.email);
|
|
656
|
+
const expirationSeconds = Math.floor(
|
|
657
|
+
this.sessionManager.expirationTimeMs / 1000
|
|
658
|
+
);
|
|
522
659
|
|
|
523
660
|
const { orgId } = existingUser
|
|
524
661
|
? await this.inner.initEmailAuth({
|
|
525
662
|
email: params.email,
|
|
526
|
-
expirationSeconds
|
|
663
|
+
expirationSeconds,
|
|
527
664
|
redirectParams: params.redirectParams,
|
|
528
665
|
})
|
|
529
666
|
: await this.inner.createAccount({
|
|
530
667
|
type: "email",
|
|
531
668
|
email: params.email,
|
|
532
|
-
expirationSeconds
|
|
669
|
+
expirationSeconds,
|
|
533
670
|
redirectParams: params.redirectParams,
|
|
534
671
|
});
|
|
535
672
|
|
|
536
673
|
this.sessionManager.setTemporarySession({ orgId });
|
|
537
|
-
this.store.setState({
|
|
674
|
+
this.store.setState({
|
|
675
|
+
status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
|
|
676
|
+
error: null,
|
|
677
|
+
});
|
|
538
678
|
|
|
539
679
|
// We wait for the session manager to emit a connected event if
|
|
540
680
|
// cross tab sessions are permitted
|
|
@@ -557,9 +697,11 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
557
697
|
throw new Error("Could not find email auth init session!");
|
|
558
698
|
}
|
|
559
699
|
|
|
560
|
-
const user = await this.inner.
|
|
700
|
+
const user = await this.inner.completeAuthWithBundle({
|
|
561
701
|
bundle: params.bundle,
|
|
562
702
|
orgId: temporarySession.orgId,
|
|
703
|
+
connectedEventName: "connectedEmail",
|
|
704
|
+
authenticatingType: "email",
|
|
563
705
|
});
|
|
564
706
|
|
|
565
707
|
return user;
|
|
@@ -568,7 +710,7 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
568
710
|
|
|
569
711
|
private authenticateWithPasskey = async (
|
|
570
712
|
args: Extract<AuthParams, { type: "passkey" }>
|
|
571
|
-
) => {
|
|
713
|
+
): Promise<User> => {
|
|
572
714
|
let user: User;
|
|
573
715
|
const shouldCreateNew = async () => {
|
|
574
716
|
if ("email" in args) {
|
|
@@ -604,11 +746,41 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
604
746
|
return user;
|
|
605
747
|
};
|
|
606
748
|
|
|
749
|
+
private authenticateWithOauth = async (
|
|
750
|
+
args: Extract<AuthParams, { type: "oauth" }>
|
|
751
|
+
): Promise<User> => {
|
|
752
|
+
const params: OauthParams = {
|
|
753
|
+
...args,
|
|
754
|
+
expirationSeconds: Math.floor(
|
|
755
|
+
this.sessionManager.expirationTimeMs / 1000
|
|
756
|
+
),
|
|
757
|
+
};
|
|
758
|
+
if (params.mode === "redirect") {
|
|
759
|
+
return this.inner.oauthWithRedirect(params);
|
|
760
|
+
} else {
|
|
761
|
+
return this.inner.oauthWithPopup(params);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
private handleOauthReturn = ({
|
|
766
|
+
bundle,
|
|
767
|
+
orgId,
|
|
768
|
+
idToken,
|
|
769
|
+
}: Extract<AuthParams, { type: "oauthReturn" }>): Promise<User> =>
|
|
770
|
+
this.inner.completeAuthWithBundle({
|
|
771
|
+
bundle,
|
|
772
|
+
orgId,
|
|
773
|
+
connectedEventName: "connectedOauth",
|
|
774
|
+
authenticatingType: "oauth",
|
|
775
|
+
idToken,
|
|
776
|
+
});
|
|
777
|
+
|
|
607
778
|
private registerListeners = () => {
|
|
608
779
|
this.sessionManager.on("connected", (session) => {
|
|
609
780
|
this.store.setState({
|
|
610
781
|
user: session.user,
|
|
611
782
|
status: AlchemySignerStatus.CONNECTED,
|
|
783
|
+
error: null,
|
|
612
784
|
});
|
|
613
785
|
});
|
|
614
786
|
|
|
@@ -624,11 +796,34 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
|
|
|
624
796
|
status: state.user
|
|
625
797
|
? AlchemySignerStatus.CONNECTED
|
|
626
798
|
: AlchemySignerStatus.DISCONNECTED,
|
|
799
|
+
...(state.user ? { error: null } : undefined),
|
|
627
800
|
}));
|
|
628
801
|
});
|
|
629
802
|
|
|
630
|
-
this.inner.on("authenticating", () => {
|
|
631
|
-
|
|
803
|
+
this.inner.on("authenticating", ({ type }) => {
|
|
804
|
+
const status = (() => {
|
|
805
|
+
switch (type) {
|
|
806
|
+
case "email":
|
|
807
|
+
return AlchemySignerStatus.AUTHENTICATING_EMAIL;
|
|
808
|
+
case "passkey":
|
|
809
|
+
return AlchemySignerStatus.AUTHENTICATING_PASSKEY;
|
|
810
|
+
case "oauth":
|
|
811
|
+
return AlchemySignerStatus.AUTHENTICATING_OAUTH;
|
|
812
|
+
default:
|
|
813
|
+
assertNever(type, "unhandled authenticating type");
|
|
814
|
+
}
|
|
815
|
+
})();
|
|
816
|
+
|
|
817
|
+
this.store.setState({
|
|
818
|
+
status,
|
|
819
|
+
error: null,
|
|
820
|
+
});
|
|
632
821
|
});
|
|
633
822
|
};
|
|
634
823
|
}
|
|
824
|
+
|
|
825
|
+
function toErrorInfo(error: unknown): ErrorInfo {
|
|
826
|
+
return error instanceof Error
|
|
827
|
+
? { name: error.name, message: error.message }
|
|
828
|
+
: { name: "Error", message: "Unknown error" };
|
|
829
|
+
}
|
package/src/client/base.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { ConnectionConfigSchema, type ConnectionConfig } from "@aa-sdk/core";
|
|
2
2
|
import { TurnkeyClient, type TSignedRequest } from "@turnkey/http";
|
|
3
3
|
import EventEmitter from "eventemitter3";
|
|
4
|
+
import { jwtDecode } from "jwt-decode";
|
|
4
5
|
import type { Hex } from "viem";
|
|
5
6
|
import { NotAuthenticatedError } from "../errors.js";
|
|
6
7
|
import { base64UrlEncode } from "../utils/base64UrlEncode.js";
|
|
8
|
+
import { assertNever } from "../utils/typeAssertions.js";
|
|
7
9
|
import type {
|
|
8
10
|
AlchemySignerClientEvent,
|
|
9
11
|
AlchemySignerClientEvents,
|
|
12
|
+
AuthenticatingEventMetadata,
|
|
10
13
|
CreateAccountParams,
|
|
11
14
|
EmailAuthParams,
|
|
12
15
|
GetWebAuthnAttestationResult,
|
|
16
|
+
OauthConfig,
|
|
17
|
+
OauthParams,
|
|
13
18
|
SignerBody,
|
|
14
19
|
SignerResponse,
|
|
15
20
|
SignerRoutes,
|
|
@@ -39,6 +44,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
39
44
|
protected turnkeyClient: TurnkeyClient;
|
|
40
45
|
protected rootOrg: string;
|
|
41
46
|
protected eventEmitter: EventEmitter<AlchemySignerClientEvents>;
|
|
47
|
+
protected oauthConfig: OauthConfig | undefined;
|
|
42
48
|
|
|
43
49
|
/**
|
|
44
50
|
* Create a new instance of the Alchemy Signer client
|
|
@@ -47,7 +53,6 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
47
53
|
*/
|
|
48
54
|
constructor(params: BaseSignerClientParams) {
|
|
49
55
|
const { stamper, connection, rootOrgId } = params;
|
|
50
|
-
|
|
51
56
|
this.rootOrg = rootOrgId ?? "24c1acf5-810f-41e0-a503-d5d13fa8e830";
|
|
52
57
|
this.eventEmitter = new EventEmitter<AlchemySignerClientEvents>();
|
|
53
58
|
this.connectionConfig = ConnectionConfigSchema.parse(connection);
|
|
@@ -57,6 +62,16 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
57
62
|
);
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Asynchronously fetches and sets the OAuth configuration.
|
|
67
|
+
*
|
|
68
|
+
* @returns {Promise<OauthConfig>} A promise that resolves to the OAuth configuration
|
|
69
|
+
*/
|
|
70
|
+
public initOauth = async (): Promise<OauthConfig> => {
|
|
71
|
+
this.oauthConfig = await this.getOauthConfig();
|
|
72
|
+
return this.oauthConfig;
|
|
73
|
+
};
|
|
74
|
+
|
|
60
75
|
protected get user() {
|
|
61
76
|
return this._user;
|
|
62
77
|
}
|
|
@@ -92,11 +107,14 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
92
107
|
exportStamper: ExportWalletStamper;
|
|
93
108
|
exportAs: "SEED_PHRASE" | "PRIVATE_KEY";
|
|
94
109
|
}): Promise<boolean> {
|
|
95
|
-
|
|
110
|
+
const { exportAs } = params;
|
|
111
|
+
switch (exportAs) {
|
|
96
112
|
case "PRIVATE_KEY":
|
|
97
113
|
return this.exportAsPrivateKey(params.exportStamper);
|
|
98
114
|
case "SEED_PHRASE":
|
|
99
115
|
return this.exportAsSeedPhrase(params.exportStamper);
|
|
116
|
+
default:
|
|
117
|
+
assertNever(exportAs, `Unknown export mode: ${exportAs}`);
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
120
|
|
|
@@ -110,17 +128,30 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
110
128
|
params: Omit<EmailAuthParams, "targetPublicKey">
|
|
111
129
|
): Promise<{ orgId: string }>;
|
|
112
130
|
|
|
113
|
-
public abstract
|
|
131
|
+
public abstract completeAuthWithBundle(params: {
|
|
114
132
|
bundle: string;
|
|
115
133
|
orgId: string;
|
|
134
|
+
connectedEventName: keyof AlchemySignerClientEvents;
|
|
135
|
+
authenticatingType: AuthenticatingEventMetadata["type"];
|
|
136
|
+
idToken?: string;
|
|
116
137
|
}): Promise<User>;
|
|
117
138
|
|
|
139
|
+
public abstract oauthWithRedirect(
|
|
140
|
+
args: Extract<OauthParams, { mode: "redirect" }>
|
|
141
|
+
): Promise<never>;
|
|
142
|
+
|
|
143
|
+
public abstract oauthWithPopup(
|
|
144
|
+
args: Extract<OauthParams, { mode: "popup" }>
|
|
145
|
+
): Promise<User>;
|
|
146
|
+
|
|
118
147
|
public abstract disconnect(): Promise<void>;
|
|
119
148
|
|
|
120
149
|
public abstract exportWallet(params: TExportWalletParams): Promise<boolean>;
|
|
121
150
|
|
|
122
151
|
public abstract lookupUserWithPasskey(user?: User): Promise<User>;
|
|
123
152
|
|
|
153
|
+
protected abstract getOauthConfig(): Promise<OauthConfig>;
|
|
154
|
+
|
|
124
155
|
protected abstract getWebAuthnAttestation(
|
|
125
156
|
options: CredentialCreationOptions,
|
|
126
157
|
userDetails?: { username: string }
|
|
@@ -190,10 +221,14 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
190
221
|
* Retrieves the current user or fetches the user information if not already available.
|
|
191
222
|
*
|
|
192
223
|
* @param {string} [orgId] optional organization ID, defaults to the user's organization ID
|
|
224
|
+
* @param {string} idToken an OIDC ID token containing additional user information
|
|
193
225
|
* @returns {Promise<User>} A promise that resolves to the user object
|
|
194
226
|
* @throws {Error} if no organization ID is provided when there is no current user
|
|
195
227
|
*/
|
|
196
|
-
public whoami = async (
|
|
228
|
+
public whoami = async (
|
|
229
|
+
orgId = this.user?.orgId,
|
|
230
|
+
idToken?: string
|
|
231
|
+
): Promise<User> => {
|
|
197
232
|
if (this.user) {
|
|
198
233
|
return this.user;
|
|
199
234
|
}
|
|
@@ -210,6 +245,15 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
210
245
|
stampedRequest,
|
|
211
246
|
});
|
|
212
247
|
|
|
248
|
+
if (idToken) {
|
|
249
|
+
const claims: Record<string, unknown> = jwtDecode(idToken);
|
|
250
|
+
user.idToken = idToken;
|
|
251
|
+
user.claims = claims;
|
|
252
|
+
if (typeof claims.email === "string") {
|
|
253
|
+
user.email = claims.email;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
213
257
|
const credentialId = (() => {
|
|
214
258
|
try {
|
|
215
259
|
return JSON.parse(stampedRequest?.stamp.stampHeaderValue)
|
|
@@ -310,6 +354,7 @@ export abstract class BaseSignerClient<TExportWalletParams = unknown> {
|
|
|
310
354
|
body: SignerBody<R>
|
|
311
355
|
): Promise<SignerResponse<R>> => {
|
|
312
356
|
const url = this.connectionConfig.rpcUrl ?? "https://api.g.alchemy.com";
|
|
357
|
+
|
|
313
358
|
const basePath = "/signer";
|
|
314
359
|
|
|
315
360
|
const headers = new Headers();
|