# Deploying Your Custom Solana Protocol

This guide provides step-by-step instructions on how to deploy your custom Solana protocol. By following these steps, you can successfully deploy your custom Solana protocol across multiple chains. If you encounter issues, refer to the [How to Debug Sent Messages](/universal-interoperability-protocol/developer-guides/how-to-debug-sent-messages.md) guide or reach out to us through our [contact form](https://entangle.zendesk.com/hc/en-us/requests/new).

## Prerequisites

{% hint style="info" %}
Ensure you have the latest versions installed.
{% endhint %}

Before proceeding, ensure you are familiar with writing Solana programs in [Rust](https://www.rust-lang.org/).

In this guide we'll be using anchor as the framework of choice, but native programs will work as well. You can follow the anchor [installation guide](https://www.anchor-lang.com/docs/installation) if it's your first time using it.&#x20;

Before proceeding make sure you have setup your environment first, as shown below.

```bash
agave-install init 2.1.0 # solana
avm use 0.31.0 # anchor
```

## Contract Dependencies <a href="#contract-dependencies" id="contract-dependencies"></a>

To streamline development, start by adding the UIP Solana SDK dependency to the configuration file.

{% hint style="info" %}
If you are not using Anchor, you don’t need to enable the `anchor-lang` feature.
{% endhint %}

{% code title="programs/\*/Cargo.toml" %}

```toml
uip-solana-sdk = { version = "0.13", default-features = false, features = ["anchor-lang"] }
```

{% endcode %}

Then, if you are developing an EVM to non-EVM protocol, you will likely need to use a library for EVM ABI encoding, we recommend using [alloy](https://github.com/alloy-rs):&#x20;

```toml
alloy-sol-types = "0.7"
```

## Sending a message <a href="#sending-a-message" id="sending-a-message"></a>

To send a message you need to perform a [CPI](https://solana.com/ru/docs/core/cpi) to the endpoint.

First, you need to declare the following accounts:

```rust
use uip_solana_sdk::UipEndpoint;

#[derive(Accounts)]
pub struct SendMessage<'info> {
    #[account(mut)]
    sender: Signer<'info>,
    /// CHECK: checked in the CPI
    endpoint_config: AccountInfo<'info>,
    /// CHECK: checked in the CPI
    #[account(mut)]
    uts_connector: AccountInfo<'info>,
    system_program: Program<'info, System>,
    uip_program: Program<'info, UipEndpoint>,
}
```

`sender` is the payer for the operation. `endpoint_config`  and`uts_connector` can be retrieved using the `ENDPOINT_CONFIG` constant and the `fetchUtsConnector` function from the TypeScript package.

Now, in the instruction, you can perform the `Propose` CPI:

```rust
use uip_solana_sdk::{chains::*, Commitment, UipEndpoint};

UipEndpoint::propose()
    .payer(ctx.accounts.sender.to_account_info())
    .endpoint_config(ctx.accounts.endpoint_config.to_account_info())
    .uts_connector(ctx.accounts.uts_connector.to_account_info())
    .system_program(ctx.accounts.system_program.to_account_info())
    .total_fee(uip_fee)
    .dest_chain_id(dest_chain_id)
    .dest_addr(&dest_addr)
    .payload(&payload)
    .custom_gas_limit(custom_gas_limit)
    .proposal_commitment(Commitment::Confirmed)
    .call()?;
```

Notice the following parameters:

* `total_fee` - it’s the fee paid for the message, usually calculated using the TypeScript UIP SDK
* `dest_chain_id` - chain ID of the destination network
* `selector` - in most situations it should be omitted
* `dest_addr` - address of the destination contract. If you’re sending to an EVM chain, make sure you left pad it so it’s 32 bytes
* `payload` - the payload, carries any data you want to send
* `proposal_commitment` - whether transmitters should wait for your proposal transaction to be finalized or confirmed. Skipping the finalization will reduce the transmission time by about 15 seconds, but could theoretically pose a risk if the Solana blockchain is compromised
* `custom_gas_limit` - used on the EVM destination to limit gas usage, must not be zero

## Receiving a message <a href="#receiving-a-message" id="receiving-a-message"></a>

First, import some helper methods.

```rust
use uip_solana_sdk::{chains::*, parse_uip_message, route_instruction, MessageDataRef};
```

When a message is received, the endpoint will invoke a special instruction on your contract with a specific discriminator: `uip_solana_sdk::EXECUTE_DISCRIMINATOR`. When using the Anchor framework, it can be declared like so:

```rust
#[instruction(discriminator = uip_solana_sdk::EXECUTE_DISCRIMINATOR)]
pub fn execute<'info>(ctx: Context<'_, '_, 'info, 'info, Execute>) -> Result<()> {
    // your code
}
```

If you are not using Anchor, make sure that the first 8 bytes of instruction data match `uip_solana_sdk::EXECUTE_DISCRIMINATOR`.

Now, the `Execute` instruction should take no arguments and require a single account, the UIP message:

```rust
#[derive(Accounts)]
pub struct Execute<'info> {
    /// CHECK: It's checked in `parse_uip_message`.
    uip_msg: AccountInfo<'info>,
}
```

The payer and the accounts that are specific to your protocol (specified in [the extension](#extension-registration)) will come after `uip_msg`. If you only need a single set of accounts for all possible payloads, you can specify these accounts in the same structure after `uip_msg`. Otherwise, they will be put in `ctx.remaining_accounts` for further routing.

In the function body, you should parse the incoming message:

```rust
let uip_msg_data = ctx.accounts.uip_msg.try_borrow_data()?;
let MessageDataRef {
    payload,
    sender_addr,
    src_chain_id,
    msg_hash,
    ..
} = parse_uip_message(&ctx.accounts.uip_msg, &uip_msg_data, &crate::ID)?;
```

First, you should validate that `src_chain_id` and `sender_addr` match one of the smart contracts that your protocol supports.

Then, decode the `payload` for further processing.

If you are using Anchor and expect different accounts for different types of messages, you can use the helper `route_instruction` function that can make it appear as if you are routing a real instruction, for example:

```rust
if some_condition {
    let ix_data = MyIxData {
        //
    };
    let params = MyIxParams {
        //
    };

    route_instruction(
        &crate::ID,
        my_ix,
        ctx.remaining_accounts,
        ix_data,
        params,
    )?;
} else {
  // route another instruction
}
```

Here, `my_ix` is defined as any other instruction, **but is not made public**. The first account must be payer, which is the UIP executor. The following accounts must be the custom accounts that you need. Don’t forget to perform all the required checks for these accounts.

```rust
/// Data for use in the anchor `instruction` attribute.
#[derive(AnchorSerialize, AnchorDeserialize)]
struct MyIxData {}

/// Input for the instruction function.
struct MyIxParams {}

#[derive(Accounts)]
#[instruction(ix_data: MyIxData)]
struct MyIx<'info> {
    #[account(mut)]
    payer: Signer<'info>,
    // other accounts
}

fn my_ix(ctx: Context<MyIx>, params: MyIxParams) -> Result<()> {
    // your implementation
}
```

Lastly, for the `Execute` instruction to be connected to the UIP network, you need to register your protocol extension.

## Extension registration <a href="#extension-registration" id="extension-registration"></a>

Solana is different to other networks in that apart from function parameters it needs accounts to be specified. In order for the executor to know what accounts need to be passed, we utilize something called extensions, compiled to WASM and stored on IPFS. Basically, it’s a separate library that describes your `Execute` instruction interface.

To implement it, initialize a new library and specify the following in `Cargo.toml`:

```toml
[lib]
crate-type = ["cdylib"]

[dependencies]
solana-program = ">=2.0,<2.2"
uip-solana-sdk = { version = "0.13", default-features = false, features = ["extension"] }
```

Then, you need to write a function called `get_api_version` that returns the version of the extension API that your extension supports. Right now, 1 is the highest supported version.

Then, you need to write the `get_instruction_info` function with a specific signature, that takes `MessageData` and writes the required accounts, compute units, and heap frame back into `InstructionInfo`. If your program needs more heap than the default, you can request heap frame (it must be a multiple of 1024 less than 256 KiB), otherwise leave it at 0.

Extensions can request at most 57 accounts for execution.

Here’s an example of an extension:

```rust
use solana_program::{instruction::AccountMeta, pubkey, pubkey::Pubkey, system_program};
use uip_solana_sdk::{
    extension::{HostCallContext, InstructionInfo},
    MessageDataRef,
};

#[no_mangle]
pub extern "C" fn get_api_version() -> u32 {
    1
}

#[no_mangle]
pub unsafe extern "C" fn get_instruction_info(
    msg_data_ptr: *const u8,
    msg_data_len: usize,
    result: &mut InstructionInfo,
) {
    let MessageDataRef {
        payload, msg_hash, ..
    } = MessageDataRef::load(msg_data_ptr, msg_data_len);

    let mut ctx = HostCallContext::new(msg_data_ptr, msg_data_len);

    // host call example
    let accounts =
        ctx.get_multiple_accounts(&[pubkey!("So11111111111111111111111111111111111111112")]);

    let (example_pda, _) = Pubkey::find_program_address(&[b"EXAMPLE"], &my_protocol::ID);
    result.push_account(AccountMeta::new(example_pda, false)).unwrap();
    result.push_account(AccountMeta::new_readonly(system_program::ID, false)).unwrap();
    result.compute_units = 50_000;
    result.heap_frame = 0;
}
```

Extensions run in an isolated environment, so they are limited in what they can do and how much CPU/memory they can use. To provide a way to communicate with the outside world, a host call mechanism is implemented. In particular, it allows extensions to fetch on-chain account data via `HostCallContext::get_multiple_accounts`.

The extension needs to be compiled to the WASI target:

```sh
cargo build --target wasm32-wasip1 --release -p example-extension
wasm-opt -O4 target/wasm32-wasip1/release/example_extension.wasm -o target/wasm32-wasip1/release/example_extension-optimized.wasm
```

Then upload `target/wasm32-wasip1/release/example_extension-optimized.wasm` to IPFS, using a service such as [Pinata](https://pinata.cloud/) (it’s free).

After that, record the CID of the uploaded file. You need to convert it to 36 bytes, it can be done using the `multiformats` package:

```typescript
import { CID } from "multiformats";

Array.from(CID.parse(ipfsCid).toV1().bytes) 
```

Your program needs to have an instruction that registers the extension using a CPI call. We recommend gating it with access control, so that only an admin or multisig can call it. You can run the instruction during initialization or upgrade of your program.

```rust
use uip_solana_sdk::UipEndpoint;

UipEndpoint::register_extension()
    .payer(ctx.accounts.payer.to_account_info())
    .extension(ctx.accounts.extension.to_account_info())
    .program_signer(ctx.accounts.program_signer.to_account_info())
    .system_program(ctx.accounts.system_program.to_account_info())
    .program_signer_bump(ctx.bumps.program_signer)
    .program_id(&crate::ID)
    .ipfs_cid(ipfs_cid)
    .call()?;
```

`program_signer` needs to be a special PDA with the following seeds: `[b"UIP_SIGNER"]`. The required `extension` can be found using the `findExtension` function from the Typescript SDK.

After performing `RegisterExtension`, your contract can now receive UIP messages!

## Client-side scripts and testing <a href="#client-side-scripts-and-testing" id="client-side-scripts-and-testing"></a>

The Solana endpoint has a TypeScript package to help with client-side interaction and testing. To use it, specify the following dependency in `package.json`:

```json
"@entangle-labs/uip-solana-sdk": "^0.6.2"
```

## Testing your contract <a href="#testing-your-contract" id="testing-your-contract"></a>

In order to test execution of your messages, you can mimic the cross-chain message execution on localnet. For this, you can use a mock UIP config with a predefined signer configuration:

```json
{
  "pubkey": "CMFjqmzBd59mHnHZgGz9c1ppZPN8VFnWZ8UtxPVUEJLq",
  "account": {
    "lamports": 1000000000,
    "data": [
      "Y29uZmlnAADJAd8re5StLtozqnHQTIUxBPzcP1D/0BcF5yammv9nTQ2e/M9oMSShewHJlfygI6T5KqWd24VF/aRvU3Qlr6VzAAAQYy1ex2sFAAAAAAAAAG2BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACY+ZK1fNWkTftaMdnFKgY/2Z5kggAAAAAAAAAAAAAAALdwH8xglWoFfkgKJ0m/FQO4fdawHgAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAC8sz2vhLHvUETFLNZB2x1X18k/xAEAAADGRxLEtG/v6UJahvO6/n/u9lZC2gEAAADJAd8re5StLtozqnHQTIUxBPzcP1D/0BcF5yammv9nTQ==",
      "base64"
    ],
    "owner": "uipby67GWuDDt1jZTWFdXNrsSu83kcxt9r5CLPTKGhX",
    "executable": false,
    "rentEpoch": 1844674407
  }
}
```

You can put it in `tests/accounts/uip_config.json`. Then, you can configure Anchor to load the mock account and the other accounts necessary for UIP endpoint execution. Add this to `Anchor.toml`:

```toml
[test.validator]
url = "https://api.devnet.solana.com"
[[test.validator.clone]]
# UIP program
address = "uipby67GWuDDt1jZTWFdXNrsSu83kcxt9r5CLPTKGhX"
[[test.validator.clone]]
# UTS config
address = "CTspuKSu7eRXzKqtYzR83H5VCZMWVRC4uRLfrA5Cy8WX"
[[test.validator.clone]]
# UTS connector
address = "vAukQz25gyuAHbdzEQS9GxMVZipVFu18MUoayKpETJz"
[[test.validator.account]]
address = "CMFjqmzBd59mHnHZgGz9c1ppZPN8VFnWZ8UtxPVUEJLq"
filename = "tests/accounts/uip_config.json"
```

Here’s an example of sending a message and intercepting the UIP proposal event.

```typescript
import {
  checkConsensus,
  encodeTransmitterParams,
  execute,
  fetchExtension,
  findExtension,
  findMessage,
  loadMessage,
  msgHashFull,
  onMessageProposed,
  sendSimulateExecuteLite,
  setProvider as setEndpointProvider,
  signMsg,
  unloadMessage,
} from "@entangle-labs/uip-solana-sdk";

const eventPromise: Promise<void> = new Promise((resolve, reject) => {
  onMessageProposed((event) => {
    try {
      selector = event.selector;
      payload = event.payload;
      resolve();
    } catch (error) {
      reject(error);
    }
  });
  setTimeout(() => {
    reject(new Error("Event did not fire within timeout"));
  }, 12000);
});

// send message via your protocol

await eventPromise;
```

And here’s an example of executing a message.

```typescript
import { privateKeyToAccount } from "viem/accounts";

const transmitterParams = {
  proposalCommitment: { confirmed: {} },
  customGasLimit: new BN(2),
};
const transmitterParamsEncoded = encodeTransmitterParams(transmitterParams);

const signer = privateKeyToAccount(
  "0x74e3ffad2b87174dc1d806edf1a01e3b017cf1be05d1894d329826f10fa1d72f",
);
const superSigner = privateKeyToAccount(
  "0xf496bcca0a4896011dbdbe2ec80417ed759a6a9cc72477b3a65b8d99b066b150",
);

const msgData = {
  initialProposal: {
    senderAddr,
    destAddr,
    ccmFee,
    payload,
    reserved: Buffer.from([]),
    transmitterParams: transmitterParamsEncoded,
    selector,
  },
  srcChainData: {
    srcBlockNumber,
    srcChainId,
    srcOpTxId,
  },
};

const signatures = [await signMsg(signer, msgData)];
const superSignatures = [
  await signMsg({ signer: superSigner, msgData, solanaChainId }),
];

const message = findMessage(msgData, solanaChainId);
await sendTx([
  await loadMessage({
    executor: executor.publicKey,
    msgData,
    solanaChainId,
  }),
  await checkConsensus({
    executor: executor.publicKey,
    message,
    signatures,
    superSignatures,
  }),
  await execute({
    executor: executor.publicKey,
    accounts,
    spendingLimit: new BN(2_000_000),
    deadline: new BN(Math.floor(Date.now() / 1000) + 10),
    destinationComputeUnits: 30_000,
    dstProgram: msgData.initialProposal.destAddr,
    message,
  }),
], [executor]);

await sendIx(await unloadMessage({ executor: executor.publicKey, message }), [
  executor,
]);
```

## Sending messages that don’t fit in a single Solana transaction <a href="#sending-messages-that-dont-fit-in-a-single-solana-transaction" id="sending-messages-that-dont-fit-in-a-single-solana-transaction"></a>

We have developed a utility program to allow loading data in chunks to later be used to invoke a Solana instruction. It allows passing up to 10KB of data to an instruction, bypassing the normal limit of around 1KB per instruction.

It will be automatically used by executors when a large message is passed to Solana from another chain. However, if you want to send a large transaction *from* Solana, you can also use the Chunk Loader program TypeScript SDK:

```json
"@lincot/solana-chunk-loader": "^0.4.4",
```

In order to pass large data to your function, you need to manually encode the function selector and arguments to a buffer, and then pass it by chunks to the Chunk Loader program. The `loadByChunks` instructions can be sent concurrently, and then followed by the `passToCpi` instruction.

```typescript
const chunkHolderId = randomInt(1 << 19);
const preInstructions = await loadByChunks({
  owner: sender,
  data,
  chunkHolderId,
});
const instruction = await passToCpi({
  owner: sender,
  program: YOUR_PROGRAM_ID,
  chunkHolderId,
  accounts: [
    { pubkey: sender, isSigner: true, isWritable: true },
    { pubkey: ENDPOINT_CONFIG, isSigner: false, isWritable: false },
    {
      pubkey: await fetchUtsConnector(connection),
      isSigner: false,
      isWritable: true,
    },
    {
      pubkey: SystemProgram.programId,
      isSigner: false,
      isWritable: false,
    },
    {
      pubkey: ENDPOINT_PROGRAM_ID,
      isSigner: false,
      isWritable: false,
    },
  ],
  cpiComputeUnits: 30_000,
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.entangle.fi/universal-interoperability-protocol/developer-guides/solana/deploying-your-custom-solana-protocol.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
