Send User Operations
Once your users have been authenticated, you can start sending user operations! Account Kit makes it really easy to send user operations using React hooks.
Sending user operations is really easy, since all you have to do is use the useSendUserOperation
hook.
If you want to sponsor the gas for a user, see our guide.
Single user operation
In the below example, we use ModularAccountV2
as the underlying Smart
Contract type, which is our default and recommended account. To learn more
about all the account options, check out our guide on choosing a smart
account.
import React from "react";
import {
type type UseSendUserOperationResult<TEntryPointVersion extends GetEntryPointFromAccount<TAccount>, TAccount extends SupportedAccounts = SupportedAccounts> = {
sendUserOperation: UseMutateFunction<SendUserOperationWithEOA<TEntryPointVersion>, Error, SendUserOperationParameters<TAccount>, unknown>;
sendUserOperationAsync: UseMutateAsyncFunction<SendUserOperationWithEOA<TEntryPointVersion>, Error, SendUserOperationParameters<TAccount>, unknown>;
sendUserOperationResult: SendUserOperationWithEOA<TEntryPointVersion> | undefined;
isSendingUserOperation: boolean;
error: Error | null;
}UseSendUserOperationResult,
function useSendUserOperation<TEntryPointVersion extends GetEntryPointFromAccount<TAccount>, TAccount extends SupportedAccounts = SupportedAccounts>(params: UseSendUserOperationArgs<TEntryPointVersion, TAccount>): UseSendUserOperationResult<TEntryPointVersion, TAccount>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation,
function useSmartAccountClient<TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccountTypes | undefined = "ModularAccountV2">(args: UseSmartAccountClientProps<TChain, TAccount>): UseSmartAccountClientResult<TChain, SupportedAccount<TAccount extends undefined ? "ModularAccountV2" : TAccount>>useSmartAccountClient,
} from "@account-kit/react";
export default function function MyOpSenderComponent(): JSX.ElementMyOpSenderComponent() {
const { const client: {
account: ModularAccountV2<AlchemySigner>;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} | undefinedclient } = useSmartAccountClient<Chain | undefined, "ModularAccountV2">(args: UseSmartAccountClientProps<Chain | undefined, "ModularAccountV2">): UseSmartAccountClientResult<Chain | undefined, ModularAccountV2<...>>useSmartAccountClient({});
const { const sendUserOperation: UseMutateFunction<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<SupportedAccounts>, unknown>sendUserOperation, const isSendingUserOperation: booleanisSendingUserOperation } = useSendUserOperation<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>(params: UseSendUserOperationArgs<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>): UseSendUserOperationResult<...>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation({
client: {
account: SupportedAccounts;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} | {
...;
} | {
...;
} | {
...;
} | undefinedclient,
// optional parameter that will wait for the transaction to be mined before returning
waitForTxn?: boolean | undefinedwaitForTxn: true,
onSuccess?: ((data: SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefinedonSuccess: ({ hash: `0x${string}`hash, request: UserOperationRequest<keyof EntryPointRegistryBase<unknown>> | undefinedrequest }) => {
// [optional] Do something with the hash and request
},
onError?: ((error: Error, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefinedonError: (error: Errorerror) => {
// [optional] Do something with the error
},
});
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() =>
const sendUserOperation: (variables: SendUserOperationParameters<SupportedAccounts>, options?: MutateOptions<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<...>, unknown> | undefined) => voidsendUserOperation({
uo: UserOperationCallData | BatchUserOperationCallDatauo: {
target: `0x${string}`target: "0xTARGET_ADDRESS",
data: `0x${string}`data: "0x",
value?: bigint | undefinedvalue: 0n,
},
})
}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefineddisabled={const isSendingUserOperation: booleanisSendingUserOperation}
>
{const isSendingUserOperation: booleanisSendingUserOperation ? "Sending..." : "Send UO"}
</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
}
Batch user operation
It’s also possible to send user operations in batch using the same hook.
import React from "react";
import {
type type UseSendUserOperationResult<TEntryPointVersion extends GetEntryPointFromAccount<TAccount>, TAccount extends SupportedAccounts = SupportedAccounts> = {
sendUserOperation: UseMutateFunction<SendUserOperationWithEOA<TEntryPointVersion>, Error, SendUserOperationParameters<TAccount>, unknown>;
sendUserOperationAsync: UseMutateAsyncFunction<SendUserOperationWithEOA<TEntryPointVersion>, Error, SendUserOperationParameters<TAccount>, unknown>;
sendUserOperationResult: SendUserOperationWithEOA<TEntryPointVersion> | undefined;
isSendingUserOperation: boolean;
error: Error | null;
}UseSendUserOperationResult,
function useSendUserOperation<TEntryPointVersion extends GetEntryPointFromAccount<TAccount>, TAccount extends SupportedAccounts = SupportedAccounts>(params: UseSendUserOperationArgs<TEntryPointVersion, TAccount>): UseSendUserOperationResult<TEntryPointVersion, TAccount>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation,
function useSmartAccountClient<TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccountTypes | undefined = "ModularAccountV2">(args: UseSmartAccountClientProps<TChain, TAccount>): UseSmartAccountClientResult<TChain, SupportedAccount<TAccount extends undefined ? "ModularAccountV2" : TAccount>>useSmartAccountClient,
} from "@account-kit/react";
export default function function MyOpSenderComponent(): JSX.ElementMyOpSenderComponent() {
const { const client: {
account: ModularAccountV2<AlchemySigner>;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} | undefinedclient } = useSmartAccountClient<Chain | undefined, "ModularAccountV2">(args: UseSmartAccountClientProps<Chain | undefined, "ModularAccountV2">): UseSmartAccountClientResult<Chain | undefined, ModularAccountV2<...>>useSmartAccountClient({});
const { const sendUserOperation: UseMutateFunction<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<SupportedAccounts>, unknown>sendUserOperation, const isSendingUserOperation: booleanisSendingUserOperation } = useSendUserOperation<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>(params: UseSendUserOperationArgs<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>): UseSendUserOperationResult<...>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation({
client: {
account: SupportedAccounts;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} | {
...;
} | {
...;
} | {
...;
} | undefinedclient,
// optional parameter that will wait for the transaction to be mined before returning
waitForTxn?: boolean | undefinedwaitForTxn: true,
onSuccess?: ((data: SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefinedonSuccess: ({ hash: `0x${string}`hash, request: UserOperationRequest<keyof EntryPointRegistryBase<unknown>> | undefinedrequest }) => {
// [optional] Do something with the hash and request
},
onError?: ((error: Error, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefinedonError: (error: Errorerror) => {
// [optional] Do something with the error
},
});
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() =>
const sendUserOperation: (variables: SendUserOperationParameters<SupportedAccounts>, options?: MutateOptions<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<...>, unknown> | undefined) => voidsendUserOperation({
uo: UserOperationCallData | BatchUserOperationCallDatauo: [
{
target: `0x${string}`target: "0xTARGET_ADDRESS",
data: `0x${string}`data: "0x",
value?: bigint | undefinedvalue: 0n,
},
{
target: `0x${string}`target: "0xTARGET_ADDRESS",
data: `0x${string}`data: "0x",
value?: bigint | undefinedvalue: 0n,
},
],
})
}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefineddisabled={const isSendingUserOperation: booleanisSendingUserOperation}
>
{const isSendingUserOperation: booleanisSendingUserOperation ? "Sending..." : "Send UO"}
</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
}