From grimoire
Use when writing Solidity contracts that need randomness — NFT mints, lotteries, games — replacing insecure block variable entropy with Chainlink VRF or commit-reveal to prevent miner manipulation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-smart-contract-randomnessThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use Chainlink VRF (Verifiable Random Function) or commit-reveal schemes instead of block variables — preventing validators from manipulating outcomes in lotteries, NFT mints, and on-chain games.
Use Chainlink VRF (Verifiable Random Function) or commit-reveal schemes instead of block variables — preventing validators from manipulating outcomes in lotteries, NFT mints, and on-chain games.
Adopted by: OWASP Smart Contract Top 10 SC10 (Insufficient Randomness). SWC-120 (Weak Sources of Randomness from Chain Attributes) is the canonical weakness. Chainlink VRF is the industry standard, used by Axie Infinity, Apes NFT, and dozens of DeFi protocols for verifiable on-chain randomness. The Ethereum Foundation's security documentation explicitly warns against using block.prevrandao, block.timestamp, or blockhash for any outcome with financial value.
Impact: The SmartBillions lottery was exploited in 2017 — an attacker manipulated their own transaction to only execute when block.blockhash produced a winning result. The Fomo3D game (2018) was gamed by miners who manipulated block timestamps to time their entries. Axie Infinity's early NFT minting using blockhash was exploited before migration to Chainlink VRF. Using keccak256(block.timestamp, msg.sender) as randomness allows the miner who produces the block to selectively include or exclude transactions based on the outcome.
Why best: All EVM block variables (block.timestamp, block.prevrandao, blockhash, block.difficulty) are either known in advance by validators or manipulable with modest resources. Chainlink VRF generates randomness off-chain with a cryptographic proof that the result was not manipulated — the proof is verifiable on-chain before the random value is used. Commit-reveal provides a cheaper alternative where manipulation resistance comes from economic cost rather than cryptographic proof.
Sources: OWASP Smart Contract Top 10 SC10; SWC-120; Chainlink VRF v2.5 documentation; Ethereum Foundation Smart Contract Best Practices
Use Chainlink VRF v2.5 for verifiable on-chain randomness:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import "@chainlink/contracts/src/v0.8/vrf/dev/interfaces/IVRFCoordinatorV2Plus.sol";
contract FairLottery is VRFConsumerBaseV2Plus {
IVRFCoordinatorV2Plus public immutable vrfCoordinator;
uint256 public immutable subscriptionId;
bytes32 public immutable keyHash;
mapping(uint256 => address) private requestToPlayer;
mapping(address => uint256) public winnings;
uint32 private constant CALLBACK_GAS_LIMIT = 200_000;
uint16 private constant REQUEST_CONFIRMATIONS = 3;
uint32 private constant NUM_WORDS = 1;
constructor(address coordinator, uint256 subId, bytes32 _keyHash)
VRFConsumerBaseV2Plus(coordinator)
{
vrfCoordinator = IVRFCoordinatorV2Plus(coordinator);
subscriptionId = subId;
keyHash = _keyHash;
}
function enterLottery() external payable returns (uint256 requestId) {
require(msg.value == 0.01 ether, "Entry fee required");
requestId = vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: keyHash,
subId: subscriptionId,
requestConfirmations: REQUEST_CONFIRMATIONS,
callbackGasLimit: CALLBACK_GAS_LIMIT,
numWords: NUM_WORDS,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
requestToPlayer[requestId] = msg.sender;
}
// Called by Chainlink with cryptographically proven random number
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
address player = requestToPlayer[requestId];
uint256 outcome = randomWords[0] % 100; // 0–99
if (outcome < 10) { // 10% win chance
winnings[player] += address(this).balance / 2;
}
}
}
Use commit-reveal for cheaper manipulation resistance:
contract CommitRevealRandom {
mapping(address => bytes32) public commitments;
mapping(address => uint256) public revealBlock;
uint256 private constant REVEAL_DELAY = 5; // must reveal after 5 blocks
// Phase 1: player commits hash of their secret
function commit(bytes32 commitment) external {
commitments[msg.sender] = commitment;
revealBlock[msg.sender] = block.number + REVEAL_DELAY;
}
// Phase 2: player reveals secret, combined with block hash for randomness
function reveal(uint256 secret) external returns (uint256 randomNumber) {
require(block.number >= revealBlock[msg.sender], "Too early to reveal");
require(block.number <= revealBlock[msg.sender] + 250, "Reveal window expired");
bytes32 commitment = commitments[msg.sender];
require(keccak256(abi.encodePacked(secret)) == commitment, "Invalid reveal");
// Combine player secret with blockhash — neither party can manipulate alone
bytes32 blockHash = blockhash(revealBlock[msg.sender]);
randomNumber = uint256(keccak256(abi.encodePacked(secret, blockHash)));
delete commitments[msg.sender];
}
}
Never use these as randomness sources:
// ALL of these are manipulable by validators or predictable:
uint256 bad1 = uint256(blockhash(block.number)); // always 0 for current block
uint256 bad2 = block.timestamp; // manipulable ±15 seconds
uint256 bad3 = block.prevrandao; // biasable by validators
uint256 bad4 = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender)));
// A validator/miner who sees a losing outcome can simply drop the transaction
// and re-include it in the next block they produce.
For NFT mints: use VRF to assign traits after mint, not during:
contract FairNFT {
uint256[] public tokenIds;
mapping(uint256 => uint256) public tokenTraits; // assigned after VRF response
function mint() external payable returns (uint256 tokenId) {
tokenId = tokenIds.length;
tokenIds.push(tokenId);
// Don't assign traits yet — request VRF
_requestVRFForToken(tokenId);
}
function _fulfillVRF(uint256 tokenId, uint256 randomWord) internal {
// Assign traits after mint — attacker can't abort the tx after seeing traits
tokenTraits[tokenId] = randomWord % NUM_TRAIT_COMBINATIONS;
}
}
block.prevrandao (formerly block.difficulty) is not secure randomness — validators can bias it by choosing when to propose a block.keccak256(abi.encodePacked(randomWord, index)) to derive multiple values from one VRF response.blockhash(block.number - 1) — this is slightly better than current block but still known to the miner who just produced that block before the current miner.keccak256 derivation for independence.npx claudepluginhub jeffreytse/grimoire --plugin grimoireIntegrates Cartridge VRF into Cairo contracts for provably fair, atomic randomness in Starknet Dojo games. Covers Scarb.toml setup, usage with nonce/salt sources, and JS/TS client tx prefixing.
Teaches essential Ethereum mental models for onchain development: nothing automatic, incentives first, state machines, CROPS. Activates for new devs, system design, or 'how it works' questions.
Prevents Solidity smart contract vulnerabilities like reentrancy, overflows, and access control using secure patterns, Checks-Effects-Interactions, and ReentrancyGuard. For writing and auditing contracts.