Cross-Chain Vault Adapter
Uses CCIP View on GitHub

Limitations

Use this page to check whether your vault and tokens fit the Cross-Chain Vault Adapter as shipped, and to find the conditions that turn requests into failed messages or leave tokens stuck. 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. The vault stays the product and CCIP stays the bridge, so the limits below sit in the adapter, the path that users on other chains take to reach the vault, and in its CCIP settings. For a request that succeeds, see How it works. For how tokens come back after a failure, see Failures and recovery.

Vault and token compatibility

The adapter works with a standard synchronous, single-asset ERC-4626 vault. Which tokens need CCIP support depends on the flow you want to offer; see Requirements. If your vault's asset is not CCIP-enabled, users can still send CCIP-enabled shares from another chain and receive the redeemed asset on the hub chain, where the asset stays local. Deposits from another chain and redemptions that return the asset to the source chain require a CCIP-enabled asset.

The cases below need a fork of the adapter or are not supported.

CaseWhat happensWhat to do
Asynchronous (request-based) vaultThe adapter calls deposit and redeem and needs the output in the same transaction. A vault that queues requests does not return shares or assets in that call, so the message fails.Fork the adapter.
Multi-asset vaultThe adapter routes by comparing the bridged token with the vault's single asset(). Any other token reverts InvalidTargetToken, and the message fails.Fork the adapter.
Target that is not ERC-4626The adapter calls asset(), deposit, and redeem on the payload's target. If the target does not implement them, the call reverts and the message fails.Fork the adapter.
Vault that restricts depositors, or applies per-account limits or cooldownsThe adapter is the depositor, the initial share owner, and the redeemer, so the vault sees one account for every user. Per-account caps apply to all users combined. Allowlisting the adapter in the vault opens the vault to any sender on a configured source chain.Fork the adapter and add your own sender or beneficiary checks.
Fee-on-transfer, rebasing, or other non-standard ERC-20 asset or share tokenThe adapter assumes a transfer moves exactly the stated amount. Adapter fee accounting and minimumOut checks use nominal amounts, so they drift from real balances.Use standard ERC-20 tokens only.
Share token that is not a CCIP cross-chain token (CCT)Deposits that ask to return shares to the source chain fail, because the return leg cannot bridge shares, and the whole message becomes a failed message. Users cannot move shares to another chain, so redeeming from another chain is not possible. Deposits with local delivery still work, because they need only the asset to be CCIP-enabled.Register the share token as a CCT on both chains, typically Lock & Release on the hub chain and Burn & Mint on the other.
Hub chain without the Shanghai upgradeThe audited bytecode uses the PUSH0 opcode, which only Shanghai-or-later chains support.Deploy only on chains that support PUSH0. Recompiling for an older EVM version changes the audited bytecode.

To register the share token as a CCT, follow the Lock & Mint tutorial. Only the vault may mint shares, which is why the hub-chain side typically uses Lock & Release.

Fees and accounting

The adapter fee is set with setAssetFee or setAssetFees and stored in assetFees. For how the adapter charges it, including why a fee change affects messages in flight and why a fee at or above the amount fails the message, see Fees. The accounting limits below come on top of that.

  • Vaults that share an asset share fee state. Redeem-return fees are keyed by the asset, so every vault on one adapter with that asset uses the same redeem-return fee for a destination. collectedFees is keyed by the asset too, so their fees pool in one balance. Deposit-return fees are keyed by each vault's share token and stay separate.
  • Only accounted fees can be withdrawn. withdrawFee moves at most collectedFees[asset]. The adapter has no general ERC-20 sweep.
  • Adapter fees do not pay CCIP fees. The adapter pays each return leg's CCIP fee from its own native gas token balance. Collected adapter fees stay in the asset. Fund the adapter with the native gas token separately. If the balance is below the router's fee, the return reverts InsufficientNativeBalance and the whole message fails.
  • minimumOut checks nominal amounts. It compares against the amount the vault returns from deposit or redeem, after the adapter fee, not against balance changes. See Slippage protection.

Operational limits

LimitWhat it means
No pauseTurning processing off does not stop CCIP delivery. See What counts as a failure.
No retryThe adapter never reprocesses a failed message. Re-enabling a vault, chain, or processing switch does not help messages that already failed.
No automatic refundSomeone must call refundFailedMessage and pay its CCIP fee, or the local refund address must call recoverFailedMessageLocally.
No sender allowlistAny sender on a configured source chain can deposit into or redeem from allowlisted vaults. The adapter checks only the source chain and the target.
One token, one vaultA message must carry exactly one token, or it fails with InvalidTokenCount. One adapter can allowlist several vaults, and the payload's target picks one per message.
Global processing switchesdepositsEnabled and redeemsEnabled apply to every vault on the adapter.
Two destinations onlyOutput goes to a beneficiary on the hub chain or back to the chain the message came from. The adapter cannot route output to a third chain.
Native gas token onlyThe adapter pays CCIP fees for return legs in the hub chain's native gas token, never in LINK. Cross-chain refunds are paid by the caller.
Token-only returnsReturn legs carry no data and a gas limit of 0, so a beneficiary contract on the source chain receives tokens without a callback.

Configuration risks

The first four risks concern CCIP 2.0 lane settings. CCIP 2.0 lanes explains each setting.

  • V3 return format on a CCIP 1.6 lane. The router rejects every return leg and cross-chain refund to that chain. Set GENERIC_EXTRA_ARGS_V3_BASIC with setEvmReturnLaneFormat only for CCIP 2.0 lanes; correcting the format unblocks later refunds.
  • Return finality the token pool does not allow. Return legs and refunds of that token revert. Check the pool's allowed finality before you call setEvmReturnRequestedFinality.
  • Inbound finality or Cross-Chain Verifier (CCV) settings that do not match senders. CCIP rejects the message before the adapter runs, so the adapter's recovery functions do not apply. Fix setInboundFinality or setCCVsConfig, then use manual execution.
  • CCIP 2.0 settings left at their defaults. The factory applies none of these settings and does not fund the adapter. Until the admin sets them, return legs use GenericExtraArgsV2, return finality cannot be requested, the adapter rejects faster-than-finality messages, and an unfunded adapter fails return legs with InsufficientNativeBalance. See Deploy the adapter.
  • The CCIP Router is fixed at deployment. ROUTER is immutable, so moving to a different CCIP Router means deploying and configuring a new adapter. Failed messages on the old adapter stay there until someone refunds or recovers them from it.
  • Refunds use the chain family recorded at failure time. If the chain type was misconfigured when a message failed, a later setChainType fix does not change how that message is refunded, so use local recovery. Setting a chain to NONE after a failure does not block its refund.
  • recoverNative takes the whole native gas token balance. recoverNative has no amount parameter. Afterward, every return leg fails with InsufficientNativeBalance until the adapter is funded again.
  • Role changes take effect in one step. The adapter uses OpenZeppelin AccessControlEnumerable, so grantRole, revokeRole, and renounceRole need no acceptance step. If the last DEFAULT_ADMIN_ROLE holder renounces, no one can change admin-controlled configuration or grant roles again. Fee setters and collectors keep only their own functions. Grant the new admin before you revoke the old one.
  • The factory and the constructor grant roles differently. Deployed directly, the constructor gives defaultAdmin all three roles, in addition to granting feeSetter and feeCollector their own. Deployed through the factory, each role goes only to its configured address, and the factory renounces its own roles. With separate addresses, a factory-deployed admin cannot set or withdraw adapter fees until it grants itself those roles. See Role handoff.
  • Changes apply to messages in flight. Disabling a chain, a vault, or a processing switch fails every message that arrives afterward, including messages sent before the change. A fee change likewise applies to messages sent before it.

When funds can get stuck

In these scenarios, tokens are lost or stranded, or one of the two recovery paths is gone. The payload checklist in What a user sends prevents the first two.

ScenarioWhyPrevention
No local refund address, and a cross-chain refund is not possibleLocal recovery needs localRefundAddress in the payload when the message is sent. It cannot be added later, and a payload that is not exactly 128 bytes leaves it zero. A cross-chain refund fails if, for example, the router no longer supports the return lane or the token has no pool for it.Always set localRefundAddress to an address the user controls on the hub chain.
Malformed payloadA hand-built payload can make the message fail inside CCIP with no recovery path, as described in Failures with no recovery path.Build the payload with a standard ABI encoder, such as Solidity abi.encode, ethers, viem, or cast.
Refund recipient is a contract that cannot use the returned tokensA cross-chain refund always goes to the original message.sender on the source chain, not to the beneficiary or the local refund address.Send from an account or contract that can move refunded tokens, or set localRefundAddress.
Tokens sent to the adapter outside a normal requestAn ERC-20 transfer straight to the adapter, or a CCIP message with empty data and a gas limit of 0, delivers tokens without calling ccipReceive. The adapter records nothing, withdrawFee withdraws only accounted fees, and there is no ERC-20 sweep.Send tokens only in CCIP messages with a 128-byte payload and a non-zero gas limit. recoverNative recovers the native gas token.
Admin role renounced while configuration is brokenNo one can fix settings such as a wrong return lane format, so cross-chain refunds that depend on them stay blocked. Only local recovery remains, and only for messages that set a local refund address.Verify the configuration with test messages before any admin change, and keep at least one DEFAULT_ADMIN_ROLE holder.

Customizing the adapter

If your vault does not fit, fork the adapter rather than modifying the vault. See Use it as-is or customize it for when to fork.

  • There are no hooks to override. processMessage and _processTarget are not virtual. Edit them in your copy of CrossChainERC4626Adapter.sol.
  • Keep the safety checks. Keep the router check in ccipReceive, the self-call that isolates processMessage in a try/catch, failed-message storage with both recovery paths, and the beneficiary address checks. The failure handler, the catch path in ccipReceive, must never revert on any input, or tokens can be stranded as in the malformed payload case above.
  • Keep the compiler settings. The audited build uses solc 0.8.24, via_ir, optimizer_runs = 1, and evm_version = cancun, as pinned in foundry.toml.
  • A fork is not the audited code. The audit covers the contracts in src/ccip as published. Any change to the source or the compiler settings changes the bytecode, so get your fork its own security review before you deploy it.

Versions covered

This page describes the following versions. The contracts in src/ccip are audited.

Component
Version
CrossChainERC4626Adapter1.0.0 (typeAndVersion is CrossChainERC4626Adapter 1.0.0)
CrossChainERC4626AdapterFactory1.0.0 (typeAndVersion is CrossChainERC4626AdapterFactory 1.0.0)
@chainlink/contracts-ccip2.0.0
@chainlink/contracts1.5.0 (ITypeAndVersion only)
OpenZeppelin Contracts5.0.2 (AccessControlEnumerable, IERC4626, ReentrancyGuard) and 4.8.3 (IERC20, SafeERC20)
Compilersolc 0.8.24, via_ir, optimizer_runs = 1, evm_version = cancun (requires PUSH0)
CCIP lanesCCIP 1.6 lanes (GenericExtraArgsV2 return legs) and CCIP 2.0 lanes (GenericExtraArgsV3 return legs)
Hub chainsEVM chains with the Shanghai upgrade or later

For exact signatures, events, and errors, continue to Adapter contract and Factory contract.

Get the latest Chainlink content straight to your inbox.