Key Differences from Push-based Messaging
| Feature | Omnichain Message | Omnichain Read |
|---|---|---|
| Flow | Source sends data to destination | Source requests data, source receives response |
| Data | bytes sent = bytes received | bytes request ≠ bytes response |
| Purpose | Push state changes to other chains | Pull external state from other chains |
Supported Chains
lzRead requires compatible Message Libraries (ReadLib1002) and DVNs with archival node access. See Read Paths for available chains and DVNs.

Installation
To start using LayerZero Read in a new project, use the LayerZero CLI tool, create-lz-oapp. The CLI tool allows developers to create any omnichain application with read capabilities quickly! Get started by running the following from your command line:- Example contracts with read capabilities
- Crosschain unit tests for read operations
- Custom LayerZero read configuration files
- Deployment scripts and setup
foundry.toml under [profile.default]:
LayerZero contracts work with both OpenZeppelin V5 and V4 contracts. Specify your desired version in your project’s package.json:
Custom Read Contract
To build your own crosschain read application, inherit fromOAppRead.sol and implement three key pieces:
- Read request construction: How you build queries for external data
- Fee estimation: How you calculate costs before sending requests
- Response handling: How you process returned data in
_lzReceive
- A constructor setting up the LayerZero endpoint, owner, and read channel
- A
readData(...)function that builds and sends read requests - A
quoteReadFee(...)function to estimate costs before sending - An override of
_lzReceive(...)that processes returned data - A target contract interface for type-safe interactions
Target Contract
The read contract interacts with a simple target contract deployed on other chains. This example demonstrates how you can call a public data variable on a destination network and get the current state from another network using LayerZero Read:- Deploy
ExampleContracton a target network withdata = 100 - Deploy
ReadPublicon your source network - Call
readData(targetContractAddress, targetEid, "0x")from source - The contract’s configured DVNs will fetch the current value of
data(100) from the target and emitDataReceived(100)on source
Constructor
- Pass the Endpoint V2 address, owner address, and read channel ID into the base contracts.
OAppRead(_endpoint, _delegate)binds your contract to LayerZero and sets the delegateOwnable(_delegate)makes the delegate the only address that can change configurations_setPeer(_readChannel, AddressCast.toBytes32(address(this)))establishes the read channel
readData(…)
-
Build the read command
_getCmd()constructs the query specifying what data to fetch and from where- Uses
IExampleContract.data.selectorfor type-safe function selection
-
Send the read request
_lzSend()packages and dispatches the read request via LayerZeroREAD_CHANNELis the special channel ID for read operations_combineOptions()merges enforced options with caller-provided options
_lzReceive(…)
-
Endpoint verification
- Only the LayerZero Endpoint can invoke this function
- The call succeeds only if the sender matches the registered read channel peer
-
Decode the returned data
- Use
abi.decode(_message, (uint256))to extract the original data - The data format matches what the target contract’s
data()function returns
- Use
-
Process the result
- Emit
DataReceivedevent with the fetched data - Add any custom business logic needed for your application
- Emit
(Optional) quoteReadFee(…)
You can call the internal_quote(...) method to get accurate cost estimates before sending read requests.
Example usage:
Deployment and Wiring
lzRead wiring is significantly simpler than traditional crosschain messaging setup. Unlike OApp messaging where you need to configure peer connections between each contract on every chain pathway, lzRead only requires configuring the source chain (where yourOAppRead child contract lives and where response data will be returned to).
Key Simplifications:
- Single-sided peer wiring: You only need to set the
OAppReadaddress itself as the peer - Dynamic target selection: Target chains are specified in the read command itself, not in wiring
- DVN requirements: DVNs must support the target chains and
block.numberorblock.timestampyou want to read - Single-direction setup: Only configure the source chain to receive responses
EVMCallRequestV1 and ensuring your DVNs support that chain.
Execution Options for lzRead
lzRead uses different execution options than standard messaging. Instead ofaddExecutorLzReceiveOption, you must use addExecutorLzReadOption with calldata size estimation:
size parameter estimates your response data size in bytes. If your actual response exceeds this size, the executor won’t deliver automatically.
Deploy and Wire
- Deploy & Wire
- Manual Configuration
Deploy your lzRead OApp:Then, review the Deploy and configure your read OApp:This automatically:
layerzero.config.ts with read-specific settings:- Sets the ReadLib1002 as send/receive library
- Configures required DVNs for read operations
- Activates specified read channels
- Sets up executor configuration
Usage
Once deployed and wired, you can begin reading data from contracts on other chains.Read data
- LayerZero CLI
- Foundry Scripts
The LayerZero CLI provides a convenient task for reading crosschain data that automatically handles fee estimation and transaction execution.Required Parameters:The task automatically:
Using the Read Task
The CLI includes a built-inlz:oapp-read:read task that:- Finds your deployed ReadPublic contract automatically
- Quotes the gas cost using your contract’s
quoteReadFee()function - Sends the read request with the correct fee
- Provides tracking links for the transaction
--target-contract: Address of the contract to read from on the target chain--target-eid: Target chain endpoint ID (e.g., 30101 for Ethereum)
--options: Additional execution options as hex string (default: “0x”)
- Finds your deployed ReadPublic contract from deployment artifacts
- Quotes the exact gas fee needed using
quoteReadFee() - Sends the read request with proper fee payment
- Provides block explorer and LayerZero Scan links for tracking
- Shows the transaction details and gas usage
Advanced Read Contracts
lzRead supports several advanced patterns for crosschain data access. Each pattern addresses different use cases, from simple data retrieval to complex multi-chain aggregation with compute logic.Call View/Pure Functions
You can use lzRead to call anyview or pure function on a target chain and bring the returned data back to your source chain contract. This is the fundamental lzRead pattern that enables crosschain function execution without state changes.
Core concept: Instead of deploying identical contracts on every chain or building complex bridging infrastructure, lzRead lets you call functions on any supported chain and receive the results natively. The target function executes via eth_call, ensuring no state modification occurs.
Use cases:
- Crosschain calculations: Call mathematical functions, pricing algorithms, or complex computations
- Remote contract queries: Access getter functions, view state, or computed values from contracts on other chains
- Protocol integration: Query external protocols (like AMMs, lending protocols, oracles) without deploying wrappers
- Data aggregation: Collect information from various chains’ contracts for unified processing
- Validation: Verify conditions or states across multiple chains before executing local logic
- Single
EVMCallRequestV1targeting specific function with parameters - Target function must be
vieworpureto ensure no state changes - Raw response data returned directly to
_lzReceive- no compute processing needed - Function selector and parameter encoding handled via standard ABI encoding
- Works with any function signature: simple getters, complex multi-parameter functions, struct returns
Installation
Get started quickly with a pre-built lzRead example for reading view or pure functions:- Example contracts for reading
view/purefunctions - Deploy and configuration scripts
- Test suites demonstrating all patterns
- Ready-to-use implementations you can customize
Contract Example
- Deploy
ReadViewOrPureon your source network - Call
readSum(5, 10, "0x")to execute the add function on the target chain - The contract’s DVNs fetch the result directly and deliver it to
SumReceived(15)event - No compute processing - raw response delivered directly to your contract
Constructor
- Pass the Endpoint V2 address, owner address, read channel ID, target chain ID, and target contract address
OAppRead(_endpoint, msg.sender)binds your contract to LayerZero and sets the delegateOwnable(msg.sender)makes the deployer the only address that can change configurations_setPeer(READ_CHANNEL, AddressCast.toBytes32(address(this)))establishes the read channel peer relationship
readSum(…)
-
Build the read command
_getCmd()constructs the query specifying target function with parameters- Uses
IExampleContract.add.selectorfor type-safe function selection
-
Send the read request
_lzSend()packages and dispatches the read request via LayerZerocombineOptions()merges enforced options with caller-provided options- Caller must provide sufficient native fee for crosschain execution
_getCmd(…)
-
Encode the function call
- Build
callDatausing function selector and parameters - Standard ABI encoding for target contract interface
- Build
-
Create the read request
- Single
EVMCallRequestV1targeting specific chain and contract appRequestLabel: 1for request tracking and identification- Uses current timestamp for fresh data reads
- Single
-
Encode the command
ReadCodecV1.encode(0, readRequests)with no compute logic- AppLabel 0 indicates basic read without additional processing
_lzReceive(…)
-
Endpoint verification
- Only LayerZero Endpoint can invoke this function
- Validates sender matches registered read channel peer
-
Decode the response
- Extract raw data using
abi.decode(_message, (uint256)) - Data format matches target function’s return type exactly
- Extract raw data using
-
Process the result
- Emit
SumReceivedevent with the fetched data - Add custom business logic, state updates, or trigger additional operations
- Emit
(Optional) quoteReadFee(…)
Estimates messaging fees before sending to avoid transaction failures:Real-world applications
The example above shows a simple mathematical function, but lzRead can call any view/pure function across chains:Add Compute Logic to Responses
Add off-chain data processing to transform, validate, or format response data before it reaches your contract. The compute layer executes between the DVN(s) getting the response data from your target contract and delivering it to your_lzReceive function, allowing complex data manipulation without additional gas costs.
Core concept: After DVNs fetch your requested data, the compute layer can process it off-chain using your custom lzMap and lzReduce functions. This enables data transformation, validation, aggregation, and formatting without consuming gas on your source chain.
How compute processing works:
- DVNs fetch data from your target contract using the specified function call
- lzMap executes (if configured) to transform each individual response
- lzReduce executes (if configured) to aggregate all mapped responses into a final result
- Final result delivered to your
_lzReceivefunction on the source chain
- Data transformation: Convert complex structs into simpler formats your contract needs
- Response validation: Filter out invalid responses or apply business logic rules
- Unit conversion: Convert between different decimal places, currencies, or measurement units
- Data cleaning: Remove outliers, normalize formats, or standardize responses
- Aggregation prep: Process individual responses before combining them
- Format standardization: Ensure all responses follow consistent encoding patterns
- Implement
IOAppMapperfor individual response processing and/orIOAppReducerfor aggregation - Add
EVMCallComputeV1struct to specify compute configuration - Configure
computeSetting:0= lzMap only,1= lzReduce only,2= both - Compute functions execute off-chain, reducing gas costs for complex operations
lzMapprocesses each response individually;lzReducecombines all mapped responses
Installation
Get started quickly with a pre-built lzRead compute example:Contract Example
- Deploy
ReadViewOrPureAndComputeon your source network - Call
readSum(5, 10, "0x")to execute the add function on the target chain with compute processing - The contract’s DVNs fetch the result, lzMap transforms it (+1), lzReduce aggregates each response (by default only 1 response, so unchanged), and the final computed result is delivered to
SumReceived(16)event
Constructor
- Initialize the contract with compute capabilities enabled via
IOAppMapperandIOAppReducerinterfaces - Sets up LayerZero connectivity and establishes read channel peer relationship for compute operations
- The contract becomes both the read requester and the compute processor (via
address(this)in compute configuration)
readSum(…)
Step 1 of compute pipeline: Dispatch read request with compute command-
Build the compute command
_getCmd()constructs both the read request AND the compute configuration- Specifies which compute functions to use (
lzMap,lzReduce, or both)
-
Send the read request
_lzSend()packages and dispatches the read request with compute processing enabled- Higher fees due to compute overhead compared to basic reads
_getCmd(…)
Key difference from basic reads: IncludesEVMCallComputeV1 configuration
- Read request structure: Same as basic pattern - specifies target function and parameters
- Compute configuration: Defines the processing pipeline that will execute after data retrieval
computeSetting: 2enables bothlzMapandlzReduceprocessingto: address(this)specifies this contract contains the compute function implementations
lzMap(…)
Step 2 of compute pipeline: Individual response transformation- Decode raw response
- Extract data from target chain function call result
- Apply transformation logic
- Convert formats, validate data, apply business rules
- Example: increment by 1, but could be unit conversion, filtering, etc.
- Re-encode for next step
- Prepare data for
lzReduceor final delivery to_lzReceive
- Prepare data for
lzReduce(…)
Step 3 of compute pipeline: Response aggregation- Process mapped responses
- Receive array of all
lzMapoutputs - Validate each response format and content
- Receive array of all
- Apply aggregation logic
- Combine responses using your business logic
- Example: sum all values, but could be averaging, min/max, weighted calculations
- Return final result
- Single aggregated value to deliver to
_lzReceive
- Single aggregated value to deliver to
_lzReceive(…)
Step 4 of compute pipeline: Final result processing- Receive computed result
- Data has already been through
lzMapandlzReduceprocessing - Final result is delivered, not raw target chain response
- Data has already been through
- Process final data
- Emit events, update state, trigger additional logic
- Result represents the fully processed and aggregated data
(Optional) quoteReadFee(…)
Fee estimation includes compute processing overhead. Costs are higher than basic reads due to:- Additional compute execution processing
- Data transformation and aggregation operations
- Multiple processing steps in the pipeline
Call Non-View Functions
lzRead can also query functions that aren’t markedview or pure, but still return valuable data without modifying state. This pattern leverages eth_call to safely execute functions that would normally require gas, enabling access to sophisticated onchain computations.
Core concept: Many useful functions (especially in DeFi) aren’t marked view because they rely on calling other non-view functions internally, even though they don’t modify state. lzRead uses eth_call to execute these functions safely, capturing their return values without gas costs or state changes.
Use cases:
- DEX price quotations: Uniswap V3’s
quoteExactInputSinglesimulates swaps to calculate output amounts - Lending protocol queries: Calculate borrow rates, collateral requirements, or liquidation thresholds
- Yield farming calculations: Determine pending rewards, APR calculations, or harvest amounts
- Options pricing: Complex mathematical models for derivative pricing
- Arbitrage detection: Calculate profit opportunities across different protocols
- Liquidation analysis: Determine if positions are liquidatable and expected returns
view:
- They call other non-view functions internally (like Uniswap’s swap simulation)
- They use try-catch blocks or other constructs that prevent
viewdesignation - They access external contracts that may not be
view-compatible - They perform complex state reads that the compiler can’t verify as non-modifying
- Functions must not revert during execution - test parameters thoroughly
- Use proper struct encoding for complex parameters (like Uniswap’s
QuoteExactInputSingleParams) - Handle multi-return-value responses with correct ABI decoding
- Target functions execute via
eth_call, so no actual state changes or gas consumption occur - DVNs verify these calls can execute successfully before returning data
Installation
Get started quickly with a pre-built Uniswap V3 quote reader example:- Uniswap V3 QuoterV2 integration contracts
- Non-view function calling examples
- Multi-chain price aggregation patterns
- Ready-to-deploy implementations for major chains
Contract Example
- Deploy
UniswapV3QuoteDemoon your source network (configured for Ethereum, Base, and Optimism) - Call
readAverageUniswapPrice("0x")to query WETH/USDC prices across all three chains simultaneously - The contract’s DVNs fetch prices from each chain’s Uniswap V3 deployment
lzMapextracts theamountOutfrom each chain’s complex responselzReducecalculates the average price across all chains- Final averaged price is delivered to
AggregatedPrice(averagePrice)event
Constructor
Pre-configures three major chains with their respective Uniswap V3 deployments using hardcoded constants:- Ethereum Mainnet: EID 30101 with WETH/USDC addresses and QuoterV2 contract
- Base Mainnet: EID 30184 with chain-specific token addresses
- Optimism Mainnet: EID 30111 with chain-specific token addresses
- Sets up LayerZero connectivity and establishes read channel peer relationship
- Key advantage: Ready-to-deploy with major chains pre-configured
readAverageUniswapPrice(…)
-
Build multi-chain command
getCmd()constructs read requests for ALL three configured chains- Each request queries
quoteExactInputSinglewith 1 WETH input amount
-
Send aggregated request
- Single
_lzSend()operation handles all three chains simultaneously - More cost-effective than separate requests per chain
- Includes compute configuration for price averaging
- Single
getCmd(…)
Multi-chain request construction with compute:-
Iterate through target chains
- Build
EVMCallRequestV1for Ethereum, Base, and Optimism - Use unique
appRequestLabel(1, 2, 3) to track responses during compute processing
- Build
-
Chain-specific parameters
- Each request uses that chain’s specific QuoterV2, WETH, and USDC addresses
- Maintains consistent
amountIn: 1 etheracross all chains for comparable results - Uses chain-specific confirmation requirements (5 blocks each)
-
Compute configuration
computeSetting: 2enables bothlzMapandlzReducefor response processingtargetEidpoints to source chain for compute executionto: address(this)specifies this contract contains the compute functions
lzMap(…) - Price Extraction
Individual chain response processing:amountOut (USDC amount for 1 WETH) from Uniswap’s complex 4-value response, normalizing all chains to simple price values.
lzReduce(…) - Price Averaging
Crosschain aggregation logic:- Weighted averaging: Weight by liquidity, volume, or chain importance
- Outlier filtering: Remove prices that deviate significantly from median
- Confidence scoring: Account for different chain finality requirements
_lzReceive(…) - Final Price Delivery
Receives the final aggregated price representing the crosschain average WETH/USDC price:- Event emission: Emits
AggregatedPrice(averagePrice)with the computed average - Result format: Single
uint256representing average USDC amount for 1 WETH across all three chains
- Crosschain arbitrage detection: Compare with local prices to find opportunities
- Multi-chain price oracles: Provide robust price feeds aggregating multiple sources
- Risk management: Monitor price discrepancies across deployments
- Liquidity routing: Direct users to chains with optimal pricing
Architecture Benefits
Single Transaction Efficiency:- One read request handles 3+ chains instead of separate transactions
- Reduced gas costs and complexity compared to multiple individual requests
- All chain data fetched and processed together
- No timing discrepancies between separate async requests
- Built-in retry logic across all target chains
- Graceful handling of individual chain failures without affecting others
- Internal non-view calls: Uniswap’s quoter calls other non-view functions internally during swap simulation
- Try-catch blocks: Error handling constructs prevent
viewdesignation even when no state changes occur - Compiler restrictions: Complex state reads that the compiler can’t verify as non-modifying
eth_call to execute these functions, ensuring:
- No actual state changes occur on the target chain
- No gas consumption on the target chain
- Results are cryptographically verified and delivered to your source chain
Multi-Chain Aggregation
Execute identical or related queries across multiple chains simultaneously and combine the results into a single, meaningful response. This is lzRead’s most powerful pattern, enabling true crosschain data synthesis and decision-making. Core concept: Instead of making separate read requests to different chains and manually combining results, multi-chain aggregation fetches data from multiple networks in a single lzRead command. The compute layer processes and combines all responses before delivering the final result to your source chain. Use cases:- Crosschain price feeds: Get token prices from major DEXes on different chains and calculate weighted averages
- Multi-chain governance: Aggregate voting results across different network deployments of your protocol
- Liquidity analysis: Compare pool depths, trading volumes, and rates across chains to find optimal routing
- Risk assessment: Analyze protocol health by checking reserves, utilization rates, and other metrics across deployments
- Arbitrage detection: Find price discrepancies and calculate potential profits across multiple networks
- Portfolio valuation: Calculate total holdings by querying balances and prices across user’s multi-chain positions
- Protocol synchronization: Monitor and compare state across different chain deployments
- Single transaction cost: One read request handles multiple chains instead of separate transactions
- Atomic aggregation: All chain data is processed together, ensuring consistency
- Reduced complexity: No need to manage multiple async requests or coordinate responses
- Gas efficiency: Compute processing happens off-chain, minimizing source chain gas usage
- Failure handling: Built-in retry and error handling across all target chains
- Array of
EVMCallRequestV1structs, each targeting different chains/contracts - Unique
appRequestLabelfor each request to track responses during compute processing lzMapprocesses each chain’s response individually (normalization, validation)lzReducecombines all mapped responses into final aggregated result- DVNs must support all target chains specified in your requests
Contract Example
Refer to the sameUniswapV3QuoteDemo contract under Non-View Functions
Architecture Benefits
Single Transaction Efficiency:- One read request handles 3+ chains instead of separate transactions
- Reduced gas costs and complexity compared to multiple individual requests
- All chain data fetched and processed together
- No timing discrepancies between separate async requests
- Built-in retry logic across all target chains
- Graceful handling of individual chain failures without affecting others
Hybrid Messaging + Read
For applications that need both messaging and read capabilities:Debugging
lzRead introduces unique challenges compared to standard LayerZero messaging. This comprehensive debugging guide covers common pitfalls, specific error scenarios, and practical solutions to help you troubleshoot lzRead implementations effectively.1. Incorrect Execution Options Type
❌ Problem: Using standard messaging options instead of lzRead-specific options causes transaction reverts. Root Cause: lzRead requiresaddExecutorLzReadOption with calldata size estimation, not addExecutorLzReceiveOption.
enforcedOptions or extraOptions:
2. Target Function Reverts (DVN Fulfillment Failure)
❌ Problem: When the target function reverts during execution, DVNs cannot fulfill the request, causing the entire read operation to fail. Root Cause: DVNs useeth_call to execute target functions. If the function reverts with the provided parameters, verification cannot complete.
Common Revert Scenarios:
- Invalid parameters passed to target function
- Target contract state changes between request and execution
- Insufficient target chain block confirmations
- Target function has built-in parameter validation that fails
- ✅ Test target function calls with your exact parameters on target chain
- ✅ Ensure target contract exists at the specified address
- ✅ Verify function selector matches target contract interface
- ✅ Check that target function doesn’t have restrictive access controls
- ✅ Test with realistic parameter ranges and edge cases
3. Nonce Ordering Issues (Sequential Verification Failure)
❌ Problem: If the first nonce in a sequence fails, all subsequent nonces are blocked because LayerZero verification is ordered. Root Cause: LayerZero processes nonces sequentially. A failed or stuck nonce prevents processing of later nonces until resolved.- Later transactions succeed but never receive responses
- LayerZero scan shows “Verified” but not “Delivered” for subsequent messages
- Nonce gaps in your application’s message history
- Identify the failed nonce causing the blockage using nonce status checks
- Use
endpoint.skip()to bypass the failed nonce and unblock subsequent processing - Ensure subsequent requests are properly formatted and verifiable before sending new reads
- Implement prevention by validating all parameters before sending future requests
4. Calldata Size Estimation Errors
❌ Problem: Underestimating response size inaddExecutorLzReadOption causes executor delivery failures.
Root Cause: Executors pre-allocate gas based on your size estimate. If the actual response exceeds this size, automatic delivery fails.
5. Block Number vs Timestamp on L2 Chains
❌ Problem: Usingblock.number on L2 chains often references L1 block numbers, causing timing and finality issues.
Root Cause: Many L2s inherit block numbers from their L1 parent chain, making block.number unsuitable for timing-sensitive operations.
Affected Chains:
- Arbitrum:
block.numberreturns L1 block number, not L2 sequence numbers - Optimism: Similar L1 block number inheritance in some contexts
Further Reading
- Read Standard Overview - Conceptual information
- Read Paths & DVNs - Available chains and DVNs
- Execution Options - Options configuration
