-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add multi-chain support to account configs (#666)
- Loading branch information
Showing
17 changed files
with
351 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import type { Chain } from "viem"; | ||
import type { AlchemyAccountsConfig } from "../types"; | ||
|
||
/** | ||
* Gets the currently active chain | ||
* | ||
* @param config the account config object | ||
* @returns the currently active chain | ||
*/ | ||
export function getChain(config: AlchemyAccountsConfig): Chain { | ||
return config.coreStore.getState().chain; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import type { Chain } from "viem"; | ||
import { createAlchemyPublicRpcClient } from "../../client/rpcClient.js"; | ||
import { ChainNotFoundError } from "../errors.js"; | ||
import type { AlchemyAccountsConfig } from "../types"; | ||
|
||
/** | ||
* Allows you to change the current chain in the core store | ||
* | ||
* @param config the accounts config object | ||
* @param 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); | ||
if (connection == null) { | ||
throw new ChainNotFoundError(chain); | ||
} | ||
|
||
config.coreStore.setState(() => ({ | ||
chain, | ||
bundlerClient: createAlchemyPublicRpcClient({ | ||
chain, | ||
connectionConfig: connection, | ||
}), | ||
})); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import type { Chain } from "viem"; | ||
import type { AlchemyAccountsConfig } from "../types"; | ||
|
||
/** | ||
* Allows you to subscribe to changes of the chain in the client store. | ||
* | ||
* @param config the account config object | ||
* @returns a function which accepts an onChange callback that will be fired when the chain changes | ||
*/ | ||
export function watchChain(config: AlchemyAccountsConfig) { | ||
return (onChange: (chain: Chain) => void) => { | ||
return config.coreStore.subscribe(({ chain }) => chain, onChange); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,104 @@ | ||
import { type ConnectionConfig } from "@alchemy/aa-core"; | ||
import type { Chain } from "viem"; | ||
import { subscribeWithSelector } from "zustand/middleware"; | ||
import { | ||
createJSONStorage, | ||
persist, | ||
subscribeWithSelector, | ||
} from "zustand/middleware"; | ||
import { createStore } from "zustand/vanilla"; | ||
import { createAlchemyPublicRpcClient } from "../../client/rpcClient.js"; | ||
import type { Connection } from "../types.js"; | ||
import { bigintMapReplacer } from "../utils/replacer.js"; | ||
import { bigintMapReviver } from "../utils/reviver.js"; | ||
import { DEFAULT_STORAGE_KEY } from "./client.js"; | ||
import type { CoreState, CoreStore } from "./types.js"; | ||
|
||
export type CreateCoreStoreParams = { | ||
connection: ConnectionConfig; | ||
connections: Connection[]; | ||
chain: Chain; | ||
storage?: Storage; | ||
ssr?: boolean; | ||
}; | ||
|
||
export const createCoreStore = ({ | ||
connection, | ||
chain, | ||
}: CreateCoreStoreParams) => { | ||
const bundlerClient = createAlchemyPublicRpcClient({ | ||
/** | ||
* Create the core store for alchemy accounts. This store contains the bundler client | ||
* as well as the chain configs (including the initial chain to use) | ||
* | ||
* @param params connections configs | ||
* @param params.connections a collection of chains and their connection configs | ||
* @param params.chain the initial chain to use | ||
* @param params.storage the storage to use for persisting the state | ||
* @param params.ssr whether the store is being created on the server | ||
* @returns the core store | ||
*/ | ||
export const createCoreStore = (params: CreateCoreStoreParams): CoreStore => { | ||
const { | ||
connections, | ||
chain, | ||
connectionConfig: connection, | ||
}); | ||
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(() => ({ | ||
bundlerClient, | ||
})) | ||
subscribeWithSelector( | ||
storage | ||
? persist(() => createInitialCoreState(connections, chain), { | ||
name: `${DEFAULT_STORAGE_KEY}:core`, | ||
storage: createJSONStorage<CoreState>(() => 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); | ||
}, | ||
}), | ||
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<number, Connection>()); | ||
|
||
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, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { useMutation } from "@tanstack/react-query"; | ||
import { useSyncExternalStore } from "react"; | ||
import type { Chain } from "viem"; | ||
import { getChain } from "../../config/actions/getChain.js"; | ||
import { setChain as setChainInternal } from "../../config/actions/setChain.js"; | ||
import { watchChain } from "../../config/actions/watchChain.js"; | ||
import { useAlchemyAccountContext } from "../context.js"; | ||
import type { BaseHookMutationArgs } from "../types.js"; | ||
|
||
export type UseChainParams = BaseHookMutationArgs<void, { chain: Chain }>; | ||
|
||
export interface UseChainResult { | ||
chain: Chain; | ||
setChain: (chain: Chain) => void; | ||
isSettingChain: boolean; | ||
} | ||
|
||
/** | ||
* A hook that returns the current chain as well as a function to set the chain | ||
* | ||
* @param mutationArgs the mutation arguments | ||
* @returns an object containing the current chain and a function to set the chain as well as loading state of setting the chain | ||
*/ | ||
export function useChain({ ...mutationArgs }: UseChainParams) { | ||
const { config } = useAlchemyAccountContext(); | ||
|
||
const chain = useSyncExternalStore( | ||
watchChain(config), | ||
() => getChain(config), | ||
() => getChain(config) | ||
); | ||
|
||
const { mutate: setChain, isPending } = useMutation({ | ||
mutationFn: ({ chain }: { chain: Chain }) => | ||
setChainInternal(config, chain), | ||
...mutationArgs, | ||
}); | ||
|
||
return { | ||
chain, | ||
setChain, | ||
isSettingChain: isPending, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.