Skip to content

Commit

Permalink
feat: add a usePlugin hook
Browse files Browse the repository at this point in the history
  • Loading branch information
moldy530 committed Apr 11, 2024
1 parent f1d512c commit 70bf968
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
139 changes: 139 additions & 0 deletions packages/alchemy/src/react/hooks/usePlugin.ts
@@ -0,0 +1,139 @@
import { useMutation } from "@tanstack/react-query";
import type { Chain, Client, Transport } from "viem";
import type { SupportedAccount } from "../../config";
import type { UseSmartAccountClientResult } from "./useSmartAccountClient";

export type UsePluginProps<
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TActions extends { [x: string]: (...args: any[]) => unknown } = {
[x: string]: (...args: any[]) => unknown;
}
> = {
client?: UseSmartAccountClientResult<
TTransport,
TChain,
SupportedAccount<"MultiOwnerModularAccount">
>["client"];
pluginActions: (
client: Client<
TTransport,
TChain,
SupportedAccount<"MultiOwnerModularAccount">
>
) => TActions;
};

export type UsePluginResult<
TActions extends { [x: string]: (...args: any[]) => unknown } = {
[x: string]: (...args: any[]) => unknown;
}
> = {
executePluginAction: <TFunctionName extends ExecutableFunctionName<TActions>>(
params: PluginActionParameters<TActions, TFunctionName>
) => void;
isExecutingPluginAction: boolean;
};

export type ExecutableFunctionName<
TActions extends { [x: string]: (...args: any[]) => unknown } = {
[x: string]: (...args: any[]) => unknown;
}
> = Exclude<
keyof TActions,
`encode${string}`
> extends infer functionName extends string
? [functionName] extends [never]
? string
: functionName
: string;

export type ExecutableFunctionArgs<
TActions extends { [x: string]: (...args: any[]) => unknown } = {
[x: string]: (...args: any[]) => unknown;
},
TFunctionName extends ExecutableFunctionName<TActions> = ExecutableFunctionName<TActions>
> = Parameters<TActions[TFunctionName]>;

// All of this is based one how viem's `encodeFunctionData` works
export type PluginActionParameters<
TActions extends { [x: string]: (...args: any[]) => unknown } = {
[x: string]: (...args: any[]) => unknown;
},
TFunctionName extends ExecutableFunctionName<TActions> = ExecutableFunctionName<TActions>,
allArgs = ExecutableFunctionArgs<
TActions,
TFunctionName extends ExecutableFunctionName<TActions>
? TFunctionName
: ExecutableFunctionName<TActions>
>
> = {
functionName: TFunctionName;
args: allArgs;
};

/**
* A hook that allows you to execute plugin actions with a given
* client connected to a Modular Account.
*
* @example
* ```tsx
* const Foo = () => {
* const { executePluginAction } = usePlugin({
* client: {} as any,
* pluginActions: sessionKeyPluginActions,
* plugin: SessionKeyPlugin,
* });
*
* executePluginAction({
* functionName: "isAccountSessionKey",
* args: [{ key: "0x0" }],
* });
* };
* ```
*/
export function usePlugin<
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TActions extends { [x: string]: (...args: any[]) => any } = {
[x: string]: (...args: any[]) => any;
}
>({
client,
pluginActions,
}: UsePluginProps<TTransport, TChain, TActions>): UsePluginResult<TActions> {
const { mutate, isPending: isExecutingPluginAction } = useMutation<
ReturnType<TActions[keyof TActions]>,
Error,
PluginActionParameters<TActions, ExecutableFunctionName<TActions>>
>({
mutationFn: async <TFunctionName extends ExecutableFunctionName<TActions>>({
functionName,
args,
}: PluginActionParameters<TActions, TFunctionName>) => {
if (!client) {
// TODO: use the strongly typed error here
throw new Error("no client");
}

const actions = pluginActions(client);
return actions[functionName](...args);
},
});

// TODO: add methods for installPlugin, uninstallPlugin, isPluginInstalled
// NOTE: installPlugin is actually already part of the generated plugin actions usually, so we don't need that here
// for uninstallPluin, we should start autogenerating that code as well

return {
executePluginAction: <
TFunctionName extends ExecutableFunctionName<TActions>
>(
params: PluginActionParameters<TActions, TFunctionName>
) => {
const { functionName, args } = params;
return mutate({ functionName, args });
},
isExecutingPluginAction,
};
}
2 changes: 2 additions & 0 deletions packages/alchemy/src/react/index.ts
Expand Up @@ -11,6 +11,8 @@ export type * from "./hooks/useAuthenticate.js";
export { useAuthenticate } from "./hooks/useAuthenticate.js";
export type * from "./hooks/useBundlerClient.js";
export { useBundlerClient } from "./hooks/useBundlerClient.js";
export type * from "./hooks/usePlugin.js";
export { usePlugin } from "./hooks/usePlugin.js";
export type * from "./hooks/useSigner.js";
export { useSigner } from "./hooks/useSigner.js";
export type * from "./hooks/useSignerStatus.js";
Expand Down

0 comments on commit 70bf968

Please sign in to comment.