Skip to main content

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:

  1. Make a read request to the onchain contract
  2. Set up a promise with a callback function
  3. 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​

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 function
    • returnData: 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.