> For the complete documentation index, see [llms.txt](https://docs.rome.builders/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rome.builders/core-concepts/token-interop.md).

# Token Interop

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.

<figure><img src="/files/AtCqSM7OVYdvhPinXVJS" alt="An ERC-20 wrapper over the same SPL token account on Solana"><figcaption></figcaption></figure>

* 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 public [`rome-solidity`](https://github.com/rome-protocol/rome-solidity) repo (git dependency or copied interfaces). Precompile interfaces live in [`contracts/interface.sol`](https://github.com/rome-protocol/rome-solidity/blob/master/contracts/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 Solana
* `transfer()` — moves tokens on Solana
* `approve()` / `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:

```solidity
import {ERC20SPLFactory} from "@rome-protocol/rome-solidity/contracts/erc20spl/erc20spl_factory.sol";

// Deploy a wrapper, loading name/symbol from Metaplex metadata
address wrapper = factory.add_spl_token_with_metadata(splMint);

// Or specify name/symbol manually
address wrapper = factory.add_spl_token_no_metadata(splMint, "USD Coin", "USDC");
```

Live factory addresses: Hadrian `0x86149124d74ebb3aa41a19641b700e88202b6285`, Martius `0xd7aeeedca26cdd4d34eb7c21110af2e590a8c58a`. Always verify against the [registry](https://github.com/rome-protocol/rome-registry/tree/main/chains) — 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`](https://github.com/rome-protocol/rome-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:

```solidity
import {IHelperProgram} from "@rome-protocol/rome-solidity/contracts/interface.sol";

IHelperProgram helper = IHelperProgram(0xFF00000000000000000000000000000000000009);

helper.create_ata(user, mint);              // create the user's ATA for a mint
helper.transfer_spl(to, tokens, mint);      // transfer SPL from the caller's PDA
```

`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 `Withdraw` precompile (`0x42…16`): `withdraw_to_pda` / `withdraw_to_ata` move tokens from the user's PDA back to Solana. The wrap-gas-to-SPL path is `withdraw_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](/reference/contract-addresses.md) — precompiles and per-chain addresses
* [Call Solana from EVM](/developer-guides/call-solana-from-evm.md) — CPI and SPL operations from Solidity


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.rome.builders/core-concepts/token-interop.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
