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

# Quickstart

> Enable privacy in any Solana program state account through MagicBlock's ER and Trusted Execution Environment on Intel TDX.

***

### Quick Access

Check out private transfer example:

<CardGroup cols={2}>
  <Card title="GitHub" icon="github" href="https://github.com/magicblock-labs/private-payments-demo" iconType="duotone">
    Anchor Implementation
  </Card>

  <Card title="dApp" icon="shield-check" href="https://private-payments.magicblock.app" iconType="duotone">
    Make private transfers
  </Card>
</CardGroup>

***

## Step-By-Step Guide

<Steps>
  <Step title={<a href="#1-write-program">Write your program</a>}>
    Write your Solana program as you normally.
  </Step>

  <Step
    title={
  <a href="#2-restrict">
    Add restrictions through permission hooks in your program.
  </a>
}
  >
    Add CPI hooks that create groups and permissions that enable restrictions
    for specific delegated accounts.
  </Step>

  <Step title={<a href="#3-delegate">Add delegation hooks in your program</a>}>
    Add CPI hooks to delegate, commit and undelegate state accounts through
    Ephemeral Rollup sessions.
  </Step>

  <Step title={<a href="#4-deploy">Deploy your program on Solana</a>}>
    Deploy your Solana program using Anchor CLI.
  </Step>

  <Step title={<a href="#5-authorize">Implement authorization in your client</a>}>
    Sign user message to retrieve authorization token from TEE endpoint.
  </Step>

  <Step title={<a href="#6-test">Execute transactions and test privacy</a>}>
    Request for authorization token and send confidential transactions.
  </Step>
</Steps>

***

## Private Transfer Example

<img
  src="https://mintcdn.com/magicblock-42-crank/A7vpMz52y_yTQtwt/images/gifs/per-short-2x-420w.gif?s=d7d007a611be6ae650cb0209d2f64c35"
  alt="Private Payments GIF"
  style={{
width: "100%",
maxWidth: "420px",
height: "auto",
objectFit: "contain",
borderRadius: "8px",
}}
  width="420"
  height="420"
  data-path="images/gifs/per-short-2x-420w.gif"
/>

The following software packages may be required, other versions may also be compatible:

| Software   | Version | Installation Guide                                              |
| ---------- | ------- | --------------------------------------------------------------- |
| **Solana** | 2.3.13  | [Install Solana](https://docs.anza.xyz/cli/install)             |
| **Rust**   | 1.85.0  | [Install Rust](https://www.rust-lang.org/tools/install)         |
| **Anchor** | 0.32.1  | [Install Anchor](https://www.anchor-lang.com/docs/installation) |
| **Node**   | 24.10.0 | [Install Node](https://nodejs.org/en/download/current)          |

### Code Snippets

<Tabs>
  <Tab title="1. Write program">
    A simple transfer program where tokens are tracked through deposit accounts, and locked in a vault account:

    ```rust theme={null}
    #[program]
    pub mod private_payments {

        /// Initializes a deposit account for a user and token mint if it does not exist.
        ///
        /// Sets up a new deposit account with zero balance for the user and token mint.
        pub fn initialize_deposit(ctx: Context<InitializeDeposit>) -> Result<()> {
            let deposit = &mut ctx.accounts.deposit;
            deposit.set_inner(Deposit {
                user: ctx.accounts.user.key(),
                token_mint: ctx.accounts.token_mint.key(),
                amount: 0,
            });

            Ok(())
        }

        /// Modifies the balance of a user's deposit account by transferring tokens in or out.
        ///
        /// If `args.increase` is true, tokens are transferred from the user's token account to the deposit account.
        /// If false, tokens are transferred from the deposit account back to the user's token account.
        pub fn modify_balance(ctx: Context<ModifyDeposit>, args: ModifyDepositArgs) -> Result<()> {
            let deposit = &mut ctx.accounts.deposit;

            if args.increase {
                transfer_checked(
                    CpiContext::new(
                        ctx.accounts.token_program.to_account_info(),
                        TransferChecked {
                            from: ctx.accounts.user_token_account.to_account_info(),
                            mint: ctx.accounts.token_mint.to_account_info(),
                            to: ctx.accounts.vault_token_account.to_account_info(),
                            authority: ctx.accounts.user.to_account_info(),
                        },
                    ),
                    args.amount,
                    ctx.accounts.token_mint.decimals,
                )?;
                deposit.amount += args.amount;
            } else {
                let seeds = [
                    VAULT_PDA_SEED,
                    &ctx.accounts.token_mint.key().to_bytes(),
                    &[ctx.bumps.vault]
                ];
                let signer_seeds = &[&seeds[..]];
                transfer_checked(
                    CpiContext::new_with_signer(
                        ctx.accounts.token_program.to_account_info(),
                        TransferChecked {
                            from: ctx.accounts.vault_token_account.to_account_info(),
                            mint: ctx.accounts.token_mint.to_account_info(),
                            to: ctx.accounts.user_token_account.to_account_info(),
                            authority: ctx.accounts.vault.to_account_info(),
                        },
                        signer_seeds,
                    ),
                    args.amount,
                    ctx.accounts.token_mint.decimals,
                )?;
                deposit.amount -= args.amount;
            }

            Ok(())
        }

        /// ... Other instructions for delegation and privacy
    }

    /// Context for InitializeDeposit
    #[derive(Accounts)]
    pub struct InitializeDeposit<'info> {
        #[account(mut)]
        pub payer: Signer<'info>,
        /// CHECK: Anyone can initialize the deposit
        pub user: UncheckedAccount<'info>,
        #[account(
            init_if_needed,
            payer = payer,
            space = 8 + Deposit::INIT_SPACE,
            seeds = [DEPOSIT_PDA_SEED, user.key().as_ref(), token_mint.key().as_ref()],
            bump
        )]
        pub deposit: Account<'info, Deposit>,
        pub token_mint: Account<'info, Mint>,
        pub token_program: Program<'info, Token>,
        pub system_program: Program<'info, System>,
    }

    /// Context args for InitializeDeposit
    #[derive(AnchorSerialize, AnchorDeserialize)]
    pub struct ModifyDepositArgs {
        pub amount: u64,
        pub increase: bool,
    }

    /// Context for ModifyDeposit
    #[derive(Accounts)]
    pub struct ModifyDeposit<'info> {
        #[account(mut)]
        pub payer: Signer<'info>,
        pub user: Signer<'info>,
        #[account(
            init_if_needed,
            payer = payer,
            space = 8 + Vault::INIT_SPACE,
            seeds = [VAULT_PDA_SEED, deposit.token_mint.as_ref()],
            bump,
        )]
        pub vault: Account<'info, Vault>,
        #[account(
            mut,
            seeds = [DEPOSIT_PDA_SEED, deposit.user.as_ref(), deposit.token_mint.as_ref()],
            bump,
            has_one = user,
            has_one = token_mint,
        )]
        pub deposit: Account<'info, Deposit>,
        #[account(
            init_if_needed,
            payer = payer,
            associated_token::mint = token_mint,
            associated_token::authority = user,
        )]
        pub user_token_account: Account<'info, TokenAccount>,
        #[account(
            init_if_needed,
            payer = payer,
            associated_token::mint = token_mint,
            associated_token::authority = vault,
        )]
        pub vault_token_account: Account<'info, TokenAccount>,
        pub token_mint: Account<'info, Mint>,
        pub token_program: Program<'info, Token>,
        pub associated_token_program: Program<'info, AssociatedToken>,
        pub system_program: Program<'info, System>,
    }

    /// A deposit account for a user and token mint.
    #[account]
    #[derive(InitSpace)]
    pub struct Deposit {
        pub user: Pubkey,
        pub token_mint: Pubkey,
        pub amount: u64,
    }

    /// A vault storing deposited tokens.
    /// Has a dummy field because Anchor requires it.
    #[account]
    #[derive(InitSpace)]
    pub struct Vault {
        _dummy: u8,
    }

    /// ... Other context and accounts for delegation and privacy

    ```

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

  <Tab title="2. Restrict">
    Define account-level permissions in two steps:

    1. Create user group
    2. Create permissions for state accounts and user group:

    ```rust theme={null}
    #[program]
    pub mod private_payments {

        /// Creates a permission group and permission for a deposit account using the external permission program.
        /// Calls out to the permission program to create a group and permission for the deposit account.
        pub fn create_permission(ctx: Context<CreatePermission>, id: Pubkey) -> Result<()> {
            let CreatePermission {
                payer,
                permission,
                permission_program,
                group,
                deposit,
                user,
                system_program,
            } = ctx.accounts;

            CreateGroupCpiBuilder::new(&permission_program)
                .group(&group)
                .id(id)
                .members(vec![user.key()])
                .payer(&payer)
                .system_program(system_program)
                .invoke()?;

            CreatePermissionCpiBuilder::new(&permission_program)
                .permission(&permission)
                .delegated_account(&deposit.to_account_info())
                .group(&group)
                .payer(&payer)
                .system_program(system_program)
                .invoke_signed(&[&[
                    DEPOSIT_PDA_SEED,
                    user.key().as_ref(),
                    deposit.token_mint.as_ref(),
                    &[ctx.bumps.deposit],
                ]])?;

            Ok(())
        }

        /// ... other instructions
    }

    #[derive(Accounts)]
    pub struct CreatePermission<'info> {
        #[account(mut)]
        pub payer: Signer<'info>,
        /// CHECK: Anyone can create the permission
        pub user: UncheckedAccount<'info>,
        #[account(
            seeds = [DEPOSIT_PDA_SEED, user.key().as_ref(), deposit.token_mint.as_ref()],
            bump
        )]
        pub deposit: Account<'info, Deposit>,
        /// CHECK: Checked by the permission program
        #[account(mut)]
        pub permission: UncheckedAccount<'info>,
        /// CHECK: Checked by the permission program
        #[account(mut)]
        pub group: UncheckedAccount<'info>,
        /// CHECK: Checked by the permission program
        pub permission_program: UncheckedAccount<'info>,
        pub system_program: Program<'info, System>,
    }

    /// Other context and accounts ...
    ```

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

  <Tab title="3. Delegate">
    Enforce privacy of accounts through delegation to validator in TEE:

    ```rust theme={null}
    #[ephemeral] // This adds undelegation instruction for ER validator
    #[program]
    pub mod private_payments {

        /// Delegates the deposit account to the ephemeral rollups delegate program.
        /// Uses the ephemeral rollups delegate CPI to delegate the deposit account.
        pub fn delegate(ctx: Context<DelegateDeposit>, user: Pubkey, token_mint: Pubkey) -> Result<()> {
            let validator = ctx.accounts.validator.as_ref().map(|v| v.key());
            ctx.accounts.delegate_deposit(
                &ctx.accounts.payer,
                &[DEPOSIT_PDA_SEED, user.as_ref(), token_mint.as_ref()],
                DelegateConfig {
                    validator,
                    ..DelegateConfig::default()
                },
            )?;
            Ok(())
        }

        /// Commits and undelegates the deposit account from the ephemeral rollups program.
        /// Uses the ephemeral rollups SDK to commit and undelegate the deposit account.
        /// Use session keys for better UX
        #[session_auth_or(
            ctx.accounts.user.key() == ctx.accounts.source_deposit.user,
            ErrorCode::Unauthorized
        )]
        pub fn undelegate(ctx: Context<UndelegateDeposit>) -> Result<()> {
            commit_and_undelegate_accounts(
                &ctx.accounts.payer,
                vec![&ctx.accounts.deposit.to_account_info()],
                &ctx.accounts.magic_context,
                &ctx.accounts.magic_program,
            )?;
            Ok(())
        }

        /// Transfers a specified amount from one user's deposit account to another's for the same token mint.
        /// Only updates the internal accounting; does not move actual tokens.
        /// Use session keys for better UX
        #[session_auth_or(
            ctx.accounts.user.key() == ctx.accounts.source_deposit.user,
            ErrorCode::Unauthorized
        )]
        pub fn transfer_deposit(ctx: Context<TransferDeposit>, amount: u64) -> Result<()> {
            let source_deposit = &mut ctx.accounts.source_deposit;
            let destination_deposit = &mut ctx.accounts.destination_deposit;

            source_deposit.amount -= amount;
            destination_deposit.amount += amount;

            Ok(())
        }

        /// ... other instructions
    }

    /// Other context and accounts ...

    /// Context for DelegateDeposit
    #[delegate]
    #[derive(Accounts)]
    #[instruction(user: Pubkey, token_mint: Pubkey)]
    pub struct DelegateDeposit<'info> {
        #[account(mut)]
        pub payer: Signer<'info>,
        /// CHECK: Checked by the delegate program
        pub validator: Option<AccountInfo<'info>>,
        /// CHECK: Checked counter accountby the delegate program
        #[account(
            mut,
            del,
            seeds = [DEPOSIT_PDA_SEED, user.as_ref(), token_mint.as_ref()],
            bump,
        )]
        pub deposit: AccountInfo<'info>,
    }

    /// Context for UndelegateDeposit
    #[commit]
    #[derive(Accounts, Session)]
    pub struct UndelegateDeposit<'info> {
        /// CHECK: Matched against the deposit account
        pub user: AccountInfo<'info>,
        #[account(mut)]
        pub payer: Signer<'info>,
        #[session(
            signer = payer,
            authority = user.key()
        )]
        pub session_token: Option<Account<'info, SessionToken>>,
        #[account(
            mut,
            seeds = [DEPOSIT_PDA_SEED, user.key().as_ref(), deposit.token_mint.as_ref()],
            bump
        )]
        pub deposit: Account<'info, Deposit>,
    }


    /// Context for TransferDeposit for ER
    #[derive(Accounts, Session)]
    pub struct TransferDeposit<'info> {
        /// CHECK: Matched against the deposit account
        pub user: AccountInfo<'info>,
        #[account(mut)]
        pub payer: Signer<'info>,
        #[session(
            signer = payer,
            authority = user.key()
        )]
        pub session_token: Option<Account<'info, SessionToken>>,
        #[account(
            mut,
            seeds = [
                DEPOSIT_PDA_SEED,
                source_deposit.user.as_ref(),
                source_deposit.token_mint.as_ref()
            ],
            bump,
            has_one = user,
            has_one = token_mint,
            constraint = source_deposit.user != destination_deposit.user,
        )]
        pub source_deposit: Account<'info, Deposit>,
        #[account(
            mut,
            seeds = [
                DEPOSIT_PDA_SEED,
                destination_deposit.user.as_ref(),
                destination_deposit.token_mint.as_ref()
            ],
            bump,
            has_one = token_mint,
        )]
        pub destination_deposit: Account<'info, Deposit>,
        pub token_mint: Account<'info, Mint>,
        pub system_program: Program<'info, System>,
    }
    ```

    > `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.

    > `Commit` is the process of updating the state of the `PDAs` from ER to the base layer. After the finalization process, the `PDAs` remain locked on base layer.

    > `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 base layer.

    Specify your preferred delegation config such as auto commits and specific ER validator:

    <Note>
      <p>
        These public validators are supported for development. Make sure to add the
        specific ER validator account when delegating:
      </p>

      <ul>
        <li>
          Asia (devnet-as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (devnet-eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          US (devnet-us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (tee.magicblock.app):{" "}
          <code>FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA</code>
        </li>

        <li>
          Local ER (localhost):{" "}
          <code>mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev</code>
        </li>
      </ul>
    </Note>

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

  <Tab title="4. Deploy">
    Now you’re program is upgraded and ready! Build and deploy to the desired cluster:

    ```bash theme={null}
      anchor build && anchor deploy
    ```

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

  <Tab title="5. Authorize">
    Set up interaction with ER RPC in TEE:

    1. Verify integrity of TEE RPC via `https://pccs.phala.network/tdx/certification/v4`
    2. Request an authorization token for user to interact with TEE endpoint

    ```typescript web3js theme={null}
    import {
      verifyTeeRpcIntegrity,
      getAuthToken,
    } from "@magicblock-labs/ephemeral-rollups-sdk";

    // Verify the integrity of TEE RPC
    const isVerified = await verifyTeeRpcIntegrity(EPHEMERAL_RPC_URL);

    // Get AuthToken before making request to TEE
    const token = await getAuthToken(
      EPHEMERAL_RPC_URL,
      wallet.publicKey,
      (message: Uint8Array) =>
        Promise.resolve(nacl.sign.detached(message, wallet.secretKey))
    );
    ```

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

  <Tab title="6. Test">
    Test private payments program using the authToken:

    `https://tee.magicblock.app?token=${token}`

    1. Initialize user deposit account (also for recipient)
    2. Deposit tokens
    3. Set permission for deposit
    4. Delegate deposit
    5. Transfer privately between delegated deposits
    6. Undelegate deposit to Solana
    7. Withdraw deposit

    ```typescript web3js theme={null}
    import {
      DELEGATION_PROGRAM_ID,
      PERMISSION_PROGRAM_ID,
      permissionPdaFromAccount,
      groupPdaFromId,
      waitUntilPermissionActive,
      GetCommitmentSignature,
    } from "@magicblock-labs/ephemeral-rollups-sdk";

    // [1] Initialize deposit account for user (also required for receiver)
    const deposit = PublicKey.findProgramAddressSync(
      [Buffer.from(DEPOSIT_PDA_SEED), user.toBuffer(), tokenMint.toBuffer()],
      program.programId
    )[0];
    const initHash = await program.methods
      .initializeDeposit()
      .accountsPartial({
        payer: program.provider.publicKey,
        user,
        deposit,
        tokenMint,
        tokenProgram: TOKEN_PROGRAM_ID,
      })
      .rpc();

    // [2] Add tokens to deposit by using a central vault (WIP with Token Program)
    // User deposits tokens into vault and receives deposit PDA receipt / updates
    const vault = PublicKey.findProgramAddressSync(
      [Buffer.from(VAULT_PDA_SEED), tokenMint.toBuffer()],
      program.programId
    )[0];
    const depositHash = await program.methods
      .modifyBalance({ amount: tokenAmount, increase: true })
      .accountsPartial({
        payer: program.provider.publicKey,
        user: wallet.publicKey,
        vault: vault,
        deposit: deposit,
        userTokenAccount: getAssociatedTokenAddressSync(
          tokenMint,
          wallet.publicKey,
          true,
          TOKEN_PROGRAM_ID
        ),
        vaultTokenAccount: getAssociatedTokenAddressSync(
          tokenMint,
          vault,
          true,
          TOKEN_PROGRAM_ID
        ),
        tokenMint,
        tokenProgram: TOKEN_PROGRAM_ID,
      })
      .rpc();

    // [3] Set permission for deposit account with required PDAs, and wait until permission is active
    const id = Keypair.generate().publicKey;
    const permission = permissionPdaFromAccount(deposit);
    const group = groupPdaFromId(id);
    const createPermissionHash = await program.methods
      .createPermission(id)
      .accountsPartial({
        payer: program.provider.publicKey,
        user,
        deposit,
        permission,
        group,
        permissionProgram: PERMISSION_PROGRAM_ID,
      })
      .rpc();
    await waitUntilPermissionActive(ephemeralRpcEndpoint, deposit);

    // [4] Delegate deposit account to TEE validator, and enforce privacy
    const teeValidator = new PublicKey(
      FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA
    );
    const delegateHash = await program.methods
      .delegate(user, tokenMint)
      .accountsPartial({
        payer: program.provider.publicKey,
        teeValidator,
        deposit,
      })
      .rpc();

    // [5] Transfer deposit amounts privately on ER on TEE
    // User transfers tokens by updating deposit accounts (initialized destination deposit is required)
    const sourceDeposit = PublicKey.findProgramAddressSync(
      [Buffer.from(DEPOSIT_PDA_SEED), user.toBuffer(), tokenMint.toBuffer()],
      program.programId
    )[0];
    const destinationDeposit = PublicKey.findProgramAddressSync(
      [Buffer.from(DEPOSIT_PDA_SEED), receiver.toBuffer(), tokenMint.toBuffer()],
      program.programId
    )[0];
    const ephemTransferHash = await programEphemeralRpcEndpointWithAuthToken.methods
      .transferDeposit(new BN(amount * Math.pow(10, 6)))
      .accountsPartial({
        sessionToken: null,
        payer: program.provider.publicKey,
        user: program.provider.publicKey,
        sourceDeposit,
        destinationDeposit,
        tokenMint,
      })
      .rpc();

    // [6] Undelegate deposit account, and wait for commitment on Solana
    const ephemUndelegateHash =
      await programEphemeralRpcEndpointWithAuthToken.methods
        .undelegate()
        .accountsPartial({
          sessionToken,
          payer: sessionKp.publicKey,
          user: wallet.publicKey,
          deposit: senderDepositPda,
        })
        .rpc();
    const txBase = await GetCommitmentSignature(
      ephemUndelegateHash,
      ephemeralRpcEndpointWithAuthToken
    );

    // [7] Withdraw tokens from central vault with deposit account
    // User claims tokens from vault by updating deposit PDA
    const vault = PublicKey.findProgramAddressSync(
      [Buffer.from(VAULT_PDA_SEED), tokenMint.toBuffer()],
      program.programId
    )[0];
    const withdrawHash = await program.methods
      .modifyBalance({ amount: tokenAmount, increase: false })
      .accountsPartial({
        payer: program.provider.publicKey,
        user: wallet.publicKey,
        vault: vault,
        deposit: deposit,
        userTokenAccount: getAssociatedTokenAddressSync(
          tokenMint,
          wallet.publicKey,
          true,
          TOKEN_PROGRAM_ID
        ),
        vaultTokenAccount: getAssociatedTokenAddressSync(
          tokenMint,
          vault,
          true,
          TOKEN_PROGRAM_ID
        ),
        tokenMint,
        tokenProgram: TOKEN_PROGRAM_ID,
      })
      .rpc();
    ```

    <Note>
      <p>
        These public validators are supported for development. Make sure to add the
        specific ER validator account when delegating:
      </p>

      <ul>
        <li>
          Asia (devnet-as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (devnet-eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          US (devnet-us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (tee.magicblock.app):{" "}
          <code>FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA</code>
        </li>

        <li>
          Local ER (localhost):{" "}
          <code>mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev</code>
        </li>
      </ul>
    </Note>

    ### Quick Access

    Check out private transfer example:

    <CardGroup cols={2}>
      <Card title="GitHub" icon="github" href="https://github.com/magicblock-labs/private-payments-demo" iconType="duotone">
        Anchor Implementation
      </Card>

      <Card title="dApp" icon="shield-check" href="https://private-payments.magicblock.app" iconType="duotone">
        Make private transfers
      </Card>
    </CardGroup>

    [⬆️ Back to Top](#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/server-status" iconType="duotone">
    Subscribe to MagicBlock Server Status
  </Card>
</CardGroup>

***

## MagicBlock Products

<CardGroup cols={2}>
  <Card title="Ephemeral Rollup (ER)" icon="bolt" href="/pages/ephemeral-rollups-ers/how-to-guide/quickstart" iconType="duotone">
    Execute real-time, zero-fee transactions securely on Solana.
  </Card>

  <Card title="Private Ephemeral Rollup (PER)" icon="shield-check" href="/pages/private-ephemeral-rollups-pers/how-to-guide/quickstart" iconType="duotone">
    Protect sensitive data with privacy-preserving computation.
  </Card>

  <Card title="Verifiable Randomness Function (VRF)" icon="dice" href="/pages/verifiable-randomness-functions-vrfs/how-to-guide/quickstart" iconType="duotone">
    Generate provably fair randomness directly on-chain.
  </Card>
</CardGroup>

***
