For the complete documentation index, see llms.txt. This page is also available as Markdown.

Call Solana from EVM

Call Solana programs from Solidity via Rome's CPI and helper precompiles.

Rome's precompiles let Solidity contracts call Solana programs directly. This guide covers the mechanics.

Prerequisites

Bind each interface to its precompile address:

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

ICrossProgramInvocation constant CpiProgram    = ICrossProgramInvocation(0xFF00000000000000000000000000000000000008);
ISystemProgram          constant SystemProgram = ISystemProgram(0xFF00000000000000000000000000000000000007);
IHelperProgram          constant Helper        = IHelperProgram(0xFF00000000000000000000000000000000000009);

The CpiProgram precompile

CpiProgram (0xFF…08) dispatches CPI (invoke / invoke_signed) plus cross-state read shortcuts (account_info, account_data_at, account_u64_at, account_lamports, pdas_batch_derive):

// Call a Solana program
CpiProgram.invoke(programId, accounts, instructionData);

// Call with PDA signing: your contract's external-authority PDA signs, plus one
// salted sub-PDA per entry in `salts` (Rome derives ["EXTERNAL_AUTHORITY", contract, salt])
CpiProgram.invoke_signed(programId, accounts, data, salts);

Transfer lamports

For simple lamport and SPL transfers, use the HelperProgram precompile — no hand-built CPI needed. A contract that calls it acts as itself: the transfers below move the contract's own lamports and tokens (its external-authority PDA signs), not the user's. For a contract to pull a user's SPL tokens into a token account it controls, and know whose they are, the user grants an SPL delegation. An ERC-20 transfer or transferFrom to a contract only credits the wrapper's escrow ledger and gives the contract no token account to spend from — see Integrate a Solana program.

transfer_spl has several overloads (including a delegate variant for transferFrom flows); see interface.sol. On the cached track, use ISplCached (0xff…05) / IAssociatedSplCached (0xff…06) instead — a contract uses one track consistently, and only the legacy track can invoke a custom program (see Integrate a Solana program).

Reading account data

Read any Solana account's data via the CpiProgram read shortcuts:

PDA derivation

Find Program Derived Addresses from Solidity via the System precompile:

Base58 conversion

Convert between bytes32 and base58 (Solana's address format):

Calling custom Solana programs

To call any Solana program, build the account list and instruction data yourself:

Key Constraints

  1. All accounts must be declared upfront. The Solana transaction must include every account the CPI will touch — dynamic account discovery inside CPI is not possible.

  2. CPI depth limit: 4 levels. Rome EVM → your target → the target's call → one more. Plan your call depth.

  3. Solana pubkeys are bytes32, not 20-byte Ethereum addresses.

  4. Instruction data is raw bytes in the format the target program expects (typically Borsh, little-endian).

What's Next

  • See it in a real app — cardo sends CPI calls to the precompile straight from the user's wallet, with no adapter contract; rome-dex (dual-lane AMM) does the same and also has a custody-less router contract; aerarium is a lending app that does not call Solana programs from Solidity.

  • Integrate a Solana program — the user's Solana identity, signing as it, the one-track rule, and account provisioning for a real program on the other side

  • Precompile reference — every selector on every precompile

  • Call EVM from Solana — the reverse: drive EVM contracts from a Solana wallet

  • Token Interop — how ERC-20 and SPL tokens work together

  • Constraints — CPI depth and other limits

Last updated

Was this helpful?