# Send & Receive Interchain Transfers (https://docs.axelar.dev/its/integrations/solana/interchain-transfers)



This guide walks through a Solana program that sends tokens to other chains through ITS, and receives tokens with data from them. For setup, see the [Introduction](https://docs.axelar.dev/llms.mdx/its/integrations/solana/intro/content.md#setup).

## Receive tokens with data [#receive-tokens-with-data]

A transfer to Solana can carry data. ITS then mints or unlocks the tokens into an account your program controls, and calls your program's `execute_with_interchain_token` instruction with the data, in the same transaction. A transfer without data only delivers the tokens: ITS doesn't call your program.

### Step 1: Define your accounts struct [#step-1-define-your-accounts-struct]

Use the `executable_with_interchain_token_accounts!` macro to generate the `AxelarExecuteWithInterchainTokenAccounts` struct, then embed it in your own accounts struct. The argument you pass to the macro is the name of your outer struct:

```rust
use anchor_lang::prelude::*;
use anchor_spl::token_interface::TokenAccount;
use solana_axelar_its::{executable::*, executable_with_interchain_token_accounts};

// Generate AxelarExecuteWithInterchainTokenAccounts and wire it to your struct
executable_with_interchain_token_accounts!(MyItsExecute);

#[derive(Accounts)]
pub struct MyItsExecute<'info> {
    // ITS accounts (generated by the macro)
    pub its_executable: AxelarExecuteWithInterchainTokenAccounts<'info>,

    // Your program's accounts, listed by the sender in `data`
    #[account(seeds = [MyState::SEED_PREFIX], bump = my_state.bump)]
    pub my_state: Account<'info, MyState>,

    #[account(mut)]
    pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
}
```

The macro generates `AxelarExecuteWithInterchainTokenAccounts` with:

* `token_program`: the SPL Token or Token-2022 program
* `token_mint`: the token's mint
* `destination_token_authority`: a PDA of your program, with seeds `[b"axelar-its-token-authority"]`
* `destination_program_ata`: the associated token account of `destination_token_authority`, which holds the tokens you received
* `interchain_transfer_execute`: a signer PDA of ITS, which proves that ITS called your program

ITS passes your own accounts after these five, in the order the sender lists them in the transfer's [data](#data-for-a-solana-program). They fill the rest of your struct; any extra ones are in `ctx.remaining_accounts`.

`MyState` is your program's account. In this guide, it holds the sender and token your program accepts, and owns the tokens your program sends:

```rust
#[account]
#[derive(InitSpace)]
pub struct MyState {
    #[max_len(32)]
    pub trusted_chain: String,
    #[max_len(64)]
    pub trusted_address: Vec<u8>,
    pub token_id: [u8; 32],
    pub bump: u8,
}

impl MyState {
    pub const SEED_PREFIX: &'static [u8] = b"my-state";
}
```

### Step 2: Implement the handler [#step-2-implement-the-handler]

ITS has already validated the message with the Gateway and delivered the tokens when it calls your program, so you don't call `validate_message()`. Check the sender first:

```rust
pub fn execute_with_interchain_token_handler<'info>(
    ctx: Context<'info, MyItsExecute<'info>>,
    execute_payload: AxelarExecuteWithInterchainTokenPayload,
) -> Result<()> {
    // Accept only your contract on the source chain, and only your token
    let state = &ctx.accounts.my_state;
    require!(
        execute_payload.source_chain == state.trusted_chain
            && execute_payload.source_address == state.trusted_address
            && execute_payload.token_id == state.token_id,
        MyError::UntrustedSender
    );

    msg!(
        "Received {} of token {} from {}",
        execute_payload.amount,
        execute_payload.token_mint,
        execute_payload.source_chain
    );

    // Decode `execute_payload.data` according to your protocol...

    // The tokens are now in `destination_program_ata`
    forward_tokens(&ctx, execute_payload.amount)
}
```

`AxelarExecuteWithInterchainTokenPayload` contains:

* `command_id`: the message's unique ID (`[u8; 32]`)
* `source_chain`: the Axelar name of the chain the transfer was sent from, e.g. `Ethereum`
* `source_address`: the sender on the source chain, as raw bytes (`Vec<u8>`)
* `token_id`: the [token ID](https://docs.axelar.dev/llms.mdx/its/how-it-works/concepts/content.md#token-id) (`[u8; 32]`)
* `token_mint`: the token's mint on Solana
* `amount`: the amount you received, in the token's smallest unit on Solana. For a token with a transfer fee, it's after the fee.
* `data`: the data the sender attached, without the account list

If your handler fails, the whole transaction reverts, including the delivery of the tokens: the message stays approved, and can be executed again.

### Check the sender [#check-the-sender]

<Callout emoji="⚠️" title="ITS doesn't check who sent the tokens">
  The `interchain_transfer_execute` signer proves that ITS called your program.
  It doesn't prove that the transfer came from your contract on the other chain,
  or that it's your token: anyone can send any interchain token, with data, to
  your program. Unless your program accepts transfers from anyone, check the
  source chain, the source address and the token ID.
</Callout>

`source_address` is the sender's address as raw bytes, not a string as in [GMP](https://docs.axelar.dev/llms.mdx/gmp/integrations/solana/gmp-messages/content.md#check-the-sender):

| Sender                        | `source_address`                     |
| ----------------------------- | ------------------------------------ |
| An EVM account or contract    | The 20-byte address                  |
| A Solana wallet               | The 32-byte public key               |
| A Solana program, through CPI | The 32-byte program ID (not its PDA) |

Compare them with values your program stores, as in the handler above.

### Step 3: Spend the tokens [#step-3-spend-the-tokens]

The tokens arrive in `destination_program_ata`, whose authority is `destination_token_authority`. Your program signs for that PDA to move them:

```rust
use anchor_spl::token_interface::{self, TransferChecked};

fn forward_tokens<'info>(ctx: &Context<'info, MyItsExecute<'info>>, amount: u64) -> Result<()> {
    let its = &ctx.accounts.its_executable;
    let bump = ctx.bumps.its_executable.destination_token_authority;
    let signer_seeds: &[&[&[u8]]] = &[&[
        solana_axelar_its::seed_prefixes::ITS_TOKEN_AUTHORITY_SEED,
        &[bump],
    ]];

    token_interface::transfer_checked(
        CpiContext::new_with_signer(
            its.token_program.key(),
            TransferChecked {
                from: its.destination_program_ata.to_account_info(),
                mint: its.token_mint.to_account_info(),
                to: ctx.accounts.recipient_token_account.to_account_info(),
                authority: its.destination_token_authority.to_account_info(),
            },
            signer_seeds,
        ),
        amount,
        its.token_mint.decimals,
    )
}
```

Transfers to your program without data land in the same account, so your program can spend them the same way.

### Step 4: Register the instruction [#step-4-register-the-instruction]

ITS calls your program with a fixed instruction discriminator. Either name the instruction exactly `execute_with_interchain_token`, so Anchor generates the matching discriminator, or set the discriminator to `ITS_EXECUTE_IX_DISC`. In both cases, import `solana_axelar_its::executable::*` in your `lib.rs`, where `#[program]` is.

**Option A:** name it `execute_with_interchain_token`:

```rust
use solana_axelar_its::executable::*;

#[program]
pub mod my_program {
    use super::*;

    pub fn execute_with_interchain_token<'info>(
        ctx: Context<'info, MyItsExecute<'info>>,
        execute_payload: AxelarExecuteWithInterchainTokenPayload,
    ) -> Result<()> {
        execute_with_interchain_token_handler(ctx, execute_payload)
    }
}
```

**Option B:** use another name, with the discriminator override:

```rust
#[instruction(discriminator = ITS_EXECUTE_IX_DISC)]
pub fn receive_tokens<'info>(
    ctx: Context<'info, MyItsExecute<'info>>,
    execute_payload: AxelarExecuteWithInterchainTokenPayload,
) -> Result<()> {
    execute_with_interchain_token_handler(ctx, execute_payload)
}
```

<Callout emoji="💡">
  The `executable_with_interchain_token_accounts!` macro is a convenience
  helper. If it doesn't fit your setup, copy the generated code from
  [`solana-axelar-its/src/executable.rs`](https://github.com/axelarnetwork/axelar-amplifier-solana/blob/main/programs/solana-axelar-its/src/executable.rs)
  and use it directly.
</Callout>

## Data for a Solana program [#data-for-a-solana-program]

The data of a transfer to a Solana program is an encoded payload: your program's data, followed by the accounts your program needs, so the relayer can pass them. ITS decodes it, checks that the transaction passes exactly these accounts, and gives your program the data without the account list. Data in any other format fails on Solana.

Build it with `ExecutablePayload`, listing the accounts in the order of your accounts struct after `its_executable`:

```rust
use anchor_lang::solana_program::instruction::AccountMeta;
use solana_axelar_its::executable::{ExecutablePayload, ExecutablePayloadEncodingScheme};

pub fn encode_data(app_data: &[u8], my_state: Pubkey, recipient_token_account: Pubkey) -> Result<Vec<u8>> {
    let data = ExecutablePayload::new(
        app_data,
        &[
            AccountMeta::new_readonly(my_state, false),       // my_state
            AccountMeta::new(recipient_token_account, false), // recipient_token_account
        ],
        ExecutablePayloadEncodingScheme::Borsh,
    )
    .encode()?;
    Ok(data)
}
```

With the Borsh scheme, the encoded data is:

| Bytes               | Content                                                                             |
| ------------------- | ----------------------------------------------------------------------------------- |
| 1                   | Encoding scheme: `0x00` for Borsh (`0x01` is ABI encoding)                          |
| 4                   | Length of your data, `u32` little-endian                                            |
| Length              | Your data                                                                           |
| 4                   | Number of accounts, `u32` little-endian                                             |
| 33 for each account | The account's 32-byte public key, then a flags byte: `0x01` signer, `0x02` writable |

<Callout emoji="💡">
  **Coming soon:** Documentation for building this data on EVM source chains.
</Callout>

Solana caps a transaction at 1232 bytes, and your data and the accounts it lists travel in the transaction that delivers the transfer, with the accounts ITS needs. The relayer compresses the accounts with [Address Lookup Tables](https://solana.com/developers/guides/advanced/lookup-tables), and creates a temporary one when the transaction doesn't fit otherwise, which costs more gas. Keep the data and the account list short.

## Send tokens [#send-tokens]

To send tokens to another chain, your program calls the ITS program's `interchain_transfer` instruction through CPI. ITS burns or locks the tokens, pays the gas and sends the transfer through the Gateway.

### Accounts [#accounts]

The tokens must be in a token account owned by a PDA of your program, here `my_state`:

```rust
use solana_axelar_its::program::SolanaAxelarIts;

#[derive(Accounts)]
pub struct SendInterchainTransfer<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    /// The PDA that owns the tokens. Your program signs for it.
    #[account(seeds = [MyState::SEED_PREFIX], bump = my_state.bump)]
    pub my_state: Account<'info, MyState>,

    // Gateway
    /// CHECK: checked by the ITS and gateway programs
    pub gateway_root_pda: UncheckedAccount<'info>,
    /// CHECK: checked by the ITS and gateway programs
    pub gateway_event_authority: UncheckedAccount<'info>,
    /// CHECK: checked by the ITS program
    pub gateway_program: UncheckedAccount<'info>,
    /// CHECK: checked by the ITS program
    pub call_contract_signing_pda: UncheckedAccount<'info>,

    // Gas Service
    /// CHECK: checked by the gas service program
    #[account(mut)]
    pub gas_treasury: UncheckedAccount<'info>,
    /// CHECK: checked by the ITS program
    pub gas_service: UncheckedAccount<'info>,
    /// CHECK: checked by the gas service program
    pub gas_event_authority: UncheckedAccount<'info>,

    // ITS
    /// CHECK: checked by the ITS program
    pub its_root_pda: UncheckedAccount<'info>,
    pub its_program: Program<'info, SolanaAxelarIts>,
    /// CHECK: checked by the ITS program
    pub its_event_authority: UncheckedAccount<'info>,
    /// CHECK: checked by the ITS program
    #[account(mut)]
    pub token_manager_pda: UncheckedAccount<'info>,

    // Token
    /// CHECK: checked by the ITS program
    pub token_program: UncheckedAccount<'info>,
    /// CHECK: checked by the ITS program
    #[account(mut)]
    pub token_mint: UncheckedAccount<'info>,
    /// The token account the tokens are sent from, owned by `my_state`
    /// CHECK: checked by the ITS program
    #[account(mut)]
    pub my_state_token_account: UncheckedAccount<'info>,
    /// CHECK: checked by the ITS program
    #[account(mut)]
    pub token_manager_ata: UncheckedAccount<'info>,

    pub system_program: Program<'info, System>,
}
```

To derive these accounts, see [Deriving the accounts](https://docs.axelar.dev/llms.mdx/its/integrations/solana/its-contracts/content.md#deriving-the-accounts).

### Handler [#handler]

```rust
pub fn send_interchain_transfer_handler(
    ctx: Context<SendInterchainTransfer>,
    token_id: [u8; 32],
    destination_chain: String,
    destination_address: Vec<u8>,
    amount: u64,
    gas_value: u64,
    data: Option<Vec<u8>>,
) -> Result<()> {
    let cpi_accounts = solana_axelar_its::cpi::accounts::InterchainTransfer {
        payer: ctx.accounts.payer.to_account_info(),
        authority: ctx.accounts.my_state.to_account_info(),
        gateway_root_pda: ctx.accounts.gateway_root_pda.to_account_info(),
        gateway_event_authority: ctx.accounts.gateway_event_authority.to_account_info(),
        gateway_program: ctx.accounts.gateway_program.to_account_info(),
        call_contract_signing_pda: ctx.accounts.call_contract_signing_pda.to_account_info(),
        gas_treasury: ctx.accounts.gas_treasury.to_account_info(),
        gas_service: ctx.accounts.gas_service.to_account_info(),
        gas_event_authority: ctx.accounts.gas_event_authority.to_account_info(),
        its_root_pda: ctx.accounts.its_root_pda.to_account_info(),
        program: ctx.accounts.its_program.to_account_info(),
        event_authority: ctx.accounts.its_event_authority.to_account_info(),
        token_manager_pda: ctx.accounts.token_manager_pda.to_account_info(),
        token_program: ctx.accounts.token_program.to_account_info(),
        token_mint: ctx.accounts.token_mint.to_account_info(),
        authority_token_account: ctx.accounts.my_state_token_account.to_account_info(),
        token_manager_ata: ctx.accounts.token_manager_ata.to_account_info(),
        system_program: ctx.accounts.system_program.to_account_info(),
    };

    // Seeds of the authority PDA, with the bump: to sign the CPI
    let bump = ctx.accounts.my_state.bump;
    let signer_seeds: &[&[&[u8]]] = &[&[MyState::SEED_PREFIX, &[bump]]];

    // The same seeds without the bump: for ITS to check the PDA
    let caller_pda_seeds = vec![MyState::SEED_PREFIX.to_vec()];

    solana_axelar_its::cpi::interchain_transfer(
        CpiContext::new_with_signer(ctx.accounts.its_program.key(), cpi_accounts, signer_seeds),
        token_id,
        destination_chain,
        destination_address,
        amount,
        gas_value,
        Some(crate::ID),        // caller_program_id
        Some(caller_pda_seeds), // caller_pda_seeds
        data,                   // optional data for the destination contract
    )
}
```

The arguments of `interchain_transfer`:

| Argument              | Meaning                                                                                                                                                                                                                                                                                                |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `token_id`            | The [token ID](https://docs.axelar.dev/llms.mdx/its/how-it-works/concepts/content.md#token-id) of the token you send                                                                                                                                                                                                                              |
| `destination_chain`   | The destination chain's Axelar name, e.g. `Ethereum`, matched exactly, including case (see [Chain names](https://docs.axelar.dev/llms.mdx/its/integrations/evm/intro/content.md#chain-names)). ITS on Solana must trust the chain                                                                                                                 |
| `destination_address` | The recipient as raw bytes, not a string: the 20-byte address on EVM chains                                                                                                                                                                                                                            |
| `amount`              | The amount in the token's smallest unit on Solana. Must be more than 0                                                                                                                                                                                                                                 |
| `gas_value`           | The fee for both hops, in lamports, paid from `payer`, which also gets any refund. Estimate it as in [Estimate ITS fees](https://docs.axelar.dev/llms.mdx/gmp/gas/estimate-and-pay/content.md#estimate-its-fees). With `0`, ITS pays no gas: [add gas](https://docs.axelar.dev/llms.mdx/gmp/track-and-recover/recover-transactions/content.md#add-gas) later, or the transfer isn't executed |
| `caller_program_id`   | Your program's ID, when the authority is a PDA of your program. `None` when the authority is a wallet                                                                                                                                                                                                  |
| `caller_pda_seeds`    | The seeds of the authority PDA, **without the bump**. `None` when the authority is a wallet                                                                                                                                                                                                            |
| `data`                | Data for the destination contract, or `None` for a plain transfer. On EVM chains, the destination contract receives it as an [interchain token executable](https://docs.axelar.dev/llms.mdx/its/integrations/evm/transfers-with-data/content.md)                                                                                                  |

ITS derives the PDA from `caller_pda_seeds` and `caller_program_id`, and checks that it's the authority. The bump must not be in `caller_pda_seeds`: ITS finds it itself, and with the bump included, it derives a different address and the transfer fails. The recipient sees `caller_program_id` as the sender.

To send from a wallet instead, the wallet signs as the authority and owns the token account, and you pass `None` for both `caller_program_id` and `caller_pda_seeds`. ITS then rejects an authority that isn't a wallet, and passing only one of the two fails.

How ITS takes the tokens depends on the token manager:

| Token manager type                                  | On sending                                                                                  |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `NativeInterchainToken`, `MintBurn`, `MintBurnFrom` | Burns the tokens. The authority signs the burn, so no approval is needed                    |
| `LockUnlock`                                        | Moves the tokens into the token manager's token account                                     |
| `LockUnlockFee`                                     | Moves the tokens into the token manager's token account, and sends the amount after the fee |

The token's [flow limit](https://docs.axelar.dev/llms.mdx/its/how-it-works/flow-limits/content.md) applies to both directions.

## Complete example [#complete-example]

The [Memo program](https://github.com/axelarnetwork/axelar-amplifier-solana/tree/main/programs/solana-axelar-memo) receives tokens with data in [`execute_with_interchain_token.rs`](https://github.com/axelarnetwork/axelar-amplifier-solana/blob/main/programs/solana-axelar-memo/src/instructions/execute_with_interchain_token.rs). It doesn't check the sender.


---

All pages of these docs, as Markdown: https://docs.axelar.dev/llms.txt