Skip to content

Commit

Permalink
feat: add a useClientActions hook
Browse files Browse the repository at this point in the history
  • Loading branch information
moldy530 committed Apr 11, 2024
1 parent f1d512c commit 2661caa
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 0 deletions.
135 changes: 135 additions & 0 deletions packages/alchemy/src/react/hooks/useClientActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { useMutation } from "@tanstack/react-query";
import { useCallback } from "react";
import type { Chain, Client, Transport } from "viem";
import type { SupportedAccounts } from "../../config";
import type { UseSmartAccountClientResult } from "./useSmartAccountClient";

export type UseClientActionsProps<
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,
SupportedAccounts
>["client"];
actions: (client: Client<TTransport, TChain, SupportedAccounts>) => TActions;
};

export type UseClientActionsResult<
TActions extends { [x: string]: (...args: any[]) => unknown } = {
[x: string]: (...args: any[]) => unknown;
}
> = {
executeAction: <TFunctionName extends ExecutableFunctionName<TActions>>(
params: ClientActionParameters<TActions, TFunctionName>
) => void;
isExecutingAction: boolean;
};

export type ExecutableFunctionName<
TActions extends { [x: string]: (...args: any[]) => unknown } = {
[x: string]: (...args: any[]) => unknown;
}
> = keyof TActions 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 ClientActionParameters<
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 leverage client decorators to execute actions
* and await them in your UX. This is particularly useful for using Plugins
* with Modular Accounts.
*
* @example
* ```tsx
* const Foo = () => {
* const { client } = useSmartAccountClient({ type: "MultiOwnerModularAccount" });
* const { executePluginAction } = useClientActions({
* client,
* pluginActions: sessionKeyPluginActions,
* });
*
* executePluginAction({
* functionName: "isAccountSessionKey",
* args: [{ key: "0x0" }],
* });
* };
* ```
*/
export function useClientActions<
TTransport extends Transport = Transport,
TChain extends Chain | undefined = Chain | undefined,
TActions extends { [x: string]: (...args: any[]) => any } = {
[x: string]: (...args: any[]) => any;
}
>({
client,
actions: pluginActions,
}: UseClientActionsProps<
TTransport,
TChain,
TActions
>): UseClientActionsResult<TActions> {
const { mutate, isPending: isExecutingAction } = useMutation<
ReturnType<TActions[keyof TActions]>,
Error,
ClientActionParameters<TActions, ExecutableFunctionName<TActions>>
>({
mutationFn: async <TFunctionName extends ExecutableFunctionName<TActions>>({
functionName,
args,
}: ClientActionParameters<TActions, TFunctionName>) => {
if (!client) {
// TODO: use the strongly typed error here
throw new Error("no client");
}

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

const executeAction = useCallback(
<TFunctionName extends ExecutableFunctionName<TActions>>(
params: ClientActionParameters<TActions, TFunctionName>
) => {
const { functionName, args } = params;
return mutate({ functionName, args });
},
[mutate]
);

return {
executeAction,
isExecutingAction,
};
}
2 changes: 2 additions & 0 deletions packages/alchemy/src/react/index.ts
Original file line number Diff line number Diff line change
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/useClientActions.js";
export { useClientActions } from "./hooks/useClientActions.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 2661caa

Please sign in to comment.