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:
- [key-C][sequence-0]
- [key-A][sequence-0]
- [key-C][sequence-1]
- [key-B][sequence-0]
- [key-A][sequence-1]
- [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.