Skip to content

How to send multiple user operations in parallel

permissionless.js library lets you to send multiple transactions in parallel. This can be useful if you want to batch multiple calls in a single user operation or if you want to send user operations concurrently.

Batching Multiple Calls

To batch multiple calls in a single user operation, you can use the sendTransactions method.

const transactionHash = await smartAccountClient.sendTransaction({
	calls: [
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: parseEther("0.1"),
			data: "0x",
		},
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: parseEther("0.1"),
			data: "0x",
		},
	],
})

Sending Multiple User Operations in Parallel

In an Externally Owned Account (EOA), the nonce is a simple incrementing number. However, in smart accounts, the nonce consists of two components: a key and a sequence:

  • 192-bit “key”
  • 64-bit “sequence”

For each unique key, the sequence must be incremented by 1 for each transaction. This means that if you send multiple transactions in parallel, you can use different keys for the parallel transactions.

Parallel Transactions Ordering

Important thing to note is that parallel transaction's ordering is not guaranteed. So execution of random key on chain could be:

  • [key-C][sequence-0]
  • [key-A][sequence-0]
  • [key-B][sequence-0]

While execution of sequence for a specific key will always be in order. So the following is a valid order of execution:

  1. [key-C][sequence-0]
  2. [key-A][sequence-0]
  3. [key-C][sequence-1]
  4. [key-B][sequence-0]
  5. [key-A][sequence-1]
  6. [key-B][sequence-1]

In the example below, we use the current timestamp as the key to send parallel transactions. You can use any other value as the key.

import { encodeNonce } from "permissionless/utils"
 
const parallelNonce1 = encodeNonce({
	key: BigInt(Date.now()),
	sequence: 0n,
})
 
const transaction1 = smartAccountClient.sendTransaction({
	calls: [
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: parseEther("0.1"),
			data: "0x",
		},
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: parseEther("0.1"),
			data: "0x",
		},
	],
	nonce: parallelNonce1,
})
 
const parallelNonce2 = encodeNonce({
	key: BigInt(Date.now()),
	sequence: 0n,
})
 
const transaction2 = smartAccountClient.sendTransaction({
	calls: [
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: parseEther("0.1"),
			data: "0x",
		},
		{
			to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
			value: parseEther("0.1"),
			data: "0x",
		},
	],
	nonce: parallelNonce2,
})
 
const hashes = await Promise.all([transaction1, transaction2])
 

This way, you can efficiently manage multiple transactions either sequentially or concurrently.