Callback payload
When DICE's deliver_callback CPIs into your program, the instruction data is exactly 72 bytes. Here's the layout.
Byte-level layout
Three contiguous fields, no padding. Anchor deserialises these into a DiceResult struct for you — this table is only relevant if you're writing a client-side decoder or debugging a CPI failure.
| Field | Offset | Size | Type |
|---|---|---|---|
| discriminator | 0 | 8 | [u8; 8] |
| channel_key | 8 | 32 | Pubkey |
| randomness | 40 | 32 | [u8; 32] |
Discriminator
The first 8 bytes are an Anchor instruction discriminator — the routing tag Anchor uses to pick which handler runs. Its value is SHA-256("global:dice_callback")[0..8], which means the name dice_callback is load-bearing: rename the handler and the CPI will miss.
channel_key
A standard 32-byte Solana Pubkey — the channel PDA that produced this round. Your handler cross-checks it against the dice_channel account passed in Accounts:
require_keys_eq!(
ctx.accounts.dice_channel.key(),
result.channel_key,
GameError::WrongChannel,
);randomness
32 uniformly-distributed bytes. Think of it as a [u8; 32] that you can reinterpret as a u64, a u32, a hash seed, whatever fits your game. The bytes are derived from SHA-256(reveal_1 ‖ reveal_2 ‖ … ‖ reveal_n), so they are not only uniform but publicly reproducible from chain data.
Accounts passed
DICE's deliver_callback CPIs your program with exactly one named account plus remaining_accounts:
- Account 0 — your game PDA, passed into your Accounts struct as the first field (conventionally named
game). Anchor validates it throughseeds = [...]in your constraint. - Account 1 —
dice_channel, the channel PDA. You declare this second in your struct.
If you need extra accounts (an oracle, a mint, a token account), add them after these two in your Accounts struct and teach your client code to pass them as remainingAccounts on the original request_randomness_auto call. DICE forwards them verbatim.
Off-chain decoder
Handy if you're debugging or previewing callbacks in a UI:
// Off-chain decoder for DICE callback instruction data.
// Useful when you want to show the result in a UI before the chain state
// catches up, or when debugging with raw TXs from the explorer.
import { PublicKey } from "@solana/web3.js"
export type DiceResult = {
channelKey: PublicKey
randomness: Uint8Array // length 32
}
export function decodeDiceCallback(data: Uint8Array): DiceResult {
if (data.length !== 72) {
throw new Error(`expected 72 bytes, got ${data.length}`)
}
// bytes [0..8] is the Anchor discriminator — skip.
return {
channelKey: new PublicKey(data.slice(8, 40)),
randomness: data.slice(40, 72),
}
}