This example stores the drand signature on-chain but does not verify the BLS signature inside the Solidity contract (on-chain BLS12-381 verification is expensive and out of scope for this guide). Consumers who need trustless randomness should either verify the signature off-chain before using the result, or add on-chain BLS verification to the contract.
How it works
- Source contract emits a
RandomnessRequestedevent - Compose task is triggered by the on-chain event
- drand API provides verifiable randomness with BLS signatures
- Target contract records the randomness along with the drand round and signature so consumers can verify it off-chain
Prerequisites
- Goldsky CLI installed
- Foundry for contract deployment
- A funded wallet on Base Sepolia
Project structure
Step 1: Set up the project
Clone the example repository:Step 2: Generate contract types
The project includes aRandomnessConsumer.json ABI in src/contracts/. Generate the typed contract class:
RandomnessConsumer class that provides type-safe contract interaction instead of raw function signature strings.
Step 3: Generate your Compose wallet
Start Compose locally in one terminal:--fork-chains lets you run a smart wallet locally by forking supported chains with TEVM. You can also use a private key wallet for local development — see Secrets for how to store the key.
In another terminal, get your wallet address:
Step 4: Deploy the smart contract
TheRandomnessConsumer.sol contract handles randomness requests and fulfillment:
Step 5: Configure the Compose app
Updatecompose.yaml with your contract address:
src/tasks/fulfill-randomness.ts—TARGET_CONTRACTsrc/tasks/request-randomness.ts—CONTRACT_ADDRESS
Step 6: Understand the fulfillment task
Thefulfill-randomness.ts task handles the core logic:
- Contract codegen —
evm.contracts.RandomnessConsumeris generated from the ABI JSON, providing type-safe method calls likecontract.fulfillRandomness(...)instead of raw function signature strings - Uses
evm.chains.baseSepolia— a built-in chain, no custom configuration needed - Gas is sponsored by default for smart wallets on supported chains. On chains where sponsorship isn’t available, fund the wallet directly or pass
sponsorGas: falseand cover gas from the wallet fulfillRandomnessdoes seven or more SSTOREs (the signature is a 96-bytebytesfield). Gas estimation handles this on Base Sepolia, but if you port this to a chain where you set the gas limit manually, budget ~250k. On Monad, where the full gas limit is charged regardless of gas used, keep the limit as tight as possible
Step 7: Understand the drand integration
Thedrand.ts library fetches verifiable randomness:
sha256(signature) == randomness, using a drand client library.
Step 8: Run locally
Start the Compose app:Step 9: Test the system
Request randomness by calling the contract:- The
RandomnessRequestedevent being detected - Randomness fetched from drand
- The fulfillment transaction submitted
Step 10: Deploy to Goldsky
Once tested locally, deploy to Goldsky’s cloud:Customization
Different chains
Use any supported chain — no custom configuration needed:Different events
Modify thecompose.yaml to listen for different events: