Send Userops on a Single Chain
Execute multiple operations in a single user operation on one chain.
import { useSmartAccount } from '@ssv-labs/compose-sdk/react';
import { composePreparedUserOps, rollupA, rollupB } from '@ssv-labs/compose-sdk';
import { prepareUserOperation } from 'viem/account-abstraction';
import { erc20Abi, encodeFunctionData } from 'viem';
import { useAccount } from 'wagmi';
import { useMutation } from '@tanstack/react-query';
function BatchOperations() {
const { isConnected } = useAccount();
// The hook returns a query object - access .data from it
const smartAccountQuery = useSmartAccount({
chainId: rollupA.id,
multiChainIds: [rollupA.id, rollupB.id],
});
// Get smart account and public client from query data
const smartAccount = smartAccountQuery.data?.account;
const publicClient = smartAccountQuery.data?.publicClient;
const batchMutation = useMutation({
mutationFn: async () => {
if (!smartAccount || !publicClient) {
throw new Error('Smart account not ready');
}
const tokenAddress = '0x969b0ad5ffa2376E8C0f5e413D510a056416D627';
// Step 1: Create a user operation with multiple calls
const { userOp } = await smartAccount.createUserOp([
// Approve spender
{
to: tokenAddress,
value: 0n,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: ['0x1388C9619aCCcd1dfff0234626EDDA61413Be74e', 10000000000000000000n]
})
},
// Transfer tokens
{
to: tokenAddress,
value: 0n,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: ['0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 5000000000000000000n]
})
}
]);
// Step 2: Prepare the user operation
const preparedUserOp = await prepareUserOperation(publicClient, userOp);
// Step 3: Compose and send the user operation
const { send, explorerUrls } = await composePreparedUserOps([{
account: smartAccount,
publicClient: publicClient,
userOp: preparedUserOp,
}]);
// Step 4: Send and wait for receipts
const { wait } = await send();
const receipts = await wait();
return {
explorerUrls,
receipts
};
}
});
const handleBatch = () => {
batchMutation.mutate();
};
if (!isConnected) {
return <div>Please connect your wallet</div>;
}
if (smartAccountQuery.isLoading) {
return <div>Loading smart account...</div>;
}
return (
<div>
<button
onClick={handleBatch}
disabled={!smartAccount || batchMutation.isPending}
>
{batchMutation.isPending ? 'Executing...' : 'Execute Batch Operations'}
</button>
{batchMutation.isSuccess && (
<div>
<p>Batch operations completed!</p>
<a
href={batchMutation.data.explorerUrls[0]}
target="_blank"
rel="noopener noreferrer"
>
View transaction
</a>
</div>
)}
{batchMutation.isError && (
<div>
<p>Error: {batchMutation.error?.message}</p>
</div>
)}
</div>
);
}