Overview

Game Builder supports two complementary flows for persistent game items:

These flows are designed so that the game logic itself is unaware of whether player input came from a selected item or was typed manually. Item selection is a frontend concern; from the game's perspective, it receives an action string.


Item Creation (Minting)

Design-Time Configuration

During game design, the design agent can define produced tokens — items that the game creates as output. The spec processor extracts this into a ProducedTokensArtifact that describes both the token's contents and where it can be used:

{
  tokens: [
    {
      tokenType: "character",           // Identifier for this token category
      description: "A superhero character",
      tokenSource: "player",            // Extract fields from player state ("player" | "game")
      fields: ["characterDescription", "characterName", "matchesWon"],
      actionId: "describe-character",   // Labels this token's action context (stored in metadata)
      consumableIn: {                   // Where this token can be used as input (optional)
        phase: "describe_character",    // Player phase that accepts this token
        actionField: "characterDescription"  // Token field to use as the action string
      }
    }
  ]
}

This is a unified artifact — it tells the system both which fields to snapshot from game state when producing a token, and in which phase the token can substitute for player input. The spec processor extracts this as a single concept because the designer already knows both sides: "players create a character by describing it, and that character can be reused in future matches."

Runtime Flow

Game ends → Player wins
    ↓
Frontend calls POST /api/tokens/prepare
    { sessionId, tokenType: "character", playerId }
    ↓
Orchestrator extracts token data from game state,
generates image, returns token preview
    ↓
Frontend calls POST /api/tokens/mint
    { sessionId, tokenType: "character", playerId }
    ↓
Orchestrator uploads metadata to IPFS (via thirdweb),
mints ERC721 on Game Asset contract (CCGADiamond),
persists token in off-chain DB
    ↓
Frontend tracks progress via GET /api/tokens/progress/:sessionId/:playerId (SSE)

Token Persistence

Token persistence is fully implemented across the stack using a hybrid approach: tokens are stored off-chain in the orchestrator's database and minted on-chain as ERC721 NFTs via the Game Asset contract (CCGADiamond) on Arbitrum Sepolia.

Storage

The orchestrator persists tokens in Supabase via SessionTokenRepository (session_tokens table):

session_tokens
├── id: string
├── session_id: string
├── player_address: string (wallet address)
├── token_type: string
├── token_id: number | null              (on-chain ERC721 token ID)
├── metadata_uri: string | null          (IPFS URI)
├── image_uri: string | null             (IPFS URI)
├── image_https_url: string | null       (HTTPS gateway URL)
├── metadata_https_url: string | null    (HTTPS gateway URL)
├── transaction_hash: string | null      (blockchain tx hash)
├── preview_token_data: JSONB | null     (extracted game state fields)
├── preview_image_url: string | null
├── created_at: timestamp
└── updated_at: timestamp

Unique constraint: (session_id, player_address, token_type) — one token per player per session per type.

SessionTokenRepository provides getByPlayer(playerAddress) for cross-session queries, but this is not yet exposed as an API endpoint (see Orchestrator Changes below).