Token Interop

Rome EVM bridges ERC-20 tokens and SPL tokens through a single-state model. This page explains how tokens work across EVM and Solana.

The Single-State Model

Unlike traditional bridges, Rome doesn't lock tokens on one chain and mint wrapped copies on another. Instead, ERC-20 tokens on Rome EVM are transparent wrappers over the underlying SPL token accounts on Solana.

┌──────────────────────────────────┐
│ Rome EVM                         │
│                                  │
│   ERC-20 "rUSDC"                 │
│   ┌────────────────────────┐     │
│   │ balanceOf(user)        │─────┼──► reads directly from SPL ATA
│   │ transfer(to, amount)   │─────┼──► executes SPL transfer via precompile
│   │ totalSupply()          │─────┼──► reads SPL mint supply
│   └────────────────────────┘     │
│                                  │
└──────────────────────────────────┘

                 │ same underlying data

┌──────────────────────────────────┐
│ Solana                           │
│                                  │
│   SPL Token Account (ATA)        │
│   Owner: user's PDA              │
│   Mint: USDC (Circle native)     │
│   Amount: 1000000 (= 1 USDC)    │
│                                  │
└──────────────────────────────────┘

What this means:

  • No bridging delay — ERC-20 balance IS the SPL balance

  • No liquidity fragmentation — DeFi on both sides sees the same tokens

  • No bridge risk — there's no separate escrow to exploit

ERC20SPL: The Wrapper Contract

SPL_ERC20 is the standard wrapper contract that provides a full ERC-20 interface over an SPL token mint:

How it works under the hood:

  • balanceOf() → derives user's ATA (Associated Token Account) → reads balance from Solana

  • transfer() → calls SPL Token precompile (0xff...05) → moves tokens on Solana

  • approve() / allowance() → uses EVM storage (standard ERC-20 pattern) since SPL doesn't natively support EVM-style allowances

  • totalSupply() → reads from the SPL mint account

ERC20SPLFactory

The factory contract deploys wrappers for any SPL token:

Factory address (devnet): 0xfd21da046c282e1d36cc45e46d9599cff5742f2b

Token Registry

The TokenRegistry provides admin-controlled registration of approved SPL tokens with cross-chain metadata:

The registry ensures each asset maps to a single canonical SPL mint — preventing multiple USDC representations from fragmenting liquidity.

Deposit / Withdraw Flow

Depositing SPL → EVM

  1. User transfers SPL tokens to the bridge vault

  2. Bridge creates the user's ATA on Rome EVM (if not exists)

  3. ERC-20 wrapper becomes active — user sees balance in MetaMask

Withdrawing EVM → SPL

  1. User calls the Withdraw precompile (0x42...16) on Rome EVM

  2. Precompile executes SPL transfer from user's PDA back to their Solana wallet

  3. SPL tokens appear in the user's Solana wallet

PDA Derivation

Every EVM address maps to a Solana PDA that owns their token accounts:

Key Patterns

Reading SPL Balances from Solidity

Transferring Tokens via SPL Precompile

Gas Token

Each Rome EVM chain has its own gas token — any SPL token chosen at chain registration:

  • RSOL — default gas token (wrapped SOL)

  • Custom tokens — any SPL token, priced via Meteora DAMM V1 pool

Gas tokens are ERC-20 representations of SPL tokens. Transfer Hooks do NOT fire on EVM-internal gas payments.

Constraints

  • SPL Token amounts are uint64 — max value 18,446,744,073,709,551,615

  • Default decimals for new SPL mints: 9

  • ERC-20 wrapper symbols must be globally unique per factory

  • Allowances use EVM storage (not Solana delegates)

What's Next

Last updated

Was this helpful?