> 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-solana-from-evm.md).

# 从 EVM 调用 Solana

通过 Rome 的 CPI 和辅助预编译合约从 Solidity 调用 Solana 程序。

Rome 的预编译合约让 Solidity 合约可以直接调用 Solana 程序。本指南介绍其工作机制。

## 前置条件

* 来自公开 [`rome-solidity`](https://github.com/rome-protocol/rome-solidity) 仓库（从 GitHub 安装，而不是 npm——参见 [部署 Solidity](/zh/kai-fa-zhe-zhi-nan/deploy-solidity.md#using-the-rome-solidity-sdk)） [`contracts/interface.sol`](https://github.com/rome-protocol/rome-solidity/blob/master/contracts/interface.sol)
* 已部署的 Rome 合约（参见 [部署 Solidity](/zh/kai-fa-zhe-zhi-nan/deploy-solidity.md))

将每个接口绑定到其预编译地址：

```solidity
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);
```

## CpiProgram 预编译合约

`CpiProgram` (`0xFF…08`）派发 CPI（`invoke` / `invoke_signed`）以及跨状态读取快捷方式（`account_info`, `account_data_at`, `account_u64_at`, `account_lamports`, `pdas_batch_derive`):

```solidity
// 调用 Solana 程序
CpiProgram.invoke(programId, accounts, instructionData);

// 使用 PDA 签名进行调用：你的合约的外部权限 PDA 签名，再加上一个
// 每个 `salts` 条目对应一个带盐的子 PDA（Rome 生成 ["EXTERNAL_AUTHORITY", contract, salt]）
CpiProgram.invoke_signed(programId, accounts, data, salts);
```

## 转账 lamports

对于简单的 lamport 和 SPL 转账，请使用 HelperProgram 预编译合约——无需手动构建 CPI。调用它的合约会作为 **它自己**：下面的转账会移动合约自身的 lamports 和代币（由其外部权限 PDA 签名），而不是用户的。若要让合约将用户的 SPL 代币拉入其控制的代币账户，并识别这些代币属于谁，用户需要授予 SPL 委托。一个 ERC-20 `transfer` 或 `transferFrom` 转给合约只会记入包装合约的托管账本，不会给合约提供可支出的代币账户——参见 [集成 Solana 程序](/zh/kai-fa-zhe-zhi-nan/integrate-solana-program.md).

```solidity
contract Transfers {
    IHelperProgram constant Helper = IHelperProgram(0xFF00000000000000000000000000000000000009);

    // 将 lamports 从此合约的 PDA 转到某个 EVM 地址的 PDA
    function transferSol(address to, uint64 lamports) external {
        Helper.transfer_lamports(to, lamports);
    }

    // 从此合约的 PDA 所拥有的代币账户转移一个 SPL 代币
    function transferSpl(address to, uint64 tokens, bytes32 mint) external {
        Helper.transfer_spl(to, tokens, mint);
    }

    // 为某个铸币创建调用者的 ATA
    function createAta(bytes32 mint) external {
        Helper.create_ata(msg.sender, mint);
    }
}
```

`transfer_spl` 具有多个重载（包括用于 `transferFrom` 流程的委托变体）；请参阅 `interface.sol`。在缓存路径上，请使用 `ISplCached` (`0xff…05`) / `IAssociatedSplCached` (`0xff…06`）代替——合约应始终一致地使用同一路径，只有传统路径才能 `invoke` 一个自定义程序（参见 [集成 Solana 程序](/zh/kai-fa-zhe-zhi-nan/integrate-solana-program.md)).

## 读取账户数据

通过 CpiProgram 的读取快捷方式读取任意 Solana 账户的数据：

```solidity
(
    uint64 lamports,
    bytes32 owner,
    bool isSigner,
    bool isWritable,
    bool executable,
    bytes memory data
) = CpiProgram.account_info(accountPubkey);
```

## PDA 推导

通过 System 预编译合约在 Solidity 中查找程序派生地址：

```solidity
ISystemProgram.Seed[] memory seeds = new ISystemProgram.Seed[](2);
seeds[0] = ISystemProgram.Seed("my-program-seed");
seeds[1] = ISystemProgram.Seed(abi.encodePacked(someValue));

(bytes32 pda, uint8 bump) = SystemProgram.find_program_address(targetProgramId, seeds);
```

## Base58 转换

在以下之间转换 `bytes32` 和 base58（Solana 的地址格式）：

```solidity
bytes memory base58Str = SystemProgram.bytes32_to_base58(pubkey);
bytes32 pubkey = SystemProgram.base58_to_bytes32(base58Bytes);
```

## 调用自定义 Solana 程序

要调用任意 Solana 程序，需要自行构建账户列表和指令数据：

```solidity
contract CustomCPI {
    ICrossProgramInvocation constant CpiProgram = ICrossProgramInvocation(0xFF00000000000000000000000000000000000008);
    bytes32 constant MY_PROGRAM = 0x0000000000000000000000000000000000000000000000000000000000000000; // 你的 Solana 程序 ID

    function callMyProgram(bytes32 account1, bytes32 account2, bytes calldata ixData) external {
        ICrossProgramInvocation.AccountMeta[] memory accounts = new ICrossProgramInvocation.AccountMeta[](2);
        accounts[0] = ICrossProgramInvocation.AccountMeta(account1, false, true);
        accounts[1] = ICrossProgramInvocation.AccountMeta(account2, false, false);

        CpiProgram.invoke(MY_PROGRAM, accounts, ixData);
    }
}
```

## 关键限制

1. **所有账户都必须预先声明。** Solana 交易必须包含 CPI 将要触及的每个账户——无法在 CPI 内动态发现账户。
2. **CPI 深度限制：4 层。** Rome EVM → 你的目标 → 目标的调用 → 再一层。请规划好调用深度。
3. **Solana 公钥是 `bytes32`,** 不是 20 字节的以太坊地址。
4. **指令数据是原始字节** ，其格式需符合目标程序的预期（通常是 Borsh、小端序）。

## 接下来

* **在真实应用中查看** — [cardo](https://github.com/rome-protocol/cardo) 直接从用户的钱包向预编译合约发送 CPI 调用，无需适配器合约； [rome-dex](https://github.com/rome-protocol/rome-dex) （双通道 AMM）也同样如此，并且还拥有一个无托管路由合约； [aerarium](https://github.com/rome-protocol/aerarium) 是一个不从 Solidity 调用 Solana 程序的借贷应用。
* [集成 Solana 程序](/zh/kai-fa-zhe-zhi-nan/integrate-solana-program.md) ——用户的 Solana 身份、代表其签名、单一路径规则，以及为另一侧真实程序提供账户
* [预编译参考](/zh/can-kao/precompiles.md) ——每个预编译合约上的每个选择器
* [从 Solana 调用 EVM](/zh/kai-fa-zhe-zhi-nan/call-evm-from-solana.md) ——反向操作：从 Solana 钱包驱动 EVM 合约
* [代币互操作](/zh/he-xin-gai-nian/token-interop.md) ——ERC-20 和 SPL 代币如何协同工作
* [约束](/zh/he-xin-gai-nian/constraints.md) ——CPI 深度及其他限制


---

# 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-solana-from-evm.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.
