Call EVM from Solana
Let a Solana wallet drive EVM contracts on Rome — no Ethereum key. The wallet signs, and its transaction goes to Solana.
The reverse of Call Solana from EVM: a Solana wallet (e.g. Phantom) calls any EVM contract on Rome directly, with no Ethereum key. This is how Aerarium and Rome DEX let Solana-native users share the same contracts as EVM users.
The one-call way is the SDK's submitRomeTxSolanaLane; this page explains what it does.
Synthetic addresses
A Solana wallet's EVM identity is its synthetic address — the last 20 bytes of the keccak256 of its 32-byte Solana public key:
synthetic_evm_address = keccak256(solana_pubkey)[12:]This is the msg.sender when the wallet calls a contract, and the stable identity its nonce and storage live under. The derivation is pinned in the protocol, so a wallet always maps to the same address.
import { syntheticAddress } from "@rome-protocol/sdk";
const from = syntheticAddress(solanaPubkey); // 0x… 20 bytesThe synthetic is a pass-through — it holds no tokens at rest
A Solana-native user's spendable balance lives in their Solana wallet (as SPL tokens), surfaced 1:1 on the EVM side as the token's ERC20SPL wrapper (e.g. wUSDC). The synthetic holds nothing at rest, so value flows through it — every step Solana-wallet-signed:
Provision (once). A brand-new synthetic's external-auth PDA doesn't exist until
create_pdaruns — and the transfers below are signed by that PDA.submitRomeTxSolanaLaneprovisions it automatically on first use (or callprovisionSyntheticfor an explicit "Activate" step; check withisSyntheticProvisioned).Fund leg (wallet → synthetic). Move the token from the wallet's ATA into the synthetic's — the
ActivateAtastep, built bybuildFundLeg. Nowwrapper.balanceOf(synthetic)reads that balance, so the synthetic spends it as an ordinary ERC-20 (transfer/transferFrom) — not as nativemsg.value, which it doesn't hold.The call(s) (
DoTxUnsigned). The synthetic runs the EVM transaction(s) — e.g.approvethen a vaultdepositthat pulls viatransferFrom.Sweep leg (synthetic → wallet). After a withdraw / borrow / claim lands tokens in the synthetic,
buildSweepLegpushes them back to the user's own wallet ATA (HelperProgram.transfer_spl), so the synthetic nets to nothing.
How the call is built
submitRomeTxSolanaLane does all of this. The mechanics:
Build an unsigned EIP-1559 transaction with
from= the synthetic address — no secp256k1 signature.Discover accounts — call
rome_emulateCallAccountswith the call'sfrom,to,data, andvalue(when the call is payable). Passingvaluematters: the proxy emulates the real call, so any value-dependent storage write — e.g. a fresh mapping slot — is allocated and its Solana account is returned. Omit it and that account is missing, and the transaction fails with "instruction modified data of a read-only account."Assemble the Solana transaction: two ComputeBudget instructions (Rome's EVM needs a raised CU limit ≈1.35M and a large heap frame ≈250 KB — Solana's defaults fault) + the
DoTxUnsignedinstruction (the unsigned RLP + the discovered accounts) + the per-chain treasure wallet (the execution pays it a small fee; discovery omits it, so the SDK appends it).The wallet signs and submits. The Solana wallet signs the Solana transaction (Ed25519) and sends it to the Solana RPC — not to the proxy. The Solana runtime verifies the signature; on-chain, the program derives
msg.senderfrom that signer. Authority is the Solana signature, not an Ethereum one. (The proxy is used only for the emulation/discovery in step 2.)
The user's PDA (["EXTERNAL_AUTHORITY", synthetic_address]) owns their token accounts and signs SPL CPIs on their behalf — so a Solana user can supply, borrow, swap, and LP entirely from Phantom.
Reference implementations
Aerarium — drives Compound v3 (supply / borrow) from Phantom via
DoTxUnsigned.Rome DEX — the Solana lane trades and LPs the same pool as the EVM lane.
Both are Solana-native without a bridge or a second wallet — the synthetic address makes one Phantom key a first-class EVM account.
What's Next
Call Solana from EVM — the other direction, via CPI
Token Interop — how balances are shared across EVM and Solana
Last updated
Was this helpful?