本文目录导读:

这是一个关于 PHP项目整合以太坊与Web3 的全面指南。
在PHP中与以太坊区块链交互,没有像JavaScript(Ethers.js, Web3.js)那样官方或流行的原生库,常见的做法是通过 HTTP JSON-RPC 直接与以太坊节点(如Geth, Infura)通信,或者使用一些封装好的PHP库。
下面将详细介绍几种主流方案、核心操作示例以及关键注意事项。
核心方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 直接使用 Guzzle/cURL 调用 JSON-RPC | 无依赖,完全可控,理解底层原理 | 代码量大,需要手动处理ABI编码/解码(非常复杂) | 简单的查询(如获取余额、Gas价格) |
| 使用 web3.php (sc0Vu 版本) | 社区最流行,功能较全,封装了RPC调用 | ABI编码仍需额外库(如 web3p/ethereum-tx),文档不算极佳 |
大多数应用场景,需要发送交易、调用合约 |
| 使用 web3.php (iexbase 版本) | 功能简洁,直接 | 更新不够活跃,功能相对少 | 简单合约调用 |
| 使用 Ethereum.php | 集成了ABI编码与签名,方便发送交易 | 对较新的以太坊特性支持可能滞后 | 需要快速实现交易发送的项目 |
| 使用 Laravel-Web3 (包) | 与Laravel框架深度集成,支持门面(Facade) | 仅限Laravel项目 | Laravel项目 |
使用 web3.php (sc0Vu 版本) — 最推荐
这个库是目前PHP社区最活跃、功能最完善的以太坊库。
安装
composer require sc0vu/web3.php
配置连接(连接到 Infura 或本地节点)
<?php require_once 'vendor/autoload.php'; use Web3\Web3; use Web3\Providers\HttpProvider; use Web3\RequestManagers\HttpRequestManager; // 1. 连接到以太坊节点(可以是本地Geth,或Infura) $rpcUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID'; // 使用Infura // $rpcUrl = 'http://127.0.0.1:8545'; // 本地Ganache/Geth $web3 = new Web3(new HttpProvider(new HttpRequestManager($rpcUrl, 30))); echo "连接成功! \n";
基本操作:获取余额与Gas价格
<?php
use Web3\Eth;
use Web3\Utils;
$eth = new Eth($web3->provider);
// 获取地址余额(单位:Wei)
$address = '0xYourAddressHere';
$eth->getBalance($address, function ($err, $balance) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
// 结果为Bignumber对象,需要转换为以太
$ethBalance = Utils::fromWei($balance->toString(), 'ether');
echo '余额: ' . $ethBalance . ' ETH' . PHP_EOL;
});
// 获取当前Gas价格(Wei)
$eth->gasPrice(function ($err, $gasPrice) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
$gasPriceEth = Utils::fromWei($gasPrice->toString(), 'gwei');
echo 'Gas价格: ' . $gasPriceEth . ' Gwei' . PHP_EOL;
});
调用智能合约的 view / pure 函数(读取数据,不需要Gas)
<?php
use Web3\Contract;
// 合约地址
$contractAddress = '0xYourContractAddress';
// 合约ABI (从编译后的JSON中获取)
$abi = '[
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
},
{
"constant": true,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "", "type": "uint256"}],
"type": "function"
}
]';
$contract = new Contract($web3->provider, $abi);
// 调用totalSupply
$contract->at($contractAddress)->call('totalSupply', function ($err, $result) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
// $result 是一个数组,第一个元素是返回值
echo 'Total Supply (Wei): ' . $result[0]->toString() . PHP_EOL;
});
// 调用balanceOf
$ownerAddress = '0xSomeOwnerAddress';
$contract->at($contractAddress)->call('balanceOf', $ownerAddress, function ($err, $result) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
echo 'Balance (Wei): ' . $result[0]->toString() . PHP_EOL;
});
发送交易(写合约或转账ETH)- 核心难点
发送交易需要签名,这意味着你需要使用私钥。绝对不要在服务器代码中硬编码私钥! 应该使用环境变量或安全的密钥管理服务。
这里需要额外的库来处理签名:
composer require web3p/ethereum-tx composer require web3p/ethereum-util
示例:转账ETH
<?php
use Web3\Utils;
use Web3p\EthereumTx\Transaction;
use Web3p\EthereumUtil\Util;
// 1. 准备参数
$fromPrivateKey = '0xYOUR_PRIVATE_KEY'; // 绝对不要硬编码!使用环境变量
$toAddress = '0xRecipientAddress';
$amountInEther = '0.01';
// 2. 获取Nonce(交易计数)
$eth = new Eth($web3->provider);
$fromAddress = (new Util())->privateKeyToPublicKey($fromPrivateKey);
$fromAddress = (new Util())->publicKeyToAddress($fromAddress); // 转换为地址
$eth->getTransactionCount($fromAddress, 'pending', function ($err, $nonce) use ($eth, $fromPrivateKey, $toAddress, $amountInEther) {
if ($err !== null) { /* 处理错误 */ }
// 3. 获取Gas价格
$eth->gasPrice(function ($err, $gasPrice) use ($nonce, $fromPrivateKey, $toAddress, $amountInEther, $eth) {
if ($err !== null) { /* 处理错误 */ }
// 4. 构建交易体
$transaction = new Transaction([
'nonce' => '0x' . dechex($nonce->toString()),
'from' => $fromAddress,
'to' => '0x' . $toAddress, // 去掉地址的0x前缀?看库要求
'gas' => '0x' . dechex(21000), // 简单的ETH转账Gas限制为21000
'gasPrice' => '0x' . $gasPrice->toHex(),
'value' => '0x' . Utils::toWei($amountInEther, 'ether')->toHex(),
'chainId' => 1 // 主网:1, Ropsten:3, Rinkeby:4, 本地Ganache:1337
]);
// 5. 使用私钥签名
$transaction->sign($fromPrivateKey);
// 6. 发送原始交易
$eth->sendRawTransaction('0x' . $transaction->serialize(), function ($err, $txHash) {
if ($err !== null) {
echo '发送失败: ' . $err->getMessage();
return;
}
echo '交易Hash: ' . $txHash . PHP_EOL;
});
});
});
注意:以上代码省略了dechex可能对大数处理的错误,实际生产环境需使用BCMath或GMP库处理大整数。ethereum-tx库内部会处理大数,但你自己构建Hex时要注意。
发送调用合约的函数(写操作)
与上述类似,但需要:
- 构建交易时,
to字段为合约地址。 value字段通常为0x0(除非是payable函数)。data字段必须是从ABI编码后的函数调用数据(包括函数选择器和参数)。gas字段需要估算(使用eth_estimateGas,但手动估算不准确,建议使用库的估算功能)。
web3.php的Contract类提供了send方法简化此过程(但底层仍需签名):
$contract->at($contractAddress)->send('transfer', $toAddress, $amount, [
'from' => $fromAddress,
'gas' => '0x200b2', // 可以不用设置gas,让库估算?但需要签名上下文
], function ($err, $result) {
// 返回的是交易Hash
});
签名上下文是缺失的,你需要将签名逻辑封装起来,或者在调用send之前设置好签名器。web3.php本身不管理私钥,这是为了安全,你可以使用 \Web3p\EthereumTx\Transaction 手动签名后,用 sendRawTransaction 发送。
使用直接cURL调用JSON-RPC(快速演示/简单查询)
如果只是做非常简单的查询(如总供应量),可以不依赖任何库。
function eth_call($rpcUrl, $contractAddress, $data) {
$payload = json_encode([
'jsonrpc' => '2.0',
'method' => 'eth_call',
'params' => [
['to' => $contractAddress, 'data' => $data],
'latest'
],
'id' => 1
]);
$ch = curl_init($rpcUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$result = curl_exec($ch);
curl_close($ch);
$response = json_decode($result, true);
return $response['result'] ?? null;
}
// 示例:获取ERC20代币名称 (函数选择器: 0x06fdde03)
$data = '0x06fdde03'; // 没有参数
$result = eth_call($rpcUrl, '0xTokenContractAddress', $data);
// 结果是一个十六进制字符串,需要解码
echo 'Name (hex): ' . $result;
缺点:对于有参数的函数,你必须自己手动进行ABI编码(将地址、uint等转换成32字节的十六进制),这非常容易出错且繁琐,复杂操作不推荐此方法。
安全与最佳实践
- 私钥安全: 永远不要将私钥硬编码在代码中或提交到Git仓库,使用
.env文件、环境变量、云端密钥管理服务(如AWS KMS, HashiCorp Vault)。 - 错误处理: 所有rpc调用都是异步回调或返回Promise(对于某些库),必须实现健壮的错误处理(网络超时、节点错误、Gas不足、Nonce冲突等)。
- Nonce管理: 对于需要发送多个交易的应用,Nonce必须严格递增,如果并发发送,需要实现Nonce锁定机制,否则容易出现“Nonce too low”错误。
- Gas估算: 总是使用
eth_estimateGas来估算Gas,但注意它可能不准确(尤其是合约内部有复杂逻辑),建议加上Buffer(例如估算值 * 1.2)。 - 链ID: 签名交易时必须指定正确的
chainId(主网:1, 等等),否则交易会在错误的链上重放。 - 使用Bignumber: PHP原生整数对于Wei(18位小数)会溢出,必须使用BCMath或GMP扩展,或者依赖库内部的处理。
web3.php返回的结果通常是phpseclib3\Math\BigInteger对象。
总结推荐
- 如果你是Laravel项目:可以尝试
Laravel-Web3包,它封装得很好。 - 如果你是纯PHP或ThinkPHP等:使用
sc0vu/web3.php搭配web3p/ethereum-tx。- 用
Contract类调用view函数。 - 用
Transaction类手动构建、签名交易,用sendRawTransaction发送。
- 用
- 只在查询余额/总供应量等简单场景:用cURL直接调用JSON-RPC,但不要用于复杂的写操作。
下一步建议:
- 安装
sc0vu/web3.php和web3p/ethereum-tx。 - 连接到Sepolia测试网(而不是主网)进行开发测试。
- 申请Infura或Alchemy的API Key。
- 编写一个简单的脚本:查询自己测试地址的余额。
- 编写一个脚本:调用一个简单的测试合约(如Counter)的
increment()函数(写操作)。
如果在测试中遇到具体的报错(如 Invalid JSON-RPC response,Nonce too low,Insufficient funds),可以查看以太坊节点的错误日志(Geth日志)或使用像Tenderly这样的工具调试交易。