> 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/zh/kai-fa-zhe-zhi-nan/call-evm-from-solana.md).

# 从 Solana 调用 EVM

让 Solana 钱包直接驱动 Rome 上的 EVM 合约——无需以太坊密钥。钱包签名后，其交易会发送到 Solana。

与……相反 [从 EVM 调用 Solana](/zh/kai-fa-zhe-zhi-nan/call-solana-from-evm.md): Solana 钱包（例如 Solflare）直接调用 Rome 上的任意 EVM 合约，且 **无需以太坊密钥**。这就是 [Aerarium](/zh/rome-shang-de-ying-yong/apps.md) 让原生 Solana 用户与 EVM 用户共享同一套合约的方式。

单次调用方式是 SDK 的 `submitRomeTxV1`，它让代理为该调用组装一笔 Solana v1 交易；本页说明其机制。

## 合成地址

Solana 钱包的 EVM 身份是其 **合成地址** ——即 `keccak256` 对其 32 字节 Solana 公钥哈希后的最后 20 字节：

```
synthetic_evm_address = keccak256(solana_pubkey)[12:]
```

这就是 `msg.sender` 钱包调用合约时的身份，也是其 nonce 和存储所在的稳定标识。该派生规则固定在协议中，因此同一个钱包始终映射到同一地址。

```javascript
import { syntheticAddress } from "@rome-protocol/sdk";
const from = syntheticAddress(solanaPubkey); // 0x… 20 bytes
```

## 合成地址是一个透传层——在静止状态下不持有任何代币

Solana 原生用户可支配余额位于其 **Solana 钱包** （以 SPL 代币形式），在 EVM 侧按 1:1 映射体现为该代币的 **ERC20SPL 包装器** （例如 `wUSDC`）。合成地址在静止状态下不持有任何资产，因此价值流转 *通过* 由它承接——每一步都由 Solana 钱包签名：

1. **预置（仅一次）。** 全新的合成地址的 external-auth PDA 在以下之前并不存在： `create_pda` 执行——而下面的转账由该 PDA 签名。 `submitRomeTxSolanaLane` 在首次使用时自动为其预置（或调用 `provisionSynthetic` 以显式执行“激活”步骤；使用以下方法检查： `isSyntheticProvisioned`）。借助 `submitRomeTxV1`，检查 `isSyntheticProvisioned` 并提交 `buildCreatePdaCall` 通过它一次。
2. **充值步骤（钱包 → 合成地址）。** 将代币从钱包的 ATA 转入合成地址的 ATA—— `ActivateAta` 步骤，由 `buildFundLeg`构建。现在 `wrapper.balanceOf(synthetic)` 会读取该余额，因此合成地址可将其作为普通 ERC-20 花费（`transfer` / `transferFrom`）—— **而不是** 按原生 `msg.value`方式花费，而它并不持有原生资产。
3. **调用（`DoTxUnsigned`).** 合成地址执行 EVM 交易——例如 `approve` 先授权，然后由金库 `deposit` 执行存入，并通过以下方式拉取： `transferFrom`.
4. **清扫步骤（合成地址 → 钱包）。** 在 withdraw / borrow / claim 将代币发放到合成地址后， `buildSweepLeg` 会把它们推回用户自己的钱包 ATA（`HelperProgram.transfer_spl`），因此合成地址的净余额归零。

下一节说明较早版本的 `submitRomeTxSolanaLane` 在客户端构建了什么。 `submitRomeTxV1` 让代理改为组装一笔 v1 交易，而你的代码中无需进行账户发现、ComputeBudget 或 treasury wallet 处理；请参见 [外部 V1 端点](#the-external-v1-endpoints) 。

## 调用如何构建

`submitRomeTxSolanaLane` 完成上述全部工作。其机制如下：

1. **构建一笔未签名的 EIP-1559 交易** 使用 `from` = 合成地址——无需 secp256k1 签名。
2. **发现账户** ——调用 [`rome_emulateCallAccounts`](/zh/can-kao/json-rpc.md) ，传入调用的 `from`, `到`, `data`, **和 `value`** （当调用可支付时）。传入 `value` 很重要：代理会模拟该 *真实* 调用，因此任何依赖 value 的存储写入——例如新建的映射槽——都会被分配，并返回其对应的 Solana 账户。若省略它，该账户就不会出现，交易会以以下错误失败： *“instruction modified data of a read-only account.”*
3. **组装 Solana 交易：** 两条 **ComputeBudget** 指令（Rome 的 EVM 需要提高的 CU 上限约为 135 万，以及较大的堆帧约 250 KB——Solana 的默认值会报错）+ **`DoTxUnsigned`** 指令（未签名 RLP + 已发现的账户）+ 按链的 **资金钱包** （执行会向其支付一小笔费用；发现步骤未包含它，因此 SDK 会将其附加）。
4. **钱包签名并提交。** 上面的 **Solana 钱包对该 Solana 交易（Ed25519）签名，并将其发送到 Solana RPC——而不是发送给代理。** Solana 运行时验证签名；在链上，程序会从该签名者派生 `msg.sender` 于该签名者。权限来源于 Solana 签名，而不是以太坊签名。（代理仅用于第 2 步的模拟/发现。）

用户的 PDA（`["EXTERNAL_AUTHORITY", synthetic_address]`）拥有其代币账户，并代表其对 SPL CPI 签名——因此 Solana 用户可以完全通过其 Solana 钱包进行供应、借款、兑换和做 LP。

## 外部 V1 端点

`submitRomeTxV1` 是单次调用方式：它向代理请求一笔已组装好的 v1 交易，进行校验，让钱包签名并发送到 Solana RPC；当调用无法容纳时，则通过代理中继。代理以与 RPC 方法相同的方式暴露该通道，因此任何客户端都可在无需 SDK 的情况下使用它（完整列表见 [JSON-RPC 支持](/zh/can-kao/json-rpc.md)):

* `rome_composeUnsignedAtomicV1(rlp, pubkey, signature)` ——一笔 Solana v1 交易；钱包对其签名，应用将其广播到 Solana RPC。若调用无法装入一笔原子交易，则会被拒绝。
* `rome_composeUnsignedIterativeV1(rlp, pubkey, signature)` ——执行的每个步骤（若可容纳则为原子式，否则为迭代步骤）；钱包对每一步分别签名， `rome_submitSignedV1Plan` broadcasts them. The limits on a long plan are in [代发调用和长调用](#relayed-calls-and-long-calls).
* `rome_relayUnsignedV1(rlp, pubkey, signature)` ——一次钱包签名，由代理的 payer 推动各步骤并返回 EVM 交易哈希；合成地址支付 Rome gas，因此先使用以下方式为其充值： `rome_composeExternalDepositV1`.

**授权签名。** `rlp` 是原始的 9 字段未签名 EIP-1559 主体。钱包使用其 Ed25519 密钥签名，签名内容可以是原始的 32 字节 `keccak256(0x02 ‖ rlp)` 或可读消息

```
Rome authorization\n0x<64 lowercase hex chars of that hash>
```

两种形式都被接受；可读形式是钱包在其 `signMessage` 签名消息提示中以清晰方式展示的内容。程序会根据其执行的主体重新计算哈希，并要求签名消息必须恰好是上述两种形式之一，因此针对某个主体的签名绝不可能授权另一个主体，而且主体中的 nonce 可防止重放。

## 代发调用和长调用

有些调用无法装入一笔 Solana 交易。本节说明此时会发生什么，以及适用哪些限制。

### 当原子组装器拒绝时

`rome_composeUnsignedAtomicV1` 会拒绝无法装入一笔原子交易的调用。 `submitRomeTxV1` 然后通过代理中继该调用，并使用相同的 **相同的** 授权签名，因此不会再次向钱包请求签名。结果包含 `route: "relay"` 且没有 Solana `签名`。传入 `relayOnRefusal: false` 即可将拒绝作为错误返回，或者自行调用 `relayV1` 以主动进行中继。

关于中继调用，有三点需要了解：

* **合成地址支付 gas。** 在中继路径中，代理提交并支付 Solana 交易，而调用所需费用从合成地址余额中的链原生 gas 代币支付。该余额至少必须为 `gasLimit × maxFeePerGas`. `fundSyntheticGas(deps, amountWei)` 会从钱包自身持有的该链 gas 代币账户中补足（Hadrian 和 Rubicon 上为 USDC）；钱包签署一笔普通 v1 交易，并且还需要少量 SOL 作为 Solana 手续费。 `amountWei` 以该链 18 位小数的 wei 计量，且必须是 10^12 的倍数，否则代理会拒绝。若不使用 SDK， `rome_composeExternalDepositV1` 会构建相同的交易。
* **revert 不会抛出异常。** 在 EVM 侧发生 revert 的中继调用会正常返回，并带有 `receipt.status === "0x0"`。在处理该调用为完成之前，请检查 `receipt?.status === "0x1"` 。 `receipt` 是 `null` 如果回执未能及时出现。
* **`relayOnRefusal: false` 是 SDK 中作出的选择，而不是硬性限制。** 代理已经持有该负载的授权签名，并可通过任一路径执行它，最多可达 `gasLimit × maxFeePerGas`.

```javascript
const res = await submitRomeTxV1(deps, { to, data, value });
if (res.route === "relay" && res.receipt?.status !== "0x1") {
  // a relayed call that reverted resolves; it does not throw
}
```

### 长调用

需要超过一笔交易的调用会以迭代会话运行：程序在每笔 Solana 交易中执行尽可能多的 EVM 步骤，并在各步骤之间保存其状态。可从 Solana 钱包驱动它的两种方式：

* **对其进行中继。** `rome_relayUnsignedV1` 只需一次钱包签名，代理的 payer 负责发送每个步骤。合成地址支付 gas，如上所述。
* **自行签署每个步骤。** `rome_composeUnsignedIterativeV1` 会返回每个步骤且签名槽为空，钱包对每一步分别签名，并且 `rome_submitSignedV1Plan` 按顺序广播它们。每个步骤都是一笔独立的 Solana 交易，需要钱包签名，因此对于一次只签一笔交易的钱包而言，除授权签名外，每个步骤都会额外提示一次。钱包需为每个步骤支付 Solana 手续费。

限制如下：

| 限制                                   | 值           | 来源                                                                   |
| ------------------------------------ | ----------- | -------------------------------------------------------------------- |
| 一次中的交易数 `rome_submitSignedV1Plan` 调用 | 64          | 代理会拒绝更长的计划                                                           |
| 迭代组装器将规划的步骤数                         | 128         | Rust Rome SDK 的迭代组装器中的上限，也限制了计划 `rome_composeUnsignedIterativeV1` 返回 |
| 一次会话中的迭代次数                           | 160         | Rome EVM 程序；超过该上限后，会话停止恢复、释放其账户锁并结束                                  |
| Solana 交易大小和账户数                      | 4 KB，64 个地址 | 每个步骤都是一笔 Solana v1 交易                                                |

大多数真实调用所需的步骤远少于这些上限。钱包签名计划中的 64 交易限制是你最先会遇到的。需要超过该步骤数的调用无法通过以下方式广播： `rome_submitSignedV1Plan`.

长调用不能发起 legacy-track CPI。对 legacy 预编译合约的变更型调用（`0xff…08`, `0xff…09` 和 `0x42…16`）在迭代会话中会被拒绝，并提示 `CpiProhibitedInIterativeTx`。对 Solana 状态的读取以及 cached-track 预编译合约（`0xff…04`, `0xff…05`, `0xff…06`, `0xff…0b`）可以正常工作。无论通过哪条通道发送，凡是会发起 legacy-track CPI 的调用都必须能装入一笔原子交易。参见 [约束](/zh/he-xin-gai-nian/constraints.md) 和 [计算预算](/zh/he-xin-gai-nian/compute-budget.md).

## 参考实现

* **Aerarium** ——通过以下方式从 Solana 钱包驱动 Compound v3（供应/借款）： `DoTxUnsigned`。它是上文所述旧版 legacy 交易通道的参考实现，而不是 `submitRomeTxV1`.
* **Rome DEX** 并不是本页的示例：其 Solana 侧是一个原生 Solana 程序，钱包直接为其签名，而其 EVM 侧共享同一个池子。

Aerarium 无需桥或第二个钱包——合成地址使一个 Solana 密钥成为一等公民的 EVM 账户。对于 v1 模式，请参见 [构建双通道应用](/zh/kai-fa-zhe-zhi-nan/dual-lane-app.md).

## 接下来

* [从 EVM 调用 Solana](/zh/kai-fa-zhe-zhi-nan/call-solana-from-evm.md) ——另一个方向，通过 CPI
* [代币互操作](/zh/he-xin-gai-nian/token-interop.md) ——余额如何在 EVM 与 Solana 之间共享


---

# 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/zh/kai-fa-zhe-zhi-nan/call-evm-from-solana.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.
