/

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.

[0 .. 8]discriminatorAnchor route. SHA-256("global:dice_callback")[0..8].
[8 .. 40]channel_keyThe DiceChannel PDA that signed this result.
[40 .. 72]randomness32 bytes of hardware-backed entropy.
FieldOffsetSizeType
discriminator08[u8; 8]
channel_key832Pubkey
randomness4032[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:

Rust
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 through seeds = [...] in your constraint.
  • Account 1dice_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:

lib/dice-decoder.ts
// 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),
  }
}