How do I use on-chain randomness on Sui?
November 10, 2026
Sui has secure randomness built into the protocol through the sui::random module. Write a private entry function that takes r: &Random (the system object at 0x8), create a RandomGenerator inside the function with random::new_generator(r, ctx), and draw values from it, for example generate_u8_in_range(&mut gen, 1, 6). The compiler and the network enforce a few rules that stop callers from looking at the outcome and reverting it.
A minimal example
module example::dice;
use sui::random::{Self, Random};
public struct Die has key, store {
id: UID,
value: u8,
}
entry fun roll(r: &Random, ctx: &mut TxContext) {
let mut generator = random::new_generator(r, ctx);
let value = generator.generate_u8_in_range(1, 6);
transfer::public_transfer(Die { id: object::new(ctx), value }, ctx.sender());
}Call it with 0x8 as the argument. From TypeScript (@mysten/sui v2) use the helper:
import { Transaction } from '@mysten/sui/transactions'
const tx = new Transaction()
tx.moveCall({
target: `${PACKAGE_ID}::dice::roll`,
arguments: [tx.object.random()],
})From the CLI: sui client call --package <PACKAGE_ID> --module dice --function roll --args 0x8.
The generator API
RandomGenerator is a pseudorandom generator seeded from the global randomness and unique to each transaction. It provides:
generate_u8/u16/u32/u64/u128/u256, plusgenerate_boolgenerate_u8_in_rangethroughgenerate_u128_in_range(inclusive[min, max], bias at most 2^-64)generate_bytes(n)andshuffle(&mut vector<T>)
The randomness itself comes from the validators. They run a distributed key generation (DKG) each epoch and then produce fresh values with a random beacon protocol. A RandomnessStateUpdate system transaction writes each new value into the Random object.
Rules that protect you
These are enforced, not just recommended:
publicfunctions can't take&Random. If they could, another package could wrap your function, look at the result andabortwhenever it lost, so a bet would only ever go through on a win. A privateentryfunction can't be called from other Move code. See entry vs public.publicfunctions can't take aRandomGenerator. A generator passed in from outside can have its internal state read (for example withbcs::to_bytes), which makes its output predictable. Always create it inside the function that uses it.- PTB restriction. After a
MoveCallthat usesRandom, a PTB may only containTransferObjectsorMergeCoinscommands. That stops a later command from inspecting the result and aborting the whole transaction. Randomis read-only. Any transaction that tries to take it mutably fails.
Gotchas the compiler can't catch
- Gas-based attacks. If the winning branch costs much more gas than the losing branch, an attacker can set a gas budget that only covers the losing path. Then a win runs out of gas and reverts. Keep all outcome paths at roughly equal cost, or split the flow.
- Commit-reveal for high stakes. In transaction 1, take payment, draw the random value and store it in an object the caller can't read in the same transaction. In transaction 2, reveal the result and settle. Make sure the inputs to step 2 can't change after step 1, and plan for users who never come back to finish step 2.
- Per-transaction limits (new objects, events, dynamic fields) also count as resources an attacker could exhaust on purpose. See object and transaction size limits.
- Transactions that use
Randomgo through consensus because0x8is a shared object.
Testing
In unit tests, random::new_generator_for_testing() gives you a non-deterministic generator, and random::new_generator_from_seed_for_testing(seed) gives you a deterministic one. You can also create a test Random object with random::create_for_testing inside a test_scenario.
Sources
- Onchain Randomness, Sui documentation
- random.move, Sui framework source
- sui::random reference, Sui documentation