Skip to main content
This guide walks you through building a randomness delivery system using Compose and drand, a distributed randomness beacon. The system listens for on-chain randomness requests and fulfills them with drand values that anyone can verify off-chain against drand’s BLS public key.
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

  1. Source contract emits a RandomnessRequested event
  2. Compose task is triggered by the on-chain event
  3. drand API provides verifiable randomness with BLS signatures
  4. Target contract records the randomness along with the drand round and signature so consumers can verify it off-chain

Prerequisites

Project structure

Step 1: Set up the project

Clone the example repository:

Step 2: Generate contract types

The project includes a RandomnessConsumer.json ABI in src/contracts/. Generate the typed contract class:
This creates a typed 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:
Save the wallet address — this will be the authorized fulfiller for your contract.

Step 4: Deploy the smart contract

The RandomnessConsumer.sol contract handles randomness requests and fulfillment:
Deploy to Base Sepolia:
Save the deployed contract address.

Step 5: Configure the Compose app

Update compose.yaml with your contract address:
Update the contract address in the task files:
  • src/tasks/fulfill-randomness.tsTARGET_CONTRACT
  • src/tasks/request-randomness.tsCONTRACT_ADDRESS

Step 6: Understand the fulfillment task

The fulfill-randomness.ts task handles the core logic:
Key points:
  • Contract codegenevm.contracts.RandomnessConsumer is generated from the ABI JSON, providing type-safe method calls like contract.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: false and cover gas from the wallet
  • fulfillRandomness does seven or more SSTOREs (the signature is a 96-byte bytes field). 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

The drand.ts library fetches verifiable randomness:
The randomness is verifiable off-chain using drand’s BLS12-381 signatures. Anyone can check that the signature is valid against drand’s public key, and that 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:
Watch the Compose logs. You should see:
  1. The RandomnessRequested event being detected
  2. Randomness fetched from drand
  3. 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 the compose.yaml to listen for different events:

Resources