diff --git a/account-kit/core/src/actions/createAccount.ts b/account-kit/core/src/actions/createAccount.ts index 77b2ea7d32..cdb6cb222c 100644 --- a/account-kit/core/src/actions/createAccount.ts +++ b/account-kit/core/src/actions/createAccount.ts @@ -59,8 +59,8 @@ export async function createAccount( { type, accountParams: params }: CreateAccountParams, config: AlchemyAccountsConfig ): Promise { - const clientStore = config.clientStore; - const accounts = clientStore.getState().accounts; + const store = config.store; + const accounts = store.getState().accounts; if (!accounts) { throw new ClientOnlyPropertyError("account"); } @@ -79,7 +79,7 @@ export async function createAccount( if (cachedAccount.status !== "RECONNECTING" && cachedAccount.account) { return cachedAccount.account; } - const cachedConfig = clientStore.getState().accountConfigs[chain.id]?.[type]; + const cachedConfig = store.getState().accountConfigs[chain.id]?.[type]; const accountPromise = (() => { switch (type) { @@ -108,7 +108,7 @@ export async function createAccount( })(); if (cachedAccount.status !== "RECONNECTING") { - clientStore.setState(() => ({ + store.setState(() => ({ accounts: { ...accounts, [chain.id]: { @@ -125,7 +125,7 @@ export async function createAccount( try { const account = await accountPromise; const initCode = await account.getInitCode(); - clientStore.setState((state) => ({ + store.setState((state) => ({ accounts: { ...accounts, [chain.id]: { @@ -149,7 +149,7 @@ export async function createAccount( }, })); } catch (error) { - clientStore.setState(() => ({ + store.setState(() => ({ accounts: { ...accounts, [chain.id]: { diff --git a/account-kit/core/src/actions/getAccount.ts b/account-kit/core/src/actions/getAccount.ts index 22f43144c5..b52b62b385 100644 --- a/account-kit/core/src/actions/getAccount.ts +++ b/account-kit/core/src/actions/getAccount.ts @@ -31,7 +31,7 @@ export const getAccount = ( { type }: GetAccountParams, config: AlchemyAccountsConfig ): GetAccountResult => { - const accounts = config.clientStore.getState().accounts; + const accounts = config.store.getState().accounts; const chain = getChain(config); const account = accounts?.[chain.id]?.[type]; if (!account) { diff --git a/account-kit/core/src/actions/getBundlerClient.ts b/account-kit/core/src/actions/getBundlerClient.ts index 896461c557..95f2ba22b8 100644 --- a/account-kit/core/src/actions/getBundlerClient.ts +++ b/account-kit/core/src/actions/getBundlerClient.ts @@ -18,5 +18,5 @@ import type { AlchemyAccountsConfig } from "../types"; export const getBundlerClient = ( config: AlchemyAccountsConfig ): ClientWithAlchemyMethods => { - return config.coreStore.getState().bundlerClient; + return config.store.getState().bundlerClient; }; diff --git a/account-kit/core/src/actions/getChain.ts b/account-kit/core/src/actions/getChain.ts index 54d23fad68..eff42fafe9 100644 --- a/account-kit/core/src/actions/getChain.ts +++ b/account-kit/core/src/actions/getChain.ts @@ -8,5 +8,5 @@ import type { AlchemyAccountsConfig } from "../types"; * @returns {Chain} the currently active chain */ export function getChain(config: AlchemyAccountsConfig): Chain { - return config.coreStore.getState().chain; + return config.store.getState().chain; } diff --git a/account-kit/core/src/actions/getConnection.ts b/account-kit/core/src/actions/getConnection.ts index 2ec23e7550..a35b4aadc1 100644 --- a/account-kit/core/src/actions/getConnection.ts +++ b/account-kit/core/src/actions/getConnection.ts @@ -18,7 +18,7 @@ import { getChain } from "./getChain.js"; */ export function getConnection(config: AlchemyAccountsConfig): Connection { const chain = getChain(config); - const connection = config.coreStore.getState().connections.get(chain.id); + const connection = config.store.getState().connections.get(chain.id); if (!connection) { throw new ChainNotFoundError(chain); } diff --git a/account-kit/core/src/actions/getSigner.ts b/account-kit/core/src/actions/getSigner.ts index e67ca25d1c..beabe56f43 100644 --- a/account-kit/core/src/actions/getSigner.ts +++ b/account-kit/core/src/actions/getSigner.ts @@ -20,5 +20,5 @@ import type { AlchemyAccountsConfig } from "../types.js"; export const getSigner = ( config: AlchemyAccountsConfig ): AlchemyWebSigner | null => { - return config.clientStore.getState().signer ?? null; + return config.store.getState().signer ?? null; }; diff --git a/account-kit/core/src/actions/getSignerStatus.ts b/account-kit/core/src/actions/getSignerStatus.ts index 4be65a304d..fd156b8b8a 100644 --- a/account-kit/core/src/actions/getSignerStatus.ts +++ b/account-kit/core/src/actions/getSignerStatus.ts @@ -16,5 +16,5 @@ import type { AlchemyAccountsConfig } from "../types"; * @returns {SignerStatus} The current signer status from the client store */ export const getSignerStatus = (config: AlchemyAccountsConfig) => { - return config.clientStore.getState().signerStatus; + return config.store.getState().signerStatus; }; diff --git a/account-kit/core/src/actions/getUser.ts b/account-kit/core/src/actions/getUser.ts index 6d434bcc35..b17ac10ce8 100644 --- a/account-kit/core/src/actions/getUser.ts +++ b/account-kit/core/src/actions/getUser.ts @@ -18,8 +18,10 @@ export type GetUserResult = (User & { type: "eoa" | "sca" }) | null; * @param {AlchemyAccountsConfig} config the account config containing app state * @returns {GetUserResult} the user if the signer or an EOA are connected */ -export const getUser = (config: AlchemyAccountsConfig): GetUserResult => { - const user = config.clientStore.getState().user; +export const getUser = ( + config: AlchemyAccountsConfig +): (User & { type: "eoa" | "sca" }) | null => { + const user = config.store.getState().user; if (user == null) return user ?? null; // @ts-ignore diff --git a/account-kit/core/src/actions/reconnect.ts b/account-kit/core/src/actions/reconnect.ts index 7249d60dcb..c99d7233c3 100644 --- a/account-kit/core/src/actions/reconnect.ts +++ b/account-kit/core/src/actions/reconnect.ts @@ -1,4 +1,4 @@ -import { createSigner } from "../store/client.js"; +import { createSigner } from "../store/store.js"; import type { AlchemyAccountsConfig } from "../types.js"; import { createAccount } from "./createAccount.js"; import { getChain } from "./getChain.js"; @@ -18,13 +18,13 @@ import { getChain } from "./getChain.js"; * @param {AlchemyAccountsConfig} config the account config which contains the client store */ export async function reconnect(config: AlchemyAccountsConfig) { - const { clientStore } = config; - const signerConfig = clientStore.getState().config; - const accountConfigs = clientStore.getState().accountConfigs; + const { store } = config; + const signerConfig = store.getState().config; + const accountConfigs = store.getState().accountConfigs; - const signer = clientStore.getState().signer ?? createSigner(signerConfig); - if (!clientStore.getState().signer) { - clientStore.setState({ + const signer = store.getState().signer ?? createSigner(signerConfig); + if (!store.getState().signer) { + store.setState({ signer, }); } @@ -56,7 +56,7 @@ export async function reconnect(config: AlchemyAccountsConfig) { }); const unsubDisconnected = signer.on("disconnected", () => { - clientStore.setState({ + store.setState({ accountConfigs: {}, }); unsubDisconnected(); diff --git a/account-kit/core/src/actions/setChain.ts b/account-kit/core/src/actions/setChain.ts index 3b5103c05d..b15c422732 100644 --- a/account-kit/core/src/actions/setChain.ts +++ b/account-kit/core/src/actions/setChain.ts @@ -21,14 +21,14 @@ import type { AlchemyAccountsConfig } from "../types.js"; * @param {Chain} chain the chain to change to. It must be present in the connections config object */ export async function setChain(config: AlchemyAccountsConfig, chain: Chain) { - const connection = config.coreStore.getState().connections.get(chain.id); + const connection = config.store.getState().connections.get(chain.id); if (connection == null) { throw new ChainNotFoundError(chain); } await switchChain(config._internal.wagmiConfig, { chainId: chain.id }); - config.coreStore.setState(() => ({ + config.store.setState(() => ({ chain, bundlerClient: createAlchemyPublicRpcClient({ chain, diff --git a/account-kit/core/src/actions/watchAccount.ts b/account-kit/core/src/actions/watchAccount.ts index b69ce5331f..648c615a62 100644 --- a/account-kit/core/src/actions/watchAccount.ts +++ b/account-kit/core/src/actions/watchAccount.ts @@ -26,13 +26,13 @@ export const watchAccount = config: AlchemyAccountsConfig ) => (onChange: (account: GetAccountResult) => void) => { - const accounts = config.clientStore.getState().accounts; + const accounts = config.store.getState().accounts; if (!accounts) { throw new ClientOnlyPropertyError("account"); } const chain = getChain(config); - return config.clientStore.subscribe( + return config.store.subscribe( // this should be available on the client now because of the check above ({ accounts }) => accounts![chain.id][type], onChange, diff --git a/account-kit/core/src/actions/watchBundlerClient.ts b/account-kit/core/src/actions/watchBundlerClient.ts index 05c5053c24..47313c7fc3 100644 --- a/account-kit/core/src/actions/watchBundlerClient.ts +++ b/account-kit/core/src/actions/watchBundlerClient.ts @@ -19,7 +19,7 @@ import type { AlchemyAccountsConfig } from "../types"; export const watchBundlerClient = (config: AlchemyAccountsConfig) => (onChange: (bundlerClient: ClientWithAlchemyMethods) => void) => { - return config.coreStore.subscribe( + return config.store.subscribe( ({ bundlerClient }) => bundlerClient, onChange ); diff --git a/account-kit/core/src/actions/watchChain.ts b/account-kit/core/src/actions/watchChain.ts index e28e2ca3df..0994fb8cf9 100644 --- a/account-kit/core/src/actions/watchChain.ts +++ b/account-kit/core/src/actions/watchChain.ts @@ -18,6 +18,6 @@ import type { AlchemyAccountsConfig } from "../types"; */ export function watchChain(config: AlchemyAccountsConfig) { return (onChange: (chain: Chain) => void) => { - return config.coreStore.subscribe(({ chain }) => chain, onChange); + return config.store.subscribe(({ chain }) => chain, onChange); }; } diff --git a/account-kit/core/src/actions/watchConnection.ts b/account-kit/core/src/actions/watchConnection.ts index cf8233291b..38006cd30b 100644 --- a/account-kit/core/src/actions/watchConnection.ts +++ b/account-kit/core/src/actions/watchConnection.ts @@ -17,12 +17,10 @@ import type { AlchemyAccountsConfig, Connection } from "../types"; */ export function watchConnection(config: AlchemyAccountsConfig) { return (onChange: (connection: Connection) => void) => { - return config.coreStore.subscribe( + return config.store.subscribe( ({ chain }) => chain, (chain) => { - const connection = config.coreStore - .getState() - .connections.get(chain.id); + const connection = config.store.getState().connections.get(chain.id); if (connection) { onChange(connection); diff --git a/account-kit/core/src/actions/watchSigner.ts b/account-kit/core/src/actions/watchSigner.ts index 514a7c7347..1975d96a9e 100644 --- a/account-kit/core/src/actions/watchSigner.ts +++ b/account-kit/core/src/actions/watchSigner.ts @@ -19,5 +19,5 @@ import type { AlchemyAccountsConfig } from "../types"; export const watchSigner = (config: AlchemyAccountsConfig) => (onChange: (signer?: AlchemyWebSigner) => void) => { - return config.clientStore.subscribe(({ signer }) => signer, onChange); + return config.store.subscribe(({ signer }) => signer, onChange); }; diff --git a/account-kit/core/src/actions/watchSignerStatus.ts b/account-kit/core/src/actions/watchSignerStatus.ts index d9476dc235..dbe5af6748 100644 --- a/account-kit/core/src/actions/watchSignerStatus.ts +++ b/account-kit/core/src/actions/watchSignerStatus.ts @@ -19,7 +19,7 @@ import type { AlchemyAccountsConfig } from "../types.js"; export const watchSignerStatus = (config: AlchemyAccountsConfig) => (onChange: (status: SignerStatus) => void) => { - return config.clientStore.subscribe( + return config.store.subscribe( ({ signerStatus }) => signerStatus, onChange, { equalityFn: (a, b) => a.status === b.status } diff --git a/account-kit/core/src/actions/watchUser.ts b/account-kit/core/src/actions/watchUser.ts index 9d5bd059f7..51baf3259d 100644 --- a/account-kit/core/src/actions/watchUser.ts +++ b/account-kit/core/src/actions/watchUser.ts @@ -18,7 +18,7 @@ import type { AlchemyAccountsConfig } from "../types"; */ export const watchUser = (config: AlchemyAccountsConfig) => (onChange: (user?: User) => void) => { - return config.clientStore.subscribe(({ user }) => user, onChange, { + return config.store.subscribe(({ user }) => user, onChange, { equalityFn: (a, b) => a?.userId === b?.userId, }); }; diff --git a/account-kit/core/src/createConfig.ts b/account-kit/core/src/createConfig.ts index b0cfc60ae3..18b346d9d7 100644 --- a/account-kit/core/src/createConfig.ts +++ b/account-kit/core/src/createConfig.ts @@ -1,14 +1,13 @@ import { ConnectionConfigSchema } from "@aa-sdk/core"; import { DEFAULT_SESSION_MS } from "@account-kit/signer"; import { createStorage, createConfig as createWagmiConfig } from "@wagmi/core"; -import { createClientStore } from "./store/client.js"; -import { createCoreStore } from "./store/core.js"; import { DEFAULT_STORAGE_KEY } from "./store/types.js"; import type { AlchemyAccountsConfig, Connection, CreateConfigProps, } from "./types.js"; +import { createAccountKitStore } from "./store/store.js"; export const DEFAULT_IFRAME_CONTAINER_ID = "alchemy-signer-iframe-container"; @@ -64,14 +63,9 @@ export const createConfig = ( }); } - const coreStore = createCoreStore({ + const store = createAccountKitStore({ connections, chain, - storage: storage?.(), - ssr, - }); - - const clientStore = createClientStore({ client: { connection: signerConnection ?? connections[0], iframeConfig, @@ -84,15 +78,13 @@ export const createConfig = ( ? { sessionLength: sessionConfig.expirationTimeMs } : undefined ), - // TODO: this is duplicated from the core store - chains: connections.map((x) => x.chain), ssr, }); const wagmiConfig = createWagmiConfig({ connectors, chains: [chain, ...connections.map((c) => c.chain)], - client: () => config.coreStore.getState().bundlerClient, + client: () => config.store.getState().bundlerClient, storage: createStorage({ key: `${DEFAULT_STORAGE_KEY}:wagmi`, storage: storage @@ -105,8 +97,7 @@ export const createConfig = ( }); const config: AlchemyAccountsConfig = { - coreStore, - clientStore, + store: store, _internal: { ssr, wagmiConfig, diff --git a/account-kit/core/src/hydrate.ts b/account-kit/core/src/hydrate.ts index 4ac0d3fe05..374069b57e 100644 --- a/account-kit/core/src/hydrate.ts +++ b/account-kit/core/src/hydrate.ts @@ -2,13 +2,13 @@ import type { Address } from "@aa-sdk/core"; import { AlchemySignerStatus } from "@account-kit/signer"; import { hydrate as wagmi_hydrate } from "@wagmi/core"; import { reconnect } from "./actions/reconnect.js"; +import type { AccountState, StoreState, StoredState } from "./store/types.js"; +import type { AlchemyAccountsConfig, SupportedAccountTypes } from "./types.js"; import { convertSignerStatusToState, createDefaultAccountState, defaultAccountState, -} from "./store/client.js"; -import type { AccountState, ClientState, StoredState } from "./store/types.js"; -import type { AlchemyAccountsConfig, SupportedAccountTypes } from "./types.js"; +} from "./store/store.js"; export type HydrateResult = { onMount: () => Promise; @@ -40,12 +40,12 @@ export function hydrate( ? initialState.alchemy : initialState; - if (initialAlchemyState && !config.clientStore.persist.hasHydrated()) { + if (initialAlchemyState && !config.store.persist.hasHydrated()) { const { accountConfigs, signerStatus, ...rest } = initialAlchemyState; const shouldReconnectAccounts = signerStatus.isConnected || signerStatus.isAuthenticating; - config.clientStore.setState({ + config.store.setState({ ...rest, accountConfigs, signerStatus: convertSignerStatusToState( @@ -74,8 +74,7 @@ export function hydrate( return { async onMount() { if (config._internal.ssr) { - await config.clientStore.persist.rehydrate(); - await config.coreStore.persist.rehydrate(); + await config.store.persist.rehydrate(); } await wagmi_onMount(); @@ -95,13 +94,13 @@ const reconnectingState = ( }); const hydrateAccountState = ( - accountConfigs: ClientState["accountConfigs"], + accountConfigs: StoreState["accountConfigs"], shouldReconnectAccounts: boolean, config: AlchemyAccountsConfig -): ClientState["accounts"] => { - const chains = Array.from( - config.coreStore.getState().connections.entries() - ).map(([, cnx]) => cnx.chain); +): StoreState["accounts"] => { + const chains = Array.from(config.store.getState().connections.entries()).map( + ([, cnx]) => cnx.chain + ); const initialState = createDefaultAccountState(chains); return Object.entries(accountConfigs).reduce((acc, [chainKey, config]) => { diff --git a/account-kit/core/src/index.ts b/account-kit/core/src/index.ts index 0d4d8afda4..efb75398a4 100644 --- a/account-kit/core/src/index.ts +++ b/account-kit/core/src/index.ts @@ -33,7 +33,7 @@ export type * from "./createConfig.js"; export { DEFAULT_IFRAME_CONTAINER_ID, createConfig } from "./createConfig.js"; export { ClientOnlyPropertyError } from "./errors.js"; export { hydrate } from "./hydrate.js"; -export { defaultAccountState } from "./store/client.js"; +export { defaultAccountState } from "./store/store.js"; export type { SignerStatus } from "./store/types.js"; export type * from "./types.js"; export { cookieStorage, cookieToInitialState } from "./utils/cookies.js"; diff --git a/account-kit/core/src/store/core.ts b/account-kit/core/src/store/core.ts deleted file mode 100644 index 0e3dbe2b5b..0000000000 --- a/account-kit/core/src/store/core.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { createAlchemyPublicRpcClient } from "@account-kit/infra"; -import type { Chain } from "viem"; -import { - createJSONStorage, - persist, - subscribeWithSelector, -} from "zustand/middleware"; -import { createStore } from "zustand/vanilla"; -import type { Connection } from "../types.js"; -import { bigintMapReplacer } from "../utils/replacer.js"; -import { bigintMapReviver } from "../utils/reviver.js"; -import { - DEFAULT_STORAGE_KEY, - type CoreState, - type CoreStore, -} from "./types.js"; - -export type CreateCoreStoreParams = { - connections: Connection[]; - chain: Chain; - storage?: Storage; - ssr?: boolean; -}; - -export const createCoreStore = (params: CreateCoreStoreParams): CoreStore => { - const { - connections, - chain, - storage = typeof window !== "undefined" ? localStorage : undefined, - ssr, - } = params; - - // State defined in here should work either on the server or on the client - // bundler client for example can be used in either setting to make RPC calls - const coreStore = createStore( - subscribeWithSelector( - storage - ? persist(() => createInitialCoreState(connections, chain), { - name: `${DEFAULT_STORAGE_KEY}:core`, - storage: createJSONStorage(() => storage, { - replacer: (key, value) => { - if (key === "bundlerClient") { - const client = value as CoreState["bundlerClient"]; - return { - connection: connections.find( - (x) => x.chain.id === client.chain.id - ), - }; - } - return bigintMapReplacer(key, value); - }, - reviver: (key, value) => { - if (key === "bundlerClient") { - const connection = value as Connection; - return createAlchemyPublicRpcClient({ - chain: connection.chain, - connectionConfig: connection, - }); - } - - return bigintMapReviver(key, value); - }, - }), - version: 1, - skipHydration: ssr, - }) - : () => createInitialCoreState(connections, chain) - ) - ); - - return coreStore; -}; - -const createInitialCoreState = ( - connections: Connection[], - chain: Chain -): CoreState => { - const connectionMap = connections.reduce((acc, connection) => { - acc.set(connection.chain.id, connection); - return acc; - }, new Map()); - - if (!connectionMap.has(chain.id)) { - throw new Error("Chain not found in connections"); - } - - const bundlerClient = createAlchemyPublicRpcClient({ - chain, - connectionConfig: connectionMap.get(chain.id)!, - }); - - return { - bundlerClient, - chain, - connections: connectionMap, - }; -}; diff --git a/account-kit/core/src/store/client.ts b/account-kit/core/src/store/store.ts similarity index 58% rename from account-kit/core/src/store/client.ts rename to account-kit/core/src/store/store.ts index de4c826110..91873d986d 100644 --- a/account-kit/core/src/store/client.ts +++ b/account-kit/core/src/store/store.ts @@ -1,4 +1,5 @@ import type { NoUndefined } from "@aa-sdk/core"; +import { createAlchemyPublicRpcClient } from "@account-kit/infra"; import { AlchemySignerStatus, AlchemyWebSigner } from "@account-kit/signer"; import type { Chain } from "viem"; import { @@ -8,48 +9,121 @@ import { } from "zustand/middleware"; import { createStore } from "zustand/vanilla"; import { DEFAULT_IFRAME_CONTAINER_ID } from "../createConfig.js"; -import type { SupportedAccountTypes } from "../types.js"; +import type { Connection, SupportedAccountTypes } from "../types.js"; import { bigintMapReplacer } from "../utils/replacer.js"; import { bigintMapReviver } from "../utils/reviver.js"; import { DEFAULT_STORAGE_KEY, type AccountState, - type ClientState, - type ClientStore, - type CreateClientStoreParams, + type ClientStoreConfig, + type CreateAccountKitStoreParams, type SignerStatus, + type Store, + type StoreState, } from "./types.js"; -export const createClientStore = (config: CreateClientStoreParams) => { +export const createAccountKitStore = ( + params: CreateAccountKitStoreParams +): Store => { const { + connections, storage = typeof window !== "undefined" ? localStorage : undefined, ssr, - } = config; + } = params; - const clientStore = createStore( + // State defined in here should work either on the server or on the client + // bundler client for example can be used in either setting to make RPC calls + const store = createStore( subscribeWithSelector( storage - ? persist(() => createInitialClientState(config), { + ? persist(() => createInitialStoreState(params), { name: DEFAULT_STORAGE_KEY, - storage: createJSONStorage(() => storage, { - replacer: bigintMapReplacer, - reviver: bigintMapReviver, + storage: createJSONStorage(() => storage, { + replacer: (key, value) => { + if (key === "bundlerClient") { + const client = value as StoreState["bundlerClient"]; + return { + connection: connections.find( + (x) => x.chain.id === client.chain.id + ), + }; + } + return bigintMapReplacer(key, value); + }, + reviver: (key, value) => { + if (key === "bundlerClient") { + const connection = value as Connection; + return createAlchemyPublicRpcClient({ + chain: connection.chain, + connectionConfig: connection, + }); + } + + return bigintMapReviver(key, value); + }, }), skipHydration: ssr, partialize: ({ signer, accounts, ...writeableState }) => writeableState, - version: 1, + version: 2, }) - : () => createInitialClientState(config) + : () => createInitialStoreState(params) ) ); - addClientSideStoreListeners(clientStore); + addClientSideStoreListeners(store); - return clientStore; + return store; }; -export const createSigner = (params: CreateClientStoreParams) => { +const createInitialStoreState = ( + params: CreateAccountKitStoreParams +): StoreState => { + const { connections, chain, client, sessionConfig } = params; + const connectionMap = connections.reduce((acc, connection) => { + acc.set(connection.chain.id, connection); + return acc; + }, new Map()); + + if (!connectionMap.has(chain.id)) { + throw new Error("Chain not found in connections"); + } + + const bundlerClient = createAlchemyPublicRpcClient({ + chain, + connectionConfig: connectionMap.get(chain.id)!, + }); + const chains = connections.map((c) => c.chain); + const accountConfigs = createEmptyAccountConfigState(chains); + const baseState: StoreState = { + bundlerClient, + chain, + connections: connectionMap, + accountConfigs, + config: { client, sessionConfig }, + signerStatus: convertSignerStatusToState(AlchemySignerStatus.INITIALIZING), + }; + + if (typeof window === "undefined") { + return baseState; + } + + const accounts = createDefaultAccountState(chains); + + return { + ...baseState, + accounts, + }; +}; + +/** + * Given initial client store parameters, it initializes an AlchemySigner instance. + * This should only be called on the client. + * + * @param {CreateClientStoreParams} params to configure and create the signer + * @returns {AlchemySigner} an instance of the AlchemySigner + */ +export const createSigner = (params: ClientStoreConfig) => { const { client, sessionConfig } = params; const { iframeContainerId } = client.iframeConfig ?? { iframeContainerId: DEFAULT_IFRAME_CONTAINER_ID, @@ -126,28 +200,7 @@ export const defaultAccountState = < T extends SupportedAccountTypes >(): AccountState => staticState; -const createInitialClientState = ( - params: CreateClientStoreParams -): ClientState => { - const accountConfigs = createEmptyAccountConfigState(params.chains); - const baseState = { - accountConfigs, - config: params, - signerStatus: convertSignerStatusToState(AlchemySignerStatus.INITIALIZING), - }; - - if (typeof window === "undefined") { - return baseState; - } - - const accounts = createDefaultAccountState(params.chains); - return { - accounts, - ...baseState, - }; -}; - -const addClientSideStoreListeners = (store: ClientStore) => { +const addClientSideStoreListeners = (store: Store) => { if (typeof window === "undefined") { return; } @@ -163,12 +216,13 @@ const addClientSideStoreListeners = (store: ClientStore) => { signer.on("connected", (user) => store.setState({ user })); signer.on("disconnected", () => { + const chains = [...store.getState().connections.values()].map( + (c) => c.chain + ); store.setState({ user: undefined, - accountConfigs: createEmptyAccountConfigState( - store.getState().config.chains - ), - accounts: createDefaultAccountState(store.getState().config.chains), + accountConfigs: createEmptyAccountConfigState(chains), + accounts: createDefaultAccountState(chains), }); }); }, @@ -180,7 +234,7 @@ const createEmptyAccountConfigState = (chains: Chain[]) => { return chains.reduce((acc, chain) => { acc[chain.id] = {}; return acc; - }, {} as ClientState["accountConfigs"]); + }, {} as StoreState["accountConfigs"]); }; export const createDefaultAccountState = (chains: Chain[]) => { @@ -191,5 +245,5 @@ export const createDefaultAccountState = (chains: Chain[]) => { defaultAccountState<"MultiOwnerModularAccount">(), }; return acc; - }, {} as NoUndefined); + }, {} as NoUndefined); }; diff --git a/account-kit/core/src/store/types.ts b/account-kit/core/src/store/types.ts index 04f7e9a239..1fd1fdaf3b 100644 --- a/account-kit/core/src/store/types.ts +++ b/account-kit/core/src/store/types.ts @@ -37,15 +37,12 @@ export type AccountState = | { status: "DISCONNECTED"; account: undefined } | { status: "ERROR"; account: undefined; error: Error }; -export type CreateClientStoreParams = { +export type ClientStoreConfig = { client: PartialBy< Exclude, "iframeConfig" >; - chains: Chain[]; sessionConfig?: AlchemySignerParams["sessionConfig"]; - storage?: Storage; - ssr?: boolean; }; export type SignerStatus = { @@ -56,7 +53,24 @@ export type SignerStatus = { isDisconnected: boolean; }; -export type ClientState = { +export type StoredState = { + alchemy: Omit; + wagmi?: WagmiState; +}; + +export type CreateAccountKitStoreParams = ClientStoreConfig & { + connections: Connection[]; + chain: Chain; + client: PartialBy< + Exclude, + "iframeConfig" + >; + sessionConfig?: AlchemySignerParams["sessionConfig"]; + storage?: Storage; + ssr?: boolean; +}; + +export type StoreState = { // non-serializable // getting this state should throw an error if not on the client signer?: AlchemyWebSigner; @@ -68,7 +82,7 @@ export type ClientState = { // serializable state // NOTE: in some cases this can be serialized to cookie storage // be mindful of how big this gets. cookie limit 4KB - config: CreateClientStoreParams; + config: ClientStoreConfig; accountConfigs: { [chain: number]: Partial<{ [key in SupportedAccountTypes]: AccountConfig; @@ -76,27 +90,12 @@ export type ClientState = { }; user?: User; signerStatus: SignerStatus; -}; - -export type ClientStore = Mutate< - StoreApi, - [["zustand/subscribeWithSelector", never], ["zustand/persist", ClientState]] ->; - -export type CoreState = { bundlerClient: ClientWithAlchemyMethods; chain: Chain; connections: Map; }; -export type CoreStore = Mutate< - StoreApi, - [["zustand/subscribeWithSelector", never], ["zustand/persist", CoreState]] +export type Store = Mutate< + StoreApi, + [["zustand/subscribeWithSelector", never], ["zustand/persist", StoreState]] >; - -export type StoredState = - | Omit - | { - alchemy: Omit; - wagmi?: WagmiState; - }; diff --git a/account-kit/core/src/types.ts b/account-kit/core/src/types.ts index 0d72b63754..361a4dee2e 100644 --- a/account-kit/core/src/types.ts +++ b/account-kit/core/src/types.ts @@ -13,7 +13,7 @@ import type { CreateConnectorFn } from "@wagmi/core"; import { type Config as WagmiConfig } from "@wagmi/core"; import type { Chain } from "viem"; import type { PartialBy } from "viem/chains"; -import type { ClientStore, CoreStore, StoredState } from "./store/types"; +import type { Store, StoredState } from "./store/types"; export type SupportedAccountTypes = "LightAccount" | "MultiOwnerModularAccount"; @@ -29,8 +29,7 @@ export type SupportedAccount = : never; export type AlchemyAccountsConfig = { - coreStore: CoreStore; - clientStore: ClientStore; + store: Store; _internal: { wagmiConfig: WagmiConfig; ssr?: boolean; diff --git a/account-kit/core/src/utils/cookies.ts b/account-kit/core/src/utils/cookies.ts index a6274b1e9f..2832b30d31 100644 --- a/account-kit/core/src/utils/cookies.ts +++ b/account-kit/core/src/utils/cookies.ts @@ -1,7 +1,7 @@ import { DEFAULT_SESSION_MS } from "@account-kit/signer"; import { cookieToInitialState as wagmiCookieToInitialState } from "@wagmi/core"; import Cookies from "js-cookie"; -import type { ClientState, StoredState } from "../store/types.js"; +import type { StoreState, StoredState } from "../store/types.js"; import type { AlchemyAccountsConfig } from "../types.js"; import { deserialize } from "./deserialize.js"; @@ -69,7 +69,7 @@ export function cookieToInitialState( if (!state) return; const alchemyClientState = deserialize<{ - state: Omit; + state: Omit; }>(state).state; const wagmiClientState = wagmiCookieToInitialState(