> ## Documentation Index
> Fetch the complete documentation index at: https://magicblock-42-dhruvja-docs-navigation-restructure.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Smart Contract Integration

> Custody and move SPL tokens from your own Solana program on an Ephemeral Rollup — PDA-signed transfers, delegation, and commit/undelegate.

***

### Quick Access

<CardGroup cols={3}>
  <Card title="binary-prediction" icon="github" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/binary-prediction" iconType="duotone">
    eATA custody end to end
  </Card>

  <Card title="rewards-delegated-vrf" icon="github" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/rewards-delegated-vrf" iconType="duotone">
    Base-layer post-commit payouts
  </Card>

  <Card title="spl-tokens" icon="github" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/spl-tokens" iconType="duotone">
    Non-custody transfers
  </Card>
</CardGroup>

This guide is for on-chain programs — a DEX, AMM, prediction market, escrow, or game — that hold user
funds and move them at [Ephemeral Rollup](/pages/ephemeral-rollups-ers/introduction/ephemeral-rollup) speed. The core pattern: a program-owned account, delegated to
the ER, with your program signing token transfers as a PDA.

<Note>
  Moving tokens from a client instead of on-chain? See the
  [Quickstart](/pages/ephemeral-spl-token/quickstart).
</Note>

### Two custody models

| Model                              | You delegate…                                | Tokens move…                                   | Use when                                                     |
| ---------------------------------- | -------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------ |
| **eATA custody**                   | a program-owned **ephemeral ATA (eATA)**     | **on the ER**, PDA-signed                      | you settle or pay out inside the rollup (e.g. a market fill) |
| **State PDA + post-commit payout** | only a **state PDA**; real ATAs stay on base | **on the base layer**, as a post-commit action | funds stay on L1 and settle on commit                        |

The walkthrough below covers **eATA custody**, with the post-commit variant in the advanced snippets.

***

## Step-By-Step Guide

Give your program authority over an eATA, delegate it, move funds PDA-signed on the ER, then settle
back to Solana:

<Steps>
  <Step title={<a href="#1-er-aware-program">Make your program ER-aware</a>}>
    Add the `#[ephemeral]` attribute and import the SDK helpers.
  </Step>

  <Step title={<a href="#2-create-eata">Create the custody PDA and eATA</a>}>
    Own an eATA under the Ephemeral SPL Token program from your custody PDA.
  </Step>

  <Step title={<a href="#3-delegate-eata">Delegate the eATA</a>}>
    CPI into the Ephemeral SPL Token program to delegate it to the ER.
  </Step>

  <Step title={<a href="#4-pda-signed-transfer">Move tokens PDA-signed</a>}>
    Sign the transfer as the custody PDA inside the rollup.
  </Step>

  <Step title={<a href="#5-commit-%26-undelegate">Commit & undelegate</a>}>
    Return the account to the base layer.
  </Step>
</Steps>

***

## Custody Example

The following software packages may be required, 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)         |
| **Anchor** | 1.0.2   | [Install Anchor](https://www.anchor-lang.com/docs/installation) |
| **Node**   | 24.10.0 | [Install Node](https://nodejs.org/en/download/current)          |

### Code Snippets

#### 1. ER-aware program

Wrap `#[program]` with `#[ephemeral]` and import the SDK helpers you need.

```rust theme={null}
use ephemeral_rollups_sdk::anchor::{commit, delegate, ephemeral};
use ephemeral_rollups_sdk::cpi::DelegateConfig;
use ephemeral_rollups_sdk::ephem::MagicIntentBundleBuilder;

#[ephemeral]
#[program]
pub mod your_program {
    use super::*;
    // ...
}
```

[⬆️ Back to Top](#code-snippets)

#### 2. Create eATA

Your program owns a state PDA (for example, a `Pool`) that is the authority over the funds. Custody
on the ER uses an **ephemeral ATA (eATA)** owned by that PDA, derived from `[owner, mint]` under the
Ephemeral SPL Token program (`SPLxh1LVZzEkX99H6rqYizhytLWPZVV296zyYDPagv2`).

```rust theme={null}
// The eATA is owned by your custody PDA (e.g. a Pool), derived under the
// Ephemeral SPL Token program from [owner, mint].
let (eata, _bump) = Pubkey::find_program_address(
    &[owner.as_ref(), mint.as_ref()],
    &EPHEMERAL_SPL_TOKEN_PROGRAM_ID,
);

// At initialize, create the eATA (InitializeEphemeralAta) and the per-mint
// global vault that backs every eATA (InitializeGlobalVault), then fund it.
```

[⬆️ Back to Top](#code-snippets)

#### 3. Delegate eATA

Delegate the eATA with a CPI to the Ephemeral SPL Token program (`DelegateEphemeralAta`,
discriminator `4`).

<Warning>
  Delegating a plain PDA-owned ATA directly to the ER is not supported — token custody on the ER
  goes through an eATA. To delegate program *state* (not a token account), use the SDK's
  `#[delegate]` macro instead.
</Warning>

```rust theme={null}
// Delegate the eATA with a CPI to the Ephemeral SPL Token program.
// An optional trailing validator pubkey routes to a specific validator.
let mut data = vec![4]; // DelegateEphemeralAta
if let Some(validator) = validator {
    data.extend_from_slice(validator.as_ref());
}

let instruction = Instruction {
    program_id: EPHEMERAL_SPL_TOKEN_PROGRAM_ID,
    accounts: vec![
        AccountMeta::new(payer.key(), true),
        AccountMeta::new(ephemeral_ata.key(), false),
        AccountMeta::new_readonly(EPHEMERAL_SPL_TOKEN_PROGRAM_ID, false),
        AccountMeta::new(buffer.key(), false),
        AccountMeta::new(record.key(), false),
        AccountMeta::new(metadata.key(), false),
        AccountMeta::new_readonly(delegation_program.key(), false),
        AccountMeta::new_readonly(system_program.key(), false),
    ],
    data,
};
invoke(&instruction, &account_infos)?;
```

[⬆️ Back to Top](#code-snippets)

#### 4. PDA-signed transfer

With the eATA delegated, move tokens inside the rollup with an SPL Token CPI signed by the custody
PDA via `CpiContext::new_with_signer`. The signer seeds are `[POOL_SEED, bump]` — the program
authorizes the payout itself, with no user signature.

```rust theme={null}
use anchor_spl::token::{self, Transfer as SplTransfer};

// The custody PDA signs the transfer — no user signature required.
let bump_seed = [pool_bump];
let signer_seeds: &[&[&[u8]]] = &[&[POOL_SEED, &bump_seed]];

let cpi_accounts = SplTransfer {
    from,
    to,
    authority: pool, // the Pool PDA
};
let cpi_ctx = CpiContext::new_with_signer(token_program.to_account_info(), cpi_accounts, signer_seeds);
token::transfer(cpi_ctx, amount)?;
```

[⬆️ Back to Top](#code-snippets)

#### 5. Commit & undelegate

Commit the ER state and return the account to the base layer with `MagicIntentBundleBuilder`.

```rust theme={null}
// The #[commit] attribute on the accounts context supplies
// magic_context and magic_program.
MagicIntentBundleBuilder::new(
    ctx.accounts.payer.to_account_info(),
    ctx.accounts.magic_context.to_account_info(),
    ctx.accounts.magic_program.to_account_info(),
)
.commit_and_undelegate(&[ctx.accounts.pool.to_account_info()])
.build_and_invoke()?;
```

[⬆️ Back to Top](#code-snippets)

***

### Advanced Code Snippets

<Tabs>
  <Tab title="Non-custody transfer">
    If your program doesn't custody funds — it moves tokens between two already-delegated accounts on a
    user's behalf — no PDA signer is needed. The authority is the user's `Signer`, with a plain
    `CpiContext::new`.

    ```rust theme={null}
    // No PDA signer — the authority is the user's Signer.
    let cpi_accounts = SplTransfer {
        from: ctx.accounts.from.to_account_info(),
        to: ctx.accounts.to.to_account_info(),
        authority: ctx.accounts.payer.to_account_info(),
    };
    token::transfer(CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts), amount)?;
    ```

    [⬆️ Back to Top](#advanced-code-snippets)
  </Tab>

  <Tab title="Base-layer post-commit payout">
    To keep real token accounts on the base layer and delegate only program state, schedule the payout
    as a post-commit action that runs PDA-signed on the base layer right after a commit — using
    `transfer_checked` (Token-2022 compatible) and committing with the action attached.

    ```rust theme={null}
    // Keep real token accounts on base, delegate only program state, and pay out
    // PDA-signed on the base layer right after the commit. Use the PDA AccountInfo
    // as the payer and pass its seeds to build_and_invoke_signed.
    MagicIntentBundleBuilder::new(
        payer_pda.to_account_info(),
        magic_context.to_account_info(),
        magic_program.to_account_info(),
    )
        .magic_fee_vault(magic_fee_vault.to_account_info())
        .commit(&[state_pda.to_account_info()])
        .add_post_commit_actions([action])
        .build_and_invoke_signed(&[payer_seeds])?;
    ```

    [⬆️ Back to Top](#advanced-code-snippets)
  </Tab>
</Tabs>

***

## Solana Explorer

Get insights about your transactions and accounts on Solana:

<CardGroup cols={2}>
  <Card title="Solana Explorer" icon="search" href="https://explorer.solana.com/" iconType="duotone">
    Official Solana Explorer
  </Card>

  <Card title="Solscan" icon="searchengin" href="https://solscan.io/" iconType="duotone">
    Explore Solana Blockchain
  </Card>
</CardGroup>

## Solana RPC Providers

Send transactions and requests through existing RPC providers:

<CardGroup cols={2}>
  <Card title="Solana" icon="star" href="https://solana.com/docs/references/clusters#on-a-high-level" iconType="duotone">
    Free Public Nodes
  </Card>

  <Card title="Helius" icon="sun" href="https://www.helius.dev/solana-rpc-nodes" iconType="duotone">
    Free Shared Nodes
  </Card>

  <Card title="Triton" icon="crystal-ball" href="https://triton.one/solana" iconType="duotone">
    Dedicated High-Performance Nodes
  </Card>
</CardGroup>

## Solana Validator Dashboard

Find real-time updates on Solana's validator infrastructure:

<CardGroup cols={2}>
  <Card title="Solana Beach" icon="wave" href="https://solanabeach.io/" iconType="duotone">
    Get Validator Insights
  </Card>

  <Card title="Validators App" icon="cloud-binary" href="https://www.validators.app/" iconType="duotone">
    Discover Validator Metrics
  </Card>
</CardGroup>

## Server Status

Subscribe to Solana's and MagicBlock's server status:

<CardGroup cols={2}>
  <Card title="Solana Status" icon="server" href="https://status.solana.com/" iconType="duotone">
    Subscribe to Solana Server Updates
  </Card>

  <Card title="MagicBlock Status" icon="heart-pulse" href="/pages/overview/additional-information/system-status" iconType="duotone">
    Subscribe to MagicBlock Server Status
  </Card>
</CardGroup>

***
