Random Hash Oracle Guide
This guide walks through how to request a random hash using the Kurier Random Hash Oracle. The process is subscription-based and involves deploying a consumer contract that can request randomness from the oracle coordinator.
Oracle Coordinator Contracts
Base Sepolia coordinator contracts:
| Contract | Address |
|---|---|
| Coordinator Implementation | 0xAa1C80d1a163C5B11Cb063e388C6b77Aa08EFC83 |
| Coordinator Proxy | 0x9569D9f81461A18C8cEaFCbdA9D79480c4943239 |
You will interact with the Coordinator Proxy to create and manage subscriptions, and to request a random hash.
Follow the steps below to create a subscription, authorize a consumer, and request randomness.
Step 1: Create and Configure a Subscription
Randomness requests are paid for via subscriptions. Each consumer contract must be explicitly authorized to use a subscription.
1.1 Create a Subscription
Call the following function on the coordinator contract: createSubscription()
This returns a subscriptionId, which will be used throughout the rest of the setup.
1.2 Fund the Subscription
Fund the subscription with the network’s native token so it can pay for randomness requests: fundSubscription(subscriptionId)
Make sure to send enough funds to cover at least one request.
There is a script to automate these two steps. Download the setup-subscription script here.
Prerequisites
This script uses Foundry’s cast CLI to send transactions and call contract functions.
If you don’t already have Foundry installed, run:
curl -L https://foundry.paradigm.xyz | bash
foundryup
Verify installation:
cast --version
Environment Variables
Before running the script, set the PRIVATE_KEY environment variable:
export PRIVATE_KEY="0xYOUR_PRIVATE_KEY"
or create an .env file with PRIVATE_KEY="0xYOUR_PRIVATE_KEY"
If you are using a .env file, load it into your shell before running the script:
set -a
source .env
set +a
Download the Script
Make it executable:
chmod +x setup-subscription.sh
Run the Script
./setup-subscription.sh
It will prompt for the amount to fund (default to 0.05 ether), and optionally the consumer contract address.
On success, the script will:
- Create a subscription
- Fund it with the specified amount
- Optionally add a consumer
- Print the created
SUBSCRIPTION_ID
Step 2: Implement the Consumer Contract
Your consumer contract is responsible for:
- Calling the Kurier Oracle coordinator to request randomness
- Receiving the oracle callback(s) with the random hash
At a minimum, the contract must:
- Store the subscription ID
- Store the coordinator contract address
- Expose a function (e.g.
requestRandomHash) that forwards the request to the coordinator - Implement the consumer callback interface (
IKurierOracleConsumer)
Coordinator requestRandomHash Parameters
Your consumer contract must forward randomness requests to the coordinator using the following function:
function requestRandomHash(
uint64 subscriptionId,
uint32 callbackGasLimit,
FulfillmentMode mode,
bytes calldata callbackData
) external returns (bytes32 requestId);
This function registers a randomness request, charges the associated subscription, and schedules a callback to the consumer contract.
Consumer callback functions (required)
Your consumer contract must implement these functions so the coordinator can deliver results:
function kurierFulfillRandomHashOptimistic(
bytes32 requestId,
bytes32 randomHash,
uint8 mode,
bytes calldata callbackData
) external;
function kurierFulfillRandomHashFinal(
bytes32 requestId,
bytes32 randomHash,
uint256 domainId,
uint256 aggregationId,
bytes32 leaf,
bytes32[] calldata merklePath,
uint256 leafCount,
uint256 index,
bytes calldata callbackData
) external;
In both callbacks, you should restrict access so only the coordinator can call them (e.g. require(msg.sender == COORDINATOR)).
Practical Notes and Troubleshooting
- Subscription must be funded + authorized: requests revert unless the consumer was added via
addConsumer(...). callbackGasLimitmust be in bounds:minCallbackGasLimit <= callbackGasLimit <= maxCallbackGasLimit.- Mode mapping (for UIs / scripts):
0=OPTIMISTIC_ONLY,1=FINAL_ONLY,2=TWO_STAGE. callbackDatais echoed back in both callbacks and its length affects cost (the coordinator reserves gas/calldata overhead based oncallbackData.length).- Callback safety: enforce
msg.sender == COORDINATORinsidekurierFulfillRandomHashOptimistic/kurierFulfillRandomHashFinal. - Getting the
randomHash:- In
OPTIMISTIC_ONLY, you receiverandomHashdirectly inkurierFulfillRandomHashOptimistic(...). - In
FINAL_ONLY, you receiverandomHashinkurierFulfillRandomHashFinal(...)after zkVerify finality checks pass. - In
TWO_STAGE, you receiverandomHashin bothkurierFulfillRandomHashOptimistic(...)andkurierFulfillRandomHashFinal(...). The samerandomHashis delivered in both callbacks.
- In
Choosing a fulfillment mode (example dapps)
-
OPTIMISTIC_ONLY(fastest)- Instant UX randomness: on-chain games (loot rolls, crits, map/dungeon seeds), social/creator apps that “spin a wheel” for cosmetic outcomes, mint previews (show traits early, settle later off-chain).
- Low-stakes or reversible: testnet faucets/quests, free-to-play drops, randomized matchmaking where you can re-roll on failure.
- Pattern: apply the result immediately in-app / in-state, but don’t irrevocably move large value based on it.
-
FINAL_ONLY(strongest guarantees)- High-value allocation: raffles/lotteries, auctions that need a random tiebreaker, NFT mints with scarce slots, fair airdrop selection, randomized vesting cliffs.
- Governance / security: committee selection (sortition), randomized proposer rotation, selecting “random challengers” for dispute games.
- Pattern: only finalize payouts / ownership transfer after final callback + on-chain attestation checks pass.
-
TWO_STAGE(best of both)- Fast reveal, delayed settlement: reveal what you “won” immediately, but only release escrow/payout after final.
- Games with escrow / wager: show the roll (optimistic) to keep players engaged; settle bets (final) once zkVerify-backed finality arrives.
- Marketplaces / drops: optimistically assign queue positions or preview allocations, then finalize allocations after final.
- Pattern: optimistic callback updates UI/state; final callback triggers the irreversible action.
Step 3: Deploy the Consumer Smart Contract
Deploy your ConsumerSmartContract after implementing the request logic.
Constructor parameters typically include:
- The subscription ID (
subscriptionId) - The Kurier Oracle coordinator address
Step 4: Add the Consumer Contract to the Subscription
Once the consumer contract is deployed, authorize it to use the subscription: addConsumer(subId, consumerContractAddress)
⚠️ Randomness requests will fail unless the consumer contract is added to the subscription.
There is a add-consumer.sh script that can be used to send this transaction. It will prompt for the subscription ID and consumer contract address. Your private key is read from your environment variables.
If you want to preflight authorization, you can query:
isConsumer(uint64 subscriptionId, address consumer) external view returns (bool)
Step 5: Verify the Consumer Contract
After deployment, verify the ConsumerSmartContract on the block explorer. While this is not required for requesting randomness, it is strongly recommended.
Contract verification allows you to:
- Interact with the contract through the explorer UI
- Debug requests and responses more easily
- Confirm constructor parameters were set correctly
Skipping this step will not affect oracle functionality, but it may make troubleshooting more difficult.
Step 6: Request a Random Hash
Once everything is set up and the consumer contract has been added to the subscription, you can test the oracle flow.
Call your consumer contract’s request function (the parameters are app-defined, but it should forward to the coordinator’s requestRandomHash(...)):
callbackGasLimit: must be within[minCallbackGasLimit, maxCallbackGasLimit]mode:0=OPTIMISTIC_ONLY,1=FINAL_ONLY,2=TWO_STAGEcallbackData: arbitrary bytes echoed back in both callbacks (its length affects cost)
This will:
- Send a request to the Kurier Oracle
- Trigger the oracle to generate randomness
- Trigger a callback into your consumer contract (optimistic and/or final, depending on
mode)
Costs & Fees (What gets paid, and by whom)
Random hash requests are paid for from the subscription balance (i.e. the balance owned by the subscription owner, not end users unless you build that into your app).
At a high level, each request costs:
- Oracle fee (
oracleFee): a fixed fee charged at request time. - Gas cost: the coordinator reserves funds based on
callbackGasLimit,mode, andcallbackDatalength, then settles actual cost at fulfillment time.
Query the current fee and config (example: Base Sepolia)
COORD="0x9569D9f81461A18C8cEaFCbdA9D79480c4943239"
RPC_URL="https://sepolia.base.org"
cast call "$COORD" "oracleFee()(uint256)" --rpc-url "$RPC_URL"
cast call "$COORD" "maxGasPrice()(uint256)" --rpc-url "$RPC_URL"
cast call "$COORD" "maxCallbackGasLimit()(uint32)" --rpc-url "$RPC_URL"
cast call "$COORD" "minCallbackGasLimit()(uint32)" --rpc-url "$RPC_URL"
Estimate the required subscription balance for a request
The coordinator exposes estimators you can use off-chain:
cast call "$COORD" \
"estimateRequestCostBreakdown(uint32,uint8,uint256)(uint256,uint256,uint256)" \
<CALLBACK_GAS_LIMIT> <MODE> <CALLBACK_DATA_BYTES> \
--rpc-url "$RPC_URL"
Where CALLBACK_DATA_BYTES is the length of the callbackData you pass to requestRandomHash(...).
Observe what was charged after fulfillment
After fulfillment, the coordinator emits a GasSettled event that includes:
gasUsed(measured around the callback, plus settlement overhead)gasPriceandactualCostrefunded(if reservation > actual cost)
This is useful for building UIs that show the “oracle fulfillment cost” for a request.
End-to-end integration checklist (Coordinator + Oracle service)
This is the practical order of operations most teams use:
- Pick a coordinator deployment (network + address from the table above)
- Create a subscription:
createSubscription() - Fund the subscription:
fundSubscription(subscriptionId)(with the network’s native token) - Deploy your consumer contract with:
- coordinator address
- subscription ID
- a reasonable
callbackGasLimit
- Authorize the consumer on the subscription:
addConsumer(subscriptionId, consumerAddress)- (Optional preflight)
isConsumer(subscriptionId, consumerAddress)
- Request randomness from the consumer by calling
requestRandomHash(...) - Oracle service picks it up and fulfills:
- An indexer watches
RandomHashRequestedevents and queues work - A dispatcher submits
fulfillRandomHashOptimistic(...)and/orfulfillRandomHashFinal(...) - The coordinator calls your consumer callback with the configured
callbackGasLimit
- An indexer watches
Troubleshooting (common failure modes)
“Not authorized consumer” / request reverts immediately
- Your consumer contract was not added to the subscription.
- Fix: call
addConsumer(subscriptionId, consumerAddress)and re-try.
Wallet / RPC can’t estimate gas for the request transaction
This is often how a revert surfaces in wallets.
- Check subscription is funded
- Check consumer is authorized
- Check the request’s
callbackGasLimitis within coordinator bounds:minCallbackGasLimit <= callbackGasLimit <= maxCallbackGasLimit
Callback fails (no result delivered to consumer)
- Most commonly:
callbackGasLimitis too low for your consumer’s fulfillment logic. - Fix: measure callback gas usage in tests / simulation, then request with a higher
callbackGasLimit.
Verifying contracts on Base Sepolia with Foundry
Foundry’s chain name uses a hyphen:
- Use
--chain base-sepolia(notbase_sepolia)
Summary
To successfully request a random hash from the Kurier Oracle, make sure you have:
- Created a subscription
- Funded the subscription with the network’s native token
- Implemented a consumer contract that forwards requests to the coordinator
- Deployed a consumer contract using the subscription ID
- Added the consumer contract to the subscription
- (Optional) Verified the consumer contract on the explorer
- Called
requestRandomHash()on the consumer contract
Once configured, your consumer contract can safely and repeatedly request random hashes from the Kurier Oracle.