Documentation Index
Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
Use this file to discover all available pages before exploring further.
sendUserOperation
Sends a user operation with one or more calls and waits for the transaction receipt. Works for both single transactions and batch transactions. This is the primary function for executing transactions with ZeroDev Account Abstraction.
Usage
import { sendUserOperation } from "@dynamic-labs-sdk/zerodev";
import { isEvmWalletAccount } from "@dynamic-labs-sdk/evm";
import { getPrimaryWalletAccount } from "@dynamic-labs-sdk/client";
import { parseEther } from "viem";
const walletAccount = getPrimaryWalletAccount();
if (walletAccount && isEvmWalletAccount(walletAccount)) {
const receipt = await sendUserOperation({
walletAccount,
calls: [
{
to: recipientAddress,
value: parseEther("0.01"),
data: "0x",
},
],
});
console.log("Transaction successful:", receipt.userOpHash);
console.log("Success:", receipt.success);
}
Parameters
You must provide either a walletAccount or a kernelClient, but not both.
| Parameter | Type | Description |
|---|
calls | BatchCall[] | Array of calls to execute (single or multiple) |
calls[].to | Hex | The recipient address for this call |
calls[].value | bigint | The value to send in wei for this call |
calls[].data | Hex (optional) | The transaction data for this call |
walletAccount | EvmWalletAccount (optional) | The wallet account. Required if no kernelClient is provided |
kernelClient | KernelClient (optional) | An existing kernel client. Required if no walletAccount is provided |
withSponsorship | boolean (optional) | Whether to use sponsorship. Default: true. Only used with walletAccount |
Returns
Promise<UserOperationReceipt> - Returns the UserOperation receipt with transaction details.
The receipt includes:
userOpHash: The UserOperation hash
success: Boolean indicating if the operation succeeded
- Additional transaction details from the receipt
Examples
const receipt = await sendUserOperation({
walletAccount,
calls: [
{
to: "0x...",
value: parseEther("0.1"),
},
],
});
console.log("UserOp Hash:", receipt.userOpHash);
Batch transaction (multiple recipients)
const receipt = await sendUserOperation({
walletAccount,
calls: [
{ to: "0xRecipient1...", value: parseEther("0.1") },
{ to: "0xRecipient2...", value: parseEther("0.2") },
{ to: "0xRecipient3...", value: parseEther("0.3") },
],
});
console.log("Batch transaction successful:", receipt.userOpHash);
const receipt = await sendUserOperation({
walletAccount,
withSponsorship: false,
calls: [
{
to: "0x...",
value: parseEther("0.1"),
},
],
});
Using existing kernel client
const kernelClient = await createKernelClientForWalletAccount({
smartWalletAccount: walletAccount,
});
const receipt = await sendUserOperation({
kernelClient,
calls: [
{
to: "0x...",
value: parseEther("0.1"),
},
],
});
Token transfer
import { encodeFunctionData } from "viem";
const receipt = await sendUserOperation({
walletAccount,
calls: [
{
to: tokenAddress,
value: 0n,
data: encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: [recipientAddress, parseEther("100")],
}),
},
],
});
DeFi workflow (Approve + Swap)
import { encodeFunctionData } from "viem";
const receipt = await sendUserOperation({
walletAccount,
calls: [
{
to: tokenAddress,
value: 0n,
data: encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: [swapRouterAddress, amount],
}),
},
{
to: swapRouterAddress,
value: 0n,
data: encodeFunctionData({
abi: swapRouterAbi,
functionName: "swap",
args: [tokenA, tokenB, amount],
}),
},
],
});
console.log("Approve + Swap completed atomically:", receipt.userOpHash);
import {
estimateUserOperationGas,
canSponsorUserOperation,
sendUserOperation
} from "@dynamic-labs-sdk/zerodev";
const calls = [
{ to: "0x...", value: parseEther("0.1") },
{ to: "0x...", value: parseEther("0.2") },
];
// Estimate gas
const estimatedGas = await estimateUserOperationGas({
walletAccount,
calls,
});
// Check sponsorship
const canSponsor = await canSponsorUserOperation({
walletAccount,
calls,
});
console.log(`Estimated gas: ${formatEther(estimatedGas)} ETH`);
console.log(`Will be sponsored: ${canSponsor}`);
// Send with appropriate sponsorship setting
const receipt = await sendUserOperation({
walletAccount,
withSponsorship: canSponsor,
calls,
});
Error handling
try {
const receipt = await sendUserOperation({
walletAccount,
calls: [
{ to: "0xRecipient1...", value: parseEther("0.1") },
{ to: "0xRecipient2...", value: parseEther("0.2") },
],
});
console.log("All operations succeeded:", receipt.userOpHash);
} catch (error) {
console.error("Batch transaction failed - no operations were executed");
console.error(error);
}
React
Use sendUserOperation inside a button handler. Use useWalletAccounts from @dynamic-labs-sdk/react-hooks to reactively get the wallet account:
import { sendUserOperation } from '@dynamic-labs-sdk/zerodev';
import { isEvmWalletAccount } from '@dynamic-labs-sdk/evm';
import { useWalletAccounts } from '@dynamic-labs-sdk/react-hooks';
import { useState } from 'react';
import { parseEther } from 'viem';
function GaslessSendButton({ recipientAddress }) {
const walletAccounts = useWalletAccounts();
const walletAccount = walletAccounts.find(isEvmWalletAccount);
const [opHash, setOpHash] = useState('');
const handleSend = async () => {
if (!walletAccount) return;
const receipt = await sendUserOperation({
walletAccount,
calls: [{ to: recipientAddress, value: parseEther('0.01'), data: '0x' }],
});
setOpHash(receipt.userOpHash);
};
return (
<div>
<button onClick={handleSend} disabled={!walletAccount}>Send (Gasless)</button>
{opHash && <p>UserOp hash: {opHash}</p>}
</div>
);
}
Benefits of Batch Transactions
- Atomicity: All operations succeed or all fail together
- Cost Efficiency: Single validation per batch reduces gas fees
- Better UX: Users approve once for multiple operations
- Composability: Enable complex multi-step workflows
Notes
- By default, transactions use sponsorship (
withSponsorship: true)
- Batch transactions are atomic - if any call fails, the entire batch fails
- The function waits for the UserOperation receipt before returning
- Gas savings increase with larger batches due to shared validation costs