From an integration perspective, using Ephemeral Rollups is similar to writing a multi-threaded program. Programs can offload work to an Ephemeral Rollup session to process transactions in a low-latency, high-throughput SVM runtime. The SVM runtime is provisioned just-in-time, and transactions are processed by nodes from the decentralized networks of ephemeral validators.

Ephemeral Rollups can be used with any program that targets the Solana blockchain. We maintain a library of common integrations for:

Lifecycle of the Integration

The lifecycle of integrating Ephemeral Rollups in your program is as follows:

1

Write your program

Write your Solana program as you normally would, using Anchor, native Rust, Bolt, C, or even assembly.

2

Add delegation and undelegation hooks

Accounts that are delegated can be used as writable in an Ephemeral Rollup session.

3

Deploy your program on Solana

Deploy your program directly on Solana.

4

Execute transactions

Execute transactions on-chain or off-chain using any SDKs that complies with the SVM RPC specification (web3.js, Solana Rust SDK, Unity SDK, or others).

These public RPC endpoints are currently free and supported for development:
Smart Router Devnet: https://devnet-rpc.magicblock.app
Solana Devnet: https://api.devnet.solana.com
ER Devnet: https://devnet.magicblock.app

Add delegation and undelegation hooks

Empower your program with ultra-latency transactions by adding delegation and undelegation hooks. Simply add two functions from the native Rust SDK to your program.

Delegation

Delegation is the process of transferring ownership of one or more of your program’s PDAs to the delegation program. Ephemeral Validators will then be able to use the PDAs to perform transactions in the SVM runtime and commit the state.

In Rust programs, you can use the ephemeral_rollups_sdk crate to delegate accounts.

Install it with:

cargo add ephemeral_rollups_sdk

Then use the delegate_account function to delegate an account to the delegation program.

Delegation
use ephemeral_rollups_sdk::cpi::delegate_account;

delegate_account(
    &ctx.accounts.payer, // The account that will pay for opening the
    &ctx.accounts.pda, // The PDA to delegate
    &ctx.accounts.owner_program, // Owner program of the PDA
    ...
    pda_seeds, // Seeds to make the PDA signer
    0, // 0 means no time limit for the delegation
    3_000, // Update frequency on the base layer in milliseconds
)?;

Both delegation and undelegation are CPIs that can be integrated in existing instructions of your program

Undelegation

Undelegation is the process of transferring ownership of the PDAs back to your program. On undelegation, the state is committed and it trigger the finalization process. Once state it validated, the PDAs are unlocked and can be used as normal on mainnet

Delegation
use ephemeral_rollups_sdk::ephem::commit_and_undelegate_accounts;

pub fn undelegate(ctx: Context<IncrementAndCommit>) -> Result<()> {
    commit_and_undelegate_accounts(
        &ctx.accounts.payer,
        vec![&ctx.accounts.pda.to_account_info()],
        &ctx.accounts.magic_context,
        &ctx.accounts.magic_program,
    )?;
    Ok(())
}

Additionally, custom CPI can instruct the ephemeral validators to commit and finalize a state or close a session.

Note that commit and undelegation accept a list of accounts. These accounts are committed atomically to the base layer which allows to maintain state consistency of dependent accounts

Frontend

To make it easier to integrate via the frontend, we created the Magic Router. You send transactions directly to the magic router, and we can determine for you whether it should be routed to the Ephemeral Rollup or base layer.

Differing from the standard Solana RPC flow, we need to first request the blockhash from the Magic Router, since it’ll differ based on route.

Frontend
const cluster = "https://devnet-router.magicblock.app"
const connection = new Connection(cluster, "confirmed");

// Construct a standard Solana transaction
const transaction = await exampleClient.current?.methods
  .exampleMethod(0)
  .accounts({
      user: userPda,
      user: userKeypair.current.publicKey,
  }).transaction() as Transaction;

const noopInstruction = new TransactionInstruction({
      programId: new PublicKey('11111111111111111111111'),
      keys: [],
      data: Buffer.from(crypto.getRandomValues(new Uint8Array(5))),
  });
transaction.add(noopInstruction);

// Get blockhash
const blockhashResponse = await fetch(cluster, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'getBlockhashForAccounts',
      params: ["11111111111111111111111", // writeable accounts
              "11111111111111111111111"] 
  })
});

// Add custom blockhash
transaction.recentBlockhash = blockHashData.result.blockhash;

transaction.feePayer = userKeypair.current.publicKey;
transaction.sign(userKeypair.current);

// Send
const rawTx = transaction.serialize();
const signature = await connection.sendRawTransaction(rawTx);
console.log("✅ tx sig:", signature);

Our current Solana DevNet Magic Router endpoint is: https://devnet-rpc.magicblock.app, however this is subject to change.