# How to send user operations with the lowest latency

This guide shows you how to land a user operation as fast as Pimlico can: it goes into its own bundle right away and its receipt comes back in the same call, with no mempool wait or receipt polling. Use it when your app needs the result before it can continue, such as a payment or a trade.

## How fast it is

Median time from signing until the receipt is in hand, against Pimlico's production bundler:

| Chain | `sendUserOperation` + poll | `sendUserOperationSync` | Faster by |
| :--- | ---: | ---: | ---: |
| Base | 697 ms | **364 ms** | 333 ms (48%) |
| BSC | 728 ms | **294 ms** | 434 ms (60%) |
| Robinhood | 258 ms | **182 ms** | 76 ms (29%) |

It is almost as fast as sending the same call from an EOA:

| Chain | `sendUserOperationSync` | EOA send + poll |
| :--- | ---: | ---: |
| Base | **364 ms** | 255 ms |
| BSC | **294 ms** | 340 ms |
| Robinhood | **182 ms** | 100 ms |

The EOA sends with `eth_sendRawTransaction`, then polls `eth_getTransactionReceipt`.

For how these were measured, see the [`sendUserOperationSync` reference](/references/permissionless/reference/pimlico-actions/sendUserOperationSync).

## Prerequisites

* A Pimlico API key with instant bundling enabled. [Contact us](https://cal.com/kristofgazso/20min) or email [support@pimlico.io](mailto\:support@pimlico.io) to enable it.
* An app on Base, BSC or Robinhood.

:::code-group
```bash [npm]
npm install permissionless@latest viem
```

```bash [pnpm]
pnpm install permissionless@latest viem
```

```bash [bun]
bun install permissionless@latest viem
```
:::

## Steps

:::steps
### Create the smart account client

Create a smart account client as usual, then extend it with `pimlicoActions` to add `sendUserOperationSync`. Replace the `owner` with the user's signer, such as an embedded wallet or a passkey.

```ts
import { createSmartAccountClient } from "permissionless";
import { toSimpleSmartAccount } from "permissionless/accounts";
import { pimlicoActions } from "permissionless/actions/pimlico";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { createPublicClient, type Hex, http } from "viem";
import { entryPoint07Address } from "viem/account-abstraction";
import { privateKeyToAccount } from "viem/accounts";
import { base as chain } from "viem/chains"; // or bsc. For Robinhood, define the chain with viem's defineChain

const pimlicoUrl = `https://api.pimlico.io/v2/${chain.id}/rpc?apikey=YOUR_PIMLICO_API_KEY`;

const publicClient = createPublicClient({
	chain,
	transport: http(), // use your own RPC URL in production
});

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

const account = await toSimpleSmartAccount({
	client: publicClient,
	owner: privateKeyToAccount(process.env.PRIVATE_KEY as Hex), // the user's signer
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	},
});

const smartAccountClient = createSmartAccountClient({
	account,
	chain,
	bundlerTransport: http(pimlicoUrl),
	paymaster: pimlicoClient,
	userOperation: {
		estimateFeesPerGas: async () => {
			return (await pimlicoClient.getUserOperationGasPrice()).fast;
		},
	},
}).extend(
	pimlicoActions({
		entryPoint: {
			address: entryPoint07Address,
			version: "0.7",
		},
	}),
);
```

### Send the user operation and check the receipt

Send the user's calls with `smartAccountClient.sendUserOperationSync`. It returns the receipt once the user operation is included. If `success` is `false`, the call reverted inside the account.

```ts
const receipt = await smartAccountClient.sendUserOperationSync({
	calls: [
		{
			to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
			value: 0n,
			data: "0x1234",
		},
	],
});

if (!receipt.success) {
	throw new Error("The call reverted inside the account");
}

console.log(
	`Included: ${chain.blockExplorers.default.url}/tx/${receipt.receipt.transactionHash}`,
);
```
:::

## If the send throws

`sendUserOperationSync` never resends on its own. When it throws a `SendUserOperationSyncError`, decide from its `kind`:

* If `kind` is `pending`, the user operation may have been sent. Wait for its receipt and don't resend.
* If `kind` is `bundle_reverted`, another bundler may have included the user operation first. Check for its receipt, and resend only if there is none.
* If `kind` is `not_found`, nothing was sent. Send the same calls again.
* If `kind` is `rejected`, the bundler rejected the user operation before sending it. Fix the cause in `error.cause`, then send again.

```ts
import { SendUserOperationSyncError } from "permissionless/actions/pimlico";

const calls = [
	{
		to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
		value: 0n,
		data: "0x1234",
	},
] as const;

try {
	await smartAccountClient.sendUserOperationSync({ calls });
} catch (error) {
	if (!(error instanceof SendUserOperationSyncError)) throw error;

	if (error.kind === "pending") {
		// May have been sent: wait for it, don't resend
		await smartAccountClient.waitForUserOperationReceipt({
			hash: error.userOperationHash,
		});
	} else if (error.kind === "bundle_reverted") {
		// Another bundler may have included it first: check before resending
		const included = await smartAccountClient
			.getUserOperationReceipt({ hash: error.userOperationHash })
			.catch(() => null);
		if (!included) await smartAccountClient.sendUserOperationSync({ calls });
	} else if (error.canResend) {
		// not_found: nothing was sent, send the calls again
		await smartAccountClient.sendUserOperationSync({ calls });
	} else {
		// rejected: fix the cause in error.cause, then send again
		throw error;
	}
}
```

For what each `kind` means, see the [`sendUserOperationSync` reference](/references/permissionless/reference/pimlico-actions/sendUserOperationSync#errors).

## Send several user operations from one account

Await each `sendUserOperationSync` call before sending the next one. A user operation sent while the previous one from the same account is still in flight is rejected with `AA25 invalid account nonce`.

* On Base, if the next user operation is rejected with `AA25` right after the previous receipt, resend it after 1–2s.
* To send in parallel from one account, give each user operation its own nonce key. See [How to send user operations in parallel](/guides/how-to/parallel-transactions).

## Cut latency further with boosted user operations (optional)

If [Boosted Sponsorships](/guides/boosted-paymaster/use-boosted-paymaster) are enabled on your API key, drop the paymaster and set both fees to `0`. This skips the gas price and paymaster calls before the send. The gas is billed on your monthly invoice.

```ts
const boostedClient = createSmartAccountClient({
	account,
	chain,
	bundlerTransport: http(pimlicoUrl),
	// no paymaster: Pimlico bills the gas to your balance
	userOperation: {
		estimateFeesPerGas: async () => ({
			maxFeePerGas: 0n,
			maxPriorityFeePerGas: 0n,
		}),
	},
}).extend(
	pimlicoActions({
		entryPoint: {
			address: entryPoint07Address,
			version: "0.7",
		},
	}),
);

const boostedReceipt = await boostedClient.sendUserOperationSync({ calls });
```

## Related

* [`sendUserOperationSync` reference and benchmarks](/references/permissionless/reference/pimlico-actions/sendUserOperationSync)
* [`pimlico_sendUserOperationNow` reference](/references/bundler/endpoints/pimlico_sendUserOperationNow)
* [Boosted Sponsorships](/guides/boosted-paymaster/use-boosted-paymaster)
* [How to send user operations in parallel](/guides/how-to/parallel-transactions)
