Send Transactions
Send single, batched, parallel, and multichain transactions with smart accounts.
Namera smart accounts use a lane-based execution model from ZeroDev. Instead of submitting one transaction at a time, you describe a set of calls grouped into batches and optionally spread across chains. The SDK packages them into UserOperations, manages nonces per lane, and returns receipts for each execution path.
This model is designed for automation and agents and gives you more control over how work is scheduled:
- Single transactions for simple actions
- Atomic batching for ordered, all-or-nothing flows
- Parallel execution on the same chain using nonce lanes
- Multi-chain execution with a single request
- Cross-chain parallelism when work is independent
Core concepts
Call is the smallest unit of execution. It maps to a single contract call or native transfer.
export type Call = {
to: Address; // contract or EOA
data: Hex; // encoded calldata
value?: bigint; // optional native value
};Batch is a group of calls executed sequentially on a single chain.
export type Batch = {
chainId: number; // target chain
calls: Call[];
nonceKey?: string; // execution lane
atomic?: boolean; // optional, treated as atomic by default
};ExecuteTransactionParams is the top-level execution container.
export type ExecuteTransactionParams = {
batches: Batch[];
clients: BaseKernelAccountClient[];
};Execution model
Sequential execution within a batch
Calls inside a batch are encoded into a single UserOperation. The order of calls is preserved and the batch is atomic. If any call reverts, the whole batch fails.
Parallel execution across batches
Parallelism is controlled by nonceKey, which maps to a nonce lane (2D nonce). Batches that share the same nonceKey execute sequentially. Batches with different nonceKey values can execute in parallel on the same chain without waiting for each other.
Multi-chain execution
Each batch specifies a chainId. The SDK routes the batch to the matching account client for that chain. Results are returned per batch, but there is no atomicity across chains.
Use cases
- One-off transfers or contract calls with a single batch
- Approve + swap, repay + withdraw, or any ordered flow that must be atomic
- Parallel portfolio actions such as swapping and lending at the same time
- Multi-chain strategies like swapping on Ethereum and lending on Polygon
- Agent workflows that need receipts per lane to decide the next action
Usage
import { executeTransaction } from "@namera-ai/sdk/transaction";
import { parseEther } from "viem";
import { publicClient, smartAccountClient } from "./clients";
const userOpReceipts = await executeTransaction({
batches: [
{
chainId: publicClient.chain.id,
calls: [
{
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
data: "0x",
value: parseEther("0.001"),
},
],
},
],
clients: [smartAccountClient],
});
const receipt = userOpReceipts[0];
console.log(receipt?.userOpHash);Parameters
executeTransaction({ batches, clients })
Expected shape:
const params: ExecuteTransactionParams = {
batches: [
{
chainId: 1,
calls: [
{
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
data: "0x",
value: 1_000_000_000_000_000n,
},
],
},
],
clients: [ethClient],
};batches is an ordered list of batches to execute. Each batch becomes one UserOperation.
const batches: Batch[] = [
{
chainId: 1,
nonceKey: "swap",
calls: [
{
to: "0xE592427A0AEce92De3Edee1F18E0157C05861564",
data: "0x", // swapExactTokensForETH(...) encoded calldata
},
],
},
{
chainId: 137,
calls: [
{
to: "0x8dff5e27ea6b7ac08ebfdf9eb090f32ee9a30fcf",
data: "0x", // supply(USDC, amount, ...)
},
],
},
];clients are the account clients used to sign and send batches. Clients are matched by chainId, and duplicate chain ids are rejected. For multiple batches on the same chain, reuse the same client.
const clients = [ethClient, polygonClient];Batch groups sequential calls on a single chain. Use nonceKey to select a lane for parallel execution. atomic is optional; batches are encoded into a single UserOperation and are atomic by default.
const batch: Batch = {
chainId: 1,
nonceKey: "lend",
calls: [
{
to: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
data: "0x", // approve(AavePool, amount)
},
{
to: "0x7d2768dE32b0b80b7a3454c06BdAc59bC0a0D5e5",
data: "0x", // supply(DAI, amount, ...)
},
],
};Call is a single contract call or native transfer. Use encodeFunctionData for contract calls, and use "0x" for a pure native transfer.
const nativeTransfer: Call = {
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
data: "0x",
value: 1000000000000000n,
};const tokenTransfer: Call = {
to: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
data: "0xa9059cbb0000000000000000000000000d8dA6BF26964aF9D7eEd9e03E53415D37aA96045" +
"0000000000000000000000000000000000000000000000000000000000989680",
};Returns
(UserOperationReceipt | null)[]
Each entry corresponds to a batch in the same order you passed it. A receipt is returned on success; null indicates a failed submission or a failed receipt lookup for that batch.
Return shape from viem:
type ExecuteTransactionResult = (UserOperationReceipt | null)[];
type UserOperationReceipt = {
actualGasCost: bigint;
actualGasUsed: bigint;
entryPoint: Address;
logs: Log[];
nonce: bigint;
paymaster?: Address | undefined;
reason?: string | undefined;
receipt: TransactionReceipt;
sender: Address;
success: boolean;
userOpHash: Hash;
};Examples
Single transaction
Send a single USDC transfer on Ethereum. The call is executed as one UserOperation with no batching or parallelism.
import { encodeFunctionData, erc20Abi } from "viem";
const data = encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", 10_000_000n],
});
const userOpReceipts = await executeTransaction({
batches: [
{
chainId: 1,
calls: [
{
to: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
data,
},
],
},
],
clients: [ethClient],
});Native transfers are even simpler: use "0x" for calldata and set value.
import { parseEther } from "viem";
const userOpReceipts = await executeTransaction({
batches: [
{
chainId: 1,
calls: [
{
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
data: "0x",
value: parseEther("0.01"),
},
],
},
],
clients: [ethClient],
});Sequential batch (atomic)
Approve then swap on the same chain. Both calls are executed in order inside a single batch, so the swap only runs if approval succeeds.
import { encodeFunctionData, erc20Abi } from "viem";
const approveData = encodeFunctionData({
abi: erc20Abi,
functionName: "approve",
args: ["0xE592427A0AEce92De3Edee1F18E0157C05861564", 10_000_000n],
});
const userOpReceipts = await executeTransaction({
batches: [
{
chainId: 1,
nonceKey: "swap",
calls: [
{
to: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
data: approveData,
},
{
to: "0xE592427A0AEce92De3Edee1F18E0157C05861564",
data: "0x", // swapExactTokensForETH(...) encoded calldata
},
],
},
],
clients: [ethClient],
});Parallel batches on the same chain
Run two independent actions in parallel using distinct lanes. Each batch is atomic, and the lanes do not block each other.
const userOpReceipts = await executeTransaction({
batches: [
{
chainId: 1,
nonceKey: "swap",
calls: [
{
to: "0xE592427A0AEce92De3Edee1F18E0157C05861564",
data: "0x", // swapExactTokensForETH(...) encoded calldata
},
],
},
{
chainId: 1,
nonceKey: "lend",
calls: [
{
to: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
data: "0x", // approve(AavePool, amount)
},
{
to: "0x7d2768dE32b0b80b7a3454c06BdAc59bC0a0D5e5",
data: "0x", // supply(DAI, amount, ...)
},
],
},
],
clients: [ethClient],
});Multi-chain batches
Execute batches on different chains. Each batch is submitted to its matching client and returns a receipt per chain.
const userOpReceipts = await executeTransaction({
batches: [
{
chainId: 1,
calls: [
{
to: "0xE592427A0AEce92De3Edee1F18E0157C05861564",
data: "0x", // swapExactETHForTokens(...)
},
],
},
{
chainId: 137,
calls: [
{
to: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
data: "0x", // approve(AavePool, amount)
},
{
to: "0x8dff5e27ea6b7ac08ebfdf9eb090f32ee9a30fcf",
data: "0x", // supply(USDC, amount, ...)
},
],
},
],
clients: [ethClient, polygonClient],
});Multi-chain + parallel lanes
Parallelize work across chains and within each chain using distinct nonce lanes.
const userOpReceipts = await executeTransaction({
batches: [
{
chainId: 1,
nonceKey: "eth-swap",
calls: [
{
to: "0xE592427A0AEce92De3Edee1F18E0157C05861564",
data: "0x", // swapExactTokensForETH(...)
},
],
},
{
chainId: 1,
nonceKey: "eth-liquidity",
calls: [
{
to: "0xD533a949740bb3306d119CC777fa900bA034cd52",
data: "0x", // add_liquidity(...)
},
],
},
{
chainId: 8453,
nonceKey: "base-lend",
calls: [
{
to: "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
data: "0x", // supply(...)
},
],
},
{
chainId: 8453,
nonceKey: "base-stake",
calls: [
{
to: "0x0000000000000000000000000000000000000000",
data: "0x", // deposit(...) in a vault
},
],
},
],
clients: [ethAccountClient, baseAccountClient],
});