Normal transactions can make only a single call to a smart contract. This means that when we need to call a function many times, or interact with different smart contracts, we need a separate transaction for each smart contract interaction.
Composable Transactions bring the ability to make many calls in a single transaction, either to the same or to different smart contracts.
Even more than just calls, this feature allows a variety of commands to be executed, including storing the result of a call into a (temporary) variable, conversion of the value, math operations, and assertion (reverts the whole transaction if evaluates to false
)
Characteristics
- Multiple calls
- Atomicity
- Sequential execution
- Act on current state
- Scripting
- No blind signing
Atomicity
If any of the calls fail, the entire transaction is aborted.
This means that either all calls succeed or none of them.
Sequential Execution
Execute a series of contract calls with the guarantee that they happen sequentially with no external action occurring between them.
Act on current state
Do actions using the current values from contracts and account states by making queries to contracts and using the returned value on the same transaction.
This is useful when another transaction arrives first and changes the contract state.
Scripting
Instead of just a list of calls, it is possible to process the returned values and act according to them.
The language supports data conversion and manipulation, assertion, conditional execution, and more.
No Blind Signing
The transaction can be reviewed even on hardware wallets before approval or rejection
The user can check each command and its arguments
The scripts contain the function signature (human readable). The function selector is computed at execution time
Use Cases
1) Purchase airplane tickets and hotel reservation from different contracts, only if both succeed. If some of the purchases fail, the other is reverted (the user does not want one of them if the other is not acquired)
2) Swap token and buy something with it. If the purchase fails, the user does not want that intermediary token
3) Transferring token (fungible and/or non-fungible) to multiple recipients at the same time
4) Transferring different tokens (fungible and/or non-fungible) to one recipient in a single transaction
5) Mint many non-fungible tokens (NFTs) in a single transaction
6) Trustless swap (or purchase): check if the purchased token was received, otherwise revert the transaction
7) Swap tokens using a split route. Check the minimum output in the transaction itself and revert if not satisfied
8) Swap to an exact amount of the output token when using a multi-hop route, by querying a contract for the right amount to send in the same transaction
9) Approve contract B to use contract A (token) and then call a function on contract B that handles the resources on contract A (eg: approve and swap, approve and add liquidity) on a single transaction
10) Approve, use resource, and remove approval on a single transaction, with the guarantee that no other operation would happen in between while the resource/token is approved to contract B
It also allows (by creating compatible smart contracts):
11) Swap tokens without approval
12) Add liquidity to a pool (2 tokens) in a single transaction without approval
13) Multisig wallet users and DAO users can create and vote on an entire script of commands
Implementation
Client Side
As most clients use web-browsers, the scripts are built in JSON format, like this one:
[
["call","<token 1>","transfer(address,uint256)","<recipient 1>","<amount 1>"],
["call","<token 2>","transfer(address,uint256)","<recipient 2>","<amount 2>"],
["call","<token 3>","transfer(address,uint256)","<recipient 3>","<amount 3>"]
]
As this format is not efficiently processed by EVM code, it is then converted to binary format using a small library on the browser (or another environment)
The converted script in binary format is then included in the transaction as the payload
The transaction is marked with a special “type” to differentiate from normal transactions.
The current test implementation uses this method:
- recipient address := signer address
- amount := 0
- payload := binary script
But it can have some other method, like a explicit type (MULTICALL or SCRIPT …)
Execution Side
The execution client can identify these transactions and process them accordingly
The current implementation uses a smart contract to parse and execute the binary script / commands
There is no reference to this contract on the transaction
The execution client calls a function on this contract passing the binary script as argument.
The call is made using the CALLCODE opcode (0xF2) executed as the caller account.
The binary format is engineered to minimize gas usage when processed
Usage by Contracts
As the script executor is a smart contract, other contracts can also interface with it using the DELEGATECALL opcode (0xF4) and passing the script as argument.
One use would be by MultiSig wallets and DAOs, in which scripts can be stored, reviewed, voted, and only executed when/if approved.
Other resources
- Scripts
- Complete list of commands
- Example Scripts for Specific Use Cases
- Human-readable display of amounts
- Display on hardware wallets
- Templates
Other discussion places
As the goal is to implement a single (or very similar) specification that can be used on most EVM-compatible blockchains (to make dApps simpler) there are separate discussions with different teams from other blockchain projects and we have also a common channel in which anyone can come together to share thoughts for cross-chain compatibility