本文目录导读:

在PHP项目中与以太坊交互,最常用的方式是通过HTTP或WebSocket调用以太坊节点的JSON-RPC API,你需要一个运行中的以太坊节点(如Geth、Nethermind、Infura或Alchemy的云节点)。
下面是一个系统性的实现方案,包含库选择、基础代码示例和关键注意事项。
核心方案:使用成熟的PHP库
手动组装JSON-RPC请求虽然可行,但容易出错且功能不全,推荐使用以下两个主流库:
web3p/web3p(原名web3.php):最流行的PHP以太坊库,功能全面,支持合约交互、交易签名、ERC20/ERC721等。kornrunner/ethereum-abi:常与web3p配合使用,专门处理合约ABI编码/解码。
安装(使用Composer):
composer require web3p/web3p composer require kornrunner/ethereum-abi
基础操作示例
以下代码演示了连接节点、查询余额、发送交易和调用智能合约。
1 初始化连接
<?php
require 'vendor/autoload.php';
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
// 1. 连接以太坊节点(这里使用Infura公共节点,生产环境请替换为自己的API Key)
$infuraUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
// 如果连接本地节点(如Geth),使用:'http://127.0.0.1:8545'
$web3 = new Web3(new HttpProvider(new HttpRequestManager($infuraUrl, 10)));
// 2. 检查连接
$web3->clientVersion(function ($err, $version) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
echo 'Connected to: ' . $version . PHP_EOL;
});
2 查询账户余额
// 查询某个地址的ETH余额
$account = '0xYourEthereumAddressHere'; // 注意:地址要有0x前缀
$web3->eth->getBalance($account, function ($err, $balance) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
// 返回的是Wei(最小单位),需要转换为ETH
$ethBalance = $balance->toString() / 1e18;
echo "Balance: " . $ethBalance . " ETH" . PHP_EOL;
});
// 由于PHP是单线程异步,上面的回调可能不会立即执行
// 需要手动调用运行循环(或使用同步封装)
$web3->provider->execute(); // 关键:触发请求执行
3 发送交易(转账ETH)
这需要管理私钥。绝对不要在生产代码中硬编码私钥,推荐使用环境变量或硬件安全模块。
use Web3\KeyStore\FileKeyStore; // 或者使用内存管理
use Web3\Personal;
// 假设你有一个解锁的账户(通过Personal模块)
$personal = new Personal('http://127.0.0.1:8545');
// 发送交易(需要节点开启personal_unlockAccount或使用签名器)
$transaction = [
'from' => '0xYourSourceAddress',
'to' => '0xDestinationAddress',
'value' => '0x' . dechex(0.1 * 1e18), // 0.1 ETH -> Wei -> 十六进制
'gas' => '0x21000', // 21000 gas(简单转账)
'gasPrice' => '0x' . dechex(20e9) // 20 Gwei
];
// 这种方式要求节点直接管理你的账户(不推荐)
$personal->sendTransaction($transaction, 'your_password', function ($err, $tx) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
} else {
echo 'Transaction Hash: ' . $tx . PHP_EOL;
}
});
更安全的做法:离线签名
use Web3\Transaction\Transaction;
use Web3\Utils;
$privateKey = 'YOUR_PRIVATE_KEY_HEX'; // '0xabcd...1234'
$transaction = new Transaction([
'nonce' => '0x10', // 需要先通过eth_getTransactionCount获取
'from' => '0xYourAddress',
'to' => '0xDestinationAddress',
'gas' => '0x21000',
'gasPrice' => '0x' . dechex(20e9),
'value' => '0x' . dechex(0.1 * 1e18),
'chainId' => 1 // 主网=1,Ropsten=3,Rinkeby=4,Goerli=5
]);
// 签名
$signedTx = $transaction->sign($privateKey);
// 发送已签名的原始交易
$web3->eth->sendRawTransaction('0x' . $signedTx, function ($err, $tx) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
} else {
echo 'Transaction Hash: ' . $tx . PHP_EOL;
}
});
4 调用智能合约(只读函数)
use Web3\Contract;
$abi = '[
{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"},
...
]'; // 从合约编译后的ABI JSON复制
$contractAddress = '0xTokenContractAddress';
$contract = new Contract($web3->provider, $abi);
$contract = $contract->at($contractAddress);
// 调用 balanceOf 函数
$contract->call('balanceOf', '0xUserAddress', function ($err, $result) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
echo 'Token Balance: ' . $result[0]->toString() . PHP_EOL;
});
$web3->provider->execute();
5 发送交易调用智能合约(写入函数)
// 例如调用ERC20的transfer函数
$privateKey = '0x...';
$toAddress = '0xReceiver';
$amount = 100 * 1e18; // 100个代币(假设代币有18位小数)
// 构造A编码数据
$contract = new Contract($web3->provider, $abi);
$contract->at($contractAddress);
// 获取函数编码
$data = $contract->getData('transfer', $toAddress, $amount);
// 然后构造交易(如上离线签名部分),设置 'to' => $contractAddress, 'data' => $data
// 发送 signedTx
关键注意事项
-
异步特性:
web3.php底层默认使用 Guzzle HTTP 的异步请求,你需要调用$web3->provider->execute()来真正发送请求并触发回调,如果想用同步方式,可以封装成 Promise 或使用web3.php的同步模式(通过设置HttpRequestManager的超时时间,并手动等待)。 -
私钥安全:
- 绝对不要在代码或版本控制中存储私钥。
- 使用环境变量(
.env文件)、Vault服务、或硬件钱包签名。 - 考虑使用 以太坊签名库(如
kornrunner/ethereum-signer)在本地签名,避免私钥离开你的服务器。
-
Gas 估算:对于写入交易,务必使用
eth_estimateGas预估Gas,避免交易失败或浪费Gas,可以使用$web3->eth->estimateGas($transaction, callback)。 -
Nonce管理:对于高并发的交易发送,需要自己管理nonce(交易计数),使用
eth_getTransactionCount获取当前nonce,并并发安全地递增,或者使用节点自动管理nonce。 -
错误处理:以太坊节点返回的错误可能不直观,常见的错误包括:
nonce too low:nonce过时。insufficient funds:余额不足。intrinsic gas too low:Gas设置过低。revert:合约执行回滚(需要解析revert原因)。
-
连接稳定性:公共节点(Infura、Alchemy)有速率限制,生产环境建议:
- 使用自己的节点(如
geth --syncmode=snap)。 - 或购买商业级API服务。
- 实现重试和退避逻辑。
- 使用自己的节点(如
替代方案与高级用法
- 使用Laravel框架:有社区包
laravel-web3可以更方便地集成。 - 事件监听(WebSocket):如果需要监听新区块或合约事件(如
Transfer事件),可以使用Web3\Providers\WebsocketProvider(需要安装composer require textalk/websocket)。 - 无需节点的轻客户端:如果不想运行节点,可以使用Etherscan API或The Graph进行只读查询,但写入操作仍需节点。
总结步骤
- 选库:
web3p/web3p+kornrunner/ethereum-abi - 连接节点(Infura或自建)
- 实现核心功能:
- 读取:
eth_call(合约只读函数),eth_getBalance,eth_getTransactionReceipt - 写入:离线签名交易,通过
eth_sendRawTransaction发送
- 读取:
- 处理异步回调:调用
$web3->provider->execute()或使用同步封装 - 注意安全:私钥永不泄露,Gas和Nonce管理。
如果你需要更具体的代码示例(比如完整ERC20交互、交易历史查询、NFT相关操作),可以进一步描述你的具体需求。