Ethereum

Ethereum

PHP

web3.php is the established way to reach Ethereum from PHP, and it covers the JSON-RPC surface you need to read balances, send transactions, and call contracts on chain 1. Install it with composer require web3p/web3.php. One thing to know up front: the library predates PHP's async story, so every eth method takes a callback of the form function ($err, $result) rather than returning a value. That style spreads quickly if you sprinkle it across controllers, so wrap it in a service class — like the EthereumService below — that resolves each callback into a plain return value and keeps your application code linear.

web3.php delivers every Ethereum result through a callback rather than a return value. In a Laravel app, push that detail into a service class so controllers ask for an ETH balance and get a string back, with the callback plumbing hidden one layer down.

Web3.php

Construction nests three objects: new Web3(new HttpProvider(new HttpRequestManager('https://ethereum.therpc.io/YOUR_API_KEY'))). The request manager handles the HTTP transport, the provider wraps it as JSON-RPC, and Web3 exposes the namespaces. From there $web3->eth->getBalance(...) hands its result to your callback as ($err, $result). Always check $err first — on a bad address or a transport failure web3.php passes the error there and leaves $result empty, so reading $result without the guard gives you a silent null instead of a thrown exception.

<?php
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
class EthereumService
{
private $web3;
public function __construct()
{
$this->web3 = new Web3(new HttpProvider(new HttpRequestManager('https://ethereum.therpc.io/YOUR_API_KEY')));
}
public function getBalance(string $address): string
{
$balance = null;
$this->web3->eth->getBalance($address, 'latest', function ($err, $result) use (&$balance) {
if ($err !== null) {
throw new Exception($err->getMessage());
}
$balance = $result;
});
return $this->web3->utils->fromWei($balance, 'ether');
}
public function sendTransaction(array $transaction): string
{
$hash = null;
$this->web3->eth->sendTransaction($transaction, function ($err, $result) use (&$hash) {
if ($err !== null) {
throw new Exception($err->getMessage());
}
$hash = $result;
});
return $hash;
}
}

Laravel Integration

In Laravel, keep the endpoint out of your code: put it in .env and read it through config('services.ethereum.node_url'), so the URL with your key never lands in a commit or a stack trace. The second win is caching. An ETH balance doesn't change between blocks, and Ethereum produces one roughly every 12 seconds, so wrapping the read in Cache::remember($key, 300, ...) serves the same address from cache for a few minutes and spares your CU budget the duplicate calls a busy dashboard would otherwise fire.

<?php
namespace App\Services;
use Web3\Web3;
use Illuminate\Support\Facades\Cache;
class EthereumService
{
private $web3;
public function __construct()
{
$this->web3 = new Web3(config('services.ethereum.node_url'));
}
public function getCachedBalance(string $address): string
{
return Cache::remember("eth_balance_{$address}", 300, function () use ($address) {
return $this->getBalance($address);
});
}
public function getTransactionCount(string $address): int
{
$count = null;
$this->web3->eth->getTransactionCount($address, 'latest', function ($err, $result) use (&$count) {
if ($err !== null) {
throw new Exception($err->getMessage());
}
$count = hexdec($result);
});
return $count;
}
}

Smart Contract Integration

For contracts, construct web3.php's Contract from the provider and ABI, then bind it to a deployed address with ->at($contractAddress). Reads run through ->call($method, $params, $callback), which performs an eth_call against Ethereum and delivers the decoded return value to the callback — no gas, no signing. The same callback discipline applies: check $err before trusting $response, so a reverted balanceOf on an ERC-20 surfaces as an error rather than a misleading zero.

<?php
use Web3\Contract;
class SmartContractService
{
private $contract;
public function __construct(string $abi, string $contractAddress)
{
$web3 = new Web3(new HttpProvider(new HttpRequestManager('https://ethereum.therpc.io/YOUR_API_KEY')));
$this->contract = new Contract($web3->provider, $abi);
$this->contract->at($contractAddress);
}
public function callMethod(string $method, array $params = [])
{
$result = null;
$this->contract->call($method, $params, function ($err, $response) use (&$result) {
if ($err !== null) {
throw new Exception($err->getMessage());
}
$result = $response;
});
return $result;
}
}

Error Handling

Define an EthereumException that carries the original error alongside your own message. Because web3.php reports failures through the $err callback argument, a thin wrapper that rethrows them as a single typed exception lets the rest of the app use ordinary try/catch and pattern-match one class. The example keeps the raw error reachable through getEthError(), so you can log the exact RPC failure from Ethereum while still showing the user a clean message.

<?php
class EthereumException extends Exception
{
private $ethError;
public function __construct(string $message, $ethError = null)
{
parent::__construct($message);
$this->ethError = $ethError;
}
public function getEthError()
{
return $this->ethError;
}
}
class SafeEthereumService
{
public function safeGetBalance(string $address): string
{
try {
return $this->getBalance($address);
} catch (Exception $e) {
throw new EthereumException(
"Failed to get balance for address: {$address}",
$e
);
}
}
}

Ready to call this in production?

Free tier covers personal projects. Pay-as-you-go scales without a card.