Maple CCIP Receiver
The Maple CCIP Receiver is an upgradeable CCIP Any2EVM receiver that binds to a single Maple pool on the destination chain. It accepts chain transfers of either the pool asset (deposit flow) or the pool share token (redemption flow), validates the sender and pool, and returns the resulting tokens to the origin chain.
Repository: https://github.com/maple-labs/maple-cross-chain-receiver
Architecture
Components
CCIP Router: Delivers inbound messages and sends outbound return messages.
MapleCCIPReceiver: Validates messages, executes pool actions, tracks fees, and handles recovery.
Maple Pool (ERC-4626): Receives deposits and processes redemptions.
WithdrawalManager: Queues and manages redemption requests.
Maple Globals: Grants the
REDEMPTION_EXECUTORrole used for redemption execution and request removal.
High-level flow

Message model and validation
UniversalMessage data
Incoming CCIP messages include a data payload that is decoded into a UniversalMessage:
universalSenderAddress: A bytes32 universal address (EVM addresses are padded, SVM addresses are native 32 bytes).
pool: Must match the single pool configured during initialization.
metaData: Tag for offchain tracking and partner attribution.
Validation gates
Sender validation: The CCIP message sender is normalized into a bytes32 address and must match
universalSenderAddress.Pool binding:
poolin the message must match the receiver's configured pool.Single token: Only one token transfer per message is supported.
Token routing:
Inbound token == pool share token -> redemption flow.
Inbound token == pool asset token -> deposit flow.
Any other token -> revert.
Chain routing
The contract stores a ChainType for each CCIP chain selector. The chain type determines how outbound messages are formatted:
EVM:
receiveris an EVM address; extra args useGenericExtraArgsV2.SVM:
receiveris the default SVM receiver; extra args useSVMExtraArgsV1withtokenReceiverset to the universal sender address.
Core flows
Deposit flow (asset token inbound)
CCIP Router calls
ccipReceivewith the asset token and encodedUniversalMessage.Receiver validates sender, pool, and token, and checks that deposits are enabled.
Deposit fee (per token) is applied and stored as collected fees.
Receiver estimates CCIP fee for returning shares and verifies the contract has enough native tokens.
Receiver deposits assets into the pool via
deposit()and receives shares.Receiver sends the shares back to the origin chain via CCIP and emits
CrossChainDeposit.
Redemption flow (share token inbound)
CCIP Router calls
ccipReceivewith pool shares and encodedUniversalMessage.Receiver validates sender, pool, and token, and checks that redemptions are enabled.
Redemption fee is computed in shares using
convertToExitShares.Receiver calls
requestRedeem()and records the request (sender, chain selector, shares).Receiver emits
CrossChainRedeemwith the request ID.A
REDEMPTION_EXECUTORlater callsexecuteRedeemList()to redeem shares into assets and return them via CCIP.
Redemption request removal (share return)
removeRedemptionRequests() allows the REDEMPTION_EXECUTOR to remove queued requests and return shares to users if a redemption must be canceled. The caller funds CCIP fees for the return transfer, and any excess ETH is refunded.
Message routing decision

CCIP send details
Outbound transfers are sent with IRouterClient.getFee() and IRouterClient.ccipSend() using a Client.EVM2AnyMessage:
feeToken is
address(0), so fees are paid in the native token of the pool chain.EVM destinations use
GenericExtraArgsV2withallowOutOfOrderExecutionenabled.SVM destinations use
SVMExtraArgsV1withtokenReceiverset to the universal sender address.
Failure handling and recovery
The receiver uses a defensive pattern to store failed messages for recovery:
On failure, the full
Any2EVMMessageis stored and markedFAILEDwhich contains the sender of the request which can be used for recovery.retryFailedMessage()retries processing using the stored message.recoverFailedTokensBackToSender()attempts a retry first, then sends tokens back to the original sender if retry fails.adminRescueTokens()is a last-resort admin path that transfers tokens directly to a beneficiary.

Roles and permissions
DEFAULT_ADMIN_ROLE
Configure chain types
Set deposit and redemption fees
Enable or disable deposits/redemptions
Withdraw tokens or native funds
Rescue tokens from failed messages
Authorize upgrades (UUPS)
REDEMPTION_EXECUTOR (assigned in Maple Globals)
Execute queued redemptions
Remove redemption requests and return shares
Public
Retry and recover failed messages (fees may be required)
Fee model
Deposit fees: Fixed per-token amount deducted from inbound assets before deposit.
Redemption fees: Fixed per-token amount charged in assets, converted to share units and deducted from inbound shares.
Collected fees accumulate in
s_tokenFeesBalancesand are withdrawable by an admin.
Operational considerations
The contract must maintain a native token balance to pay CCIP fees for outbound transfers.
Chain selectors must be configured with a
ChainTypebefore messages can be sent.Redemptions require a configured
REDEMPTION_EXECUTORin Maple Globals.Use CCIP message IDs and emitted events (
MessageReceived,MessageSent,CrossChainDeposit,CrossChainRedeem) for observability.For integrators: validate deposits and redemptions so the amount is greater than the configured fee for that token. Deposit fees and redemption fees are configured on the receiver contract, so amounts below the fee will revert.
Last updated
Was this helpful?