kornrunner/ethereum-util
kornrunner/ethereum-util is a lightweight PHP library for common Ethereum utilities, including address validation, EIP-55 checksum handling, and conversion helpers for working with keys and hex data in PHP applications.
composer require kornrunner/ethereum-util
There is no service provider, no config file, and no facade to register — the package is a single class of static helpers (namespace kornrunner\EthereumUtil; confirm with grep -n 'public static function' vendor/kornrunner/ethereum-util/src/*.php, which is genuinely the fastest way to read the whole API). That also means it works the same in Laravel, Symfony, or a bare script.
Two things to verify before your first commit:
ext-bcmath is installed (wei math needs arbitrary precision — a float will silently lose precision at 18 decimals).composer.json; it tracks recent PHP versions.The highest-value first use case is not crypto at all — it's address hygiene on user input. Users paste addresses from block explorers, spreadsheets, and chat apps, with mixed casing, missing 0x, or trailing whitespace.
use kornrunner\EthereumUtil;
// 1. Validate before it reaches your DB or an RPC call
if (! EthereumUtil::isValidAddress($request->input('wallet'))) {
// reject
}
// 2. Normalize to EIP-55 checksum for storage and display
$address = EthereumUtil::toChecksumAddress($request->input('wallet'));
// 3. Convert at the fiat/UI boundary only
EthereumUtil::toWei('0.05', 'ether'); // "50000000000000000"
EthereumUtil::fromWei($wei, 'ether'); // "0.05"
EthereumUtil::fromWei($gasPriceWei, 'gwei');
The unit ladder follows the Ethereum naming convention (wei, kwei/babbage, mwei/lovelace, gwei/shannon, szabo, finney, ether, kether, …), so gwei for gas display and ether for user-facing balances are both one call away.
Store wei as a string, everywhere. Never let a wei value touch a PHP int (64-bit overflow at ~9.2 ETH) or a float. Use DECIMAL(65,0) or VARCHAR(78) in MySQL; convert to ether only in presenters/API resources.
// Eloquent cast so the model always hands you an exact string
protected $casts = ['balance_wei' => 'string'];
Wrap the statics in one injectable service. This is the single most valuable thing you can do, because it makes every call site testable and gives you one place to add caching or swap implementations later.
final class Ethereum
{
public function toChecksum(string $address): string
{
return EthereumUtil::toChecksumAddress(strtolower(trim($address)));
}
public function weiToEther(string $wei, int $decimals = 6): string
{
return number_format((float) EthereumUtil::fromWei($wei, 'ether'), $decimals, '.', '');
}
}
Note the deliberate (float) only at the display step — the lossy conversion happens on a formatted human string, never on a stored value.
Custom validation rule. Wrap the check so the error message is domain-specific and the rule is reusable across FormRequests:
class EthereumAddressRule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (! is_string($value) || ! EthereumUtil::isValidAddress($value)) {
$fail('The :attribute must be a valid Ethereum address.');
}
}
}
Normalize on write, in a model mutator. Store the checksummed form so WHERE address = ? lookups are consistent and copy-paste out of your UI is verifiable by the user.
Hashing for topics and selectors. keccak256 gives you ERC-20 event topics and 4-byte function selectors without pulling in a full ABI library:
$topic0 = EthereumUtil::keccak256('Transfer(address,address,uint256)');
// 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
$selector = substr(EthereumUtil::keccak256('transfer(address,uint256)'), 0, 10);
// 0xa9059cbb
These two constants are also excellent smoke tests that your install is wired up correctly.
Know where the boundary is. This package does not sign transactions, encode ABI payloads, or derive addresses from private keys. For those, pair it with kornrunner/ethereum-address (key → address), kornrunner/ethereum-offline-raw-tx (build/sign), or a JSON-RPC client. Use ethereum-util purely as the formatting/validation layer in front of them.
toWei. toWei(0.1, 'ether') is a precision bug, not a convenience. Pass strings: toWei('0.1', 'ether'). If a value arrives as a float from an API, cast via number_format($v, 18, '.', '') first, and treat that as a code smell.hash('sha3-256', ...) produces different output. Always go through this package (it depends on kornrunner/keccak), and never "optimise" it away to a native hash — your event topics will silently stop matching.0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAeD should fail. Test this explicitly — plenty of implementations only do the regex check.toChecksumAddress is usually fine, but lowercasing first makes the operation idempotent and removes a class of "works in dev, not in prod" bugs where upstream data has odd casing.fromWei returns a string with full precision, not a formatted number. fromWei('1', 'ether') is '0.000000000000000001'; you'll still need number_format/bcdiv for display, and you should decide explicitly whether to round or truncate. Round-trip test fromWei(toWei($x)) in your suite.0x prefix handling is inconsistent across Ethereum tooling. Decide once (I'd store with 0x) and strip/restore at the edges; if the package offers a stripZero-style helper, use it rather than ltrim($x, '0x') — that's a character-class trim and will eat leading zeros from the payload.'Ether' or 'ETH' won't resolve. Validate user-supplied units against an allowlist before handing them over.EthereumUtil:: directly from a controller you cannot stub it in a test and you cannot swap it if EIP-55 or a unit definition changes. The thin wrapper above costs 20 lines and pays for itself immediately.app/Support in an afternoon if it ever goes stale.0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed; if a hash looks wrong, check the 0xddf252ad… Transfer topic above; if a number looks wrong, check whether bcmath is actually loaded (php -m | grep bcmath) before you suspect the library.How can I help you explore Laravel packages today?