Why on-chain randomness matters โ and how ours actually works.
THE PROBLEM WITH "RANDOM"
Most APIs that claim to generate random numbers are black boxes. You call an endpoint, get a number back, and trust that it wasn't manipulated. There's no audit trail, no proof, no way to verify after the fact. For games, lotteries, agent-to-agent decisions, or anything where fairness matters โ trust isn't enough. You need proof.
HOW ON-CHAIN RNG WORKS
Our RandomOracle smart contract lives on Base (Ethereum L2, chain 8453). When you request a random number, we don't generate it on a server โ we call random() on-chain. The contract combines four entropy sources into a single hash:
A
blockhash(block.number - 1) โ The hash of the previous block. Determined by the entire network of validators; no single party controls it.
B
block.prevrandao โ A pseudo-random value injected by the beacon chain's RANDAO accumulator. Updated every slot by every validator that proposes a block.
C
msg.sender โ The address of the caller. Adds caller-specific entropy so two requests in the same block produce different results.
D
nonce โ A contract-internal counter that increments on every call. Guarantees uniqueness even if all other inputs are identical.
These four values are packed together and run through keccak256 โ Ethereum's native cryptographic hash function. The result is a 256-bit seed that's unpredictable before the transaction is mined and immutable after.
THE SOLIDITY (IT'S ~30 LINES)
function random() external onlyOwner returns (uint256) {
uint256 seed = uint256(keccak256(
abi.encodePacked(
blockhash(block.number - 1),
block.prevrandao,
msg.sender,
nonce
)
));
nonce++;
emit RandomGenerated(msg.sender, seed, nonce);
return seed;
}
Full source verified on Sourcify. Read it yourself โ that's the point.
FROM SEED TO RESULT
The contract returns a raw 256-bit integer. Our API server then shapes it into whatever you asked for:
- ๐ฏ /random/number โ
seed % (max - min + 1) + min
- ๐ช /random/coin โ
seed % 2 === 0 ? "heads" : "tails"
- ๐ฒ /random/dice โ Each die uses a derived sub-seed:
keccak256(seed, i) % sides + 1
- ๐ข /random/sequence โ N numbers, each from
keccak256(seed, i), mapped to your range
- ๐ /random/shuffle โ Fisher-Yates shuffle using derived sub-seeds for each swap
All derivations are deterministic from the on-chain seed. Given the same seed, anyone can reproduce the exact same result. That's verifiability.
VERIFYING A RESULT
Every API response includes a proof object with the transaction hash. To verify:
- Copy the
txHash from the response
- Look it up on Basescan
- Check the
RandomGenerated event in the logs โ it contains the exact seed returned
- The seed, block, sender, and nonce are all visible on-chain โ permanently, immutably
No trust required. Just math and a public ledger.
SECURITY MODEL & HONEST TRADEOFFS
This is not a VRF (Verifiable Random Function) like Chainlink VRF. A VRF gives you cryptographic proof that the random number was generated correctly by a specific key. Our approach is simpler and cheaper:
- โก Cost: ~$0.0002 per on-chain call (gas). VRF costs $0.10โ$1+ per request.
- โก Speed: One transaction, one block (~2s on Base). VRF needs a callback (2+ blocks).
- โ ๏ธ Tradeoff: A block proposer could theoretically influence
prevrandao, but the economic cost of doing so vastly exceeds any gain from manipulating a $0.05 API call.
For high-stakes applications (on-chain lotteries with $100k+ pools), use Chainlink VRF. For everything else โ games, agent decisions, fair selections, content generation โ this is faster, cheaper, and still verifiable on-chain.
WHY x402 PAYMENTS
Traditional APIs make you sign up, get an API key, manage billing, and handle rate limits. x402 flips this: your client pays with USDC on Base, per-request, inline with the HTTP protocol. No accounts. No keys. No invoices. An AI agent can discover the endpoint and start using it immediately โ the payment is the authentication.
This is what agent-native commerce looks like. The Random Oracle is one of the first services built this way.