@account-kit/signer 4.31.2 → 4.33.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 (46) hide show
  1. package/dist/esm/base.d.ts +11 -8
  2. package/dist/esm/base.js +141 -38
  3. package/dist/esm/base.js.map +1 -1
  4. package/dist/esm/client/base.d.ts +132 -9
  5. package/dist/esm/client/base.js +34 -4
  6. package/dist/esm/client/base.js.map +1 -1
  7. package/dist/esm/client/index.d.ts +36 -14
  8. package/dist/esm/client/index.js +36 -18
  9. package/dist/esm/client/index.js.map +1 -1
  10. package/dist/esm/client/types.d.ts +19 -0
  11. package/dist/esm/client/types.js.map +1 -1
  12. package/dist/esm/signer.d.ts +88 -33
  13. package/dist/esm/signer.js +28 -3
  14. package/dist/esm/signer.js.map +1 -1
  15. package/dist/esm/solanaSigner.d.ts +3 -3
  16. package/dist/esm/solanaSigner.js +1 -1
  17. package/dist/esm/solanaSigner.js.map +1 -1
  18. package/dist/esm/types.d.ts +1 -0
  19. package/dist/esm/types.js.map +1 -1
  20. package/dist/esm/version.d.ts +1 -1
  21. package/dist/esm/version.js +1 -1
  22. package/dist/esm/version.js.map +1 -1
  23. package/dist/types/base.d.ts +11 -8
  24. package/dist/types/base.d.ts.map +1 -1
  25. package/dist/types/client/base.d.ts +132 -9
  26. package/dist/types/client/base.d.ts.map +1 -1
  27. package/dist/types/client/index.d.ts +36 -14
  28. package/dist/types/client/index.d.ts.map +1 -1
  29. package/dist/types/client/types.d.ts +19 -0
  30. package/dist/types/client/types.d.ts.map +1 -1
  31. package/dist/types/signer.d.ts +88 -33
  32. package/dist/types/signer.d.ts.map +1 -1
  33. package/dist/types/solanaSigner.d.ts +3 -3
  34. package/dist/types/solanaSigner.d.ts.map +1 -1
  35. package/dist/types/types.d.ts +1 -0
  36. package/dist/types/types.d.ts.map +1 -1
  37. package/dist/types/version.d.ts +1 -1
  38. package/package.json +6 -7
  39. package/src/base.ts +191 -63
  40. package/src/client/base.ts +36 -7
  41. package/src/client/index.ts +41 -18
  42. package/src/client/types.ts +21 -0
  43. package/src/signer.ts +36 -3
  44. package/src/solanaSigner.ts +4 -4
  45. package/src/types.ts +1 -0
  46. package/src/version.ts +1 -1
@@ -1,8 +1,7 @@
1
- import { type SmartAccountAuthenticator } from "@aa-sdk/core";
2
- import { type GetTransactionType, type Hex, type IsNarrowable, type LocalAccount, type SerializeTransactionFn, type SignableMessage, type TransactionSerializable, type TransactionSerialized, type TypedData, type TypedDataDefinition } from "viem";
3
- import { type Authorization } from "viem/experimental";
1
+ import { type SmartAccountAuthenticator, type AuthorizationRequest } from "@aa-sdk/core";
2
+ import { type GetTransactionType, type Hex, type IsNarrowable, type LocalAccount, type SerializeTransactionFn, type SignableMessage, type SignedAuthorization, type TransactionSerializable, type TransactionSerialized, type TypedData, type TypedDataDefinition } from "viem";
4
3
  import type { BaseSignerClient } from "./client/base";
5
- import type { MfaFactor, OauthConfig, User, VerifyMfaParams, AddMfaParams, AddMfaResult, RemoveMfaParams } from "./client/types";
4
+ import { type MfaFactor, type OauthConfig, type User, type VerifyMfaParams, type AddMfaParams, type AddMfaResult, type RemoveMfaParams, type AuthLinkingPrompt } from "./client/types.js";
6
5
  import { type SessionManagerParams } from "./session/manager.js";
7
6
  import type { AuthParams } from "./signer";
8
7
  import { SolanaSigner } from "./solanaSigner.js";
@@ -11,6 +10,7 @@ export interface BaseAlchemySignerParams<TClient extends BaseSignerClient> {
11
10
  client: TClient;
12
11
  sessionConfig?: Omit<SessionManagerParams, "client">;
13
12
  initialError?: ErrorInfo;
13
+ initialAuthLinkingPrompt?: AuthLinkingPrompt;
14
14
  }
15
15
  export type EmailConfig = {
16
16
  mode?: "MAGIC_LINK" | "OTP";
@@ -38,7 +38,7 @@ export declare abstract class BaseAlchemySigner<TClient extends BaseSignerClient
38
38
  * @param {SessionConfig} param0.sessionConfig Configuration for managing sessions
39
39
  * @param {ErrorInfo | undefined} param0.initialError Error already present on the signer when initialized, if any
40
40
  */
41
- constructor({ client, sessionConfig, initialError, }: BaseAlchemySignerParams<TClient>);
41
+ constructor({ client, sessionConfig, initialError, initialAuthLinkingPrompt, }: BaseAlchemySignerParams<TClient>);
42
42
  /**
43
43
  * Allows you to subscribe to events emitted by the signer
44
44
  *
@@ -277,10 +277,10 @@ export declare abstract class BaseAlchemySigner<TClient extends BaseSignerClient
277
277
  * });
278
278
  * ```
279
279
  *
280
- * @param {Authorization<number, false>} unsignedAuthorization the authorization to be signed
281
- * @returns {Promise<Authorization<number, true>> | undefined} a promise that resolves to the authorization with the signature
280
+ * @param {AuthorizationRequest<number>} unsignedAuthorization the authorization to be signed
281
+ * @returns {Promise<SignedAuthorization<number>> | undefined} a promise that resolves to the authorization with the signature
282
282
  */
283
- signAuthorization: (unsignedAuthorization: Authorization<number, false>) => Promise<Authorization<number, true>>;
283
+ signAuthorization: (unsignedAuthorization: AuthorizationRequest<number>) => Promise<SignedAuthorization<number>>;
284
284
  /**
285
285
  * Gets the current MFA status
286
286
  *
@@ -450,6 +450,7 @@ export declare abstract class BaseAlchemySigner<TClient extends BaseSignerClient
450
450
  private authenticateWithPasskey;
451
451
  private authenticateWithOauth;
452
452
  private authenticateWithOtp;
453
+ private setAwaitingEmailAuth;
453
454
  private handleOauthReturn;
454
455
  private handleMfaRequired;
455
456
  private getExpirationSeconds;
@@ -613,4 +614,6 @@ export declare abstract class BaseAlchemySigner<TClient extends BaseSignerClient
613
614
  */
614
615
  getConfig: () => Promise<SignerConfig>;
615
616
  protected fetchConfig: () => Promise<SignerConfig>;
617
+ private setAuthLinkingPrompt;
618
+ private waitForConnected;
616
619
  }
package/dist/esm/base.js CHANGED
@@ -1,15 +1,16 @@
1
- import { takeBytes } from "@aa-sdk/core";
1
+ import { takeBytes, } from "@aa-sdk/core";
2
2
  import { hashMessage, hashTypedData, keccak256, serializeTransaction, } from "viem";
3
3
  import { toAccount } from "viem/accounts";
4
- import { hashAuthorization } from "viem/experimental";
5
4
  import { subscribeWithSelector } from "zustand/middleware";
6
5
  import { createStore } from "zustand/vanilla";
6
+ import {} from "./client/types.js";
7
7
  import { NotAuthenticatedError } from "./errors.js";
8
8
  import { SignerLogger } from "./metrics.js";
9
9
  import { SessionManager, } from "./session/manager.js";
10
10
  import { SolanaSigner } from "./solanaSigner.js";
11
11
  import { AlchemySignerStatus, } from "./types.js";
12
12
  import { assertNever } from "./utils/typeAssertions.js";
13
+ import { hashAuthorization } from "viem/utils";
13
14
  /**
14
15
  * Base abstract class for Alchemy Signer, providing authentication and session management for smart accounts.
15
16
  * Implements the `SmartAccountAuthenticator` interface and handles various signer events.
@@ -25,7 +26,7 @@ export class BaseAlchemySigner {
25
26
  * @param {SessionConfig} param0.sessionConfig Configuration for managing sessions
26
27
  * @param {ErrorInfo | undefined} param0.initialError Error already present on the signer when initialized, if any
27
28
  */
28
- constructor({ client, sessionConfig, initialError, }) {
29
+ constructor({ client, sessionConfig, initialError, initialAuthLinkingPrompt, }) {
29
30
  Object.defineProperty(this, "signerType", {
30
31
  enumerable: true,
31
32
  configurable: true,
@@ -73,22 +74,28 @@ export class BaseAlchemySigner {
73
74
  // is fired. In the Client and SessionManager we use EventEmitter because it's easier to handle internally
74
75
  switch (event) {
75
76
  case "connected":
76
- return this.store.subscribe(({ status }) => status, (status) => status === AlchemySignerStatus.CONNECTED &&
77
- listener(this.store.getState().user), { fireImmediately: true });
77
+ return subscribeWithDelayedFireImmediately(this.store, ({ status }) => status, (status) => status === AlchemySignerStatus.CONNECTED &&
78
+ listener(this.store.getState().user));
78
79
  case "disconnected":
79
- return this.store.subscribe(({ status }) => status, (status) => status === AlchemySignerStatus.DISCONNECTED &&
80
- listener(), { fireImmediately: true });
80
+ return subscribeWithDelayedFireImmediately(this.store, ({ status }) => status, (status) => status === AlchemySignerStatus.DISCONNECTED &&
81
+ listener());
81
82
  case "statusChanged":
82
- return this.store.subscribe(({ status }) => status, listener, { fireImmediately: true });
83
+ return subscribeWithDelayedFireImmediately(this.store, ({ status }) => status, listener);
83
84
  case "errorChanged":
84
- return this.store.subscribe(({ error }) => error, (error) => listener(error ?? undefined), { fireImmediately: true });
85
+ return subscribeWithDelayedFireImmediately(this.store, ({ error }) => error, (error) => listener(error ?? undefined));
85
86
  case "newUserSignup":
86
- return this.store.subscribe(({ isNewUser }) => isNewUser, (isNewUser) => {
87
+ return subscribeWithDelayedFireImmediately(this.store, ({ isNewUser }) => isNewUser, (isNewUser) => {
87
88
  if (isNewUser)
88
89
  listener();
89
- }, { fireImmediately: true });
90
+ });
90
91
  case "mfaStatusChanged":
91
- return this.store.subscribe(({ mfaStatus }) => mfaStatus, (mfaStatus) => listener(mfaStatus), { fireImmediately: true });
92
+ return subscribeWithDelayedFireImmediately(this.store, ({ mfaStatus }) => mfaStatus, (mfaStatus) => listener(mfaStatus));
93
+ case "emailAuthLinkingRequired":
94
+ return subscribeWithDelayedFireImmediately(this.store, ({ authLinkingStatus }) => authLinkingStatus, (authLinkingStatus) => {
95
+ if (authLinkingStatus) {
96
+ listener(authLinkingStatus.email);
97
+ }
98
+ });
92
99
  default:
93
100
  assertNever(event, `Unknown event type ${event}`);
94
101
  }
@@ -471,8 +478,8 @@ export class BaseAlchemySigner {
471
478
  * });
472
479
  * ```
473
480
  *
474
- * @param {Authorization<number, false>} unsignedAuthorization the authorization to be signed
475
- * @returns {Promise<Authorization<number, true>> | undefined} a promise that resolves to the authorization with the signature
481
+ * @param {AuthorizationRequest<number>} unsignedAuthorization the authorization to be signed
482
+ * @returns {Promise<SignedAuthorization<number>> | undefined} a promise that resolves to the authorization with the signature
476
483
  */
477
484
  Object.defineProperty(this, "signAuthorization", {
478
485
  enumerable: true,
@@ -482,7 +489,12 @@ export class BaseAlchemySigner {
482
489
  const hashedAuthorization = hashAuthorization(unsignedAuthorization);
483
490
  const signedAuthorizationHex = await this.inner.signRawMessage(hashedAuthorization);
484
491
  const signature = this.unpackSignRawMessageBytes(signedAuthorizationHex);
485
- return { ...unsignedAuthorization, ...signature };
492
+ const { address, contractAddress, ...unsignedAuthorizationRest } = unsignedAuthorization;
493
+ return {
494
+ ...unsignedAuthorizationRest,
495
+ ...signature,
496
+ address: address ?? contractAddress,
497
+ };
486
498
  })
487
499
  });
488
500
  /**
@@ -731,23 +743,17 @@ export class BaseAlchemySigner {
731
743
  return this.completeEmailAuth(params);
732
744
  }
733
745
  const { orgId, otpId, isNewUser } = await this.initOrCreateEmailUser(params.email, params.emailMode, params.multiFactors, params.redirectParams);
734
- this.sessionManager.setTemporarySession({
735
- orgId,
736
- isNewUser,
737
- });
738
- this.store.setState({
739
- status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
740
- otpId,
741
- error: null,
742
- });
746
+ this.setAwaitingEmailAuth({ orgId, otpId, isNewUser });
747
+ // Clear the auth linking status if the email has changed. This would mean
748
+ // that the previously initiated social login is not associated with the
749
+ // email which is now being used to login.
750
+ const { authLinkingStatus } = this.store.getState();
751
+ if (authLinkingStatus && authLinkingStatus.email !== params.email) {
752
+ this.store.setState({ authLinkingStatus: undefined });
753
+ }
743
754
  // We wait for the session manager to emit a connected event if
744
755
  // cross tab sessions are permitted
745
- return new Promise((resolve) => {
746
- const removeListener = this.sessionManager.on("connected", (session) => {
747
- resolve(session.user);
748
- removeListener();
749
- });
750
- });
756
+ return this.waitForConnected();
751
757
  }
752
758
  });
753
759
  Object.defineProperty(this, "authenticateWithPasskey", {
@@ -790,6 +796,7 @@ export class BaseAlchemySigner {
790
796
  configurable: true,
791
797
  writable: true,
792
798
  value: async (args) => {
799
+ this.store.setState({ authLinkingStatus: undefined });
793
800
  const params = {
794
801
  ...args,
795
802
  expirationSeconds: this.getExpirationSeconds(),
@@ -797,9 +804,12 @@ export class BaseAlchemySigner {
797
804
  if (params.mode === "redirect") {
798
805
  return this.inner.oauthWithRedirect(params);
799
806
  }
800
- else {
801
- return this.inner.oauthWithPopup(params);
807
+ const result = await this.inner.oauthWithPopup(params);
808
+ if (!isAuthLinkingPrompt(result)) {
809
+ return result;
802
810
  }
811
+ this.setAuthLinkingPrompt(result);
812
+ return this.waitForConnected();
803
813
  }
804
814
  });
805
815
  Object.defineProperty(this, "authenticateWithOtp", {
@@ -825,12 +835,7 @@ export class BaseAlchemySigner {
825
835
  });
826
836
  if (response.mfaRequired) {
827
837
  this.handleMfaRequired(response.encryptedPayload, response.multiFactors);
828
- return new Promise((resolve) => {
829
- const removeListener = this.sessionManager.on("connected", (session) => {
830
- resolve(session.user);
831
- removeListener();
832
- });
833
- });
838
+ return this.waitForConnected();
834
839
  }
835
840
  const user = await this.inner.completeAuthWithBundle({
836
841
  bundle: response.bundle,
@@ -845,9 +850,34 @@ export class BaseAlchemySigner {
845
850
  isNewUser: false,
846
851
  });
847
852
  }
853
+ const { authLinkingStatus } = this.store.getState();
854
+ if (authLinkingStatus) {
855
+ (async () => {
856
+ this.inner.addOauthProvider({
857
+ providerName: authLinkingStatus.providerName,
858
+ oidcToken: authLinkingStatus.idToken,
859
+ });
860
+ })();
861
+ }
848
862
  return user;
849
863
  }
850
864
  });
865
+ Object.defineProperty(this, "setAwaitingEmailAuth", {
866
+ enumerable: true,
867
+ configurable: true,
868
+ writable: true,
869
+ value: ({ orgId, otpId, isNewUser, }) => {
870
+ this.sessionManager.setTemporarySession({
871
+ orgId,
872
+ isNewUser,
873
+ });
874
+ this.store.setState({
875
+ status: AlchemySignerStatus.AWAITING_EMAIL_AUTH,
876
+ otpId,
877
+ error: null,
878
+ });
879
+ }
880
+ });
851
881
  Object.defineProperty(this, "handleOauthReturn", {
852
882
  enumerable: true,
853
883
  configurable: true,
@@ -1111,6 +1141,38 @@ export class BaseAlchemySigner {
1111
1141
  return this.inner.request("/v1/signer-config", {});
1112
1142
  }
1113
1143
  });
1144
+ Object.defineProperty(this, "setAuthLinkingPrompt", {
1145
+ enumerable: true,
1146
+ configurable: true,
1147
+ writable: true,
1148
+ value: (prompt) => {
1149
+ this.setAwaitingEmailAuth({
1150
+ orgId: prompt.orgId,
1151
+ otpId: prompt.otpId,
1152
+ isNewUser: false,
1153
+ });
1154
+ this.store.setState({
1155
+ authLinkingStatus: {
1156
+ email: prompt.email,
1157
+ providerName: prompt.providerName,
1158
+ idToken: prompt.idToken,
1159
+ },
1160
+ });
1161
+ }
1162
+ });
1163
+ Object.defineProperty(this, "waitForConnected", {
1164
+ enumerable: true,
1165
+ configurable: true,
1166
+ writable: true,
1167
+ value: () => {
1168
+ return new Promise((resolve) => {
1169
+ const removeListener = this.sessionManager.on("connected", (session) => {
1170
+ resolve(session.user);
1171
+ removeListener();
1172
+ });
1173
+ });
1174
+ }
1175
+ });
1114
1176
  this.inner = client;
1115
1177
  this.store = createStore(subscribeWithSelector(() => ({
1116
1178
  user: null,
@@ -1133,6 +1195,9 @@ export class BaseAlchemySigner {
1133
1195
  // then initialize so that we can catch those events
1134
1196
  this.sessionManager.initialize();
1135
1197
  this.config = this.fetchConfig();
1198
+ if (initialAuthLinkingPrompt) {
1199
+ this.setAuthLinkingPrompt(initialAuthLinkingPrompt);
1200
+ }
1136
1201
  }
1137
1202
  handleMfaRequired(encryptedPayload, multiFactors) {
1138
1203
  // Store complete MFA context in the temporary session
@@ -1283,4 +1348,42 @@ function toErrorInfo(error) {
1283
1348
  ? { name: error.name, message: error.message }
1284
1349
  : { name: "Error", message: "Unknown error" };
1285
1350
  }
1351
+ // eslint-disable-next-line jsdoc/require-param, jsdoc/require-returns
1352
+ /**
1353
+ * Zustand's `fireImmediately` option calls the listener before
1354
+ * `store.subscribe` has returned, which breaks listeners which call
1355
+ * unsubscribe, e.g.
1356
+ *
1357
+ * ```ts
1358
+ * const unsubscribe = store.subscribe(
1359
+ * selector,
1360
+ * (update) => {
1361
+ * handleUpdate(update);
1362
+ * unsubscribe();
1363
+ * },
1364
+ * { fireImmediately: true },
1365
+ * )
1366
+ * ```
1367
+ *
1368
+ * since `unsubscribe` is still undefined at the time the listener is called. To
1369
+ * prevent this, if the listener triggers before `subscribe` has returned, delay
1370
+ * the callback to a later run of the event loop.
1371
+ */
1372
+ function subscribeWithDelayedFireImmediately(store, selector, listener) {
1373
+ let subscribeHasReturned = false;
1374
+ const unsubscribe = store.subscribe(selector, (...args) => {
1375
+ if (subscribeHasReturned) {
1376
+ listener(...args);
1377
+ }
1378
+ else {
1379
+ setTimeout(() => listener(...args), 0);
1380
+ }
1381
+ }, { fireImmediately: true });
1382
+ subscribeHasReturned = true;
1383
+ return unsubscribe;
1384
+ }
1385
+ function isAuthLinkingPrompt(result) {
1386
+ return (result?.status ===
1387
+ "ACCOUNT_LINKING_CONFIRMATION_REQUIRED");
1388
+ }
1286
1389
  //# sourceMappingURL=base.js.map