Token Interop
How ERC-20 and SPL tokens are the same underlying account on Rome.
Rome represents an ERC-20 token and its underlying SPL token as one shared account. This page explains how tokens work across EVM and Solana.
The shared-state model
Rome doesn't lock tokens on one side and mint wrapped copies on another. An ERC-20 token on Rome is a transparent wrapper over an SPL token account on Solana — the ERC-20 balance is the SPL balance.
No bridging delay — the ERC-20 balance is the SPL balance
No liquidity fragmentation — DeFi on both sides sees the same tokens
No bridge risk — there is no separate escrow to exploit
Import paths below use
@rome-protocol/rome-solidity. The npm publish is pending; today you consume these from the publicrome-solidityrepo (git dependency or copied interfaces). Precompile interfaces live incontracts/interface.sol.
The wrapper contract
SPL_ERC20 (and its cached-track variant SPL_ERC20_cached, which the factory deploys today) provide a full ERC-20 interface over an SPL mint:
balanceOf()— reads the user's ATA balance from Solanatransfer()— moves tokens on Solanaapprove()/allowance()— use EVM storage (SPL has no EVM-style allowances)totalSupply()— reads the SPL mint supply
The factory
ERC20SPLFactory deploys a wrapper for any SPL mint:
Live factory addresses: Hadrian 0x86149124d74ebb3aa41a19641b700e88202b6285, Martius 0xd7aeeedca26cdd4d34eb7c21110af2e590a8c58a. Always verify against the registry — it is the source of truth for deployed addresses.
Canonical mints
There is no on-chain token-registry contract. Canonical wrappers and gas/bridge tokens are curated in the off-chain rome-protocol/registry; permissionless wrappers created via add_spl_token_no_metadata are discovered from the on-chain TokenCreated event. This keeps each asset mapped to a single canonical SPL mint without fragmenting liquidity.
SPL operations from Solidity
For user-PDA-signed SPL primitives, use the HelperProgram precompile (0xFF…09) — ATA creation, SPL transfers, and gas↔lamports conversion:
transfer_spl has several overloads (including a delegate variant for transferFrom flows); see interface.sol for exact signatures. On the cached track, the equivalent operations live on ISplCached (0xFF…05) and IAssociatedSplCached (0xFF…06). A contract uses one track consistently.
Deposit and withdraw
Into EVM — the SPL side credits the user's PDA-owned ATA; the ERC-20 wrapper immediately reflects the balance. Cross-chain inbound transfers settle trustlessly via a user-signed authorization on the bridge.
Out to Solana — call the
Withdrawprecompile (0x42…16):withdraw_to_pda/withdraw_to_atamove tokens from the user's PDA back to Solana. The wrap-gas-to-SPL path iswithdraw_to_ata.
Gas token
Each chain has its own gas token — any SPL token, priced via a Meteora DAMM pool (v1 or v2, configurable). The public chains (Martius, Hadrian) use USDC. There is no universal default gas token.
Constraints
SPL token amounts are
uint64(max 18,446,744,073,709,551,615)Allowances use EVM storage, not Solana delegates
ERC-20 wrapper symbols must be unique per factory
What's Next
Contract Addresses — precompiles and per-chain addresses
Call Solana from EVM — CPI and SPL operations from Solidity
Last updated
Was this helpful?