CCIP Best Practices (SVM)
Before you deploy your cross-chain dApps to mainnet, make sure that your dApps follow the best practices in this document. You are responsible for thoroughly reviewing your code and applying best practices to ensure that your cross-chain dApps are secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, contact the Chainlink Labs Team before deploying your application to mainnet.
Verify destination chain
Before calling the router's ccip_send
instruction, ensure your code verifies that the destination chain is supported by the CCIP Router. Sending messages to unsupported chains will fail and potentially waste transaction fees.
You can programmatically verify destination chain support using Solana PDAs (Program Derived Addresses). Here below is a JavaScript example of how to verify destination chain support:
import { Connection, PublicKey } from "@solana/web3.js"
/**
* Verifies if a destination chain is supported by the CCIP Router
*
* @param {Connection} connection - Solana connection object
* @param {string} routerProgramId - The CCIP Router program ID
* @param {BigInt} destinationChainSelector - Chain selector to verify
* @returns {Promise<boolean>} - Whether the chain is supported
*/
async function isDestinationChainSupported(connection, routerProgramId, destinationChainSelector) {
// Convert chain selector to little-endian buffer (Solana standard)
const chainSelectorBuffer = Buffer.alloc(8)
chainSelectorBuffer.writeBigUInt64LE(BigInt(destinationChainSelector))
// Derive the PDA for this destination chain
// The Router stores chain state in PDAs with seed ["dest_chain_state", chainSelector]
const [destChainPda] = PublicKey.findProgramAddressSync(
[Buffer.from("dest_chain_state"), chainSelectorBuffer],
new PublicKey(routerProgramId)
)
// If the account exists, the chain is supported
const accountInfo = await connection.getAccountInfo(destChainPda)
return accountInfo !== null
}
Verify source chain
When implementing the ccip_receive
method in a program residing on the destination chain, ensure to verify the source chain of the incoming CCIP message. This verification ensures that CCIP messages can only be received from trusted source chains.
Verify sender
When implementing the ccip_receive
instruction in a program residing on the destination chain, it's important to validate the sender of the incoming CCIP message. This check ensures that CCIP messages are received only from trusted sender addresses.
Note: Depending on your use case, this verification might not always be necessary.
Verify authority and allowed offramp
When you implement the ccip_receive
instruction in the program residing on the destination chain, validate that the authority
account is the correct Offramp CPI signer PDA and that allowed_offramp
is the correct PDA owned by the router program. This verification ensures that only the authorized CCIP Offramp program can call the ccip_receive
function.
Example in Rust:
#[derive(Accounts)]
#[instruction(message: Any2SVMMessage)]
pub struct CcipReceive<'info> {
// Offramp CPI signer PDA must be first
#[account(
seeds = [EXTERNAL_EXECUTION_CONFIG_SEED, crate::ID.as_ref()],
bump,
seeds::program = offramp_program.key(),
)]
pub authority: Signer<'info>,
/// CHECK: Offramp program exists only to derive the allowed offramp PDA
pub offramp_program: UncheckedAccount<'info>,
/// CHECK: PDA owned by the router program verifying this is an allowed offramp
#[account(
owner = state.router @ CcipReceiverError::InvalidCaller,
seeds = [
ALLOWED_OFFRAMP,
message.source_chain_selector.to_le_bytes().as_ref(),
offramp_program.key().as_ref()
],
bump,
seeds::program = state.router,
)]
pub allowed_offramp: UncheckedAccount<'info>,
// Your state account containing the router address
#[account(seeds = [STATE_SEED], bump)]
pub state: Account<'info, ProgramState>,
// Additional accounts as needed
// ...
}
Using extra_args
The extra_args
parameter provides chain-specific configuration for cross-chain messaging. It controls execution parameters on the destination chain, including resource allocation and message ordering guarantees.
Parameter Selection
When sending a CCIP message, you must select the appropriate extra_args
structure based on your destination chain:
SVMExtraArgsV1
: For Solana and other SVM-based destinationsEVMExtraArgsV2
: For Ethereum and other EVM-based destinations
For the full parameter specification, refer to the CCIP API Reference.
Setting compute_units
(SVM destinations)
The compute_units
parameter specifies the maximum Solana compute budget (in units) that the CCIP OffRamp can use when executing the ccip_receive()
instruction on the destination program. This parameter directly affects fee calculation since higher compute budgets require more resources.
Best Practices:
- For Program Receivers: Set sufficient compute units for your program logic execution; any unused units are not refunded.
- For Wallet Receivers: When transferring tokens directly to a wallet with no
ccip_receive()
implementation, setcompute_units
to0
since no program execution is needed. - For Compute Unit Estimation: Test your receiver program under varying conditions to determine optimal values. Consider:
- Message size and complexity
- Token transfer operations
- Program execution paths
- Additional accounts referenced
Setting accountIsWritableBitmap
When using the SVMExtraArgsV1
structure, the accountIsWritableBitmap
field specifies which additional accounts in your message should be marked as writable:
- This is a 64-bit bitmap where each bit position corresponds to an account in the
accounts
array. - Set the corresponding bit to
1
to mark an account as writable (bit 0 for the first account, bit 1 for the second, etc.). - Must be provided in little-endian format for Solana compatibility.
Setting tokenReceiver
The tokenReceiver
parameter in SVMExtraArgsV1
specifies which Solana account will receive the tokens:
When Receiving at Wallet Addresses
When sending tokens to an end-user wallet:
- Set
tokenReceiver
to the user's wallet address (base58 encoded) - Do NOT use an Associated Token Account (ATA) - use the wallet address directly
- The CCIP infrastructure will automatically derive the proper ATA on the recipient's behalf
// Example: Setting tokenReceiver to a user's wallet
tokenReceiver: "EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB" // Recipient wallet
When Receiving at Program Addresses
When sending tokens to a Solana program:
- Set
tokenReceiver
to a Program Derived Address (PDA) that the program has authority over - The PDA must be derived using seeds that the program recognizes
- The program must include logic to handle and manage the received tokens
// Example: Setting tokenReceiver to a PDA the program controls
tokenReceiver: "57y3NXjkiAzP5Gw9WuUwzJMJbJQAHH6jUYBQfZdTE5zJ" // PDA with program authority
In Solana's security model, programs cannot directly control tokens unless they have authority over the token account:
- Program Derived Addresses (PDAs) must be derived from the program's ID using specified seeds
- Only the program that created the PDA can sign as that PDA
- Without proper authority, the program cannot transfer, burn, or otherwise manipulate the tokens
For Data-Only Messages
When sending only data (no tokens):
- Set
tokenReceiver
to the default Solana PublicKey (11111111111111111111111111111111
) - This is required even though no tokens are being transferred
Setting allowOutOfOrderExecution
The allowOutOfOrderExecution
parameter controls message ordering guarantees:
true
: Messages may be processed out of sequence relative to other messages from the same senderfalse
: Messages are processed in the exact sequence they were sent
Evaluate the security and reliability of the networks that you use
Although CCIP has been thoroughly reviewed and audited, inherent risks might still exist based on your use case, the blockchain networks where you deploy your programs, and the network conditions on those blockchains.
Review and audit your code
Before securing value with programs that implement CCIP interfaces and routers, ensure that your code is secure and reliable. If you have a unique use case for CCIP that might involve additional cross-chain risk, contact the Chainlink Labs Team before deploying your application to mainnet.
Soak test your dApps
Be aware of the Service Limits and Rate Limits for Supported Networks. Before you provide access to end users or secure value, soak test your cross-chain dApps. Ensure that your dApps can operate within these limits and operate correctly during usage spikes or unfavorable network conditions.
Monitor your dApps
When you build applications that depend on CCIP, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on your dApp, potential delays, and outages.
Create your own monitoring alerts based on deviations from normal activity. This will notify you when potential issues occur so you can respond to them.
Multi-Signature Authorities
Multi-signature authorities enhance security by requiring multiple signatures to authorize transactions.
Threshold configuration
Set an optimal threshold for signers based on the trust level of participants and the required security.
Role-based access control
Assign roles with specific permissions to different signers, limiting access to critical operations to trusted individuals.
Hardware wallet integration
Use hardware wallets for signers to safeguard private keys from online vulnerabilities. Ensure that these devices are secure and regularly updated.
Regular audits and updates
Conduct periodic audits of signer access and authority settings. Update the multisig setup as necessary, especially when personnel changes occur.
Emergency recovery plans
Implement procedures for recovering from lost keys or compromised accounts, such as a predefined recovery multisig or recovery key holders.
Transaction review process
Establish a standard process for reviewing and approving transactions, which can include a waiting period for large transfers to mitigate risks.
Documentation and training
Maintain thorough documentation of multisig operations and provide training for all signers to ensure familiarity with processes and security protocols.