> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Read onchain randomness

> Select a participant using network randomness and verify the result onchain

> [!NOTE]
>
> **What you'll be able to do**
>
> * Select a participant using a future random value from the network.
> * Verify the selection from finalized chain data.
> * Cancel an unresolved selection after a fixed deadline.

> [!WARNING]
>
> The randomness beacon is [not on Metal Testnet yet](https://docs.metalntwx.com/reference/contract-addresses.md#contracts-not-yet-on-metal-testnet).
> Follow this guide on a network that has code at its address.

Metal's **randomness beacon** is a network service that publishes random values
onchain. Your application can use these values to choose a participant in a draw
or shuffle a list. Once a value is published, anyone can reproduce the result
using the same inputs and selection rules.

This guide selects one participant from a fixed list. You first record the list
and a future **slot**, a one-second interval of chain time, before its random
value is available. Once the value is published, the application uses it to
select a participant, and anyone can verify the selection.

## Check the beacon and its assumptions

The beacon contract at `0x00000000000000000000000000000000000000ff` stores a
signature produced jointly by validators for each available slot. The API calls
this signature a slot key. `randomness(slot)` returns the random value derived
from that key as `keccak256(keyAt(slot))`.

Check for beacon runtime bytecode and `hasKey() == true` at a finalized block.
Record `groupKey()` and `keySet()` with the block number and hash.

```typescript theme={null}
import { parseAbi } from 'viem'

const beacon = '0x00000000000000000000000000000000000000ff' as const
const beaconAbi = parseAbi([
  'function hasKey() view returns (bool)',
  'function groupKey() view returns (bytes)',
  'function keySet() view returns (bytes32)',
  'function latestSlot() view returns (uint64)',
  'function keyAt(uint64 slot) view returns (bytes)',
  'function randomness(uint64 slot) view returns (bytes32)',
  'error NoKey()',
  'error NotInRing()',
])
const block = await client.getBlock({ blockTag: 'finalized' })
const hasKey = await client.readContract({
  address: beacon, abi: beaconAbi, functionName: 'hasKey', blockNumber: block.number,
})
if (!hasKey) throw new Error('Beacon key unavailable')
```

Honest validators release signature shares after the slot ends in finalized
chain time. Chain time can lead wall time by up to 60 seconds, or 2 seconds when
the [scheduler](https://docs.metalntwx.com/build/contracts/schedule-a-contract-call.md) is active, so choose
the future slot relative to the chain timestamp.

Unpredictability depends on the validators keeping their key shares secret. With
`n` validators and `f = floor((n - 1) / 3)`, a key with no publicly revealed
shares resists up to `2f` colluding validators. A key distribution round can
reveal up to `f` shares, reducing that resistance to `f` colluders until the
shares are redistributed. Redistribution preserves the group secret, so a quorum
of former validators that retained old shares can also recover future slot keys.

## Commit the decision before the slot

The `FutureSelection` contract records one selection. In one transaction, its
owner commits an ordered participant list, a unique business decision ID, a
future slot, and a cancellation deadline. These inputs remain fixed, and anyone
can submit the transaction that resolves the selection.

```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IBeacon {
    function hasKey() external view returns (bool);
    function randomness(uint64 slot) external view returns (bytes32);
}

contract FutureSelection {
    IBeacon constant BEACON = IBeacon(0x00000000000000000000000000000000000000ff);
    address public immutable owner = msg.sender;
    address[] public participants;
    bytes32 public commitment;
    uint64 public slot;
    uint64 public deadline;
    bool public committed;
    bool public resolved;
    bool public cancelled;
    address public winner;
    event Committed(bytes32 indexed commitment, uint64 slot, uint64 deadline);
    event Selected(bytes32 indexed commitment, address winner, uint256 index);
    event Cancelled(bytes32 indexed commitment);

    function commit(bytes32 decisionId, address[] calldata choices, uint64 futureSlot,
        uint64 deadline_) external {
        require(msg.sender == owner && !committed, "authority or duplicate");
        require(BEACON.hasKey(), "no key");
        require(futureSlot >= block.timestamp + 30, "slot too soon");
        require(deadline_ > futureSlot && deadline_ <= uint256(futureSlot) + 120, "deadline");
        require(choices.length > 1 && choices.length <= 256, "participant count");
        for (uint256 i = 0; i < choices.length; i++) {
            require(choices[i] != address(0), "zero participant");
            for (uint256 j = 0; j < i; j++) require(choices[i] != choices[j], "duplicate participant");
        }
        participants = choices;
        slot = futureSlot;
        deadline = deadline_;
        commitment = keccak256(abi.encode(block.chainid, address(this), decisionId,
            futureSlot, deadline_, choices));
        committed = true;
        emit Committed(commitment, futureSlot, deadline_);
    }

    function finish() external {
        require(committed && !resolved && !cancelled, "state");
        require(block.timestamp <= deadline, "deadline elapsed");
        bytes32 entropy = BEACON.randomness(slot);
        uint256 n = participants.length;
        uint256 threshold = addmod(type(uint256).max, 1, n);
        for (uint256 counter = 0; counter < 16; counter++) {
            uint256 candidate = uint256(keccak256(abi.encode(entropy, commitment, counter)));
            if (candidate < threshold) continue;
            uint256 index = candidate % n;
            winner = participants[index];
            resolved = true;
            emit Selected(commitment, winner, index);
            return;
        }
        revert("sampling limit");
    }

    function cancelExpired() external {
        require(committed && !resolved && !cancelled, "state");
        require(block.timestamp > deadline, "deadline active");
        cancelled = true;
        emit Cancelled(commitment);
    }
}
```

[Deploy the contract](https://docs.metalntwx.com/build/contracts/deploy-an-application-contract.md), then call
`commit` with the decision ID and agreed participant order. Choose a slot far
enough beyond the latest chain timestamp to allow inclusion and finality. This
contract requires the slot to be at least 30 seconds ahead at execution and the
cancellation deadline to fall within the following 120 seconds.

Use the [SDK signing procedure](https://docs.metalntwx.com/reference/typescript-sdk.md#signmip20contract) and
[fee estimates](https://docs.metalntwx.com/build/prepare/estimate-and-record-fees.md) for each call. Save the
transaction hash and signed bytes before broadcasting. Verify a successful
receipt containing `Committed`, check its canonical block and finality, and read
the commitment, slot, deadline, and participant array at that block.
Independently recompute the commitment using ABI types
`uint256,address,bytes32,uint64,uint64,address[]` in the contract's field order.

Save the selection contract address with the business decision and accept only
that contract's commitment for it. This prevents an operator from discarding an
unfavorable result and running another selection. Share the finalized commitment
with participants before its slot ends.

## Read and verify the result

Poll `randomness(slot)` at finalized blocks using the committed slot. Once its
key is available, the returned entropy lets you reproduce the selection:

```typescript theme={null}
import { encodeAbiParameters, keccak256, parseAbiParameters, type Hex } from 'viem'

function selectIndex(entropy: Hex, commitment: Hex, count: number): number {
  if (!Number.isInteger(count) || count < 2 || count > 256) throw new Error('Count')
  const n = BigInt(count)
  const threshold = (1n << 256n) % n
  for (let counter = 0n; counter < 16n; counter++) {
    const candidate = BigInt(keccak256(encodeAbiParameters(
      parseAbiParameters('bytes32, bytes32, uint256'), [entropy, commitment, counter],
    )))
    if (candidate >= threshold) return Number(candidate % n)
  }
  throw new Error('Sampling limit')
}
```

Rejection sampling gives each participant an equal chance even when the
participant count does not divide `2^256`. The contract and client both test
candidates in the same order, using the committed inputs. If all 16 attempts
reject, the selection remains pending until its cancellation deadline.

Submit `finish()` in time to execute before the deadline. It reads the slot's
entropy and records the winner. Verify `Selected` in a successful receipt, check
the receipt's canonical block and finality, and compare `winner` with the
participant at the index returned by `selectIndex`. Mark the business decision
complete once using the stored commitment. Follow
[finalized receipt verification](https://docs.metalntwx.com/build/payments/verify-and-reconcile-payments.md) and
[settlement semantics](https://docs.metalntwx.com/reference/network-fundamentals/settlement.md).

## Handle a delayed or missing key

The beacon stores keys in a 4096-slot ring indexed by `slot % 4096`. Each
position holds one key and its slot label. `NotInRing` means the requested key
has either not arrived or been overwritten by a later slot at the same position.
Save finalized results promptly if you need to retain them for an audit.

Nodes sign the 65 slots up to the newest finalized block, so a long halt can
leave older slots permanently without a key. Key generation or validator
transitions can also delay publication.

Keep the decision pending and retry the same slot until the fixed deadline. If
the selection remains unresolved after the deadline, call `cancelExpired()` and
verify its finalized `Cancelled` event. Cancellation is final even if a key
becomes available later. If your application issues refunds, use this
cancellation state to authorize them.

Test past slots, duplicate commitments, reordered participants, unauthorized
commits, repeat resolution, delayed keys, expiry, and ring lookup failures. Use
controlled candidate values to exercise sampling rejection. Then run a selection
on the network and verify its finalized commitment and result.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.