> ## Documentation Index
> Fetch the complete documentation index at: https://docs.magicblock.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust Example

> Learn how to write a simple Rust program that delegates and increments a counter on Solana

<div
  style={{
position: "relative",
paddingBottom: "56.25%",
height: 0,
overflow: "hidden",
}}
>
  <iframe
    src="https://www.youtube.com/embed/b77bwSGDHK0?si=Oknc5f8CuC17WBnV&list=PLWR_ZQiGMS8mIe1kPZe8OfHIbhvZqaM8V"
    title="Build a real-time Rust Counter on Solana with MagicBlock's Ephemeral Rollup | Tutorial"
    style={{
  position: "absolute",
  top: 0,
  left: 0,
  width: "100%",
  height: "100%",
}}
    frameBorder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    allowFullScreen
    referrerPolicy="strict-origin-when-cross-origin"
  />
</div>

This guide will walk you through the process of writing a simple native Rust program (without Anchor dependencies) that increments a counter. You’ll learn how to deploy this program on Solana and interact with it using a Typescript test script.

## Software Packages

This program is developed and tested with the following software packages. Other versions may also be compatible.

| Software   | Version | Installation Guide                                      |
| ---------- | ------- | ------------------------------------------------------- |
| **Solana** | 3.1.9   | [Install Solana](https://docs.anza.xyz/cli/install)     |
| **Rust**   | 1.89.0  | [Install Rust](https://www.rust-lang.org/tools/install) |

## Quick Access

If you prefer to dive straight into the code:

<CardGroup cols={2}>
  <Card title="Source Code: Native Rust Counter Program" icon="rust" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/counter/native-rust" iconType="duotone" />

  <Card title="Source Code: Typescript Test Script" icon="js" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/counter/native-rust/tests" iconType="duotone" />
</CardGroup>

## Core Functionality

The program implements two main instructions:

1. `InitializeCounter`: Initialize and sets the counter to 0 (called on Base Layer)
2. `IncreaseCounter`: Increments the initialized counter by X amount (called on Base Layer or ER)

The program implements specific instructions for delegating and undelegating the counter:

1. `Delegate`: Delegates counter from Base Layer to ER (called on Base Layer)
2. `CommitAndUndelegate`: Schedules sync of counter from ER to Base Layer, and undelegates counter on ER (called on ER)
3. `Commit`: Schedules sync of counter from ER to Base Layer (called on ER)
4. `Undelegate`: Undelegates counter on the Base Layer (called on Base Layer through validator CPI)

Here's the core structure of our program:

```rust theme={null}
pub enum ProgramInstruction {
    InitializeCounter,
    IncreaseCounter {
        increase_by: u64
    },
    Delegate,
    CommitAndUndelegate,
    Commit,
    Undelegate {
        pda_seeds: Vec<Vec<u8>>
    }
}

#[derive(BorshDeserialize)]
struct IncreaseCounterPayload {
    increase_by: u64,
}

impl ProgramInstruction {
    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        // Ensure the input has at least 8 bytes for the variant
        if input.len() < 8 {
            return Err(ProgramError::InvalidInstructionData);
        }

        // Extract the first 8 bytes as variant
        let (variant_bytes, rest) = input.split_at(8);
        let mut variant = [0u8; 8];
        variant.copy_from_slice(variant_bytes);

        Ok(match variant {
            [0, 0, 0, 0, 0, 0, 0, 0] => Self::InitializeCounter,
            [1, 0, 0, 0, 0, 0, 0, 0] => {
                let payload = IncreaseCounterPayload::try_from_slice(rest)?;
                Self::IncreaseCounter {
                    increase_by: payload.increase_by,
                }
            },
            [2, 0, 0, 0, 0, 0, 0, 0] => Self::Delegate,
            [3, 0, 0, 0, 0, 0, 0, 0] => Self::CommitAndUndelegate,
            [4, 0, 0, 0, 0, 0, 0, 0] => Self::Commit,
            [196, 28, 41, 206, 48, 37, 51, 167] => {
                let pda_seeds: Vec<Vec<u8>> = Vec::<Vec<u8>>::try_from_slice(rest)?;
                Self::Undelegate {
                    pda_seeds
                }
            }
            _ => return Err(ProgramError::InvalidInstructionData),
        })
    }
}
```

<Note>
  Your "Undelegate" instruction must have the exact discriminator. It is never
  called by you, instead the validator on the Base Layer will callback with a
  CPI into your program after undelegating your account on ER.
</Note>

### Delegating the Counter PDA

In order to delegate the counter PDA, and make it writable in an Ephemeral Rollup session, we need to add an instruction which
internally calls the `delegate_account` function. `delegate_account` will CPI to the delegation program, which upon validation will gain ownership of the account.
After this step, an ephemeral validator can start processing transactions on the counter PDA and propose state diff trough the delegation program.

<Card title="Transaction (Base Layer): Delegate" icon="magnifying-glass" href="https://solscan.io/tx/5jUdf5rsfQsbLYAahS9axrnLnEjdbUqtXUmGfgGuS7QqbYJ4FZHgXTNoT1bxPXp7XQu78r8Ebpp1RT2u9V6qsc1r?cluster=devnet" iconType="duotone">Inspect transactions details on Solana Explorer</Card>

```rust theme={null}
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    pubkey::Pubkey,
    entrypoint::ProgramResult,
    program_error::ProgramError,
};
use ephemeral_rollups_sdk::cpi::{delegate_account, DelegateAccounts, DelegateConfig};

// For Base Layer only
// Set specific validator based on ER, see https://docs.magicblock.gg/pages/get-started/how-integrate-your-program/local-setup
pub fn process_delegate(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
    // Get accounts
    let account_info_iter = &mut accounts.iter();
    let initializer = next_account_info(account_info_iter)?;
    let system_program = next_account_info(account_info_iter)?;
    let pda_to_delegate = next_account_info(account_info_iter)?;
    let owner_program = next_account_info(account_info_iter)?;
    let delegation_buffer = next_account_info(account_info_iter)?;
    let delegation_record = next_account_info(account_info_iter)?;
    let delegation_metadata = next_account_info(account_info_iter)?;
    let delegation_program = next_account_info(account_info_iter)?;
    let validator_account = account_info_iter.next();

    // Optional: client-provided validator or default validator
    let validator_pubkey: Option<Pubkey> = validator_account.map(|acc_info| acc_info.key.clone());

    // Prepare counter pda seeds
    let seed_1 = b"counter";
    let seed_2 = initializer.key.as_ref();
    let pda_seeds: &[&[u8]] = &[seed_1, seed_2];

    let delegate_accounts = DelegateAccounts {
        payer: initializer,
        pda: pda_to_delegate,
        owner_program,
        buffer: delegation_buffer,
        delegation_record,
        delegation_metadata,
        delegation_program,
        system_program,
    };

    let delegate_config = DelegateConfig {
        validator: validator_pubkey, // Set delegating ER validator
        ..Default::default()
    };

    delegate_account(delegate_accounts, pda_seeds, delegate_config)?;

    Ok(())
}

```

### Committing while the PDA is delegated

The ephemeral runtime allows committing the state of the PDA while it is delegated. This is done by building a `MagicIntentBundleBuilder` with the `commit` intent.

<CardGroup cols={2}>
  <Card title="Transaction (ER): Commit" icon="magnifying-glass" href="https://solscan.io/tx/5GiFGyrnJPkEQbhE8EHVYczoj2RPGe1YSnoq1DzcUHAxpyzMUKtxG2Tc7TLkxtcCHr72ftnvmkAVMfecTaf6TCK8?cluster=custom&customUrl=https://devnet.magicblock.app" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>

  <Card title="Transaction (Base layer): Commit" icon="magnifying-glass" href="https://solscan.io/tx/5fHBADq99LAEBzGoeDXGtN3ut8RBf2s5UhbDM1N6TMTpvADNeYLz8e8vinNWj1VhLhrR7UxFoW7bo2u6pBR3YRjj?cluster=devnet" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>
</CardGroup>

```rust theme={null}
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    pubkey::Pubkey,
    entrypoint::ProgramResult,
    program_error::ProgramError,
};
use ephemeral_rollups_sdk::ephem::{FoldableIntentBuilder, MagicIntentBundleBuilder};

// For ER only
pub fn process_commit(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
) -> ProgramResult {
    // Get accounts
    let account_info_iter = &mut accounts.iter();
    let initializer = next_account_info(account_info_iter)?;
    let counter_account = next_account_info(account_info_iter)?;
    let magic_program = next_account_info(account_info_iter)?;
    let magic_context = next_account_info(account_info_iter)?;

    // Signer should be the same as the initializer
    if !initializer.is_signer {
        msg!("Initializer {} should be the signer", initializer.key);
        return Err(ProgramError::MissingRequiredSignature);
    }

    MagicIntentBundleBuilder::new(
        initializer.clone(),
        magic_context.clone(),
        magic_program.clone(),
    )
    .commit(&[counter_account.clone()])
    .build_and_invoke()?;

    Ok(())
}
```

### Undelegating the PDA

Undelegating the PDA is done by building a `MagicIntentBundleBuilder` with the `commit_and_undelegate` intent as part of some instruction.
This commits the latest state and returns ownership of the PDA to the owner program. After undelegating and finalizing the state, the validator will create a callback CPI into `undelegate` on the base layer.

<CardGroup cols={2}>
  <Card title="Transaction (ER): Undelegate" icon="magnifying-glass" href="https://solscan.io/tx/bMN6AhXrGH93Uc6ALibGgjnE39hcnY57mBhYkZ8TRaKxNRvyFaweaQPBmDxPv81cgR47WTTzzhfziTUEgAT8Y5m?cluster=custom&customUrl=https://devnet.magicblock.app" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>

  <Card title="Transaction (Base layer): Undelegate" icon="magnifying-glass" href="https://solscan.io/tx/8JafYWiXmd4CHc2E97WKYnaNPmChZeg8aGYY7UUWaQ7Z54N5WoMcAVivyv2vdn9wKirMkR3y4UcmFPdXYqBtKAa?cluster=devnet" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>
</CardGroup>

```rust theme={null}
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    pubkey::Pubkey,
    entrypoint::ProgramResult,
    program_error::ProgramError,
};
use ephemeral_rollups_sdk::cpi::undelegate_account;
use ephemeral_rollups_sdk::ephem::{FoldableIntentBuilder, MagicIntentBundleBuilder};

// For ER only
pub fn process_commit_and_undelegate(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
) -> ProgramResult {
    // Get accounts
    let account_info_iter = &mut accounts.iter();
    let initializer = next_account_info(account_info_iter)?;
    let counter_account = next_account_info(account_info_iter)?;
    let magic_program = next_account_info(account_info_iter)?;
    let magic_context = next_account_info(account_info_iter)?;

    // Signer should be the same as the initializer
    if !initializer.is_signer {
        msg!("Initializer {} should be the signer", initializer.key);
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Commit and undelegate counter_account on ER
    MagicIntentBundleBuilder::new(
        initializer.clone(),
        magic_context.clone(),
        magic_program.clone(),
    )
    .commit_and_undelegate(&[counter_account.clone()])
    .build_and_invoke()?;

    Ok(())

}

// For Base Layer CPI callback
pub fn process_undelegate(
program_id: &Pubkey,
accounts: &[AccountInfo],
pda_seeds: Vec<Vec<u8>>,
) -> ProgramResult {
// Get accounts
let account_info_iter = &mut accounts.iter();
let delegated_pda = next_account_info(account_info_iter)?;
let delegation_buffer = next_account_info(account_info_iter)?;
let initializer = next_account_info(account_info_iter)?;
let system_program = next_account_info(account_info_iter)?;

    // CPI on Solana
    undelegate_account(
        delegated_pda,
        program_id,
        delegation_buffer,
        initializer,
        system_program,
        pda_seeds,
    )?;

    Ok(())

}

```

## Testing the program

Build valid transactions that calls your program instructions for delegation and undelegation.
The complete test for this project can be found in the [Typescript Test Script](https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/counter/native-rust/tests).

### Test `delegation` transaction

Create a instruction with the right order and attributes of accounts, and the instruction discriminator for `delegation` of your program. Send the transaction with the instruction to Base Layer (Solana) network.

<Card title="Transaction (Base Layer): Delegate" icon="magnifying-glass" href="https://solscan.io/tx/5jUdf5rsfQsbLYAahS9axrnLnEjdbUqtXUmGfgGuS7QqbYJ4FZHgXTNoT1bxPXp7XQu78r8Ebpp1RT2u9V6qsc1r?cluster=devnet" iconType="duotone">
  Inspect transactions details on Solana Explorer
</Card>

```typescript theme={null}
import * as web3 from "@solana/web3.js";

// Build delegation transaction
const tx = new web3.Transaction();
const keys = [
  // Initializer
  {
    pubkey: userKeypair.publicKey,
    isSigner: true,
    isWritable: true,
  },
  // System Program
  {
    pubkey: web3.SystemProgram.programId,
    isSigner: false,
    isWritable: false,
  },
  // Counter Account
  {
    pubkey: counterPda,
    isSigner: false,
    isWritable: true,
  },
  // Owner Program
  {
    pubkey: PROGRAM_ID,
    isSigner: false,
    isWritable: false,
  },
  // Delegation Buffer
  {
    pubkey: getDelegationBufferPda(counterPda, PROGRAM_ID),
    isSigner: false,
    isWritable: true,
  },
  // Delegation Record
  {
    pubkey: getDelegationRecordPda(counterPda),
    isSigner: false,
    isWritable: true,
  },
  // Delegation Metadata
  {
    pubkey: getDelegationMetadataPda(counterPda),
    isSigner: false,
    isWritable: true,
  },
  // Delegation Program
  {
    pubkey: DELEGATION_PROGRAM_ID,
    isSigner: false,
    isWritable: false,
  },
];
const serializedInstructionData = Buffer.from(
  CounterInstruction.Delegate,
  "hex"
);
const delegateIx = new web3.TransactionInstruction({
  keys: keys,
  programId: PROGRAM_ID,
  data: serializedInstructionData,
});
tx.add(delegateIx);

// Send and confirm transaction to Base Layer
const connection = new web3.Connection(rpcSolana);
const txHash = await web3.sendAndConfirmTransaction(
  connection,
  tx,
  [userKeypair],
  {
    skipPreflight: true,
    commitment: "confirmed",
  }
);
```

## Test `commit` transaction

Create a instruction with the right order and attributes of accounts, and the instruction discriminator for `commit` of your program. Send the transaction with the instruction to ER network.

<CardGroup cols={2}>
  <Card title="Transaction (ER): Commit" icon="magnifying-glass" href="https://solscan.io/tx/5GiFGyrnJPkEQbhE8EHVYczoj2RPGe1YSnoq1DzcUHAxpyzMUKtxG2Tc7TLkxtcCHr72ftnvmkAVMfecTaf6TCK8?cluster=custom&customUrl=https://devnet.magicblock.app" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>

  <Card title="Transaction (Base layer): Commit" icon="magnifying-glass" href="https://solscan.io/tx/5fHBADq99LAEBzGoeDXGtN3ut8RBf2s5UhbDM1N6TMTpvADNeYLz8e8vinNWj1VhLhrR7UxFoW7bo2u6pBR3YRjj?cluster=devnet" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>
</CardGroup>

```typescript theme={null}
import * as web3 from "@solana/web3.js";

// 3: Commit
// Create, send and confirm transaction
const tx = new web3.Transaction();
const keys = [
  // Initializer
  {
    pubkey: userKeypair.publicKey,
    isSigner: true,
    isWritable: true,
  },
  // Counter Account
  {
    pubkey: counterPda,
    isSigner: false,
    isWritable: true,
  },
  // Magic Program
  {
    pubkey: MAGIC_PROGRAM_ID,
    isSigner: false,
    isWritable: false,
  },
  // Magic Context
  {
    pubkey: MAGIC_CONTEXT_ID,
    isSigner: false,
    isWritable: true,
  },
];
const serializedInstructionData = Buffer.from(CounterInstruction.Commit, "hex");
const commitIx = new web3.TransactionInstruction({
  keys: keys,
  programId: PROGRAM_ID,
  data: serializedInstructionData,
});
tx.add(commitIx);
const connection = new web3.Connection(rpcMagicblock);
const txHash = await web3.sendAndConfirmTransaction(
  connection,
  tx,
  [userKeypair],
  {
    skipPreflight: true,
    commitment: "confirmed",
  }
);
```

### Test `undelegation` transaction

Create a instruction with the right order and attributes of accounts, and the instruction discriminator for `undelegation` of your program. Send the transaction with the instruction to ER network.

<CardGroup cols={2}>
  <Card title="Transaction (ER): Undelegate" icon="magnifying-glass" href="https://solscan.io/tx/bMN6AhXrGH93Uc6ALibGgjnE39hcnY57mBhYkZ8TRaKxNRvyFaweaQPBmDxPv81cgR47WTTzzhfziTUEgAT8Y5m?cluster=custom&customUrl=https://devnet.magicblock.app" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>

  <Card title="Transaction (Base layer): Undelegate" icon="magnifying-glass" href="https://solscan.io/tx/8JafYWiXmd4CHc2E97WKYnaNPmChZeg8aGYY7UUWaQ7Z54N5WoMcAVivyv2vdn9wKirMkR3y4UcmFPdXYqBtKAa?cluster=devnet" iconType="duotone">
    Inspect transaction details on Solana Explorer
  </Card>
</CardGroup>

```typescript theme={null}
import * as web3 from "@solana/web3.js"
// Build commit and undelegation transaction
const tx = new web3.Transaction();
const keys = [
  // Initializer
  {
  pubkey: userKeypair.publicKey,
  isSigner: true,
  isWritable: true,
  },
  // Counter Account
  {
  pubkey: counterPda,
  isSigner: false,
  isWritable: true,
  },
  // Magic Program
  {
  pubkey: MAGIC_PROGRAM_ID,
  isSigner: false,
  isWritable: false,
  },
  // Magic Context
  {
  pubkey: MAGIC_CONTEXT_ID,
  isSigner: false,
  isWritable: true,
  },
];
const serializedInstructionData = Buffer.from(
  CounterInstruction.CommitAndUndelegate,
  "hex"
);
const undelegateIx = new web3.TransactionInstruction({
  keys: keys,
  programId: PROGRAM_ID,
  data: serializedInstructionData,
});
tx.add(undelegateIx);

// Send and confirm transaction to ER. Afterwards CPI callback will be triggered to "Undelegate" instruction of your program on the Base Layer.
const connection = new web3.Connection(rpcER);
const txHash = await web3.sendAndConfirmTransaction(
connection,
tx,
[userKeypair],
{
skipPreflight: true,
commitment: "confirmed",
}
)

```

### Ephemeral Endpoint Configuration

To interact with the Ephemeral Rollup session, you need to configure the appropriate endpoint:

* For devnet, use the following ephemeral endpoint:

  [https://devnet.magicblock.app](https://devnet.magicblock.app)

* For mainnet, please reach out to the MagicBlock team to receive the appropriate endpoint.

* For <a href="/pages/get-started/how-integrate-your-program/local-setup">localhost</a>, download, install, and run the ephemeral validator locally with the appropriate environment variables.

Make sure to update your client configuration to use the correct endpoint based on your development or production environment.

<Note>
  These public RPC endpoints are currently free and supported for development:
  <br /> Magic Router Devnet: [https://devnet-router.magicblock.app](https://devnet-router.magicblock.app) <br />
  Solana Devnet: [https://api.devnet.solana.com](https://api.devnet.solana.com) <br />
  ER Devnet: [https://devnet.magicblock.app](https://devnet.magicblock.app) <br />
  TEE Devnet: [https://devnet-tee.magicblock.app/](https://devnet-tee.magicblock.app/) <br />
  Find out more details{" "}
  <a href="/pages/ephemeral-rollups-ers/how-to-guide/local-development">here</a>
  .
</Note>

<CardGroup cols={2}>
  <Card title="Source Code: Native Rust Counter Program" icon="rust" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/counter/native-rust" iconType="duotone" />

  <Card title="Source Code: Typescript Test Script" icon="js" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/counter/native-rust/tests" iconType="duotone" />
</CardGroup>

```
```
