CCIP v1.6.0 SVM Router API Reference
Router
Below is a complete API reference for the CCIP Router program instructions.
Message Sending
ccip_send
This instruction is the entry point for sending a cross-chain message from an SVM-based blockchain to a specified destination blockchain.
fn ccip_send(
ctx: Context<CcipSend>,
dest_chain_selector: u64,
message: SVM2AnyMessage,
token_indexes: Vec<u8>
) -> Result<[u8; 32]>;
Parameters
Name | Type | Description |
---|---|---|
dest_chain_selector | u64 | The unique CCIP blockchain identifier of the destination blockchain. |
message | SVM2AnyMessage | Read Messages for more details. |
token_indexes | Vec<u8> | Index offsets slicing the remaining accounts so each token's subset can be grouped (see Context). |
Context (Accounts)
These are the required accounts passed alongside the instructions. For relevant PDAs, the instructions on how to derive seeds are given below.
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
dest_chain_state | Account<DestChain> | Yes | Per-destination blockchain PDA for sequence_number , chain config, etc. Derivation: ["dest_chain_state", dest_chain_selector] under the ccip_router program. |
nonce | Account<Nonce> | Yes | Current nonce PDA for (authority, dest_chain_selector) . Derivation: ["nonce", dest_chain_selector, authority_pubkey] under the ccip_router program. |
authority | Signer<'info> | Yes | The user/wallet paying for the ccip_send transaction. Also, it must match the seeds in nonce . |
system_program | Program<'info, System> | No | Standard System Program. |
fee_token_program | Interface<'info, TokenInterface> | No | The token program used for fee payment (e.g., SPL Token). If paying with native SOL, this is just the SystemProgramID . |
fee_token_mint | InterfaceAccount<'info, Mint> | No | Fee token mint. If paying in SPL, pass your chosen token mint. If paying in native SOL, a special "zero" mint (Pubkey::default() ) or "native mint" (native_mint::ID ) is used. |
fee_token_user_associated_account | UncheckedAccount<'info> | Yes | If fees are paid in SPL, this is the user's ATA. Derivation: It is derived via the Associated Token Program seeds: [authority_pubkey, fee_token_program.key(), fee_token_mint.key() ] under the relevant Token Program (Make sure you use the correct token program ID—Token-2022 vs.SPL Token). If paying with native SOL, pass the zero address (Pubkey::default()) and do not mark it writable. |
fee_token_receiver | InterfaceAccount<'info, TokenAccount> | Yes | The ATA where all the fees are collected. Derivation: from [fee_billing_signer,fee_token_program.key(),fee_token_mint.key()] . |
fee_billing_signer | UncheckedAccount<'info> | No | PDA is the router's billing authority for transferring fees (native SOL or SPL tokens). from fee_token_user_associated_account to fee_token_receiver. Derivation: ["fee_billing_signer"] under the ccip_router program. |
fee_quoter | UncheckedAccount<'info> | No | The Fee Quoter program ID. |
fee_quoter_config | UncheckedAccount<'info> | No | The global Fee Quoter config PDA. Derivation: ["config"] under the fee_quoter program. |
fee_quoter_dest_chain | UncheckedAccount<'info> | No | Per-destination blockchain PDA in the Fee Quoter program. It stores chain-specific configuration (gas price data, limits, etc.) for SVM2Any messages. Derivation: ["dest_chain",dest_chain_selector] under the fee_quoter program. |
fee_quoter_billing_token_config | UncheckedAccount<'info> | No | A per-fee-token PDA in the Fee Quoter program stores token-specific parameters (price data, billing premiums, etc.) used to calculate fees. Derivation: If the message pays fees in native SOL, the seed uses the native_mint::ID ; otherwise, it uses the SPL token's mint public key. ["fee_billing_token_config", seed] under the fee_quoter program. |
fee_quoter_link_token_config | UncheckedAccount<'info> | No | PDA containing the Fee Quoter's LINK token billing configuration (LINK price data, premium multipliers, etc.). The fee token amount is converted into "juels" using LINK's valuation from this account during fee calculation. Derivation: ["fee_billing_token_config", link_token_mint] under the fee_quoter program. |
rmn_remote | UncheckedAccount<'info> | No | The RMN program ID used to verify if a given chain is cursed. |
rmn_remote_curses | UncheckedAccount<'info> | No | PDA containing list of curses chain selectors and global curses. Derivation: ["curses"] under the rmn_remote program. |
rmn_remote_config | UncheckedAccount<'info> | No | RMN config PDA, containing configuration that control how curse verification works. Derivation: ["config"] under the rmn_remote program. |
token_pools_signer | UncheckedAccount<'info> | Yes | PDA with the authority to CPI into token pool logic (mint/burn, lock/release). Derivation: ["external_token_pools_signer"] under the ccip_router program. |
remaining_accounts | &[AccountInfo] | Yes | You pass extra accounts for each token you wish to transfer (does not include fee tokens). Typically includes the sender ATA, the token pool config, token admin registry PDAs… etc. |
How remaining_accounts
and token_indexes
Work
When you call the Router's ccip_send
instruction, you pass:
- A list of
token_amounts
you want to transfer cross-chain. - A slice of
remaining_accounts
containing the per-token PDAs (e.g., user token ATA, pool config, token admin registry PDA, etc.). - A
token_indexes
array tells the Router where inremaining_accounts
each token's sub-slice begins.
Reason for remaining_accounts
On Solana, each Anchor instruction has a fixed set of named accounts. However, CCIP must handle any number of tokens, each requiring many accounts. Rather than define a massive static context, the Router uses Anchor's dynamic ctx.remaining_accounts
: All token-specific accounts are packed into one slice.
Reason for token_indexes
The Router must figure out which segment of that slice corresponds to token #0, token #1, etc. So you provide an integer offset in token_indexes[i]
indicating where the i
th token's accounts begin inside remaining_accounts
.
The Router:
- Slices out
[start..end)
for thei
th token's accounts. The subslice is from start up to but not including end. This is how you indicate that the token i's accounts occupy positions start, start+1, …, end-1. - Validates each account.
- Calls the appropriate token pool to the lock-or-burn operation on them.
Structure of Each Token's Sub-slice
Inside each token's sub-slice, the Router expects:
- The user's token account (ATA).
- The token's chain PDAs.
- Lookup table PDAs, token admin registry, pool program, pool config, pool signer, token program, the mint, etc.
In total, it is typically 12 or more accounts per token. Repeat that "per-token chunk" of ~12 accounts for each token if you have multiple tokens. These accounts are extracted in this order:
Index | Account | Description |
---|---|---|
0 | user_token_account | ATA for (authority, mint, token_program) . |
1 | token_billing_config | Per-destination blockchain-specific fee overrides for a given token. Note: In most cases, tokens do not have a custom billing fee structure. In these cases, CCIP uses the fallback default fee configuration. PDA ["per_chain_per_token_config", dest_chain_selector, mint] under the fee_quoter program. |
2 | pool_chain_config | PDA ["ccip_tokenpool_chainconfig", dest_chain_selector, mint] under fee_quoter program. |
3 | lookup_table | Address Lookup Table that the token's admin registry claims. Must match the Token Admin Registry's lookup_table field. |
4 | token_admin_registry | PDA ["token_admin_registry", mint] under the ccip_router program. |
5 | pool_program | The Token Pool program ID (for CPI calls). |
6 | pool_config | PDA [ "ccip_tokenpool_config", mint ] under the pool_program. |
7 | pool_token_account | ATA for (pool_signer , mint , token_program ). |
8 | pool_signer | PDA [ "ccip_tokenpool_signer", mint ] under the pool_program. |
9 | token_program | Token program ID (e.g. spl_token or 2022). Also, it must match the mint's owner . |
10 | mint | The SPL Mint (public key) for this token. |
11 | fee_token_config | A token billing configuration account under the Fee Quoter program. It contains settings such as whether there is a specific pricing for the token, its pricing in USD, and any premium multipliers. Note: In most cases, tokens do not have a custom billing fee structure. In these cases, CCIP uses the fallback default fee configuration. PDA ["fee_billing_token_config", mint] under the fee_quoter program. |
12 | … | Additional accounts are passed if required by the token pool. |
Examples
One Token Transfer
Suppose you want to send one token (myMint
) cross-chain:
token_amounts
: length = 1, e.g.[{ token: myMint_pubkey, amount: 1000000 }]
.token_indexes
:[1]
. Meaning:- The 0th token's remaining_accounts sub-slice will be
[token_indexes[0] .. endOfArray)
, i.e.[1..]
. - The user's Associated Token Account (ATA) for that token is found at
remaining_accounts[0]
.
- The 0th token's remaining_accounts sub-slice will be
- Your
remaining_accounts
must have:- 1 user's ATA (the sender ATA for the single token).
- 12 pool-related accounts (pool config, chain config, token program, etc.). That is 13 total.
Two Token Transfers
Suppose you want to send two tokens (mintA
and mintB
) cross-chain:
token_amounts
: length = 2, e.g.[{ token: mintA_pubkey, amount: 1000000 },{ token: mintB_pubkey, amount: 2000000 } ]
.token_indexes
must be length=2 since there are two tokens, and token_indexes = [2, 14]. Explanation:- After we skip the user ATAs at indices [0..2), we want the next 12 accounts for the first token to lie in
[2..14)
, and then the next 12 for the second token to lie in[14..end)
. - The Router program will use token_indexes:
- For the first token: The sub slice is [2..14).
- For the second token: The sub slice is [14…endOfArray).
- After we skip the user ATAs at indices [0..2), we want the next 12 accounts for the first token to lie in
- Your
remaining_accounts
must have:- 2 user ATAs (one for
mintA
, one formintB
). - 12 pool accounts for
mintA
. - 12 pool accounts for
mintB
.
- 2 user ATAs (one for
Thus 2 + 12 + 12 = 26
accounts in remaining_accounts
.
get_fee
Queries the Router for the fee required to send a cross-chain message. This is a permissionless call that calculates fees without verifying curse status to avoid RMN CPI overhead.
fn get_fee(
ctx: Context<GetFee>,
dest_chain_selector: u64,
message: SVM2AnyMessage,
) -> Result<GetFeeResult>;
Parameters
Name | Type | Description |
---|---|---|
dest_chain_selector | u64 | The unique CCIP blockchain identifier of the destination blockchain. |
message | SVM2AnyMessage | The message for which to calculate fees. Read Messages for more details. |
Return Value
Type | Description |
---|---|
GetFeeResult | Contains fee amount, fee in juels (LINK), and the fee token address. |
struct GetFeeResult {
pub amount: u64, // Fee amount in the specified fee token
pub juels: u128, // Fee value converted to LINK juels
pub token: Pubkey, // The fee token address
}
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
dest_chain_state | Account<DestChain> | No | Per-destination blockchain PDA for retrieving lane version. Derivation: ["dest_chain_state", dest_chain_selector] under the ccip_router program. |
fee_quoter | UncheckedAccount<'info> | No | The Fee Quoter program ID. |
fee_quoter_config | UncheckedAccount<'info> | No | The global Fee Quoter config PDA. Derivation: ["config"] under the fee_quoter program. |
fee_quoter_dest_chain | UncheckedAccount<'info> | No | Per-destination blockchain PDA in the Fee Quoter program. Derivation: ["dest_chain", dest_chain_selector] under the fee_quoter program. |
fee_quoter_billing_token_config | UncheckedAccount<'info> | No | Fee token billing configuration PDA. Derivation: ["fee_billing_token_config", fee_token_mint] under the fee_quoter program. Uses native_mint::ID if paying with native SOL. |
fee_quoter_link_token_config | UncheckedAccount<'info> | No | LINK token billing configuration PDA for fee conversion. Derivation: ["fee_billing_token_config", link_token_mint] under the fee_quoter program. |
remaining_accounts | &[AccountInfo] | No | Per-token billing configurations. See Remaining Accounts Structure below. |
Remaining Accounts Structure for get_fee
The remaining_accounts
must be provided in this specific order for each token being transferred:
- Billing Token Config: Token-specific billing configuration under the Fee Quoter program
- Derivation:
["fee_billing_token_config", token_mint]
under thefee_quoter
program
- Derivation:
- Per-Chain Per-Token Config: Chain and token specific fee overrides under the Fee Quoter program
- Derivation:
["per_chain_per_token_config", dest_chain_selector, token_mint]
under thefee_quoter
program
- Derivation:
Example for 2 tokens:
remaining_accounts = [
billing_config_token_A, // Token A billing config
per_chain_config_token_A, // Token A per-chain config
billing_config_token_B, // Token B billing config
per_chain_config_token_B, // Token B per-chain config
]
Usage Notes
- Permissionless: Anyone can call this instruction to query fees
- No Curse Check: Does not verify RMN curse status for performance
- Fee Calculation: Returns both the fee amount in the specified token and the equivalent value in LINK juels
- Token Support: Supports both native SOL and SPL token fee payments
Token Administration
These instructions manage token administrator roles and token registration with CCIP.
owner_propose_administrator
Proposes a token administrator for a given SPL token. This is used for self-service registration when you control the token's mint authority.
fn owner_propose_administrator(
ctx: Context<RegisterTokenAdminRegistryByOwner>,
token_admin_registry_admin: Pubkey,
) -> Result<()>;
Parameters
Name | Type | Description |
---|---|---|
token_admin_registry_admin | Pubkey | The public key of the proposed token administrator. |
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
token_admin_registry | Account<TokenAdminRegistry> | Yes | Token admin registry PDA to initialize. Derivation: ["token_admin_registry", mint] under the ccip_router program. |
mint | InterfaceAccount<'info, Mint> | No | The SPL token mint for which to propose an administrator. |
authority | Signer<'info> | Yes | Must be the mint authority of the SPL token. |
system_program | Program<'info, System> | No | Standard System Program. |
Authorization
- Caller: Must be the SPL token's
mint_authority
- Registry State: TokenAdminRegistry PDA must not already exist for this mint
owner_override_pending_administrator
Overrides the pending administrator before they accept the role. Can only be called by the token's mint authority.
fn owner_override_pending_administrator(
ctx: Context<OverridePendingTokenAdminRegistryByOwner>,
token_admin_registry_admin: Pubkey,
) -> Result<()>;
Parameters
Name | Type | Description |
---|---|---|
token_admin_registry_admin | Pubkey | The public key of the new proposed administrator. |
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
token_admin_registry | Account<TokenAdminRegistry> | Yes | Existing token admin registry PDA. Derivation: ["token_admin_registry", mint] under the ccip_router program. |
mint | InterfaceAccount<'info, Mint> | No | The SPL token mint. |
authority | Signer<'info> | Yes | Must be the mint authority of the SPL token. |
system_program | Program<'info, System> | No | Standard System Program. |
Authorization
- Caller: Must be the SPL token's
mint_authority
- Registry State: TokenAdminRegistry PDA must exist but have no accepted administrator yet
ccip_admin_propose_administrator
Proposes a token administrator via CCIP governance. Used when the caller cannot access the mint authority.
fn ccip_admin_propose_administrator(
ctx: Context<RegisterTokenAdminRegistryByCCIPAdmin>,
token_admin_registry_admin: Pubkey,
) -> Result<()>;
Parameters
Name | Type | Description |
---|---|---|
token_admin_registry_admin | Pubkey | The public key of the proposed token administrator. |
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
token_admin_registry | Account<TokenAdminRegistry> | Yes | Token admin registry PDA to initialize. Derivation: ["token_admin_registry", mint] under the ccip_router program. |
mint | InterfaceAccount<'info, Mint> | No | The SPL token mint for which to propose an administrator. |
authority | Signer<'info> | Yes | Must be the Router program's upgrade authority. |
system_program | Program<'info, System> | No | Standard System Program. |
Authorization
- Caller: Must be the Router program's upgrade authority
- Registry State: TokenAdminRegistry PDA must not already exist for this mint
accept_admin_role_token_admin_registry
Accepts the administrator role for a token. Must be called by the pending administrator to finalize registration.
fn accept_admin_role_token_admin_registry(
ctx: Context<AcceptAdminRoleTokenAdminRegistry>,
) -> Result<()>;
Parameters
This instruction takes no additional parameters.
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
token_admin_registry | Account<TokenAdminRegistry> | Yes | Existing token admin registry PDA. Derivation: ["token_admin_registry", mint] under the ccip_router program. |
mint | InterfaceAccount<'info, Mint> | No | The SPL token mint. |
authority | Signer<'info> | Yes | Must be the pending administrator listed in the token admin registry. |
Authorization
- Caller: Must match the
pending_administrator
field in the TokenAdminRegistry PDA - Registry State: TokenAdminRegistry PDA must exist with a pending administrator
transfer_admin_role_token_admin_registry
Transfers the administrator role to a new administrator. This is a two-step process requiring the new admin to accept.
fn transfer_admin_role_token_admin_registry(
ctx: Context<ModifyTokenAdminRegistry>,
new_admin: Pubkey,
) -> Result<()>;
Parameters
Name | Type | Description |
---|---|---|
new_admin | Pubkey | The public key of the new administrator. |
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
token_admin_registry | Account<TokenAdminRegistry> | Yes | Existing token admin registry PDA. Derivation: ["token_admin_registry", mint] under the ccip_router program. |
mint | InterfaceAccount<'info, Mint> | No | The SPL token mint. |
authority | Signer<'info> | Yes | Must be the current administrator of the token admin registry. |
Authorization
- Caller: Must be the current
administrator
listed in the TokenAdminRegistry PDA - Registry State: TokenAdminRegistry PDA must exist with an active administrator
set_pool
Sets the Address Lookup Table that defines the token pool for cross-chain transfers. This enables or disables a token for CCIP.
fn set_pool(
ctx: Context<SetPoolTokenAdminRegistry>,
writable_indexes: Vec<u8>,
) -> Result<()>;
Parameters
Name | Type | Description |
---|---|---|
writable_indexes | Vec<u8> | Bitmap of indexes in the lookup table that should be marked as writable during transactions. |
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
token_admin_registry | Account<TokenAdminRegistry> | Yes | Existing token admin registry PDA. Derivation: ["token_admin_registry", mint] under the ccip_router program. |
mint | InterfaceAccount<'info, Mint> | No | The SPL token mint. |
pool_lookuptable | UncheckedAccount<'info> | No | Address Lookup Table containing the token pool accounts. Pass zero address to delist token from CCIP. |
authority | Signer<'info> | Yes | Must be the current administrator of the token admin registry. |
Authorization
- Caller: Must be the current
administrator
listed in the TokenAdminRegistry PDA - Pool Validation: If not zero address, the lookup table must contain at least the minimum required accounts
Pool Status
- Enable Token: Set
pool_lookuptable
to a valid Address Lookup Table with required pool accounts - Disable Token: Set
pool_lookuptable
to zero address (Pubkey::default()
) to delist from CCIP
set_pool_supports_auto_derivation
Configures whether a token pool supports automatic account derivation for cross-chain transfers.
fn set_pool_supports_auto_derivation(
ctx: Context<EditPoolTokenAdminRegistry>,
mint: Pubkey,
supports_auto_derivation: bool,
) -> Result<()>;
Parameters
Name | Type | Description |
---|---|---|
mint | Pubkey | The SPL token mint to configure. |
supports_auto_derivation | bool | Whether the pool supports dynamic account derivation. |
Context (Accounts)
Field | Type | Writable? | Description |
---|---|---|---|
config | Account<Config> | No | Router config PDA. Derivation: ["config"] under the ccip_router program. |
token_admin_registry | Account<TokenAdminRegistry> | Yes | Existing token admin registry PDA. Derivation: ["token_admin_registry", mint] under the ccip_router program. |
authority | Signer<'info> | Yes | Must be the current administrator of the token admin registry. |
Authorization
- Caller: Must be the current
administrator
listed in the TokenAdminRegistry PDA
Auto-Derivation Impact
- Enabled (
true
): The Router validates only static accounts in the Address Lookup Table, allowing the pool to handle additional dynamically-derived accounts - Disabled (
false
): The Router validates all accounts against the static Address Lookup Table, expecting a complete and fixed account set