Comparing base with magicblock

Author

@0xinit

Stars

53

Repository

0xinit/cryptoskills

skills/base/SKILL.md

Base L2 Development

Base is Coinbase's L2 built on Optimism's OP Stack. It shares Ethereum security, uses ETH as gas, and settles to L1. If you can deploy to Ethereum, you can deploy to Base with zero code changes — just point at a different RPC.

What You Probably Got Wrong

  • Base is NOT a separate EVM — It is an OP Stack rollup. Same opcodes, same Solidity compiler, same tooling. There is nothing "Base-specific" about contract development. If your contract works on Ethereum or Optimism, it works on Base.
  • Gas has two components — L2 execution gas (cheap, ~0.001 gwei base fee) plus L1 data fee (variable, depends on Ethereum blob fees). The L1 data fee is the dominant cost for most transactions. After EIP-4844 (Dencun), L1 data costs dropped ~100x.
  • msg.sender works differently with Smart Wallet — Coinbase Smart Wallet is a smart contract account (ERC-4337). msg.sender is the smart wallet address, not the EOA. If your contract checks tx.origin == msg.sender to block contracts, it will block Smart Wallet users.
  • OnchainKit is NOT a wallet library — It is a React component library for building Base-native UIs. It handles identity resolution, transaction submission, token swaps, and Farcaster frames. You still need wagmi/viem for the underlying wallet connection.
  • Paymaster does NOT mean free transactions — A paymaster sponsors gas on behalf of users. Someone still pays. You configure a Coinbase Developer Platform paymaster policy that defines which contracts and methods are sponsored, with spend limits.
  • Block time is 2 seconds — Not 12 seconds like Ethereum L1. Plan your polling and confirmation logic accordingly.
  • Basescan and Blockscout are both available — Basescan (Etherscan-based) is the primary explorer. Blockscout is the alternative. Contract verification works on both but uses different APIs.

Quick Start

# Add Base to Foundry project
forge create src/Counter.sol:Counter \
  --rpc-url https://mainnet.base.org \
  --private-key $PRIVATE_KEY

# Check balance
cast balance $ADDRESS --rpc-url https://mainnet.base.org

# Deploy to testnet
forge script script/Deploy.s.sol \
  --rpc-url https://sepolia.base.org \
  --broadcast --verify \
  --etherscan-api-key $BASESCAN_API_KEY
import { createPublicClient, createWalletClient, http } from 'viem';
import { base, baseSepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

const walletClient = createWalletClient({
  chain: base,
  transport: http(),
  account: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`),
});

const blockNumber = await publicClient.getBlockNumber();

Chain Configuration

Base Mainnet

ParameterValue
Chain ID8453
CurrencyETH
RPC (public)https://mainnet.base.org
RPC (Coinbase)https://api.developer.coinbase.com/rpc/v1/base/$CDP_API_KEY
WebSocketwss://mainnet.base.org
Explorerhttps://basescan.org
Blockscouthttps://base.blockscout.com
Bridgehttps://bridge.base.org
Block time2 seconds
Finality~12 minutes (L1 finality)

Base Sepolia (Testnet)

ParameterValue
Chain ID84532
CurrencyETH
RPC (public)https://sepolia.base.org
RPC (Coinbase)https://api.developer.coinbase.com/rpc/v1/base-sepolia/$CDP_API_KEY
Explorerhttps://sepolia.basescan.org
Faucethttps://www.coinbase.com/faucets/base-ethereum-goerli-faucet

Viem Chain Definitions

viem includes Base chains out of the box:

import { base, baseSepolia } from 'viem/chains';

// base.id === 8453
// baseSepolia.id === 84532

Hardhat Network Config

// hardhat.config.ts
const config: HardhatUserConfig = {
  networks: {
    base: {
      url: process.env.BASE_RPC_URL || 'https://mainnet.base.org',
      accounts: [process.env.PRIVATE_KEY!],
      chainId: 8453,
    },
    baseSepolia: {
      url: process.env.BASE_SEPOLIA_RPC_URL || 'https://sepolia.base.org',
      accounts: [process.env.PRIVATE_KEY!],
      chainId: 84532,
    },
  },
  etherscan: {
    apiKey: {
      base: process.env.BASESCAN_API_KEY!,
      baseSepolia: process.env.BASESCAN_API_KEY!,
    },
    customChains: [
      {
        network: 'base',
        chainId: 8453,
        urls: {
          apiURL: 'https://api.basescan.org/api',
          browserURL: 'https://basescan.org',
        },
      },
      {
        network: 'baseSepolia',
        chainId: 84532,
        urls: {
          apiURL: 'https://api-sepolia.basescan.org/api',
          browserURL: 'https://sepolia.basescan.org',
        },
      },
    ],
  },
};

Foundry Config

# foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

[rpc_endpoints]
base = "https://mainnet.base.org"
base_sepolia = "https://sepolia.base.org"

[etherscan]
base = { key = "${BASESCAN_API_KEY}", url = "https://api.basescan.org/api", chain = 8453 }
base_sepolia = { key = "${BASESCAN_API_KEY}", url = "https://api-sepolia.basescan.org/api", chain = 84532 }

Deployment

Base uses the OP Stack. Deployment is identical to Ethereum — no special compiler flags, no custom precompiles for basic use cases.

Foundry

# Deploy to Base Sepolia
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url https://sepolia.base.org \
  --broadcast \
  --verify \
  --etherscan-api-key $BASESCAN_API_KEY

# Deploy to Base Mainnet
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url https://mainnet.base.org \
  --broadcast \
  --verify \
  --etherscan-api-key $BASESCAN_API_KEY \
  --slow

Verification on Basescan

# Verify after deployment
forge verify-contract $CONTRACT_ADDRESS src/MyContract.sol:MyContract \
  --chain base \
  --etherscan-api-key $BASESCAN_API_KEY

# With constructor args
forge verify-contract $CONTRACT_ADDRESS src/MyContract.sol:MyContract \
  --chain base \
  --etherscan-api-key $BASESCAN_API_KEY \
  --constructor-args $(cast abi-encode "constructor(address,uint256)" $TOKEN_ADDR 1000)

# Verify on Blockscout (no API key needed)
forge verify-contract $CONTRACT_ADDRESS src/MyContract.sol:MyContract \
  --chain base \
  --verifier blockscout \
  --verifier-url https://base.blockscout.com/api/

OnchainKit

OnchainKit is Coinbase's React component library for building onchain apps on Base. It provides ready-made components for identity, transactions, swaps, wallets, and Farcaster frames.

Installation

npm install @coinbase/onchainkit
# Peer dependencies
npm install viem wagmi @tanstack/react-query

Provider Setup

import { OnchainKitProvider } from '@coinbase/onchainkit';
import { base } from 'viem/chains';

function App({ children }: { children: React.ReactNode }) {
  return (
    <OnchainKitProvider
      apiKey={process.env.NEXT_PUBLIC_CDP_API_KEY}
      chain={base}
    >
      {children}
    </OnchainKitProvider>
  );
}

Identity Components

Resolve onchain identity (ENS, Basenames, Farcaster):

import {
  Identity,
  Name,
  Avatar,
  Badge,
  Address,
} from '@coinbase/onchainkit/identity';

function UserProfile({ address }: { address: `0x${string}` }) {
  return (
    <Identity address={address} schemaId="0x...">
      <Avatar />
      <Name>
        <Badge />
      </Name>
      <Address />
    </Identity>
  );
}

Transaction Component

Submit transactions with built-in status tracking:

import { Transaction, TransactionButton, TransactionStatus } from '@coinbase/onchainkit/transaction';
import { baseSepolia } from 'viem/chains';

const contracts = [
  {
    address: '0xContractAddress' as `0x${string}`,
    abi: contractAbi,
    functionName: 'mint',
    args: [1n],
  },
];

function MintButton() {
  return (
    <Transaction
      chainId={baseSepolia.id}
      contracts={contracts}
      onStatus={(status) => console.log('tx status:', status)}
    >
      <TransactionButton text="Mint NFT" />
      <TransactionStatus />
    </Transaction>
  );
}

Swap Component

Token swaps powered by 0x/Uniswap:

import { Swap, SwapAmountInput, SwapButton, SwapToggle } from '@coinbase/onchainkit/swap';
import type { Token } from '@coinbase/onchainkit/token';

const ETH: Token = {
  name: 'Ethereum',
  address: '',
  symbol: 'ETH',
  decimals: 18,
  image: 'https://...',
  chainId: 8453,
};

const USDC: Token = {
  name: 'USD Coin',
  address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  symbol: 'USDC',
  decimals: 6,
  image: 'https://...',
  chainId: 8453,
};

function SwapWidget() {
  return (
    <Swap>
      <SwapAmountInput label="Sell" token={ETH} type="from" />
      <SwapToggle />
      <SwapAmountInput label="Buy" token={USDC} type="to" />
      <SwapButton />
    </Swap>
  );
}

Wallet Component

import {
  ConnectWallet,
  Wallet,
  WalletDropdown,
  WalletDropdownDisconnect,
} from '@coinbase/onchainkit/wallet';

function WalletConnect() {
  return (
    <Wallet>
      <ConnectWallet>
        <Avatar />
        <Name />
      </ConnectWallet>
      <WalletDropdown>
        <Identity />
        <WalletDropdownDisconnect />
      </WalletDropdown>
    </Wallet>
  );
}

Smart Wallet

Coinbase Smart Wallet is a smart contract wallet using passkeys (WebAuthn) for authentication. It follows ERC-4337 (account abstraction) so users do not need a seed phrase or browser extension.

How It Works

  1. User creates wallet via passkey (fingerprint, Face ID, or security key)
  2. A smart contract wallet is deployed on Base (counterfactual — deployed on first transaction)
  3. Transactions are signed with the passkey and submitted as UserOperations
  4. Compatible with ERC-4337 bundlers and paymasters

wagmi + Smart Wallet

import { http, createConfig } from 'wagmi';
import { base, baseSepolia } from 'wagmi/chains';
import { coinbaseWallet } from 'wagmi/connectors';

export const config = createConfig({
  chains: [base, baseSepolia],
  connectors: [
    coinbaseWallet({
      appName: 'My Base App',
      // preference: 'smartWalletOnly' forces smart wallet (no extension)
      preference: 'smartWalletOnly',
    }),
  ],
  transports: {
    [base.id]: http(),
    [baseSepolia.id]: http(),
  },
});

Sending Transactions from Smart Wallet

import { useWriteContract } from 'wagmi';

function MintWithSmartWallet() {
  const { writeContract } = useWriteContract();

  const handleMint = () => {
    writeContract({
      address: '0xContractAddress',
      abi: contractAbi,
      functionName: 'mint',
      args: [1n],
    });
  };

  return <button onClick={handleMint}>Mint</button>;
}

Batch Transactions (EIP-5792)

Smart Wallet supports wallet_sendCalls for atomic batching:

import { useWriteContracts } from 'wagmi/experimental';

function BatchMint() {
  const { writeContracts } = useWriteContracts();

  const handleBatch = () => {
    writeContracts({
      contracts: [
        {
          address: '0xTokenAddress',
          abi: erc20Abi,
          functionName: 'approve',
          args: ['0xSpender', 1000000n],
        },
        {
          address: '0xSpender',
          abi: spenderAbi,
          functionName: 'deposit',
          args: [1000000n],
        },
      ],
    });
  };

  return <button onClick={handleBatch}>Approve + Deposit</button>;
}

Paymaster (Gasless Transactions)

A paymaster sponsors gas fees so users pay zero gas. Coinbase Developer Platform provides a paymaster service for Base.

Setup

  1. Create a project at portal.cdp.coinbase.com
  2. Enable the Paymaster service
  3. Configure a paymaster policy (which contracts/methods to sponsor)
  4. Get your paymaster URL: https://api.developer.coinbase.com/rpc/v1/base/$CDP_API_KEY

wagmi + Paymaster (EIP-5792)

import { useWriteContracts } from 'wagmi/experimental';
import { base } from 'viem/chains';

const PAYMASTER_URL = `https://api.developer.coinbase.com/rpc/v1/base/${process.env.NEXT_PUBLIC_CDP_API_KEY}`;

function SponsoredMint() {
  const { writeContracts } = useWriteContracts();

  const handleMint = () => {
    writeContracts({
      contracts: [
        {
          address: '0xContractAddress',
          abi: contractAbi,
          functionName: 'mint',
          args: [1n],
        },
      ],
      capabilities: {
        paymasterService: {
          url: PAYMASTER_URL,
        },
      },
    });
  };

  return <button onClick={handleMint}>Mint (Gasless)</button>;
}

Viem + Paymaster (Direct UserOperation)

import { createPublicClient, http, encodeFunctionData } from 'viem';
import { base } from 'viem/chains';

const PAYMASTER_URL = `https://api.developer.coinbase.com/rpc/v1/base/${process.env.CDP_API_KEY}`;

async function sponsoredCall(
  smartWalletAddress: `0x${string}`,
  target: `0x${string}`,
  calldata: `0x${string}`
) {
  const client = createPublicClient({
    chain: base,
    transport: http(),
  });

  // pm_getPaymasterStubData returns gas estimates for the UserOp
  const paymasterResponse = await fetch(PAYMASTER_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'pm_getPaymasterStubData',
      params: [
        {
          sender: smartWalletAddress,
          callData: calldata,
          callGasLimit: '0x0',
          verificationGasLimit: '0x0',
          preVerificationGas: '0x0',
          maxFeePerGas: '0x0',
          maxPriorityFeePerGas: '0x0',
        },
        // EntryPoint v0.7
        '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
        `0x${base.id.toString(16)}`,
      ],
    }),
  });

  return paymasterResponse.json();
}

Paymaster Policy

Configure which transactions are sponsored in the Coinbase Developer Platform dashboard:

  • Contract allowlist — Only sponsor calls to specific contract addresses
  • Method allowlist — Only sponsor specific function selectors
  • Spend limits — Max gas per user per day/week/month
  • Rate limits — Max sponsored txs per user per time window

Bridging

Base uses the OP Stack standard bridge. ETH and ERC-20 tokens can be bridged between Ethereum L1 and Base L2.

Bridge ETH to Base (L1 -> L2)

import { createWalletClient, http, parseEther } from 'viem';
import { mainnet } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

// OptimismPortal on L1 (Ethereum mainnet)
const OPTIMISM_PORTAL = '0x49048044D57e1C92A77f79988d21Fa8fAF36f97B' as const;

const walletClient = createWalletClient({
  chain: mainnet,
  transport: http(),
  account: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`),
});

// Deposit ETH to Base — sends to OptimismPortal with value
const hash = await walletClient.sendTransaction({
  to: OPTIMISM_PORTAL,
  value: parseEther('0.1'),
  // depositTransaction calldata for simple ETH transfer
  data: '0x',
});
// ETH arrives on Base in ~1-5 minutes

Bridge ETH from Base (L2 -> L1)

L2-to-L1 withdrawals follow the OP Stack withdrawal flow:

  1. Initiate withdrawal on L2 (sends message to L2ToL1MessagePasser)
  2. Wait for state root to be posted on L1 (~1 hour)
  3. Prove withdrawal on L1 (submit Merkle proof)
  4. Wait for challenge period (~7 days on mainnet)
  5. Finalize withdrawal on L1
import { createPublicClient, createWalletClient, http, parseEther } from 'viem';
import { base } from 'viem/chains';

// L2ToL1MessagePasser predeploy on Base
const L2_TO_L1_MESSAGE_PASSER = '0x4200000000000000000000000000000000000016' as const;

const l2WalletClient = createWalletClient({
  chain: base,
  transport: http(),
  account: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`),
});

// Step 1: Initiate withdrawal on L2
const hash = await l2WalletClient.writeContract({
  address: L2_TO_L1_MESSAGE_PASSER,
  abi: [
    {
      name: 'initiateWithdrawal',
      type: 'function',
      stateMutability: 'payable',
      inputs: [
        { name: '_target', type: 'address' },
        { name: '_gasLimit', type: 'uint256' },
        { name: '_data', type: 'bytes' },
      ],
      outputs: [],
    },
  ],
  functionName: 'initiateWithdrawal',
  args: [
    l2WalletClient.account.address, // send to self on L1
    100_000n, // L1 gas limit for the relay
    '0x',
  ],
  value: parseEther('0.1'),
});
// Steps 2-4 require waiting and L1 transactions (use the Base Bridge UI or OP SDK)

Gas Model

Base follows the OP Stack gas model: L2 execution fee + L1 data fee.

L2 Execution Fee

Same as Ethereum: gasUsed * baseFee. Base uses EIP-1559 with a 2-second block time. Base fees are typically very low (0.001-0.01 gwei).

L1 Data Fee

Every L2 transaction is posted to Ethereum L1 as calldata or blobs. The L1 data fee covers this cost.

After EIP-4844 (Dencun upgrade, March 2024), L1 data is posted as blobs, reducing costs ~100x.

import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

// GasPriceOracle predeploy
const GAS_PRICE_ORACLE = '0x420000000000000000000000000000000000000F' as const;

const gasPriceOracleAbi = [
  {
    name: 'getL1Fee',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: '_data', type: 'bytes' }],
    outputs: [{ name: '', type: 'uint256' }],
  },
  {
    name: 'baseFeeScalar',
    type: 'function',
    stateMutability: 'view',
    inputs: [],
    outputs: [{ name: '', type: 'uint32' }],
  },
  {
    name: 'blobBaseFeeScalar',
    type: 'function',
    stateMutability: 'view',
    inputs: [],
    outputs: [{ name: '', type: 'uint32' }],
  },
] as const;

const client = createPublicClient({ chain: base, transport: http() });

// Estimate L1 data fee for a transaction
const l1Fee = await client.readContract({
  address: GAS_PRICE_ORACLE,
  abi: gasPriceOracleAbi,
  functionName: 'getL1Fee',
  args: ['0x...serializedTxData'],
});

Gas Estimation Tips

  • Total cost = L2 execution fee + L1 data fee
  • L1 data fee dominates for simple transactions
  • Minimize calldata size to reduce L1 costs (pack structs, use shorter data)
  • eth_estimateGas returns L2 gas only — add L1 data fee separately
  • viem's estimateTotalFee on OP Stack chains handles both components

Ecosystem

Major Base Protocols

ProtocolCategoryDescription
AerodromeDEXDominant DEX on Base (ve(3,3) model, Velodrome fork)
Uniswap V3DEXDeployed on Base with full feature set
Aave V3LendingLending/borrowing on Base
Compound IIILendingUSDC lending market
Extra FinanceYieldLeveraged yield farming
MoonwellLendingFork of Compound, native to Base
SeamlessLendingIntegrated lending protocol
BaseSwapDEXNative Base DEX
MorphoLendingPermissionless lending vaults

Key Token Addresses (Base Mainnet)

TokenAddress
WETH0x4200000000000000000000000000000000000006
USDC (native)0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
USDbC (bridged)0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6D
cbETH0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22
DAI0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb
AERO0x940181a94A35A4569E4529A3CDfB74e38FD98631

Last verified: 2025-04. Always verify onchain with cast code <address> --rpc-url https://mainnet.base.org before production use.

Key Differences from Other OP Stack Chains

AspectBaseOptimismOther OP Chains
Sequencer operatorCoinbaseOptimism FoundationVaries
Fee recipientCoinbase multisigOptimism FoundationVaries
Smart WalletNative Coinbase integrationNot defaultNot default
PaymasterCoinbase Developer PlatformThird-party onlyThird-party only
OnchainKitFull supportPartial (identity only)None
Superchain membershipYesYes (leader)Varies
Fault proofsStage 1 (planned)Stage 1Varies

Useful Commands

# Check L1 data fee for a tx
cast call 0x420000000000000000000000000000000000000F \
  "getL1Fee(bytes)(uint256)" 0x... \
  --rpc-url https://mainnet.base.org

# Read GasPriceOracle scalars
cast call 0x420000000000000000000000000000000000000F \
  "baseFeeScalar()(uint32)" \
  --rpc-url https://mainnet.base.org

# Check if address is a contract (smart wallet check)
cast code $ADDRESS --rpc-url https://mainnet.base.org

# Get current L2 base fee
cast base-fee --rpc-url https://mainnet.base.org

# Decode L1 batch data
cast call 0x4200000000000000000000000000000000000015 \
  "l1BlockNumber()(uint64)" \
  --rpc-url https://mainnet.base.org

References

magicblock

View full →

Author

@0xinit

Stars

53

Repository

0xinit/cryptoskills

skills/magicblock/SKILL.md

MagicBlock Ephemeral Rollups Guide

A comprehensive guide for building high-performance Solana applications with MagicBlock Ephemeral Rollups - enabling sub-10ms latency and gasless transactions.

Overview

MagicBlock Ephemeral Rollups (ER) are specialized SVM runtimes that enhance Solana with:

  • Sub-10ms latency (vs ~400ms on base Solana)
  • Gasless transactions for seamless UX
  • Full composability with existing Solana programs
  • Horizontal scaling via on-demand rollups

Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Your Application                        │
├─────────────────────────────────────────────────────────────┤
│  Base Layer (Solana)          │  Ephemeral Rollup (ER)      │
│  - Initialize accounts        │  - Execute operations       │
│  - Delegate accounts          │  - Process at ~10-50ms      │
│  - Final state commits        │  - Zero gas fees            │
│  - ~400ms finality            │  - Commit state to Solana   │
└─────────────────────────────────────────────────────────────┘

Core Flow

  1. Initialize - Create accounts on Solana base layer
  2. Delegate - Transfer account ownership to delegation program
  3. Execute - Run fast operations on Ephemeral Rollup
  4. Commit - Sync state back to base layer
  5. Undelegate - Return ownership to your program

Prerequisites

# Required versions
Solana: 2.3.13
Rust: 1.85.0
Anchor: 0.32.1
Node: 24.10.0

# Install Anchor (if needed)
cargo install --git https://github.com/coral-xyz/anchor anchor-cli

Quick Start

1. Add Dependencies (Cargo.toml)

[dependencies]
anchor-lang = "0.32.1"
ephemeral-rollups-sdk = { version = "0.6.5", features = ["anchor", "disable-realloc"] }

2. Program Setup (lib.rs)

use anchor_lang::prelude::*;
use ephemeral_rollups_sdk::anchor::{delegate_account, commit_accounts, ephemeral};
use ephemeral_rollups_sdk::cpi::DelegationProgram;

declare_id!("YourProgramId111111111111111111111111111111");

#[ephemeral]  // Required: enables ER support
#[program]
pub mod my_program {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        ctx.accounts.state.value = 0;
        Ok(())
    }

    #[delegate]  // Auto-injects delegation accounts
    pub fn delegate(ctx: Context<Delegate>) -> Result<()> {
        Ok(())
    }

    pub fn increment(ctx: Context<Update>) -> Result<()> {
        ctx.accounts.state.value += 1;
        Ok(())
    }

    #[commit]  // Auto-injects commit accounts
    pub fn undelegate(ctx: Context<Undelegate>) -> Result<()> {
        Ok(())
    }
}

3. TypeScript Client Setup

import { Connection, PublicKey } from "@solana/web3.js";
import { AnchorProvider, Program } from "@coral-xyz/anchor";
import { DELEGATION_PROGRAM_ID } from "@magicblock-labs/ephemeral-rollups-sdk";

// CRITICAL: Separate connections for each layer
const baseConnection = new Connection("https://api.devnet.solana.com");
const erConnection = new Connection("https://devnet.magicblock.app");

// Create providers
const baseProvider = new AnchorProvider(baseConnection, wallet, { commitment: "confirmed" });
const erProvider = new AnchorProvider(erConnection, wallet, {
  commitment: "confirmed",
  skipPreflight: true,  // Required for ER
});

// Check delegation status
async function isDelegated(pubkey: PublicKey): Promise<boolean> {
  const info = await baseConnection.getAccountInfo(pubkey);
  return info?.owner.equals(DELEGATION_PROGRAM_ID) ?? false;
}

Key Concepts

Delegation

Delegation transfers PDA ownership to the delegation program, allowing the Ephemeral Validator to process transactions.

#[derive(Accounts)]
pub struct Delegate<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    /// CHECK: Will be delegated
    #[account(mut, del)]  // 'del' marks for delegation
    pub state: AccountInfo<'info>,
    pub delegation_program: Program<'info, DelegationProgram>,
}

Commit

Commits update PDA state from ER to base layer without undelegating.

use ephemeral_rollups_sdk::anchor::commit_accounts;

pub fn commit(ctx: Context<Commit>) -> Result<()> {
    commit_accounts(
        &ctx.accounts.payer,
        vec![&ctx.accounts.state.to_account_info()],
        &ctx.accounts.magic_context,
        &ctx.accounts.magic_program,
    )?;
    Ok(())
}

Undelegation

Returns PDA ownership to your program while committing final state.

#[commit]  // Handles commit + undelegate
pub fn undelegate(ctx: Context<Undelegate>) -> Result<()> {
    Ok(())
}

ER Validators (Devnet)

RegionValidator Identity
AsiaMAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
EUMEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
USMUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
TEEFnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA

Magic Router (auto-selects best): https://devnet-router.magicblock.app

Critical Rules

DO:

  • Maintain separate connections for base layer and ER
  • Use skipPreflight: true for all ER transactions
  • Verify delegation status before sending to ER
  • Use AccountInfo for delegated accounts in Rust
  • Match PDA seeds exactly between Rust and TypeScript

DON'T:

  • Send delegated account operations to base layer
  • Mix base layer and ER operations in single transaction
  • Assume account ownership without checking
  • Skip commitment verification before base layer reads

Products

ProductDescription
Ephemeral Rollup (ER)High-performance, gasless transactions
Private ER (PER)Privacy-preserving computation with Intel TDX
VRFVerifiable random function for on-chain randomness
BOLT FrameworkECS architecture for fully on-chain games
Solana PluginsApp-specific extensions for enhanced capabilities

Solana Plugins (New)

Solana Plugins are modular capabilities that can be added to your dApp to extend what's possible on Solana. Think of them as your custom toolkit: plug in what you need, when you need it.

Available Plugins

PluginDescriptionUse Cases
Verifiable Randomness (VRF)Provably fair on-chain randomnessGames, lotteries, NFT drops
Real-Time Price FeedsUp-to-the-millisecond market dataDEXs, trading bots, DeFi
AI OraclesCall AI models directly from smart contractsDynamic NFTs, AI agents

Using VRF Plugin

import { requestRandomness, getRandomnessResult } from "@magicblock-labs/vrf-sdk";

// Request randomness
const requestTx = await requestRandomness({
  payer: wallet.publicKey,
  seed: Buffer.from("my_game_seed"),
});

// Get result after confirmation
const result = await getRandomnessResult(requestId);
console.log("Random value:", result.randomness);

Privacy with Intel TDX

MagicBlock enables privacy in any Solana program state account through Ephemeral Rollups running in Trusted Execution Environments (TEE) on Intel TDX. This allows:

  • Private computation without exposing state
  • Verifiable execution guarantees
  • Selective disclosure of results

Resources

Skill Structure

magicblock/
├── SKILL.md                          # This file
├── resources/
│   ├── api-reference.md              # Complete API reference
│   └── program-ids.md                # All program IDs and constants
├── examples/
│   ├── anchor-counter/README.md      # Basic counter with delegation
│   ├── delegation-flow/README.md     # Full delegation lifecycle
│   ├── vrf-randomness/README.md      # VRF integration
│   └── crank-automation/README.md    # Scheduled tasks
├── templates/
│   ├── program-template.rs           # Rust program starter
│   └── client-template.ts            # TypeScript client starter
└── docs/
    ├── advanced-patterns.md          # Complex patterns
    └── troubleshooting.md            # Common issues

AI Skill Finder

Ask me what skills you need

What are you building?

Tell me what you're working on and I'll find the best agent skills for you.