# Tutorial — Batch multiple transactions from a smart account

In this tutorial, you will deploy an ERC-4337 smart account and submit your first user operation — one that batches **multiple calls** into a single on-chain transaction.

Batching is one of the core superpowers of smart accounts. Instead of asking the user to sign two transactions (e.g. an ERC-20 `approve` followed by a `swap`), you can bundle them into a single user operation that executes atomically: either everything succeeds, or nothing does.

You will set up the necessary permissionless.js clients, build a batched user operation, fund the smart account with Sepolia ETH so it can pay its own gas, and then submit it on-chain with Pimlico's bundler.

:::tip[Want gasless transactions instead?]
If you'd rather have a paymaster sponsor the gas for your user, jump to [Tutorial 1 — Send your first gasless transaction](/guides/tutorials/tutorial-1).
:::

:::tip[Let your coding agent do it]
Install the [Pimlico CLI](/references/platform/cli) and ask your coding agent:

> Follow docs.pimlico.io/guides/tutorials/send-transaction: create a Pimlico API key with the pimlico CLI, deploy a smart account on Sepolia and send a batched user operation.
:::

## Steps

::::steps
### Get a Pimlico API key

Either use [our dashboard](/guides/create-api-key#using-the-dashboard) or the [Pimlico CLI](/guides/create-api-key#using-the-cli-recommended).

### Clone the Pimlico tutorial template repository

We have created a [Pimlico tutorial template repository](https://github.com/pimlicolabs/tutorial-template) that you can use to get started. It comes set up with Typescript, viem, and permissionless.js.

```bash
git clone https://github.com/pimlicolabs/tutorial-template.git pimlico-tutorial-send-transaction
cd pimlico-tutorial-send-transaction
```

Now, let's install the dependencies:

```bash
npm install
```

The main file we will be working with is `index.ts`. Let's run it to make sure everything is working:

```bash
npm start
```

If everything has been set up correctly, you should see `Hello world!` printed to the console.

### Create the public client and generate a private key

The public client will be responsible for querying the blockchain. We will also use a Pimlico client to fetch gas prices from the bundler.

Make sure to replace `YOUR_PIMLICO_API_KEY` in the code below with your actual Pimlico API key.

Let's open up `index.ts`, and add the following to the bottom:

```ts
const apiKey = process.env.PIMLICO_API_KEY
if (!apiKey) throw new Error("Missing PIMLICO_API_KEY")

const privateKey =
	(process.env.PRIVATE_KEY as Hex) ??
	(() => {
		const pk = generatePrivateKey()
		writeFileSync(".env", `PRIVATE_KEY=${pk}`)
		return pk
	})()

export const publicClient = createPublicClient({
	chain: sepolia,
	transport: http("https://sepolia.rpc.thirdweb.com"),
})

const pimlicoUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}`

const pimlicoClient = createPimlicoClient({
	transport: http(pimlicoUrl),
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	},
})
```

### Create the `SmartAccount` instance

For the purposes of this guide, we will be using [Safe](https://safe.global) accounts. This account is an ERC-4337 wallet controlled by a single EOA signer.

:::tip[Tip]
Want to learn more about using Safe accounts? Take a look at our [dedicated Safe guide](/references/permissionless/how-to/accounts/use-safe-account)
:::

To create the Safe account, we will use the `toSafeSmartAccount` utility function from permissionless.js. We need to specify the Safe version we are using as well as the global ERC-4337 EntryPoint address. For the signer, we will be using the previously generated private key.

Add the following to the bottom of `index.ts`:

```ts
const account = await toSafeSmartAccount({
	client: publicClient,
	owners: [privateKeyToAccount(privateKey)],
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	}, // global entrypoint
	version: "1.4.1",
})

// Send some Sepolia ETH to this address so the smart account can pay its own gas.
console.log(`Smart account address: https://sepolia.etherscan.io/address/${account.address}`)
```

Let's run this code with `npm start`. You should see the smart account address printed to the console.

```txt
Smart account address: https://sepolia.etherscan.io/address/0x374b42bCFAcf85FDCaAB84774EA15ff36D42cdA7
```

:::info
If you visit the address on Etherscan, you might notice that no contract is actually deployed to this address yet. This is because smart accounts are counterfactual, meaning that they are only deployed on-chain the first time you send a transaction through the account.
:::

### Fund the smart account with Sepolia ETH

Since we are not using a paymaster in this tutorial, the smart account needs to pay its own gas. Send a small amount of Sepolia ETH (0.01 ETH is plenty) to the smart account address printed above.

You can get Sepolia ETH from a public faucet, for example the [PoW Sepolia Faucet](https://sepolia-faucet.pk910.de/).

:::note
If you'd prefer to skip funding and have the gas sponsored instead, see [Tutorial 1](/guides/tutorials/tutorial-1), which uses Pimlico's verifying paymaster.
:::

### Create the bundler and smart account clients

Now that we have a `SmartAccount` instance, we need to create a `SmartAccountClient` instance to be able to transact from it. `SmartAccountClient` is an almost drop-in replacement for a viem [`WalletClient`](https://viem.sh/docs/clients/wallet), but it also includes some additional functionality for interacting with smart accounts.

We specify the `gasPrice` middleware function to fetch the gas price from the bundler that we will use to submit the user operation in the next step. We do **not** configure a paymaster — the smart account will pay for its own gas using the ETH you just sent it.

Add the following to the bottom of `index.ts`:

```typescript
const smartAccountClient = createSmartAccountClient({
	account,
	chain: sepolia,
	bundlerTransport: http(pimlicoUrl),
	userOperation: {
		estimateFeesPerGas: async () => {
			return (await pimlicoClient.getUserOperationGasPrice()).fast
		},
	},
})
```

### Batch multiple calls in one user operation

Finally, let's submit a batched user operation from the smart account. We will send **two calls** to `0xd8da6bf26964af9d7eed9e03e53415d37aa96045` (vitalik.eth) with different `callData`, packed into a single user operation.

Instead of passing a single `to`/`data` pair, we pass an array of `calls` — the smart account will execute them atomically.

Underneath the hood, the `SmartAccountClient` will build a user operation, sign it with the smart account's private key, and then submit it to the bundler. The bundler will then query for receipts until it sees the user operation included on-chain.

Add the following to the bottom of `index.ts`:

```typescript
const txHash = await smartAccountClient.sendTransaction({
	calls: [
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: 0n,
			data: "0x1234",
		},
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: 0n,
			data: "0x5678",
		},
	],
})

console.log(`User operation included: https://sepolia.etherscan.io/tx/${txHash}`)
```

:::tip[Real-world batching]
In production, batching is how you deliver "one-click" UX for multi-step flows — for example, combining an ERC-20 `approve` and a DEX `swap` into a single signature, or minting an NFT and listing it for sale in one go.
:::

Let's run this code again with `npm start`. You should see the transaction hash bundling the user operation on-chain printed to the console.

```txt
User operation included: https://sepolia.etherscan.io/tx/0x7a2b61b4b7b6e9e66c459e3c9c24c7a292fc6c740533ce35dbf58710960cc0e5
```

You can now view the transaction on the Sepolia testnet explorer. By sending this user operation, you have:

* Deployed the counterfactual smart account contract
* Had this newly-deployed smart account verify the private key's signature
* Paid for the user operation's gas fees from the smart account's own ETH balance
* Executed **two calls atomically** in a single on-chain transaction

All in a couple lines of code.

Congratulations, you are now a pioneer of Account Abstraction! 🎉

Please [get in touch](https://t.me/pimlicoHQ) if you have any questions or if you'd like to share what you're building!
::::

### Combined code

If you want to see the complete code that combines all of the previous steps, we uploaded it to a [separate repository](https://github.com/pimlicolabs/tutorials). If you're looking to run it, remember to replace the API key with your own!
