CCIP v1.6.0 Aptos Receiver API Reference
Receiver
Below is a complete API reference for the ccip_receive function that must be implemented by any Aptos Move module wishing to receive CCIP messages.
ccip_receive
This function is the required entry point for a module to be a valid CCIP receiver. It is not a standard entry function called directly by users. Instead, it is triggered as a secure callback by the CCIP Receiver Dispatcher through the dispatchable_fungible_asset standard.
// As implemented in the ccip_message_receiver example
public fun ccip_receive<T: key>(
_metadata: Object<T>
): Option<u128> acquires YourModuleState
Parameters
| Name | Type | Description |
|---|---|---|
_metadata | Object<T> | A generic object provided by the dispatchable_fungible_asset callback mechanism. It is not directly used, but is a required part of the function signature for the callback to work. |
Returns
| Name | Type | Description |
|---|---|---|
(unnamed) | Option<u128> | The function must return option::none() to conform to the dispatchable asset interface. |
Execution Flow (The Secure Callback Pattern)
- The CCIP OffRamp calls the Receiver Dispatcher.
- The Receiver Dispatcher securely stores the incoming
Any2AptosMessagepayload in the Receiver Registry. - The Receiver Dispatcher then triggers a
derived_supplycall on a dummy fungible asset that was registered for your receiver module. - This
derived_supplycall invokes your module'sccip_receivefunction as the registered callback. - Your
ccip_receiveimplementation must then immediately callreceiver_registry::get_receiver_inputto securely fetch the message payload that was stored for it.
Implementation Requirements
-
Module Registration: Your module must be registered as a valid receiver by calling
receiver_registry::register_receiver. This is typically done once during your module's initialization and requires defining a unique, emptyProofTypestruct. -
Account Type:
- If your module will handle tokens, it must be deployed to a Resource Account.
- If your module is data-only, it can be deployed to a regular user account or a code object account.
-
Security Validations: Your
ccip_receivefunction is responsible for application-level security checks, such as verifying thesource_chain_selectorandsenderfrom the fetched message against an allowlist.
Example
Below is a minimal implementation of a ccip_receive function, based on the ccip_message_receiver module as given in the Aptos Starter Kit:
// In your custom receiver module
// 1. Define a unique, empty proof struct
struct CCIPReceiverProof has drop {}
// 2. Implement the ccip_receive function with the correct signature
public fun ccip_receive<T: key>(
_metadata: Object<T>
): Option<u128> acquires CCIPReceiverState {
// 3. Load state and create a signer for the module's resource account
let state = borrow_global_mut<CCIPReceiverState>(@receiver);
let state_signer = account::create_signer_with_capability(&state.signer_cap);
// 4. Fetch the message payload from the registry using your proof type
let message = receiver_registry::get_receiver_input(
@receiver, CCIPReceiverProof {}
);
// 5. Process the message...
let data = client::get_data(&message);
if (data.length() != 0) {
// Your logic here
}
option::none()
}
Token Handling
When your receiver module is the destination for a token transfer, the CCIP Off-Ramp automatically deposits the assets into your module account's primary fungible store before your ccip_receive function is called.
Using Received Tokens
To access and manage these tokens (e.g., to forward them to another user), your module must use the SignerCapability stored in its resource. This capability allows the module to generate a signer for itself and authorize outgoing transfers.
// Example logic inside ccip_receive to forward tokens
// Assumes `CCIPReceiverState` resource holds the `signer_cap`
public fun forward_tokens(
state_signer: &signer, // The signer generated from the SignerCapability
token_address: address,
amount: u64,
final_recipient: address
) {
let fa_metadata = object::address_to_object<Metadata>(token_address);
// Get the store for the receiver module itself
let receiver_store = primary_fungible_store::ensure_primary_store_exists(
signer::address_of(state_signer),
fa_metadata
);
// Get the store for the final recipient
let final_recipient_store = primary_fungible_store::ensure_primary_store_exists(
final_recipient,
fa_metadata
);
// Authorize the transfer using the module's own signer
fungible_asset::transfer(
state_signer,
receiver_store,
final_recipient_store,
amount
);
}