在当今区块链技术飞速发展的时代,以太坊作为全球领先的智能合约平台,吸引了无数开发者的目光,对于许多基于 Web 技术栈的开发者而言,使用 PHP 与以太坊进行交互是一个常见的需求,而实现这种交互的核心方式之一,就是通过以太坊的 JSON-RPC (JSON-RPC) 接口,本文将详细介绍如何使用 PHP 对接以太坊 RPC,实现从环境搭建到具体功能调用的一系列操作。

什么是以太坊 RPC?

以太坊 JSON-RPC 是一个标准的通信协议,它允许客户端(如我们的 PHP 应用程序)与以太坊节点(如 Geth, Parity 或 Infura 这样的远程节点)进行通信,通过发送 JSON 格式的请求,客户端可以调用节点提供的各种方法,例如查询账户余额、发送交易、部署智能合约、读取智能合约状态等,你可以把 RPC 看作是 PHP 应用与以太坊区块链之间的“翻译官”和“桥梁”。

准备工作

在开始编码之前,我们需要准备以下几样东西:

  1. PHP 环境:确保你的系统已经安装了 PHP,建议版本在 7.4 或更高,以获得更好的性能和安全性支持。
  2. 以太坊节点访问方式
    • 本地节点:在你的机器上运行一个以太坊客户端,如 Geth 或 Parity,优点是完全自主可控,缺点是对硬件资源要求较高,同步区块可能需要较长时间和大量存储空间。
    • 远程节点服务(推荐初学者):使用 Infura、Alchemy 等提供的远程节点服务,你只需要注册账号,获取一个 RPC URL 即可,无需维护节点,非常方便,Infura 提供的以太坊主网或测试网 RPC URL。
  3. PHP HTTP 客户端库:PHP 本身没有内置直接、高效的 JSON-RPC 客户端,我们可以使用 cURL 扩展(PHP 默认自带)或者一些轻量级的 HTTP 客户端库,如 Guzzle,本文主要使用 cURL 进行演示,因为它更通用,无需额外安装依赖。
  4. 以太坊钱包(可选,用于发送交易):如果你需要发送交易,你需要一个包含一定 ETH 的钱包,以及对应的私钥(注意:私钥务必妥善保管,切勿泄露)。

PHP 对接以太坊 RPC 核心步骤

对接以太坊 RPC 的核心步骤无非就是:构造 JSON-RPC 请求 -> 通过 HTTP 发送请求 -> 接收并解析 JSON 响应。

  1. 构造 JSON-RPC 请求

    一个标准的 JSON-RPC 请求包含以下字段:

    • jsonrpc: 版本,通常为 "2.0"。
    • method: 要调用的以太坊 RPC 方法名,如 eth_blockNumber, eth_getBalance
    • params: 方法调用所需的参数数组,顺序和类型需与方法定义一致,如果没有参数则为空数组。
    • id: 请求 ID,用于标识请求,响应中会包含相同的 ID。

    获取最新区块号的请求 JSON 如下:

    {
        "jsonrpc": "2.0",
        "method": "eth_blockNumber",
        "params": [],
        "id": 1
    }
  2. 发送 HTTP 请求并接收响应

    我们可以使用 PHP 的 cURL 扩展来发送 POST 请求到以太坊节点的 RPC URL。

PHP 代码示例

下面我们通过几个常用的场景来展示具体的 PHP 代码实现。

连接以太坊节点并获取最新区块号

<?php
/**
 * 以太坊 RPC 客户端类示例
 */
class EthereumRpcClient
{
    private $rpcUrl;
    private $id = 0;
    public function __construct($rpcUrl)
    {
        $this->rpcUrl = $rpcUrl;
    }
    /**
     * 发送 JSON-RPC 请求
     * @param string $method 方法名
     * @param array $params 参数数组
     * @return array 响应结果
     * @throws Exception
     */
    public function request($method, $params = [])
    {
        $this->id  ;
        $payload = json_encode([
            'jsonrpc' => '2.0',
            'method' => $method,
            'params' => $params,
            'id' => $this->id
        ]);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->rpcUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 设置超时时间
        $response = curl_exec($ch);
        if (curl_errno($ch)) {
            throw new Exception('cURL Error: ' . curl_error($ch));
        }
        curl_close($ch);
        $result = json_decode($response, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception('JSON Decode Error: ' . json_last_error_msg());
        }
        // 检查 RPC 响应中的 error 字段
        if (isset($result['error']) && $result['error'] !== null) {
            throw new Exception('RPC Error: ' . $result['error']['message'] . ' (Code: ' . $result['error']['code'] . ')');
        }
        return $result['result'] ?? null;
    }
}
// 使用示例
try {
    // 替换成你的 Infura 或其他节点服务的 RPC URL
    // 测试网示例 (Goerli)
    $rpcUrl = 'https://goerli.infura.io/v3/YOUR_INFURA_PROJECT_ID';
    // 主网示例 (需要替换为你的实际 RPC URL)
    // $rpcUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
    $client = new EthereumRpcClient($rpcUrl);
    // 1. 获取最新区块号
    $latestBlockNumber = $client->request('eth_blockNumber');
    echo "最新区块号 (Hex): " . $latestBlockNumber . "\n";
    // 将十六进制区块号转为十进制
    $latestBlockNumberDec = hexdec($latestBlockNumber);
    echo "最新区块号 (Dec): " . $latestBlockNumberDec . "\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}
?>

查询指定地址的 ETH 余额

// 接续上面的 EthereumRpcClient 类
// 使用示例
try {
    $client = new EthereumRpcClient($rpcUrl); // 确保 $rpcUrl 已定义
    // 要查询的地址
    $address = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'; // 示例地址,可以替换成你想查询的地址
    // eth_getBalance 的第二个参数是区块号,可以是 'latest', 'pending', 'earliest' 或十六进制区块号
    $balance = $client->request('eth_getBalance', [$address, 'latest']);
    echo "地址 {$address} 的余额 (Hex Wei): " . $balance . "\n";
    // 将 Wei 转为 ETH (1 ETH = 10^18 Wei)
    $balanceEth = bcdiv(hexdec($balance), 1000000000000000000, 18);
    echo "地址 {$address} 的余额 (ETH): " . $balanceEth . "\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

发送交易(转账 ETH)

发送交易比查询操作要复杂,因为它需要私钥签名。注意:在实际应用中,切勿在代码中硬编码私钥! 应该使用硬件钱包、环境变量或专门的密钥管理服务。

这里我们为了演示,会使用一个私钥,但请务必理解其风险。

// 接续上面的 EthereumRpcClient 类
// 辅助函数:将私钥转换为地址(简化版,实际应使用更安全的库如 web3.php)
function privateKeyToAddress($privateKey) {
    // 这里仅作概念演示,实际需要椭圆曲线运算等复杂逻辑
    // 通常我们会使用如 `web3.php` 这样的库来处理
    // 假设这个函数能返回地址
    // 注意:这是一个简化的示意,真实实现非常复杂且不安全直接写
    return '0x' . substr(keccak256(hex2bin(str_pad($privateKey, 64, '0', STR_PAD_LEFT))), -40);
}
// 辅助函数:签名交易