The two lanes
One token deployment, two wallet worlds. Both lanes execute the same contracts with the same compliance gate — they differ only in who signs and how the transaction reaches the chain.
EVM lane
Standard Ethereum tooling, unchanged: the wallet signs an EVM transaction, the chain's JSON-RPC endpoint accepts it, the token contract enforces the allowlist. Nothing Bloom-specific to integrate — measured cost of a gated transfer is ~3.1M gas (Rome gas is USDC-denominated; a plain native transfer is ~1.48M for scale).
Solana lane
A Solana wallet drives the same EVM contracts natively — no EVM key exists anywhere:
Identity. The user's EVM-side address is synthetic: derived deterministically as
keccak256(solana_pubkey)[12..32]. The Rome EVM program derives it on-chain from the transaction's actual signer — it cannot be spoofed, and no secp256k1 private key for it exists, so the Solana signature is the only thing that can ever drive this address.Discovery. The client asks the chain's RPC (
rome_emulateCallAccounts) which Solana accounts the EVM call will touch; the emulation also decides which are writable (the call'svaluemust be included — writability of the recipient's balance account depends on it).Submit. The client builds a
DoTxUnsignedinstruction — an unsigned EIP-1559 payload authorized by the Solana signature — plus compute-budget instructions (250KB heap, 1.35M CU) and the fee-pool account discovery omits. The wallet signs one Solana transaction; done.Size, measured on this deployment 2026-07-30: a storefront buy resolves to 22 accounts and serialises to 1,061 bytes against the 1232-byte legacy limit — 171 bytes of headroom, about five more accounts. So it does NOT need a lookup table today. A state that touches more — an associated token account still to be created, a first-time holder — could still cross it, and past 1232 bytes the client falls back to a v0 transaction over an address lookup table it CREATES for the purpose.
That fallback is expensive for a wallet: each ALT-extend chunk and the v0 resubmit is a separate
signTransaction, so one Buy becomes three or four prompts. Hadrian already has a persistent dApp table holding these contracts' accounts — referencing it instead is the fix, which the lane client cannot do yet.Confirm. The transaction confirms on Solana; the EVM view catches up a beat later (the tooling polls the nonce before sending the next call).
ATOMIC ONLY — the lane's hard boundary
DoTxUnsigned is one EVM transaction executed inside one Solana transaction by Rome's atomic VM. The iterative VM — the one that stages a single EVM execution across several Solana transactions — is not reachable through this lane yet, so a call that will not fit atomically cannot take this lane at all. There is no fallback to reach for and none should be added; the fix for a call that does not fit is a smaller call.
Two things are commonly mistaken for it, and neither is:
The v0 + lookup-table fallback is a bigger envelope, not a different execution: the same single instruction, carried in a transaction format with room for far more account keys. Measured today the storefront buy does not need it (1,061 bytes of 1232), and a call that did would still be atomic.
A multi-step journey is not a multi-leg execution. Bloom's
Runcalls its steps legs — approve, then buy — and each is a whole transaction, signed separately, atomic on its own. Rome calls the chunks of ONE split execution legs too. This app has the first and must never ask for the second. The word collides; the mechanisms do not.
Every flow in the table below is one atomic transaction, and the heaviest uses just over half the per-transaction compute ceiling — so the boundary is not close for anything Bloom does today.
Measured on Rome devnet, one signature each — ranges observed across two chains:
Gated RWA transfer
~339–381K
wUSDC approve
~189–191K
Storefront buy (approve + buy = 2 sigs total)
~730–796K
Cash-leg sweep to the wallet's own token account
~42–54K
Plain value transfer
~47K
TestApprover.approve — allowlist admission, wallet-signed
220,794
For comparison, Solana's per-transaction compute ceiling is 1.4M CU — the heaviest Bloom flow uses just over half of it.
Custody: why the RWA stays at the synthetic
The design question every permissioned asset must answer: can the token leak out of the compliance perimeter? On the Solana lane the answer is structural, not policy:
The RWA is EVM-state-resident. Its balances are storage inside the Rome EVM program's accounts. There is no SPL mint of it, no token account representation, nothing Solana-native to move. (This is deliberate — a wrapped/mirrored version would be the escape hatch.)
The Solana-side plumbing can't touch it. The lane's asset-moving legs (
create_ata, SPL transfers, sweeps) operate on SPL token accounts; EVM storage balances have no such account. There is no instruction that moves an EVM balance to a Solana token account.Only the owner's Solana signature drives the synthetic, and every movement it can express is a call into the token contract — which runs the allowlist gate — so a transfer to a non-whitelisted address reverts identically whether signed by an EVM key or a Solana key.
The cash leg is the deliberate exception. wUSDC (yield, sale proceeds) is SPL-backed and should round-trip: the exit leg (
transfer_spl) moves it from the synthetic to the wallet's own associated token account, verified end-to-end. Asset stays in the perimeter; cash flows freely.
Two practical consequences, stated plainly: a Solana wallet UI won't display the RWA holding (there is no SPL token to list) — the Bloom app is the positions surface; and a lost Solana key is recovered through the issuer's process (COMPLIANCE.md), not by moving the asset out-of-band — which is exactly the property a permissioned asset wants.
Last updated
Was this helpful?