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 pretty straightforward. All you have to do is use the useSendUserOperation() hook from the @account-kit/react-native package.

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. You can also use a different account type (see other options here).

import React, { 
function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

useState
,
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

useEffect
} from "react";
import {
type Alert = AlertStatic const Alert: AlertStatic
Alert
,
class View
View
,
class Button
Button
} from "react-native";
import {
type User = { email?: string; orgId: string; userId: string; address: Address; solanaAddress?: string; credentialId?: string; idToken?: string; claims?: Record<string, unknown>; }
User
} from "@account-kit/signer";
import {
function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)
createModularAccountV2Client
,
type ModularAccountV2<TSigner extends SmartAccountSigner = SmartAccountSigner<any>> = { address: Address; nonceManager?: NonceManager | undefined; sign?: ((parameters: { hash: Hash; }) => Promise<Hex>) | undefined | undefined; ... 6 more ...; type: "local"; } & { ...; } & { ...; } & { ...; }
ModularAccountV2
,
} from "@account-kit/smart-contracts"; import {
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-native"; import {
const sepolia: Chain
sepolia
,
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.

alchemy
} from "@account-kit/infra";
import {
type SmartAccountClient<transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartContractAccount | undefined = SmartContractAccount | undefined, actions extends Record<string, unknown> = Record<...>, rpcSchema extends RpcSchema = [...], context extends UserOperationContext | undefined = UserOperationContext | undefined> = { [K in keyof Client<transport, chain, account, rpcSchema, actions & { buildUserOperation: (args: BuildUserOperationParameters<account, context>) => Promise<...>; ... 11 more ...; signTypedData: <const TTypedData extends { ...; } | { ...; }, TPrimaryType extends string = string>(args: SignTypedDataParameters<...>) => Promise<Hex>; } & (IsUndefined<...> extends false ? { ...; } : { ...; }) & BundlerActions & PublicActions>]: Client<...>[K]; }
SmartAccountClient
} from "@aa-sdk/core";
export default function
function MyOpSenderComponent(): JSX.Element
MyOpSenderComponent
() {
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<...>; } | undefined
client
} =
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: boolean
isSendingUserOperation
} =
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<...>; } | { ...; } | { ...; } | { ...; } | undefined
client
,
waitForTxn?: boolean | undefined
waitForTxn
: true,
onSuccess?: ((data: SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefined
onSuccess
: ({
hash: `0x${string}`
hash
,
request: UserOperationRequest<keyof EntryPointRegistryBase<unknown>> | undefined
request
}) => {
// [optional] Do something with the hash and request },
onError?: ((error: Error, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefined
onError
: (
error: Error
error
) => {
// [optional] Do something with the error }, }); const
const handleSendUserOperation: () => void
handleSendUserOperation
= () => {
const sendUserOperation: (variables: SendUserOperationParameters<SupportedAccounts>, options?: MutateOptions<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<...>, unknown> | undefined) => void
sendUserOperation
({
uo: UserOperationCallData | BatchUserOperationCallData
uo
: {
target: `0x${string}`
target
: "0xTARGET_ADDRESS",
data: `0x${string}`
data
: "0x",
value?: bigint | undefined
value
: 0n,
}, }); }; return ( <
class View
View
>
<
class Button
Button
onPress?: ((event: GestureResponderEvent) => void) | undefined

Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).

onPress
={
const handleSendUserOperation: () => void
handleSendUserOperation
}
disabled?: boolean | undefined

If true, disable all interactions for this component.

disabled
={
const isSendingUserOperation: boolean
isSendingUserOperation
}
title: string

Text to display inside the button. On Android the given title will be converted to the uppercased form.

title
={
const isSendingUserOperation: boolean
isSendingUserOperation
? "Sending..." : "Send UO"}
/> </
class View
View
>
); }

Batch user operation

To send multiple user operations in a single call, simply pass an array of user operations to the sendUserOperation method.

import React, { 
function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

useState
,
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void

Accepts a function that contains imperative, possibly effectful code.

useEffect
} from "react";
import {
type Alert = AlertStatic const Alert: AlertStatic
Alert
,
class View
View
,
class Button
Button
} from "react-native";
import {
type User = { email?: string; orgId: string; userId: string; address: Address; solanaAddress?: string; credentialId?: string; idToken?: string; claims?: Record<string, unknown>; }
User
} from "@account-kit/signer";
import {
function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)
createModularAccountV2Client
,
type ModularAccountV2<TSigner extends SmartAccountSigner = SmartAccountSigner<any>> = { address: Address; nonceManager?: NonceManager | undefined; sign?: ((parameters: { hash: Hash; }) => Promise<Hex>) | undefined | undefined; ... 6 more ...; type: "local"; } & { ...; } & { ...; } & { ...; }
ModularAccountV2
,
} from "@account-kit/smart-contracts"; import {
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-native"; import {
const sepolia: Chain
sepolia
,
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.

alchemy
} from "@account-kit/infra";
import {
type SmartAccountClient<transport extends Transport = Transport, chain extends Chain | undefined = Chain | undefined, account extends SmartContractAccount | undefined = SmartContractAccount | undefined, actions extends Record<string, unknown> = Record<...>, rpcSchema extends RpcSchema = [...], context extends UserOperationContext | undefined = UserOperationContext | undefined> = { [K in keyof Client<transport, chain, account, rpcSchema, actions & { buildUserOperation: (args: BuildUserOperationParameters<account, context>) => Promise<...>; ... 11 more ...; signTypedData: <const TTypedData extends { ...; } | { ...; }, TPrimaryType extends string = string>(args: SignTypedDataParameters<...>) => Promise<Hex>; } & (IsUndefined<...> extends false ? { ...; } : { ...; }) & BundlerActions & PublicActions>]: Client<...>[K]; }
SmartAccountClient
} from "@aa-sdk/core";
export default function
function MyOpSenderComponent(): JSX.Element
MyOpSenderComponent
() {
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<...>; } | undefined
client
} =
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: boolean
isSendingUserOperation
} =
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<...>; } | { ...; } | { ...; } | { ...; } | undefined
client
,
waitForTxn?: boolean | undefined
waitForTxn
: true,
onSuccess?: ((data: SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefined
onSuccess
: ({
hash: `0x${string}`
hash
,
request: UserOperationRequest<keyof EntryPointRegistryBase<unknown>> | undefined
request
}) => {
// [optional] Do something with the hash and request },
onError?: ((error: Error, variables: SendUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefined
onError
: (
error: Error
error
) => {
// [optional] Do something with the error }, }); const
const handleSendBatchUserOperations: () => void
handleSendBatchUserOperations
= () => {
const sendUserOperation: (variables: SendUserOperationParameters<SupportedAccounts>, options?: MutateOptions<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<...>, unknown> | undefined) => void
sendUserOperation
({
uo: UserOperationCallData | BatchUserOperationCallData
uo
: [
{
target: `0x${string}`
target
: "0xTARGET_ADDRESS",
data: `0x${string}`
data
: "0x",
value?: bigint | undefined
value
: 0n,
}, {
target: `0x${string}`
target
: "0xTARGET_ADDRESS",
data: `0x${string}`
data
: "0x",
value?: bigint | undefined
value
: 0n,
}, ], }); }; return ( <
class View
View
>
<
class Button
Button
onPress?: ((event: GestureResponderEvent) => void) | undefined

Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).

onPress
={
const handleSendBatchUserOperations: () => void
handleSendBatchUserOperations
}
disabled?: boolean | undefined

If true, disable all interactions for this component.

disabled
={
const isSendingUserOperation: boolean
isSendingUserOperation
}
title: string

Text to display inside the button. On Android the given title will be converted to the uppercased form.

title
={
const isSendingUserOperation: boolean
isSendingUserOperation
? "Sending..." : "Send UO"}
/> </
class View
View
>
); }