Entropy
Entropy is Pyth's verifiable randomness service. It answers a single question: where do I get a random number that no one can manipulate?. Coin flips, NFT trait rolls, lottery draws, loot boxes, free respins — every game on-chain eventually needs this.
Why on-chain randomness is hard
Naive solutions fail in predictable ways:
block.timestamp— miners and sequencers can shift this. Not random.blockhash(block.number - 1)— predictable one block ahead. A miner mints all the rare NFTs.- An off-chain server posting numbers — you have to trust the server. If they roll until they like the outcome, no one can tell.
Entropy fixes this with a commit-reveal scheme: two parties each commit to a secret, then both reveal. Neither one alone controls the result. Both have to deviate to cheat, and any deviation is provable.
The v2 callback flow
Entropy v2 packages the whole dance behind a single function call. Your contract requests a number, Entropy delivers it through a callback. Two transactions, ~10 seconds end-to-end on most chains.
- Your contract calls
entropy.requestV2()and pays a small fee. Entropy stores your request and emits an event. - An off-chain keeper sees the event and fulfills it by calling back into your contract with a random number plus a proof.
- The Entropy contract verifies the proof and invokes
entropyCallback(...)on your contract with the verifiedrandomNumber. Your game logic runs.
Coin flip, end to end
import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
contract CoinFlip is IEntropyConsumer {
IEntropyV2 entropy;
mapping(uint64 => address) public flipper;
event FlipResult(address indexed user, bool heads);
constructor(address _entropy) { entropy = IEntropyV2(_entropy); }
function flip() external payable returns (uint64 seq) {
uint fee = entropy.getFeeV2();
require(msg.value >= fee, "underpaid");
seq = entropy.requestV2{value: fee}();
flipper[seq] = msg.sender;
}
function entropyCallback(
uint64 sequence,
address /* provider */,
bytes32 randomNumber
) internal override {
address user = flipper[sequence];
bool heads = uint256(randomNumber) % 2 == 0;
emit FlipResult(user, heads);
// pay out, mint NFT, whatever your game needs
}
function getEntropy() internal view override returns (address) {
return address(entropy);
}
}Deriving many values from one
bytes32 into many independent uniform numbers, hash it with a counter: keccak256(abi.encode(randomNumber, i)). That gives you a fresh 256-bit number per iteration without paying for another request.What it costs
- A small fee in native gas — usually a fraction of a cent on L2s.
- Two transactions worth of gas: the request and the callback. The keeper pays the callback gas and is reimbursed by Entropy.
- About 5 to 15 seconds of wall-clock latency, depending on the chain.
When NOT to use Entropy
- You need an answer in the same transaction. Use a different design — Entropy is two-step.
- You need cryptographic-grade randomness for a key. Entropy is for game outcomes and selection, not key material.
- You can tolerate predictable results. If a slight miner advantage does not matter, use
prevrandaoand skip the fee.