Interacting with the Batch Precompile
Introduction
The batch precompiled contract on Tangle allows developers to combine multiple EVM calls into one.
Normally, having users interact with multiple contracts would require multiple transaction confirmations in the user's wallet. An example would be approving a smart contract's access to a token, then transferring it. With the batch precompile, developers can enhance user experience with batched transactions as it minimizes the number of transactions a user is required to confirm to one. Additionally, gas fees can be reduced since batching avoids multiple base gas fees (the initial 21000 units of gas spent to begin a transaction).
The precompile interacts directly with the EVM pallet on Tangle. The caller of the batch function will have their address act as the msg.sender
for all subtransactions, but unlike delegate calls (opens in a new tab), the target contract will still affect its own storage. It is effectively the same as if the user signed multiple transactions, but with only one confirmation.
The precompile is located at the following addresses:
- Tangle Mainnet:
0x0000000000000000000000000000000000000808
- Tangle Testnet:
0x0000000000000000000000000000000000000808
The Batch Solidity Interface
Below is the Solidity interface for the batch precompile on Tangle, which exposes three functions:
Below is more detail on how these functions work:
batchSome
Performs multiple calls, where the same index of each array combines into the information required for a single subcall. If a subcall reverts, following subcalls will still be attempted.
to
— array of addresses to direct subtransactions to, where each entry is a subtransactionvalue
— array of native currency values to send in the subtransactions, where the index corresponds to the subtransaction of the same index in theto
array. If this array is shorter than theto
array, all the following subtransactions will default to a value of 0callData
— array of call data to include in the subtransactions, where the index corresponds to the subtransaction of the same index in theto
array. If this array is shorter than theto
array, all of the following subtransactions will include no call datagasLimit
— array of gas limits for each subtransaction, where the index corresponds to the subtransaction of the same index in theto
array. Values of 0 are interpreted as "unlimited" and will have all remaining gas of the batch transaction forwarded. If this array is shorter than theto
array, all of the following subtransactions will have all remaining gas forwarded
batchSomeUntilFailure
Performs multiple calls, where the same index of each array combines into the information required for a single subcall. If a subcall reverts, no following subcalls will be executed, but the successful subcalls remain intact. It does not revert the entire batch transaction.
to
— array of addresses to direct subtransactions tovalue
— array of native currency values to sendcallData
— array of call data to include in each subtransactiongasLimit
— array of gas limits for each subtransaction
batchAll
Performs multiple calls atomically. If a subcall reverts, all subcalls will revert.
to
— array of addresses to direct subtransactions tovalue
— array of native currency values to sendcallData
— array of call data to include in each subtransactiongasLimit
— array of gas limits for each subtransaction
Interact with the Solidity Interface
Checking Prerequisites
To follow along with this tutorial, you will need to have:
- MetaMask installed and connected to Tangle Testnet or Tangle Mainnet
- Create or have two accounts on Tangle Testnet to test out the different features in the batch precompile
- At least one of the accounts will need to be funded with test tokens on Tangle Testnet.
Example Contract
The following contract, SimpleContract.sol
, will be used as an example of batching contract interactions, but in practice, any contract can be interacted with:
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;
contract SimpleContract {
mapping(uint256 => string) public messages;
function setMessage(uint256 id, string memory message) public {
messages[id] = message;
}
}
Remix Set Up
You can interact with the batch precompile using Remix (opens in a new tab). To add the interface and the example contract and follow along with this tutorial, you will need to:
- Click on the File explorer tab in Remix
- Create a file named Batch.sol, and paste in the batch interface shown above
- Create a file named SimpleContract.sol, and paste in the
SimpleContract
provided above
Compile the Contract
Next, you will need to compile both files in Remix:
- Open the Batch.sol file
- Click on the Compile tab
- Click on Compile Batch.sol
If the interface was compiled successfully, you'll see a green checkmark next to the Compile tab. Then, repeat these steps for SimpleContract.sol.
Access the Precompile
Instead of deploying the batch precompile, you will access the interface given the address of the precompiled contract:
- Click on the Deploy and Run tab (below the Compile tab) in Remix. The precompiled contract is already deployed on Tangle
- Select Injected Provider - MetaMask under ENVIRONMENT. MetaMask may prompt you to connect your account
- Ensure the correct account is displayed under ACCOUNT
- Select Batch - Batch.sol under CONTRACT
- Copy the batch precompile address for Tangle (0x0000000000000000000000000000000000000808) and paste it into the At Address field
- Click At Address
A new instance of Batch - Batch.sol will appear under Deployed Contracts.
Deploy Example Contract
On the other hand, SimpleContract.sol
will be deployed as a new contract. After compiling SimpleContract.sol
:
- Click on the Deploy and Run tab
- Select Injected Provider - MetaMask under ENVIRONMENT
- Ensure the correct account is displayed under ACCOUNT
- Select SimpleContract - SimpleContract.sol in the CONTRACT dropdown
- Click Deploy
- Confirm the MetaMask transaction
A new instance of SimpleContract will appear under Deployed Contracts.
Send Native Currency via Precompile
Sending native currency with the batch precompile involves specifying which addresses to send to and how much to send, all in a single batch call. For this example, you'll use the batchAll function to send native currency atomically in Tangle Testnet:
- Make sure you have enough test tokens in your connected wallet on Tangle Testnet
- Expand the Batch - Batch.sol precompile instance
- Expand the batchAll function
- For the to input, provide the addresses you want to send tokens to, for example:
["ADDRESS_1", "ADDRESS_2"]
- For the value input, provide the amounts to send in Wei, for example:
["100000000000000000", "200000000000000000"]
which corresponds to 0.1 and 0.2 tokens respectively - For the callData and gasLimit inputs, provide empty arrays:
[]
- Click transact
- Confirm in MetaMask
Once the transaction is complete, confirm both recipient addresses have the appropriate token balances in MetaMask or via a block explorer.
Find a Contract Interaction's Call Data
Visual interfaces like Remix and libraries like Ethers.js (opens in a new tab) or Web3.js (opens in a new tab) encapsulate the call data used to interact with Solidity contracts, but you can also obtain it explicitly to use with the batch precompile.
Try finding a transaction's call data manually in Remix:
- Expand the deployed
SimpleContract.sol
instance - Expand the setMessage function
- Enter example values, such as
id = 1
andmessage = "moonbeam"
- Instead of clicking transact, click the copy button next to it to copy the call data
This copied string is the function selector plus encoded arguments for the function call.
Function Interaction via Precompile
To batch contract interactions, function inputs must be encoded as call data within the callData
array. For an atomic operation where a single subcall failure reverts all subcalls:
- Copy the address of your
SimpleContract.sol
instance - Expand the Batch - Batch.sol instance under Deployed Contracts
- Expand the batchAll function
- For to, insert your contract's address, for example:
["INSERT_SIMPLE_CONTRACT_ADDRESS"]
- For value, if your function call does not require any native currency, pass an array with
[0]
- For callData, insert the call data string you obtained earlier in brackets, for example:
["0x648345c800000000000000000000000000000000000000000000000000000000..."]
- For gasLimit, insert an empty array
[]
or the gas limit you wish to enforce - Click transact and confirm in MetaMask
Afterwards, you can verify the contract state change by calling functions (for example, messages(1)
) on the deployed SimpleContract
.
Combining Subtransactions
So far, each operation has been separate, but the real power of batching is to combine transfers and contract interactions into a single transaction. For instance, consider these arrays when using batchAll:
• Three subtransactions:
- A native token transfer to some address
- A call to
SimpleContract
that sets a message - Another call to
SimpleContract
that sets another message
• The to
array might look like this:
[
"0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b",
"0xd14b70a55F6cBAc06d4FA49b99be0370D0e1BD39",
"0xd14b70a55F6cBAc06d4FA49b99be0370D0e1BD39"
]
• The value
array:
["1000000000000000000", "0", "0"]
• The callData
array (first item is empty, so the native token transfer does nothing beyond sending currency; the next two strings correspond to calls of setMessage
with different parameters):
[
"0x",
"0x648345c8000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000009796f752061726520610000000000000000000000000000000000000000000000",
"0x648345c800000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000e61206d6f6f6e6265616d2070726f000000000000000000000000000000000000"
]
• And an empty array for gasLimit
:
[]
Entering these arrays under the batchAll function will execute all three subtransactions—one token transfer and two contract interactions—in a single transaction.