@account-kit/signer 4.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/base.d.ts +37 -0
  3. package/dist/cjs/base.js +292 -0
  4. package/dist/cjs/base.js.map +1 -0
  5. package/dist/cjs/client/base.d.ts +230 -0
  6. package/dist/cjs/client/base.js +298 -0
  7. package/dist/cjs/client/base.js.map +1 -0
  8. package/dist/cjs/client/index.d.ts +146 -0
  9. package/dist/cjs/client/index.js +260 -0
  10. package/dist/cjs/client/index.js.map +1 -0
  11. package/dist/cjs/client/types.d.ts +106 -0
  12. package/dist/cjs/client/types.js +3 -0
  13. package/dist/cjs/client/types.js.map +1 -0
  14. package/dist/cjs/errors.d.ts +4 -0
  15. package/dist/cjs/errors.js +16 -0
  16. package/dist/cjs/errors.js.map +1 -0
  17. package/dist/cjs/index.d.ts +8 -0
  18. package/dist/cjs/index.js +14 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/package.json +1 -0
  21. package/dist/cjs/session/manager.d.ts +45 -0
  22. package/dist/cjs/session/manager.js +230 -0
  23. package/dist/cjs/session/manager.js.map +1 -0
  24. package/dist/cjs/session/types.d.ts +16 -0
  25. package/dist/cjs/session/types.js +3 -0
  26. package/dist/cjs/session/types.js.map +1 -0
  27. package/dist/cjs/signer.d.ts +262 -0
  28. package/dist/cjs/signer.js +34 -0
  29. package/dist/cjs/signer.js.map +1 -0
  30. package/dist/cjs/types.d.ts +14 -0
  31. package/dist/cjs/types.js +12 -0
  32. package/dist/cjs/types.js.map +1 -0
  33. package/dist/cjs/utils/base64UrlEncode.d.ts +1 -0
  34. package/dist/cjs/utils/base64UrlEncode.js +12 -0
  35. package/dist/cjs/utils/base64UrlEncode.js.map +1 -0
  36. package/dist/cjs/utils/generateRandomBuffer.d.ts +1 -0
  37. package/dist/cjs/utils/generateRandomBuffer.js +10 -0
  38. package/dist/cjs/utils/generateRandomBuffer.js.map +1 -0
  39. package/dist/cjs/version.d.ts +1 -0
  40. package/dist/cjs/version.js +5 -0
  41. package/dist/cjs/version.js.map +1 -0
  42. package/dist/esm/base.d.ts +37 -0
  43. package/dist/esm/base.js +288 -0
  44. package/dist/esm/base.js.map +1 -0
  45. package/dist/esm/client/base.d.ts +230 -0
  46. package/dist/esm/client/base.js +291 -0
  47. package/dist/esm/client/base.js.map +1 -0
  48. package/dist/esm/client/index.d.ts +146 -0
  49. package/dist/esm/client/index.js +256 -0
  50. package/dist/esm/client/index.js.map +1 -0
  51. package/dist/esm/client/types.d.ts +106 -0
  52. package/dist/esm/client/types.js +2 -0
  53. package/dist/esm/client/types.js.map +1 -0
  54. package/dist/esm/errors.d.ts +4 -0
  55. package/dist/esm/errors.js +12 -0
  56. package/dist/esm/errors.js.map +1 -0
  57. package/dist/esm/index.d.ts +8 -0
  58. package/dist/esm/index.js +6 -0
  59. package/dist/esm/index.js.map +1 -0
  60. package/dist/esm/package.json +1 -0
  61. package/dist/esm/session/manager.d.ts +45 -0
  62. package/dist/esm/session/manager.js +223 -0
  63. package/dist/esm/session/manager.js.map +1 -0
  64. package/dist/esm/session/types.d.ts +16 -0
  65. package/dist/esm/session/types.js +2 -0
  66. package/dist/esm/session/types.js.map +1 -0
  67. package/dist/esm/signer.d.ts +262 -0
  68. package/dist/esm/signer.js +30 -0
  69. package/dist/esm/signer.js.map +1 -0
  70. package/dist/esm/types.d.ts +14 -0
  71. package/dist/esm/types.js +9 -0
  72. package/dist/esm/types.js.map +1 -0
  73. package/dist/esm/utils/base64UrlEncode.d.ts +1 -0
  74. package/dist/esm/utils/base64UrlEncode.js +8 -0
  75. package/dist/esm/utils/base64UrlEncode.js.map +1 -0
  76. package/dist/esm/utils/generateRandomBuffer.d.ts +1 -0
  77. package/dist/esm/utils/generateRandomBuffer.js +6 -0
  78. package/dist/esm/utils/generateRandomBuffer.js.map +1 -0
  79. package/dist/esm/version.d.ts +1 -0
  80. package/dist/esm/version.js +2 -0
  81. package/dist/esm/version.js.map +1 -0
  82. package/dist/types/base.d.ts +89 -0
  83. package/dist/types/base.d.ts.map +1 -0
  84. package/dist/types/client/base.d.ts +246 -0
  85. package/dist/types/client/base.d.ts.map +1 -0
  86. package/dist/types/client/index.d.ts +151 -0
  87. package/dist/types/client/index.d.ts.map +1 -0
  88. package/dist/types/client/types.d.ts +107 -0
  89. package/dist/types/client/types.d.ts.map +1 -0
  90. package/dist/types/errors.d.ts +5 -0
  91. package/dist/types/errors.d.ts.map +1 -0
  92. package/dist/types/index.d.ts +9 -0
  93. package/dist/types/index.d.ts.map +1 -0
  94. package/dist/types/session/manager.d.ts +46 -0
  95. package/dist/types/session/manager.d.ts.map +1 -0
  96. package/dist/types/session/types.d.ts +17 -0
  97. package/dist/types/session/types.d.ts.map +1 -0
  98. package/dist/types/signer.d.ts +269 -0
  99. package/dist/types/signer.d.ts.map +1 -0
  100. package/dist/types/types.d.ts +15 -0
  101. package/dist/types/types.d.ts.map +1 -0
  102. package/dist/types/utils/base64UrlEncode.d.ts +2 -0
  103. package/dist/types/utils/base64UrlEncode.d.ts.map +1 -0
  104. package/dist/types/utils/generateRandomBuffer.d.ts +2 -0
  105. package/dist/types/utils/generateRandomBuffer.d.ts.map +1 -0
  106. package/dist/types/version.d.ts +2 -0
  107. package/dist/types/version.d.ts.map +1 -0
  108. package/package.json +79 -0
  109. package/src/base.ts +386 -0
  110. package/src/client/base.ts +399 -0
  111. package/src/client/index.ts +267 -0
  112. package/src/client/types.ts +121 -0
  113. package/src/errors.ts +15 -0
  114. package/src/index.ts +10 -0
  115. package/src/session/manager.ts +249 -0
  116. package/src/session/types.ts +16 -0
  117. package/src/signer.ts +55 -0
  118. package/src/types.ts +17 -0
  119. package/src/utils/base64UrlEncode.ts +7 -0
  120. package/src/utils/generateRandomBuffer.ts +5 -0
  121. package/src/version.ts +3 -0
@@ -0,0 +1,121 @@
1
+ import type { Address } from "@aa-sdk/core";
2
+ import type { TSignedRequest, getWebAuthnAttestation } from "@turnkey/http";
3
+ import type { Hex } from "viem";
4
+
5
+ export type CredentialCreationOptionOverrides = {
6
+ publicKey?: Partial<CredentialCreationOptions["publicKey"]>;
7
+ } & Pick<CredentialCreationOptions, "signal">;
8
+
9
+ // [!region User]
10
+ export type User = {
11
+ email?: string;
12
+ orgId: string;
13
+ userId: string;
14
+ address: Address;
15
+ credentialId?: string;
16
+ };
17
+ // [!endregion User]
18
+
19
+ export type ExportWalletParams = {
20
+ iframeContainerId: string;
21
+ iframeElementId?: string;
22
+ };
23
+
24
+ export type CreateAccountParams =
25
+ | {
26
+ type: "email";
27
+ email: string;
28
+ expirationSeconds?: number;
29
+ redirectParams?: URLSearchParams;
30
+ }
31
+ | {
32
+ type: "passkey";
33
+ username: string;
34
+ creationOpts?: CredentialCreationOptionOverrides;
35
+ };
36
+
37
+ export type EmailAuthParams = {
38
+ email: string;
39
+ expirationSeconds?: number;
40
+ targetPublicKey: string;
41
+ redirectParams?: URLSearchParams;
42
+ };
43
+
44
+ export type SignupResponse = {
45
+ orgId: string;
46
+ userId?: string;
47
+ address?: Address;
48
+ };
49
+
50
+ export type SignerRoutes = SignerEndpoints[number]["Route"];
51
+ export type SignerBody<T extends SignerRoutes> = Extract<
52
+ SignerEndpoints[number],
53
+ { Route: T }
54
+ >["Body"];
55
+ export type SignerResponse<T extends SignerRoutes> = Extract<
56
+ SignerEndpoints[number],
57
+ { Route: T }
58
+ >["Response"];
59
+
60
+ export type SignerEndpoints = [
61
+ {
62
+ Route: "/v1/signup";
63
+ Body:
64
+ | (Omit<EmailAuthParams, "redirectParams"> & { redirectParams?: string })
65
+ | {
66
+ passkey: {
67
+ challenge: string;
68
+ attestation: Awaited<ReturnType<typeof getWebAuthnAttestation>>;
69
+ };
70
+ };
71
+ Response: SignupResponse;
72
+ },
73
+ {
74
+ Route: "/v1/whoami";
75
+ Body: {
76
+ stampedRequest: TSignedRequest;
77
+ };
78
+ Response: User;
79
+ },
80
+ {
81
+ Route: "/v1/auth";
82
+ Body: Omit<EmailAuthParams, "redirectParams"> & { redirectParams?: string };
83
+ Response: {
84
+ orgId: string;
85
+ };
86
+ },
87
+ {
88
+ Route: "/v1/lookup";
89
+ Body: {
90
+ email: string;
91
+ };
92
+ Response: {
93
+ orgId: string | null;
94
+ };
95
+ },
96
+ {
97
+ Route: "/v1/sign-payload";
98
+ Body: {
99
+ stampedRequest: TSignedRequest;
100
+ };
101
+ Response: {
102
+ signature: Hex;
103
+ };
104
+ }
105
+ ];
106
+
107
+ export type AlchemySignerClientEvents = {
108
+ connected(user: User): void;
109
+ authenticating(): void;
110
+ connectedEmail(user: User, bundle: string): void;
111
+ connectedPasskey(user: User): void;
112
+ disconnected(): void;
113
+ };
114
+
115
+ export type AlchemySignerClientEvent = keyof AlchemySignerClientEvents;
116
+
117
+ export type GetWebAuthnAttestationResult = {
118
+ attestation: Awaited<ReturnType<typeof getWebAuthnAttestation>>;
119
+ challenge: ArrayBuffer;
120
+ authenticatorUserId: ArrayBuffer;
121
+ };
package/src/errors.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { BaseError } from "@aa-sdk/core";
2
+
3
+ export class NotAuthenticatedError extends BaseError {
4
+ constructor() {
5
+ super(
6
+ [
7
+ "Signer not authenticated",
8
+ "Please authenticate to use this signer",
9
+ ].join("\n"),
10
+ {
11
+ docsPath: "/signers/alchemy-signer/introduction.html",
12
+ }
13
+ );
14
+ }
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type * from "./signer.js";
2
+ export { AlchemyWebSigner } from "./signer.js";
3
+
4
+ export type * from "./types.js";
5
+ export { AlchemySignerStatus } from "./types.js";
6
+
7
+ export { BaseSignerClient } from "./client/base.js";
8
+ export { AlchemySignerWebClient } from "./client/index.js";
9
+ export type * from "./client/types.js";
10
+ export { DEFAULT_SESSION_MS } from "./session/manager.js";
@@ -0,0 +1,249 @@
1
+ import EventEmitter from "eventemitter3";
2
+ import { z } from "zod";
3
+ import {
4
+ createJSONStorage,
5
+ persist,
6
+ subscribeWithSelector,
7
+ } from "zustand/middleware";
8
+ import { createStore, type Mutate, type StoreApi } from "zustand/vanilla";
9
+ import type { BaseSignerClient } from "../client/base";
10
+ import type { User } from "../client/types";
11
+ import type { Session, SessionManagerEvents } from "./types";
12
+
13
+ export const DEFAULT_SESSION_MS = 15 * 60 * 1000; // 15 minutes
14
+
15
+ export const SessionManagerParamsSchema = z.object({
16
+ sessionKey: z.string().default("alchemy-signer-session"),
17
+ storage: z
18
+ .enum(["localStorage", "sessionStorage"])
19
+ .default("localStorage")
20
+ .or(z.custom<Storage>()),
21
+ expirationTimeMs: z
22
+ .number()
23
+ .default(DEFAULT_SESSION_MS)
24
+ .describe(
25
+ "The time in milliseconds that a session should last before expiring [default: 15 minutes]"
26
+ ),
27
+ client: z.custom<BaseSignerClient>(),
28
+ });
29
+
30
+ export type SessionManagerParams = z.input<typeof SessionManagerParamsSchema>;
31
+
32
+ type SessionState = {
33
+ session: Session | null;
34
+ };
35
+
36
+ type Store = Mutate<
37
+ StoreApi<SessionState>,
38
+ [["zustand/subscribeWithSelector", never], ["zustand/persist", SessionState]]
39
+ >;
40
+
41
+ export class SessionManager {
42
+ private sessionKey: string;
43
+ private client: BaseSignerClient;
44
+ private eventEmitter: EventEmitter<SessionManagerEvents>;
45
+ readonly expirationTimeMs: number;
46
+ private store: Store;
47
+
48
+ constructor(params: SessionManagerParams) {
49
+ const {
50
+ sessionKey,
51
+ storage: storageType,
52
+ expirationTimeMs,
53
+ client,
54
+ } = SessionManagerParamsSchema.parse(params);
55
+ this.sessionKey = sessionKey;
56
+ const storage =
57
+ typeof storageType === "string"
58
+ ? storageType === "localStorage"
59
+ ? localStorage
60
+ : sessionStorage
61
+ : storageType;
62
+ this.expirationTimeMs = expirationTimeMs;
63
+ this.client = client;
64
+ this.eventEmitter = new EventEmitter<SessionManagerEvents>();
65
+
66
+ this.store = createStore(
67
+ subscribeWithSelector(
68
+ persist(this.getInitialState, {
69
+ name: this.sessionKey,
70
+ storage: createJSONStorage<SessionState>(() => storage),
71
+ })
72
+ )
73
+ );
74
+
75
+ this.registerEventListeners();
76
+ }
77
+
78
+ public getSessionUser = async (): Promise<User | null> => {
79
+ const existingSession = this.getSession();
80
+ if (existingSession == null) {
81
+ return null;
82
+ }
83
+
84
+ switch (existingSession.type) {
85
+ case "email": {
86
+ const result = await this.client
87
+ .completeEmailAuth({
88
+ bundle: existingSession.bundle,
89
+ orgId: existingSession.user.orgId,
90
+ })
91
+ .catch((e) => {
92
+ console.warn("Failed to load user from session", e);
93
+ return null;
94
+ });
95
+
96
+ if (!result) {
97
+ this.clearSession();
98
+ return null;
99
+ }
100
+
101
+ return result;
102
+ }
103
+ case "passkey": {
104
+ // we don't need to do much here if we already have a user
105
+ // this will setup the client with the user context, but
106
+ // requests still have to be signed by the user on first request
107
+ // so this is fine
108
+ return this.client.lookupUserWithPasskey(existingSession.user);
109
+ }
110
+ default:
111
+ throw new Error("Unknown session type");
112
+ }
113
+ };
114
+
115
+ public clearSession = () => {
116
+ this.store.setState({ session: null });
117
+ };
118
+
119
+ public setTemporarySession = (session: { orgId: string }) => {
120
+ // temporary session must be placed in localStorage so that it can be accessed across tabs
121
+ localStorage.setItem(
122
+ `${this.sessionKey}:temporary`,
123
+ JSON.stringify(session)
124
+ );
125
+ };
126
+
127
+ public getTemporarySession = (): { orgId: string } | null => {
128
+ // temporary session must be placed in localStorage so that it can be accessed across tabs
129
+ const sessionStr = localStorage.getItem(`${this.sessionKey}:temporary`);
130
+
131
+ if (!sessionStr) {
132
+ return null;
133
+ }
134
+
135
+ return JSON.parse(sessionStr);
136
+ };
137
+
138
+ on = <E extends keyof SessionManagerEvents>(
139
+ event: E,
140
+ listener: SessionManagerEvents[E]
141
+ ) => {
142
+ this.eventEmitter.on(event, listener as any);
143
+
144
+ return () => this.eventEmitter.removeListener(event, listener as any);
145
+ };
146
+
147
+ private getSession = (): Session | null => {
148
+ const session = this.store.getState().session;
149
+
150
+ if (!session) {
151
+ return null;
152
+ }
153
+
154
+ /**
155
+ * TODO: this isn't really good enough
156
+ * A user's session could be about to expire and we would still return it
157
+ *
158
+ * Instead we should check if a session is about to expire and refresh it
159
+ * We should revisit this later
160
+ */
161
+ if (session.expirationDateMs < Date.now()) {
162
+ this.store.setState({ session: null });
163
+ return null;
164
+ }
165
+
166
+ return session;
167
+ };
168
+
169
+ private setSession = (
170
+ session:
171
+ | Omit<Extract<Session, { type: "email" }>, "expirationDateMs">
172
+ | Omit<Extract<Session, { type: "passkey" }>, "expirationDateMs">
173
+ ) => {
174
+ this.store.setState({
175
+ session: {
176
+ ...session,
177
+ expirationDateMs: Date.now() + this.expirationTimeMs,
178
+ },
179
+ });
180
+ };
181
+
182
+ public initialize() {
183
+ this.getSessionUser()
184
+ .then((user) => {
185
+ // once we complete auth we can update the state of the session to connected or disconnected
186
+ if (user) this.eventEmitter.emit("connected", this.getSession()!);
187
+ else this.eventEmitter.emit("disconnected");
188
+ })
189
+ .finally(() => {
190
+ this.eventEmitter.emit("initialized");
191
+ });
192
+ }
193
+
194
+ private getInitialState(): SessionState {
195
+ return {
196
+ session: null,
197
+ };
198
+ }
199
+
200
+ private registerEventListeners = () => {
201
+ this.store.subscribe(
202
+ ({ session }) => session,
203
+ (session, prevSession) => {
204
+ if (session != null && prevSession == null) {
205
+ this.eventEmitter.emit("connected", session);
206
+ } else if (session == null && prevSession != null) {
207
+ this.eventEmitter.emit("disconnected");
208
+ }
209
+ }
210
+ );
211
+
212
+ this.client.on("disconnected", () => this.clearSession());
213
+
214
+ this.client.on("connectedEmail", (user, bundle) => {
215
+ const existingSession = this.getSession();
216
+ if (
217
+ existingSession != null &&
218
+ existingSession.type === "email" &&
219
+ existingSession.user.userId === user.userId &&
220
+ // if the bundle is different, then we've refreshed the session
221
+ // so we need to reset the session
222
+ existingSession.bundle === bundle
223
+ ) {
224
+ return;
225
+ }
226
+
227
+ this.setSession({ type: "email", user, bundle });
228
+ });
229
+
230
+ this.client.on("connectedPasskey", (user) => {
231
+ const existingSession = this.getSession();
232
+ if (
233
+ existingSession != null &&
234
+ existingSession.type === "passkey" &&
235
+ existingSession.user.userId === user.userId
236
+ ) {
237
+ return;
238
+ }
239
+
240
+ this.setSession({ type: "passkey", user });
241
+ });
242
+
243
+ // sync local state if persisted state has changed from another tab
244
+ window.addEventListener("focus", () => {
245
+ this.store.persist.rehydrate();
246
+ this.initialize();
247
+ });
248
+ };
249
+ }
@@ -0,0 +1,16 @@
1
+ import type { User } from "../client/types";
2
+
3
+ export type Session =
4
+ | {
5
+ type: "email";
6
+ bundle: string;
7
+ expirationDateMs: number;
8
+ user: User;
9
+ }
10
+ | { type: "passkey"; user: User; expirationDateMs: number };
11
+
12
+ export type SessionManagerEvents = {
13
+ connected(session: Session): void;
14
+ disconnected(): void;
15
+ initialized(): void;
16
+ };
package/src/signer.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ import { BaseAlchemySigner } from "./base.js";
3
+ import {
4
+ AlchemySignerClientParamsSchema,
5
+ AlchemySignerWebClient,
6
+ } from "./client/index.js";
7
+ import type { CredentialCreationOptionOverrides } from "./client/types.js";
8
+ import { SessionManagerParamsSchema } from "./session/manager.js";
9
+
10
+ export type AuthParams =
11
+ | { type: "email"; email: string; redirectParams?: URLSearchParams }
12
+ | { type: "email"; bundle: string; orgId?: string }
13
+ | {
14
+ type: "passkey";
15
+ createNew: false;
16
+ }
17
+ | {
18
+ type: "passkey";
19
+ createNew: true;
20
+ username: string;
21
+ creationOpts?: CredentialCreationOptionOverrides;
22
+ };
23
+
24
+ export const AlchemySignerParamsSchema = z
25
+ .object({
26
+ client: z
27
+ .custom<AlchemySignerWebClient>()
28
+ .or(AlchemySignerClientParamsSchema),
29
+ })
30
+ .extend({
31
+ sessionConfig: SessionManagerParamsSchema.omit({ client: true }).optional(),
32
+ });
33
+
34
+ export type AlchemySignerParams = z.input<typeof AlchemySignerParamsSchema>;
35
+
36
+ /**
37
+ * A SmartAccountSigner that can be used with any SmartContractAccount
38
+ */
39
+ export class AlchemyWebSigner extends BaseAlchemySigner<AlchemySignerWebClient> {
40
+ constructor(params_: AlchemySignerParams) {
41
+ const { sessionConfig, ...params } =
42
+ AlchemySignerParamsSchema.parse(params_);
43
+
44
+ let client: AlchemySignerWebClient;
45
+ if ("connection" in params.client) {
46
+ client = new AlchemySignerWebClient(params.client);
47
+ } else {
48
+ client = params.client;
49
+ }
50
+ super({
51
+ client,
52
+ sessionConfig,
53
+ });
54
+ }
55
+ }
package/src/types.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { User } from "./client/types";
2
+
3
+ export type AlchemySignerEvents = {
4
+ connected(user: User): void;
5
+ disconnected(): void;
6
+ statusChanged(status: AlchemySignerStatus): void;
7
+ };
8
+
9
+ export type AlchemySignerEvent = keyof AlchemySignerEvents;
10
+
11
+ export enum AlchemySignerStatus {
12
+ INITIALIZING = "INITIALIZING",
13
+ CONNECTED = "CONNECTED",
14
+ DISCONNECTED = "DISCONNECTED",
15
+ AUTHENTICATING = "AUTHENTICATING",
16
+ AWAITING_EMAIL_AUTH = "AWAITING_EMAIL_AUTH",
17
+ }
@@ -0,0 +1,7 @@
1
+ export const base64UrlEncode = (challenge: ArrayBuffer): string => {
2
+ return Buffer.from(challenge)
3
+ .toString("base64")
4
+ .replace(/\+/g, "-")
5
+ .replace(/\//g, "_")
6
+ .replace(/=/g, "");
7
+ };
@@ -0,0 +1,5 @@
1
+ export const generateRandomBuffer = (): ArrayBuffer => {
2
+ const arr = new Uint8Array(32);
3
+ crypto.getRandomValues(arr);
4
+ return arr.buffer;
5
+ };
package/src/version.ts ADDED
@@ -0,0 +1,3 @@
1
+ // This file is autogenerated by inject-version.ts. Any changes will be
2
+ // overwritten on commit!
3
+ export const VERSION = "4.0.0-alpha.0";