How it works
The hub chain is the chain where the vault and the adapter are deployed, and a source chain is any other chain that users send deposits and redemptions from.
A request to the Cross-Chain Vault Adapter is one CCIP message that carries the vault's asset or shares from a source chain to the hub chain. In the transaction that receives the message, the adapter calls the vault, then either delivers the output on the hub chain or sends it back to the source chain. This page follows a request that succeeds. Failures and recovery covers requests that do not.
Architecture
The vault is the product. It holds one asset and earns the yield. CCIP is the bridge that moves tokens between chains. The adapter is how users on other chains reach the vault. It turns an incoming CCIP message into a vault call. For how CCIP itself carries a message, see the CCIP architecture overview.
The following diagram shows the contracts in a deployment, where each one lives, and what the adapter holds: its configuration, its three roles, and its link to the hub chain's CCIP Router.
All three contracts live on the hub chain:
- Vault: the team's existing ERC-4626 vault, unchanged. It holds one asset and issues the share token.
- Adapter:
CrossChainERC4626Adapterreceives CCIP messages, calls the vault, and delivers or returns the output. The vault team deploys and operates it. - Factory:
CrossChainERC4626AdapterFactorydeploys an adapter with its initial configuration in one transaction, then hands every role to the vault team's accounts.
The adapter needs no contract on source chains. Users call ccipSend on the CCIP Router there directly. On the hub chain, the CCIP Router delivers messages to the adapter and sends the adapter's return legs. Shares that travel to or from a source chain do need a share token deployed there, as described in Redeem flow.
The adapter enforces the configuration shown in the diagram:
- Allowed source chains: a chain type per CCIP chain selector, set with
setChainType. A selector set toNONEis not allowed. - Allowed vaults: set with
setTargetEnabled. The payload names one of them. - Deposit and redeem switches:
depositsEnabledandredeemsEnabled, set together withsetProcessingEnabled. - Adapter fees: see Fees.
- CCIP 2.0 settings: the inbound verifier and finality policy per source chain, and the return format and finality per destination. See CCIP 2.0 lanes.
- Roles:
DEFAULT_ADMIN_ROLEchanges the configuration,FEE_SETTER_ROLEsets adapter fees, andFEE_COLLECTOR_ROLEwithdraws them. See Roles.
One adapter can serve several vaults and several source chains. The adapter does not check who sent a message. Any sender on an allowed source chain can deposit into or redeem from any allowlisted vault. The CCIP Router address is fixed when the adapter is deployed, and only that router can call ccipReceive.
What a user sends
A user sends one CCIP message to the adapter. The message must carry exactly one token in tokenAmounts and a payload of exactly 128 bytes in data. The token decides the action:
- The vault's asset (the address returned by the vault's
asset()) means deposit. - The vault's share token (the vault address itself) means redeem.
- Any other token fails the request.
The payload is the ABI encoding of four 32-byte fields:
Field | Type | Meaning |
|---|---|---|
target | address | The vault to deposit into or redeem from. It must be on the adapter's allowlist. |
beneficiary | bytes32 | The account that receives the output: on the hub chain for local delivery, on the source chain for a return. It must be an EVM address left-padded with zeros, so the upper 12 bytes are zero. |
minimumOut | uint256 | The smallest acceptable output after the adapter fee, in output units: shares for a deposit, asset for a redemption. See Slippage protection. |
deliveryAndRefund | uint256 | Two delivery and refund options packed into one word. Bit 0 is the return-to-source flag, and bits 1 to 160 hold the local refund address. |
The sender packs the last field as (uint256(uint160(localRefundAddress)) << 1) | (returnToSourceChain ? 1 : 0). The adapter ignores bits 161 to 255. The local refund address is the hub-chain account allowed to take back the tokens if the request fails. The message format reference has the exact encoding. Build the payload builds one with the repository's pnpm ccip:build-payload script.
The rest of the message follows standard CCIP rules:
- Receiver: the adapter address on the hub chain.
- Fee token: the sender pays the CCIP fee on the source chain in the native gas token or LINK.
- Extra arguments: on a CCIP 2.0 lane, the message carries
GenericExtraArgsV3, encoded withExtraArgsCodec.
The gas limit in the extra arguments pays for everything the adapter does on the hub chain: its checks, the vault call, and, when the output returns to the source chain, the outbound CCIP send. CCIP bills the sender for the gas limit, not the gas used. Measure the gas with a test message against your own vault, and give requests that return output to the source chain more headroom than local delivery, because only they include the outbound send. If the limit is too low, ccipReceive can run out of gas before it records the failure, and CCIP marks the message as failed. Recovery then needs CCIP manual execution with a higher limit.
The requested finality in the extra arguments must be one that both the adapter and the token pools allow. By default, the adapter accepts only messages that wait for finality.
Deposit flow
A deposit turns the asset sent from the source chain into vault shares. The following diagram shows a deposit from a source chain into a vault on the hub chain, with the return-to-source branch dashed.
The request passes these checks and decisions in order. From the second item on, the first check that fails turns the request into a failed message, and What counts as a failure lists the error for each.
- CCIP 2.0 policy, before the adapter runs. On a CCIP 2.0 lane, the OffRamp on the hub chain checks the message against the adapter's Cross-Chain Verifier (CCV) and finality policy for the source chain. If it passes, CCIP transfers the asset to the adapter and calls
ccipReceive, which runs every later check in an isolated self-call,processMessage. - Source chain and message shape. The source chain's selector must not be set to
NONE. The message must carry exactly one token and a 128-byte payload. - Vault and amount. The payload's
targetmust be allowlisted, which the adapter checks before it looks at the token. The amount must be greater than zero. - Action. The token is the vault's asset, so the request is a deposit, and
depositsEnabledmust be true. - Adapter fee, return only. When bit 0 of
deliveryAndRefundasks for a return, the adapter deducts the fee keyed by the source chain's selector and the share token. The fee must be smaller than the amount. - Vault deposit. The adapter deposits the rest with itself as the receiver, so it is the depositor and the first owner of the new shares. If the vault's
depositreverts, the request fails. - Output. The vault must mint more than zero shares and at least
minimumOut. - Delivery. In both branches, the beneficiary's upper 12 bytes must be zero. For local delivery, the adapter transfers the shares on the hub chain. For a return, it quotes the return-leg CCIP fee, pays it from its own native gas token balance, and sends the shares to the source chain in a second CCIP message.
The adapter's checks, the vault call, and the delivery all run in one transaction on the hub chain. A request that passes them ends with MessageSucceeded. If any check or call fails, the adapter rolls back the vault call, the fee, and the outbound send, keeps the inbound asset, and records a failed message. The CCIP Explorer still shows Success, as Failures and recovery explains.
Redeem flow
A redemption turns shares sent from the source chain into the vault's asset. It follows the deposit flow with these differences:
- The user sends shares. The vault's share token must be a CCIP cross-chain token (CCT) on the lane. A typical setup uses a Lock & Release pool on the hub chain, because only the vault may mint shares, and a Burn & Mint pool on the source chain. The Lock & Mint tutorial shows that setup.
- The adapter calls
redeem. The token is the vault address, so the adapter routes the request as a redemption, which requiresredeemsEnabled. It redeems the shares as both owner and receiver. - The adapter fee comes from the output. When returning, the adapter deducts the fee from the redeemed assets instead of from the input.
minimumOutis in asset units. The adapter compares it with the redeemed assets after the fee.- The output is the asset. The adapter transfers it to the beneficiary on the hub chain or sends it back to the source chain.
Delivery options
Bit 0 of deliveryAndRefund chooses where the output goes. The following table compares the two options.
| Local delivery | Return to the source chain | |
|---|---|---|
| Where the output goes | The beneficiary on the hub chain | The beneficiary on the source chain the message came from |
| Adapter fee | None | Charged in asset units |
| CCIP fee for delivery | None, because there is no second message | Paid by the adapter from its native gas token balance on the hub chain |
| Tokens that must be CCIP-enabled for deposit | The asset | The asset and the share token |
| Tokens that must be CCIP-enabled for redeem | The share token | The share token and the asset |
| Extra latency | None | One more CCIP message, from the hub chain to the source chain |
| Event on the hub chain | LocalTokenDelivered | MessageSent, with the return message's CCIP message ID |
A return goes only to the chain the request came from, never to a third chain.
The return leg is a separate, token-only CCIP message with empty data and a gas limit of 0. The adapter builds it itself and never copies extra arguments from the user's message. Track it on the CCIP Explorer with the message ID from MessageSent.
Fees
A request can involve up to four fees. The CCIP billing page explains how CCIP prices a message.
| Fee | Who pays | Paid in | When | Controlled by |
|---|---|---|---|---|
| CCIP fee for the user message | The sender, on the source chain | Native gas token or LINK | Every request | CCIP |
| Adapter fee | The user, from the deposited asset or the redeemed asset | The asset's smallest units | Only when the output returns to the source chain | Set by FEE_SETTER_ROLE with setAssetFee or setAssetFees; withdrawn by FEE_COLLECTOR_ROLE with withdrawFee |
| Return-leg CCIP fee | The adapter, from its native gas token balance on the hub chain | Native gas token | Every return to the source chain | CCIP |
| Refund CCIP fee | Whoever calls the cross-chain refund, through msg.value | Native gas token | Only for a cross-chain refund of a failed message | CCIP; the adapter returns any excess msg.value to the caller |
Adapter fee
The adapter fee is a flat amount, stored in assetFees. The adapter charges the fee on both deposits and redemptions whose output returns to the source chain, and it takes the fee from the deposited asset before a deposit or from the redeemed asset after a redemption. The fee is keyed by the source chain selector and the token that goes back: the share token for a deposit return, the asset for a redeem return. The amount is in the asset's smallest units even when shares go back.
The adapter reads the fee when the message executes on the hub chain, so a fee change made while a message is in flight applies to that message. If the fee is equal to or larger than the deposit amount or the redeemed assets, the request fails. Fees accrue per asset in collectedFees, and the fee collector withdraws them with withdrawFee.
Return-leg CCIP fee
The adapter quotes the return-leg fee from the CCIP Router at send time and pays it from its own native gas token balance, which the vault team funds by sending the native gas token to the adapter. If the balance is below the quote, the send reverts and the request becomes a failed message. Collected adapter fees are a separate balance. The adapter never converts them into the native gas token.
Slippage protection
minimumOut is the smallest output the beneficiary accepts. It is measured in output units after the adapter fee: shares for a deposit, asset for a redemption. The adapter checks it after the vault call and before delivery. An output of zero always fails, even when minimumOut is 0. An output below minimumOut fails the request, which becomes a failed message that the user can refund or recover.
To choose a value, call preview on the adapter on the hub chain. Pass the inbound token (the asset for a deposit, the share token for a redemption), the vault, the amount, the return flag, and the source chain selector, which the CCIP Directory lists. Pass the selector for local delivery too. For a non-zero amount, preview reverts when the selector is not configured. preview applies the same routing and adapter fee as a real request and asks the vault's previewDeposit or previewRedeem for the result.
For some requests that would fail, such as one where the adapter fee consumes the amount, preview returns 0 instead of reverting. For others, such as one that names a vault that is not allowlisted, it reverts. The preview reference lists each condition in the order the function checks it.
The vault's exchange rate can move between the send and the execution on the hub chain, a gap that includes the lane's CCIP latency. Subtract a tolerance for that movement from the preview result and use the remainder as minimumOut. Build the payload uses a 1% tolerance.
minimumOut compares the amounts the vault reports. It does not protect against fee-on-transfer or rebasing tokens, or against donation or share inflation effects on the vault. Limitations lists which vaults and tokens the adapter supports.
CCIP 2.0 lanes
A lane's version is the version of the CCIP OnRamp and OffRamp contracts that serve it, and the CCIP Directory shows it.
The adapter implements the CCIP 2.0 receiver interface, IAny2EVMMessageReceiverV2. On a CCIP 2.0 lane, it controls which messages it accepts and how its own return legs are encoded.
Inbound settings
The admin sets two values per source chain:
- CCV policy:
setCCVsConfigsets the required CCVs, the optional CCVs, and how many optional CCVs must attest. An empty configuration means the lane's default verifiers. - Allowed finality:
setInboundFinalitysets which finality modes the adapter accepts. The default,0x00000000, accepts only messages that wait for finality, so CCIP rejects faster-than-finality messages to the adapter.
The CCIP 2.0 OffRamp reads both values through getCCVsAndFinalityConfig before it calls ccipReceive. A message that lacks the required CCV attestations or requests a finality the adapter does not allow fails at the CCIP level, before the adapter runs. The adapter records no failed message in that case. Recovery is described in Failures the adapter can't catch. CCIP 1.6 lanes ignore both inbound settings.
Outbound settings
Return legs and cross-chain refunds are messages that the adapter sends, so the admin sets their format per destination chain:
- Return format:
setEvmReturnLaneFormatsetsGENERIC_EXTRA_ARGS_V3_BASICfor a CCIP 2.0 lane. For a CCIP 1.6 lane, leave the format unset or setLEGACY_EXTRA_ARGS_V2. The adapter then sendsGenericExtraArgsV2with a gas limit of 0 and out-of-order execution allowed. - Requested finality:
setEvmReturnRequestedFinalitysets the finality for each destination and returned token. It accepts a non-zero value only after the destination's format is V3.0x00000000waits for finality and is the default.0x00010000waits for thesafetag.0x00000001to0x0000FFFFwaits for that number of blocks. The token pools on the lane must allow the requested finality.
Do not set the V3 format on a CCIP 1.6 lane. The CCIP 1.6 FeeQuoter accepts only the V1 and V2 extra arguments tags and reverts with InvalidExtraArgsTag for any other. The adapter quotes the fee before every send, so return requests to that chain become failed messages and cross-chain refunds to it revert.
The following table lists the CCIP settings for an EVM source chain connected to the hub chain by CCIP 2.0 lanes.
| Setting | Function | Value on a CCIP 2.0 lane |
|---|---|---|
| Allowed source chain | setChainType | The source chain's selector, chain type EVM |
| Inbound verifiers | setCCVsConfig | Empty lists to use the lane's default verifiers |
| Inbound finality | setInboundFinality | 0x00000000 (default) to accept only messages that wait for finality |
| Return format | setEvmReturnLaneFormat | GENERIC_EXTRA_ARGS_V3_BASIC |
| Return finality | setEvmReturnRequestedFinality | 0x00000000 (default) per returned token, or a value the pools allow |
The factory can set the chain type at deployment, but none of the other four settings, so the admin sets them after deployment. For an example that sets the return format with pnpm ccip:configure, see Set the return format and fund the adapter.
Where to go next
- Failures and recovery: what happens when a request fails and how the tokens come back.
- Limitations: vault and token compatibility, operational limits, and when funds can get stuck.
- Adapter contract: every function, event, and error of
CrossChainERC4626Adapter. - Deploy the vault and set up its share token and Deploy the adapter: the tutorials for the vault team.
- Deposit from a source chain and Redeem from a source chain: the tutorials that send a deposit and a redemption through the adapter as a user on Arbitrum Sepolia.