> 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/developer-guides/open-to-solana-users.md).

# Open your contract to Solana users

Deploy a Solidity contract on Rome (Hadrian testnet, Rubicon mainnet) and open it to Solana wallets — what the contract needs, how a Solana wallet user calls it, per-network addresses, and a launch ch

One deployment, two kinds of users. A contract on Rome is reached by MetaMask users with their EVM key and by Solana wallet users (Solflare, for example) with their Solana key — same address, same state. This page is the end-to-end path: deploy, make the contract Solana-ready, wire the Solana lane in your app, and ship on mainnet.

## 1. Deploy on the network you want

Rome chains are standard EVM targets: Hardhat, Foundry, viem and ethers work unchanged (full walkthrough in [Deploy Solidity Contracts](/developer-guides/deploy-solidity.md)).

| Network                                       | Chain ID | RPC                                         | Explorer                                                                              | Gas token | How to get gas                                                                                                                                                |
| --------------------------------------------- | -------- | ------------------------------------------- | ------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [**Rubicon**](/networks/rubicon.md) (mainnet) | `7531`   | `https://rubicon.mainnet.romeprotocol.xyz/` | [via-rubicon.mainnet.romeprotocol.xyz](https://via-rubicon.mainnet.romeprotocol.xyz/) | USDC      | Bridge USDC from Ethereum (CCTP) or from Solana with the [Rome Bridge](https://bridge-api.mainnet.romeprotocol.xyz/); it lands as gas                         |
| **Hadrian** (testnet)                         | `200010` | `https://hadrian.testnet.romeprotocol.xyz/` | [via-hadrian.testnet.romeprotocol.xyz](https://via-hadrian.testnet.romeprotocol.xyz/) | test USDC | Bridge test USDC from a testnet ([Faucets](/resources/faucets.md)) or wrap from a Solana devnet wallet in the [Rome App](https://app.devnet.romeprotocol.xyz) |

Gas is USDC with 18 decimals on the EVM side; there is no Rome faucet on any network. Writes cost more gas than Ethereum intuition suggests, because most of the cost is creating Solana accounts: a never-used recipient, a new token account, a deployed contract. Use `eth_estimateGas`, which standard tooling does, and never hardcode 21,000. Measured on Hadrian, deploying a 10-byte contract estimated and used 1,147,921 gas, and a native transfer to a never-used address 1,086,961, against 21,000 for a transfer to an existing address. Rome charges the gas actually used, which in these measurements was never more than the estimate. Fund a few USDC before a first deploy. Verify source on the explorer afterwards ([Verify Contracts](/developer-guides/verify-contracts.md); the mainnet verifier is `https://verify.mainnet.romeprotocol.xyz`).

Develop on Hadrian, then redeploy the same artifacts to Rubicon with the chain id and the mainnet wrapper addresses swapped; nothing in the contract changes between networks. Rubicon is not in the public registry, `create-rome-app` or `rome-cli` yet, so tools that read chain values from them cannot target it; fill the values by hand from [Rubicon](/networks/rubicon.md). Gas on Rubicon is real USDC at about ten times Hadrian's gas price, so a deploy costs real money there.

## 2. What makes a contract Solana-ready

A Solana user's EVM identity is their **synthetic address**, `keccak256(solana_pubkey)[12:]`: an ordinary `msg.sender`, with an ordinary nonce. Most contracts need no change at all. The three things that matter:

* **Take value as ERC-20, not `msg.value`.** A Solana user's funds are SPL tokens in their wallet, seen on the EVM side as the token's wrapper (`wUSDC`, `wETH`, `wSOL`). The synthetic holds nothing at rest and the user's assets arrive as the wrapper, so pull with `transferFrom` after an `approve` and return with `transfer`. Each user grants the wrapper its SPL delegate right once before the first pull ([One-time: grant the wrapper](/developer-guides/dual-lane-app.md#one-time-grant-the-wrapper)). A `payable` entry point only works once the synthetic has been given native gas (the relay path's `rome_composeExternalDepositV1`), which the fund-and-sweep model does not do. An existing ERC-20 vault, AMM or lending market already has the right shape.
* **Use the canonical wrappers.** Each chain has one wrapper per mint; the addresses are in [Contract Addresses](/reference/contract-addresses.md) (Hadrian's also in the public [registry](https://github.com/rome-protocol/rome-registry)). Point constructors at those addresses; do not deploy your own.
* **Fit the atomic budget where a wallet signs.** A Solana wallet signs one Solana transaction, so a Solana-lane call must fit it: about 1.4M compute units, 64 accounts, 4 KB ([Constraints](/core-concepts/constraints.md)). Measured on Hadrian: a transfer, an approve and an Aave supply fit; a Uniswap V3 swap fits with little headroom (about 1.24M CU); an Aave borrow does not and takes the relay path in the next section. Measure with the Solana receipt's `computeUnitsConsumed`, requesting the transaction with `maxSupportedTransactionVersion: 1` (Rome legs are v1 transactions; without it the field reads 0).

What you do not need: a Solana program, a bridge, a second deployment, or any Rome-specific Solidity. If the contract also calls Solana programs through the precompiles, see [Call Solana from EVM](/developer-guides/call-solana-from-evm.md).

## 3. Let a Solana wallet call it

Your app's EVM path is unchanged (MetaMask + `submitRomeTx`, or plain viem/ethers). For the Solana path there are two shapes.

**The SDK lane — one Solana v1 transaction per call.** [`@rome-protocol/sdk`](https://github.com/rome-protocol/rome-sdk-ts) (`submitRomeTxV1`) has the proxy compose one Solana v1 transaction for your EVM call (up to 64 accounts and 4 KB, with no address lookup table; there is no ComputeBudget instruction, treasure wallet or account discovery in your code). The wallet signs twice with the same key: first a readable authorization message (`Rome authorization` on one line, `0x` and the EVM transaction hash on the next), which the chain checks through the Ed25519 program, then the transaction. The app sends the signed transaction to the Solana RPC; the call returns the EVM transaction hash and its receipt. Before the wallet signs the transaction, the SDK checks it: the payer is the only signer, only the Rome program and the Ed25519 verifier run, the Rome instruction carries exactly the call the user authorized, and there is no priority fee. Anything else is refused.

If a call does not fit one transaction, the composer refuses it and the SDK hands it to the proxy to relay, with the same authorization signature, so the user still sees one authorization prompt. The result then has `route: "relay"`. The rules for relayed calls and long calls are in [Relayed calls and long calls](/developer-guides/call-evm-from-solana.md#relayed-calls-and-long-calls). A relayed call is paid in the chain's native gas, so the synthetic needs a gas balance: `fundSyntheticGas(deps, amountWei)` moves the chain's gas token (USDC on Hadrian and Rubicon) from the wallet's token account into it, in one wallet-signed v1 transaction.

The fund leg (`buildFundLeg`) and the wallet token-account creation before a sweep are plain Solana instructions; `submitSolanaInstructionsV1` sends them as v1 transactions too, signed by the same wallet signer, with no proxy involved. The sweep's own call (`buildSweepLeg`, `HelperProgram.transfer_spl`) goes through `submitRomeTxV1` like any other, and the proxy finds the token accounts. `submitRomeTxV1` does not create a new synthetic's account by itself: before the first value-moving call, check `isSyntheticProvisioned(...)` and, if it is false, submit `buildCreatePdaCall(synthetic)` through `submitRomeTxV1` once. A full deposit-and-withdraw walkthrough with code is in [Build a dual-lane app](/developer-guides/dual-lane-app.md); the mechanics are in [Call EVM from Solana](/developer-guides/call-evm-from-solana.md). The earlier `submitRomeTxSolanaLane`, which builds a legacy Solana transaction in the client, is still in the SDK as the older path.

**Where each `deps` value comes from.** The SDK's `deps` object has five required fields (the rest, such as `fetchImpl` and the timeouts, are optional). Four come from the chain and one from the wallet:

| `deps` field | Where it comes from                                                                                                  | Registry getter (`@rome-protocol/registry` v0.2.0)                                                   |
| ------------ | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `proxyUrl`   | the chain's RPC URL                                                                                                  | `getChain(chainId).rpcUrl`                                                                           |
| `chainId`    | the chain ID                                                                                                         | `getChain(chainId).chainId`                                                                          |
| `programId`  | the Rome program ID the chain runs on                                                                                | `getChain(chainId).romeEvmProgramId`                                                                 |
| `connection` | a Solana RPC for the chain's cluster, where the signed transaction is sent (devnet for Hadrian, mainnet for Rubicon) | `getChain(chainId).solana.rpc` (a public devnet URL for Hadrian); for mainnet use an RPC of your own |
| `signer`     | the wallet's `LaneSigner`, or a `Keypair` in scripts                                                                 | none                                                                                                 |

The leg builders take values that are not in `deps`. Their `mint` (called `usdcMint` in the walkthrough) is the gas token's Solana mint, from `getTokens(chainId)` (the entry with `kind: "gas"`, field `mintId`). The wUSDC wrapper address is not a leg-builder input; you use it for the ERC-20 calls, and it is the entry with `kind: "spl_wrapper"` and symbol `wUSDC` (field `address`). The registry covers Hadrian. For Rubicon, fill them by hand: `proxyUrl` `https://rubicon.mainnet.romeprotocol.xyz/`, `chainId` `7531`, `programId` `RomePq9X3iAoTr813HR7uRarafFDUJty6GdiEceYpzX`, `mint` `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`, the wrapper from the table in the next section, and a Solana mainnet RPC for `connection`. [Rubicon](/networks/rubicon.md) has the full list.

**The proxy's external-V1 endpoints — the same lane without the SDK.** The SDK wraps the atomic compose, the relay and the gas deposit (`fundSyntheticGas`). The raw RPC methods remain for clients that do not use the SDK, and they include the iterative compose, which the SDK does not wrap. The wallet signs one Ed25519 message over the call (the readable form `Rome authorization\n0x<hash>` or the raw hash), and the proxy either composes the Solana transaction(s) for the wallet to sign (`rome_composeUnsignedAtomicV1`, `rome_composeUnsignedIterativeV1` + `rome_submitSignedV1Plan`) or runs the whole execution itself with one wallet signature (`rome_relayUnsignedV1`, after a `rome_composeExternalDepositV1` (the SDK's `fundSyntheticGas`) that funds the synthetic's gas balance; relayed calls spend from it, so top it up when it runs low). Method shapes in [JSON-RPC Support](/reference/json-rpc.md); the authorisation rules in [Call EVM from Solana](/developer-guides/call-evm-from-solana.md#the-external-v1-endpoints). Use the relay for anything iterative, and the SDK lane for everything that fits.

Either way the contract sees a normal transaction from the synthetic address and the user never holds an EVM key.

## 4. Per-network wrapper addresses

| Network          | `wUSDC` wrapper                              | Other wrappers                                                                                           |
| ---------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Rubicon (7531)   | `0x0952d233b0c254cd375f3bfb3e5869c1a906f9b3` | `wETH` `0x01e8b3573da1d4b7b4b1ce0932cf07c8af0c92e9`, `wSOL` `0xc4e29e0f726a906e93277cbbc37bf27a9848a113` |
| Hadrian (200010) | `0xbce339ba4c3a8de3bb8af1c5539e6ac11da3112d` | `wETH` `0x02aeace038269f9961c93d04dcacd6fb3adfa55f`, `wSOL` `0x39f36444fcb79b3e691544aee2a832ebef54ca9f` |

The native gas token appears as `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` in token lists; it is not an ERC-20 contract. Wrappers carry the SPL mint's decimals, not 18: wUSDC 6, wETH 8, wSOL 9.

## 5. Launch checklist

1. Deployed on Hadrian; the EVM path works from MetaMask.
2. Every value-moving entry point is ERC-20 based and pointed at the chain's canonical wrapper.
3. A Solana wallet that signs v1 transactions (Solflare does on devnet today; Phantom does not yet) completes the full round trip on Hadrian: provision the synthetic (`create_pda`, once) → fund leg → grant → approve → call → sweep leg, using the SDK lane; or the relay path if the call is iterative.
4. Each Solana-lane call's `computeUnitsConsumed` is read from the Solana receipt and sits under the atomic budget, or the call is routed through the relay.
5. The wrapper grant is in your app's first-deposit flow for both lanes: `isEnabled(user)` is checked and `approve_spl` is sent once when it is false ([One-time: grant the wrapper](/developer-guides/dual-lane-app.md#one-time-grant-the-wrapper)).
6. Source verified on the explorer.
7. Redeployed on Rubicon with the mainnet wrapper addresses. Rubicon is not in the registry, so its `deps` values are filled by hand from [Rubicon](/networks/rubicon.md). The same round trip is run once with real funds, using a wallet you have checked signs v1 transactions on mainnet.

## What's next

* [Build a dual-lane app](/developer-guides/dual-lane-app.md) — the vault example, step by step, both wallets
* [Call EVM from Solana](/developer-guides/call-evm-from-solana.md) — synthetic addresses, account discovery, the external-V1 endpoints
* [Deploy Solidity Contracts](/developer-guides/deploy-solidity.md) · [Verify Contracts](/developer-guides/verify-contracts.md) · [Contract Addresses](/reference/contract-addresses.md)


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.rome.builders/developer-guides/open-to-solana-users.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
