kornrunner/solidity
PHP library for working with Solidity smart contracts, including ABI encoding/decoding and utilities for Ethereum contract interaction. Encode calls, decode results and events, and handle contract data in PHP applications. Lightweight and suitable for transaction building and event parsing.
Install via Composer:
composer require kornrunner/solidity
The package has no framework requirements. It is a small parser that reads Solidity source and exposes contracts, functions, events, and modifiers as PHP objects.
Where to look first
src/Solidity.php — the entry point and the only class you need to construct.tests/ — there is essentially no documentation beyond a thin README, so the test fixtures are the real API reference. Read a test, copy the call shape.src/Element/ — the value objects (Contract, Function, Event, Modifier) that you will iterate over.First use case: signatures and selectors without solc
The primary day-to-day value is extracting function signatures and event signatures from source so you can derive 4-byte selectors and 32-byte log topics in pure PHP — useful when you don't want to shell out to solc or ship compiled JSON artifacts.
use kornrunner\Solidity;
use kornrunner\Keccak;
$source = file_get_contents(__DIR__ . '/contracts/MyToken.sol');
$solidity = new Solidity($source);
$contracts = $solidity->getContracts();
foreach ($contracts as $contract) {
foreach ($contract->getFunctions() as $function) {
$signature = $function->getName() . '(' . implode(',', $function->getArguments()) . ')';
$selector = substr(Keccak::hash($signature, 256), 0, 8);
printf("%-40s 0x%s\n", $signature, $selector);
}
}
If you only need one known contract, start by dumping getContracts() with var_dump on a small fixture — method names and object shapes are easiest to confirm against the tests than to guess from the README.
1. Pair it with kornrunner/keccak and a web3 client.
This package does not hash anything for you. Use it to produce canonical signatures, then hash with Keccak-256 (not SHA3) to get selectors and topics. Selectors feed calldata encoding (web3.php, ethereum-offline-raw-tx); event topics feed log filtering and decoding.
2. Parse once, cache the derived map. Parsing on every request is wasteful and the results rarely change. Build a small codegen step (see pattern 3) or cache keyed on file content:
$key = 'solidity:' . sha1($source);
$map = Cache::rememberForever($key, fn () => $this->buildSelectorMap($source));
Hash the content, not the path, so edits invalidate correctly.
3. Build a code-generated registry in CI.
A console command that walks contracts/*.sol and emits a PHP array or enum of Contract::function => selector is the most reliable long-term pattern. Commit the output, regenerate in CI, and diff against the previous run to catch unexpected churn.
4. Validate against real artifacts. Since this is a hand-rolled parser, add a CI test that compares derived selectors against the ABI produced by Hardhat/Foundry for the same contracts. This is the only defense against silent parser drift on newer Solidity syntax.
5. Wrap it behind your own interface.
Do not let kornrunner\Solidity types leak into your domain. Expose your own SignatureProvider and have the implementation call this package. When (not if) you need to switch to reading ABI JSON, only one class changes.
6. Log decoding.
Build a topic => event signature map at deploy/build time and use it to decode eth_getLogs responses. Combine with indexed-argument awareness from the event definition.
7. Multi-contract files and inheritance.
Parse each contract in a file individually. Treat inherited functions as unresolvable and rely on flattening (Hardhat/Foundry flatten output) or on your ABI validation step for completeness.
override/virtual, user-defined value types, event structs, named mapping parameters — are likely to break or be silently skipped. Smoke-test against your actual sources before adopting.uint must become uint256, address payable must become address, and structs/tuples must be expanded into their component types before hashing. If you hash a raw uint, your selector will be wrong and transactions will revert with no useful error.transfer(address,uint256) must hash to 0xa9059cbb. Put that in a unit test. It catches Keccak-vs-SHA3 mistakes, whitespace in signatures, and type-normalization bugs in one line.hash('sha3-256', ...) in PHP is NIST SHA-3 and gives different output. Use kornrunner/keccak exclusively.How can I help you explore Laravel packages today?