Prerequisites
Before implementing OVault, you should understand:- OFT Standard: How Omnichain Fungible Tokens work and what the typical deployment looks like
- Composer Pattern: Understanding of
composeMsgencoding and crosschain message workflows - ERC-4626 Vaults: How the tokenized vault standard interface works for
deposit/redeemoperations
- an
OFTasset - an
ERC4626vault - an
OFTAdapterto transform the vault’s share into an omnichain token - a
VaultComposerSyncto orchestrate omnichain deposits and redemptions between the asset and share - an
OFTto represent the shares on spoke chains
Step 1. Project Installation
To start using LayerZero OVault contracts in a new project, use the LayerZero CLI tool, create-lz-oapp. The CLI tool allows developers to create any omnichain application in <4 minutes! Get started by running the following from your command line:.env.example in the project root, add your PRIVATE_KEY, RPC_URL you will be working with, and rename the file to .env:
Step 2. Network Configuration
Updatehardhat.config.ts to include your desired networks. Modify your .env file or the URL directly to change network RPCs:
Step 3. Deployment Configuration
Configure your vault deployment indevtools/deployConfig.ts. This file controls which contracts to deploy and to what chains.
The deployConfig supports several modes, depending on what contracts already have been deployed on the hub chain. If your asset token is already an OFT, you do not need to deploy a new OFT contract mesh. Both Stargate Hydra assets (e.g., USDC.e) and standard OFTs (e.g., USDT0) can be used as the asset inside the ERC4626 vault. To see a list of existing OFT-compatible assets, review the LayerZero OFT API.
Pick the setup section that best aligns with your deployment needs:
- 3.1a Existing AssetOFT
- 3.1b Existing AssetOFT and Vault
- 3.1c Existing AssetOFT, Vault, and ShareOFTAdapter
3.1a Existing AssetOFT
The only requirement is that yourassetOFT is deployed on the hub chain defined in the deployConfig file.
-
Update the
_hubEidand the_spokeEidsfor the networks you plan to deploy to accordingly. -
Add the
assetOFTAddresscontract for the_hubEidnetwork under yourvaultconfig. -
Add any changes necessary to your
VaultandShareOFTconfigcontractormetadata.
3.1b Existing AssetOFT and Vault
If yourassetOFT and ERC4626 contracts are already deployed, you only need to deploy the ShareOFTAdapter and Composer.
-
Update the
_hubEidand the_spokeEidsfor the networks you plan to deploy to accordingly. -
Add the
vaultAddressandassetOFTAddressfor the_hubEidnetwork under yourvaultconfig. -
Add any changes necessary to your
ShareOFTconfigcontractormetadata.
This configuration will skip deploying the
AssetOFT and ERC4626 Vault contracts, deploying only the ShareOFTAdapter and Composer.3.1c Existing AssetOFT, Vault, and ShareOFT
If yourassetOFT, ERC4626, and ShareOFTAdapter have already been deployed, you only need to deploy the Composer.
-
Update the
_hubEidand the_spokeEidsfor the networks you plan to deploy to accordingly. -
Add the
vaultAddress,assetOFTAddress, andshareOFTAdapterAddressfor the_hubEidnetwork under yourvaultconfig. -
Add any changes necessary to your
composerconfig undervault.
Vault, ShareOFT, and AssetOFT configs and deployments will be skipped.
3.1d New AssetOFT, Vault, and ShareOFT
If you have no existing OVault contracts, this configuration will deployAssetOFT, ERC4626 vault, ShareOFTAdapter, and the Composer.
This configuration will deploy all core OVault contracts for a full fresh setup.
3.2 Build
Compile your contracts:If you’re deploying the asset OFT from scratch for testing purposes, you’ll need to mint an initial supply. Uncomment the
_mint line in the MyAssetOFT constructor to provide initial liquidity. This ensures you have tokens to test deposit and crosschain transfer functionality.3.3 Deploy
Deploy all vault contracts across all configured chains:deployConfig.ts, this single command will begin deploying the defined contracts on your target _hubEid and _spokeEids.
The deployment scripts automatically skip existing deployments, so you can safely run this command when expanding to new chains. Simply add the new chain endpoints to your deployConfig.ts and run the deploy command again.
Tip: To deploy to specific networks only, use the--networksflag:
Step 4. Wiring New Mesh
This establishes the peer relationships between each OFT deployment, enabling crosschain token transfers. See the OFT Wiring Step for more information. Depending on your deployment configuration in Step 3, you will have to wire either your newly deployedShareOFT, AssetOFT, or both.
4.1 Existing Asset
After modifying yourlayerzero.share.config.ts:
4.2 Existing Asset & Share
No action needed.4.3 New Asset & Share
After modifying yourlayerzero.asset.config.ts and layerzero.share.config.ts:
Step 5: Usage
OVault enables two main operation patterns: deposits and redemptions. Each uses the standardOFT.send() interface with the composer handling vault operations automatically.
The provided project scaffold demonstrates how to create send calls in devtools/examples/ovault-evm/tasks/sendOVaultComposer.ts.
Deposit Assets → Receive Shares
Scenario: Depositasset from a _spokeEid, receive vault shares on the same _spokeEid
Scenario: Deposit
asset from a _spokeEid, receive vault shares on a different _spokeEid
Scenario: Deposit
asset from _spokeEid, receive vault shares on the _hubEid chain
Redeem Shares → Receive Assets
Scenario: Redeem vaultshares from _spokeEid, receive asset on different _spokeEid
Scenario: Redeem
vault shares from _spokeEid, receive asset on the _hubEid chain
SDK Integration
For programmatic integration, use the official SDK@layerzerolabs/ovault-evm/src which simplifies OVault operations by using viem to generate the necessary calldata for calling OFT.send() with the proper composeMsg for the hub composer.
The SDK’s OVaultMessageBuilder.generateOVaultInputs() method handles all the complex message encoding and returns ready-to-use transaction parameters for viem wallet clients.
Technical Reference
Contracts Overview
OVault uses a hub-and-spoke model:- Hub Chain: Hosts the
OFTasset,ERC4626vault, theVaultComposerSync, and the share’sOFTAdapter(lockbox) - Spoke Chains: Host
OFTassets andOFTshares that connect to the hub implementations
OFT from a source blockchain, deposit or redeem the token amount in the ERC4626 vault, and receive the corresponding output token amount back on the source network.
If you have an existing
assetOFT, vault, or ShareOFT implementation, you may only need to deploy some of the contracts provided in the ovault-evm example repo:
Share OFT (Spoke Chains)
MyAssetOFT, MyShareOFT is a standard OFT representation of the share token from the ERC4626 vault to be used on other spoke chains. This contract requires MyShareOFTAdapter to be deployed on the hub chain using the share address as the _token argument.
If your intended vault
share is already an OFT (e.g., sUSDe), you do not need to deploy this contract, and will only need to deploy MyOVaultComposer.Asset OFT (All Chains)
MyAssetOFT is an example of a standard OFT and ERC20 token that will be the asset inside the ERC4626 vault.
The asset token must be deployed on at least the hub and one spoke chain.
If your intended vault
asset is already an OFT (e.g., USDT0, USDe), you do not need to deploy this contract. if your vault asset is not an OFT (e.g., USDC via CCTP), you will need to convert the asset into an OFT compatible asset (e.g., USDC via Stargate Hydra, OFTAdapter).See the OFT API /list endpoint for a detailed list of all known tokens using the OFT standard.Vault + Share Adapter (Hub Chain)
MyERC4626 is the standard tokenized vault contract. Given an _asset address for a valid ERC20 contract in the constructor, the vault will create a corresponding share token using the vanilla ERC4626 implementation.
This share must then be transformed into an Omnichain Fungible Token using MyShareOFTAdapter.
If you have an existing
ERC4626 vault deployed, you will only need to deploy MyShareOFTAdapter using the share token address as the address _token argument in the constructor.Composer (Hub Chain)
VaultComposerSync is the orchestrator contract that enables crosschain vault operations between the OFT standard and ERC-4626 vaults, automatically handling deposits and redemptions based on incoming token transfers.
The “Sync” in VaultComposerSync refers to synchronous vault operations - meaning the vault must support immediate, single-transaction deposits and redemptions without delays or waiting periods.
VaultComposerSync Contract
TheVaultComposerSync contract is the core orchestrator for omnichain vault operations. It handles deposits, redemptions, and automatic refunds for crosschain vault interactions.
Key Methods
depositAndSend()
redeemAndSend()
quoteSend()
lzCompose()
handleCompose()
State Variables
VAULT()
ASSET_OFT()
ASSET_ERC20()
SHARE_OFT()
SHARE_ERC20()
ENDPOINT()
VAULT_EID()
Events
Sent - Vault Operation Success
Refunded - Operation Failed with Refund
Deposited - Assets Deposited
Redeemed - Shares Redeemed
Error Messages
ShareOFTNotAdapter - Invalid Share Configuration
ShareTokenNotVault - Share Token Mismatch
AssetTokenNotVaultAsset - Asset Token Mismatch
OnlyEndpoint - Unauthorized Endpoint Call
OnlySelf - Invalid Self-Call
OnlyValidComposeCaller - Invalid Compose Caller
InsufficientMsgValue - Not Enough Gas for Delivery
NoMsgValueExpected - Unexpected Payment
SlippageExceeded - Slippage Protection Triggered
Integration Notes
When integrating with VaultComposerSync:- Always use quoteSend() before operations to get accurate fee estimates and preview vault conversions
- Set appropriate slippage in the compose message’s
minAmountLDparameter (2-5% recommended) - Monitor events to track operation success (
Sent) or refunds (Refunded) - Handle refund scenarios by monitoring both source and hub chains for refunded tokens
- Check vault limits before operations using
vault.maxDeposit()andvault.maxRedeem()
Two-Phase Operation Flow
OVault operations follow a two-phase architecture where failures and slippage protection occur in distinct stages: Phase 1: Source → Hub (Standard OFT)- User calls
OFT.send()targeting the hub composer - Standard LayerZero transfer with compose message
- Reliable transfer with minimal failure modes
- Composer executes vault operations (
deposit/redeem) - Critical slippage point: Vault conversion rates may have changed
- Output tokens routed to final destination (local or crosschain)
Operation Detection
The composer automatically determines the vault operation based on which OFT sent the tokens:- AssetOFT caller → Triggers
depositoperation (assets→shares) - ShareOFT caller → Triggers
redeemoperation (shares→assets)
Slippage Protection Strategy
Since the real slippage occurs during vault operations on the hub, thecomposeMsg contains the critical slippage parameters:
- Phase 1
minAmountLD: Set for source token (not critical for vault rates) - Phase 2
minAmountLD: Set incomposeMsgfor vault output (critical protection)
1. Standard OFT Transfer Initiation
Users call the standard OFT interface with compose instructions:2a. Composer Message Reception
When tokens arrive at the hub vialzReceive(), the composer is triggered via lzCompose():
2b. Operation Detection & Execution
The composer automatically determines the vault operation based on which OFT sent the tokens: Asset Deposit Flow (AssetOFT → Composer):2c. Smart Output Routing
The_send() function handles both local and crosschain delivery:
Key Implementation Tips
- Start Simple: Deploy a basic vault first, add yield strategies later
- Test Thoroughly: Each operation type has different gas requirements
- Monitor Closely: Set up alerts for failed compose messages
- Plan Recovery: Document procedures for each failure scenario
- Optimize Gas: Use the task’s automatic optimization, adjust as needed
Troubleshooting
OVault operations have only two possible final outcomes:Success or Failed (but Refunded). Understanding the failure flow helps determine appropriate recovery actions.
Refund Scenarios and Recovery
TheVaultComposerSync uses a try-catch pattern around handleCompose() to ensure robust error handling:
InsufficientMsgValue- insufficient gas for destination delivery → Auto refundSlippageExceeded- vault output below minimum → Manual refund available- Vault operational errors (paused, insufficient liquidity) → Manual refund available
Transaction Revert: Gas or Fee Issues
What happens: OFT transfer fails on source chain before any tokens move Common causes:- Insufficient native tokens for LayerZero fees
- Invalid destination endpoint configuration
- Gas estimation errors
- Use
quoteSend()to get accurate fee estimation - Verify destination chain configuration
- Ensure sufficient native tokens for crosschain fees
Automatic Refund: Insufficient msg.value for Second Hop
What happens: LayerZero completes lzReceive and lzCompose successfully, but insufficient gas for destination delivery Technical flow:- LZ Executor calls
lzReceive()- tokens credited to composer ✓ - OFT calls
endpoint.sendCompose()- composeMsg stored ✓ - LZ Executor calls
lzCompose()on VaultComposerSync ✓ - Try-catch around
handleCompose()catchesInsufficientMsgValuerevert - Automatic
_refund()triggered back to source chain
- Underestimated gas for second hop during
quoteSend() - Gas price fluctuations between quote and execution
- Complex destination chain operations requiring more gas
- Crosschain transfer appears successful initially
- Composer automatically triggers refund to source chain
- Original tokens returned within minutes
- Monitor source chain for refunded tokens
- Retry with higher gas estimate from
quoteSend()
Manual Refund: Vault Operation Issues
What happens: LayerZero flow completes successfully, but vault operation fails slippage check Technical flow:- LZ Executor calls
lzReceive()- tokens credited to composer ✓ - OFT calls
endpoint.sendCompose()- composeMsg stored ✓ - LZ Executor calls
lzCompose()on VaultComposerSync ✓ - Try-catch around
handleCompose()executes vault operation ✓ - Vault returns
actualAmount(shares or assets) - Slippage check:
actualAmount >= minAmountLDFAILS SlippageExceededrevert caught by try-catch- Manual
_refund()available (user must trigger)
- Vault share/asset price changed during crosschain transfer
- Vault hit deposit/withdrawal limits between quote and execution
minAmountLDset too high based on stalepreviewDeposit/previewRedeemdata
- Crosschain transfer succeeds
- Vault operation fails on hub with slippage error
- Tokens held by composer awaiting user action
- Switch wallet to hub chain network
- Call composer refund function
- Original tokens returned to source chain
- Retry operation with adjusted slippage tolerance
- Use wider slippage tolerance (2-5% for volatile vaults)
- Check vault limits:
vault.maxDeposit(),vault.maxRedeem() - Monitor vault state with
vault.previewDeposit()before large operations - Account for time delays in crosschain operations when setting
minAmountLD