Read onchain from EVMx
EVMx allows you to read state from contracts deployed on blockchains. This chain-abstracted applications that need to access or verify onchain data before executing transactions to asynchronisely read the data before performing any other required operation.
The challenge of chain-abstracted reads​
When working with multiple blockchains, reading data presents a unique challenge: data cannot be read synchronously across chains. Instead, EVMx uses a promise-based pattern to handle asynchronous data retrieval.
Promise-based reading pattern​
Reading data from onchain contracts follows this pattern:
- Make a read request to the onchain contract
- Set up a promise with a callback function
- Process the returned data in the callback function
↘ See a reference implementation of this functionality here.
Here's an example of checking a user's token balance before initiating a transfer:
function transfer(uint256 amount, address srcForwarder, address dstForwarder)
external
async
returns (bytes32 asyncId)
{
// Enable read mode
_setOverrides(Read.ON);
// Request balance from source chain
ISomeContract(srcForwarder).balanceOf(msg.sender);
// Set up callback with necessary data
IPromise(srcForwarder).then(this.checkBalance.selector, abi.encode(amount));
// Disable read mode
_setOverrides(Read.OFF);
// These operations will only execute if the balance check passes
// Notice here that no promises were set as burn and mint are not expected to return anything
ISomeContract(srcForwarder).burn(msg.sender, amount);
ISomeContract(dstForwarder).mint(msg.sender, amount);
}
function checkBalance(bytes memory data, bytes memory returnData) external onlyPromises {
uint256 amount = abi.decode(data, uint256);
bytes32 asyncId = _getCurrentAsyncId();
uint256 balance = abi.decode(returnData, (uint256));
if (balance < amount) {
_revertTx(asyncId);
return;
}
}
Key components​
Async Modifier​
The async
modifier signals that this function will perform operations that interact with blockchains.
Forwarder Address​
Instead of calling the contract directly, you call a forwarder address that routes your call to the correct contract on the target blockchain.
Read mode​
_setOverrides(Read.ON)
signals the start of a read operation_setOverrides(Read.OFF)
signals the end of a read operation
Promise setup​
The then
function takes two parameters:
- The callback function selector (e.g.,
this.checkBalance.selector
) - Encoded data needed by the callback (e.g.,
abi.encode(amount)
)
Callback function​
- Must use the
onlyPromises
modifier - Takes two parameters:
data
: The encoded data from the original functionreturnData
: The actual data returned from the source chain
↘ Learn more about promises here.
Interface modifications​
When defining interfaces for chain-abstracted reads, you need to:
- Remove visibility modifiers (like
view
) - Remove return value declarations from the function signature
interface IISomeContract {
// For EVMx - no view modifier, no return value
function balanceOf(address owner) external;
// Standard ERC20 interface would be:
// function balanceOf(address owner) external view returns (uint256);
}
Handling Conditional Execution​
You can control the execution flow based on read results using the _revertTx
function:
if (balance < amount) {
_revertTx(asyncId);
return;
}
This function cancels the remaining operations in the transaction queue associated with the given asyncId
, preventing subsequent operations from executing.
Best Practices​
- Always validate data before performing critical operations
- Implement proper error handling for read failures
By following these patterns, you can safely and efficiently read data from onchain contracts before executing chain-abstracted transactions.
Common errors​
Seeing Failed to estimate EIP1559 fees
when running EVMx scripts or cast commands
Please ensure you have --legacy
flag when running the commands.
Seeing Failed to estimate gas for tx
when running EVMx scripts or cast commands
Please ensure you have --with-gas-price 0
flag when running scripts and --gas-price 0
flag when running commands.
I cannot see transactions for my new AppGateway on the EVMx explorer
Please confirm you have updated the APP_GATEWAY
variable on the .env
file.
If you have exported your .env
file, please confirm that the variable is up to date on your environment.
Deploying onchain contracts is reverting with 0x8d53e553
- InsufficientFees()
Please confirm you have deposited enough to pay for fees.
- See how to Deposit fees.
- Check your AppGateway fee balance.
Reading onchain contracts via EVMx is reverting with 0xb9521e1a
- AsyncModifierNotUsed()
Please confirm the function you're calling has the async
modifier as it is expected to wait for a promise since it is either reading or writing information onchain. See key components for onchain reads.
Reading onchain contracts via EVMx is reverting with generic EVM error
Please confirm you are
- Passing the forwarder contract address and not the onchain contract address. See key components for onchain reads
- Setting the expected overrides to read. See Promise-based reading pattern.
Not being able to read onchain values via EVMx - can't change state in a static call
Please confirm you are setting a promise to read the value after requesting an onchain read. See key components for onchain reads
Remember you cannot directly perform actions when reading values as calls are asynchronous. For instance, this means that require(ICounter(forwarder).value() == 0)
is not possible. You will need to confirm the read value is zero on the promise handling. See Promise-based reading pattern.