Skip to content

Subscriptions (prepaid credit) ​

Pay for randomness the way you'd pay for an API plan. You top up a subscription once, authorize your contracts, and they request randomness without sending a fee. Each request is deducted from the prepaid balance.

This is an addition to pay-per-request. A contract can use either model, or both.

Pay per requestSubscription
Who paysWhoever triggers the requestThe subscription owner
Payment per requestHYPE as msg.value, or an approved GENESIS transferNone
Good forUsers paying for their own actionsFree-to-play games, apps that sponsor their users, backends

How it works ​

  1. Create: createSubscription() makes you the owner and returns a subId.
  2. Top up: fundSubscription(subId, token, amount) in HYPE (address(0), sent as value) or GENESIS (approve the coordinator first). Anyone can top up any subscription.
  3. Authorize: addConsumer(subId, yourContract). Up to 100 consumers per subscription.
  4. Request: your contract calls requestRandomWordsWithSubscription(subId, keyHash, confirmations, callbackGasLimit, numWords) and sends nothing. The fee is deducted from the subscription balance.

Which token is charged ​

  • After launch, when the subscription holds enough GENESIS for the request, it's charged in GENESIS, which is the cheaper option.
  • Otherwise it's charged in HYPE.
  • If neither balance covers one request, the request reverts with InsufficientSubscriptionBalance.

Fees are priced for the subscription owner, so the owner's staking discount applies to every request.

Example: a free-to-enter raffle ​

contracts/examples/RaffleGame.sol is a complete game where players never pay for randomness:

solidity
contract RaffleGame is GenesisVRFConsumerBase, Ownable {
    uint256 public subscriptionId;

    function enter() external { /* free: no payment */ }

    function draw() external returns (uint256 requestId) {
        // ... checks: entry window closed, at least two players
        requestId = _requestRandomWordsWithSubscription(subscriptionId, keyHash, 1, 200_000, 1);
    }

    function fulfillRandomWords(uint256, uint256[] memory words) internal override {
        winner = players[words[0] % players.length];
    }
}

Anyone can trigger draw() once a round's entry window has closed. That spends at most one request of credit per round, so the owner's cost stays bounded. Rounds with fewer than two players are extended instead of drawn.

Run it locally against the dev chain:

bash
cd node
npx tsx src/raffle-demo.ts

Fair Selection with credit ​

Organizers who own a subscription can draw without sending a fee:

solidity
coordinator.addConsumer(subId, address(fairSelection));   // once
fairSelection.drawWithSubscription(selectionId, subId);   // the caller must own subId

Fair Selection checks that the caller owns the subscription, so nobody can draw against someone else's credit.

Managing a subscription ​

CallWhoEffect
getSubscription(subId)anyoneOwner, consumers, request count, HYPE and GENESIS balances
quoteSubscriptionRequest(subId)anyoneToken and fee the next request will be charged
withdrawSubscription(subId, token, amount, to)ownerWithdraw unused credit at any time
removeConsumer(subId, consumer)ownerRevoke a contract
requestSubscriptionOwnerTransfer / acceptSubscriptionOwnerTransferowner, then new ownerTwo-step handover
cancelSubscription(subId, to)ownerRefund everything and close

Because requests are charged when they're made, withdrawing never affects requests already in flight.

From TypeScript ​

ts
const subId = await client.createSubscription();
await client.fundSubscription(subId, ZeroAddress, parseEther("0.05"));   // HYPE
await client.vrfCoordinator.addConsumer(subId, myContract);
const info = await client.getSubscriptionInfo(subId);
// info.nativeBalance, info.genesisBalance, info.requestsLeft, info.nextCharge

GENESIS · verifiable infrastructure for Elysium