# Arbitrary Messaging: EVM to SVM
Source: https://docs.chain.link/ccip/tutorials/svm/destination/arbitrary-messaging
Last Updated: 2025-05-19

> For the complete documentation index, see [llms.txt](/llms.txt).

This tutorial demonstrates how to send arbitrary data from an Ethereum Virtual Machine (EVM) chain to a program on a SVM chain using Chainlink's Cross-Chain Interoperability Protocol (CCIP). You will learn how to configure CCIP messages that trigger a program execution on the destination chain.

> \*\*NOTE: Prerequisites\*\*
>
>
>
> Make sure you understand how to [build CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages)
> before beginning this tutorial.

## Introduction

This tutorial shows you how to send data-only messages from Ethereum Sepolia to a receiver program on Solana devnet.

## What You will Build

In this tutorial, you will:

- Configure a CCIP message for arbitrary data messaging
- Send data from Ethereum Sepolia to a Solana program
- Pay for CCIP transaction fees using LINK tokens
- Verify the data was received and processed by the program

## Prerequisites

Before starting EVM to SVM tutorials, ensure you have:

### Development Environment

- Node.js v20 or higher. You can use the [nvm package](https://www.npmjs.com/package/nvm) to install and switch between Node.js versions. Once installed, you can verify the installation by running `node -v` in your terminal:

  ```bash
  node -v
  ```

  Example output:

  ```bash
  $ node -v
  v23.11.0
  ```

- Git for cloning the repository.

- [Yarn](https://yarnpkg.com/) for installing dependencies.

- **Anchor and Solana CLI Tools**: Install Anchor and Solana CLI Tools following the [installation guide](https://www.anchor-lang.com/docs/installation). This requires Rust to be installed.

### Wallets

- An Ethereum wallet with a private key. If you use MetaMask, you can follow this [guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to export your private key.

### RPC URLs

Get an Ethereum RPC URL. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/), or another node provider service.

### Setup Instructions

- Clone the repository:
  ```bash
  git clone https://github.com/smartcontractkit/solana-starter-kit.git && cd solana-starter-kit
  ```

- Install dependencies:

  ```bash
  yarn install
  ```

- Create a `.env` file in the project root based on the provided example:

  ```
  EVM_PRIVATE_KEY=your_private_key_here
  EVM_RPC_URL=your_rpc_url_here
  ```

- Replace the `EVM_PRIVATE_KEY` and `EVM_RPC_URL` values with your own Ethereum private key and RPC URL that you obtained in the previous steps.

### Tokens for Testing

You'll need the following tokens on Ethereum Sepolia testnet to complete the tutorials:

| Token | Purpose              | How to Obtain                                                                                                                                                               |
| ----- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ETH   | Transaction gas fees | [Chainlink Faucet](https://faucets.chain.link/) or alternative sources such as the [Google Cloud Faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) |
| LINK  | CCIP fees            | [Chainlink Faucet](https://faucets.chain.link/)                                                                                                                             |

## Understanding Arbitrary Messaging to SVM

This tutorial focuses on arbitrary messaging from EVM chains to SVM programs. For detailed information about CCIP message structure and parameters, refer to the [guide on building CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages).

### Key Points Specific to Arbitrary Messaging

- **Program Execution**: Messages trigger program execution on the destination chain
- **Mandatory Settings**:
  - `computeUnits` must be sufficient for the execution of `ccip_receive` instruction of the receiver program
  - `receiver` field should be set to the Solana program ID
  - `tokenReceiver` is typically set to the default PublicKey (`11111111111111111111111111111111`)
  - Required accounts must be specified in the `accounts` array
  - `accountIsWritableBitmap` must correctly identify which accounts should be writable
  - `allowOutOfOrderExecution` must be `true`

For more details on the CCIP message structure, `extraArgs`, and how the SVM account model works, refer to the [guide on building CCIP messages from EVM to SVM](/ccip/tutorials/svm/destination/build-messages).

### Key Differences from Token Transfers

### The CCIP Basic Receiver Program

This tutorial uses the [Basic Receiver program](https://github.com/smartcontractkit/solana-starter-kit/tree/main/programs/ccip-basic-receiver) deployed on [Solana Devnet](https://explorer.solana.com/address/BqmcnLFSbKwyMEgi7VhVeJCis1wW26VySztF34CJrKFq?cluster=devnet) as the destination program.

> \*\*NOTE: Scope Clarification\*\*
>
>
>
> Learning how to write, deploy, and initialize a SVM program is outside the scope of this tutorial. This guide focuses
> on sending messages to an existing program. The principles you will learn can be applied to any SVM program that
> implements the CCIP receiver interface.

#### Program Interface Overview

The Basic Receiver program implements the CCIP receiver interface, which requires a `ccip_receive` instruction. This instruction is called by the CCIP offramp when a cross-chain message arrives. Key aspects include:

- The program verifies the caller (must be the trusted CCIP offramp)
- It deserializes and validates the incoming message
- The message data is stored in the program's storage PDA
- The program maintains a message counter to track received messages
- Received messages can be accessed using helper methods provided in the program

#### Understanding Program Derived Addresses (PDAs)

To interact with SVM programs, you need to understand Program Derived Addresses (PDAs):

- PDAs are deterministic addresses derived from seeds and a program ID
- They provide onchain storage for Solana programs
- Each PDA serves a specific purpose
- When sending messages to a program, you must specify all accounts (including PDAs) that the program requires

For the CCIP Basic Receiver program, we need two main PDAs:

1. **State PDA**:
   - Contains essential program settings like the router address and owner
   - Derived using the seed "state"
   - Controlled by the program owner/authority
   - The State PDA is critical for validating incoming messages in the `ccip_receive` instruction. It stores the trusted router program ID, which is used to authenticate the caller
   - When a message is received, the program checks if the caller is an authorized CCIP router by validating against the router address stored in this PDA

2. **Messages Storage PDA**:

   - Contains only the most recent cross-chain message
   - Derived using the seed "messages"
   - Maintains a message counter and last updated timestamp
   - Stores message such as the message ID, type, and data payload

## Implementing Arbitrary Messaging

In this section, we'll examine how arbitrary messaging works in the example script from the starter kit repository. This will help you understand how to send messages to the CCIP Basic Receiver program on Solana Devnet.

### Deriving Program Derived Addresses

The script first derives the PDAs required by the receiver program. This is a critical step because the program can only process messages if the correct PDAs are provided:

```typescript
function deriveReceiverPDAs(receiverProgramIdStr: string) {
  const receiverProgramId = new PublicKey(receiverProgramIdStr)

  const STATE_SEED = Buffer.from("state")
  const MESSAGES_STORAGE_SEED = Buffer.from("messages_storage")

  const [statePda] = PublicKey.findProgramAddressSync([STATE_SEED], receiverProgramId)

  const [messagesStoragePda] = PublicKey.findProgramAddressSync([MESSAGES_STORAGE_SEED], receiverProgramId)

  return {
    state: statePda,
    messagesStorage: messagesStoragePda,
  }
}

// Get the receiver program ID for Solana Devnet
const receiverProgramId = getCCIPSVMConfig(ChainId.SOLANA_DEVNET).receiverProgramId.toString()

// Derive the PDAs for the receiver program
const pdas = deriveReceiverPDAs(receiverProgramId)
```

> \*\*NOTE: PDA Derivation\*\*
>
>
>
> When interacting with any Solana program, PDAs must be derived using the exact same seeds that the program uses
> internally. Incorrect seeds will result in different addresses, causing transaction failures.

### Configuring the Message

The script then configures the CCIP message with all necessary parameters for arbitrary messaging:

```typescript
const MESSAGE_CONFIG = {
  // Custom message to send - must be properly encoded as hex with 0x prefix
  // This example encodes "Hello World" to hex
  data: "0x48656c6c6f20576f726c64", // "Hello World" in hex

  // Destination program on Solana that will receive the message
  receiver: receiverProgramId,

  // Fee token to use for CCIP fees
  feeToken: FeeTokenType.LINK,

  // No tokens are transferred with this message
  tokenAmounts: [],

  // Extra configuration for Solana
  extraArgs: {
    // Compute units for Solana execution
    // Needed because message processing requires compute units
    computeUnits: 200000,

    // Allow out-of-order execution - MUST be true for Solana
    allowOutOfOrderExecution: true,

    // Binary 10 (decimal 2) means only the messages storage account is writable
    accountIsWritableBitmap: BigInt(2),

    // Token receiver - for arbitrary messages, this is usually the default PublicKey
    tokenReceiver: PublicKey.default.toString(),

    // Accounts required by the receiver program
    accounts: [pdas.state.toString(), pdas.messagesStorage.toString()],
  },
}
```

### Understanding AccountIsWritableBitmap

When sending messages to Solana programs, you need to specify which accounts can be written to. The `accountIsWritableBitmap` parameter in the script indicates which accounts in the `accounts` array should be marked as writable.

For our example:

- The script provides 2 accounts: `[statePda, messagesStoragePda]`
- Only the messagesStoragePda needs to be writable for the program to update it
- The state PDA is read-only during message processing
- The bitmap uses binary representation to specify writable permissions:
  - Position 0 (statePda): Not writable → Bit value = 0
  - Position 1 (messagesStoragePda): Writable → Bit value = 1
  - Binary value: 10 (base 2) = 2 (base 10)

Therefore, we set `accountIsWritableBitmap: BigInt(2)` in our message configuration.

### Creating and Sending the CCIP Message

Finally, the script creates and sends the CCIP message using the CCIP SDK:

```typescript
// Setup client context
const { client, wallet } = await setupClientContext()

// Create the CCIP message request
const ccipMessageRequest = await createCCIPMessageRequest(client, config.destinationChainSelector, MESSAGE_CONFIG)

// Send the CCIP message
const result = await client.sendCCIPMessage(ccipMessageRequest)
```

This sends the message to the CCIP router, which then forwards it to the Solana program on the destination chain.

The script includes detailed comments that explain its implementation and follows a straightforward flow. You can review the source code at `ccip-scripts/evm/router/2_arbitrary-messaging.ts` in the starter kit repository.

## Running the Arbitrary Messaging Script

### Run the Script

Now that you understand how the arbitrary messaging script works, let's execute it to send a message:

```bash
yarn evm:arbitrary-messaging
```

### Expected Output

When you run the script, it will display detailed information about the message being sent. You should see output similar to this:

```
==== Environment Information ====
chainId ethereum-sepolia
[arbitrary-messaging] [INFO] Router Address: 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
chainId ethereum-sepolia
[ccip-messenger] [INFO] Creating client for chain: Ethereum Sepolia (ethereum-sepolia)
[arbitrary-messaging] [INFO] Wallet Address: 0x9d087fC03ae39b088326b67fA3C788236645b717
[arbitrary-messaging] [INFO] Native Balance: 600.023603232108193848 ETH
[arbitrary-messaging] [INFO] Creating CCIP message request
[arbitrary-messaging] [INFO] Using fee token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
[arbitrary-messaging] [INFO]
==== Transfer Summary ====
[arbitrary-messaging] [INFO] Source Chain: Ethereum Sepolia
[arbitrary-messaging] [INFO] Destination Chain: Solana Devnet (16423721717087811551)
[arbitrary-messaging] [INFO] Sender: 0x9d087fC03ae39b088326b67fA3C788236645b717
[arbitrary-messaging] [INFO] Receiver: BqmcnLFSbKwyMEgi7VhVeJCis1wW26VySztF34CJrKFq
[arbitrary-messaging] [INFO] Token Receiver: 11111111111111111111111111111111
[arbitrary-messaging] [INFO] Fee Token: 0x779877A7B0D9E8603169DdbD7836e478b4624789
[arbitrary-messaging] [INFO] No tokens being transferred
[arbitrary-messaging] [INFO]
Message Data: 0x48656c6c6f20576f726c64
[arbitrary-messaging] [INFO] Message Data Size: 11 bytes
[arbitrary-messaging] [INFO] Message Data (decoded): Hello World
[arbitrary-messaging] [INFO]
Extra Args: Solana-specific, 292 bytes
[arbitrary-messaging] [INFO] Additional Accounts: 9XDoTJ5mYNnxqdtWV5dA583VCiGUhmL3oEMWirqys3tF, CB7ptrDkY9EgwqHoJwa3TF8u4rhwYmTob2YqzaSpPMtE
[arbitrary-messaging] [INFO] Account Is Writable Bitmap: 2
[arbitrary-messaging] [INFO]
Sending CCIP message...
[ccip-messenger] [INFO] Estimated fee: 12417653034565940
[ccip-messenger] [INFO] Approving 0.014901183641479128 LINK for CCIP Router
[ccip-messenger] [INFO] This token is being used as the fee token with a 20% buffer included
[ccip-messenger] [INFO] Approving 14901183641479128 tokens for 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
[ccip-messenger] [INFO] LINK approved for CCIP Router
[ccip-messenger] [INFO] ✅ Verified on-chain allowance for LINK: 0.014901183641479128 (required: 0.014901183641479128)
[ccip-messenger] [INFO] Sending CCIP message...
[ccip-messenger] [INFO] Sending CCIP message...
[ccip-messenger] [INFO] Transaction sent: 0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[ccip-messenger] [INFO] Transaction sent: 0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[ccip-messenger] [INFO] Message ID: 0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[arbitrary-messaging] [INFO]
==== Transfer Results ====
[arbitrary-messaging] [INFO] Transaction Hash: 0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[arbitrary-messaging] [INFO] Transaction URL: https://sepolia.etherscan.io/tx/0x6181d2d3d033698149f8dd439df5a5b6753c741e5378761567d013746845213f
[arbitrary-messaging] [INFO] Message ID: 0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[arbitrary-messaging] [INFO] CCIP Explorer: https://ccip.chain.link/msg/0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[arbitrary-messaging] [INFO] Destination Chain Selector: 16423721717087811551
[arbitrary-messaging] [INFO] Sequence Number: 13
[arbitrary-messaging] [INFO]
Message tracking for Solana destinations:
[arbitrary-messaging] [INFO] Please check the CCIP Explorer link to monitor your message status.
[arbitrary-messaging] [INFO]
✅ Transaction completed on the source chain
```

### Check the Message Status

The output indicates that the transaction was completed on the source chain. In this example, the message ID is `0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b`. You can use the [CCIP Explorer](https://ccip.chain.link/) to monitor the message status.

Wait for the message to be shown as successful on the CCIP Explorer. This may take several minutes to complete.

## Verification: Retrieving the Message

Make sure to wait for the message to be shown as successful on the CCIP Explorer before retrieving the message.

### Query the Receiver Program

After sending your message, you'll want to verify that it was received and processed by the receiver program. The CCIP Basic Receiver program stores messages, allowing you to retrieve them:

```bash
yarn svm:receiver:get-message
```

This script queries the program's message storage PDA, deserializes the data, and displays the content of the most recently received cross-chain message.

### Expected Verification Output

When you run the verification script, you'll see output that confirms your message was received:

```
[2025-05-01T22:20:48.307Z] CCIP Basic Receiver - Get Latest Message
[2025-05-01T22:20:48.310Z] Program ID: BqmcnLFSbKwyMEgi7VhVeJCis1wW26VySztF34CJrKFq
[2025-05-01T22:20:48.337Z] Wallet public key: EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB
[2025-05-01T22:20:48.343Z] Messages Storage PDA: CB7ptrDkY9EgwqHoJwa3TF8u4rhwYmTob2YqzaSpPMtE
[2025-05-01T22:20:52.088Z] Fetching latest message...
[2025-05-01T22:20:52.693Z]
======== LATEST MESSAGE ========
[2025-05-01T22:20:52.693Z] Message ID: 0x1ebd6f5b23b23613e6ba277c1b4e8ce2a04c6c03e8db20474e97c040a01b3f0b
[2025-05-01T22:20:52.695Z] Source Chain Selector: 16015286601757825753
[2025-05-01T22:20:52.695Z] Sender: 0x9d087fc03ae39b088326b67fa3c788236645b717
[2025-05-01T22:20:52.695Z] Message Type: Arbitrary Messaging
[2025-05-01T22:20:52.696Z] Received Timestamp: 2025-05-01T22:20:09.000Z
[2025-05-01T22:20:52.696Z] Data (hex): 0x48656c6c6f20576f726c64
[2025-05-01T22:20:52.696Z] Data (text): Hello World
[2025-05-01T22:20:52.696Z] No tokens transferred in this message
```

> \*\*NOTE: Verifying Message Details\*\*
>
>
>
> In the output, check that:
>
> - The **Message ID** matches the one from your arbitrary messaging script
> - The **Source Chain Selector** indicates Ethereum Sepolia (16015286601757825753)
> - The **Sender** matches your EVM wallet address
> - The **Data** shows your message correctly decoded

## Behind the Scenes: Message and Account Encoding

When sending arbitrary messages to SVM programs, proper encoding of both the message data and account structures is critical. The script handles several complex encoding tasks that would need to be implemented in your own applications:

Key utilities used include:

- `deriveReceiverPDAs()`: Calculates Program Derived Addresses using the same seeds as the SVM program
- `createCCIPMessageRequest()`: Builds the properly formatted CCIP message with all required fields
- Hex encoding utilities: Convert string data to properly formatted hex with `0x` prefix
- `accountIsWritableBitmap` calculation: Determines which accounts can be written to by the program

These utilities handle essential tasks such as:

- Converting between different address formats (Ethereum addresses vs. SVM public keys)
- Properly encoding message data with correct prefixes
- Setting up the required accounts array with properly formatted SVM addresses
- Calculating the correct bitmap values for account permissions

If you're building your own implementation, you should review these key files:

- `ccip-scripts/evm/utils/message-utils.ts`
- `ccip-lib/evm/utils/solana.ts`
- `ccip-lib/evm/core/models.ts`

Understanding these encoding details becomes especially important when working with your own SVM programs, as each program will have its own PDA structure and account requirements.

> **CAUTION: Educational Example Disclaimer**
>
> This page includes an educational example to use a Chainlink system, product, or service and is provided to
> demonstrate how to interact with Chainlink's systems, products, and services to integrate them into your own. This
> template is provided "AS IS" and "AS AVAILABLE" without warranties of any kind, it has not been audited, and it may be
> missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the
> code in this example in a production environment without completing your own audits and application of best practices.
> Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs
> that are generated due to errors in code.