Both Developer-Controlled Wallets and Modular Smart Contract Accounts (MSCAs) support batching user operations into a single atomic transaction. These batched operations have several advantages:
You can batch transactions through the
contractExecution
endpoint.
The following example demonstrates how to generate a call to the
contractExecution
endpoint that batches two USDC transfer transactions. This
example uses an MSCA wallet, on the ETH-SEPOLIA testnet.
This code creates the encoding of the transfer
function calls that make up the
abiParameters
data for the call to the contractExecution
endpoint:
const { ethers } = require('ethers')
const abiEncode = async function () {
// encode callData for transfers
const abiTransfer = ['function transfer(address recipient, uint256 amount)']
const contractInterfaceTransfer = new ethers.utils.Interface(abiTransfer)
let data = contractInterfaceTransfer.encodeFunctionData('transfer', [
'0xa1404d9E7646b0112C49aE0296D6347C956D0867',
100000000, // 100 usdc
])
console.log(data)
}
abiEncode()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
Note that the preceding code sample encodes only a single call to the transfer
function, the call is then specified twice in the abiParameters
field of the
call to the contractExecution
endpoint.
{
"idempotencyKey": "{{$randomUUID}}",
"walletId": "{{wallet-id}}",
"feeLevel": "HIGH",
"refId": "test batch transfer",
"contractAddress": "0xcd3d4d703f1884313e8f3188a8c60cc6d389ebd1", // sca wallet address
"abiFunctionSignature": "executeBatch((address, uint256, bytes)[])",
"abiParameters": [
[
[
"0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
"0",
"0xa9059cbb000000000000000000000000a1404d9e7646b0112c49ae0296d6347c956d08670000000000000000000000000000000000000000000000000000000000000001"
],
[
"0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
"0",
"0xa9059cbb000000000000000000000000a1404d9e7646b0112c49ae0296d6347c956d08670000000000000000000000000000000000000000000000000000000000000001"
]
]
],
"entitySecretCiphertext": "{{entity-secret-ciphertext}}"
}
The function signature called is
executeBatch((address address, uint256 amount, bytes func)[])
. The address
is the USDC contract address, amount
is 0
(the amount of native token
transferred), and func
is the encoded function call generated by the sample
code. The example provides the parameters twice, so the resulting batched
transactions result in 200 USDC transferred (100 for each transaction).
The following example demonstrates how to generate a call to the
contractExecution
endpoint that batches two related transactions: approve
,
and depositForBurn
in the CCTP contract. This example uses an MSCA wallet, on
the ETH-SEPOLIA testnet.
This code creates the encoding of the two calls that make up the abiParameters
data for the call to the contractExecution
endpoint.
const { ethers } = require('ethers')
const abiEncode = async function () {
const abiDepositForBurn = [
'function depositForBurn(uint256,uint32,bytes32,address)',
]
const abiApprove = ['function approve(address,uint256)']
const contractInterfaceApprove = new ethers.utils.Interface(abiApprove)
const contractInterfaceDepositForBurn = new ethers.utils.Interface(
abiDepositForBurn,
)
// encode approve
let dataApprove = contractInterfaceApprove.encodeFunctionData('approve', [
'0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5',
100000000, // 100 usdc
])
console.log(dataApprove)
// encode sca wallet address
const abi = ethers.utils.defaultAbiCoder
let encodedAddress = abi.encode(
['address'],
['0xc4b69f1f26c820e7fa18c925da8bf2b2a3357fe0'],
)
console.log(encodedAddress)
// encode depositForBurn
let dataDepositForBurn = contractInterfaceDepositForBurn.encodeFunctionData(
'depositForBurn',
[
100000000,
2, // AVAX
encodedAddress, // encoded sca wallet address
'0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // usdc contract
],
)
console.log(dataDepositForBurn)
}
abiEncode()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
The first transaction in the batch is a call to the function on the USDC
contract: approve(address spender, uint256 value)
. Calling approve
allows
the spender
to spend value
USDC on your behalf. This is necessary for the
subsequent depositForBurn
transaction in the batch. In this example, address
is the TokenMessenger
contract address and the value
is 100000000
(100
USDC). Note that the example code encodes the parameters of the function call,
the subsequent call to the Circle API specifies the contract address to make the
call to.
The second transaction in the batch is a call to the function on the
TokenMessenger
contract:
depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken)
.
Calling depositForBurn
results in the TokenMessenger
contract transferring
and burning the specified amount of USDC from the wallet on the origin chain.
The parameters to the depositForBurn
call are:
amount
: the amount of USDC to burn. In this example, 100000000
(100 USDC)destinationDomain
: a Circle-issued identifier for a network where Circle
deploys CCTP contracts. In this example, 2
to specify Avalanche. For a full
list, see the CCTP domain list.mintRecipient
: the destination address, encoded as a Solidity address.burnToken
: the contract address of the token to burn. In this example, the
USDC contract on the source chain.The preceding code results in a request to the contractExecution
endpoint like
this:
{
"idempotencyKey": "{{$randomUUID}}",
"walletId": "{{wallet-id}}",
"feeLevel": "HIGH",
"refId": "test batch CCTP",
"contractAddress": "0xcd3d4d703f1884313e8f3188a8c60cc6d389ebd1", // sca wallet address
"abiFunctionSignature": "executeBatch((address, uint256, bytes)[])",
"abiParameters": [
[
[
"0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
"0",
"0x095ea7b30000000000000000000000009f3b8679c73c2fef8b59b4f3444d4e156fb70aa500000000000000000000000000000000000000000000000000000000000003e8"
],
[
"0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5",
"0",
"0x6fd3504e00000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c4b69f1f26c820e7fa18c925da8bf2b2a3357fe00000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238"
]
]
],
"entitySecretCiphertext": "{{entity-secret-ciphertext}}"
}
The function signature called is
executeBatch((address address, uint256 amount, bytes func)[])
. The parameters
for each transaction are:
approve
address
is the USDC token contract addressamount
is the amount of native tokens transferredfunc
is the encoded function call generated by the sample codedepositForBurn
address
is the TokenMessenger
contract addressamount
is the amount of native tokens transferredfunc
is the encoded function call generated by the sample code