随着区块链技术的飞速发展,以太坊作为最智能合约平台之一,其上的代币(尤其是遵循 ERC-20 标准的代币)数量激增,对于许多基于 Web 的应用而言,实现与以太坊代币的对接(如代币转账、余额查询、代币转账记录查询等)成为了一项常见需求,PHP 作为一种广泛使用的服务器端脚本语言,同样可以与以太坊网络进行交互,实现代币对接,本文将详细介绍如何使用 PHP 进行以太坊代币对接的基本步骤和核心要点。

准备工作:环境与工具

在开始之前,你需要准备以下几样东西:

  1. PHP 开发环境:确保你的系统已安装 PHP,建议版本为 7.4 或更高,以获得更好的性能和兼容性。
  2. 以太坊节点或 RPC 接口
    • 本地节点:运行自己的以太坊全节点或轻节点(如 Geth, Parity),这提供了最高的数据独立性和隐私性,但对硬件和带宽有一定要求。
    • 第三方 RPC 服务:使用 Infura、Alchemy 等服务商提供的 RPC URL,这是目前更便捷的选择,无需维护节点,但需要注册账号并获取 API Key。
  3. 以太坊钱包与私钥(可选,用于发送交易):如果你的应用需要发起代币转账等交易,你需要一个包含足够 ETH 支付 gas 费用的钱包,以及对应的私钥(注意:私钥极度敏感,切勿泄露,建议使用硬件钱包或妥善加密存储)。
  4. PHP 以太坊库:我们将使用 web3.php 库,这是以太坊官方 JavaScript 库 web3.js 的 PHP 移植版,功能强大且文档相对完善,可以通过 Composer 安装:
    composer require sc0vu/web3.php

连接以太坊网络

我们需要使用 PHP 连接到以太坊网络,这通常通过 RPC 接口实现。

<?php
require 'vendor/autoload.php';
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
// 替换为你的以太坊节点 RPC URL
$rpcUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
// 创建 HTTP Provider
$provider = new HttpProvider(new HttpRequestManager($rpcUrl, 5000)); // 5000 是超时时间(毫秒)
// 创建 Web3 实例
$web3 = new Web3($provider);
// 测试连接
$web3->eth->blockNumber(function ($err, $blockNumber) {
    if ($err !== null) {
        echo "Error: " . $err->getMessage();
        return;
    }
    echo "Current Ethereum Block Number: " . $blockNumber->toString() . "\n";
});
?>

YOUR_INFURA_PROJECT_ID 替换为你在 Infura 上创建的项目 ID,运行上述代码,如果能正确返回当前区块号,说明连接成功。

对接 ERC-20 代币的核心:合约 ABI 与地址

ERC-20 代币的核心功能是通过智能合约实现的,要与代币交互,我们需要两个关键信息:

  1. 代币合约地址:每个代币在以太坊上都有一个唯一的合约地址,USDT 的主网合约地址是 0xdAC17F958D2ee523a2206206994597C13D831ec7
  2. 合约 ABI (Application Binary Interface):这是定义智能合约接口的 JSON 文件,包含了所有可调用的函数及其参数和返回值类型,对于 ERC-20 代币,标准的 ABI 包括 balanceOf(), transfer(), transferFrom(), approve(), allowance(), totalSupply(), decimals(), symbol(), name() 等函数。

你可以从 Etherscan、合约官方文档或代币项目方获取这些信息,在 Etherscan 上代币页面点击 "Contract" -> "Contract ABI" 即可复制。

常用代币交互操作

查询代币余额

要查询某个地址的代币余额,我们需要使用代币合约的 balanceOf(address) 函数。

<?php
// 假设已连接 $web3
// 代币合约地址 (USDT)
$tokenContractAddress = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
// 要查询余额的地址
$queryAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e';
// 代币合约 ABI (简化版,实际使用需要完整的 ERC20 ABI)
$erc20Abi = json_decode('[{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]', true);
// 创建合约实例
$contract = $web3->eth->contract($erc20Abi, $tokenContractAddress);
// 调用 balanceOf 函数
$contract->call('balanceOf', $queryAddress, function ($err, $balance) {
    if ($err !== null) {
        echo "Error: " . $err->getMessage();
        return;
    }
    // 余额是 uint256 类型,通常需要根据代币的 decimals 进行转换
    // USDT 的 decimals 是 6
    $decimals = 6; // 这里需要根据具体代币设置
    $formattedBalance = bcdiv($balance->toString(), bcpow(10, $decimals), $decimals);
    echo "Token Balance: " . $formattedBalance . "\n";
});
?>

注意balanceOf 返回的是最小单位(如 wei 对于 ETH,satoshi 对于 BTC),代币也有自己的 decimals(小数位数),通常需要将结果除以 10^decimals 才是我们常见的显示单位,你需要先调用 decimals() 函数获取代币的小数位数。

获取代币基本信息(名称、符号、小数位数)

<?php
// 假设已连接 $web3 和 $contract 实例
// 获取名称
$contract->call('name', function ($err, $name) {
    if ($err === null) {
        echo "Token Name: " . $name . "\n";
    }
});
// 获取符号
$contract->call('symbol', function ($err, $symbol) {
    if ($err === null) {
        echo "Token Symbol: " . $symbol . "\n";
    }
});
// 获取小数位数
$contract->call('decimals', function ($err, $decimals) {
    if ($err === null) {
        echo "Token Decimals: " . $decimals->toString() . "\n";
    }
});
?>

发起代币转账 (transfer)

代币转账需要构建一个交易并发送到以太坊网络,这需要发送者的私钥进行签名。

<?php
// 假设已连接 $web3 和 $contract 实例
// 发送者私钥 (极度敏感,实际应用中务必安全处理!)
$privateKey = 'YOUR_PRIVATE_KEY_WITHOUT_0X';
// 接收者地址
$toAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e';
// 转账数量 (假设代币 decimals 是 6,要转 100 个代币)
$amount = bcmul('100', bcpow(10, 6), 0); // 转换为最小单位
// 构建交易参数
$transaction = [
    'from' => 'YOUR_SENDER_ADDRESS_WITHOUT_0X', // 发送者地址,从私钥推导或提供
    'to' => $tokenContractAddress, // 代币合约地址
    'value' => '0x0', // 对于代币转账,value 通常为 0
    'gas' => '0x100000', // Gas 限制,根据实际情况调整
    'gasPrice' => $web3->eth->gasPrice, // 获取当前建议的 Gas Price
    'nonce' => $web3->eth->getTransactionCount('YOUR_SENDER_ADDRESS', 'latest'), // 获取 nonce
    'data' => $contract->at('transfer')->encodeABI($toAddress, $amount) // 编码 transfer 函数调用数据
];
// 使用私钥签名交易
$web3->eth->sendRawTransaction($transaction, $privateKey, function ($err, $txHash