Gaming on Nostr
NIP-XX: Gaming on Nostr
draft optional
This NIP defines event kinds and protocols for enabling online gaming experiences on Nostr, including MMO (Massively Multiplayer Online) games, Battle Royale games, trading systems, turn-based games, real-time multiplayer, and more.
Motivation
Gaming is one of the most engaging forms of digital interaction, but current gaming platforms suffer from centralization issues:
- Player data and achievements are locked to specific platforms
- In-game items and currencies cannot be truly owned or transferred
- Game servers can shut down, losing all player progress
- Cross-platform play is limited by corporate decisions
- Players have no control over their gaming identity
Nostr's decentralized architecture solves these problems by:
- Giving players true ownership of their gaming identity and achievements
- Enabling cross-game item trading and currency exchange
- Allowing games to persist even if original developers stop supporting them
- Creating interoperable gaming experiences across different clients
- Enabling community-driven game development and moderation
Overview
This NIP introduces three categories of gaming events:
- Ephemeral Events (20000-29999): Real-time game state that doesn't need permanent storage
- Addressable Events (30000-39999): Game sessions, player profiles, and updateable game data
- Regular Events (1000-9999): Permanent game records, achievements, and results
Event Kinds
Game Session Management
kind:38743 - Game Session (Addressable)
A game session represents an instance of a game that players can join. This is an addressable event that can be updated as the game progresses.
{
"kind": 38743,
"tags": [
["d", "<unique-session-id>"], // Required: Unique session identifier
["game", "<game-identifier>"], // Required: Game type/name
["title", "<session-title>"], // Required: Display name for the session
["description", "<session-description>"], // Optional: Session description
["status", "<waiting|in-progress|completed>"], // Required: Current session status
["max-players", "<number>"], // Required: Maximum player count
["current-players", "<number>"], // Optional: Current player count
["game-mode", "<mode>"], // Optional: Game mode (e.g., "battle-royale", "team-deathmatch")
["difficulty", "<easy|medium|hard|custom>"], // Optional: Difficulty level
["map", "<map-identifier>"], // Optional: Map or world identifier
["rules", "<json-rules>"], // Optional: Game-specific rules as JSON
["entry-fee", "<amount>", "<currency>"], // Optional: Entry fee (amount, currency/token)
["prize-pool", "<amount>", "<currency>"], // Optional: Prize pool
["starts", "<unix-timestamp>"], // Optional: Scheduled start time
["ends", "<unix-timestamp>"], // Optional: Expected/actual end time
["version", "<game-version>"], // Optional: Game version/protocol version
["p", "<pubkey>", "<relay-hint>", "<role>"], // Required: Host and players with roles
["relay", "<wss://relay-url>"], // Optional: Preferred relays for this session
["image", "<preview-image-url>"], // Optional: Session preview image
["t", "<hashtag>"] // Optional: Hashtags for discovery
],
"content": "", // Optional: Additional session details or configuration
// other fields...
}**Roles for p tags:**
host- Session creator/administratorplayer- Active participantspectator- Observer onlymoderator- Can moderate but not playbot- AI player
Status values:
waiting- Session created, waiting for playersin-progress- Game is currently runningpaused- Game temporarily pausedcompleted- Game has endedcancelled- Game was cancelled
Example:
{
"kind": 38743,
"tags": [
["d", "br-island-match-001"],
["game", "nostr-royale"],
["title", "Nostr Island Showdown #1"],
["description", "100-player battle royale on Island map"],
["status", "waiting"],
["max-players", "100"],
["current-players", "47"],
["game-mode", "battle-royale"],
["map", "island-v2"],
["starts", "1735689600"],
["version", "1.0.0"],
["p", "91cf9..4e5ca", "wss://relay.nostr.band", "host"],
["p", "14aeb..8dad4", "", "player"],
["image", "https://example.com/island-preview.jpg"],
["t", "gaming"],
["t", "battle-royale"]
],
"content": ""
}Real-time Game State (Ephemeral)
kind:25211 - Game Action (Ephemeral)
Ephemeral events for real-time game actions. These are not stored permanently by relays and are used for live gameplay.
{
"kind": 25211,
"tags": [
["a", "38743:<host-pubkey>:<session-id>"], // Required: Reference to game session
["action", "<action-type>"], // Required: Type of action
["data", "<json-action-data>"], // Optional: Action-specific data as JSON
["seq", "<sequence-number>"], // Optional: Sequence number for ordering
["tick", "<game-tick>"], // Optional: Game tick/frame number
["p", "<target-pubkey>"] // Optional: Target player (for interactions)
],
"content": "", // Optional: Additional action data
// other fields...
}Common action types:
move- Player movementattack- Attack actionuse-item- Item usagechat- In-game chat messageping- Keep-alive/latency checkspawn- Player spawn/respawnpickup- Item pickuptrade-request- Initiate tradevote-kick- Vote to remove player
Example (Movement):
{
"kind": 25211,
"tags": [
["a", "38743:91cf9..4e5ca:br-island-match-001"],
["action", "move"],
["data", "{\"x\": 125.5, \"y\": 67.2, \"z\": 10.0, \"rotation\": 45}"],
["tick", "12847"]
],
"content": ""
}Example (Attack):
{
"kind": 25211,
"tags": [
["a", "38743:91cf9..4e5ca:br-island-match-001"],
["action", "attack"],
["data", "{\"weapon\": \"rifle\", \"direction\": [0.8, 0.6, 0], \"damage\": 25}"],
["p", "14aeb..8dad4"], // Target player
["tick", "12848"]
],
"content": ""
}kind:25212 - Game State Update (Ephemeral)
Broadcast game state updates from the authoritative server/host. Used for synchronizing game state across all clients.
{
"kind": 25212,
"tags": [
["a", "38743:<host-pubkey>:<session-id>"], // Required: Reference to game session
["state-type", "<type>"], // Required: Type of state update
["data", "<json-state-data>"], // Required: State data as JSON
["tick", "<game-tick>"], // Optional: Game tick/frame number
["hash", "<state-hash>"] // Optional: Hash for validation
],
"content": "", // Optional: Additional state data
// other fields...
}Common state types:
world-state- Overall world/map stateplayer-state- Player positions and statsinventory- Inventory updatesscore- Score updatesenvironment- Environmental changesevent- In-game events (storm closing, supply drop, etc.)
Example:
{
"kind": 25212,
"tags": [
["a", "38743:91cf9..4e5ca:br-island-match-001"],
["state-type", "environment"],
["data", "{\"storm\": {\"center\": [100, 100], \"radius\": 500}, \"remaining-players\": 23}"],
["tick", "15000"]
],
"content": ""
}Player Data
kind:38744 - Player Gaming Profile (Addressable)
A player's gaming profile that tracks stats, achievements, and preferences across games.
{
"kind": 38744,
"tags": [
["d", "<game-identifier>"], // Required: Specific game or "global" for all games
["game", "<game-identifier>"], // Required: Game this profile is for
["level", "<number>"], // Optional: Player level
["rank", "<rank-identifier>"], // Optional: Player rank/tier
["wins", "<number>"], // Optional: Total wins
["losses", "<number>"], // Optional: Total losses
["draws", "<number>"], // Optional: Total draws
["playtime", "<seconds>"], // Optional: Total playtime in seconds
["rating", "<number>"], // Optional: Skill rating (e.g., ELO)
["avatar", "<image-url>"], // Optional: In-game avatar
["faction", "<faction-id>"], // Optional: Team/faction membership
["achievement", "<achievement-id>", "<unlocked-timestamp>"], // Optional: Unlocked achievements
["stat", "<stat-name>", "<value>"] // Optional: Custom game-specific stats
],
"content": "", // Optional: Additional profile data
// other fields...
}Example:
{
"kind": 38744,
"tags": [
["d", "nostr-royale"],
["game", "nostr-royale"],
["level", "47"],
["rank", "diamond"],
["wins", "342"],
["losses", "289"],
["playtime", "892340"],
["rating", "2450"],
["achievement", "first-win", "1698765432"],
["achievement", "hundred-wins", "1702345678"],
["stat", "kills", "4521"],
["stat", "headshots", "892"],
["avatar", "https://example.com/avatar.png"]
],
"content": ""
}Game Results & Records
kind:8343 - Game Result (Regular)
Permanent record of a game's outcome. Published when a game session completes.
{
"kind": 8343,
"tags": [
["a", "38743:<host-pubkey>:<session-id>"], // Required: Reference to game session
["game", "<game-identifier>"], // Required: Game identifier
["duration", "<seconds>"], // Optional: Game duration
["completed-at", "<unix-timestamp>"], // Optional: Completion timestamp
["p", "<pubkey>", "<relay-hint>", "winner"], // Winners
["p", "<pubkey>", "<relay-hint>", "loser"], // Losers
["p", "<pubkey>", "<relay-hint>", "draw"], // Draw participants
["score", "<pubkey>", "<score>"], // Player scores
["stat", "<pubkey>", "<stat-name>", "<value>"], // Player statistics
["replay", "<replay-data-url>"] // Optional: Link to replay data
],
"content": "", // Optional: Game summary or commentary
// other fields...
}Example:
{
"kind": 8343,
"tags": [
["a", "38743:91cf9..4e5ca:br-island-match-001"],
["game", "nostr-royale"],
["duration", "1847"],
["completed-at", "1735693200"],
["p", "14aeb..8dad4", "wss://relay.nostr.band", "winner"],
["score", "14aeb..8dad4", "8"], // 8 kills
["stat", "14aeb..8dad4", "damage-dealt", "2450"],
["stat", "14aeb..8dad4", "survival-time", "1847"],
["replay", "https://replays.example.com/br-island-match-001.json"]
],
"content": "Epic final showdown in the storm! 🏆"
}Trading & Economy
kind:38745 - Trade Offer (Addressable)
A trade offer between players for in-game items, currency, or NFTs.
{
"kind": 38745,
"tags": [
["d", "<unique-trade-id>"], // Required: Unique trade identifier
["game", "<game-identifier>"], // Required: Game context
["status", "<pending|accepted|rejected|expired|completed>"], // Required: Trade status
["p", "<counterparty-pubkey>"], // Required: Other party in trade
["offer-item", "<item-id>", "<quantity>", "<metadata>"], // Items being offered
["request-item", "<item-id>", "<quantity>", "<metadata>"], // Items being requested
["expires", "<unix-timestamp>"], // Optional: Expiration time
["escrow", "<escrow-pubkey>"] // Optional: Escrow service pubkey
],
"content": "", // Optional: Trade message or notes
// other fields...
}Example:
{
"kind": 38745,
"tags": [
["d", "trade-20250101-abc"],
["game", "nostr-quest"],
["status", "pending"],
["p", "14aeb..8dad4"],
["offer-item", "sword-legendary-001", "1", "{\"rarity\": \"legendary\"}"],
["offer-item", "gold-coins", "500", "{}"],
["request-item", "armor-epic-042", "1", "{\"rarity\": \"epic\"}"],
["expires", "1735779600"]
],
"content": "Looking to trade my legendary sword for epic armor. Fair trade!"
}kind:8344 - Trade Completion (Regular)
Permanent record of a completed trade.
{
"kind": 8344,
"tags": [
["e", "<trade-offer-event-id>"], // Required: Reference to trade offer
["p", "<party-1-pubkey>"], // Required: First party
["p", "<party-2-pubkey>"], // Required: Second party
["completed-at", "<unix-timestamp>"], // Required: Completion time
["item", "<from-pubkey>", "<to-pubkey>", "<item-id>", "<quantity>"] // Items transferred
],
"content": "", // Optional: Trade notes
// other fields...
}Achievements & Badges
kind:8345 - Achievement Unlock (Regular)
Permanent record of achievement unlocked by a player.
{
"kind": 8345,
"tags": [
["game", "<game-identifier>"], // Required: Game identifier
["achievement", "<achievement-id>"], // Required: Achievement identifier
["title", "<achievement-title>"], // Required: Achievement name
["description", "<achievement-description>"], // Optional: Description
["rarity", "<common|rare|epic|legendary>"], // Optional: Rarity tier
["points", "<number>"], // Optional: Points value
["image", "<badge-image-url>"], // Optional: Badge image
["proof", "<proof-data>"] // Optional: Proof of achievement
],
"content": "", // Optional: Achievement story or context
// other fields...
}Example:
{
"kind": 8345,
"tags": [
["game", "nostr-royale"],
["achievement", "double-digit-wins"],
["title", "Perfect Ten"],
["description", "Win 10 consecutive matches"],
["rarity", "epic"],
["points", "100"],
["image", "https://example.com/achievements/perfect-ten.png"],
["proof", "{\"matches\": [\"match1\", \"match2\", ...]}"]
],
"content": "Achieved 10 consecutive victories! 🔥"
}Matchmaking & Invitations
kind:25213 - Matchmaking Request (Ephemeral)
Request to join matchmaking queue.
{
"kind": 25213,
"tags": [
["game", "<game-identifier>"], // Required: Game identifier
["mode", "<game-mode>"], // Required: Game mode
["rank", "<rank-range-min>", "<rank-range-max>"], // Optional: Rank range
["region", "<region-code>"], // Optional: Preferred region
["party", "<party-leader-pubkey>"] // Optional: Party leader if in group
],
"content": "",
// other fields...
}kind:8346 - Game Invitation (Regular)
Invitation to join a game session.
{
"kind": 8346,
"tags": [
["a", "38743:<host-pubkey>:<session-id>"], // Required: Game session reference
["p", "<invited-pubkey>"], // Required: Invited player
["expires", "<unix-timestamp>"], // Optional: Invitation expiry
["role", "<player|spectator>"] // Optional: Invited role
],
"content": "Join me for a battle royale match!",
// other fields...
}Leaderboards & Rankings
kind:38746 - Leaderboard (Addressable)
Leaderboard for a specific game, mode, or timeframe.
{
"kind": 38746,
"tags": [
["d", "<leaderboard-id>"], // Required: Leaderboard identifier
["game", "<game-identifier>"], // Required: Game identifier
["type", "<global|seasonal|event>"], // Required: Leaderboard type
["metric", "<rating|wins|kills|score>"], // Required: Ranking metric
["season", "<season-id>"], // Optional: Season identifier
["starts", "<unix-timestamp>"], // Optional: Leaderboard period start
["ends", "<unix-timestamp>"], // Optional: Leaderboard period end
["entry", "<rank>", "<pubkey>", "<score>", "<data>"] // Leaderboard entries
],
"content": "",
// other fields...
}Example:
{
"kind": 38746,
"tags": [
["d", "global-rating-2025-q1"],
["game", "nostr-royale"],
["type", "seasonal"],
["metric", "rating"],
["season", "2025-q1"],
["starts", "1735689600"],
["ends", "1743552000"],
["entry", "1", "14aeb..8dad4", "3250", "{\"wins\": 142, \"losses\": 34}"],
["entry", "2", "91cf9..4e5ca", "3180", "{\"wins\": 128, \"losses\": 41}"],
["entry", "3", "a1b2c..3d4e5", "3095", "{\"wins\": 115, \"losses\": 38}"]
],
"content": ""
}Implementation Guidelines
Client Behavior
Game Clients SHOULD:
- Subscribe to the game session (
kind:38743) and monitor for updates - Subscribe to real-time game actions (
kind:25211,kind:25212) with appropriate filters - Implement proper conflict resolution for concurrent actions
- Validate game state updates against known game rules
- Handle network latency and packet loss gracefully
- Cache recent game state locally for offline replay
- Implement anti-cheat measures where applicable
Game Clients MAY:
- Act as authoritative servers for peer-to-peer games
- Validate other players' actions and broadcast violations
- Implement client-side prediction for better responsiveness
- Store replay data locally or publish to blob storage
- Cross-reference achievements with game results for validation
Relay Behavior
Relays MAY:
- Implement specialized gaming relays with lower latency
- Provide game-specific query filters and indexes
- Validate game events against game rules
- Rate-limit game action events to prevent spam
- Store game results (
kind:8343) while discarding ephemeral events - Implement geographic routing for regional matchmaking
Relays SHOULD:
- Respect ephemeral event lifetimes
- Index game sessions by game identifier and status
- Maintain leaderboards and rankings efficiently
- Provide WebSocket connections optimized for gaming
Anti-Cheat Considerations
This NIP does not mandate specific anti-cheat mechanisms, but implementations SHOULD consider:
- Server-Authoritative Design: Critical game state should be validated by a trusted host/server
- Proof of Work: Require PoW for game actions to rate-limit spam
- Reputation Systems: Track player behavior and flag suspicious patterns
- Cryptographic Signatures: Verify all events are properly signed
- State Validation: Cross-validate game state across multiple observers
- Replay Analysis: Store and analyze game replays for cheating patterns
- Community Moderation: Allow players to report and vote on suspicious behavior
Interoperability
To maximize interoperability between games and clients:
- Standardize Common Elements: Use consistent item identifiers, stat names, and achievement structures where possible
- Version Compatibility: Include version tags in all game-specific events
- Fallback Behavior: Gracefully handle unknown tags or event types
- Documentation: Game developers SHOULD publish schemas for their custom tags
- Cross-Game Items: Consider supporting cross-game item transfers where applicable
Examples
Complete Battle Royale Flow
- Host creates game session:
{
"kind": 38743,
"tags": [
["d", "br-001"],
["game", "nostr-royale"],
["title", "Evening Battle Royale"],
["status", "waiting"],
["max-players", "100"],
["current-players", "1"],
["game-mode", "battle-royale"],
["p", "91cf9..4e5ca", "", "host"]
],
"content": ""
}- Players join (host updates session):
{
"kind": 38743,
"tags": [
["d", "br-001"],
// ... other tags ...
["current-players", "47"],
["p", "91cf9..4e5ca", "", "host"],
["p", "14aeb..8dad4", "", "player"],
["p", "a1b2c..3d4e5", "", "player"]
// ... more players ...
],
"content": ""
}- Game starts (status update):
{
"kind": 38743,
"tags": [
["d", "br-001"],
// ... other tags ...
["status", "in-progress"],
["starts", "1735693200"]
],
"content": ""
}- Real-time gameplay (ephemeral actions):
// Player movement
{
"kind": 25211,
"tags": [
["a", "38743:91cf9..4e5ca:br-001"],
["action", "move"],
["data", "{\"x\": 125.5, \"y\": 67.2}"],
["tick", "1000"]
],
"content": ""
}
// State update from host
{
"kind": 25212,
"tags": [
["a", "38743:91cf9..4e5ca:br-001"],
["state-type", "player-state"],
["data", "{\"players\": [{\"pubkey\": \"14aeb..8dad4\", \"pos\": [125.5, 67.2], \"hp\": 100}]}"],
["tick", "1000"]
],
"content": ""
}- Game completes (result record):
{
"kind": 8343,
"tags": [
["a", "38743:91cf9..4e5ca:br-001"],
["game", "nostr-royale"],
["duration", "1847"],
["p", "14aeb..8dad4", "", "winner"],
["score", "14aeb..8dad4", "8"]
],
"content": "Incredible victory! 🏆"
}- Achievement unlocked:
{
"kind": 8345,
"tags": [
["game", "nostr-royale"],
["achievement", "first-win"],
["title", "First Victory Royale"],
["rarity", "common"]
],
"content": "Won my first battle royale!"
}Turn-Based Game (Chess-like)
For turn-based games, use regular events instead of ephemeral:
{
"kind": 8347, // Custom kind for turn-based moves
"tags": [
["a", "38743:host-pubkey:chess-001"],
["turn", "42"],
["move", "e4"], // Or use standardized notation
["prev", "<previous-move-event-id>"]
],
"content": "",
// other fields...
}Security Considerations
- Validate Event Signatures: Always verify that events are signed by the claimed pubkey
- Rate Limiting: Implement rate limits on game actions to prevent spam
- State Validation: Don't trust client-reported state; validate with authoritative source
- Replay Protection: Use sequence numbers or timestamps to prevent replay attacks
- Privacy: Consider privacy implications of real-time position data
- DDoS Protection: Ephemeral events can be used for DDoS; relays should implement protections
Future Extensions
This NIP provides a foundation for gaming on Nostr. Future extensions might include:
- Guilds/Clans: Kind for persistent gaming organizations
- Tournaments: Multi-session competitive events
- Spectator Mode: Enhanced support for watching games
- Voice Chat: Integration with Nostr-based voice systems
- Game Assets: NFT-like game items with provable ownership
- Cross-Chain Integration: Bridge to blockchain-based game economies
- Mod Support: Distribution and verification of game modifications
References
- [NIP-01: Basic Protocol](01.md)
- [NIP-53: Live Activities](53.md)
- [NIP-64: Chess](64.md)
- [NIP-72: Moderated Communities](72.md)
License
This NIP is released into the public domain.