Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Callbacks & Automation

When a game's state changes, the MinigameToken can automatically notify the minting platform. This enables reactive systems like automatic prize distribution, quest completion, and leaderboard updates.

IMetagameCallback Interface

pub const IMETAGAME_CALLBACK_ID: felt252 =
    0x3b4312c1422de8c35936cc79948381ab8ef9fd083d8c8e20317164690aa1600;
 
#[starknet::interface]
pub trait IMetagameCallback<TState> {
    /// Called on every update_game() call to notify the metagame of a game action
    fn on_game_action(ref self: TState, token_id: u256, score: u64);
 
    /// Called when a game ends (game_over becomes true)
    fn on_game_over(ref self: TState, token_id: u256, final_score: u64);
 
    /// Called when a token completes its objective
    fn on_objective_complete(ref self: TState, token_id: u256);
}

How It Works

When update_game(token_id) is called on the MinigameToken:

  1. Token reads the minter address from the packed token ID
  2. Token checks if the minter implements IMetagameCallback via SRC5
  3. If supported, token dispatches the appropriate callback(s)
update_game(token_id)

    ├── Read score from game contract
    ├── Read game_over from game contract

    ├── minter.on_game_action(token_id, score)  ← fires every time

    ├── Game over? (and wasn't before)
    │   └── Yes → minter.on_game_over(token_id, final_score)

    └── Objective completed? (and wasn't before)
        └── Yes → minter.on_objective_complete(token_id)

SRC5 Gating

Callbacks are opt-in. The token only dispatches callbacks if the minter's contract:

  1. Implements SRC5 (has supports_interface())
  2. Returns true for IMETAGAME_CALLBACK_ID

If the minter is an account or a contract without SRC5, no callbacks are attempted. This is safe - no reverts, no wasted gas.

Implementing Callbacks

#[starknet::contract]
mod MyTournament {
    use game_components_metagame::callback::IMetagameCallback;
    use openzeppelin_introspection::src5::SRC5Component;
 
    component!(path: SRC5Component, storage: src5, event: SRC5Event);
 
    #[abi(embed_v0)]
    impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;
 
    #[storage]
    struct Storage {
        #[substorage(v0)]
        src5: SRC5Component::Storage,
        leaderboard: Map<felt252, u64>,  // token_id -> score
        completed: Map<felt252, bool>,
    }
 
    #[constructor]
    fn constructor(ref self: ContractState) {
        // Register callback support so the token knows to call us
        self.src5.register_interface(
            0x3b4312c1422de8c35936cc79948381ab8ef9fd083d8c8e20317164690aa1600
        );
    }
 
    #[abi(embed_v0)]
    impl CallbackImpl of IMetagameCallback<ContractState> {
        fn on_game_action(ref self: ContractState, token_id: u256, score: u64) {
            // Update leaderboard
            let id: felt252 = token_id.try_into().unwrap();
            self.leaderboard.write(id, score);
        }
 
        fn on_game_over(ref self: ContractState, token_id: u256, final_score: u64) {
            // Mark as completed, trigger prize distribution
            let id: felt252 = token_id.try_into().unwrap();
            self.completed.write(id, true);
            self.leaderboard.write(id, final_score);
        }
 
        fn on_objective_complete(ref self: ContractState, token_id: u256) {
            // Award achievement badge or unlock next quest
        }
    }
}

Use Cases

Tournament Automation

Player finishes game
    → update_game() called
    → on_game_over(token_id, final_score) callback
    → Tournament contract updates leaderboard
    → If all entrants finished, distribute prizes

Quest Completion

Player achieves objective
    → update_game() called
    → on_objective_complete(token_id) callback
    → Quest contract marks quest as complete
    → Unlock next quest in chain

Real-time Leaderboards

Player scores points
    → update_game() called
    → on_game_action(token_id, score) callback
    → Leaderboard contract re-ranks entries
    → Frontend subscribes to events for live updates

Security Considerations

  • Callbacks are called by the token contract, not the player. The MetagameCallbackComponent enforces this automatically via assert_only_token — you don't need to check the caller yourself
  • Callbacks should not revert on unexpected data. A reverting callback blocks update_game() for all tokens minted by that platform
  • Gas limits: Keep callback logic lightweight. Heavy computation should be deferred to separate transactions