# 从 EVM 调用 Solana

Rome 的 CPI 预编译允许 Solidity 合约直接调用任何 Solana 程序。本指南介绍其工作机制。

## 前置条件

* 已安装 Rome Solidity SDK： `npm install @rome-protocol/solidity-sdk`
* 一个已部署的 Rome 合约（见 [部署 Solidity](/zh/kai-fa-zhe-zhi-nan/deploy-solidity.md))

## CPI 预编译

位于 `0xFF00000000000000000000000000000000000008` 提供两个函数：

```solidity
import {CpiProgram} from "@rome-protocol/solidity-sdk/contracts/core/Precompiles.sol";

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

// 使用 PDA 签名调用（你的合约作为 PDA 签名）
CpiProgram.invoke_signed(programId, accounts, data, seeds);
```

## 基础示例：转移 SOL

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {SystemProgram, CpiProgram} from "@rome-protocol/solidity-sdk/contracts/core/Precompiles.sol";
import {RomeEVMAccount} from "@rome-protocol/solidity-sdk/contracts/core/RomeEVMAccount.sol";

contract SolTransfer {
    function transferSol(bytes32 recipient, uint64 lamports) external {
        // 获取发送方的 PDA
        bytes32 senderPda = RomeEVMAccount.pda(msg.sender);

        // 构建 System Program 转账指令
        ICrossProgramInvocation.AccountMeta[] memory accounts = new ICrossProgramInvocation.AccountMeta[](2);
        accounts[0] = ICrossProgramInvocation.AccountMeta(senderPda, true, true);   // 发送方（签名者，可写）
        accounts[1] = ICrossProgramInvocation.AccountMeta(recipient, false, true);   // 接收方（可写）

        // System Program 转账指令（变体 2，小端 u64 金额）
        bytes memory data = abi.encodePacked(uint32(2), lamports);

        CpiProgram.invoke(SystemProgram.program_id(), accounts, data);
    }
}
```

## 读取账户数据

CPI 预编译还允许你读取任意 Solana 账户的数据：

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

## 使用 SPL Token

使用 SPL Token 预编译执行常见代币操作：

```solidity
import {SplToken, AssociatedSplToken} from "@rome-protocol/solidity-sdk/contracts/core/Precompiles.sol";

contract TokenOperations {
    // 读取代币账户余额
    function getBalance(bytes32 tokenAccount) external view returns (uint64) {
        ISplToken.Account memory acc = SplToken.account_state(tokenAccount);
        return acc.amount;
    }

    // 转移 SPL 代币
    function transferTokens(bytes32 recipientAta, bytes32 mint, uint256 amount) external {
        SplToken.transfer(recipientAta, mint, amount);
    }

    // 创建关联代币账户
    function createAta(bytes32 wallet, bytes32 mint) external {
        AssociatedSplToken.create_associated_token_account(wallet, mint);
    }
}
```

## PDA 推导

在 Solidity 中查找程序派生地址：

```solidity
import {SystemProgram} from "@rome-protocol/solidity-sdk/contracts/core/Precompiles.sol";

// 推导一个 PDA
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
// bytes32 → base58 字符串
bytes memory base58Str = SystemProgram.bytes32_to_base58(pubkey);

// base58 字符串 → bytes32
bytes32 pubkey = SystemProgram.base58_to_bytes32(base58Bytes);
```

## 调用自定义 Solana 程序

要调用任意 Solana 程序：

```solidity
contract CustomCPI {
    bytes32 constant MY_PROGRAM = 0x...; // 你的 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。** 所有地址都是 32 字节的 Solana 公钥，而不是 20 字节的 Ethereum 地址。
4. **指令数据是原始字节。** 你需要按照目标程序期望的格式对指令数据进行编码（通常为 Borsh 编码，采用小端序）。

## 下一步

* [代币包装](https://github.com/rome-protocol/docs/blob/main/developer-guides/token-wrapping.md) — 为 SPL 代币部署 ERC-20 包装器
* [构建转账钩子](https://github.com/rome-protocol/docs/blob/main/developer-guides/build-transfer-hook.md) — 创建由 EVM 驱动的转账钩子
* [CU 优化](https://github.com/rome-protocol/docs/blob/main/developer-guides/cu-optimization.md) — 降低 CPI 调用的计算单元消耗


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rome.builders/zh/kai-fa-zhe-zhi-nan/call-solana-from-evm.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
