如何使用PHP实现ERC20代币在不同账户间的转账?
Alright, let's tackle how to work with ERC20 tokens using PHP without any Node.js or JavaScript dependencies. All you need is a connection to an Ethereum node (like a local Geth/Parity instance or a service like Infura) and some PHP code to handle JSON-RPC calls and transaction signing. Here's a step-by-step breakdown of the most common operations:
- A running Ethereum node (or access to a hosted node service)
- Basic understanding of ERC20 standard method signatures
- For transaction signing: Install these PHP packages via Composer:
composer require kornrunner/keccak simplito/elliptic-php
1. Fetch ERC20 Contract Basic Info (name, symbol, decimals, totalSupply)
All compliant ERC20 contracts expose these read-only view functions. We'll use eth_call to query them without writing to the blockchain.
Example: Get Token Name
<?php $nodeUrl = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"; // Replace with your node URL $contractAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; // DAI contract example // ERC20 `name` method signature hash: 0x06fdde03 $payload = [ "jsonrpc" => "2.0", "method" => "eth_call", "params" => [ ["to" => $contractAddress, "data" => "0x06fdde03"], "latest" ], "id" => 1 ]; // Send JSON-RPC request $ch = curl_init($nodeUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]); $response = curl_exec($ch); curl_close($ch); // Parse and format result $result = json_decode($response, true); $tokenName = hex2bin(substr($result['result'], 2)); echo "Token Name: " . $tokenName . "\n"; ?>
Other Common Info Methods
- Symbol: Use method signature
0x95d89b41 - Decimals: Use method signature
0x313ce567(returns a uint8, convert hex to decimal directly) - Total Supply: Use method signature
0x18160ddd(returns uint256, convert hex to decimal then divide by decimals for human-readable value)
2. Check an Address's ERC20 Balance
Use the balanceOf method to query how many tokens an address holds.
<?php $nodeUrl = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"; $contractAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; $holderAddress = "0xYourTargetAddressHere"; // Encode address to 64-character hex (left-pad with zeros) $encodedAddress = str_pad(substr($holderAddress, 2), 64, '0', STR_PAD_LEFT); // `balanceOf` method signature + encoded address $data = "0x70a08231" . $encodedAddress; $payload = [ "jsonrpc" => "2.0", "method" => "eth_call", "params" => [["to" => $contractAddress, "data" => $data], "latest"], "id" => 1 ]; // Send request and parse $ch = curl_init($nodeUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response, true); $rawBalance = hexdec($result['result']); $decimals = 18; // Replace with actual token decimals $humanReadableBalance = $rawBalance / (10 ** $decimals); echo "Balance: " . $humanReadableBalance . " DAI\n"; ?>
3. Send ERC20 Tokens to Another Address
This requires signing a transaction with your private key and broadcasting it to the network. It's a write operation, so you'll need to pay gas fees in ETH.
<?php require_once 'vendor/autoload.php'; use Elliptic\EC; use kornrunner\Keccak; $nodeUrl = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"; $contractAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; $fromAddress = "0xYourWalletAddress"; $privateKey = "YOUR_PRIVATE_KEY_WITHOUT_0X_PREFIX"; $toAddress = "0xRecipientAddress"; $amount = 1 * (10 ** 18); // 1 DAI (adjust for token decimals) // Step 1: Get transaction nonce $payloadNonce = [ "jsonrpc" => "2.0", "method" => "eth_getTransactionCount", "params" => [$fromAddress, "pending"], "id" => 1 ]; $ch = curl_init($nodeUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadNonce)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]); $nonce = hexdec(json_decode(curl_exec($ch), true)['result']); // Step 2: Get current gas price $payloadGasPrice = ["jsonrpc" => "2.0", "method" => "eth_gasPrice", "params" => [], "id" => 1]; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadGasPrice)); $gasPrice = hexdec(json_decode(curl_exec($ch), true)['result']); // Step 3: Encode transfer data $encodedTo = str_pad(substr($toAddress, 2), 64, '0', STR_PAD_LEFT); $encodedAmount = str_pad(dechex($amount), 64, '0', STR_PAD_LEFT); $data = "0xa9059cbb" . $encodedTo . $encodedAmount; // `transfer` method signature // Step 4: Build transaction structure $tx = [ 'nonce' => '0x' . dechex($nonce), 'gasPrice' => '0x' . dechex($gasPrice), 'gasLimit' => '0x' . dechex(210000), // Safe gas limit for ERC20 transfers 'to' => $contractAddress, 'value' => '0x0', // No ETH needed for ERC20 transfers 'data' => $data, 'chainId' => 1 // Mainnet=1, Goerli=5, Sepolia=11155111 ]; // Step 5: Sign transaction $ec = new EC('secp256k1'); $key = $ec->keyFromPrivate($privateKey, 'hex'); $txRLP = serializeTransaction($tx); $hash = Keccak::hash(hex2bin($txRLP), 256); $signature = $key->sign($hash, ['canonical' => true]); $r = str_pad($signature->r->toString('hex'), 64, '0', STR_PAD_LEFT); $s = str_pad($signature->s->toString('hex'), 64, '0', STR_PAD_LEFT); $v = dechex($signature->recoveryParam + 35 + $tx['chainId'] * 2); $signedTx = '0x' . $txRLP . $r . $s . $v; // Step 6: Broadcast transaction $payloadSendTx = [ "jsonrpc" => "2.0", "method" => "eth_sendRawTransaction", "params" => [$signedTx], "id" => 1 ]; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadSendTx)); $response = json_decode(curl_exec($ch), true); curl_close($ch); if (isset($response['result'])) { echo "Transaction sent! Hash: " . $response['result'] . "\n"; } else { echo "Error: " . $response['error']['message'] . "\n"; } // Helper: RLP serialize transaction function serializeTransaction($tx) { $elements = [ hex2bin(substr($tx['nonce'], 2)), hex2bin(substr($tx['gasPrice'], 2)), hex2bin(substr($tx['gasLimit'], 2)), hex2bin(substr($tx['to'], 2)), hex2bin(substr($tx['value'], 2)), hex2bin(substr($tx['data'], 2)), hex2bin(dechex($tx['chainId'])), hex2bin('00'), hex2bin('00') ]; return rlpEncode($elements); } function rlpEncode($input) { if (is_string($input)) { if (strlen($input) == 1 && ord($input) < 0x80) return $input; return encodeLength(strlen($input), 0x80) . $input; } elseif (is_array($input)) { $output = ''; foreach ($input as $item) $output .= rlpEncode($item); return encodeLength(strlen($output), 0xc0) . $output; } throw new Exception('Invalid input type'); } function encodeLength($length, $offset) { if ($length < 0x3f) return chr($offset + $length); elseif ($length <= 0xff) return chr($offset + 0x80 + 1) . chr($length); elseif ($length <= 0xffff) return chr($offset + 0x80 + 2) . pack('n', $length); elseif ($length <= 0xffffffff) return chr($offset + 0x80 + 4) . pack('N', $length); throw new Exception('Length too large'); } ?>
内容的提问来源于stack exchange,提问作者Furqan Siddiqui




