# Provable Games > Using cryptography to create incorruptible and indestructible fun. ## Battle System Summit uses the same proven combat mechanics from [Loot Survivor](/lootsurvivor/guide/battle), creating a familiar yet competitive environment where your beasts battle each other instead of adventurers. ### Combat Fundamentals #### Turn-Based Combat Just like in Loot Survivor, combat follows a turn-based system: 1. **Attacker Goes First** - The challenging beast attacks 2. **Defender Counter-Attacks** - If they survive, they strike back 3. **Repeat** - Continue until one beast falls The key difference in Summit is that **both combatants are beasts**, making type advantages and upgrades even more crucial. #### Damage Calculation Damage in Summit follows the Loot Survivor formula with some additions: **Base Damage Formula:** ``` Power = Beast Level × (6 - Beast Tier) ``` **Damage Modifiers:** * **Type Advantage**: +50% damage when strong, -50% when weak * **Attack Potions**: Each potion adds 10% bonus damage (strength) * **Critical Hits**: Chance based on Luck stat (0% base, up to 95%) * **Special Abilities**: Prefix/suffix bonuses when unlocked ### Type Advantage System Summit uses the same rock-paper-scissors system from Loot Survivor:
{/* Brute - Top */}
🔨
Brute
Bludgeon
{/* Hunter - Bottom Left */}
⚔️
Hunter
Blade
{/* Magical - Bottom Right */}
Magical
Magic
{/* Center circular arrows */} {/* Three arrows showing advantage flow */} {/* Brute to Hunter (pointing bottom left) */} {/* Hunter to Magical (pointing right) */} {/* Magical to Brute (pointing top left) */}
**Remember:** Brute beats Hunter, Hunter beats Magical, Magical beats Brute ### Summit-Specific Combat Features #### Multi-Beast Attacks Summit allows you to attack with multiple beasts in sequence: 1. **Sequential Combat** - Beasts fight one at a time 2. **All Attacks in One Transaction** - Your sequence of beasts attacks in a single transaction, giving you a better chance to take the Summit 3. **Strategic Order** - Choose your beast order wisely #### Multi-Attack with One Beast You can attack multiple times with the same beast in a single transaction by using revival potions: 1. **Chain Attacks** - Your beast attacks, dies, revives, and attacks again 2. **Atomic Execution** - All attacks happen in one transaction, preventing the Summit holder from adding extra lives or other defensive measures between attacks 3. **Attack Potions** - Combining Attack Potions with a multi-attack applies the potions to all your attacks, making it the most effecient use of those potions 4. **Revival Cost** - Each revival costs increasing amounts of potions (1st costs 1, 2nd costs 2, etc.) 5. **Effective for Securing Summit** - This strategy is particularly powerful for overwhelming defenders #### Extra Lives System Defenders can have multiple lives: * When health reaches 0, an extra life is consumed * Beast returns to full health (base + bonus) * Combat continues without interruption * Beasts can have up to 4000 extra lives #### Critical Hit Mechanics Critical hits in Summit are determined by the Luck stat: | Luck | Critical Chance | | ---- | --------------- | | 0 | 0% | | 1 | 10% | | 10 | 25% | | 50 | 65% | | 100 | 100% | **Critical Hit Damage**: +100% base damage ### Strategic Considerations #### Attacking Strategy When planning your assault: 1. **Scout the Defender** - Check their type, health, and extra lives 2. **Order Your Beasts** - Put your strongest with type advantage first 3. **Load Attack Potions** - Apply optimal amount of attack potions 4. **Time Your Strike** - Attack when defender is weakened #### Defending Strategy To hold the Summit longer: 1. **Maximize Health** - Feed $CORPSE tokens for bonus health (up to 2000) 2. **Stock Extra Lives** - Apply them immediately after taking Summit 3. **Choose Defensive Upgrades** - Wisdom to gain bonus levels, Luck to get critical chance, Specials to unlocks name match bonus #### Type Matchup Planning Since you know the defending beast's type: * **Favorable Matchup**: +50% damage makes victory likely * **Neutral Matchup**: Comes down to stats and potions * **Unfavorable Matchup**: -50% damage makes victory difficult ### Battle Rewards #### Experience Points XP in Summit works differently than Loot Survivor: **Attackers Gain:** * Base: 10 XP per attack * Attack Streak Bonus: +1 XP per consecutive attack (max +10) * Beasts can gain up to 40 bonus levels **Defenders Gain:** * XP = Defeated attacker's power / 100 * Wisdom upgrade required * Rewards successful defense ### Tips for Success #### Know Your Enemy * Each beast type has predictable strengths and weaknesses * Plan your attacks around type advantages * Consider the defender's upgrades and health #### Resource Efficiency * Don't waste potions on unlikely victories * Save revival potions for coordinated attacks * Balance upgrades amongst your strongest beasts #### Timing is Everything * Attack during low activity periods * Coordinate with allies for sequential strikes * Take advantage of weakened defenders The battle system rewards both tactical planning and strategic resource management. Master these mechanics to dominate the Summit! ## Beast Upgrades Summit introduces a unique upgrade system that expands upon your [Loot Survivor Beasts](/lootsurvivor/beasts), adding new strategic layers through attributes and abilities that are exclusive to Summit gameplay. ### How Upgrades Work Your beasts can be enhanced using tokens earned from Loot Survivor gameplay: * **Skull Tokens** - Earned when your beasts kill adventurers (1 per kill). Used to increase attributes or unlock abilities * **Corpse Tokens** - Extracted from your dead adventurers (1 per level). Used exclusively to increase your beast's health * **One-time upgrades** - Permanent improvements that fundamentally change how your beast performs in Summit The key is that Summit rewards players who are active in both games - your success in Loot Survivor directly translates to more powerful beasts in Summit: * **$SKULL Tokens** - Earned when your beasts kill adventurers (1 per kill) * **$CORPSE Tokens** - Extracted from your dead adventurers (1 per adventurer level) * **Beast Locking** - When an adventurer kills one of your beasts in Loot Survivor, that beast gets locked in Summit for 24 hours and cannot attack ### Attributes #### 🎲 Luck - Critical Hit Chance Luck increases your chance to land critical hits for massive damage. **Cost:** 1 $Skull per level (up to 100) **Best For:** High powered beasts focused on dealing damage #### ⚡ Spirit - Revival Time Reduction Spirit reduces the revival time after your beast dies, getting them back in action faster. **Effect:** * Each point of Spirit reduces revival time **Revival Time Calculation:** * Base: 24 hours * With Maximum Spirit: Reduced by 20 hours * Minimum possible: 4 hours **Cost:** 1 $Skull per level (up to 100) **Best For:** Active players who attack frequently ### Abilities Abilities are powerful one-time unlocks that fundamentally change how your beast performs: #### ⭐ Specials - Name Match Bonuses Unlocks your beast's prefix and suffix abilities, providing combat bonuses based on name matches. **Effect:** * Activates prefix ability (8x bonus damage) * Activates suffix ability (2x bonus damage) * Bonuses apply when names match in combat **Cost:** 10 Skull Tokens **Best For:** Beasts with matching names against strong defenders #### 🤝 Diplomacy - Shared Rewards & Power Boost Empower and share rewards with other beasts that have the same prefix and suffix. **Effect:** * When a matching beast holds Summit, you gain a share of the $SURVIVOR reward * Empowers the matching Summit holder with a damage bonus based on your beast's power * Creates alliance opportunities * Encourages coordinated play **Damage Bonus Calculation:** ``` Damage Boost = (Total Power of all Diplomacy beasts / 250) × 10% ``` **Example:** * Your "Agony Bane" beast is holding the Summit * Two other "Agony Bane" beasts (Power 150 each) have Diplomacy unlocked * Their Total Power = 150 + 150 = 300 * Summit holder gets: (300 / 250) × 10% = 10% damage boost * Note: Summit holder does not get bonus from itself unlocking Diplomacy **Cost:** 15 Skull Tokens **Best For:** * Players with multiple matching beasts or coordinating with allies * Casual players who won't take Summit themselves but have name matches with powerful players #### 🧠 Wisdom - Defensive XP Gain Gain experience points when successfully defending attacks on the Summit. **Effect:** * XP gained = Attacker's power / 100 * Only triggers on successful defense * Helps level up while holding Summit **Cost:** 20 Skull Tokens **Best For:** Defensive strategies and long Summit reigns ### Vitality - Bonus Health #### ❤️ Health Enhancement Permanently increase your beast's health using Corpse Tokens. **Stats:** * Maximum Bonus: 2,000 health * Cost: 1 Corpse Token per health point **Strategic Impact:** * More health = longer Summit defense * Essential for tanky builds * Pairs well with Extra Life potions ### Upgrade Strategy Guide #### Offensive Build Focus on maximizing damage output over time: 1. **Luck** to gain critical chance 2. **Specials** for name bonuses 3. **Spirit** for frequent attacks #### Defensive Build Optimize for holding the Summit: 1. **Maximum health** (2,000 bonus) 2. **Wisdom** for XP while defending 3. **Moderate Luck** for counter-attacks #### Alliance Build For coordinated groups: 1. **Diplomacy** on matching beasts 2. **Balanced stats** across team 3. **Coordinate Summit control** ### Token Management #### Earning Tokens Tokens are earned through [Loot Survivor](/lootsurvivor) gameplay: ##### Skull Tokens Earned when your beast successfully kills an adventurer: * **1 Skull Token** per adventurer killed * Accumulates across all your beasts ##### Corpse Tokens Extracted from your own dead adventurers: * **1 Corpse Token per adventurer level** * Only from adventurers you own * Adventurer must be dead (0 health) * Extract tokens through the Summit interface **Example:** A dead level 15 adventurer yields 15 Corpse Tokens #### Spending Priority Consider these factors: 1. **Immediate needs** - What helps you win now? 2. **Long-term goals** - Building for sustained success 3. **Beast potential** - Invest in your strongest beasts 4. **Play style** - Match upgrades to how you play The upgrade system transforms your beasts into customized warriors. Choose wisely - these permanent improvements define your Summit strategy! ### What Happens After Summit Ends? There are currently no planned expansions or additional seasons of Summit beyond the current game. Summit is a finite experience — once the reward pool is distributed, the game concludes. **Will upgrades persist if another Summit happens?** If a future Summit is introduced, our intention is to carry over beast upgrades and stats. However, we cannot guarantee this, as it depends on factors that are not yet known — including game design decisions, technical considerations, and the needs of future player bases. Our commitment is to build valuable and fun experiences. We will not compromise future gameplay, as it would be -EV for everyone in the ecosystem. What we can say is that the upgrades you earn in this Summit are designed for this Summit — to help you compete, complete quests, and earn $SURVIVOR. **The right mindset:** Upgrade your beasts for the game in front of you. Every $SKULL and $CORPSE you spend should be an investment in winning now — holding the Summit longer, completing more quests, and claiming your share of the 92,000 $SURVIVOR reward pool (56,000 Summit, 36,000 quests) plus 8,000 $SURVIVOR seeded as initial consumable liquidity. ## Consumables Potions are the strategic fuel of Summit battles. Knowing when and how to use them can mean the difference between a brief appearance and a legendary reign. ### Types of Potions #### 🔄 Revival Potions Skip the 24-hour death cooldown and get back in the fight immediately. **How They Work:** * Instantly revives a dead beast * Cost increases with each use on the same beast * Essential for chain attacks **Cost Scaling:** | Revival # | Potions Required | Total Used | | --------- | ---------------- | ---------- | | 1st | 1 | 1 | | 2nd | 2 | 3 | | 3rd | 3 | 6 | | 4th | 4 | 10 | | ... | ... | ... | | 64th | 64 | 904 | **Note:** Revival cost caps at 64 potions. All revivals will cost 64 potions at this point. **Strategic Uses:** * Enable rapid counter-attacks after defeat * Chain multiple beast assaults * Maintain pressure on Summit holder * React quickly to opportunities * Combine with attack potions in a multi-attack #### ⚔️ Attack Potions Boost your damage output for devastating first strikes. **How They Work:** * Each potion increases attack damage * Only usable by attackers (not defenders) * Maximum 255 potions per beast **Strategic Uses:** * Overwhelm high-health defenders * Secure one-shot kills with type advantage * Break through extra lives quickly * Make impossible matchups winnable #### ❤️ Extra Life Potions Give your beast multiple chances to defend the Summit. **How They Work:** * Automatically activates when health reaches 0 * Restores beast to full health (base + bonus) * Can only be applied to current Summit holder * Maximum 4000 extra lives **Strategic Uses:** * Extend Summit defense duration * Discourage attackers (psychological warfare) * Survive coordinated assaults #### ☠️ Poison Potions Slowly weaken the Summit holder over time without direct combat. **How They Work:** * Deals 1 damage per second to the Summit holder * Damage stops at 1 health and 0 extra lives (cannot kill) * Multiple poison potions stack for faster damage * Can only be applied to current Summit holder **Strategic Uses:** * Weaken defenders before attacking * Force defenders to waste extra lives * Create time pressure on Summit holders * Coordinate with allies for timed strikes ### Potion Market #### Continuous Clearing Auction (CCA) Potions are distributed using a Continuous Clearing Auction (CCA) — the same mechanism used for [Loot Survivor's dungeon tickets](/lootsurvivor/dungeon-tickets). Potions are the revenue source of Summit. **How It Works:** * Potions are streamed block-by-block into an [Ekubo](https://ekubo.org/) TWAMM (Time-Weighted Average Market Maker) pool * Price starts intentionally high at game launch and decreases over time as supply enters the pool * The price naturally settles at what players are willing to pay -no more, no less * At launch, each of the four potion pools is seeded with **2,000 $SURVIVOR** in initial liquidity for price discovery and stability. **Daily Issuance Rates:** | Potion Type | Tokens Per Day | Initial Liquidity | Starting Price | | ------------- | -------------- | ----------------- | -------------- | | ⚔️ Attack | 97,206 | 2,000 $SURVIVOR | $0.01 | | 🔄 Revival | 97,206 | 2,000 $SURVIVOR | $0.01 | | ☠️ Poison | 97,206 | 2,000 $SURVIVOR | $0.01 | | ❤️ Extra Life | 4,860 | 2,000 $SURVIVOR | $0.20 | **Key Features:** * Price adjusts based on supply and demand in real time * Current price is always visible before purchase * Purchase with any token on Starknet **Strategic Purchasing:** * No need to rush — prices start high and come down as potions stream in * Monitor price trends for optimal buying times * Consider DCA (Dollar-Cost Averaging) orders for bulk purchases **Revenue Distribution:** 100% of proceeds from potion sales are used to buy back $SURVIVOR tokens for the DAO treasury, supporting the long-term sustainability of the Survivor ecosystem. ### Tips for Success #### For Beginners 1. Start with revival potions to learn timing 2. Use attack potions with type advantage 3. Apply extra lives on attack before taking Summit #### For Veterans 1. Calculate exact potion requirements 2. Time market purchases strategically 3. Coordinate group strategies 4. Optimize cost per second held #### Common Mistakes * Using attack potions with type disadvantage * Applying extra lives on weak beasts * Reviving the same beast too many times * Ignoring potion economics The key to potion mastery is understanding not just what each does, but when each creates maximum value. Use them wisely, and the Summit will be yours! ### After Summit Ends Potions are consumables designed exclusively for Summit gameplay. Once the game concludes and the reward pool is fully distributed, potions will have no further use case. Purchase and use them with this in mind — they are a tool for winning now, not an asset to hold. ## Disclaimers This documentation is provided for informational purposes only and is not legal, financial, tax, or investment advice. You are responsible for understanding and complying with all applicable laws, rules, and regulations in your jurisdiction. ### Smart Contract Risk (Unaudited) Summit is powered by smart contracts. **These smart contracts are unaudited** and may contain bugs, design flaws, or security vulnerabilities. Smart contracts and blockchain systems can fail unexpectedly, be exploited, or behave in ways that cause partial or total loss of funds, tokens, in-game items, or other assets. ### No Warranties (As-Is, As-Available) Summit and all related software, smart contracts, interfaces, and documentation are provided on an **“AS IS”** and **“AS AVAILABLE”** basis, without warranties of any kind (express, implied, or statutory), including warranties of merchantability, fitness for a particular purpose, non-infringement, or that the system will be uninterrupted, secure, error-free, or free of harmful components. ### Assumption of Risk; Irreversible Transactions By interacting with Summit, you acknowledge and accept the risks of cryptographic and blockchain-based systems, including volatility, MEV/front-running, downtime, congestion, reorgs, forks, bridge failures, oracle or pricing anomalies, and wallet compromise. Blockchain transactions are typically **irreversible** once confirmed, and neither the Summit team nor any contributors can reverse, cancel, or recover transactions or assets. ### Third-Party Dependencies Summit may integrate with or rely on third-party protocols, networks, wallets, RPC providers, bridges, liquidity venues, and other services. Those third parties are not under our control, and failures or changes in third-party services may result in loss of assets, degraded functionality, incorrect displays of information, or inability to access features. ### Limitation of Liability To the fullest extent permitted by law, in no event will the Summit team, contributors, or any related parties be liable for any indirect, incidental, special, consequential, exemplary, or punitive damages, or for any loss of profits, revenue, goodwill, data, or assets, arising out of or related to your access to or use of Summit, the smart contracts, or any related services, even if advised of the possibility of such damages. ### Your Responsibility You are solely responsible for safeguarding your wallet, private keys, and recovery phrases, verifying addresses and transaction parameters, and understanding the mechanics of any smart contract interactions before you approve them. You are also solely responsible for any taxes arising from your activity. ## Summit: FAQ Find answers to common questions about Summit. Summit is currently in alpha and details may change. ### General **What is Summit?** Summit is an all-against-all, king-of-the-hill PvP arena where players use Loot Survivor Beasts to battle for control of the Summit and earn $SURVIVOR rewards. **Is Summit still in alpha?** Yes. Summit is currently in alpha. Game mechanics, rewards, and features are subject to change. **How long does a Summit game last?** Each Summit game lasts 8 million seconds (approximately 93 days). **What is the total reward pool?** 92,000 $SURVIVOR total: 56,000 for holding the Summit and 36,000 for quest completion, plus 8,000 $SURVIVOR seeded as initial liquidity for the four consumables at launch. ### Getting Started **What do I need to play?** You need Beasts from Loot Survivor. Beasts are earned by killing monsters during Loot Survivor gameplay or can be purchased on secondary markets. **How do I get Beasts?** Play Loot Survivor and kill beasts during your adventure — each beast kill mints a collectible Beast. You can also buy them on secondary markets. **How do I start?** Select one of your Beasts and attack the current Summit holder. Your first attack unlocks quests for that beast. ### Battle System **How do battles work?** Battles are turn-based: the attacker strikes first, then the defender counter-attacks. Damage is calculated using Base Power = Beast Level x (6 - Beast Tier), modified by type advantages and upgrades. **What are the type advantages?** Rock-paper-scissors system: Brute beats Hunter, Hunter beats Magical, Magical beats Brute. Having type advantage gives +50% damage; disadvantage gives -50% damage. **What is multi-attack?** You can attack the Summit multiple times in a single transaction using revival potions. The cost increases per use (1st costs 1 potion, 2nd costs 2, etc., up to a max cost of 64). All attacks happen atomically, preventing the defender from making changes mid-battle. **How do critical hits work?** Critical hits deal +100% base damage. The chance depends on your beast's Luck stat, ranging from 0% at Luck 0 to 100% at Luck 100. **How does XP work?** Attackers earn 10 XP base + 1 XP per consecutive attack (max +10 bonus). Attackers can gain up to 40 bonus levels. Defenders earn XP only if they have the Wisdom upgrade (XP = attacker's power / 100). ### Beast Upgrades **What are $SKULL and $CORPSE tokens?** $SKULL tokens are earned by killing beasts in Loot Survivor (1 per kill) and used for attribute and ability upgrades. $CORPSE tokens are earned from dead adventurers in Loot Survivor (1 per adventurer level) and used for health upgrades. **What attributes can I upgrade?** * **Luck**: 1 $SKULL per level (up to 100 levels). Increases critical hit chance. * **Spirit**: 1 $SKULL per level (up to 100 levels). Reduces revival cooldown from 24 hours down to 4 hours at max level. **What abilities can I unlock?** * **Specials** (10 $SKULL): Activates prefix (8x) and suffix (2x) damage bonuses when beast names match. * **Diplomacy** (15 $SKULL): Share rewards with other beasts that have matching names. * **Wisdom** (20 $SKULL): Gain XP when defending (XP = attacker power / 100). * **Vitality** (1 $CORPSE per point): Add bonus health, up to 2,000 extra HP. ### Consumables **What potions are available?** * **Revival Potions**: Skip the 24-hour death cooldown. Cost increases per use (1, 2, 3... up to 64). * **Attack Potions**: +10% bonus damage per potion (max 255 per beast). Attackers only. * **Extra Life Potions**: Give defenders multiple lives (max 4,000). Restores full health when consumed. * **Poison Potions**: Deal 1 damage per second to the Summit holder. Multiple stack. Cannot kill (stops at 1 HP with 0 extra lives). **How does the potion market work?** Potions are sold through a Continuous Clearing Auction (CCA) via Ekubo liquidity pool. Prices start high and decrease over time. 100% of proceeds go to buy back $SURVIVOR for the DAO treasury. ### Rewards & Quests **How do I earn $SURVIVOR from holding the Summit?** The Summit holder earns 0.007 $SURVIVOR per second. You must hold for at least one full block (approximately 2-3 seconds) to earn anything. Rewards are claimed when your beast is dethroned. **How does Diplomacy reward sharing work?** Each beast with the Diplomacy upgrade and a matching name earns 0.00005 $SURVIVOR per second while a matching beast holds the Summit. Up to 75 matching beasts can earn simultaneously. **What quests are available?** Quests include: First Blood (attack the Summit), Consistency is Key (10-attack streak), level-up milestones (1/3/5/10 levels), Summit Conqueror (capture the Summit), Iron Grip (hold for 10+ seconds), Second Wind (revive and attack), and A Vital Boost (use attack potion). Each beast can earn up to 0.95 $SURVIVOR from quests. The 36,000 $SURVIVOR quest pool is first come, first serve. ### Strategy **How should I choose which beast to use?** Check the current Summit holder's type. Use a beast with type advantage for +50% damage. Higher level and lower tier beasts deal more base damage. **When is the best time to attack?** Attack during low-activity periods or when top beasts are on cooldown (24 hours after death, reduced by Spirit upgrades). Early in the game, there are fewer beasts competing and lower upgrade costs. **Should I focus on offense or defense?** Offensive builds (Attack Potions, Luck for crits) are good for capturing the Summit. Defensive builds (Extra Lives, Vitality) help hold it longer to earn more $SURVIVOR per second. ### After the Game Ends **Will there be another Summit season?** There are no planned additional seasons at this time. **Do my beast upgrades carry over?** Upgrades are designed for the current Summit. While upgrades may carry over to future events, this is not guaranteed. ## Savage Summit Savage Summit is an all-against-all, king-of-the-hill battle game where players use their [Loot Survivor Beasts](/lootsurvivor/beasts) to fight for control of the Summit. The beast standing on the Summit earns rewards based on how long they can defend their position. ### How It Works Summit takes the collectible beasts from Loot Survivor and gives them a new battlefield where they can compete directly against each other. Think of it as a PvP arena where your beasts battle for supremacy and earn rewards. #### The Basic Loop 1. **Challenge the Summit** - Use your beasts to attack whoever currently holds the Summit 2. **Claim Victory** - If your beast wins, it becomes the new king of the hill 3. **Defend Your Throne** - Fight off challengers to maintain control 4. **Earn Rewards** - Receive 0.007 $SURVIVOR for every second you hold the Summit #### What You Need to Play * **Beast NFTs** * Collect them by playing [Loot Survivor](https://lootsurvivor.io/survivor) * Purchase on marketplace: [Marketplace](https://beast-dex.vercel.app/marketplace) ### Key Features #### 🏆 King of the Hill Only one beast can control the Summit at a time. Every attacker tries to dethrone the current king, creating an endless battle for dominance. #### 💀 Revival Mechanics Dead beasts have a 24-hour cooldown before they can fight again - unless you use revival potions to get them back in action immediately. #### 📈 Beast Progression Your beasts gain experience from battles and can be upgraded with new abilities unique to Summit: * **Luck** - Increases critical hit chance * **Spirit** - Reduces revival cooldown time * **Specials** - Unlocks prefix/suffix name match bonus * **Diplomacy** - Empower & Share rewards with matching beasts * **Wisdom** - Gain XP when defending the Summit * **Vitality** - Gain Bonus Health #### 📜 Quests Complete [objectives](/summit/quests) with your beasts to earn $SURVIVOR without holding the Summit: * **10 quests** ranging from first attacks to holding the Summit for 10+ seconds * **Per-beast tracking** — each beast has independent quest progress * **First come, first serve** — 36,000 $SURVIVOR quest pool depletes as players complete quests #### 🧪 Consumables Four types of potions add layers of strategy: * **Revival Potions** - Skip the death cooldown * **Attack Potions** - Boost damage for assault * **Extra Life Potions** - Give defenders multiple lives * **Poison Potions** - Poison the Summit, making it take damage every second ### Game Ending Summit reward allocation is split as follows: * **56,000 $SURVIVOR** for holding the Summit * **36,000 $SURVIVOR** in the quest pool for completing [quests](/summit/quests) * **8,000 $SURVIVOR** seeded as initial liquidity across the Attack, Revival, Poison, and Extra Life potion pools * Game ends after 8 million seconds (93 days) ### Mainnet Contracts | Contract | Address | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Summit Game | [`0x0214d382e80781f8c1059a751563d6b46e717c652bb670bf230e8a64a68e6064`](https://voyager.online/contract/0x0214d382e80781f8c1059a751563d6b46e717c652bb670bf230e8a64a68e6064) | | Attack Potion | [`0x016f9def00daef9f1874dd932b081096f50aec2fe61df31a81bc5707a7522443`](https://voyager.online/contract/0x016f9def00daef9f1874dd932b081096f50aec2fe61df31a81bc5707a7522443) | | Poison Potion | [`0x049eaed2a1bA2F2Eb6Ac2661ffd2d79231CdD7d5293D9448Df49c5986C9897aE`](https://voyager.online/contract/0x049eaed2a1bA2F2Eb6Ac2661ffd2d79231CdD7d5293D9448Df49c5986C9897aE) | | Revive Potion | [`0x029023e0a455d19d6887bc13727356070089527b79e6feb562ffe1afd6711dbe`](https://voyager.online/contract/0x029023e0a455d19d6887bc13727356070089527b79e6feb562ffe1afd6711dbe) | | Extra Life Potion | [`0x016dea82a6588ca9fb7200125fa05631b1c1735a313e24afe9c90301e441a796`](https://voyager.online/contract/0x016dea82a6588ca9fb7200125fa05631b1c1735a313e24afe9c90301e441a796) | | Corpse Token | [`0x0103eafe79f8631932530cc687dfcdeb013c883a82619ebf81be393e2953a87a`](https://voyager.online/contract/0x0103eafe79f8631932530cc687dfcdeb013c883a82619ebf81be393e2953a87a) | | Skull Token | [`0x01c3c8284d7EED443b42F47e764032a56eAf50A9079D67993B633930E3689814`](https://voyager.online/contract/0x01c3c8284d7EED443b42F47e764032a56eAf50A9079D67993B633930E3689814) | See [Disclaimers](/summit/disclaimers) for important legal and smart contract risk information, including the fact that Summit contracts are unaudited. ### Getting Started 1. **Get Beasts** - Own at least one beast NFT 2. **Scout the Summit** - Check who currently holds the position 3. **Plan Your Attack** - Time your assault when you have the best chance 4. **Upgrade Wisely** - Use **$SKULL** tokens earned from killing adventurers and **$CORPSE** tokens earned from fallen adventurers to upgrade your beasts 5. **Complete [Quests](/summit/quests)** - Complete objectives for your beasts to earn $SURVIVOR ### Why Play Summit? * **Direct Competition** - Test your beasts against other players * **Earn Rewards** - $SURVIVOR tokens for holding the Summit and completing [quests](/summit/quests) * **Strategic Depth** - Multiple paths to victory through upgrades and timing * **Community Interaction** - Form alliances or rivalries with other players * **Limited Duration** - Once the time is up and rewards distributed, the game ends Ready to conquer the Summit? Learn about the [Battle System](/summit/battle-system) to understand combat mechanics, or check out the [Quests](/summit/quests) to start earning rewards right away. ## Quests Guide Complete objectives with your beasts to earn $SURVIVOR tokens from the quest reward pool. Quests provide a structured way to earn rewards beyond holding the Summit, rewarding players for engaging with all aspects of the game. ### Reward Pool Quests draw from a dedicated pool of **36,000 $SURVIVOR** tokens: * Each beast can earn up to **0.95 $SURVIVOR** by completing all quests * The pool is **first come, first serve** — once depleted, quests no longer reward tokens * Quest rewards are separate from Summit holding rewards ### Quest List | Quest | Description | Reward ($SURVIVOR) | Notes | | ------------------ | ------------------------------------------------- | ------------------ | ------------------------ | | First Blood | Attack the Summit | 0.05 | Unlocks all other quests | | Consistency is Key | Reach a max attack streak of 10 | 0.10 | | | Growing Stronger | Level up once | 0.04 | Level Up Tier 1 | | Rising Power | Level up 3 times | 0.06 | Level Up Tier 2 | | Apex Predator | Level up 5 times | 0.08 | Level Up Tier 3 | | Mastery | Level up 10 times | 0.12 | Level Up Tier 4 | | Summit Conqueror | Capture the Summit | 0.10 | | | Iron Grip | Hold the Summit for 10+ seconds | 0.20 | Highest reward | | Second Wind | Buy a revival potion and attack with a dead beast | 0.10 | | | A Vital Boost | Buy an attack potion and attack using it | 0.10 | | ### Key Mechanics #### Per-Beast Tracking Quests are tracked **per beast** — each beast has its own independent quest progress. This means you can earn quest rewards across multiple beasts. #### Tiered Level Up Quests The level up quests have 4 progressive tiers with increasing rewards: 1. **Growing Stronger** (1 level) → 0.04 $SURVIVOR 2. **Rising Power** (3 levels) → 0.06 $SURVIVOR 3. **Apex Predator** (5 levels) → 0.08 $SURVIVOR 4. **Mastery** (10 levels) → 0.12 $SURVIVOR Each tier is a separate quest — completing higher tiers also completes the lower ones. #### In-Game Quest Guides Some quests have built-in help guides accessible via the help button in the quests modal. These provide step-by-step instructions for completion. ### Tips for Success #### Getting Started 1. **Check your quest progress** in the quests modal to see what's available 2. **Check your beasts** by hovering over the beast cards to see what's remaining #### Maximizing Rewards 1. **Prioritize Iron Grip** — at 0.20 $SURVIVOR, it's the highest single quest reward 2. **Level up tiers stack** — working toward Mastery (10 levels) completes all four level up quests for a combined 0.30 $SURVIVOR 3. **Spread across beasts** — since quests are per-beast, completing quests on multiple beasts multiplies your total earnings #### Act Fast The quest reward pool is finite and first come, first serve. Early and active players will claim the largest share of the 36,000 $SURVIVOR pool before it's depleted. ## Rewards Guide Summit's reward system is straightforward: hold the Summit longer, earn more $SURVIVOR. With a total gameplay pool of **92,000 $SURVIVOR** (plus initial liquidity noted below) to distribute, every second matters. ### How Rewards Work #### The Prize Pool Summit has a **finite reward pool**: * **Total Distribution**: 92,000 $SURVIVOR tokens * **Summit Holding**: 56,000 $SURVIVOR - distributed to holding the Summit * **Quest Rewards**: 36,000 $SURVIVOR - distributed through quest completions * **Initial Potion Liquidity**: 8,000 $SURVIVOR seeded across the 4 consumable pools at launch * **Rate**: \~0.007 $SURVIVOR per second to the Summit holder * **Game Duration**: 8 million seconds (93 days) #### Block-by-Block Earnings Every block your beast holds the Summit: * Base reward: **\~0.007 $SURVIVOR** per second * Reward accumulates automatically * Can be claimed when your beast is dethroned * **Your beast must hold the Summit for at least one full block to earn any reward.** **Examples (blocks are \~2-3 seconds):** | Scenario | Reward | | ---------------------------------------------------------- | -------------------------------------------------------------- | | Beast claims Summit and is dethroned in the **same block** | **0 $SURVIVOR** — no full block held | | Beast claims Summit and holds for **1 block** (\~2-3s) | **\~0.014-0.021 $SURVIVOR** — rewarded for time between blocks | | Beast holds for **10 blocks** (\~20-30s) | **\~0.07-0.105 $SURVIVOR** | **Diplomacy Reward Sharing:** If matching beasts have the Diplomacy upgrade: * Each Diplomacy beast receives **0.00005 $SURVIVOR per second** * Maximum of 75 matching beasts can exist * Summit holder keeps the remainder **Example Distribution Each Second:** | Diplomacy Beasts | Each Gets | Summit Holder Gets | | ---------------- | --------- | ------------------ | | 0 | - | 0.007 $SURVIVOR | | 10 | 0.00005 | 0.0065 $SURVIVOR | | 75 (max) | 0.00005 | 0.00325 $SURVIVOR | \*$SURVIVOR is the governance token for the Survivor ecosystem ### The Economics of Summit #### Circular Economy Summit creates a sustainable economic loop: 1. **Potion Market Revenue** → 100% used to buy back $SURVIVOR tokens 2. **$SURVIVOR Distribution** → Tokens distributed to Summit holders 3. **Governance Power** → Token holders govern the ecosystem 4. **Market Dynamics** → Demand for potions driven by competition for $SURVIVOR **Fair Distribution:** * No pre-mining or special allocations * Pure merit-based rewards * Skill and strategy determine earnings * Self-sustaining through potion sales ### Maximizing Your Rewards #### Timing Strategies **Early Bird Advantage:** * Fewer beasts in circulation early on = less competition * Lower skull token supply = fewer upgraded beasts * Establish dominance before the ecosystem matures **Peak Hours vs Off-Hours:** * Monitor global activity patterns * Strike during low-competition periods * Coordinate with timezone advantages **Beast Cooldown Windows:** * Track when dominant beasts are on revival cooldown * Attack when top competitors can't respond * Monitor recent battles to predict cooldown timings * 24-hour windows create predictable opportunities **Potion Supply Awareness:** * Monitor current potion market supply * Stock up when prices drop during low demand * Scarcity creates defensive advantages ### Tracking Your Performance #### Key Metrics Monitor these statistics: * **Total $SURVIVOR Earned**: Your lifetime rewards * **Seconds Held**: Total time on Summit * **ROI**: Earnings vs investment #### Alliance Opportunities **Strategic Cooperation:** * Diplomacy creates passive income for supporters (see reward distribution above) * Coordinate attacks with name-matching beast owners * Share upgrade costs and potion resources * Build "beast families" for mutual benefit ### Summary Summit's reward system is elegant in its simplicity: defend the hill, earn $SURVIVOR. With 92,000 $SURVIVOR to distribute — 56,000 for holding the Summit, 36,000 for quest rewards, and 8,000 seeded as initial potion liquidity — success comes down to: 1. **Strategic Timing** - Attack when you can win and hold 2. **Quests Completion** - Focus on completing as many quests as possible 3. **Resource Management** - Invest enough to succeed, not more 4. **Beast Development** - Build defensive capabilities 5. **Governance Value** - $SURVIVOR tokens grant voting power in the Survivor ecosystem The clock is ticking, the reward pool is finite, and only the strongest beasts will claim the largest shares. Make every second count! ## Combat Mechanics Combat in Loot Survivor involves complex calculations that determine damage output between adventurers and beasts. Understanding these mechanics helps you optimize your build and strategy. ### Combat Overview
Combat damage is calculated through a series of steps:
1. **Base Damage Calculation** - Weapon tier and level determine base power 2. **Elemental Adjustments** - Type advantages modify damage by ±50% 3. **Bonus Calculations** - All bonuses use elemental adjusted damage: * **Strength Bonus** - +10% of elemental damage per STR point * **Critical Hit Bonus** - +100% of elemental damage (if triggered) * **Special Bonuses** - Name matches (2× or 8× elemental damage) 4. **Total Attack** - Sum of elemental damage + all bonuses 5. **Armor Reduction** - Base armor (tier × level) subtracted from total 6. **Minimum Damage** - Adventurers deal min 4, beasts deal min 2
### Damage Calculation Formula
#### Step 1: Base Attack & Defense ``` Base Attack = Item Level × (6 - Tier) Base Defense = Armor Level × (6 - Tier) ``` **Tier Damage Formula:** | Tier | Formula (6 - Tier) | Example (Level 10) | | ------ | --------------------- | ------------------ | | **T1** | 10 × (6 - 1) = 10 × 5 | 50 damage | | **T2** | 10 × (6 - 2) = 10 × 4 | 40 damage | | **T3** | 10 × (6 - 3) = 10 × 3 | 30 damage | | **T4** | 10 × (6 - 4) = 10 × 2 | 20 damage | | **T5** | 10 × (6 - 5) = 10 × 1 | 10 damage | #### Step 2: Elemental Adjustment ``` Elemental Effect = Base Attack / 2 ``` * **Strong** (Type advantage): `Base Attack + Elemental Effect` (+50% damage) * **Fair** (Neutral): `Base Attack` (no change) * **Weak** (Type disadvantage): `Base Attack - Elemental Effect` (-50% damage) #### Step 3: Add Bonuses (All Based on Elemental Adjusted Damage) ``` Strength Bonus = (Elemental Adjusted Damage × STR) / 10 Critical Bonus = Elemental Adjusted Damage × 1.0 (if triggered) Special Bonus = Elemental Adjusted Damage × Multiplier (if name match) Total Attack = Elemental Adjusted Damage + Strength Bonus + Critical Bonus + Special Bonus ``` #### Step 4: Apply Armor & Calculate Final Damage ``` Base Armor = Armor Level × (6 - Tier) Final Damage = MAX(Minimum Damage, Total Attack - Base Armor) Minimum Damage: - Adventurer attacks: 4 damage - Beast attacks: 2 damage ```
### Combat Type System
#### The Three Combat Types | Combat Type | Weapon | Armor | Strong Against | Weak Against | | ----------- | -------- | ----- | -------------- | ------------ | | **Brute** | Bludgeon | Metal | Hunter | Magical | | **Hunter** | Blade | Hide | Magical | Brute | | **Magical** | Magic | Cloth | Brute | Hunter | #### Type Advantage Cycle ``` Brute → Hunter → Magical → Brute ``` * **Brute** (Bludgeon + Metal) beats **Hunter** (Blade + Hide) * **Hunter** (Blade + Hide) beats **Magical** (Magic + Cloth) * **Magical** (Magic + Cloth) beats **Brute** (Bludgeon + Metal) > **Combat Rule:** Type advantage grants +50% damage, disadvantage gives -50% damage
### Critical Hits
Critical hits provide bonus damage based on your LUCK stat: #### Critical Hit Chance ``` Crit Chance = LUCK / 100 (%) ``` | LUCK | Crit Chance | | ---- | ----------- | | 0 | 0% | | 25 | 25% | | 50 | 50% | | 75 | 75% | | 100 | 100% | #### Critical Hit Damage When a critical hit occurs, bonus damage is added: * **Critical bonus:** +100% of elemental-adjusted damage * **Total damage:** 2× the normal damage when a critical hit is rolled
### Name Match Bonuses
High-level beasts (19+) and items (Greatness 19+) have special name prefixes that provide combat bonuses when matched. #### Name Structure
Items and beasts can have names like "**Whisper** **Glow** Demon" where:
* **Prefix 1:** "Whisper" (primary name) * **Prefix 2:** "Glow" (secondary name) #### Bonus Calculation | Match Type | Damage Multiplier | | ------------------ | ---------------------- | | **Prefix 2 Match** | 2× elemental damage | | **Prefix 1 Match** | 8× elemental damage | | **Both Match** | Cumulative (16× total) | #### Important Note * **Offense**: If your weapon shares a prefix with a beast, you deal bonus damage. * **Defense**: If your **armor** shares a prefix with a beast, the beast deals bonus damage to you. > **Pro Tip:** Name matches are a double-edged sword—gear that empowers you against some beasts might leave you vulnerable to others!
### Strength Bonus
The Strength stat provides a direct damage increase: ``` Strength Bonus = (Elemental Adjusted Damage × STR) / 10 ``` | STR | Damage Increase | | --- | --------------- | | 0 | +0% | | 5 | +50% | | 10 | +100% | | 15 | +150% | | 20 | +200% |
### Combat Example
**Scenario:** Level 10 Adventurer with T3 Blade attacking a Beast with T4 Hide armor ``` 1. Base Attack = 10 (level) × (6 - 3) = 10 × 3 = 30 2. Elemental: Blade vs Hide = Fair (no change) = 30 3. Bonuses (all based on elemental damage of 30): - Strength Bonus (STR=5): 30 × 0.5 = +15 - Critical Hit: None triggered - Special Bonus: None 4. Total Attack = 30 + 15 = 45 5. Base Armor = 10 (level) × (6 - 4) = 10 × 2 = 20 6. Final Damage = MAX(4, 45 - 20) = 25 HP ``` **Alternative with Type Advantage & Critical:** ``` If using T3 Bludgeon vs Hide (Strong) with critical hit: 1. Base Attack = 30 2. Elemental: 30 + (30/2) = 45 (+50% for type advantage) 3. Bonuses (all based on elemental damage of 45): - Strength (STR=5): 45 × 0.5 = +22 - Critical Hit: 45 × 1.0 = +45 - Special: None 4. Total Attack = 45 + 22 + 45 = 112 5. Base Armor = 20 6. Final Damage = MAX(4, 112 - 20) = 92 HP! ``` **Ultimate Example with Name Match:** ``` T3 Bludgeon "Glow" vs Beast "Glow" (Prefix 2 match): 1. Base Attack = 30 2. Elemental: 30 (neutral) 3. Bonuses (all based on elemental damage of 30): - Strength (STR=5): 30 × 0.5 = +15 - Critical: None - Name Match: 30 × 2 = +60 (Prefix 2 match) 4. Total Attack = 30 + 15 + 60 = 105 5. Base Armor = 20 6. Final Damage = MAX(4, 105 - 20) = 85 HP! Note: If Prefix 1 matched instead, bonus would be 30 × 8 = +240! ```
### Combat Strategy Tips
#### Optimization Guidelines 1. **Prioritize Type Advantages** - +50% damage is a significant boost 2. **Stack STR for Consistent Damage** - Linear scaling with no cap 3. **LUCK for Burst Potential** - Critical hits can turn battles 4. **Match Tier to Level** - Higher tier = more base damage 5. **Name Matching Endgame** - Massive multipliers at high levels #### Common Pitfalls * Fighting with type disadvantage (-50% damage reduction) * Neglecting STR investment (missing easy damage boost) * Using low-tier weapons at high levels * Ignoring name match opportunities
### See Also * [Battle Guide](/lootsurvivor/guide/battle) - Combat interface and tactics * [Stats Guide](/lootsurvivor/stats) - Detailed stat explanations * [Loot System](/lootsurvivor/loot) - Equipment and tier information import { ContractTable } from "../../components/ContractTable"; ## Smart Contracts Loot Survivor is built on Starknet using the Dojo engine, with all game logic executed fully onchain. The game's smart contracts handle everything from adventurer creation to combat resolution, ensuring transparency and verifiability. ### Death Mountain Contracts The core game engine powering Loot Survivor. ### NFT & Token Contracts ### Governance Contracts ### Ecosystem Contracts ### Development Resources * [GitHub Repository](https://github.com/Provable-Games/death-mountain) - Source code and documentation * [Dojo Engine](https://dojoengine.org/) - Framework powering the game * [Starknet Documentation](https://docs.starknet.io/) - Layer 2 blockchain platform ### Contract Interaction Players interact with the smart contracts through: * **Game Interface**: Main game client at lootsurvivor.io * **Direct Calls**: Advanced users can call contract functions directly * **Block Explorers**: View transactions and state on Starkscan or Voyager All game actions are recorded onchain, providing complete transparency and allowing for independent verification of gameplay mechanics. ### Verification Status Several contracts have been verified on Voyager for enhanced transparency: * **SURVIVOR Token**: Verified ✓ * **Survivor Controller**: Verified ✓ * **Survivor Governor**: Verified ✓ ## Corpse Token ($CORPSE)
Corpse Token
### Overview $CORPSE is an ERC20 token on Starknet earned by extracting value from your fallen adventurers in Loot Survivor: Beast Mode, created alongside [Summit](/summit). When one of your adventurers dies, you can extract $CORPSE tokens proportional to their level. Currently, the only known use case for $CORPSE is increasing beast health in [Summit](/summit/beast-upgrades). #### At a Glance * **Standard**: ERC20 on Starknet * **Supply**: Uncapped — minted continuously as adventurers fall in Loot Survivor: Beast Mode * **Earning**: 1 $CORPSE per adventurer level (extracted from dead adventurers you own) * **Current Use**: Increasing beast health in [Summit](/summit/beast-upgrades) * **Contract Address**: TBA ### How to Earn $CORPSE $CORPSE tokens are earned exclusively through [Loot Survivor](/lootsurvivor) gameplay: * Play Loot Survivor: Beast Mode and level up your adventurer * When your adventurer dies, they become eligible for extraction * Extract $CORPSE through the Summit interface * You receive **1 $CORPSE per adventurer level** **Example:** A dead level 15 adventurer yields **15 $CORPSE**. A dead level 30 adventurer yields **30 $CORPSE**. The higher you level your adventurers before they fall, the more $CORPSE you can extract. This turns every loss in Loot Survivor into a resource for Summit. #### Retroactive Claims Adventurers that died before $CORPSE was introduced are still eligible. If you have dead adventurers from past Loot Survivor games, you can extract $CORPSE from them immediately — meaning you may already be sitting on a stockpile of claimable tokens. ### What $CORPSE Is Used For $CORPSE is currently used in [Summit](/summit) to permanently increase your beast's health. See the [Beast Upgrades](/summit/beast-upgrades) guide for full details on health enhancement and costs. ### Token Economics $CORPSE creates a new layer to the Loot Survivor ecosystem — even when your adventurers die, they contribute value: 1. **Play Loot Survivor: Beast Mode** → Adventurer dies → Extract $CORPSE based on level 2. **Spend $CORPSE in Summit** → Increase beast health → Hold the Summit longer 3. **Longer Summit holds** → More $SURVIVOR earned → Greater ecosystem participation Since $CORPSE has no supply cap, the token continues to be minted as long as adventurers are dying in Loot Survivor. Higher-level adventurers yield proportionally more tokens, rewarding skilled players who push deeper into the game before falling. ## Dungeon Tickets ### Overview
Dungeon tickets are your entry pass to [Beast Mode](/lootsurvivor/game-modes) in Loot Survivor. Each ticket grants access to the Dungeon where you can collect unique [Beast NFTs](/lootsurvivor/beasts) and earn [SURVIVOR tokens](/lootsurvivor/token) through gameplay.
Dungeon Ticket
### Dynamic Pricing System Loot Survivor uses an innovative dynamic pricing model for dungeon tickets: * **Continuous Emission**: Tickets are continuously emitted over time * **Real-Time Adjustment**: Price adjusts based on demand - can move up or down * **Transparent Pricing**: Live price is always visible before purchase * **Market-Driven**: Price discovery happens naturally through supply and demand #### Issuance Schedule The ticket issuance system is designed for long-term sustainability: * **Total Supply**: 100 million games over 132 years * **Base Rate**: \~2,000 games issued per day * **Duration**: Maximum issuance period of 132 years * **Distribution**: Equal rate emission ensures predictable supply #### Adaptive Issuance Mechanism The protocol includes an automatic adjustment mechanism to maintain market balance: * **Price Monitoring**: Contract continuously monitors ticket prices * **Trigger Threshold**: If price falls below $1 for 3 consecutive days * **Automatic Response**: Issuance rate halves to reduce supply pressure * **Market Protection**: Helps maintain sustainable pricing over the long term This adaptive system ensures that ticket supply responds to market conditions, protecting both players and the ecosystem from extreme price volatility while maintaining accessibility. #### Governance Control All key parameters of the dungeon ticket system are fully configurable by [SURVIVOR token](/lootsurvivor/token) holders through on-chain governance: * **Issuance Parameters**: Emission rate, halving triggers, and total supply * **Payment Tokens**: Which tokens are accepted for ticket purchases * **Price Thresholds**: Adjustment triggers and response mechanisms * **Distribution Ratios**: Revenue split between treasury and veLORDS This decentralized control ensures the community can adapt the system to changing market conditions and player needs over time. ### What You Get Each dungeon ticket provides: #### 🐉 Beast Collection * Access to collect unique [Beast NFTs](/lootsurvivor/beasts/collectibles) * 85% of the total Beast supply still available in the Dungeon * Each Beast is a unique, tradeable NFT #### 💰 SURVIVOR Tokens * Earn [SURVIVOR tokens](/lootsurvivor/token/tokenomics) based on your performance * Tokens = level reached (starting at level 3) * **Limited Time Boost**: 4x SURVIVOR rewards during weeks 2-4 #### 🎯 Wanted Beast Bounties * Hunt [special Beasts](/lootsurvivor/beasts/wanted-beasts) with 100,000 STRK bounty (\~$15,000) * Split equally between three legendary Beasts * First adventurer to defeat each Beast claims their bounty ### How to Purchase Dungeon tickets can be purchased with **any token** on Starknet - ETH, USDC, STRK, LORDS, and more. The system automatically handles the conversion, making it convenient regardless of what tokens you hold.
Ticket Purchase Interface

Purchase tickets with any token - the interface shows live pricing

#### Option 1: Direct Purchase Purchase through the [Ekubo Protocol](https://app.ekubo.org) AMM at the live market price. This is the most direct method for experienced DeFi users. Select your preferred token and the system handles the swap automatically. #### Option 2: Simple Checkout Use [Cartridge](https://cartridge.gg) for easy entry with card/fiat payment. Perfect for newcomers to Web3 gaming. Credit card payments are converted seamlessly. #### Pro Strategy: Dollar-Cost Averaging The most efficient purchasing method is placing a DCA (Dollar-Cost Averaging) order through Ekubo: * **Spread purchases over time** to smooth out price fluctuations * **Often achieves better average price** than buying all at once * **Automated execution** reduces manual effort * Learn more: [Ekubo DCA Guide](https://docs.ekubo.org/user-guides/dollar-cost-average-orders)
Note on Token Flow: When you purchase a ticket with any token, the protocol automatically converts it to LORDS tokens. These LORDS are then distributed: 20% to veLORDS holders and 80% used for SURVIVOR token buybacks, supporting both the Bibliotheca ecosystem and the Survivor treasury.
### Revenue Distribution All ticket proceeds are distributed transparently on-chain: #### 80% → Survivor Treasury * Automated [SURVIVOR token](/lootsurvivor/token) buybacks via TWAMM * DAO-controlled treasury for ecosystem growth * Governed by SURVIVOR token holders * Pre-seeded at genesis for sustainability #### 20% → veLORDS Holders * Distributed to [veLORDS](https://realms.world) stakers * Rewards the [Bibliotheca DAO](https://bibliothecadao.xyz) ecosystem * Recognizes Bibliotheca as the original incubator ### Key Benefits

🎮 Beast Collection

Enter the dungeon and battle to collect unique Beast NFTs

💎 NFT Collection

Every [Beast](/lootsurvivor/beasts) is a unique, tradeable NFT with varying rarity

📈 Token Rewards

Earn [SURVIVOR governance tokens](/lootsurvivor/token) based on your skill

🏆 Bounty Hunting

Chase [special Beasts](/lootsurvivor/beasts/wanted-beasts) for substantial STRK rewards

### Frequently Asked Questions #### When should I buy tickets? With dynamic pricing, tickets can be cheaper during low-demand periods. Consider using DCA orders to spread your purchases over time. #### How many games can I play with one ticket? One ticket grants you one entry into the dungeon for a Beast collection attempt. Each dungeon run requires a new ticket. #### Can ticket prices go down? Yes! Unlike fixed-price models, our dynamic system allows prices to decrease when demand is low, creating opportunities for strategic buyers. #### What happens to my ticket after use? Each ticket is consumed when you enter the dungeon, regardless of the outcome. You'll need a new ticket for each dungeon run. ### Get Started Ready to enter the Dungeon? Purchase your ticket at [lootsurvivor.io](https://lootsurvivor.io) and start your Beast hunting adventure today! ## Frequently Asked Questions ### General **What is Loot Survivor?** Loot Survivor is a fully onchain dungeon crawler RPG built on Starknet. Every action, from combat to loot generation, happens directly on the blockchain, making the game transparent, verifiable, and permanent. **Is Loot Survivor free to play?** Yes, Loot Survivor has a completely free game mode for players to practice their skills. For the full dungeon experience, a ticket is required which can be purchased directly through the game. No gas required. **What do I need to play?** * **Free Version:** No wallet, gas, or ticket required — just a modern browser (Chrome, Firefox, Safari, Edge). * **Full Version:** A Cartridge Controller wallet (for VRF support) and a dungeon ticket purchased through the game. No gas fees. **Is the game mobile-friendly?** Yes. Loot Survivor has a fully responsive design with a dedicated mobile interface optimized for touch controls. ### Gameplay **How do I start playing?** Choose your game mode and click "New Game" to start exploring. See the [Getting Started Guide](/lootsurvivor/guide/getting-started) for detailed instructions. **What happens when my adventurer dies?** Death is permanent — your adventurer's journey ends but earned assets remain onchain forever. Start a new adventure anytime. **Can I have multiple adventurers?** Yes. Each adventurer is a separate onchain token with unique progression. **How does combat work?** Turn-based combat with rock-paper-scissors type advantages (plus or minus 50% damage). Each weapon/armor type beats one and loses to another. See: [Battle Guide](/lootsurvivor/guide/battle) and [Combat Mechanics](/lootsurvivor/combat). **What are the different stats?** Seven core stats: STR (damage), DEX (flee), VIT (HP), INT (obstacles), WIS (ambush), CHA (prices), LUCK (crits). Each affects combat and exploration differently. Full details: [Stats Guide](/lootsurvivor/stats). **How do items work?** 5 tiers (T5 common to T1 legendary). Items level up and unlock suffixes (Lv15) and prefixes (Lv19+) for bonuses. See: [Loot Guide](/lootsurvivor/loot). **What's the best strategy?** No single best strategy. Focus on type advantages, stat balance, tactical decisions, and resource management. See: [Battle Guide](/lootsurvivor/guide/battle). ### Technical **What blockchain is Loot Survivor on?** Loot Survivor runs on Starknet, a Layer 2 scaling solution for Ethereum. Benefits include low fees (under $0.01 per transaction), fast confirmations (1-3 seconds), and Ethereum-level security. **Are the game outcomes truly random?** Uses VRF (Verifiable Random Function) seeds by default for true randomness. Can be configured to use deterministic seeds in settings. **Can I verify the game logic?** Yes. All contracts are open source and verified on Starknet. You can inspect the code on [GitHub](https://github.com/Provable-Games/death-mountain) and verify any game action onchain. **How are gas fees kept low?** Optimizations include packed storage, batch operations, and efficient algorithms, plus Starknet's L2 scaling benefits. ### Economic **How does the market work?** The in-game market refreshes when you level up, uses VRF seeds for item generation, and prices decrease with Charisma. No real money transactions between players. **Can I trade items with other players?** Currently, direct player-to-player trading is not implemented. All trading happens through the in-game market system. **Are there rewards for playing?** Yes. When you kill beasts during gameplay, you collect them and earn $SURVIVOR tokens on collection. [Budokan](/budokan) tournaments with prize pools are another way to earn rewards. Check [Discord](https://discord.gg/Q36rUxS66c) for current tournament info and schedules. ### Wallet & Account **Which wallets are supported?** For VRF ([Budokan](/budokan) tournament) mode, Cartridge Controller is required. For standard mode, any Starknet-compatible wallet works (Argent X, Braavos, etc.). **I lost access to my wallet. Can I recover my adventurer?** If you lose wallet access, you lose access to your adventurers. Always keep your recovery phrase secure and backed up. **Can I play on multiple devices?** Yes. Your adventurers are tied to your wallet address, not your device. Play from any device where you can access your wallet. **Why do transactions sometimes fail?** Common reasons include network congestion and wallet not being properly connected. ### Advanced **Can I build on top of Loot Survivor?** Yes. Loot Survivor is open source and designed to be composable. You can build custom frontends, create analytical tools, extend game mechanics, and integrate with other protocols. **How do I integrate Loot Survivor into my app?** See our [Integration Guide](/lootsurvivor/technical/integration) for detailed instructions on building with Loot Survivor's contracts. **What's the maximum level?** There is no hard level cap. The theoretical maximum depends on your ability to survive increasingly difficult challenges. **How are beasts generated?** Beasts are generated from VRF seeds based on your adventurer's level, a random seed value, and tier distribution tables. See: [Combat Mechanics](/lootsurvivor/combat). **Can I predict market items?** No. Market items use VRF seeds for true randomness (unless configured otherwise in settings). ### Troubleshooting **The game won't load** Try these steps: clear browser cache (Ctrl+Shift+Del), disable ad blockers (most common cause of wallet connection issues), try Chrome or Firefox, check [status.starknet.io](https://status.starknet.io), and ensure JavaScript is enabled. **Transactions are stuck** Wait for network congestion to clear, check block explorer, refresh page, or reconnect wallet. More help: [Getting Started Guide](/lootsurvivor/guide/getting-started). **I can't connect my wallet** Ensure correct controller login details, check network, refresh page, or try a different browser. Setup help: [Getting Started Guide](/lootsurvivor/guide/getting-started). **The game shows incorrect data** This is a sync issue. Refresh the page, clear cache, reconnect wallet, or wait for the indexer. ### Community **How can I contribute?** Report bugs on [GitHub](https://github.com/Provable-Games/death-mountain/issues), suggest features in Discord, build tools and integrations, or create content and guides. **Where can I get help?** * [Discord Community](https://discord.gg/Q36rUxS66c) * [Twitter](https://twitter.com/lootsurvivor) * [GitHub Discussions](https://github.com/Provable-Games/death-mountain/discussions) **Are there tournaments?** Yes. Regular tournaments with prize pools are held on [Budokan](/budokan), the permissionless tournament platform. Check Discord for announcements and schedules. ### Legal & Safety **Is Loot Survivor audited?** The smart contracts have not undergone an official audit yet. As with all blockchain applications, use at your own risk. **Is this gambling?** Loot Survivor is a skill-based game with random elements. The outcome depends on player decisions and strategy, not purely chance. **What data is collected?** Only onchain data (transactions, game actions) is recorded. No personal information is collected or stored off-chain. **Can I lose money playing?** You pay entry fees for certain game modes. Entry fees can be paid in any supported token. Never spend more than you can afford to lose. ### Getting More Help If your question isn't answered here: 1. Check the [comprehensive guides](/lootsurvivor/guide) 2. Read the [technical documentation](/lootsurvivor/technical/architecture) 3. Ask in [Discord](https://discord.gg/Q36rUxS66c) 4. Open a [GitHub issue](https://github.com/Provable-Games/death-mountain/issues) ## Golden Token V2
Golden Token V2
### Overview Golden Token V2 is the successor to the original Golden Token NFT collection on Starknet, modernized for full ecosystem compatibility and built entirely on-chain. V2 preserves the provenance of the original via a fully onchain airdrop while upgrading to modern standards and tooling. V2 expands the collection by airdropping seven new ERC721 tokens to each of the 160 V1 holders, resulting in a total supply of 1,120. Each V2 token grants its holder one free Loot Survivor game per week, forever. #### At a Glance * **Supply**: 1,120 tokens (160 holders × 7 each) * **Distribution**: Airdropped in seven rounds to current V1 holders at execution time * **Compatibility**: OpenZeppelin ERC721 + ERC2981 on Cairo 2.11.4 * **Governance**: ERC721Votes for onchain voting and delegation * **Metadata**: Fully on-chain JSON + SVG (no base URI, data URI returned) * **Art**: Animated SVG instead of static PNG V2 addresses early V1 limitations (pre-standard ERC721) by adopting modern standards and audited components, ensuring wallet/marketplace support, enforceable royalties, and native governance—while keeping the experience faithful to the original Golden Token vision. ### The Origin Story The original Golden Tokens were launched on October 31st, 2023, alongside the inaugural release of Loot Survivor. In a true fair-launch fashion, Golden Tokens were made available through a three-week open edition mint with: * ✅ No allowlist * ✅ No mint limits * ✅ No team allocation * 💰 Cost: 75 games' worth of Loot Survivor V1 Golden Tokens provided a simple utility: one free game of Loot Survivor per day. V2 Golden Tokens now provide one free game of Loot Survivor per week. ### V2: Technical Evolution & Enhanced Ecosystem The original Golden Tokens were pioneering NFTs on Starknet, created before formal ERC721 specifications were available. This early adoption meant inconsistent compatibility - not all wallets, block explorers, and marketplaces properly support V1 tokens. Golden Token V2 addresses these limitations while expanding the ecosystem: #### Technical Improvements * **Full ERC721 Compliance**: Built with OpenZeppelin 2.0.0 specifications for universal compatibility * **Modern Cairo**: Upgraded to Cairo 2.11.4 from 2.1.0 Cairo contracts * **Ecosystem Compatibility**: Full support across all Starknet wallets, explorers, and marketplaces * **Onchain Governance**: Integrated ERC721Votes enables holders to formally express views through onchain voting * **Enhanced Security**: Latest audited OpenZeppelin components with comprehensive security patterns #### Ecosystem Enhancements * **Increase Optionality**: Weekly games instead of daily, giving holders more flexibility * **Expand Liquidity**: 7x supply increase (160 → 1,120 tokens) enables more trading and market activity * **Preserve Legacy**: Original holders receive the entire V2 supply via airdrop * **Maintain Exclusivity**: No public mint - only original holders can receive V2 tokens ### Key Differences from V1 | Feature | V1 | V2 | | -------------- | --------------------- | ---------------------- | | Cairo Version | Cairo 2.1.0 | Cairo 2.11.4 | | ERC721 Spec | OpenZeppelin 0.7.0 | OpenZeppelin 2.0.0 | | Wallet Support | Limited compatibility | Universal support | | Voting | N/A | ERC721Votes integrated | | Total Supply | 160 tokens | 1,120 tokens | | Distribution | Open edition mint | Airdrop to V1 holders | | Game Frequency | 1 free game/day | 1 free game/week | | Launch Date | October 31, 2023 | September 5th, 2025 | ### Features * 🎮 **Weekly Gaming Rights**: Each token grants one free Loot Survivor game per week in perpetuity * 🪂 **Fair Airdrop System**: 7 rounds of airdrops, each distributing 160 tokens to original holders * 🏛️ **Legacy Preservation**: Rewards early supporters with expanded holdings * 🗳️ **Onchain Governance**: ERC721Votes enables formal onchain voting for ecosystem decisions * 🔧 **Full Compatibility**: Works seamlessly with all Starknet wallets, explorers, and marketplaces * 💰 **Creator Royalties**: 5% royalty on secondary sales via ERC2981 standard * 🎨 **Fully Onchain**: All metadata and SVG artwork stored and generated onchain * ⛓️ **Modern Standards**: Built with latest Cairo 2.11.4 and OpenZeppelin 2.0.0 ### Technical Architecture #### Smart Contract Components The contract leverages battle-tested OpenZeppelin Cairo components: * **ERC721Component**: Core NFT functionality with metadata extension * **OwnableComponent**: Administrative access control * **ERC2981Component**: NFT royalty standard implementation (5% on secondary sales) * **VotesComponent**: Democratic governance through NFT-based voting * **SRC5Component**: Interface detection for contract introspection * **NoncesComponent**: Signature replay protection #### Airdrop Mechanism The V2 distribution occurs through a systematic airdrop process: * **7 Rounds Total**: Each round airdrops 160 new tokens to match the original collection size * **1:7 Ratio**: Each original Golden Token holder receives 7 V2 Golden Tokens * **Owner Verification**: Tokens are airdropped to current holders at the time of execution. No snapshots, no merkle trees, no manual claims required import { ContractTable } from "../../components/ContractTable"; import { Button } from "vocs/components"; ![Overview](docs/ls-banner-docs.png) ## Loot Survivor Loot Survivor is a fully onchain dungeon crawler RPG built on Starknet using the Dojo engine. Navigate through procedurally generated dungeons, battle fierce beasts, overcome deadly obstacles, and collect legendary loot in your quest for survival and glory. The items in the game are dervied from the Loot project. ### Overview Death Mountain powers Loot Survivor - a token-agnostic, no-code onchain dungeon creator that provides a complete RPG experience. Every action, from combat to loot generation, is verifiable and deterministic on the blockchain. #### Key Features

🗡️ Deep RPG Mechanics

7 core stats, 8 equipment slots, and level-based progression

👾 75 Unique Beasts

From lowly gnomes to mighty dragons, each with distinct behaviors

⚡ 75 Deadly Obstacles

Magical, sharp, and crushing hazards to overcome

💎 101 Unique Items

Tier-based loot system with evolving item powers

🏪 Dynamic Market

Charisma-based pricing and level rotating shop items

🎲 Verifiable Randomness

VRF integration for true randomness

⛓️ Fully Onchain

All game logic executed on Starknet

### Game Systems #### Adventurer System Create and develop your character with comprehensive RPG mechanics: * **Stats**: Strength, Dexterity, Vitality, Intelligence, Wisdom, Charisma, Luck * **Equipment**: Weapon, Chest, Head, Waist, Foot, Hand, Neck, Ring slots * **Progression**: XP-based leveling with stat point allocation * **Health**: Starting HP of 100, scalable with Vitality and replenished with potions. #### Combat System Engage in strategic turn-based combat against beasts: * **Combat Types**: Weapon triangle system (Magical > Brute > Hunter > Magical) * **Critical Hits**: Luck based strikes, grows with better Jewelry * **Flee Mechanics**: Dexterity-based escape chances #### Exploration Navigate through dangerous dungeons: * **Beasts**: Fight 75 different beast types with scaling difficulty * **Obstacles**: 75 unique environmental hazards * **Loot**: Find items, gold, and XP through exploration * **Strategic Choices**: Decide when to fight, flee, or explore #### Item System Collect and upgrade powerful equipment: * **Tier System**: T1 (Legendary) - T5 (Common) items with inverse pricing * **Item Evolution**: Items gain "greatness" unlocking suffixes and prefixes * **Special Powers**: 16 unique suffixes providing stat bonuses * **Type Advantages**: Strategic equipment choices based on enemy types ### Documentation #### 🎮 Getting Started * [Game Modes](/lootsurvivor/game-modes) - Free vs. Full game modes * [Getting Started Guide](/lootsurvivor/guide/getting-started) - Your first adventure * [Dungeon Tickets](/lootsurvivor/dungeon-tickets) - Dynamic pricing and Beast Mode access * [Golden Token](/lootsurvivor/golden-token) - Premium NFT collection for unlimited weekly gameplay * [Settings](/lootsurvivor/settings) - Game configuration and VRF options * [FAQ](/lootsurvivor/faq) - Frequently asked questions #### ⚔️ Game Mechanics * [Stats System](/lootsurvivor/stats) - Character attributes and progression * [Combat Mechanics](/lootsurvivor/combat) - Damage calculations and formulas * [Battle Guide](/lootsurvivor/guide/battle) - Combat tactics and interface * [Exploration](/lootsurvivor/guide/explore) - Discovery and obstacles * [Character Upgrade](/lootsurvivor/guide/upgrade) - Leveling and stat allocation #### 🎒 Items & Creatures * [Loot System](/lootsurvivor/loot) - Equipment, tiers, and bonuses * [Jewelry](/lootsurvivor/loot/jewelry) - Rings, amulets, and accessories * [Suffix Boosts](/lootsurvivor/loot/suffix-boost) - Item enhancement system * [Prefixes](/lootsurvivor/loot/prefixes) - Complete prefix and suffix reference * [Beasts](/lootsurvivor/beasts) - Combat types, tiers, and strategies * [Beast Collectibles](/lootsurvivor/beasts/collectibles) - Achievement system * [Wanted Beasts](/lootsurvivor/beasts/wanted-beasts) - Special bounties with STRK rewards ### Technical Resources #### 🔧 Development & Contracts * [Smart Contracts](/lootsurvivor/contracts) - Contract addresses and technical details * [GitHub Repository](https://github.com/Provable-Games/death-mountain) - Source code and development * [Dojo Engine](https://dojoengine.org/) - Framework powering the game #### 🌐 Community * [Discord Community](https://discord.gg/Q36rUxS66c) - Join the community * [Twitter](https://twitter.com/lootsurvivor) - Latest updates and news ## Market System The market is your lifeline in Loot Survivor, offering equipment upgrades and life-saving potions. Understanding pricing mechanics and market generation helps you make strategic purchasing decisions that can mean the difference between victory and defeat. > **🛍️ Market Philosophy:** Every gold piece spent should advance your survival strategy! ### Market Overview

🏪 Market Mechanics

  • Duration: Same market persists for your entire level
  • Refresh: New randomly generated market on next level up
  • Purchases: Buy as many items as you can afford
  • Inventory: Limited to 15 bag slots + 8 equipped items
*** ## Item Pricing ### Base Pricing System

💰 Equipment Pricing Formula

Base Price = Item Tier × 4
Tier Base Price With 10 CHA With 20 CHA
T5 (Common) 20 gold 10 gold 1 gold (min)
T4 16 gold 6 gold 1 gold (min)
T3 12 gold 2 gold 1 gold (min)
T2 8 gold 1 gold (min) 1 gold (min)
T1 (Legendary) 4 gold 1 gold (min) 1 gold (min)
Charisma Discount: -1 gold per CHA point (minimum 1 gold)
💡 Important: All purchased items start at Greatness 1. A T1 item may initially be weaker than your leveled T5 equipment - you need to gain experience and level the item to unlock its full potential!
*** ## Potion System ### Health Potions

🧪 Potion Mechanics

Health Restored: +10 HP per potion
Purchase Limit: Unlimited (buy as many as you can afford)
Price Formula:
Base Price = 1 gold × Adventurer Level
CHA Discount = 2 gold per CHA point
Final Price = Base - Discount (minimum 1 gold)
#### Potion Pricing Table | Level | Base Price | 1 CHA | 5 CHA | 10 CHA | 15 CHA | | ----- | ---------- | ----- | ----- | ------ | ------ | | 1 | 1g | 1g | 1g | 1g | 1g | | 5 | 5g | 3g | 1g | 1g | 1g | | 10 | 10g | 8g | 1g | 1g | 1g | | 20 | 20g | 18g | 10g | 1g | 1g | | 50 | 50g | 48g | 40g | 30g | 20g | > **💡 Strategy:** High CHA makes potions extremely cheap, allowing bulk healing purchases! *** ## Market Generation

🎲 How Markets Generate

Each level generates a unique market through RNG:

  1. Game rolls for 25 random items
  2. Duplicate items are removed (only one instance appears)
  3. Market size varies from 1-25 items
  4. Small markets = many duplicate rolls occurred
  5. Market remains constant until next level
#### Market Size Probabilities * **20-25 items:** Common (most markets) * **15-19 items:** Occasional * **10-14 items:** Uncommon * **5-9 items:** Rare * **1-4 items:** Very rare (extreme bad luck) *** ## Market Tips & Tricks

🎯 Pro Market Strategies

  • Check Everything: Scroll through entire market - gems might be at the bottom
  • Type Coverage: Prioritize items that give you type advantages
  • Empty Slots: Any armor is better than no armor
### Common Mistakes to Avoid * **Overspending Early:** Don't buy everything just because you can * **Ignoring CHA:** Even 3 CHA dramatically reduces T5 prices * **Potion Neglect:** Always keep HP topped up between fights *** ## Summary The market system rewards strategic planning and smart stat allocation. High Charisma turns the market into your personal armory, while understanding pricing helps you maximize every gold piece. Remember: the best deal is the item that keeps you alive! **Key Market Principles:** * Markets refresh on level up only * CHA dramatically reduces all prices * Potions scale with level but CHA discount is powerful * Small markets mean bad RNG - work with what you get * Every purchase should advance your survival strategy ## Platform Comparison Loot Survivor delivers a consistent, high-quality gaming experience across all platforms. Whether you're at your desk or on the move, enjoy the same depth of gameplay with interfaces optimized for each device type. ### Desktop vs Mobile Experience

🎮 Same Core Experience

Both platforms offer identical gameplay mechanics, features, and progression systems. The differences lie in how the interface is presented and optimized for each device type.

  • ✅ Same game mechanics and rules
  • ✅ Identical stats, combat, and progression
  • ✅ Full market and inventory systems
  • ✅ Complete blockchain integration
  • ✅ Cross-platform wallet compatibility
*** ### Desktop Interface
#### Desktop Advantages

🖥️ Full High-Resolution AI Visuals & Animations

Experience cutting-edge AI-powered graphics with detailed animations, rich visual effects, and immersive high-resolution artwork. Perfect for deep exploration sessions where visual fidelity enhances the adventure.

*** ### Mobile Interface
#### Mobile Advantages

📱 Mobile-Friendly UI Where Speed is Priority

Streamlined pixel-art interface optimized for lightning-fast gameplay. Touch controls, instant responses, and efficient layouts ensure quick decision-making perfect for on-the-go adventures and rapid combat encounters.

*** ### Choose Your Adventure Both platforms offer the complete Loot Survivor experience with their own unique advantages. Whether you prefer the immersive desktop experience or the convenience of mobile gaming, your adventure awaits!

Ready to Start Playing?

Visit lootsurvivor.io on any device and begin your adventure into Death Mountain!

📖 Back to Getting Started Guide →
## Skull Token ($SKULL)
Skull Token
### Overview $SKULL is an ERC20 token on Starknet that represents beast kills in Loot Survivor, created alongside [Summit](/summit). Every time one of your beasts kills an adventurer, you can claim 1 $SKULL. Currently, the only known use case for $SKULL is upgrading beasts in [Summit](/summit/beast-upgrades). #### At a Glance * **Standard**: ERC20 on Starknet * **Supply**: Uncapped — minted continuously as adventurers are killed in Loot Survivor * **Earning**: 1 $SKULL per adventurer killed by your beast * **Current Use**: Beast upgrades in [Summit](/summit/beast-upgrades) * **Contract Address**: TBA ### How to Earn $SKULL $SKULL tokens are earned exclusively through [Loot Survivor](/lootsurvivor) gameplay: * Own a beast NFT * When an adventurer encounters your beast and is killed, you can claim **1 $SKULL** * Tokens accumulate across all your beasts — every kill counts The more beasts you own and the stronger they are (tier 1 beasts kill most adventurers), the more adventurers they'll defeat and the more $SKULL you'll accumulate. #### Retroactive Claims Beasts that haven't been minted yet may have already slain adventurers in Loot Survivor. Once you mint a beast, you can claim $SKULL for all of its past kills — meaning a newly minted beast could come with multiple $SKULL ready to claim. ### What $SKULL Is Used For $SKULL is currently used in [Summit](/summit) to upgrade your beasts with attributes and abilities. See the [Beast Upgrades](/summit/beast-upgrades) guide for full details on available upgrades and costs. ### Token Economics $SKULL creates a natural connection between Loot Survivor and Summit: 1. **Collect beasts** → Own beast NFTs through Loot Survivor gameplay or the [marketplace](https://beast-dex.vercel.app/marketplace) 2. **Beasts kill adventurers** → Earn $SKULL from kills in Loot Survivor 3. **Spend $SKULL in Summit** → Upgrade beasts → Compete for $SURVIVOR rewards 4. **Stronger beasts in Summit** → More $SURVIVOR earned → Greater ecosystem participation Since $SKULL has no supply cap, the token continues to be minted as long as Loot Survivor is played. This means the upgrade economy grows alongside the player base — more players means more kills, more $SKULL, and more competition in Summit. ## Stats Understanding and optimizing your adventurer's stats is crucial for survival in Loot Survivor. Each of the seven core stats plays a vital role in different aspects of gameplay, from combat effectiveness to market efficiency. Master stat allocation to create powerful builds tailored to your playstyle! ### Stats Overview
Every adventurer has **7 core stats** that define their capabilities:
* **Physical Stats**: Strength, Dexterity, Vitality * **Mental Stats**: Intelligence, Wisdom, Charisma * **Metaphysical**: Luck (special stat with unique mechanics)
Each Adventurer starts with 12 random stat points.
> **📈 Progression Note:** You gain 1 stat point per level to allocate freely, except for Luck which can only be increased through items!
### The Seven Core Stats #### Physical Stats ##### 💪 Strength (STR) * **Effect:** +10% attack damage per point * **Formula:** `Damage = Base × (1 + STR/10)` * Each point increases damage output by 10% with linear scaling and no cap * Works with all weapon types * **⚔️ Tip:** 10 STR = double damage! ##### 🏃 Dexterity (DEX) * **Effect:** Improves flee success rate * **Formula:** `Success% = (DEX / Level) × 100` * Flee success depends on DEX/Level ratio * Keep DEX equal to level for guaranteed escapes * **🏃 Tip:** DEX = Level = 100% escape! ##### ❤️ Vitality (VIT) * **Effect:** +15 HP per point (instant heal) * **Formula:** `Max HP = 100 + (VIT × 15)` * Increases maximum and current health * Base health is 100 HP * **Maximum health cap: 1023 HP** * **🛡️ Tip:** VIT upgrades heal instantly! #### Mental Stats ##### 🧠 Intelligence (INT) * **Effect:** Avoids obstacle damage * **Formula:** `Avoidance% = (INT / Level) × 100` * Obstacle avoidance based on INT/Level ratio * Keep INT equal to level for full immunity * **🧠 Tip:** INT = Level = No obstacle damage! ##### 📚 Wisdom (WIS) * **Effect:** Prevents ambush penalties * **Formula:** `Evasion% = (WIS / Level) × 100` * Avoids surprise attack penalties * Also improves exploration rewards * **👁️ Tip:** Avoids penalties, not the fight! ##### 😎 Charisma (CHA) * **Effect:** Market discounts * **Prices:** -1 gold/point for items, -2 gold/point for potions (min 1 gold) * **Example:** 10 CHA = 10g off items, 20g off potions * Makes late-game shopping nearly free * **💰 Tip:** High CHA = nearly free shopping! #### Metaphysical Stat ##### 🍀 Luck (LUCK) * **The Special Stat** - Cannot be upgraded with points, only through equipment! * **Effect:** Critical hit chance & better loot * **Formula:** `Crit Chance = LUCK%` (caps at 100) * **Critical Damage:** 2× base damage when triggered * Determines critical hit probability and improves item discovery * Get LUCK from jewelry items * **🎰 Tip:** Stack jewelry for guaranteed crits!
⚠️ Warning: Dropping jewelry will remove LUCK stats equal to the greatness level of the item dropped!
*** ### Suffix Boosts & Item Management

🏆 Enhanced Items

Items with Greatness 15+ gain powerful suffix boosts that provide additional stat bonuses beyond base equipment stats. See the Suffix Boost Guide for complete details on all available bonuses.

⚠️ Unequipping Items with Suffix Boosts

When you unequip items with suffix boosts, most stat bonuses are immediately removed from your adventurer. However, there are two important exceptions:

  • Vitality (VIT): Remains active even when the item is in your bag
  • Charisma (CHA): Remains active even when the item is in your bag
### Conclusion Stats are the foundation of every successful adventurer in Loot Survivor. Whether you choose to be an unstoppable tank, a glass cannon dealing massive damage, or a nimble escape artist, understanding stat mechanics is key to survival. Remember: there's no single "best" build - the optimal stat distribution depends on your playstyle, the items you find, and your ability to adapt to the dungeon's challenges. > **🏆 Final Thought:** The best stat allocation is one that keeps you alive and progressing. When in doubt, add VIT! ## Free Games Airdrop ### Overview The Loot Survivor airdrop provides **free game entries** to eligible NFT holders and community members. These free games give players access to the LS2 Dungeon, which contains **2,258,100 SURVIVOR tokens** distributed as rewards based on gameplay performance. ### How It Works #### Game-Based Distribution Instead of receiving SURVIVOR tokens directly, eligible participants receive free game entries to play Loot Survivor. The LS2 Dungeon holds 2,258,100 SURVIVOR tokens that are distributed to players based on their performance: * **Minimum Level 3**: Must reach at least level 3 to earn tokens * **Tokens = Level Reached**: Starting at level 3, tokens equal to the level reached * **Promotion Periods**: 4X tokens during weeks 2-4, 2X tokens after week 4 * **Performance-based rewards**: Higher levels mean more tokens * **Multiple attempts**: Each free game entry is a new opportunity to earn tokens #### Example Rewards **Standard Rate (Week 1):** * Level 3: 3 tokens * Level 10: 10 tokens * Level 25: 25 tokens **4X Promotion (Weeks 2-4):** * Level 10: 40 tokens * Level 25: 100 tokens **2X Promotion (After Week 4):** * Level 10: 20 tokens * Level 25: 50 tokens ### Eligibility Categories #### NFT Collection Holders Holders of eligible NFT collections receive free game entries based on their holdings at the snapshot date (August 18th, 2025 at 00:00 UTC). The number of free games is determined by: * Collection * Number of NFTs held * Games Amount multiplier for each collection [View all eligible collections →](/lootsurvivor/token/eligible-collections) #### Community Contributors Active community members earn free games through: * Early participation in Loot Survivor v1 * Community building and content creation * Bug reporting and testing * Protocol development contributions ### Claiming Your Free Games To claim your free game entries and start earning SURVIVOR tokens: #### Visit the Claims Portal Navigate to [claims.lootsurvivor.io](https://claims.lootsurvivor.io) to begin the claim process. ![Eligibility Checker Interface](/docs/lootsurvivor/eligibility-checker.png) *Enter your wallet address to check your free games allocation* #### What to Expect 1. **Enter Your Wallet Address** - Input your EVM or Starknet wallet address 2. **View Your Allocation** - See your total free games based on NFT holdings 3. **Connect/Create Controller** - Set up a controller for smooth claims process (can claim for multiple wallets) 4. **Claim Your Games** - Complete the claim for all eligible free games 5. **Start Playing** - Begin your adventure and earn SURVIVOR tokens (within 2 weeks) ![Eligibility Results](/docs/lootsurvivor/eligibility-results.png) *View your allocation and claim your free games* Controller Wallets *Connect multiple wallets to claim games from different eligible addresses* #### Wallet Ownership Verification For wallets that are not controllers (such as hardware wallets or EOA wallets): 1. **Sign a Message** - You'll be prompted to sign a message to prove ownership of your wallet 2. **Verification** - The signature verifies you own the NFTs from the snapshot 3. **Games Sent to Controller** - Once verified, your free games are sent to your controller wallet 4. **Play on Controller** - Use your controller wallet to play and earn SURVIVOR tokens ![Wallet Verification](/docs/lootsurvivor/argent-claim.png) *Sign a message to verify ownership and claim games to your controller* The claims portal automatically: * Verifies your eligibility based on the August 18th, 2025 snapshot * Calculates your total free games across all eligible collections * Handles the entire claim process securely * Provides access to start playing immediately ### Free Games Allocation #### Calculation Method Free games are allocated based on: * **NFT Holdings**: Each eligible NFT grants games based on its collection's multiplier * **Games Amount**: Collections have different game allocations * **Total Cap**: Maximum 50 free games per eligible address #### Distribution Examples * **Loot Holder**: 5 free games per Loot NFT (capped at 50 total) * **Pudgy Penguin Holder**: 10 free games per Penguin (capped at 50 total) * **OGS Discord Role**: 10 free games for verified members * **Multiple Collections**: Games stack across all eligible holdings (up to 50 max) ### Playing Your Free Games #### Getting Started 1. Claim your free games at [claims.lootsurvivor.io](https://claims.lootsurvivor.io) 2. Access Loot Survivor gameplay interface 3. Start a new game using a free entry 4. Progress through levels to earn SURVIVOR tokens #### Earning Tokens * **Level 1-2**: No token rewards * **Level 3+**: Earn tokens equal to the level reached (e.g., Level 3 = 3 tokens, Level 20 = 20 tokens) * **Death**: Game ends, tokens earned are credited based on highest level reached * **Strategy**: Plan your builds to maximize level progression for more tokens #### Token Distribution * When a game ends, a claim button appears * Click the claim button to receive your earned tokens * Track your total earnings across all games played ### Important Notes #### 2-Week Expiration Period * **Free games expire 2 weeks after the claim window opens** * Plan your gameplay strategy within this timeframe * Expired games cannot be recovered or extended * All eligible addresses have the same expiration deadline #### Gas Fees * **All gas fees are covered** through our paymaster * No ETH required for claiming free games * No transaction fees for playing games * Completely free experience from claim to gameplay #### Fair Play * Protected by onchain VRF (Verifiable Random Function) * Anti-bot measures built into the smart contract * Fully transparent and verifiable gameplay ### Strategy Tips #### Maximize Your Earnings * Study optimal builds before playing * Join community discussions for strategies * Use your free games before the 2-week window expires #### Resource Management * Free games are valuable opportunities * Each game is a chance at significant token earnings * Token rewards scale directly with level reached * Community guides available for gameplay optimization ### Support For assistance with free games: * Visit the official documentation * Join Discord for community strategies * Check FAQs for common questions * Submit support tickets for technical issues ### Disclaimer Free game entries are distributed to reward early supporters and community members. The 2,258,100 SURVIVOR tokens in the LS2 Dungeon are distributed solely based on gameplay performance. Token earnings depend on individual skill and game progression. ## Eligible Collections for Free Games The following NFT collections are eligible for **free game entries** to Loot Survivor, where players can earn SURVIVOR tokens through gameplay. ### Free Games Distribution Eligible NFT holders receive free game entries based on their collection holdings at the snapshot date. These free games provide access to the LS2 Dungeon containing 2,000,000 SURVIVOR tokens. Players earn tokens equal to the level they reach (minimum level 3 required). ### Eligible Collections List | Collection | Network | Collection Size | Free Games per NFT | View Contract | | ----------------------- | -------- | --------------- | ------------------ | ------------------------------------------------------------------------------------------------------------- | | 1337 Skulls | Ethereum | 7,331 | 5 | [Etherscan](https://etherscan.io/address/0x9251dec8df720c2adf3b6f46d968107cbbadf4d4) | | Loot | Ethereum | 7,779 | 5 | [Etherscan](https://etherscan.io/address/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7) | | Loot Explorers | Ethereum | 7,998 | 2 | [Etherscan](https://etherscan.io/address/0x508d06b8f3a4b0fd363239ce61e0c4b0b82f3626) | | The Wild Bunch | Ethereum | 3,997 | 2 | [Etherscan](https://etherscan.io/address/0xe9a1a323b4c8fd5ce6842edaa0cd8af943cbdf22) | | BAYC | Ethereum | 9,998 | 5 | [Etherscan](https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d) | | Dizzy Dragons | Ethereum | 9,401 | 2 | [Etherscan](https://etherscan.io/address/0x882A47e6070acA3f38Ce6929501F4787803A072b) | | Hyperloot | Ethereum | 8,585 | 2 | [Etherscan](https://etherscan.io/address/0x0290d49f53a8d186973b82faafdafe696b29acbb) | | LobsterDAO | Ethereum | 3,546 | 4 | [Etherscan](https://etherscan.io/address/0x026224a2940bfe258d0dbe947919b62fe321f042) | | Moonbirds | Ethereum | 9,999 | 4 | [Etherscan](https://etherscan.io/address/0x23581767a106ae21c074b2276d25e5c3e136a68b) | | Foxy PFP | Linea | 10,000 | 2 | [Lineascan](https://lineascan.build/address/0xe46c02315c4a991d061a06f165028f8c9167249b) | | The Birth Lottery | Linea | 666 | 2 | [Lineascan](https://lineascan.build/address/0xc7e1c28dc11e43ebe93f2664a53ae3775eba68cc) | | Genesis Mana | Ethereum | 11,535 | 2 | [Etherscan](https://etherscan.io/address/0xf4b6040a4b1b30f1d1691699a8f3bf957b03e463) | | The Eye for Adventurers | Ethereum | 1,614 | 3 | [Etherscan](https://etherscan.io/address/0xb8a51862964f77025abb65e2c6a39ee8070c8ed4) | | Genesis Adventurers | Ethereum | 633 | 5 | [Etherscan](https://etherscan.io/address/0x8db687aceb92c66f013e1d614137238cc698fedb) | | Terraforms | Ethereum | 9,911 | 5 | [Etherscan](https://etherscan.io/address/0x4e1f41613c9084fdb9e34e11fae9412427480e56) | | The Dudes | Ethereum | 512 | 3 | [Etherscan](https://etherscan.io/address/0xb0cf7da8dc482997525be8488b9cad4f44315422) | | Crypts and Caverns | Ethereum | 8,785 | 2 | [Etherscan](https://etherscan.io/address/0x86f7692569914B5060Ef39aAb99e62eC96A6Ed45) | | Trusta OG Dragons | Ethereum | 3,707 | 2 | [Etherscan](https://etherscan.io/address/0xe0e126ce63becbecd72bee4f7673b4e50d5a9965) | | Wandernauts | Ethereum | 8,887 | 2 | [Etherscan](https://etherscan.io/address/0x793daf78b74aadf1eda5cc07a558fed932360a60) | | Based Ghouls | Ethereum | 6,667 | 2 | [Etherscan](https://etherscan.io/address/0xef1a89cbfabe59397ffda11fc5df293e9bc5db90) | | Pirate Nation | Ethereum | 9,999 | 3 | [Etherscan](https://etherscan.io/address/0x1b41d54b3f8de13d58102c50d7431fd6aa1a2c48) | | Grugs Lair | Ethereum | 1,000 | 1 | [Etherscan](https://etherscan.io/address/0xfa9ed22ca5d329ecaee9347f72e18c1fc291471b) | | Dope Wars | Ethereum | 7,999 | 3 | [Etherscan](https://etherscan.io/address/0x8707276df042e89669d69a177d3da7dc78bd8723) | | Blobert | Starknet | 4,844 | 2 | [Voyager](https://voyager.online/contract/0x00539f522b29ae9251dbf7443c7a950cf260372e69efab3710a11bf17a9599f1) | | Schizoid | Starknet | 999 | 3 | [Voyager](https://voyager.online/contract/0x077485a949c130cf0d98819d2b0749f5860b0734ea28cb678dd3f39379131bfa) | | FOCG Boi Hands | Starknet | 39 | 2 | [Voyager](https://voyager.online/contract/0x02ef78f4031b4250e4ae8c4f0b18af4ef0cbbb405cdb20f80d1041b53aa2f1a1) | | Brother ID | Starknet | 2,265 | 2 | [Voyager](https://voyager.online/contract/0x0212f1c57700f5a3913dd11efba540196aad4cf67772f7090c62709dd804fa74) | | Realms | Starknet | 5,099 | 3 | [Voyager](https://voyager.online/contract/0x07ae27a31bb6526e3de9cf02f081f6ce0615ac12a6d7b85ee58b8ad7947a2809) | | Banners (Ethereum) | Ethereum | 1,785 | 3 | [Etherscan](https://etherscan.io/address/0x527a4206ac04c2017295cf32f1fc2f9e034a7c40) | | Banners (Starknet) | Starknet | 9,170 | 2 | [Voyager](https://voyager.online/contract/0x02d66679de61a5c6d57afd21e005a8c96118bd60315fd79a4521d68f5e5430d1) | | Wolf Nation | Starknet | 333 | 3 | [Voyager](https://voyager.online/contract/0x0413485dbeccec6320d35dcba14375e7ca973f021486496a61286edb958fd861) | | zKube | Starknet | 11,326 | 1 | [Voyager](https://voyager.online/contract/0x04fd5df500e6c6615e4423258639f189455672bc841ba58f1c781ac7c5ff4bd8) | | Karats | Starknet | 512 | 2 | [Voyager](https://voyager.online/contract/0x07d8ea58612a5de25f29281199a4fc1f2ce42f0f207f93c3a35280605f3b8e68) | | Karats 2 | Starknet | 512 | 2 | [Voyager](https://voyager.online/contract/0x030f694df2a04e04cf3e1d3e79dd5aadfdaa77295bb007696222fc60a5e8730d) | | Ducks Everywhere | Starknet | 300 | 10 | [Voyager](https://voyager.online/contract/0x04fa864a706e3403fd17ac8df307f22eafa21b778b73353abf69a622e47a2003) | | Pistols | Starknet | 126 | 5 | [Voyager](https://voyager.online/contract/0x014aa76e6c6f11e3f657ee2c213a62006c78ff2c6f8ed40b92c42fd554c246f2) | | Influence Asteroids | Starknet | 11,726 | 1 | [Voyager](https://voyager.online/contract/0x0603cf837055c64d026a3c5a9e3a83036cea6c4a3f68a9e19f7a687d726fe817) | | Starkurabu | Starknet | 10,000 | 1 | [Voyager](https://voyager.online/contract/0x03ab1124ef9ec3a2f2b1d9838f9066f9a894483d40b33390dda8d85c01a315a3) | | Jokers Of Neon Beasts | Starknet | 104 | 5 | [Voyager](https://voyager.online/contract/0x07268fcf96383f8691b91ba758cc8fefe0844146f0557909345b841fb1de042f) | | Based Punks | Base | 5,000 | 2 | [Basescan](https://basescan.org/address/0xcB28749c24AF4797808364D71d71539bc01E76d4) | | OK Computer | Base | 5,000 | 2 | [Basescan](https://basescan.org/address/0xCE2830932889C7fB5e5206287C43554E673dCc88) | | Blackmirror Experience | Base | 7,078 | 2 | [Basescan](https://basescan.org/address/0x108dB2038C6e8Bc1A0b058c70e752b14d08648BF) | | re:generates | Base | 6,665 | 2 | [Basescan](https://basescan.org/address/0x56dFE6ae26bf3043DC8Fdf33bF739B4fF4B3BC4A) | | Writ of Passage | Arbitrum | 10,000 | 2 | [Arbiscan](https://arbiscan.io/address/0x5d541B55763A9277f61a739f40B6021A16C2d3d8) | | Liberty Cats | Polygon | 7,777 | 2 | [Polygonscan](https://polygonscan.com/address/0x0030F47D6a73Bc518CF18fE027Ea91DD6B2b6003) | | Billions Genesis | Polygon | 2,999 | 2 | [Polygonscan](https://polygonscan.com/address/0xeCb63be05886749De33709835e824AEFcFd74f8F) | | Pudgy Penguins | Ethereum | 8,888 | 10 | [Etherscan](https://etherscan.io/address/0xBd3531dA5CF5857e7CfAA92426877b022e612cf8) | | The Larp Collective | Arbitrum | 420 | 2 | [Arbiscan](https://arbiscan.io/address/0x48D2c1716DFDF8aDd5a5EA658c460e7257448660) | | ZTX Genesis Homes | Arbitrum | 3,999 | 2 | [Arbiscan](https://arbiscan.io/address/0x35373efc2FD7D852729cae869Cc32acc979100bD) | | Forgotten Runes | Ethereum | 10,000 | 3 | [Etherscan](https://etherscan.io/address/0x521f9c7505005cfa19a8e5786a9c3c9c9f5e6f42) | | Cool Cats | Ethereum | 10,000 | 3 | [Etherscan](https://etherscan.io/address/0x1a92f7381b9f03921564a437210bb9396471050c) | | Bankr | Base | 1,000 | 10 | [Basescan](https://basescan.org/address/0x9fab8c51f911f0ba6dab64fd6e979bcf6424ce82) | | Mee6 Genesis Pass | Ethereum | 4,444 | 3 | [Etherscan](https://etherscan.io/address/0xe9ab13482aeab3727e976257d8e1f026e12e9ecd) | | Adventurers | Starknet | 10,638 | 1 | [Voyager](https://voyager.online/contract/0x018108b32cea514a78ef1b0e4a0753e855cdf620bc0565202c02456f618c4dc4) | | OGS Discord Role | Starknet | - | 10 | Discord Verified | ### Understanding Free Games #### How Free Games Work The "Free Games per NFT" column shows how many free game entries each NFT from that collection grants. For example: * Owning 1 Loot NFT = 5 free games * Owning 2 Pudgy Penguins = 20 free games (10 per NFT) * Holding multiple eligible NFTs = Games stack across all collections * **Maximum 50 free games per address** regardless of total holdings #### Token Earning Potential Each free game is an opportunity to earn SURVIVOR tokens through gameplay: * **Level 1-2**: No rewards (warm-up levels) * **Level 3+**: Earn tokens equal to the level reached * **Example**: Reaching level 3 earns 3 tokens, level 25 earns 25 tokens, level 50 earns 50 tokens #### Multi-Chain Support Eligible collections span multiple networks: * **Ethereum** - The majority of legacy collections * **Starknet** - Native Loot Survivor ecosystem collections * **Base** - Emerging gaming communities * **Arbitrum** - L2 gaming collections * **Polygon** - Alternative L2 collections * **Linea** - Next-generation gaming projects ### Collection Categories #### Premium Allocations (10+ games) Collections receiving the highest free game allocations: * **Pudgy Penguins**: 10 games per NFT * **Ducks Everywhere**: 10 games per NFT * **Bankr**: 10 games per NFT * **OGS Discord Role**: 10 games for verified members #### Loot Ecosystem (5 games) Original Loot-related collections with enhanced allocations: * **Loot**: 5 games per NFT * **Genesis Adventurers**: 5 games per NFT * **1337 Skulls**: 5 games per NFT * **BAYC**: 5 games per NFT * **Terraforms**: 5 games per NFT #### Standard Gaming Collections (2-4 games) Most collections receive standard allocations: * **2 games**: Common allocation for gaming NFTs * **3 games**: Enhanced allocation for active communities * **4 games**: Premium allocation for key partners #### Entry Level (1 game) Collections with base-level allocations: * **Grugs Lair**: 1 game per NFT * **Influence Asteroids**: 1 game per NFT * **Starkurabu**: 1 game per NFT * **Adventurers**: 1 game per NFT * **zKube**: 1 game per NFT ### Snapshot Details The eligibility snapshot was taken on **August 18th, 2025 at 00:00 UTC**. All NFT holdings and free game allocations are based on ownership at this specific point in time. ### Verification Process To verify your free games allocation: 1. Check your NFT holdings as of August 18th, 2025 at 00:00 UTC 2. Calculate total free games: (NFTs held × Free Games per NFT) 3. Sum across all eligible collections you held 4. Apply the 50 free games per address maximum cap 5. Connect wallet to claim interface for automatic calculation ### Important Notes * **Maximum 50 free games per eligible address** * **Free games expire 2 weeks after the claim window opens** - Same deadline for all players * Multiple NFTs from the same collection grant cumulative games (up to the 50 game cap) * Transfers after the snapshot do not affect eligibility * The OGS Discord Role requires Discord verification, not on-chain holdings * Strategic gameplay can maximize token earnings from free games ## SURVIVOR Token The SURVIVOR token is the native governance and economic coordination token for Loot Survivor, designed to decentralize the ownership role of the game contracts and promote the growth of the Loot Survivor ecosystem. ### Overview * Total Supply: 10,000,000 * Distribution: 75% Community; 25% Team * Lockups: None * Contract: [`0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b`](/lootsurvivor/contracts#governance-contracts) ### Purpose SURVIVOR is the governance and economic coordination token for Loot Survivor. Governance: SURVIVOR lets holders propose and vote on specific onchain parameters (e.g., which Dungeon the Beasts inhabit, royalty settings for the Beast NFT collection, Beast Mode settings, which ERC‑20s the Dungeon accepts, and the Dungeon’s payment token). Autonomous treasury growth: When a player buys a Dungeon Ticket, immutable code automatically uses 80% of that payment to purchase SURVIVOR on a decentralized exchange and deposits the purchased tokens into the Survivor Treasury, which is governed by SURVIVOR holders. No promises / no rights to payouts: SURVIVOR does not grant rights to profits, revenue, dividends, or distributions. The automated purchases are a mechanical feature of the contracts, not a commitment to support price or provide returns. ### Key Features #### Decentralized Ownership Role The SURVIVOR token ships with a fully onchain governance system, built on top of [OpenZeppelin’s audited governance components](https://docs.openzeppelin.com/contracts-cairo/2.0.0/governance). The governance system enables SURVIVOR token holders to express their will onchain and for their shared will to execute as an onchain transaction. This allows sensitive, configurable game settings,such as which Dungeon the Beasts are in, to be truly owned and controllable by the community instead of the developers. The Survivor Governance Contract will control the following settings: * [Beast NFT](https://voyager.online/nft-contract/0x0280ace0b2171106eaebef91ca9b097a566108e9452c45b94a7924a9f794ae80): * Move Beasts to a new Dungeon * Change royalty information (default: 5% distributed to the Survivor Treasury) * [LS2 Beast Mode](https://voyager.online/contract/0x02fb722c25768e4a9994258754e7103895fd2f33cbcc1edc8711807950830cb5): * Change Beast Mode settings * Add and remove ERC‑20s from the Dungeon * Change the payment token for the Dungeon #### Economic Coordination To support the sustainable growth of the Loot Survivor ecosystem, Loot Survivor ships with an autonomous and immutable SURVIVOR buy‑back mechanism. 80% of the proceeds from the sale of Dungeon Tickets, which are required to enter the Dungeon and collect Beasts, are used to purchase SURVIVOR tokens using Ekubo’s TWAMM and to deposit them into the Survivor Treasury. The remaining 20% is distributed to $veLORDS holders for the benefit of Bibliotheca DAO, the original incubator of Loot Survivor. #### Treasury Bootstrap The treasury is initially seeded with 1M SURVIVOR and approximately 3M LORDS at genesis. #### SURVIVOR Sale (Programmatic DCA via TWAMM): * Supply: 15% of total supply. * Method: A 3‑month programmatic DCA order via TWAMM (Oct 15 – Jan 15). * Proceeds: 100% contributed to the community treasury for ecosystem growth as governed onchain. DISCLAIMERS: This sale is programmatic, there are no allocations, discounts, or price/return statements. #### Community Allocation 75% of the total supply is allocated to the community through various distribution mechanisms, ensuring broad ownership and aligned incentives. Allocations include: * 22.5% LS2 Dungeon claim allocations (FCFS) * 1 Token per level starting at level 3 * 4X Promotion for games played during weeks 2-4 * 2X Promotion after week 4 till all tokens are claimed. * 15% treasury bootstrap DCA * 10% Survivor Treasury * 9.3% Beast Entitlements (claim at mint) * 6.6% LS1/LS1.5 Players * 5% Loot Realms; 2.5% Loot; 1.5% 1337 Skulls; 1% Genesis Project; 1% Tournaments; 1% Beast Games Note: NFT royalties, if any, are not guaranteed across marketplaces. #### Free Games Airdrop Loot Survivor launches with a two‑week launch promotion that makes \~500k free games available to \~80k addresses across Ethereum, Arbitrum, Base, Starknet, Abstract, Linea, and Polygon. Those holding select NFTs on these platforms will be able to claim free games (including gas), allowing them to play Loot Survivor and collect Beasts and SURVIVOR tokens. ### Quick Links * [Tokenomics](/lootsurvivor/token/tokenomics) - Detailed breakdown of token distribution and allocations * [Airdrop](/lootsurvivor/token/airdrop) - How to claim your SURVIVOR tokens and eligibility criteria * [Eligible Collections](/lootsurvivor/token/eligible-collections) - NFT collections eligible for free games ### Security Governance contracts use well‑known OpenZeppelin components. This is not a security audit; no system is risk‑free. Game contracts are cutting‑edge and unaudited; use at your own risk. ### Disclaimers The SURVIVOR token is designed to decentralize ownership and governance of the Loot Survivor protocol. It represents participation rights in the ecosystem and should not be considered a financial investment. What SURVIVOR is not: SURVIVOR is not equity or debt. Automated purchases into the treasury are mechanical contract behavior, not price support or an offer of returns. Participation involves smart‑contract risk, market volatility, and governance outcomes that may not align with your preferences. Users should understand these risks before participating. Nothing here is investment advice. ## Tokenomics ### Total Supply The total supply of the SURVIVOR token is **10,000,000** tokens, distributed between community and team allocations to ensure sustainable growth and development of the Loot Survivor ecosystem. ### Distribution Overview | Category | Tokens | Percentage | Description | | -------------------- | -------------- | ---------- | ----------------------------------- | | **Community** | **7,500,000** | **75%** | | | LS2 Dungeon | 2,258,100 | 22.58% | Gameplay rewards | | Treasury DCA | 1,500,000 | 15% | 3-month programmatic sale via TWAMM | | Survivor Treasury | 1,002,222 | 10.02% | DAO-controlled treasury | | Beast Entitlements | 931,500 | 9.32% | Claim at Beast mint | | Biblio (Loot Realms) | 500,000 | 5% | DAO Sponsor | | Legacy Beast Bonus | 332,028 | 3.32% | LS1 Beast holder rewards | | Top 1k LS1.5 Players | 326,150 | 3.26% | Top player rewards | | Loot | 250,000 | 2.5% | DAO Sponsor | | 1337 Skulls | 150,000 | 1.5% | 1337 Skulls | | Genesis Project | 100,000 | 1% | DAO Sponsor | | Budokan | 100,000 | 1% | Tournament Rewards | | Summit | 100,000 | 1% | Beast Game Rewards | | **Team** | **2,500,000** | **25%** | No lockups | | **Total** | **10,000,000** | **100%** | | ### Distribution Breakdown #### Community Allocation: 7,500,000 (75%) The majority of SURVIVOR tokens are allocated to the community through various mechanisms designed to reward participation, contribution, and loyalty to the ecosystem. ##### LS2 Dungeon Rewards: 2,258,100 (22.58%) * First-come, first-served distribution through gameplay * Tokens equal to the level reached (starting at level 3) * **4X Promotion**: Weeks 2-4 (multiply token rewards by 4) * **2X Promotion**: After week 4 until all tokens claimed (multiply token rewards by 2) ##### Treasury Bootstrap DCA: 1,500,000 (15%) * 3-month programmatic DCA order via TWAMM (Oct 15 - Jan 15) * 100% of proceeds contributed to community treasury * Ensures fair price discovery and broad distribution ##### Survivor Treasury: 1,002,222 (10.02%) * DAO-controlled treasury for ecosystem development * Initial seed funding at genesis * Governed by SURVIVOR token holders ##### Beast Entitlements: 931,500 (9.32%) * Auto-claimed with each Beast mint (average 10 tokens per Beast) * Tier-based distribution: * T1 Beasts: 14 tokens per mint * T2 Beasts: 12 tokens per mint * T3 Beasts: 10 tokens per mint * T4 Beasts: 8 tokens per mint * T5 Beasts: 6 tokens per mint ##### Legacy Players: 658,178 (6.58%) * **Legacy Beast Bonus**: 332,028 (3.32%) * LS1 Beasts (2,306 total): 249,048 tokens (108 per Beast) * LS1.5 Beasts: 82,980 tokens (36 per Beast) * **Top 1k LS1.5 Players**: 326,150 (3.26%) - Top player rewards ##### Ecosystem Allocations: 1,150,000 (11.5%) * **Biblio (Loot Realms)**: 500,000 (5%) - Realms ecosystem development * **Loot**: 250,000 (2.5%) - Distributed to multisig of trusted Loot ecosystem entities * **1337 Skulls**: 150,000 (1.5%) - Distributed to multisig of trusted Skulls ecosystem entities * **Genesis Project**: 100,000 (1%) - Distributed to multisig of trusted Genesis ecosystem entities * **Budokan**: 100,000 (1%) - Player incentives for game-agnostic tournament platform * **Summit**: 100,000 (1%) - Player incentives and utility for Beasts collected in Loot Survivor #### Team Allocation: 2,500,000 (25%) Reserved for the Loot Survivor team. This allocation aligns long-term incentives between the team and the ecosystem's success. ### Value Accrual Mechanisms #### Governance Treasury SURVIVOR token holders control the governance treasury, which consists of: * Proceeds from the Treasury Bootstrap DCA (1.5M tokens sold over 3 months) * 80% of proceeds from dungeon ticket purchases * 1,002,222 SURVIVOR tokens from the Survivor Treasury allocation * Initial seed of \~3M LORDS at genesis These funds can be directed towards: * Buyback and burns to distribute game revenue to token holders * Development funding for new features * Liquidity provision incentives * Ecosystem partnerships and growth initiatives #### Governance Rights SURVIVOR token holders control: * **Beast NFT Settings**: * Move Beasts to new Dungeons * Royalty settings (default 5% to Survivor Treasury) * **LS2 Beast Mode**: * Beast Mode configuration * ERC-20 acceptance in Dungeons * Dungeon payment token selection * **Treasury Management**: * Allocation of treasury funds * Partnership approvals and funding * Community initiative funding ### Distribution Schedule #### Launch Distributions * **Free Games Airdrop**: \~500k games to \~80k addresses (2-week claim window) * **Beast Entitlements**: Claimable at Beast mint * **LS1/LS1.5 Players**: Based on historical participation #### Ongoing Distributions * **LS2 Dungeon**: First-come, first-served through gameplay * Week 1: Standard rate (tokens = level reached, starting at L3) * Weeks 2-4: 4X promotion (standard rate × 4) * After Week 4: 2X promotion until exhausted (standard rate × 2) * **Treasury Bootstrap DCA**: 3-month programmatic sale (Oct 15 - Jan 15) #### No Lockups * All allocations are immediately liquid upon receipt * No vesting schedules or lockup periods * Team tokens have no restrictions ### Economic Sustainability The tokenomics model is designed to create a self-sustaining ecosystem where: * Players are rewarded for skill and participation * Developers are incentivized to build on the platform * Token holders benefit from ecosystem growth * Protocol revenue supports ongoing development This balanced approach ensures long-term viability while maintaining decentralization and community ownership. ## Technical Architecture Loot Survivor is built on Starknet using the Dojo engine, implementing a fully onchain game architecture where all game logic, state management, and randomness are handled by smart contracts. ### Technology Stack
#### ⛓️ Blockchain Layer | Component | Technology | Version | | ------------- | -------------- | ------- | | **Network** | Starknet L2 | Mainnet | | **Language** | Cairo | 2.10.1 | | **Framework** | Dojo | 1.6.2 | | **Standards** | ERC20/721/1155 | Latest |
#### 🌐 Frontend Stack | Component | Technology | Purpose | | ------------- | -------------- | ----------------- | | **Framework** | React 18 | UI rendering | | **Language** | TypeScript | Type safety | | **Build** | Vite | Fast bundling | | **State** | Zustand | State management | | **Styling** | Tailwind + MUI | Responsive design |
#### 🏭 Infrastructure | Service | Provider | Purpose | | ----------- | ---------- | ----------------- | | **Indexer** | Torii | Event indexing | | **RPC** | Starknet | Blockchain access | | **Storage** | IPFS | Asset storage | | **CDN** | Cloudflare | Global delivery |
#### Frontend Stack * **Framework**: React 18 with TypeScript * **Build Tool**: Vite * **State Management**: Zustand * **Styling**: Tailwind CSS + Material-UI * **Wallet Integration**: StarknetKit + Cartridge Controller #### Infrastructure * **Indexer**: Torii (Dojo's indexer) * **RPC**: Starknet full nodes * **Storage**: IPFS for assets * **CDN**: Cloudflare for static assets ### System Architecture #### High-Level Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ Frontend (React) │ ├─────────────────────────────────────────────────────────────┤ │ StarknetKit / Cartridge │ ├─────────────────────────────────────────────────────────────┤ │ Torii Indexer │ ├─────────────────────────────────────────────────────────────┤ │ Dojo Framework (ECS) │ ├─────────────────────────────────────────────────────────────┤ │ Cairo Smart Contracts │ ├─────────────────────────────────────────────────────────────┤ │ Starknet L2 │ └─────────────────────────────────────────────────────────────┘ ``` #### Component Architecture ##### Smart Contract Layer The contract architecture follows a modular system design: ``` contracts/ ├── systems/ │ ├── adventurer/ # Player character logic │ ├── beast/ # Enemy AI and combat │ ├── game/ # Core game loop │ ├── loot/ # Item generation │ ├── market/ # Trading system │ └── settings/ # Configuration ├── models/ │ ├── adventurer/ # Character data structures │ ├── beast.cairo # Beast entities │ ├── combat.cairo # Combat state │ ├── loot.cairo # Item models │ └── market.cairo # Market state └── libs/ ├── game.cairo # Game utilities └── settings.cairo # Config helpers ``` ##### Frontend Architecture ``` client/src/ ├── desktop/ # Desktop-optimized UI │ ├── components/ # Reusable components │ ├── overlays/ # Screen overlays │ └── pages/ # Route pages ├── mobile/ # Mobile-optimized UI │ ├── components/ # Mobile components │ ├── containers/ # Screen containers │ └── pages/ # Mobile routes ├── dojo/ # Blockchain integration │ ├── useSystemCalls.ts # Contract interactions │ ├── useGameSettings.ts # Settings hooks │ └── useQuests.ts # Quest system ├── stores/ # Zustand state management │ ├── gameStore.ts # Game state │ ├── marketStore.ts # Market state │ └── uiStore.ts # UI state └── generated/ # Auto-generated bindings ``` ### Data Models #### Entity-Component System (ECS) Dojo uses an ECS architecture where: * **Entities**: Unique identifiers (adventurer ID, beast ID) * **Components**: Data containers (stats, equipment, position) * **Systems**: Logic processors (combat, movement, trading) #### Core Entities
##### 🧿 Adventurer Entity ```cairo pub struct Adventurer { pub health: u16, // 10 bits pub xp: u16, // 15 bits pub gold: u16, // 9 bits pub beast_health: u16, // 10 bits pub stat_upgrades_available: u8, // 4 bits pub stats: Stats, // 30 bits - 7 core attributes pub equipment: Equipment, // 128 bits - 8 gear slots pub item_specials_seed: u16, // 16 bits pub action_count: u16, } ``` > **💡 Storage Optimization:** Entire adventurer state packed into a single storage slot!
##### Beast Entity ```cairo struct Beast { id: u32, beast_type: BeastType, level: u8, health: u16, damage: u16, special_abilities: Array, } ``` ##### Item Entity ```cairo struct Item { id: u32, item_type: ItemType, tier: Tier, level: u8, prefix: Option, suffix: Option, greatness: u8, } ``` #### State Management ##### Onchain State * **Persistent**: Adventurer data, items, achievements * **Session**: Current combat, market seed, active quests * **Global**: Leaderboards, statistics, events ##### Client State ```typescript interface GameState { adventurer: Adventurer | null; currentBeast: Beast | null; market: MarketItem[]; combatLog: CombatEvent[]; isLoading: boolean; } ``` ### Smart Contract Systems #### Game System Central orchestrator managing game flow: * Action validation * State transitions * Event emission * Gas optimization #### Adventurer System Handles player character logic: * Stat management * Equipment handling * Level progression * Health/death mechanics #### Combat System Manages battle mechanics: * Damage calculation * Type advantages * Critical hits * Flee mechanics #### Loot System Generates and manages items: * Deterministic generation * Tier distribution * Prefix/suffix assignment * Item evolution #### Market System Handles trading mechanics: * Seed-based generation * Price calculation * Inventory rotation * Transaction processing ### Network Architecture #### Transaction Flow 1. **User Action**: Player initiates action in UI 2. **Wallet Signing**: Transaction signed via wallet 3. **Contract Execution**: Cairo contract processes logic 4. **State Update**: Dojo updates entity state 5. **Event Emission**: Events logged onchain 6. **Indexer Update**: Torii indexes new state 7. **UI Update**: Frontend reflects changes #### Gas Optimization ##### Packed Storage ```cairo // Efficient packing of multiple values struct PackedStats { // All stats in single felt252 packed: felt252, } ``` ##### Batch Operations ```cairo // Multiple actions in single transaction fn batch_actions(actions: Array) { // Process all actions together } ``` ### Integration Points #### Wallet Integration ##### StarknetKit Connection ```typescript const { connect, connectors } = useStarknet(); // Available connectors const wallets = [argentX(), braavos(), cartridge()]; ``` #### RPC Integration ##### Contract Calls ```typescript // Read operations (free) const adventurer = await contract.get_adventurer(id); // Write operations (gas required) const tx = await contract.attack_beast(); ``` #### Event System ##### Event Structure ```cairo #[event] struct BeastSlain { adventurer_id: u256, beast_id: u32, gold_earned: u16, xp_earned: u32, } ``` ##### Event Listening ```typescript contract.on("BeastSlain", (event) => { updateGameState(event); }); ``` ### Randomness Architecture #### Deterministic Randomness Default mode using blockchain data: ```cairo fn get_random_seed() -> felt252 { let info = get_block_info().unbox(); pedersen(info.block_number, info.block_timestamp) } ``` #### VRF Integration Optional true randomness: ```cairo fn request_random() -> u256 { let vrf = IVRFProvider::new(); vrf.request_random(callback) } ``` ### Security Architecture
#### 🔐 Access Control ```cairo #[external(v0)] fn admin_action() { assert(get_caller_address() == ADMIN, 'Not authorized'); // Admin logic } ``` | Security Layer | Protection | Implementation | | -------------------- | ---------------------- | --------------------- | | **Access Control** | Role-based permissions | Owner/Admin checks | | **Input Validation** | Parameter sanitization | Assert statements | | **Reentrancy Guard** | Attack prevention | Lock mechanisms | | **Integer Overflow** | Math safety | Cairo built-in checks | > **⚠️ Security Note:** All contracts audited by \[Auditor Name] in \[Date]
#### Input Validation ```cairo fn validate_action(action: Action) { assert(action.is_valid(), 'Invalid action'); assert(has_resources(), 'Insufficient resources'); } ``` #### Reentrancy Protection ```cairo mod ReentrancyGuard { fn start() { assert(!is_locked(), 'Reentrant call'); set_locked(true); } fn end() { set_locked(false); } } ``` ### Performance Optimization #### Frontend Optimization ##### Code Splitting ```typescript const GamePage = lazy(() => import("./pages/GamePage")); const MarketPage = lazy(() => import("./pages/MarketPage")); ``` ##### State Management ```typescript // Zustand with persistence const useGameStore = create( persist( (set) => ({ // Minimal state updates }), { name: "game-storage" } ) ); ``` #### Contract Optimization ##### Storage Patterns ```cairo // Use packed structs // Minimize storage writes // Batch operations ``` ##### Computation Optimization ```cairo // Precompute values // Use lookup tables // Minimize loops ``` ### Deployment Architecture #### Environment Configuration ##### Development ```toml [environment] rpc_url = "http://localhost:5050" account_address = "0xDEV" private_key = "0xDEV_KEY" ``` ##### Production ```toml [environment] rpc_url = "https://starknet-mainnet.public.blastapi.io" account_address = "0xPROD" private_key = "$PROD_KEY" ``` #### Contract Deployment ##### Using Dojo CLI ```bash # Build contracts sozo build # Deploy to network sozo deploy --world 0xWORLD_ADDRESS # Verify deployment sozo inspect ``` #### Frontend Deployment ##### Build Process ```bash # Install dependencies pnpm install # Build for production pnpm build # Deploy to CDN pnpm deploy ``` ### Monitoring and Analytics #### Onchain Metrics * Transaction volume * Active players * Gas usage * Error rates #### Application Metrics * Page load times * API latencies * User engagement * Error tracking #### Performance Monitoring ```typescript // Performance tracking performance.mark("action-start"); await performAction(); performance.mark("action-end"); performance.measure("action", "action-start", "action-end"); ``` ### Scaling Considerations #### Horizontal Scaling * Multiple RPC endpoints * Load balancing * CDN distribution * Regional deployments #### Vertical Scaling * Optimized contracts * Efficient indexing * Caching strategies * Database optimization ### Future Architecture #### Planned Enhancements ##### Multi-chain Support * Ethereum L1 integration * Cross-chain bridges * Multi-network deployment ##### Advanced Features * Multiplayer dungeons * Guild systems * Tournament infrastructure * Achievement system ##### Technical Improvements * ZK proof integration * Advanced VRF * State channels * Optimistic updates ### Development Workflow #### Local Development 1. Start local Starknet node 2. Deploy contracts locally 3. Run frontend dev server 4. Connect to local network #### Testing Pipeline 1. Unit tests (Cairo) 2. Integration tests 3. Frontend tests 4. E2E tests #### Deployment Pipeline 1. Code review 2. Automated testing 3. Staging deployment 4. Production release ### Best Practices
#### 📝 Contract Development | Practice | Implementation | | -------------------- | ----------------------- | | **Modular Design** | Separate concerns | | **Gas Optimization** | Pack storage, batch ops | | **Testing** | 90%+ coverage | | **Security** | Regular audits |
#### 🌐 Frontend Development | Practice | Implementation | | --------------- | ------------------------- | | **Components** | Reusable, typed | | **State** | Minimal, efficient | | **Performance** | Code splitting, lazy load | | **Design** | Mobile-first, accessible |
#### 🔗 Integration Guidelines | Practice | Implementation | | ------------- | ---------------------- | | **Errors** | Graceful handling | | **Retries** | Exponential backoff | | **Fallbacks** | Degradation paths | | **Feedback** | Loading states, toasts |
> **🏆 Excellence Standard:** Follow these practices for production-ready code! ### Conclusion The Loot Survivor architecture demonstrates how complex game mechanics can be implemented fully onchain while maintaining performance and user experience. The modular design allows for easy extension and modification, making it an ideal foundation for blockchain gaming. ## Smart Contracts Documentation This document provides detailed information about the Loot Survivor smart contracts, their interfaces, and how to interact with them. ### Contract Overview Loot Survivor's smart contracts are written in Cairo and deployed on Starknet. The contracts follow a modular architecture using the Dojo framework's Entity-Component-System (ECS) pattern. ### Core Contracts #### Game Contract The main orchestrator managing the entire game flow. **Address (Mainnet)**: `0xbcb2386436161d8d3afea0a805a8610ab90af5cf5582d866b83e9cb779bef3` ##### Key Functions ##### start\_game ```cairo fn start_game( client_reward_address: ContractAddress, weapon_type: WeaponType, name: felt252, golden_token_id: u256, interface_camel: bool ) -> u256 ``` * Starts a new adventure * Returns the adventurer ID * Requires payment (ETH or LORDS) ##### explore ```cairo fn explore(adventurer_id: u256, till_beast: bool) ``` * Explores the dungeon * `till_beast`: Continue until beast encounter if true * Triggers obstacles and discoveries ##### attack ```cairo fn attack(adventurer_id: u256, to_the_death: bool) ``` * Attacks the current beast * `to_the_death`: Fight until death if true * Calculates damage and rewards ##### flee ```cairo fn flee( adventurer_id: u256, to_the_death: bool ) ``` * Attempts to flee from combat * Success based on Dexterity * May take damage on failure ##### upgrade ```cairo fn upgrade( adventurer_id: u256, stat_upgrades: Stats, items_to_purchase: Array ) ``` * Allocates stat points * Purchases items from market * Called after leveling up #### Adventurer Contract Manages adventurer data and progression. **Address (Mainnet)**: `0x3befa9c969bf82bbfa0a96374da9f7aab172101298c0ff2611ec8c2fd02692` ##### Data Structures ##### Adventurer ```cairo struct Adventurer { // Identity id: u256, owner: ContractAddress, name: felt252, // Progression level: u8, experience: u32, // Health health: u16, max_health: u16, // Resources gold: u16, // Stats strength: u8, dexterity: u8, vitality: u8, intelligence: u8, wisdom: u8, charisma: u8, luck: u8, // Equipment weapon: Item, chest: Item, head: Item, waist: Item, foot: Item, hand: Item, neck: Item, ring: Item, // Inventory bag: Bag, // State beast_id: u32, stat_points_available: u8, actions_per_block: u8, mutated: bool, } ``` ##### Stats ```cairo struct Stats { strength: u8, dexterity: u8, vitality: u8, intelligence: u8, wisdom: u8, charisma: u8, luck: u8, } ``` ##### Key Functions ##### get\_adventurer ```cairo fn get_adventurer(adventurer_id: u256) -> Adventurer ``` * Returns full adventurer data * Read-only function (no gas) ##### get\_adventurer\_stats ```cairo fn get_adventurer_stats(adventurer_id: u256) -> Stats ``` * Returns just the stats * Useful for calculations ##### update\_adventurer ```cairo fn update_adventurer(adventurer: Adventurer) ``` * Internal function * Updates adventurer state #### Beast Contract Handles beast generation and combat. **Address (Mainnet)**: `0x66744a5847c4459e019511f390ba8a8da53c5d25de8cd4e24e5f3e450c5d038` ##### Data Structures ##### Beast ```cairo struct Beast { id: u32, beast_type: BeastType, tier: Tier, level: u8, health: u16, starting_health: u16, special_abilities: SpecialAbilities, } ``` ##### BeastType Enum ```cairo enum BeastType { // Starter (T5) Rat, Gnome, Goblin, Skeleton, // Common (T4) Wolf, Orc, Troll, Zombie, DireWolf, Bear, Spider, Ghoul, // Uncommon (T3) Minotaur, Cyclops, Griffin, Vampire, Manticore, Phoenix, Dragon, Lich, // Rare (T2) Balrog, Titan, Leviathan, Tarrasque, Typhon, Fenrir, Hydra, Colossus, // Legendary (T1) // ... 75 total beasts } ``` ##### Key Functions ##### generate\_beast ```cairo fn generate_beast( seed: felt252, adventurer_level: u8 ) -> Beast ``` * Creates a new beast encounter * Level scales with adventurer * Deterministic based on seed ##### calculate\_damage ```cairo fn calculate_damage( attacker_strength: u8, weapon_type: WeaponType, beast_type: BeastType, is_critical: bool ) -> u16 ``` * Calculates combat damage * Includes type advantages * Critical hit multiplier #### Loot Contract Manages item generation and evolution. **Address (Mainnet)**: `0x66744a5847c4459e019511f390ba8a8da53c5d25de8cd4e24e5f3e450c5d038` ##### Data Structures ##### Item ```cairo struct Item { id: u32, item_type: ItemType, tier: Tier, slot: Slot, // Evolution level: u8, greatness: u8, // Special properties prefix: Option, suffix: Option, // Stats damage: u16, armor: u16, stat_bonuses: Stats, } ``` ##### ItemType Enum ```cairo enum ItemType { // Weapons Blade, Bludgeon, Magic, // Armor Cloth, Hide, Metal, // Accessories Ring, Amulet, Pendant, } ``` ##### Tier Enum ```cairo enum Tier { T1, // Legendary (most expensive) T2, // Rare T3, // Uncommon T4, // Common T5, // Starter (cheapest) } ``` ##### Key Functions ##### generate\_item ```cairo fn generate_item( seed: felt252, slot: Slot, tier: Tier ) -> Item ``` * Creates a new item * Deterministic generation * Tier affects rarity ##### evolve\_item ```cairo fn evolve_item( item: Item, xp_gained: u32 ) -> Item ``` * Levels up an item * Unlocks suffixes at level 15 * Unlocks prefixes at level 20 #### Market Contract Handles the in-game marketplace. ##### Data Structures ##### Market ```cairo struct Market { seed: felt252, items: Array, potion_price: u16, refreshed_at: u64, } ``` ##### MarketItem ```cairo struct MarketItem { item: Item, price: u16, stock: u8, } ``` ##### Key Functions ##### generate\_market ```cairo fn generate_market( seed: felt252, adventurer_level: u8, charisma: u8 ) -> Market ``` * Creates market inventory * Prices based on charisma * Refreshes on level up ##### purchase\_item ```cairo fn purchase_item( market: Market, item_id: u32, adventurer_gold: u16 ) -> (Market, u16) ``` * Processes item purchase * Returns updated market and remaining gold ### Events #### Combat Events ```cairo #[event] struct AttackEvent { adventurer_id: u256, beast_id: u32, damage_dealt: u16, damage_taken: u16, is_critical: bool, } #[event] struct BeastSlainEvent { adventurer_id: u256, beast_id: u32, beast_type: BeastType, gold_earned: u16, xp_earned: u32, items_dropped: Array, } #[event] struct FleeAttemptEvent { adventurer_id: u256, beast_id: u32, success: bool, damage_taken: u16, } ``` #### Progression Events ```cairo #[event] struct LevelUpEvent { adventurer_id: u256, new_level: u8, stat_points_gained: u8, } #[event] struct ItemLevelUpEvent { adventurer_id: u256, item_id: u32, new_level: u8, suffix_unlocked: Option, prefix_unlocked: Option, } ``` #### Market Events ```cairo #[event] struct ItemPurchasedEvent { adventurer_id: u256, item_id: u32, price: u16, } #[event] struct MarketRefreshedEvent { adventurer_id: u256, seed: felt252, items_available: u8, } ``` ### Integration Examples #### Starting a Game ```typescript // TypeScript/JavaScript const startGame = async () => { const tx = await gameContract.start_game( rewardAddress, // Address for rewards WeaponType.Blade, // Starting weapon "Adventurer", // Name 0, // Golden token ID (0 if none) false // Interface camel case ); const receipt = await tx.wait(); const adventurerId = receipt.events[0].adventurer_id; return adventurerId; }; ``` #### Combat Loop ```typescript const combatLoop = async (adventurerId: bigint) => { // Attack until victory or death const tx = await gameContract.attack( adventurerId, true // to_the_death ); await tx.wait(); // Check if adventurer survived const adventurer = await adventurerContract.get_adventurer(adventurerId); if (adventurer.health === 0) { console.log("Game Over!"); } }; ``` #### Upgrading After Level Up ```typescript const upgrade = async (adventurerId: bigint) => { const statUpgrades = { strength: 1, dexterity: 0, vitality: 0, intelligence: 0, wisdom: 0, charisma: 0, luck: 0, }; const itemsToPurchase = [ { item_id: 123, equip: true }, { item_id: 456, equip: false }, ]; await gameContract.upgrade( adventurerId, statUpgrades, itemsToPurchase ); }; ``` ### Gas Optimization #### Batch Operations ```cairo // Efficient: Single transaction fn batch_actions(actions: Array) { for action in actions { process_action(action); } } // Inefficient: Multiple transactions fn single_action(action: Action) { process_action(action); } ``` #### Storage Packing ```cairo // Packed storage (efficient) struct PackedAdventurer { // Multiple values in single felt252 packed_stats: felt252, packed_equipment: felt252, } // Unpacked storage (less efficient) struct UnpackedAdventurer { strength: u8, dexterity: u8, vitality: u8, // ... each stat separate } ``` ### Security Considerations #### Access Control All contracts implement access control: ```cairo #[external(v0)] fn admin_function() { assert_caller_is_admin(); // Admin logic } fn assert_caller_is_admin() { let caller = get_caller_address(); assert(caller == ADMIN_ADDRESS, 'Unauthorized'); } ``` #### Input Validation All user inputs are validated: ```cairo fn validate_stat_upgrade(stats: Stats) { let total = stats.strength + stats.dexterity + // ... assert(total <= available_points, 'Invalid points'); assert(stats.strength <= MAX_STAT, 'Stat too high'); } ``` #### Reentrancy Protection Critical functions use reentrancy guards: ```cairo fn critical_function() { assert(!is_locked(), 'Reentrant'); set_locked(true); // Function logic set_locked(false); } ``` ### Testing Contracts #### Unit Tests ```cairo #[test] fn test_combat_damage() { let damage = calculate_damage( strength: 10, weapon_type: WeaponType::Blade, beast_type: BeastType::Goblin, is_critical: false ); assert(damage > 0, 'Damage should be positive'); } ``` #### Integration Tests ```cairo #[test] fn test_full_game_flow() { let adventurer_id = start_game(...); explore(adventurer_id, false); attack(adventurer_id, false); upgrade(adventurer_id, ...); // Assert final state } ``` ### Contract Addresses #### Mainnet Contracts | Contract | Address | | ---------- | ------------------------------------------------------------------- | | Game | `0xbcb2386436161d8d3afea0a805a8610ab90af5cf5582d866b83e9cb779bef3` | | Adventurer | `0x3befa9c969bf82bbfa0a96374da9f7aab172101298c0ff2611ec8c2fd02692` | | Beast | `0x66744a5847c4459e019511f390ba8a8da53c5d25de8cd4e24e5f3e450c5d038` | | Loot | `0x66744a5847c4459e019511f390ba8a8da53c5d25de8cd4e24e5f3e450c5d038` | | Market | `0x1e1c477f2ef896fd638b50caa31e3aa8f504d5c6cb3c09c99cd0b72523f07f7` | #### Testnet Contracts (Sepolia) | Contract | Address | | ---------- | -------------------------------- | | Game | `0x[TESTNET_GAME_ADDRESS]` | | Adventurer | `0x[TESTNET_ADVENTURER_ADDRESS]` | | Beast | `0x[TESTNET_BEAST_ADDRESS]` | | Loot | `0x[TESTNET_LOOT_ADDRESS]` | | Market | `0x[TESTNET_MARKET_ADDRESS]` | ### ABI Documentation Full ABI specifications are available in the generated files: * `/contracts/target/dev/[contract_name].contract_class.json` ### Upgradeability Contracts use the Dojo framework's upgrade system: ```cairo #[starknet::contract] mod UpgradeableContract { #[external(v0)] fn upgrade(new_class_hash: ClassHash) { assert_caller_is_admin(); replace_class_syscall(new_class_hash); } } ``` ### Best Practices #### Reading Contract State ```typescript // Always use view functions for reading const adventurer = await contract.get_adventurer(id); // These are free (no gas) ``` #### Writing to Contracts ```typescript // Batch operations when possible const tx = await contract.batch_actions([ action1, action2, action3, ]); // Single transaction = less gas ``` #### Error Handling ```typescript try { const tx = await contract.attack(adventurerId, true); await tx.wait(); } catch (error) { if (error.message.includes('Dead adventurer')) { // Handle game over } } ``` ### Conclusion The Loot Survivor smart contracts provide a robust, gas-efficient foundation for fully onchain gaming. The modular architecture allows for easy extension while maintaining security and performance. ## Integration Guide This guide explains how to integrate with Loot Survivor, whether you're building a frontend client, creating tools, or extending the game with additional features. ### Quick Start #### Prerequisites * Node.js 18+ or your preferred runtime * Starknet wallet or programmatic account * Basic understanding of Starknet and Cairo #### Installation ##### JavaScript/TypeScript ```bash # Using npm npm install starknet @dojoengine/core # Using pnpm pnpm add starknet @dojoengine/core # Using yarn yarn add starknet @dojoengine/core ``` ##### Python ```bash pip install starknet-py ``` ### Setting Up Connection #### Initialize Provider ```typescript import { Provider, constants } from 'starknet'; // Mainnet const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_MAIN, }, }); // Testnet (Sepolia) const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_SEPOLIA, }, }); ``` #### Contract Instances ```typescript import { Contract } from 'starknet'; import GameABI from './abis/Game.json'; import AdventurerABI from './abis/Adventurer.json'; const GAME_ADDRESS = "0xbcb2386436161d8d3afea0a805a8610ab90af5cf5582d866b83e9cb779bef3"; const ADVENTURER_ADDRESS = "0x3befa9c969bf82bbfa0a96374da9f7aab172101298c0ff2611ec8c2fd02692"; const gameContract = new Contract(GameABI, GAME_ADDRESS, provider); const adventurerContract = new Contract(AdventurerABI, ADVENTURER_ADDRESS, provider); ``` ### Wallet Integration #### Using StarknetKit ```typescript import { connect, disconnect } from '@starknet-io/get-starknet'; const connectWallet = async () => { const starknet = await connect({ modalMode: "alwaysAsk", modalTheme: "dark", }); if (!starknet?.isConnected) { throw new Error("Failed to connect wallet"); } return starknet; }; ``` #### Using Cartridge Controller ```typescript import Controller from '@cartridge/controller'; const controller = new Controller({ policies: [ { target: GAME_ADDRESS, method: 'attack', }, { target: GAME_ADDRESS, method: 'explore', }, ], rpc: "https://api.cartridge.gg/x/starknet/mainnet", }); await controller.connect(); ``` ### Reading Game State #### Get Adventurer Data ```typescript interface Adventurer { id: bigint; owner: string; name: string; level: number; health: number; gold: number; experience: number; stats: Stats; equipment: Equipment; } const getAdventurer = async (adventurerId: bigint): Promise => { const result = await adventurerContract.get_adventurer(adventurerId); return { id: result.id, owner: result.owner, name: result.name, level: Number(result.level), health: Number(result.health), gold: Number(result.gold), experience: Number(result.experience), stats: parseStats(result.stats), equipment: parseEquipment(result.equipment), }; }; ``` #### Get Current Beast ```typescript const getCurrentBeast = async (adventurerId: bigint) => { const beast = await gameContract.get_current_beast(adventurerId); if (!beast) return null; return { id: beast.id, type: beast.beast_type, level: Number(beast.level), health: Number(beast.health), maxHealth: Number(beast.starting_health), }; }; ``` #### Get Market Items ```typescript const getMarketItems = async (adventurerId: bigint) => { const market = await gameContract.get_market(adventurerId); return market.items.map(item => ({ id: item.id, name: item.name, type: item.item_type, tier: item.tier, price: Number(item.price), slot: item.slot, })); }; ``` ### Writing Transactions #### Starting a Game ```typescript const startAdventure = async ( account: Account, weaponType: number, name: string ) => { const tx = await account.execute({ contractAddress: GAME_ADDRESS, entrypoint: 'start_game', calldata: [ account.address, // reward address weaponType, // 0: Blade, 1: Bludgeon, 2: Magic name, // adventurer name 0, // golden token (0 if none) 0, // interface camel case (0 for snake) ], }); const receipt = await provider.waitForTransaction(tx.transaction_hash); // Extract adventurer ID from events const adventurerId = extractAdventurerIdFromEvents(receipt.events); return adventurerId; }; ``` #### Combat Actions ```typescript // Attack beast const attack = async ( account: Account, adventurerId: bigint, tillDeath: boolean = false ) => { const tx = await account.execute({ contractAddress: GAME_ADDRESS, entrypoint: 'attack', calldata: [ adventurerId.toString(), tillDeath ? 1 : 0, ], }); return await provider.waitForTransaction(tx.transaction_hash); }; // Flee from combat const flee = async ( account: Account, adventurerId: bigint ) => { const tx = await account.execute({ contractAddress: GAME_ADDRESS, entrypoint: 'flee', calldata: [ adventurerId.toString(), 0, // to_the_death ], }); return await provider.waitForTransaction(tx.transaction_hash); }; ``` #### Exploration ```typescript const explore = async ( account: Account, adventurerId: bigint, tillBeast: boolean = false ) => { const tx = await account.execute({ contractAddress: GAME_ADDRESS, entrypoint: 'explore', calldata: [ adventurerId.toString(), tillBeast ? 1 : 0, ], }); return await provider.waitForTransaction(tx.transaction_hash); }; ``` #### Upgrading ```typescript interface StatUpgrade { strength: number; dexterity: number; vitality: number; intelligence: number; wisdom: number; charisma: number; luck: number; } interface ItemPurchase { item_id: number; equip: boolean; } const upgrade = async ( account: Account, adventurerId: bigint, stats: StatUpgrade, items: ItemPurchase[] ) => { const calldata = [ adventurerId.toString(), // Stats (must sum to available points) stats.strength, stats.dexterity, stats.vitality, stats.intelligence, stats.wisdom, stats.charisma, stats.luck, // Items array items.length, ...items.flatMap(i => [i.item_id, i.equip ? 1 : 0]), ]; const tx = await account.execute({ contractAddress: GAME_ADDRESS, entrypoint: 'upgrade', calldata, }); return await provider.waitForTransaction(tx.transaction_hash); }; ``` ### Event Listening #### Setting Up Event Listeners ```typescript import { events } from 'starknet'; const subscribeToEvents = (adventurerId: bigint) => { // Listen for combat events gameContract.on('AttackEvent', (event) => { if (event.adventurer_id === adventurerId) { console.log('Attack:', { damage_dealt: event.damage_dealt, damage_taken: event.damage_taken, is_critical: event.is_critical, }); } }); // Listen for level up gameContract.on('LevelUpEvent', (event) => { if (event.adventurer_id === adventurerId) { console.log('Level Up! New level:', event.new_level); } }); // Listen for death gameContract.on('AdventurerDiedEvent', (event) => { if (event.adventurer_id === adventurerId) { console.log('Game Over! Final level:', event.level); } }); }; ``` #### Processing Historical Events ```typescript const getAdventurerHistory = async (adventurerId: bigint) => { const events = await provider.getEvents({ address: GAME_ADDRESS, from_block: { block_number: 0 }, to_block: 'latest', keys: [[adventurerId.toString()]], chunk_size: 100, }); return events.events.map(parseEvent); }; ``` ### Using Torii Indexer #### GraphQL Queries ```typescript const TORII_URL = "https://api.cartridge.gg/x/lootsurvivor/torii/graphql"; const query = ` query GetAdventurer($id: String!) { adventurer(id: $id) { id owner level health gold experience stats { strength dexterity vitality intelligence wisdom charisma luck } } } `; const fetchAdventurerData = async (adventurerId: string) => { const response = await fetch(TORII_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { id: adventurerId }, }), }); const data = await response.json(); return data.data.adventurer; }; ``` #### WebSocket Subscriptions ```typescript import { createClient } from 'graphql-ws'; const client = createClient({ url: 'wss://api.cartridge.gg/x/lootsurvivor/torii/ws', }); const subscription = ` subscription OnAdventurerUpdate($id: String!) { adventurerUpdated(id: $id) { id level health gold } } `; client.subscribe( { query: subscription, variables: { id: adventurerId }, }, { next: (data) => console.log('Update:', data), error: (err) => console.error('Error:', err), complete: () => console.log('Subscription complete'), } ); ``` ### Building Custom Frontends #### React Hook Example ```typescript import { useState, useEffect } from 'react'; const useAdventurer = (adventurerId: bigint) => { const [adventurer, setAdventurer] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const data = await getAdventurer(adventurerId); setAdventurer(data); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchData(); // Set up event listeners const unsubscribe = subscribeToAdventurerUpdates( adventurerId, setAdventurer ); return unsubscribe; }, [adventurerId]); return { adventurer, loading, error }; }; ``` #### State Management ```typescript import { create } from 'zustand'; interface GameStore { adventurer: Adventurer | null; beast: Beast | null; market: MarketItem[]; setAdventurer: (adventurer: Adventurer) => void; setBeast: (beast: Beast) => void; setMarket: (items: MarketItem[]) => void; attack: () => Promise; flee: () => Promise; explore: () => Promise; } const useGameStore = create((set, get) => ({ adventurer: null, beast: null, market: [], setAdventurer: (adventurer) => set({ adventurer }), setBeast: (beast) => set({ beast }), setMarket: (market) => set({ market }), attack: async () => { const { adventurer } = get(); if (!adventurer) return; const result = await attackBeast(adventurer.id); // Update state based on result }, // ... other actions })); ``` ### Building Game Tools #### Leaderboard API ```typescript const getLeaderboard = async (limit = 100) => { const query = ` query GetLeaderboard($limit: Int!) { adventurers( orderBy: "level", orderDirection: "desc", limit: $limit ) { id owner name level gold timestamp } } `; const response = await fetch(TORII_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { limit }, }), }); const data = await response.json(); return data.data.adventurers; }; ``` #### Statistics Tracker ```typescript const getPlayerStats = async (playerAddress: string) => { const query = ` query GetPlayerStats($owner: String!) { adventurers(where: { owner: $owner }) { id level gold experience timestamp died_at } } `; const response = await fetch(TORII_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables: { owner: playerAddress }, }), }); const data = await response.json(); // Calculate statistics const adventures = data.data.adventurers; return { totalAdventures: adventures.length, highestLevel: Math.max(...adventures.map(a => a.level)), totalGold: adventures.reduce((sum, a) => sum + a.gold, 0), averageLevel: adventures.reduce((sum, a) => sum + a.level, 0) / adventures.length, }; }; ``` ### Error Handling #### Common Errors ```typescript const handleGameAction = async (action: () => Promise) => { try { const result = await action(); return { success: true, data: result }; } catch (error) { // Handle specific errors if (error.message.includes('Adventurer is dead')) { return { success: false, error: 'GAME_OVER' }; } if (error.message.includes('Insufficient gold')) { return { success: false, error: 'INSUFFICIENT_FUNDS' }; } if (error.message.includes('Invalid stat allocation')) { return { success: false, error: 'INVALID_STATS' }; } // Generic error return { success: false, error: error.message }; } }; ``` #### Retry Logic ```typescript const retryWithBackoff = async ( fn: () => Promise, maxRetries = 3, delay = 1000 ) => { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (i === maxRetries - 1) throw error; await new Promise(resolve => setTimeout(resolve, delay * (i + 1))); } } }; ``` ### Testing Integration #### Unit Tests ```typescript import { describe, it, expect } from 'vitest'; describe('Game Integration', () => { it('should start a new game', async () => { const adventurerId = await startAdventure( mockAccount, WeaponType.Blade, 'Test Hero' ); expect(adventurerId).toBeGreaterThan(0n); }); it('should handle combat', async () => { const result = await attack(mockAccount, adventurerId); expect(result.status).toBe('ACCEPTED'); }); }); ``` #### Integration Tests ```typescript describe('Full Game Flow', () => { it('should complete a full game cycle', async () => { // Start game const id = await startAdventure(account, 0, 'Hero'); // Explore await explore(account, id, false); // Combat const beast = await getCurrentBeast(id); if (beast) { await attack(account, id, false); } // Check state const adventurer = await getAdventurer(id); expect(adventurer.experience).toBeGreaterThan(0); }); }); ``` ### Performance Optimization #### Caching Strategy ```typescript class GameCache { private cache = new Map(); private ttl = 5000; // 5 seconds async get(key: string, fetcher: () => Promise) { const cached = this.cache.get(key); if (cached && Date.now() - cached.timestamp < this.ttl) { return cached.data; } const data = await fetcher(); this.cache.set(key, { data, timestamp: Date.now() }); return data; } } const cache = new GameCache(); const getCachedAdventurer = (id: bigint) => cache.get(`adventurer:${id}`, () => getAdventurer(id)); ``` #### Batch Requests ```typescript const batchGetAdventurers = async (ids: bigint[]) => { const promises = ids.map(id => getAdventurer(id)); return await Promise.all(promises); }; ``` ### Security Best Practices #### Input Validation ```typescript const validateAdventurerId = (id: any): bigint => { if (typeof id !== 'bigint' && typeof id !== 'string') { throw new Error('Invalid adventurer ID type'); } const bigIntId = BigInt(id); if (bigIntId <= 0n) { throw new Error('Adventurer ID must be positive'); } return bigIntId; }; ``` #### Rate Limiting ```typescript class RateLimiter { private requests = new Map(); private limit = 10; // requests per second canMakeRequest(key: string): boolean { const now = Date.now(); const requests = this.requests.get(key) || []; // Remove old requests const recent = requests.filter(t => now - t < 1000); if (recent.length >= this.limit) { return false; } recent.push(now); this.requests.set(key, recent); return true; } } ``` ### Conclusion This integration guide provides the foundation for building applications that interact with Loot Survivor. Whether you're creating a custom frontend, building tools, or extending the game, these patterns and examples will help you get started quickly and build robust integrations. ## Loot System

The loot system in Loot Survivor is inspired by the original Loot collection and forms the backbone of your adventurer's power progression.

Understanding how items work, their tiers, and special properties is essential for survival in Death Mountain. Each piece of equipment can evolve and gain powerful bonuses as you progress through the dangerous depths.

Loot Survivor Items Display Loot item display showing tier and special properties
### Item Properties Each item has several key properties that determine its effectiveness: * **Tier** - Base power level (T1-T5) * **Greatness** - Special enhancement level (0-20) * **Suffix** - Name suffix at Greatness 15+ * **Prefixes** - Additional bonuses at Greatness 19+ * **+1 Modifier** - +1 modifier at Greatness 20 ### Item Tiers Items in Loot Survivor are categorized into 5 tiers, with T1 being the most powerful: | Tier | Name | Rarity | Base Power | Drop Rate | Description | | ------ | --------- | --------- | ------------- | --------- | ------------------------------------------ | | **T1** | Legendary | Highest | 5× multiplier | \~0.066% | Best-in-slot items with maximum base stats | | **T2** | Epic | Very High | 4× multiplier | \~0.2% | Powerful items with excellent stats | | **T3** | Rare | High | 3× multiplier | \~0.4% | Good equipment with solid performance | | **T4** | Uncommon | Medium | 2× multiplier | \~1.0% | Decent upgrades over starting gear | | **T5** | Common | Low | 1× multiplier | \~1.65% | Basic gear and starting equipment | **Tier Effects:** * Higher tiers provide better base damage/protection * Tier multiplier affects combat calculations * Better tiers are more likely to have special properties * All tiers can potentially have suffixes and prefixes ***
**🎮 Loot Survivor Implementation Note:**
Everything below this point represents **Loot Survivor's unique interpretation** of the original Loot collection. The Greatness System, combat types, materials, and their gameplay mechanics are specific to this game's implementation and not part of the base Loot specification.
## Greatness System Greatness is a special property that enhances items beyond their base tier: ### Greatness Levels | Greatness | Enhancement | Special Property | | --------- | --------------- | -------------------------- | | **1-14** | Base item | Standard tier bonuses only | | **15** | Suffix unlocked | +3 stat bonus from suffix | | **16-18** | Enhanced suffix | Suffix bonuses continue | | **19** | Prefix unlocked | Additional stat bonuses | | **20** | Stat Bonus | +1 assignable stat bonus | ### How Greatness Works * Items always start at greatness 1 * Greatness increases the item's overall power * Greatness 15+ items gain [suffix bonuses](/lootsurvivor/loot/suffix-boost) * Greatness 19+ items gain additional prefix [prefixes](/lootsurvivor/loot/prefixes) * Greatness 20 items get a +1 stat modifier *** ## Equipment Types Loot Survivor features 8 equipment slots for different item types: | Equipment Slot | Item Types | Function | | -------------- | ------------------------------------- | -------- | | **Weapon** | Katana, Wand, Club, Short Sword | Weapon | | **Chest** | Divine Robe, Shirt, Demon Husk | Armor | | **Head** | Crown, Cap, Helm | Armor | | **Waist** | Ornate Belt, Sash, Heavy Belt | Armor | | **Foot** | Divine Slippers, Boots, Shoes | Armor | | **Hand** | Divine Gloves, Gloves, Gauntlets | Armor | | **Neck** | Amulet, Pendant, Necklace | Jewelry | | **Ring** | Platinum Ring, Gold Ring, Silver Ring | Jewelry | *** ## Combat Types & Materials Items have dual classifications that affect combat performance: ### Weapon Types **⚔️ Blade Weapons** * Strong vs Hide armor * Weak vs Metal armor * Examples: Katana, Short Sword, Scimitar **🔨 Bludgeon Weapons** * Strong vs Metal armor * Weak vs Cloth armor * Examples: Club, Mace, Warhammer **✨ Magic Weapons** * Strong vs Cloth armor * Weak vs Hide armor * Examples: Wand, Book, Ghost Wand ### Armor Materials **🛡️ Hide Armor** * Resists Blade attacks * Weak to Magic attacks * Examples: Leather armor, Dragon skin **⚡ Metal Armor** * Resists Bludgeon attacks * Weak to Blade attacks * Examples: Chain mail, Plate armor **🧙 Cloth Armor** * Resists Magic attacks * Weak to Bludgeon attacks * Examples: Robes, Shirts *** ## Item Acquisition ### Discovery Methods Items can be obtained through several methods: | Method | Rate | Quality | Notes | | ---------------------- | ------------------- | ----------- | ------------------------------- | | **Exploration** | 3.3% of explores | Random tier | Primary acquisition method | | **Market Purchase** | Level-dependent | Variable | Costs gold, refresh on level up | | **Starting Equipment** | 1 weapon guaranteed | T5 only | Random weapon type | ### Market System * Market refreshes completely on each level up * Available items are randomly generated * Item count increases with adventurer level * CHA stat reduces purchase prices * Market items follow same tier distribution as drops *** ## Item Management ### Inventory System * **8 Equipment Slots** - One for each equipment type * **15 Bag Slots** - Additional storage for backup items * **Auto-equip** - New items automatically equip if slots are empty * **Gold Conversion** - Duplicate items convert to gold if no space ### Item Usage **Equipping Items:** * Drag from bag to equipment slot * Items provide immediate stat bonuses * Combat type/material affects battle performance * Can switch equipment during exploration (not in combat) **Item Stacking:** * Only one item per equipment slot * Identical items convert to gold * Higher tier items typically replace lower tier * Consider suffix/prefix bonuses when deciding *** ## Special Properties ### Suffixes Items with Greatness 15+ gain powerful suffix bonuses. See [Suffix Boost Guide](/lootsurvivor/loot/suffix-boost) for complete details. ### Prefixes Items with Greatness 19+ gain additional prefix bonuses that stack with suffix effects. These same prefixes are also used for named beasts in Beast Mode NFTs. **Complete Prefix & Suffix Reference:** See [Prefixes Guide](/lootsurvivor/loot/prefixes) for the full library of all 69 prefixes and 18 suffixes. *** ## Summary The loot system provides deep character customization through equipment choices, tier progression, and special properties. Understanding how tiers, greatness, suffixes, and prefixes work together allows you to build powerful adventurers capable of surviving Death Mountain's greatest challenges. **Key Loot Principles:** * Higher tiers provide better base power * Greatness 15+ items gain significant suffix bonuses * Greatness 19+ items gain additional prefix bonuses * Combat type and material matter for battle effectiveness * Market refreshes provide new acquisition opportunities each level ## Jewelry Items Jewelry items increase the Adventurer's Luck, which is directly proportional to their critical hit chance. 20 Luck = 20% chance of a critical hit. 100 Luck = 100% chance of a critical hit. The total Luck the Adventurer receives from their jewelry is equal to the total level of all their jewelry, both equipped and in their bag. For example, if you have a level 20 Bronze Ring in your bag and a level 20 Gold Ring equipped, your Luck will be 40. It is possible to achieve 100 Luck and therefore a 100% critical hit chance with enough jewelry items. Jewelry items also provide special abilities, but those only apply when they are equipped. Those special abilities are: | Item | Boost | | ------------- | ------------------------------------------ | | Pendant | 3% increase per level hide armor defence. | | Necklace | 3% increase per level metal armor defence. | | Amulet | 3% increase per level cloth armor defence. | | Bronze Ring | No special ability (economy option). | | Silver Ring | +1 bonus luck per level. | | Gold Ring | 3% per level in gold rewards from beasts. | | Platinum Ring | 3% per level in name match damage bonus. | | Titanium Ring | 3% per level in critical hit damage bonus. | ## Item & Beast Prefixes Prefixes are special name modifiers that appear on high-greatness items (Level 19+) and named beasts. This comprehensive reference covers all 69 prefixes and 18 suffixes used throughout Loot Survivor. ### Prefix System Overview
**Dual Usage:** Prefixes serve two important functions in the game:
* **Item Enhancement**: High-greatness items (19+) gain prefix bonuses for additional stats * **Beast Names**: Named beasts (Level 19+) use these same prefixes for unique identities * **NFT Collection**: Beast Mode NFTs mint with prefix names for collectible variety
> **Cross-System Integration:** The same 69 prefixes create both powerful equipment and collectible named beasts!
### Complete Prefix Library (69 Total)
#### 🌑 Dark Themes (17) 1. Agony 2. Apocalypse 3. Armageddon 4. Blight 5. Blood 6. Corruption 7. Damnation 8. Death 9. Demon 10. Dire 11. Doom 12. Dread 13. Ghoul 14. Gloom 15. Grim 16. Hate 17. Horror
#### 🏆 Power & Creatures (17) 18. Beast 19. Behemoth 20. Dragon 21. Empyrean 22. Fate 23. Golem 24. Honour 25. Kraken 26. Miracle 27. Phoenix 28. Skull 29. Soul 30. Spirit 31. Victory 32. Viper 33. Brood 34. Chimeric
#### 🌊 Elemental & Nature (17) 35. Bramble 36. Brimstone 37. Carrion 38. Corpse 39. Dusk 40. Eagle 41. Gale 42. Maelstrom 43. Sol 44. Storm 45. Tempest 46. Vortex 47. Cataclysm 48. Foe 49. Havoc 50. Onslaught 51. Plague
#### ✨ Mystical & Abstract (18) 52. Glyph 53. Hypnotic 54. Loath 55. Mind 56. Morbid 57. Oblivion 58. Pain 59. Pandemonium 60. Rage 61. Rapture 62. Rune 63. Sorrow 64. Torment 65. Vengeance 66. Woe 67. Wrath 68. **Lights** ⭐ 69. **Shimmering** ⭐
#### Ultra Rare Prefixes **Lights (#68)** and **Shimmering (#69)** are considered the most valuable and rare prefixes in the entire system, making items or beast NFTs with these names extremely sought after by collectors.
### Complete Suffix Library (18 Total)
| ID | Suffix | | -- | ---------- | | 1. | Bane | | 2. | Root | | 3. | Bite | | 4. | Song | | 5. | Roar | | 6. | Grasp | | 7. | Instrument | | 8. | Glow | | 9. | Bender |
| ID | Suffix | | --- | ------- | | 10. | Shadow | | 11. | Whisper | | 12. | Shout | | 13. | Growl | | 14. | Tear | | 15. | Peak | | 16. | Form | | 17. | Sun | | 18. | Moon |
**Suffix Usage:** Only applies to items with Greatness 15+ for stat bonuses. See [Suffix Boost Guide](/lootsurvivor/loot/suffix-boost) for detailed stat effects.
### Usage Examples #### Item Naming Examples
**High-Greatness Item Examples:**
* **"Doom Katana of Power"** - Prefix: Doom, Base: Katana, Suffix: of Power * **"Shimmering Divine Robe of Giants"** - Prefix: Shimmering, Base: Divine Robe, Suffix: of Giants * **"Blood Wand of Skill"** - Prefix: Blood, Base: Wand, Suffix: of Skill
**Beast Naming Examples:**
* **"Apocalypse Dragon"** - Prefix: Apocalypse, Beast: Dragon * **"Lights Kraken"** - Prefix: Lights, Beast: Kraken * **"Storm Phoenix"** - Prefix: Storm, Beast: Phoenix
### Cross-Reference Usage #### When to Use This Page
**Reference Scenarios:** 1. **Item Identification**: Understanding what prefix names mean on your equipment 2. **Beast NFT Hunting**: Identifying valuable prefix combinations for collection 3. **Market Evaluation**: Determining item value based on prefix rarity 4. **Collection Planning**: Targeting specific prefixes for complete sets 5. **Cross-Game Integration**: Understanding naming for Summit game access **Documentation Links:** * Items with prefixes: Reference this page from item evaluation * Named beasts: Reference this page from beast documentation * NFT collecting: Use for understanding collectible variety
### See Also * [Loot System](/lootsurvivor/loot) - Main equipment and tier information * [Suffix Boost Guide](/lootsurvivor/loot/suffix-boost) - Detailed suffix stat effects * [Beast Collectibles](/lootsurvivor/beasts/collectibles) - Named beast NFT system * [Beast Overview](/lootsurvivor/beasts) - Combat types and beast information ## Suffix Boosts When an item reaches Greatness 15, it gains a powerful suffix that provides significant stat bonuses. These suffixes are based on the [16 Orders of Divinity](https://docs.loot.foundation/canonical-principles/loot/the-16-orders) from the original Loot collection and follow canonical loot lore. > **⚡ Power Boost:** Suffix bonuses can make a T3 item with the right suffix more powerful than a base T1 item! ### How Suffixes Work * **Greatness Requirement:** Items must reach Greatness 15 to unlock suffix bonuses * **Random Assignment:** Suffixes are randomly assigned based on the Orders of Divinity * **Permanent Bonuses:** Suffix bonuses are permanent and stack with base item stats * **Lore Integration:** Each suffix follows canonical Loot Foundation principles *** ## Complete Suffix List ### Suffix Bonuses by Order | Item Suffix | Stat Bonuses | Total Bonus | Order Association | | -------------------- | ----------------------------------------- | ----------- | --------------------------------- | | **Of Power** | Strength +3 | +3 | Power-focused enhancement | | **Of Giant** | Vitality +3 | +3 | Health and endurance boost | | **Of Titans** | Strength +2, Charisma +1 | +3 | Balanced physical prowess | | **Of Skill** | Dexterity +3 | +3 | Agility and precision enhancement | | **Of Perfection** | Strength +1, Dexterity +1, Vitality +1 | +3 | Balanced all-around improvement | | **Of Brilliance** | Intelligence +3 | +3 | Mental acuity enhancement | | **Of Enlightenment** | Wisdom +3 | +3 | Spiritual awareness boost | | **Of Protection** | Vitality +2, Dexterity +1 | +3 | Defensive-focused bonuses | | **Of Anger** | Strength +2, Dexterity +1 | +3 | Aggressive combat enhancement | | **Of Rage** | Wisdom +1, Strength +1, Charisma +1 | +3 | Controlled fury bonuses | | **Of Fury** | Vitality +1, Charisma +1, Intelligence +1 | +3 | Passionate intensity boost | | **Of Vitriol** | Intelligence +2, Wisdom +1 | +3 | Sharp mental enhancement | | **Of the Fox** | Dexterity +2, Charisma +1 | +3 | Cunning and agility boost | | **Of Detection** | Wisdom +2, Dexterity +1 | +3 | Awareness and reflexes | | **Of Reflection** | Wisdom +2, Intelligence +1 | +3 | Contemplative wisdom boost | | **Of the Twins** | Charisma +3 | +3 | Social influence enhancement | *** ## Suffix Categories ### Single Stat Focus These suffixes provide maximum bonuses to one primary stat: * **Of Power** - Pure Strength focus for maximum damage * **Of Giant** - Pure Vitality focus for maximum health * **Of Skill** - Pure Dexterity focus for escape mastery * **Of Brilliance** - Pure Intelligence focus for obstacle avoidance * **Of Enlightenment** - Pure Wisdom focus for ambush prevention * **Of the Twins** - Pure Charisma focus for market optimization ### Balanced Combinations These suffixes provide balanced stat distributions: * **Of Perfection** - Equal bonuses across STR/DEX/VIT * **Of Rage** - Equal bonuses across WIS/STR/CHA * **Of Fury** - Equal bonuses across VIT/CHA/INT ### Specialized Builds These suffixes target specific build archetypes: * **Of Titans** - Combat-focused (STR+CHA) * **Of Protection** - Defense-focused (VIT+DEX) * **Of Anger** - Aggressive melee (STR+DEX) * **Of Vitriol** - Mental mastery (INT+WIS) * **Of the Fox** - Agile trader (DEX+CHA) * **Of Detection** - Alert explorer (WIS+DEX) * **Of Reflection** - Wise scholar (WIS+INT) *** ## Suffix Strategy ### Evaluating Suffix Value When comparing items with suffixes, consider: 1. **Your Build Focus** - Does the suffix support your stat priorities? 2. **Current Weaknesses** - Does the suffix shore up weak areas? 3. **Base Tier vs Suffix** - A T3+suffix might beat a base T1 4. **Total Stat Impact** - All +3 bonuses are equally valuable mathematically ### Suffix Synergies **Combat Builds:** * **Of Power** - Maximum damage output * **Of Titans** - Damage + market efficiency * **Of Anger** - Damage + escape capability **Survival Builds:** * **Of Giant** - Maximum health pool * **Of Protection** - Health + escape ability * **Of Perfection** - Balanced survivability **Utility Builds:** * **Of the Fox** - Escape + market efficiency * **Of Detection** - Awareness + agility * **Of Reflection** - Mental mastery *** ## Summary Suffix bonuses transform the loot system from simple tier progression to complex item evaluation. A well-rolled suffix can make any item tier competitive, while the wrong suffix might make you overlook a potentially powerful piece of equipment. **Key Suffix Principles:** * All suffixes provide exactly +3 total stat points * Suffixes unlock at Greatness 15 * Suffix distribution matters more than individual bonuses * Consider your build when evaluating suffix value * Don't automatically dismiss lower tiers with good suffixes ## Combat Guide Combat in Loot Survivor is a turn-based system where preparation meets execution. Every encounter tests your understanding of type advantages, stat optimization, and tactical decision-making. Master the art of battle to survive Death Mountain's deadliest challenges. > **🎯 Combat Philosophy:** Turn-based combat means every action counts. Sometimes survival means avoiding the fight entirely. ### Battle Interface
Loot Survivor Battle Screen
Figure: Battle interface showing adventurer vs beast encounter
*** ## Combat Mechanics ### Turn-Based Combat Flow

⏱️ Combat Turn Order

Combat follows a strict turn-based sequence where adventurers always act first:

  1. Adventurer Turn - Choose your action:
    • ⚔️ Attack the beast
    • 🏃 Attempt to flee
    • 🔄 Switch equipment (beast gets free attack)
  2. Beast Counterattack - If not defeated or fled:
    • Beast automatically attacks back
    • Targets a random armor slot (1 of 5: Head, Chest, Waist, Foot, Hand)
    • Damage reduced by armor in that specific slot
  3. Repeat - Combat continues until victory, defeat, or successful escape
> **💡 Key Insight:** Since beasts attack random armor slots, having balanced armor coverage is more important than stacking defense in one slot! ### Damage System

⚡ Understanding Power

Power is displayed in combat for the base damage before modifiers. It appears as a number on item and beast info showing the raw damage potential.

Power = Weapon Level × (6 - Weapon Tier)

#### Combat Damage Modifiers The full damage amount includes several factors during combat: * **⚔️ Base Damage** - Your Power value (Weapon tier × level formula above) * **🎯 Type Advantage** - +50% when strong, -50% when weak * **💪 Strength Bonus** - +10% damage per STR point * **✨ Critical Hits** - LUCK/100% chance for 100% bonus damage (see [Combat Mechanics](/lootsurvivor/combat) for details) ### Type Advantage System
{/* Brute - Top */}
🔨
Brute
Bludgeon + Metal
{/* Hunter - Bottom Left */}
⚔️
Hunter
Blade + Hide
{/* Magical - Bottom Right */}
Magical
Magic + Cloth
{/* Center circular arrows */} {/* Three arrows showing advantage flow */} {/* Brute to Hunter (pointing bottom left) */} {/* Hunter to Magical (pointing right) */} {/* Magical to Brute (pointing top left) */}
Bludgeon + Metal beats Blade + Hide beats Magic + Cloth beats Bludgeon + Metal
#### Type Advantage Effects **📈 With Advantage:** * **Damage Multiplier:** +50% * **Example:** 20 base damage → 30 damage output * **Visual Cue:** Green damage numbers in combat log **📉 With Disadvantage:** * **Damage Multiplier:** -50% * **Example:** 20 base damage → 10 damage output * **Visual Cue:** Red damage numbers in combat log #### Strategic Equipment Sets
🛡️ Full Armor Sets

Each armor type excels against specific attacks: Metal resists Blade, Hide resists Magic, Cloth resists Bludgeon. But beware - each has a weakness!

⚔️ Offensive Priority

Your weapon choice matters more than armor. Remember: Bludgeon crushes Hide, Blade slices Cloth, Magic pierces Metal. +50% damage is huge!

🎯 Beast Hunting

When hunting specific beasts, matching your full armor set to counter their attacks is powerful. Know your target and gear up accordingly for maximum survivability.

💡 Pro Strategy: Early game, prioritize covering all armor slots with appropriate types over hunting for higher tiers!
*** ## Combat Actions ### ⚔️ Attack Options #### Single Attack Execute one calculated strike against your opponent. **Stat Influences:** * **STR:** +10% damage per point * **LUCK:** Critical hit chance (LUCK/100%) * **Weapon Tier:** Base damage multiplier **Best for:** Controlled combat, testing damage, conserving resources #### Attack Until Death Chain attacks automatically until victory or defeat. **⚠️ High Risk Mode:** * No control once initiated * Fight continues until resolution * Cannot flee mid-sequence **Use when:** * Beast health is very low * You have type advantage * Confident in victory * Emergency/desperate situations ### 🏃 Flee Mechanics **Flee Formula:** ``` Success Rate = (DEX / Level) × 100% ``` | DEX/Level | Success | Level 10 Example | Level 20 Example | | --------- | ------- | ---------------- | ---------------- | | 0.5 | 50% | DEX 5 | DEX 10 | | 0.75 | 75% | DEX 7-8 | DEX 15 | | 1.0 | 100% | DEX 10 | DEX 20 | | 1.5 | 100% | DEX 15 | DEX 30 | **Key Points:** * DEX equal to level = 100% success * Success rate caps at 100% * Failed flee attempts waste a turn * Ambush encounters have no additional penalties ### 🛡️ Equipment Switching You can change your equipment mid-combat to gain type advantages, but the beast gets a free attack when you do. Strategic swapping can turn a losing fight into victory - switch to counter their type for +50% damage or -50% damage reduction. > **💡 Important:** Bundle all gear changes into one action to minimize the number of free attacks the beast gets. Multiple switches still only give the beast one free attack! *** ## Combat Decision Making When entering combat, consider these factors: * **🏃 Flee** - When you have low HP, type disadvantage, or facing a stronger beast * **🔄 Switch Gear** - When you have the wrong equipment type and need advantage * **⚔️ Attack** - When you have good matchup and are confident in victory * **💀 All-Out Attack** - When beast has very low HP and victory is guaranteed *** ## Combat Rewards ### Experience Points (XP)

💰 Victory Rewards

Defeating beasts grants XP and gold based on their power, with bonuses for finding items:

Base Reward Formulas:
Adventurer XP = Beast Power / 2
Item XP = 2 \* Adventurer XP
Gold = Beast Power / 2 (same as XP)
Level Decay:

XP rewards decrease as you level up (2% per level, max 95% reduction)
Gold rewards do NOT decay - always full value!

#### Reward Calculation Examples | Your Level | Beast (T3, Lv10) | Base Rewards | XP After Decay | Gold (No Decay) | Item XP | | ---------- | ---------------- | ------------ | -------------- | --------------- | ------- | | Level 1 | Power: 30 | 15 | 15 XP (0%) | 15 🪙 | 30 XP | | Level 10 | Power: 30 | 15 | 12 XP (-20%) | 15 🪙 | 24 XP | | Level 25 | Power: 30 | 15 | 7 XP (-50%) | 15 🪙 | 15 XP | | Level 50+ | Power: 30 | 15 | 1 XP (-95%) | 15 🪙 | 2 XP | > **💡 Tip:** The XP decay encourages fighting appropriate challenges, but gold rewards stay consistent - making every victory valuable! *** ## Summary Combat success in Loot Survivor comes from understanding type advantages, optimizing your stats, and making tactical decisions. Every battle offers multiple approaches - from switching gear for advantages to knowing when discretion is the better part of valor. **Key Combat Principles:** * Type advantage provides significant damage bonuses/penalties * STR scaling makes strength investment worthwhile for damage dealers * DEX equal to your level guarantees successful escapes * Gear switching can turn losing fights into victories * Sometimes the best fight is the one you avoid ## Exploration Guide Exploration is the heart of Loot Survivor's gameplay loop. Every step into the unknown brings potential rewards and deadly dangers. Understanding the exploration system is key to surviving Death Mountain's treacherous depths. > **🎲 Random Encounters:** Every exploration is a roll of the dice with equal chances for discovery, obstacles, or beasts! ### Exploration Interface
Loot Survivor Exploration Screen
Figure: Exploration interface with adventure log and market
*** ## How Exploration Works When you click "Explore", the game rolls for one of three equally likely outcomes: * **📦 Item Discovery (33.33%)** - Find gold, potions, or equipment * **🪨 Obstacle (33.33%)** - Environmental hazards to overcome * **👹 Beast Encounter (33.33%)** - Mandatory combat encounters ### Item Discovery Breakdown When you hit the 33% item discovery chance, it subdivides into: * **💰 Gold (45% of discoveries)** - Currency for market purchases (\~15% of all explores) * **🧪 Health Potion (45% of discoveries)** - Restores HP when used (\~15% of all explores) * **⚔️ Equipment (10% of discoveries)** - Weapons, armor, jewelry (\~3.3% of all explores) *** ## Discovery Types ### 💰 Gold Discovery **Formula:** `Gold = Random(1 to Adventurer Level)` | Level | Gold Range | Average | | ----- | ---------- | ------- | | 5 | 1-5g | 3g | | 10 | 1-10g | 5.5g | | 20 | 1-20g | 10.5g | | 30 | 1-30g | 15.5g | | 50 | 1-50g | 25.5g | **Key Features:** * Amount scales with your level * Can be saved between explorations * CHA stat reduces market prices * Essential for equipment upgrades * **Maximum capacity: 511 gold** > **💰 Gold Cap:** You can only carry up to 511 gold. Any excess is lost, so spend wisely! ### ⚔️ Equipment Discovery Equipment drops are your primary source of gear upgrades. When you discover loot (3.3% of all explores), the tier distribution is: | Item Tier | Drop Rate | Of All Explores | Description | | ------------------ | --------- | --------------- | --------------- | | **T5 (Common)** | 50% | \~1.65% | Basic gear | | **T4 (Uncommon)** | 30% | \~1.0% | Decent upgrades | | **T3 (Rare)** | 12% | \~0.4% | Good equipment | | **T2 (Epic)** | 6% | \~0.2% | Powerful items | | **T1 (Legendary)** | 2% | \~0.066% | Best in slot | > **Item assigning:** Items are automatically equipped or added to bag if there are free slots. Otherwise, if items are already discovered or there is no space they are converted to gold. ### 🧪 Health Discovery **Formula:** `HP = Random(2 to Level × 2)` | Level | HP Range | Average | % of Max HP | | ----- | -------- | ------- | ----------- | | 5 | 2-10 HP | 6 HP | \~0.6% | | 10 | 2-20 HP | 11 HP | \~1.1% | | 20 | 2-40 HP | 21 HP | \~2.1% | | 30 | 2-60 HP | 31 HP | \~3.0% | | 50 | 2-100 HP | 51 HP | \~5.0% | **Potion Information:** * Found in 15% of all explores * Instantly restores HP when discovered * **Maximum health: 1023 HP** (adventurer cap) > **❤️ Health Cap:** Adventurers have a maximum health of 1023 HP. Health potions cannot exceed this limit. ### ⭐ Experience Gains Exploration provides consistent experience points for leveling up: | XP Source | XP Gained | Scaling | Risk Level | | ------------------ | ----------------------------------- | ------- | ---------- | | **Item Discovery** | 1 | None | Safe | | **Beast Kills** | Beast Level × (Tier Multiplier - 1) | Decayed | High | | **Obstacles** | Same as above | Decayed | Medium | *** ## Beast Encounters
### ⚠️ Mandatory Combat **IMPORTANT:** Discovering a beast during exploration **always locks you into combat**. There is no way to avoid the fight once a beast is encountered. **Combat is Unavoidable:** * Beast encounters force immediate battle * No option to flee before combat begins * You must fight until victory, defeat, or successful flee attempt * See the [Battle Guide](/lootsurvivor/guide/battle) for detailed combat mechanics
### Beast Strength Calculation When a beast is discovered, its level and health are determined by your adventurer level: #### Beast Level Formula ``` Base Level = 1 + Random(0 to Adventurer Level × 3 - 1) Final Level = Base Level + Difficulty Bonus ``` **Difficulty Bonuses by Adventurer Level:** | Adventurer Level | Difficulty Bonus | Level Range | Average Level | | ---------------- | ---------------- | ----------- | ------------- | | **1-19** | +0 | 1-57 | \~29 | | **20-29** | +10 | 11-97 | \~54 | | **30-39** | +20 | 21-137 | \~79 | | **40-49** | +40 | 41-187 | \~114 | | **50+** | +80 | 81-230+ | \~155+ | #### Beast Health Formula ``` Base Health = 1 + Random(0 to Adventurer Level × 20 - 1) Final Health = Base Health + Health Bonus ``` **Health Bonuses by Adventurer Level:** | Adventurer Level | Health Bonus | Health Range | Average Health | | ---------------- | ------------ | ------------ | -------------- | | **1-19** | +10 | 11-390 | \~200 | | **20-29** | +100 | 101-680 | \~390 | | **30-39** | +200 | 201-980 | \~590 | | **40-49** | +400 | 401-1023\* | \~712 | | **50+** | +500 | 501-1023\* | \~762 | \*Capped at maximum health of 1023 HP > **⚠️ Power Spikes:** Beasts get significantly stronger at levels 20, 30, 40, and 50. Plan your upgrades accordingly! > > **❤️ Beast Health Cap:** Like adventurers, beasts have a maximum health of 1023 HP. ### Ambush Disadvantages Beast encounters during exploration catch you off-guard with several penalties: * **⚡ Initiative Loss** - Beast attacks first in combat * **🛡️ No Preparation** - Cannot switch gear before combat begins ### Ambush Avoidance **Ambush Avoidance Formula:** ``` Avoidance Chance = (WIS / Level) × 100% ``` | WIS/Level | Avoidance | Level 10 Example | Level 20 Example | | --------- | --------- | ---------------- | ---------------- | | 0.5 | 50% | WIS 5 | WIS 10 | | 0.75 | 75% | WIS 7-8 | WIS 15 | | 1.0 | 100% | WIS 10 | WIS 20 | | 1.5 | 100% | WIS 15 | WIS 30 | **Key Points:** * WIS equal to level = 100% avoidance * Success rate caps at 100% * Only applies to beast encounters * Does not prevent the encounter entirely > **👁️ Important:** Wisdom helps avoid the ambush penalties, but you will still be locked into combat when a beast is encountered. *** ## Environmental Challenges ### 🪨 Obstacle System Obstacles are unavoidable environmental hazards with three distinct categories: #### Obstacle Types **✨ Magical Obstacles (25 types)** * Examples: Demonic Altar, Vortex of Despair, Cursed Tomb * **Counter:** Hide armor **🗡️ Blade Obstacles (25 types)** * Examples: Pendulum Blades, Poison Darts, Hidden Arrows * **Counter:** Metal armor **🔨 Bludgeon Obstacles (25 types)** * Examples: Collapsing Ceiling, Rolling Boulder, Crushing Walls * **Counter:** Cloth armor #### Obstacle Level Calculation When an obstacle is encountered, its level is determined using the same formula as beast levels: ``` Base Level = 1 + Random(0 to Adventurer Level × 3 - 1) Final Level = Base Level + Difficulty Bonus ``` **Difficulty Bonuses by Adventurer Level:** | Adventurer Level | Difficulty Bonus | Obstacle Level Range | Average Level | | ---------------- | ---------------- | -------------------- | ------------- | | **1-19** | +0 | 1-57 | \~29 | | **20-29** | +10 | 11-97 | \~54 | | **30-39** | +20 | 21-137 | \~79 | | **40-49** | +40 | 41-187 | \~114 | | **50+** | +80 | 81-230+ | \~155+ | > **📊 Note:** Unlike beasts, obstacles don't have health - they deal damage based on their level if not avoided. #### Damage Mitigation | Method | Effect | Requirement | | ----------------- | -------------------- | -------------------- | | **Intelligence** | Complete avoidance | INT ≥ obstacle level | | **Armor Match** | 50% damage reduction | Correct armor type | | **No Protection** | Full damage | No mitigation | *** ## Summary Exploration provides the foundation for your Death Mountain adventure through consistent rewards and progression opportunities. Understanding the 33/33/33 split between discoveries, obstacles, and beasts helps you make informed decisions about when and how to explore. **Key Takeaways:** * Equal chances for all three outcome types * Higher levels = better gold and health discoveries * LUCK influences equipment tier drops * INT helps avoid obstacles, WIS helps avoid ambushes * Choose exploration modes based on your current needs ## Getting Started with Loot Survivor Welcome, adventurer! This guide will help you begin your journey into the depths of Death Mountain. Whether you're new to blockchain gaming or Starknet, we'll get you up and running quickly. > **⚡ Fast Track:** Want to play right now? Click "Practice Mode" and start exploring immediately! ### Game Modes

🎮 Practice Mode

  • ✓ Instant play
  • ✓ No rewards or progression
  • ✓ No wallet needed
Perfect for learning

🏆 Beast Mode

  • ✓ Dungeon Ticket entry
  • ✓ Collectible beasts
  • ✓ Requires wallet setup
Full blockchain experience
> **Recommendation:** Start with Practice Mode to learn the basics, then upgrade to Beast Mode for the full experience. ### Play Anywhere

📱 Fully Mobile Ready

Loot Survivor is designed for seamless gameplay across all devices. Whether you prefer the immersive desktop experience or the convenience of mobile gaming, enjoy the same full-featured adventure anywhere you go.

🖥️

Desktop

AI-powered immersive visuals with detailed UI

Latest AI technology creates stunning visual experiences for deep exploration sessions
📱

Mobile

Pixel-art adventurer for fast-paced gameplay

Classic pixel aesthetics optimized for quick sessions and touch controls
### Main Menu Interface
Loot Survivor Main Menu
Figure: Main menu interface
*** ## Beast Mode Setup > **Ready for the full experience?** Beast Mode offers real blockchain rewards, NFT beasts, and permanent progression. ### Prerequisites **🔐 Wallet Required** * Cartridge Controller * Built for gaming with gasless transactions * Supports social login and multi-platform access **💰 Payment Options** * Multiple crypto tokens accepted (ETH, USDC, STRK, and more) * Credit card payments supported * Variable ticket pricing based on demand ### Wallet Setup Process #### Step 1: Create Controller Wallet
Choose your signup method:
  • 🔐 Social Login - Discord, Google, GitHub
  • 🔑 Passkey - Biometric authentication
  • 💼 Existing Wallet - MetaMask, Phantom, Rabby

Benefits of Controller:
  • Gasless transactions for smooth gameplay
  • Social login convenience
  • Multi-platform access
  • Built specifically for gaming
Controller sign-up interface Controller sign-up interface
#### Step 2: Connect and Purchase
Follow these steps:
  1. 🌐 Visit Game - Navigate to lootsurvivor.io
  2. 🔗 Connect Wallet - Click "Login" and approve connection
  3. 🎟️ Buy Ticket - Purchase dungeon entry with crypto or credit card (see Dungeon Tickets for details)
  4. ⚔️ Start Adventure - Create your adventurer

Pro Tips:
  • Keep some funds for multiple games
  • Ticket prices vary based on demand
  • Your progress saves to the blockchain
Dungeon ticket purchase interface Dungeon ticket purchase interface
*** ## Your First Adventure ### Understanding Your Adventurer When you create a new adventurer, you'll receive: * **⚔️ Starting Weapon** - Random Tier 5 weapon * **📊 Base Stats** - 12 points randomly distributed * **❤️ Health** - 100 HP + VIT bonus ### Core Game Loop The adventure follows a simple cycle: 1. **🔍 Explore** - Search for items, gold, and XP 2. **⚔️ Combat** - Fight or flee from beasts 3. **📈 Level Up** - Allocate stat points 4. **🛍️ Market** - Buy equipment and potions 5. **🔄 Repeat** - Continue deeper into Death Mountain **Goal:** Survive as long as possible! ### Game Interface Overview #### Main HUD Elements | Element | Icon | Description | Location | | --------------- | ---- | ----------------------------- | ------------ | | **Health Bar** | ❤️ | Current HP (100 + VIT bonus) | Top left | | **XP Progress** | ⭐ | Experience toward next level | Below health | | **Gold** | 🪙 | Currency for market purchases | Top right | | **Equipment** | 🛡️ | 8 equipped item slots | Center panel | | **Inventory** | 🎒 | 15 additional storage slots | Right panel | #### Action Controls * **🔍 Explore** - Continue deeper into the dungeon * **⚔️ Attack** - Fight beasts you encounter * **🏃 Flee** - Escape from combat encounters * **📈 Upgrade** - Allocate stat points when available * **🛍️ Market** - Buy equipment and potions *** ## Community & Support Join the Loot Survivor community: * **💬 Discord:** [discord.gg/lootsurvivor](https://discord.gg/Q36rUxS66c) * **🐦 Twitter:** [@lootsurvivor](https://twitter.com/lootsurvivor) * **🐙 GitHub:** [Report Issues](https://github.com/Provable-Games/death-mountain/issues) *** ## Ready to Begin? Every adventure is unique, every death is permanent, and every decision matters. Start with **Practice Mode** to learn the basics, then upgrade to **Beast Mode** for the full blockchain experience! ## Loot Survivor: Guide Welcome to the Loot Survivor guide! Here you'll find everything you need to get started and master the core mechanics. Whether you're a new adventurer or looking to understand the game systems, this guide covers the essential aspects of gameplay. ### Table of Contents * [Getting Started](/lootsurvivor/guide/getting-started): Setting up your wallet and starting your first adventure * [Explore](/lootsurvivor/guide/explore): Navigating the dungeon and discovering challenges * [Battle](/lootsurvivor/guide/battle): Combat mechanics and defeating beasts * [Upgrade](/lootsurvivor/guide/upgrade): Leveling up and stat allocation ### Overview Loot Survivor is a fully onchain dungeon crawler roguelike where every decision could be your last. In this guide, you'll learn: 1. **How to onboard and set up your wallet** for seamless play on Starknet 2. **The core gameplay loop**—from exploring dungeons to battling beasts 3. **How to allocate stats** when leveling up your adventurer 4. **Combat mechanics** including type advantages and battle actions Each section provides clear explanations and visuals to help you understand the game mechanics. Dive in and start your journey! ## Upgrade Guide Leveling up and upgrading your adventurer is a pivotal moment in every Loot Survivor run. Each level brings stat points to allocate and opens the market with fresh items. Master the upgrade system to transform your adventurer from a fragile newcomer into an unstoppable force! > **⚡ Power Spike:** Every level up is an opportunity to dramatically increase your power. Plan your upgrades carefully! ### Upgrade Overview The upgrade system consists of three key components: 1. **Stat Allocation** - Distribute points to enhance your adventurer's capabilities 2. **Market Refresh** - Access new items with each level up 3. **Purchase Decisions** - Buy equipment and potions from the market ### Upgrade Interface
Loot Survivor Upgrade Screen
Figure: Upgrade interface with stat menu
*** ## Stat Allocation ### Stat Point Distribution You receive **1 stat point per level** to allocate among: | Stat | Effect per Point | Primary Use | | -------- | ------------------------------ | -------------------- | | **STR** | +10% attack damage | Combat damage | | **DEX** | Improves flee success | Escaping from beasts | | **VIT** | +15 HP max & current | Health and survival | | **INT** | Aids obstacle avoidance | Avoiding obstacles | | **WIS** | Helps evade beast ambushes | Ambush prevention | | **CHA** | Gold discount on items/potions | Market purchases | | **LUCK** | ❌ Cannot upgrade | Item discovery only | > **🎯 Allocation Note:** Consider focusing on 2-3 primary stats rather than spreading points evenly across all stats. *** ## Market System ### How Markets Generate

🎲 Market Generation Mechanics

Each level up generates a unique market through random selection:

  • 25 Rolls: The game rolls for 25 random items
  • No Duplicates: If the same item is rolled multiple times, it only appears once
  • Variable Size: Markets can have 1-25 items depending on duplicate rolls
  • Level Locked: The same market persists for your entire level
  • Fresh on Level Up: New random items appear when you reach the next level
#### Market Contents Markets can contain: * **⚔️ Weapons** - All tiers and types * **🛡️ Armor** - Head, chest, waist, foot, and hand pieces * **💍 Jewelry** - Rings and necklaces for stat boosts * **🧪 Potions** - Health and other consumables #### Market Strategy * **Check Everything:** With limited rolls, valuable items might be rare * **Plan Purchases:** The market won't refresh until next level * **CHA Benefits:** Higher Charisma reduces all market prices * **Gold Management:** Save for critical upgrades vs. immediate needs > **💡 Pro Tip:** A small market (few items) means many duplicate rolls occurred. Don't expect it to have everything you need! ## Beast Collectibles In **Beast Mode** defeating uncollected named beasts in Loot Survivor will mint an NFT to your wallet. There is a fixed supply of **93,225** uniquely generated Beasts across **75 species** (1,243 variants per species) to be collected.
Warlock Regular Static

Regular Static

Warlock Shiny Static

Shiny Static

Warlock Regular Animated

Regular Animated

Warlock Shiny Animated

Shiny Animated

### Beast Mode NFT System
**NFT Minting:** Defeating uncollected named beasts automatically mints collectible NFTs to your wallet **Beast Mode Features:** * **Named Beast Focus**: Only special named beasts (Level 19+) mint NFTs * **First Victory Bonus**: NFTs only mint on first defeat of each named beast * **Automatic Collection**: No manual claiming - NFTs appear directly in your wallet * **Permanent Ownership**: Each NFT represents a unique named beast victory > **Collect Them All:** Build your personal gallery of defeated legendary named beasts!
### Collection Overview
* **Fully onchain artwork + metadata**: Images and JSON are generated and rendered directly from smart contracts * **Born onchain**: Beasts are earned in Loot Survivor using verifiable randomness; every step is recorded onchain * **Battle‑ready NFTs**: Each Beast mints with combat stats (level, health), type, and tier compatible with gameplay * **Live ranking within species**: Each species tracks ranks #1–#1,243 as they mint; when complete, a King Beast is crowned * **Credibly neutral traits**: Onchain state includes defeat timestamp and the last Adventurer to slay that Beast
#### 🎨 NFT Visual Rarity & Properties
**NFT Visual Rarities:**
| Visual Type | Rarity Chance | Description | Collection Value | | -------------------- | ------------- | -------------------------- | ---------------- | | **Standard** | 89.75% | Regular static beast image | Base | | **Animated** | 5% | Moving/animated beast NFT | Rare | | **Shiny** | 5% | Shimmering visual effects | Rare | | **Animated + Shiny** | 0.25% | Both animated AND shiny | Ultra Rare |
**NFT Metadata (All Beasts Include):**
* **Level & Health**: Combat stats encoded at mint * **Tier & Type**: T1–T5 tier and one of Magical, Hunter, Brute * **Name & Prefix**: Base species name plus optional prefix (e.g., "Doom Kraken") * **Defeat Timestamp**: Onchain record of when the Beast was defeated * **Provenance Fields**: Credibly neutral traits like last slayer enable onchain leaderboards and history
### NFT Minting Requirements
#### Named Beast NFT Mechanics **Level Requirement:** Only beasts level 19+ can gain special names and be collected\ **Name Structure:** \[Prefix] \[Suffix] \[Beast Name] = Unique NFT\ **Example:** "Doom Shadow" Kraken and "Demon Moon" Kraken mint as separate collectibles #### NFT Minting Rules **First Victory Only:** Each named beast can only mint one NFT per wallet\ **Automatic Process:** No manual claiming - NFT appears immediately after victory\ **Unique Metadata:** Each NFT contains beast stats, prefix, and victory timestamp\ **Permanent Record:** NFTs serve as proof of your legendary victories > **Complete Reference:** Visit the loot section for the full categorized list of all 69 prefixes!
### Tier Logo Colors
* **Tier 1 (Legendary)**: Orange logo — most powerful * **Tier 2 (Epic)**: Purple logo * **Tier 3 (Rare)**: Blue logo * **Tier 4 (Uncommon)**: Green logo * **Tier 5 (Common)**: White logo
### Contract & Source Code * [Mainnet Contract](https://voyager.online/nft-contract/0x046da8955829adf2bda310099a0063451923f02e648cf25a1203aac6335cf0e4) * [Source Code](https://github.com/Provable-Games/beasts/tree/v1.0.1) ### NFT Ownership Benefits #### Digital Asset Value * **True Ownership**: NFTs belong to you, not the game * **Transferable Assets**: Trade or sell your named beast NFTs * **Provable Rarity**: Onchain verification of NFT scarcity * **Permanent Record**: Victory achievements stored forever on Starknet *** *The Beasts are a generative art collection where the algorithm is the decisions of the adventurers in the Loot Survivor dungeon.* ### See Also * [Beast Overview](/lootsurvivor/beasts) - Complete beast guide and combat types * [Combat Mechanics](/lootsurvivor/combat) - Damage calculations and combat system * [Battle Guide](/lootsurvivor/guide/battle) - Tactical combat strategies ## Beasts Beasts are the creatures you'll encounter and battle throughout your adventures in Loot Survivor. Understanding beast types, tiers, and combat advantages is crucial for survival and progression. ### Beast Overview
**75 unique beasts** populate Loot Survivor, each with distinct characteristics:
* **3 Combat Types**: Magical, Hunter, and Brute * **5 Tiers**: T1 (Legendary) to T5 (Common) * **Special Names**: High-level beasts gain name prefixes at level 19+ * **Dynamic Generation**: Each encounter uses VRF seeds for true randomness
> **Combat Focus:** Every beast follows the rock-paper-scissors combat system - choose your equipment wisely!
### Beast Tier System
| Tier | Rarity | Power Level | Gold Reward | XP Reward | Examples | | ------ | ----------- | ----------- | ----------- | --------- | ------------------------ | | **T1** | Legendary | Highest | 5× level | Maximum | Warlock, Griffin, Kraken | | **T2** | Rare | High | 4× level | High | Gorgon, Qilin, Titan | | **T3** | Uncommon | Medium | 3× level | Medium | Werewolf, Wyvern, Oni | | **T4** | Common | Low | 2× level | Low | Goblin, Fenrir, Yeti | | **T5** | Very Common | Lowest | 1× level | Minimum | Fairy, Bear, Troll | **Reward Formula:** Gold = (Tier Multiplier × Beast Level) ÷ 2
### Beast Categories #### 🪄 Magical Beasts (25 Total)
**T1 Legendary (5):** Warlock, Typhon, Jiangshi, Anansi, Basilisk\ **T2 Rare (5):** Gorgon, Kitsune, Lich, Chimera, Wendigo\ **T3 Uncommon (5):** Rakshasa, Werewolf, Banshee, Draugr, Vampire\ **T4 Common (5):** Goblin, Ghoul, Wraith, Sprite, Kappa\ **T5 Very Common (5):** Fairy, Leprechaun, Kelpie, Pixie, Gnome
#### 🏹 Hunter Beasts (25 Total)
**T1 Legendary (5):** Griffin, Manticore, Phoenix, Dragon, Minotaur\ **T2 Rare (5):** Qilin, Ammit, Nue, Skinwalker, Chupacabra\ **T3 Uncommon (5):** Weretiger, Wyvern, Roc, Harpy, Pegasus\ **T4 Common (5):** Hippogriff, Fenrir, Jaguar, Satori, Direwolf\ **T5 Very Common (5):** Bear, Wolf, Mantis, Spider, Rat
#### 🔨 Brute Beasts (25 Total)
**T1 Legendary (5):** Kraken, Colossus, Balrog, Leviathan, Tarrasque\ **T2 Rare (5):** Titan, Nephilim, Behemoth, Hydra, Juggernaut\ **T3 Uncommon (5):** Oni, Jotunn, Ettin, Cyclops, Giant\ **T4 Common (5):** Nemeanlion, Berserker, Yeti, Golem, Ent\ **T5 Very Common (5):** Troll, Bigfoot, Ogre, Orc, Skeleton
### Special Beast Names
#### Naming System
High-level beasts (Level 19+) can gain **special name prefixes** that provide:
* **Enhanced Combat Power**: Named beasts are significantly stronger * **Bonus Rewards**: Higher gold and XP rewards * **Collectible Value**: Named beast encounters become permanent achievements
#### Example Names
**Prefix Examples:** Agony, Apocalypse, Armageddon, Beast, Behemoth, Blood, Death, Demon, Doom, Dragon
**Full Named Beast:** "**Doom** **Phoenix**" or "**Blood** **Kraken**"
> **Pro Tip:** Named beasts are dangerous but offer the best rewards and collectible potential!
### Beast Encounter Mechanics
#### Generation System * **VRF Seeds**: True randomness for beast selection (configurable in settings) * **Level Scaling**: Beast power increases with your adventurer level * **Tier Distribution**: Higher tiers become more common at lower levels * **Special Names**: 19+ level beasts can gain name prefixes randomly #### Combat Rewards ``` Gold Reward = (Tier Multiplier × Beast Level) ÷ 2 XP Reward = Minimum 4 + beast tier bonuses ``` **Bonus Multipliers:** * Critical hits from LUCK stat * Name match bonuses (weapon/armor names matching beast names) * Type advantage damage (+50%)
### See Also * [Combat Mechanics](/lootsurvivor/combat) - Detailed damage calculations * [Battle Guide](/lootsurvivor/guide/battle) - Combat tactics and interface * [Loot System](/lootsurvivor/loot) - Equipment and weapons * [Beast Collectibles](/lootsurvivor/beasts/collectibles) - Achievement system * [Wanted Beasts](/lootsurvivor/beasts/wanted-beasts) - Special bounties with STRK rewards ## Wanted Beasts ### Starkware & Cartridge Partnership Loot Survivor has partnered with Starkware and Cartridge to bring an exclusive promotion featuring 100,000 STRK tokens distributed across three legendary Beasts. This collaboration celebrates the launch of SURVIVOR and rewards the most skilled adventurers who can defeat these special creatures. #### Prize Distribution The 100,000 STRK prize pool is split between three unique Beasts, each offering substantial rewards to the adventurer who claims victory:
Torment Bane Balrog

"Torment Bane" Balrog

  • STRK Reward: 33,333 STRK
  • Location: The Molten Depths
  • Special Traits: Ancient demon of fire and shadow, Wielder of eternal flames
Pain Whisper Warlock

"Pain Whisper" Warlock

  • STRK Reward: 33,333 STRK
  • Location: The Shadow Sanctum
  • Special Traits: Master of dark arts, Collector of tormented souls
Demon Grasp Dragon

"Demon Grasp" Dragon

  • STRK Reward: 33,334 STRK
  • Location: The Dragon's Lair
  • Special Traits: Guardian of ancient treasures, Corrupted by demonic influence
#### How It Works 1. **Find the Beast**: These special Beasts spawn randomly in the dungeon 2. **Defeat to Claim**: The first adventurer to defeat each Beast claims the STRK reward 3. **Automatic Distribution**: STRK tokens are automatically sent to the winning adventurer's wallet 4. **One Winner per Beast**: Each Beast can only be defeated once for the reward #### Promotion Period This promotion runs indefinitely. These special Beasts will continue to spawn in the dungeon, offering adventurers ongoing opportunities to claim the STRK rewards. The promotion remains active until all three unique Beasts have been defeated and the full 100,000 STRK has been distributed. #### About Our Partners **Starkware** - The technology company behind Starknet, providing the scalable infrastructure that powers Loot Survivor's fully onchain gameplay. **Cartridge** - The gaming infrastructure platform enabling seamless wallet experiences and player onboarding for Starknet games. This partnership demonstrates the strong ecosystem support for Loot Survivor and commitment to rewarding skilled players with meaningful prizes. ## Applications Platforms and applications that use the Embeddable Game Standard to embed, orchestrate, and distribute games. ### Denshokan Denshokan (伝承館 - "Hall of Legends") is the reference implementation of the Embeddable Game Standard. It is organized as a 6-package workspace and provides the complete stack: * **Token Contract** - ERC721 with all game-components composed (settings, objectives, minter, context, renderer, enumerable) * **Game Registry** - Discovers and catalogs registered games * **Viewer Contract** - Efficient on-chain batch queries and filtering with 30+ query methods * **Indexer** - Apibara-based event indexer writing to PostgreSQL * **REST API** - Hono-based API with filtering, pagination, and WebSocket * **Client** - React frontend with Cartridge Controller integration * **SDK** - `@provable-games/denshokan-sdk` for frontend developers Denshokan includes two reference games (Number Guess and Tic-Tac-Toe) that demonstrate the full game lifecycle from contract to UI. > **Tip:** Use Denshokan as a starting point for your own EGS deployment, or connect to the existing Denshokan instance with the SDK. ### Budokan: Permissionless Tournaments Budokan uses EGS to provide a fully permissionless tournament protocol. With EGS, games no longer need to implement their own leaderboard or revenue logic. Budokan: * Accepts any supported token for entry fees and prizes * Offers extensive customization for tournament formats, entry requirements, and prize structures * Handles all leaderboard, prize, and entry logic on behalf of the game * Mints game tokens for tournament entrants with specific settings and timing ![Budokan Overview](/docs/overview.png) *Figure 1: Budokan overview page.* > **Tip:** By integrating with Budokan, game developers can focus on gameplay while the platform handles tournaments, rewards, and community engagement. ### Eternum: Embedded Mini-Games Eternum, a next-generation MMO, leverages EGS to embed mini-games directly into its core experience. In Season 1, Eternum introduced a quest system powered by EGS: * Seamless integration of third-party mini-games as quests * Emergence of new strategies and gameplay loops * A more dynamic, deconstructed MMO experience ![Eternum Quest](/docs/eternum-quest.png) *Figure 2: Eternum Quest.* Embedding games within Eternum allows mini-games to reach a much wider audience while bringing diverse gameplay experiences to Eternum's core player base. ### Why Build Applications with EGS? * **Diverse game library** - Instantly access every EGS-compliant game * **Easy integration** - A single `mint()` call starts a game; `score()` and `game_over()` validate results * **Automatic callbacks** - Get notified when scores update or games complete via `IMetagameCallback` * **No per-game integration** - The standard interface means one integration works for all games * **Royalties** - Respect game creator royalties automatically via ERC-2981 ### Related Guides * [Embeddable Game Standard Overview](/embeddable-game-standard) * [Building a Platform](/embeddable-game-standard/building-a-platform) * [Budokan Tournaments](/budokan) * [Frontend Integration](/embeddable-game-standard/frontend) import { ContractTable } from "../../components/ContractTable"; ## Architecture > **Source**: [denshokan](https://github.com/Provable-Games/denshokan) — turnkey implementation of the EGS architecture (token contract, registry, indexer, API, and client). The Embeddable Game Standard is built around three contract roles: **Metagame** (the platform), **MinigameToken** (the shared ERC721), and **Minigame** (the game logic). These roles communicate through well-defined interfaces discovered via SRC5. ### System Diagram ``` ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ │ Metagame │──mint──▶│ MinigameToken │◀──reg───│ Registry │ │ (Platform) │ │ (ERC721) │ │ │ │ │◀callback│ │──read──▶│ Minigame │ │ │ │ ┌────────────┐ │ │ (Your Game)│ └─────────────┘ │ │ CoreToken │ │ └─────────────┘ │ │ Minter │ │ │ │ Settings │ │ │ │ Objectives │ │ │ │ Context │ │ │ │ Renderer │ │ │ │ Lifecycle │ │ │ └────────────┘ │ └──────────────────┘ ``` #### MinigameToken (Center) The MinigameToken is an ERC721 contract that composes multiple game-components. Each token represents a single game session. The token ID is a packed `felt252` encoding immutable game data (game ID, settings, timestamps, flags) directly into the ID itself. **Composed components:** * **CoreTokenComponent** - Base lifecycle: mint, update, game over * **MinterComponent** - Tracks who minted each token (platforms, users) * **SettingsComponent** - Stores game setting configurations * **ObjectivesComponent** - Tracks achievement completion * **ContextComponent** - Optional mutable context data * **RendererComponent** - Custom token URI rendering * **SkillsComponent** - Per-token AI agent skills address override * **ERC721EnumerableComponent** - On-chain token iteration #### Minigame (Right) Your game contract implements `IMinigameTokenData` to expose score and game-over status. When the token contract calls `update_game()`, it reads your game's state and syncs it to the token. #### Metagame (Left) A platform contract that mints tokens and optionally receives callbacks. If the minter implements `IMetagameCallback` (discoverable via SRC5), the token automatically dispatches: * `on_game_action(token_id, score)` on every `update_game()` call * `on_game_over(token_id, final_score)` when a game completes * `on_objective_complete(token_id)` when an objective is met #### Registry The registry maps game contracts to metadata (name, description, developer, genre, image, royalties). Games self-register and receive a creator NFT that controls royalty payments. ### Game Lifecycle ``` 1. Setup Game contract deploys and registers with the Registry 2. Mint Platform or user calls mint() on MinigameToken → Creates packed token ID with game_id, settings, timing 3. Play Player interacts with the Minigame contract → Game updates its internal state (score, progress) 4. Sync Anyone calls update_game(token_id) on MinigameToken → Token reads score/game_over from Minigame via IMinigameTokenData → If minter supports IMetagameCallback, dispatches callbacks 5. Complete Game marks game_over = true → Final sync writes permanent result to token ``` ### Component Relationships | Component | Depends On | Purpose | | ------------------- | ----------------------------------- | --------------------------------------------- | | CoreTokenComponent | ERC721, SRC5 | Base mint/update/lifecycle | | MinterComponent | CoreToken | Track minting addresses with incrementing IDs | | SettingsComponent | Minigame (IMinigameTokenSettings) | Store named setting configurations | | ObjectivesComponent | Minigame (IMinigameTokenObjectives) | Track named objectives and completion | | ContextComponent | Metagame (IMetagameContext) | Store per-token context from platform | | RendererComponent | - | Delegate token URI to external contract | | SkillsComponent | - | Per-token AI agent skills address override | ### SRC5 Interface Discovery EGS uses [SRC5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) (Starknet's equivalent of ERC-165) for runtime interface detection. Before calling an interface method, contracts check `supports_interface(interface_id)`. This enables safe, optional integration: a token can check if a minter supports callbacks before dispatching, and a platform can check if a game supports settings before querying them. Interface IDs are defined alongside their traits in the [game-components](https://github.com/Provable-Games/game-components) source. See the [SRC5 reference →](/embeddable-game-standard/advanced/src5) for a full listing. ### Deployed Contracts :::note Mainnet contracts are coming soon. The addresses below are for **Sepolia testnet**. ::: #### Denshokan Token = `minigame_token_address` The **Denshokan Token** contract above is the shared MinigameToken (ERC721) that all EGS games can use. Whenever you see `minigame_token_address` in the docs or in code (e.g. the game initializer, `pre_action`, `post_action`), this is the address to pass. Using the shared Denshokan contract gives you: * **Indexing & API** — your game is automatically indexed by the Denshokan indexer, giving you REST API, WebSocket, and SDK access out of the box * **Registry integration** — your game appears in the shared game registry alongside all other EGS games * **Platform compatibility** — any EGS platform (tournaments, quests, arcades) can mint tokens for your game immediately * **Token rendering** — default SVG rendering and metadata for all tokens You can deploy your own MinigameToken contract if you need custom behavior, but most games should use the shared Denshokan contract. ### Data Flow ``` ┌─────────────────────────────────────────┐ │ Starknet Blockchain │ │ │ mint() ──────────▶│ MinigameToken ◀──── Minigame │ update_game() ───▶│ │ │ │ │ ▼ │ │ │ Events (Transfer, │ │ │ ScoreUpdate, │ │ │ GameOver) │ │ └───────┬──────────────────┘ │ │ ┌───────▼───────┐ │ Indexer │ (Apibara) │ ┌───────┐ │ │ │ Decode│ │ │ │ Events│ │ │ └───┬───┘ │ └───────┼──────┘ │ ┌───────▼───────┐ │ PostgreSQL │ └───────┬───────┘ │ ┌───────────┼───────────┐ │ │ │ ┌──────▼─────┐ ┌──▼──┐ ┌──────▼──────┐ │ REST API │ │ WS │ │ RPC (direct) │ └──────┬─────┘ └──┬──┘ └──────┬───────┘ │ │ │ ┌──────▼───────────▼───────────▼──────┐ │ denshokan-sdk │ │ (DenshokanClient + React hooks) │ └─────────────────────────────────────┘ ``` The SDK provides three data paths: 1. **REST API** - Indexed data with filtering, pagination, and aggregation 2. **WebSocket** - Real-time event subscriptions (scores, mints, game over) 3. **RPC** - Direct contract reads for on-chain truth (ERC721 methods, batch queries) Smart fallback: if the API is unavailable, the SDK automatically falls back to RPC. ## Embeddable Game Standard: FAQ Developer-oriented FAQ for the Embeddable Game Standard (EGS) on Starknet. For deeper dives, follow the links to the relevant guide pages. ### General **What is the Embeddable Game Standard?** An open protocol for building composable, provable on-chain games on Starknet. It defines how games expose their logic, how platforms mint and manage game tokens, and how frontends display real-time game state. See the [Overview](/embeddable-game-standard). **What problem does EGS solve?** On-chain games are silos. Each game reinvents token minting, score tracking, and settings management. Platforms that want to embed multiple games must write custom integration code for each one. EGS provides a common interface so games can be listed in tournaments, embedded as quests, and tracked across platforms without per-game integration work. **What is Denshokan?** Denshokan is the turnkey implementation of EGS — a deployed token contract, registry, indexer, API, and client. If you don't want to deploy your own token contract, you can register your game with the existing Denshokan deployment and start minting immediately. **What is game-components?** The [game-components](https://github.com/Provable-Games/game-components) library provides modular Cairo components (MinigameComponent, MetagameComponent, SettingsComponent, etc.) that you compose into your contracts. See the [Reference](/embeddable-game-standard/reference). **What is the denshokan-sdk?** A TypeScript SDK with React hooks and WebSocket subscriptions for querying games, tokens, players, and activity. See [Frontend Integration](/embeddable-game-standard/frontend). **What games currently use EGS?** Dark Shuffle, Loot Survivor, zKube, Nums, and Dope Wars all implement the EGS interface. See [Games](/embeddable-game-standard/games). ### Building a Game **What's the minimum I need to implement to make my game EGS-compliant?** 1. Implement `IMinigameTokenData` — expose `score()` and `game_over()` for your game 2. Register `IMINIGAME_ID` via SRC5 3. Register with the game Registry 4. Call `assert_token_ownership`, `pre_action`, and `post_action` in every player-facing action See the [Quick Start](/embeddable-game-standard/building-a-game/quick-start) for a full walkthrough. **What are `pre_action` and `post_action`?** `pre_action` validates that a token is playable (not game over, not expired, objective not already completed). `post_action` syncs your game's score and game\_over state back to the token contract. Every player-facing action must call both. See [Lifecycle & Playability](/embeddable-game-standard/building-a-game/lifecycle). **Why do I need `assert_token_ownership`?** A player should never be able to act on a token they don't own. This should be the first check in every action function, before `pre_action`. **My game has multiple contracts. How do I call pre\_action/post\_action from other contracts?** Import the library functions directly and pass the denshokan token address: ```cairo use game_components_embeddable_game_standard::minigame::minigame::{ assert_token_ownership, pre_action, post_action, }; fn action(ref self: ContractState, token_id: felt252) { let token_address = self.minigame_token_address.read(); assert_token_ownership(token_address, token_id); pre_action(token_address, token_id); // ... game logic ... post_action(token_address, token_id); } ``` Only the contract initialized with the MinigameComponent can use `self.minigame.pre_action(token_id)`. See the [Quick Start](/embeddable-game-standard/building-a-game/quick-start#multi-contract-games-library-functions). **What happens if I forget to call `post_action`?** The token contract won't know when scores change or games end. Score updates won't be emitted, game\_over transitions won't be recorded, and platform callbacks won't fire. **Are settings and objectives required?** No. Settings (difficulty modes) and objectives (achievements) are optional extensions. Your game works without them. See [Settings & Objectives](/embeddable-game-standard/building-a-game/settings-and-objectives). **How does scoring work?** Your contract implements `score()` and `game_over()`. The token contract reads these via `post_action` and emits `ScoreUpdate` events. You define the scoring formula — EGS doesn't prescribe one. See [Score & Game Over](/embeddable-game-standard/building-a-game/score-and-game-over). **Should `game_over` be reversible?** No. Once `game_over()` returns true, it should stay true. The token contract treats this as a permanent state transition. Reversing it would break platform integrations and tournament results. ### Token Lifecycle **How are game tokens minted?** A platform (or the player directly) calls `mint()` on the token contract, specifying the game address, optional settings, start/end times, and other parameters. The token ID is a packed felt252 encoding all this metadata. **How does the lifecycle (start/end) work?** The minter provides `start` and `end` as absolute Unix timestamps. The contract converts these to relative delays for packing into the token ID. A token is playable when the current time is between start and end. `start=0` means immediately playable, `end=0` means never expires. See [Lifecycle & Playability](/embeddable-game-standard/building-a-game/lifecycle). **What are packed token IDs?** Token IDs encode game metadata (game ID, minter, settings, lifecycle, objective, flags) into a single felt252. This allows reading token properties without any storage lookups or RPC calls. See [Packed Token IDs](/embeddable-game-standard/advanced/packed-token-ids). **What error do I get if a token isn't playable?** Specific messages for each case: * `"Token is not playable - game is over"` * `"Token is not playable - objective already completed"` * `"Token is not playable - game has not started (now={}, start={})"` * `"Token is not playable - game has expired (now={}, end={})"` ### Building a Platform **What is a Metagame?** A platform contract (tournament system, quest platform, social app) that mints game tokens and orchestrates gameplay. It uses the MetagameComponent to track its token contract and receive callbacks. See [Building a Platform](/embeddable-game-standard/building-a-platform). **How do callbacks work?** When `post_action` updates a token, the token contract checks if the minter supports `IMetagameCallback` via SRC5. If so, it dispatches `on_game_action`, `on_game_over`, or `on_objective_complete`. The `MetagameCallbackComponent` handles caller verification automatically — you just implement the `MetagameCallbackHooksTrait`. See [Callbacks](/embeddable-game-standard/building-a-platform/callbacks). **Do I need to verify the caller in my callback implementation?** No. The `MetagameCallbackComponent` enforces `assert_only_token` automatically before delegating to your hooks. You just implement the business logic. **What is the Registry?** A contract that maps game addresses to incrementing IDs, enabling multi-game token contracts. Games register once, and the registry provides discovery for platforms. See [Registry & Discovery](/embeddable-game-standard/building-a-platform/registry). **What platforms currently use EGS?** * **Budokan**: Permissionless tournament protocol. Any EGS game can be used in tournaments with custom entry fees and prize pools. * **Eternum**: MMO that embeds EGS games as quests, extending gameplay through mini-game integrations. ### Frontend **How do I query game data?** Use the `denshokan-sdk` to query games, tokens, players, and activity through the Denshokan API. The SDK provides typed responses and handles pagination. See [denshokan-sdk](/embeddable-game-standard/frontend/sdk). **How do I get real-time updates?** Subscribe via WebSocket hooks (`useScoreUpdates`, `useGameActivity`, etc.) for live score changes, game completions, and minting events. See [WebSocket Subscriptions](/embeddable-game-standard/frontend/websockets). **Can I decode token IDs client-side?** Yes. The SDK provides `decodeTokenId()` which unpacks the felt252 into all its fields (game ID, minter, settings, lifecycle, etc.) without any RPC calls. See [Types Reference](/embeddable-game-standard/frontend/types). ### More Resources * [Quick Start](/embeddable-game-standard/building-a-game/quick-start) — Build a Number Guess game step by step * [Architecture](/embeddable-game-standard/architecture) — How the layers fit together * [game-components Reference](/embeddable-game-standard/reference) — Interface and struct documentation * [Games](/embeddable-game-standard/games) — Live games built with EGS * [Applications](/embeddable-game-standard/applications) — Platforms using EGS import { GameCard } from "../../components/GameCard"; ## Games Games that implement the Embeddable Game Standard. Each game exposes `IMinigameTokenData` and can be embedded in any EGS-compatible platform. ### Why Build Games with EGS? * **Instant distribution**: Your game is automatically available in any EGS platform (Budokan, Denshokan, and more) * **Focus on gameplay**: The standard handles token minting, score tracking, and metadata - you write game logic * **Permissionless settings**: Anyone can create custom difficulty modes for your game * **Community objectives**: Players and organizers can define their own achievement goals * **Royalties**: Earn royalties on secondary sales via ERC-2981 with dynamic receiver resolution ### Getting Started See the [Building a Game](/embeddable-game-standard/building-a-game) guide to make your game EGS-compatible. ## Embeddable Game Standard The Embeddable Game Standard (EGS) is an open protocol for building composable, provable on-chain games on Starknet. It defines how games expose their logic, how platforms mint and manage game tokens, and how frontends display real-time game state. For a high-level overview of why EGS matters, see the [EGS presentation deck](https://provable.games/decks/egs/index.html). ### The Problem On-chain games are silos. Each game reinvents token minting, score tracking, settings management, and leaderboard logic. Platforms that want to embed multiple games must write custom integration code for each one. There's no standard way for a tournament system, a quest platform, or a social app to discover, launch, and validate games. ### Three Pillars EGS is built on three layers: | Layer | What It Is | Who Uses It | | ------------------------------------------------------------ | -------------------------------------------------------------------------- | ---------------------------------------------- | | [**game-components**](/embeddable-game-standard/reference) | Modular Cairo component library for Starknet | Game and platform developers writing contracts | | [**Denshokan**](https://github.com/Provable-Games/denshokan) | Turnkey implementation: token contract, registry, indexer, API, and client | Teams that want a ready-made deployment | | [**denshokan-sdk**](/embeddable-game-standard/frontend) | TypeScript SDK with React hooks and WebSocket subscriptions | Frontend developers integrating game data | ### What You Can Build **Games** that are automatically compatible with any EGS platform: * Implement a single trait (`IMinigameTokenData`) to expose score and game-over status * Optionally add settings (difficulty modes), objectives (achievements), and custom metadata * Your game becomes mintable, trackable, and embeddable anywhere **Platforms** that aggregate and orchestrate games: * Mint game tokens on behalf of users with a single `mint()` call * Receive automatic callbacks when scores update or games complete * Run tournaments, quests, and reward systems across any EGS-compliant game **Frontends** that display real-time game data: * Query games, tokens, players, and activity through a unified API * Subscribe to live score updates and game completions via WebSocket * Decode packed token IDs client-side for instant display without RPC calls ### Architecture at a Glance ``` Metagame ──→ MinigameToken (ERC721) ──→ Minigame │ ▲ │ │ │ │ └── Registry ├── Settings (optional) │ │ └── Objectives (optional) │ └── IMetagameCallback (on_game_action, on_game_over, on_objective_complete) └── Context (optional) ``` **Game Lifecycle**: Setup → Mint → Play → Sync (`update_game()`) → Complete (`game_over()`) [Learn more about the architecture →](/embeddable-game-standard/architecture) ### AI-Assisted Development with Context7 All EGS repositories are indexed on [Context7](https://context7.com) and can be queried by AI coding assistants (Claude, Cursor, Copilot, etc.) for up-to-date documentation and code examples. | Library | Context7 ID | Snippets | | -------------------------------------------------------------------- | --------------------------------- | -------- | | [This documentation](https://github.com/Provable-Games/docs) | `/provable-games/docs` | 1289 | | [game-components](https://github.com/Provable-Games/game-components) | `/provable-games/game-components` | 183 | | [denshokan](https://github.com/Provable-Games/denshokan) | `/provable-games/denshokan` | 270 | | [denshokan-sdk](https://github.com/Provable-Games/denshokan-sdk) | `/provable-games/denshokan-sdk` | 80 | #### Claude Code Context7 is available as an MCP server. Add it to your Claude Code config: ```bash claude mcp add context7 -- npx -y @upstash/context7-mcp ``` Then ask questions referencing the library — for example: *"Using `/provable-games/game-components`, how do I implement IMinigameTokenData?"* #### OpenAI Codex Add Context7 to your `codex.md` or system prompt: ``` When working with EGS, fetch up-to-date docs via Context7 MCP using these library IDs: - /provable-games/docs - /provable-games/game-components - /provable-games/denshokan - /provable-games/denshokan-sdk ``` #### Other tools Any tool that supports MCP servers (Cursor, Windsurf, etc.) can use Context7. See the [Context7 docs](https://context7.com) for setup instructions. ### Quick Links | I want to... | Start here | | -------------------------------------- | --------------------------------------------------------------------------------------------------- | | Build a game | [Building a Game](/embeddable-game-standard/building-a-game) | | Build a platform that embeds games | [Building a Platform](/embeddable-game-standard/building-a-platform) | | Integrate game data in my frontend | [Frontend Integration](/embeddable-game-standard/frontend) | | Look up a specific interface or struct | [game-components Reference](/embeddable-game-standard/reference) | | See working examples | [Games](/embeddable-game-standard/games) and [Applications](/embeddable-game-standard/applications) | | Understand packed token IDs or SRC5 | [Advanced Topics](/embeddable-game-standard/advanced) | ## game-components Reference > **Source**: [game-components](https://github.com/Provable-Games/game-components) The `game-components` library provides modular Cairo components for building EGS-compliant contracts on Starknet. Each component is a self-contained unit that can be composed into your contract. ### Package Overview | Package | Crate Name | Purpose | | ------------------------------ | ------------------------------------------ | ---------------------------------------------------------------------------- | | **interfaces** | `game_components_interfaces` | All trait definitions, structs, and interface IDs | | **embeddable\_game\_standard** | `game_components_embeddable_game_standard` | Token, Minigame, Metagame, Registry components | | **metagame** | `game_components_metagame` | Leaderboard, registration, entry gating, entry fees, prizes, ticket booth | | **economy** | `game_components_economy` | Tokenomics: buyback (Ekubo TWAMM), stream token | | **utilities** | `game_components_utilities` | Math, distribution, encoding, SVG rendering | | **presets** | `game_components_presets` | Ready-to-deploy contracts: LeaderboardPreset, AutonomousBuyback, StreamToken | ### Dependency Graph ``` interfaces ◀─── embeddable_game_standard ▲ ▲ │ │ ├── metagame ├── utilities ├── economy └── presets └── utilities ``` All packages depend on `interfaces` for trait definitions. The `embeddable_game_standard` package is the most complex, composing Token, Minigame, Metagame, and Registry components. ### Installation Add the packages you need to your `Scarb.toml`: ```toml [dependencies] starknet = "2.15.1" # Core - Token, Minigame, Metagame, Registry game_components_embeddable_game_standard = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } # Leaderboard, registration, entry gating, entry fees, prizes game_components_metagame = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } # Tokenomics: buyback, stream token game_components_economy = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } # Math, distribution, encoding, SVG rendering game_components_utilities = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } # Ready-to-deploy contracts game_components_presets = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } # OpenZeppelin (required) openzeppelin_token = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v3.0.0" } openzeppelin_introspection = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v3.0.0" } openzeppelin_access = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v3.0.0" } ``` ### Reference Pages #### Core Components * [MinigameToken (ERC721)](/embeddable-game-standard/reference/token) — The core token contract with packed IDs and component composition * [Token Extensions](/embeddable-game-standard/reference/token-extensions) — Minter, Renderer, Skills, Settings, Objectives, and Context extensions * [MinigameComponent](/embeddable-game-standard/reference/minigame) — Game logic foundation: `IMinigameTokenData`, settings, objectives * [MetagameComponent](/embeddable-game-standard/reference/metagame) — Platform foundation: context, callbacks * [RegistryComponent](/embeddable-game-standard/reference/registry) — Game registration, discovery, and metadata #### Metagame Package * [Metagame Extensions](/embeddable-game-standard/reference/metagame-extensions) — Leaderboard, Registration, Entry Requirements, Entry Fees, Prizes ## Metagame Extensions The `game_components_metagame` package provides components for competitive features: leaderboards, registration, entry gating, entry fees, and prizes. These are separate from the core EGS `embeddable_game_standard` package and used by platform contracts like Budokan. ```toml [dependencies] game_components_metagame = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } ``` ### Leaderboard Multi-context leaderboard that supports multiple simultaneous rankings (tournaments, seasons, etc.). #### ILeaderboard ```cairo pub const ILEADERBOARD_ID: felt252 = 0x381684b12c5a80d08222695ee5eca750b99e56cb53101a47378c542859907e1; #[starknet::interface] pub trait ILeaderboard { fn submit_score( ref self: TState, context_id: u64, token_id: felt252, score: u64, position: u32, ) -> LeaderboardResult; fn get_entries(self: @TState, context_id: u64) -> Array; fn get_top_entries(self: @TState, context_id: u64, count: u32) -> Array; fn get_position(self: @TState, context_id: u64, token_id: felt252) -> Option; fn qualifies(self: @TState, context_id: u64, score: u64) -> bool; fn is_full(self: @TState, context_id: u64) -> bool; fn get_leaderboard_length(self: @TState, context_id: u64) -> u32; fn get_config(self: @TState, context_id: u64) -> LeaderboardStoreConfig; } ``` #### ILeaderboardAdmin ```cairo #[starknet::interface] pub trait ILeaderboardAdmin { fn configure( ref self: TState, context_id: u64, max_entries: u32, ascending: bool, game_address: ContractAddress, ); fn clear(ref self: TState, context_id: u64); fn owner(self: @TState) -> ContractAddress; fn transfer_ownership(ref self: TState, new_owner: ContractAddress); } ``` #### IGameDetails Game contracts implement this so the leaderboard can read scores: ```cairo #[starknet::interface] pub trait IGameDetails { fn score(self: @TState, token_id: felt252) -> u64; } ``` #### Leaderboard Data Structures ```cairo #[derive(Drop, Serde, Copy)] pub struct LeaderboardEntry { pub id: felt252, pub score: u64, } #[derive(Drop, Serde, Copy)] pub struct LeaderboardConfig { pub max_entries: u32, pub ascending: bool, pub allow_ties: bool, } #[derive(Drop, Serde, Copy)] pub struct LeaderboardStoreConfig { pub max_entries: u32, pub ascending: bool, pub game_address: ContractAddress, } #[derive(Drop, Serde, Copy)] pub enum LeaderboardResult { Success, InvalidPosition, LeaderboardFull, ScoreTooLow, ScoreTooHigh, DuplicateEntry, InvalidConfig, } ``` ### Registration Tracks entries (participants) per context. Used by tournament contracts to manage who has entered. #### IRegistration ```cairo #[starknet::interface] pub trait IRegistration { fn get_entry(self: @TState, context_id: u64, entry_id: u32) -> Registration; fn entry_exists(self: @TState, context_id: u64, entry_id: u32) -> bool; fn is_entry_banned(self: @TState, context_id: u64, entry_id: u32) -> bool; fn get_entry_count(self: @TState, context_id: u64) -> u32; } ``` #### Registration Struct ```cairo #[derive(Copy, Drop, Serde)] pub struct Registration { pub context_id: u64, pub entry_id: u32, pub game_token_id: felt252, pub has_submitted: bool, pub is_banned: bool, } ``` ### Entry Requirements Gates entry to contexts based on token ownership, allowlists, or custom extension logic. #### IEntryRequirement ```cairo pub const IENTRY_REQUIREMENT_ID: felt252 = 0x153355c05540b99cd8196c632c72891a6039efdc0b4097d8161fbeed3809182; #[starknet::interface] pub trait IEntryRequirement { fn get_entry_requirement(self: @TState, context_id: u64) -> Option; fn get_qualification_entries( self: @TState, context_id: u64, proof: QualificationProof, ) -> QualificationEntries; } ``` #### Entry Requirement Data Structures ```cairo #[derive(Copy, Drop, Serde, PartialEq)] pub struct EntryRequirement { pub entry_limit: u32, pub entry_requirement_type: EntryRequirementType, } #[derive(Copy, Drop, Serde, PartialEq)] pub enum EntryRequirementType { token: ContractAddress, // Must hold an NFT from this contract allowlist: Span, // Must be on the allowlist extension: ExtensionConfig, // Custom gating logic } #[derive(Copy, Drop, Serde, PartialEq)] pub enum QualificationProof { NFT: NFTQualification, Address: ContractAddress, Extension: Span, } #[derive(Copy, Drop, Serde)] pub struct QualificationEntries { pub context_id: u64, pub qualification_proof: QualificationProof, pub entry_count: u32, } #[derive(Copy, Drop, Serde, PartialEq, starknet::Store)] pub struct NFTQualification { pub token_id: u256, } ``` ### Entry Fees Manages fee collection and distribution for paid contexts (tournaments with buy-ins). #### IEntryFee ```cairo pub const IENTRY_FEE_ID: felt252 = 0x2386e28587616f249621004456b4ed72932c1f731b2a732d87d8a722ee739a1; #[starknet::interface] pub trait IEntryFee { fn get_entry_fee(self: @TState, context_id: u64) -> Option; } ``` #### Entry Fee Data Structures ```cairo #[derive(Drop, Serde, PartialEq)] pub struct EntryFeeConfig { pub token_address: ContractAddress, // ERC20 token for payment pub amount: u128, // Fee amount pub game_creator_share: Option, // Basis points (10000 = 100%) pub refund_share: Option, // Refunded to each depositor pub additional_shares: Span, } #[derive(Copy, Drop, Serde, PartialEq)] pub struct AdditionalShare { pub recipient: ContractAddress, pub share_bps: u16, // Basis points (10000 = 100%) } ``` Fee distribution order: 1. `game_creator_share` goes to the game creator 2. `refund_share` is returned to each depositor 3. `additional_shares` go to specified recipients 4. Remainder is distributed to winners based on the `Distribution` strategy #### Distribution ```cairo #[derive(Drop, Copy, Serde, PartialEq)] pub enum Distribution { Linear: u16, // Linear decay with step size Exponential: u16, // Exponential decay with base Uniform, // Equal split Custom: Span, // Custom basis point shares per position } ``` ### Prizes Manages ERC20 and ERC721 prize pools for contexts. #### IPrize ```cairo pub const IPRIZE_ID: felt252 = 0x2a7a3be3dafc2154ab2780a63f0457adc535ad295bc44ce46cc3fbb11019641; #[starknet::interface] pub trait IPrize { fn get_prize(self: @TState, prize_id: u64) -> PrizeData; fn get_total_prizes(self: @TState) -> u64; fn is_prize_claimed(self: @TState, context_id: u64, prize_type: PrizeType) -> bool; } ``` #### Prize Data Structures ```cairo #[derive(Drop, Serde)] pub struct PrizeData { pub id: u64, pub context_id: u64, pub token_address: ContractAddress, pub token_type: TokenTypeData, pub sponsor_address: ContractAddress, } #[derive(Drop, Serde)] pub struct PrizeConfig { pub token_address: ContractAddress, pub token_type: TokenTypeData, } #[derive(Drop, Serde)] pub enum TokenTypeData { erc20: ERC20Data, erc721: ERC721Data, } #[derive(Drop, Serde)] pub struct ERC20Data { pub amount: u128, pub distribution: Option, pub distribution_count: Option, } #[derive(Copy, Drop, Serde, starknet::Store)] pub struct ERC721Data { pub id: u128, } #[derive(Copy, Drop, Serde, PartialEq)] pub enum PrizeType { Single: u64, // Single winner prize (prize_id) Distributed: (u64, u32), // Multi-winner prize (prize_id, position) } ``` ## MetagameComponent The MetagameComponent is the foundation for platform contracts that integrate with EGS games. It provides context management, game state callbacks, and the base interface that platforms embed. ### Core Interface: IMetagame ```cairo pub const IMETAGAME_ID: felt252 = 0x7997c74299c045696726f0f7f0165f85817acbb0964e23ff77e11e34eff6f2; #[starknet::interface] pub trait IMetagame { fn context_address(self: @TContractState) -> ContractAddress; fn default_token_address(self: @TContractState) -> ContractAddress; } ``` * `context_address` — returns the address of the contract providing context data for tokens * `default_token_address` — returns the default MinigameToken contract address this platform uses ### Context Extension Platforms can attach context data to tokens. The token contract checks for this interface via SRC5 before reading context. #### IMetagameContext ```cairo pub const IMETAGAME_CONTEXT_ID: felt252 = 0x1633419b5abcc4c0bbed8bd37a363fbe6de5bd25908761ab6dcda6a9b598ca9; #[starknet::interface] pub trait IMetagameContext { fn has_context(self: @TState, token_id: felt252) -> bool; } ``` #### IMetagameContextDetails ```cairo #[starknet::interface] pub trait IMetagameContextDetails { fn context_details(self: @TState, token_id: felt252) -> GameContextDetails; } ``` #### IMetagameContextSVG ```cairo #[starknet::interface] pub trait IMetagameContextSVG { fn context_svg(self: @TState, token_id: felt252) -> ByteArray; } ``` ### Callback Extension Metagame contracts can receive automatic callbacks from token contracts when game state changes. This enables tournament score aggregation without manual syncing. #### IMetagameCallback ```cairo pub const IMETAGAME_CALLBACK_ID: felt252 = 0x3b4312c1422de8c35936cc79948381ab8ef9fd083d8c8e20317164690aa1600; #[starknet::interface] pub trait IMetagameCallback { /// Called on every update_game() call fn on_game_action(ref self: TState, token_id: u256, score: u64); /// Called when game_over transitions to true fn on_game_over(ref self: TState, token_id: u256, final_score: u64); /// Called when the objective is completed fn on_objective_complete(ref self: TState, token_id: u256); } ``` The token contract checks if the minter supports `IMETAGAME_CALLBACK_ID` via SRC5 before dispatching callbacks. See [Callbacks & Automation](/embeddable-game-standard/building-a-platform/callbacks) for integration details. ### Data Structures #### GameContextDetails ```cairo #[derive(Drop, Serde, Clone)] pub struct GameContextDetails { pub name: ByteArray, pub description: ByteArray, pub id: Option, pub context: Span, } ``` #### GameContext ```cairo #[derive(Drop, Serde, Copy, Clone)] pub struct GameContext { pub name: felt252, pub value: felt252, } ``` ## MinigameComponent The MinigameComponent is the foundation that every game contract embeds. It handles registration with the game registry, initialization, and provides the interface that the token contract calls to read game state. ### Core Interface: IMinigameTokenData Every game **must** implement this trait: ```cairo #[starknet::interface] pub trait IMinigameTokenData { fn score(self: @TState, token_id: felt252) -> u64; fn game_over(self: @TState, token_id: felt252) -> bool; fn score_batch(self: @TState, token_ids: Span) -> Array; fn game_over_batch(self: @TState, token_ids: Span) -> Array; } ``` Interface ID: `IMINIGAME_ID` - must be registered via SRC5. ### Optional Interfaces #### IMinigameDetails Rich game state for display: ```cairo #[starknet::interface] pub trait IMinigameDetails { fn token_name(self: @TState, token_id: felt252) -> ByteArray; fn token_description(self: @TState, token_id: felt252) -> ByteArray; fn game_details(self: @TState, token_id: felt252) -> Array; fn token_name_batch(self: @TState, token_ids: Span) -> Array; fn token_description_batch(self: @TState, token_ids: Span) -> Array; fn game_details_batch( self: @TState, token_ids: Span, ) -> Array>; } ``` #### IMinigameTokenSettings Configurable game modes: ```cairo #[starknet::interface] pub trait IMinigameTokenSettings { fn create_settings( ref self: TState, name: ByteArray, description: ByteArray, settings: Span, ) -> u32; fn settings_exists(self: @TState, settings_id: u32) -> bool; fn settings_count(self: @TState) -> u32; fn settings_details(self: @TState, settings_id: u32) -> GameSettingDetails; fn settings_details_batch( self: @TState, settings_ids: Span, ) -> Array; } ``` #### IMinigameTokenObjectives Trackable achievements: ```cairo #[starknet::interface] pub trait IMinigameTokenObjectives { fn create_objective( ref self: TState, name: ByteArray, description: ByteArray, objectives: Span, ) -> u32; fn objective_exists(self: @TState, objective_id: u32) -> bool; fn objectives_count(self: @TState) -> u32; fn completed_objective(self: @TState, token_id: felt252, objective_id: u32) -> bool; fn objectives_details(self: @TState, objective_id: u32) -> GameObjectiveDetails; fn objectives_details_batch( self: @TState, objective_ids: Span, ) -> Array; fn completed_objective_batch( self: @TState, token_ids: Span, objective_id: u32, ) -> Array; } ``` ### Component Storage The MinigameComponent stores: * MinigameToken address * Settings contract address * Objectives contract address ```cairo #[storage] struct Storage { token_address: ContractAddress, settings_address: ContractAddress, objectives_address: ContractAddress, } ``` ### Initialization ```cairo self.minigame.initializer( game_creator, // Address that receives creator NFT game_name, // "Number Guess" game_description, // "Guess the secret number" game_developer, // "Provable Games" game_publisher, // "Provable Games" game_genre, // "Puzzle" game_image, // Image URL game_color, // Option - hex color client_url, // Option - game client URL renderer_address, // Option - custom renderer settings_address, // Option - settings provider objectives_address, // Option - objectives provider minigame_token_address, // MinigameToken contract address royalty_fraction, // Option - basis points skills_address, // Option - AI agent skills version, // u64 - game version number ); ``` During initialization, the component: 1. Registers the game with the MinigameRegistry 2. Stores the assigned game\_id 3. Registers SRC5 interfaces (`IMINIGAME_ID`) ### Events ```cairo #[event] enum Event { ScoreUpdate: ScoreUpdate, GameOver: GameOver, } #[derive(Drop, starknet::Event)] struct ScoreUpdate { #[key] token_id: felt252, score: u64, } #[derive(Drop, starknet::Event)] struct GameOver { #[key] token_id: felt252, final_score: u64, } ``` ### Data Structures #### GameDetail ```cairo #[derive(Drop, Serde, Copy)] pub struct GameDetail { pub name: felt252, pub value: felt252, } ``` #### GameSettingDetails ```cairo #[derive(Drop, Serde)] pub struct GameSettingDetails { pub name: ByteArray, pub description: ByteArray, pub settings: Span, } #[derive(Drop, Serde)] pub struct GameSetting { pub name: felt252, pub value: felt252, } ``` #### GameObjectiveDetails ```cairo #[derive(Drop, Serde)] pub struct GameObjectiveDetails { pub name: ByteArray, pub description: ByteArray, pub objectives: Span, } #[derive(Drop, Serde, Copy)] pub struct GameObjective { pub name: felt252, pub value: felt252, } ``` ## MinigameRegistryComponent The MinigameRegistryComponent manages game registration, discovery, and metadata. It is itself an ERC721 — registering a game mints a creator NFT that controls royalties. For usage examples and integration patterns, see [Registry & Discovery](/embeddable-game-standard/building-a-platform/registry). ### IMinigameRegistry ```cairo pub const IMINIGAME_REGISTRY_ID: felt252 = 0x2ff8aa8dda405faf0eb17c5f806d7482b7352cf91fa9668e9ddf030f14b2ee9; #[starknet::interface] pub trait IMinigameRegistry { // Discovery fn game_count(self: @TState) -> u64; fn game_id_from_address(self: @TState, contract_address: ContractAddress) -> u64; fn game_address_from_id(self: @TState, game_id: u64) -> ContractAddress; fn game_metadata(self: @TState, game_id: u64) -> GameMetadata; fn is_game_registered(self: @TState, contract_address: ContractAddress) -> bool; fn skills_address(self: @TState, game_id: u64) -> ContractAddress; // Registration fn register_game( ref self: TState, creator_address: ContractAddress, name: ByteArray, description: ByteArray, developer: ByteArray, publisher: ByteArray, genre: ByteArray, image: ByteArray, color: Option, client_url: Option, renderer_address: Option, royalty_fraction: Option, skills_address: Option, version: u64, ) -> u64; // Admin fn set_game_royalty(ref self: TState, game_id: u64, royalty_fraction: u128); // Batch queries fn game_metadata_batch(self: @TState, game_ids: Span) -> Array; fn games_registered_batch( self: @TState, addresses: Span, ) -> Array; fn get_games(self: @TState, start: u64, count: u64) -> Array; // Filtered queries fn get_games_by_developer( self: @TState, developer: ByteArray, start: u64, count: u64, ) -> Array; fn get_games_by_publisher( self: @TState, publisher: ByteArray, start: u64, count: u64, ) -> Array; fn get_games_by_genre( self: @TState, genre: ByteArray, start: u64, count: u64, ) -> Array; } ``` ### GameMetadata ```cairo #[derive(Drop, Serde, Clone, starknet::Store)] pub struct GameMetadata { pub contract_address: ContractAddress, pub name: ByteArray, pub description: ByteArray, pub developer: ByteArray, pub publisher: ByteArray, pub genre: ByteArray, pub image: ByteArray, pub color: ByteArray, pub client_url: ByteArray, pub renderer_address: ContractAddress, pub skills_address: ContractAddress, pub royalty_fraction: u128, pub created_at: u64, pub version: u64, } ``` ### Component Storage ```cairo #[storage] struct Storage { game_counter: u64, game_id_by_address: Map, game_metadata: Map, } ``` ### Events ```cairo #[derive(Drop, starknet::Event)] struct GameMetadataUpdate { #[key] game_id: u64, metadata: GameMetadata, } #[derive(Drop, starknet::Event)] struct GameRegistryUpdate { #[key] game_id: u64, contract_address: ContractAddress, } #[derive(Drop, starknet::Event)] struct GameRoyaltyUpdate { #[key] game_id: u64, royalty_fraction: u128, } ``` ### Hooks The registry component provides hooks for custom behavior: ```cairo pub trait MinigameRegistryHooksTrait { fn before_register_game( ref self: TContractState, caller_address: ContractAddress, creator_address: ContractAddress, ); fn after_register_game( ref self: TContractState, game_id: u64, creator_address: ContractAddress, ); } ``` The Denshokan implementation uses `after_register_game` to mint a creator ERC721 to the `creator_address`. ## Token Extensions The MinigameToken supports optional extensions that add functionality beyond the core `IMinigameToken` interface. Each extension has its own SRC5 interface ID and can be independently checked and queried. ### Minter Extension Tracks which contracts have minted tokens. Each minter receives an auto-incrementing ID that gets packed into token IDs. #### IMinigameTokenMinter ```cairo pub const IMINIGAME_TOKEN_MINTER_ID: felt252 = 0x2198424b9ee68499f53f33ad952598acbd6d141af6c6863c9c56b117063acca; #[starknet::interface] pub trait IMinigameTokenMinter { fn get_minter_address(self: @TState, minter_id: u64) -> ContractAddress; fn get_minter_id(self: @TState, minter_address: ContractAddress) -> u64; fn minter_exists(self: @TState, minter_address: ContractAddress) -> bool; fn total_minters(self: @TState) -> u64; } ``` Minters are registered automatically on first mint. The minter ID (truncated to 40 bits) is packed into the token ID so it can be decoded without storage lookups. ### Renderer Extension Allows per-token custom renderers. When a token has a custom renderer, `token_uri()` delegates to that contract instead of the default renderer. #### IMinigameTokenRenderer ```cairo pub const IMINIGAME_TOKEN_RENDERER_ID: felt252 = 0x2899a752da88d6acf4ed54cc644238f3956b4db3c9885d3ad94f6149f0ec465; #[starknet::interface] pub trait IMinigameTokenRenderer { fn get_renderer(self: @TState, token_id: felt252) -> ContractAddress; fn has_custom_renderer(self: @TState, token_id: felt252) -> bool; fn reset_token_renderer(ref self: TState, token_id: felt252); // Batch operations fn reset_token_renderer_batch(ref self: TState, token_ids: Span); fn get_renderer_batch( self: @TState, token_ids: Span, ) -> Array; } ``` The renderer address can be set at mint time via `MintParams.renderer_address`, or overridden later by the token owner using `set_token_renderer` on the core token interface. ### Skills Extension Per-token AI agent skills. Each token can override the game's default skills provider with a custom one. #### ISkills The external contract interface that skills providers implement: ```cairo pub const ISKILLS_ID: felt252 = 0x39fae678a19cd9b999da1d9ad54f00e686406974a4ced6f7eb51c8959aabd98; #[starknet::interface] pub trait ISkills { fn skills(self: @TState, token_id: felt252) -> ByteArray; } ``` #### IMinigameTokenSkills The token extension interface for per-token skills address management: ```cairo pub const IMINIGAME_TOKEN_SKILLS_ID: felt252 = 0x33846532a9b9e859675aaa1a6c3ae6a45ccf1920c83e2d34898fa2f116201b3; #[starknet::interface] pub trait IMinigameTokenSkills { fn get_skills_address(self: @TState, token_id: felt252) -> ContractAddress; fn has_custom_skills(self: @TState, token_id: felt252) -> bool; fn reset_token_skills(ref self: TState, token_id: felt252); // Batch operations fn reset_token_skills_batch(ref self: TState, token_ids: Span); fn get_skills_address_batch( self: @TState, token_ids: Span, ) -> Array; } ``` Resolution order: per-token override > game-level default (from registry) > zero address (no skills). ### Settings Extension Stores game settings metadata on the token contract. The game contract creates settings via `create_settings`, and the token contract stores the display metadata for SDK/RPC queries. #### IMinigameTokenSettings ```cairo pub const IMINIGAME_TOKEN_SETTINGS_ID: felt252 = 0x3c6f5c714fef5141bb7edbbbf738c80782154e825a5675355c937aa9bc07bae; #[starknet::interface] pub trait IMinigameTokenSettings { fn create_settings( ref self: TState, game_address: ContractAddress, creator_address: ContractAddress, settings_id: u32, settings_details: GameSettingDetails, ); } ``` Called by the `SettingsComponent` in the game contract during `create_settings`. The token contract emits events that the indexer picks up for SDK queries. ### Objectives Extension Stores objective metadata on the token contract. Mirrors the pattern of the Settings extension. #### IMinigameTokenObjectives ```cairo pub const IMINIGAME_TOKEN_OBJECTIVES_ID: felt252 = 0x1e9f4982a68b67ddda6e894e8e620fe12ae877cf303308fe16814ceb2706077; #[starknet::interface] pub trait IMinigameTokenObjectives { fn create_objective( ref self: TState, game_address: ContractAddress, creator_address: ContractAddress, objective_id: u32, objective_details: GameObjectiveDetails, ); } ``` Called by the `ObjectivesComponent` in the game contract during `create_objective`. The token contract stores and indexes the metadata. ### Context Extension The context extension is an SRC5-only marker — there is no corresponding trait. The constant is used to indicate that a token contract supports context data: ```cairo pub const IMINIGAME_TOKEN_CONTEXT_ID: felt252 = 0x02b329e82f6f6b94f8949b36c5dc95acf86c6083b08d99bc81e399b4b0e8d19a; ``` Context data is attached to tokens at mint time via `MintParams.context` and is read through the metagame's [IMetagameContextDetails](/embeddable-game-standard/reference/metagame#imetagamecontextdetails) interface. ## MinigameToken (ERC721) The MinigameToken is the central ERC721 contract that represents game sessions. It composes multiple game-components and packs immutable game data directly into the token ID. ### IMinigameTokenMixin The full token interface combines core ERC721, game lifecycle, and optional extensions: #### Core Methods ```cairo // Minting fn mint(ref self: TState, params: MintParams) -> felt252; fn mint_batch(ref self: TState, params: Array) -> Array; // Game lifecycle fn update_game(ref self: TState, token_id: felt252); fn update_game_batch(ref self: TState, token_ids: Span); // Token metadata fn token_metadata(self: @TState, token_id: felt252) -> TokenMetadata; fn token_metadata_batch(self: @TState, token_ids: Span) -> Array; fn token_mutable_state(self: @TState, token_id: felt252) -> TokenMutableState; fn token_mutable_state_batch(self: @TState, token_ids: Span) -> Array; // Game address resolution fn token_game_address(self: @TState, token_id: felt252) -> ContractAddress; fn token_game_address_batch(self: @TState, token_ids: Span) -> Array; // Playability fn is_playable(self: @TState, token_id: felt252) -> bool; fn is_playable_batch(self: @TState, token_ids: Span) -> Array; ``` #### Player Name Methods ```cairo fn player_name(self: @TState, token_id: felt252) -> felt252; fn player_name_batch(self: @TState, token_ids: Span) -> Array; fn update_player_name(ref self: TState, token_id: felt252, name: felt252); fn update_player_name_batch(ref self: TState, updates: Span); ``` #### Settings Extension ```cairo fn settings_id(self: @TState, token_id: felt252) -> u32; fn settings_id_batch(self: @TState, token_ids: Span) -> Array; ``` #### Objectives Extension ```cairo fn objective_id(self: @TState, token_id: felt252) -> u32; fn objective_id_batch(self: @TState, token_ids: Span) -> Array; ``` #### Minter Extension ```cairo fn minted_by(self: @TState, token_id: felt252) -> felt252; fn minted_by_batch(self: @TState, token_ids: Span) -> Array; ``` #### Full State & Batch Extensions ```cairo fn token_full_state_batch( self: @TState, token_ids: Span, ) -> Array; fn mint_batch_recipients( ref self: TState, params: Array, recipients: Array, ) -> Array; fn game_address(self: @TState) -> ContractAddress; fn game_registry_address(self: @TState) -> ContractAddress; fn client_url(self: @TState, token_id: felt252) -> ByteArray; fn assert_is_playable(self: @TState, token_id: felt252); ``` #### TokenFullState ```cairo #[derive(Copy, Drop, Serde)] pub struct TokenFullState { pub token_id: felt252, pub owner: ContractAddress, pub player_name: felt252, pub is_playable: bool, pub game_address: ContractAddress, pub game_over: bool, pub completed_objective: bool, pub lifecycle: Lifecycle, } ``` #### Soulbound Extension ```cairo fn is_soulbound(self: @TState, token_id: felt252) -> bool; fn is_soulbound_batch(self: @TState, token_ids: Span) -> Array; ``` #### Renderer Extension ```cairo fn renderer_address(self: @TState, token_id: felt252) -> ContractAddress; fn renderer_address_batch(self: @TState, token_ids: Span) -> Array; fn set_token_renderer(ref self: TState, token_id: felt252, renderer: ContractAddress); fn reset_token_renderer(ref self: TState, token_id: felt252); ``` #### Skills Extension ```cairo fn skills_address(self: @TState, token_id: felt252) -> ContractAddress; fn skills_address_batch(self: @TState, token_ids: Span) -> Array; fn set_token_skills(ref self: TState, token_id: felt252, skills: ContractAddress); fn reset_token_skills(ref self: TState, token_id: felt252); ``` ### MintParams ```cairo #[derive(Drop, Serde)] pub struct MintParams { pub game_address: ContractAddress, pub player_name: Option, pub settings_id: Option, pub start: Option, pub end: Option, pub objective_id: Option, pub context: Option, pub client_url: Option, pub renderer_address: Option, pub skills_address: Option, pub to: ContractAddress, pub soulbound: bool, pub paymaster: bool, pub salt: u16, pub metadata: u16, } ``` ### TokenMetadata ```cairo #[derive(Copy, Drop, Serde)] pub struct TokenMetadata { pub game_id: u64, pub minted_at: u64, pub settings_id: u32, pub lifecycle: Lifecycle, pub minted_by: u64, pub soulbound: bool, pub game_over: bool, pub completed_objective: bool, pub has_context: bool, pub objective_id: u32, pub paymaster: bool, pub metadata: u16, } ``` ### TokenMutableState Only two fields are mutable after minting: ```cairo #[derive(Copy, Drop, Serde)] pub struct TokenMutableState { pub game_over: bool, pub completed_objective: bool, } ``` These are stored separately and packed into a single `felt252` using `StorePacking` (2 bits total). ### Packed Token IDs Token IDs are `felt252` values with 13 fields packed into 251 bits. This allows reading immutable token data without any storage lookups. See [Packed Token IDs](/embeddable-game-standard/advanced/packed-token-ids) for the bit layout and decoding. ## Frontend Integration > **Always use `@provable-games/denshokan-sdk` for client-side interactions.** The SDK handles data source selection, fallback, type safety, and packed token ID decoding automatically. Direct RPC calls should only be needed for write operations. > **Source**: [denshokan-sdk](https://github.com/Provable-Games/denshokan-sdk) The `@provable-games/denshokan-sdk` provides everything you need to integrate EGS game data into your frontend: a client for querying data, React hooks for declarative UI, and WebSocket subscriptions for real-time updates. ### Installation ```bash npm install @provable-games/denshokan-sdk # or pnpm add @provable-games/denshokan-sdk ``` **Peer dependencies** (install if you need them): * `react >=18.0.0` - Required for React hooks * `starknet >=9.0.0` - Required for RPC methods ### Quick Start #### Vanilla TypeScript ```typescript import { DenshokanClient } from "@provable-games/denshokan-sdk"; const client = new DenshokanClient({ chain: "mainnet", }); // List all registered games const games = await client.getGames(); // Get a specific token const token = await client.getToken("0x123..."); // Decode a packed token ID (no RPC call needed) const decoded = client.decodeTokenId("0x123..."); console.log(decoded.gameId, decoded.settingsId, decoded.mintedAt); ``` #### React ```tsx import { DenshokanProvider, useGames, useToken, } from "@provable-games/denshokan-sdk/react"; function App() { return ( ); } function GameList() { const { data: games, isLoading, error } = useGames(); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return (
    {games?.data.map((game) => (
  • {game.name}
  • ))}
); } ``` ### Data Source Architecture The SDK provides three data paths, automatically choosing the best one: | Source | Speed | Freshness | Use Case | | ------------- | --------- | ---------------------- | ------------------------------------------------------ | | **REST API** | Fast | Indexed (slight delay) | Lists, search, aggregation, pagination | | **WebSocket** | Real-time | Live | Score updates, new mints, game completions | | **RPC** | Slower | On-chain truth | ERC721 methods, batch contract reads, write operations | #### Smart Fallback If the API is unavailable, the SDK automatically falls back to RPC: ``` Request → Try API → Success → Return data → Failure → Try RPC → Success → Return data → Failure → Throw DataSourceError ``` Configure the primary source: ```typescript const client = new DenshokanClient({ chain: "mainnet", primarySource: "api", // Default: try API first, fallback to RPC // or primarySource: "rpc", // Always use RPC (no API dependency) }); ``` ### Three Integration Options #### 1. React Hooks (Recommended) Best for React apps. Handles loading states, caching, and re-fetching automatically. ```tsx const { data, isLoading, error, refetch } = useGames(); ``` [React Hooks Reference →](/embeddable-game-standard/frontend/react-hooks) #### 2. DenshokanClient Best for non-React apps, server-side code, or when you need full control. ```typescript const client = new DenshokanClient({ chain: "mainnet" }); const games = await client.getGames(); ``` [SDK Reference →](/embeddable-game-standard/frontend/sdk) #### 3. WebSocket Subscriptions Best for live dashboards, real-time leaderboards, and notifications. Eight typed hooks provide per-channel subscriptions with event buffering: ```tsx import { useScoreUpdates } from "@provable-games/denshokan-sdk/react"; const { lastEvent, events, isConnected } = useScoreUpdates({ gameIds: [1], bufferSize: 50, }); ``` Or use the client directly: ```typescript const unsub = client.subscribe( { channels: ["scores", "game_over"], gameIds: [1] }, (message) => console.log(message), ); ``` [WebSocket Reference →](/embeddable-game-standard/frontend/websockets) ## React Hooks The SDK provides React hooks for declarative data fetching. All hooks return `{ data, isLoading, error, refetch }` and handle loading states automatically. ### Setup Wrap your app with `DenshokanProvider`: ```tsx import { DenshokanProvider } from "@provable-games/denshokan-sdk/react"; function App() { return ( ); } ``` #### Provider Props ```typescript interface DenshokanProviderProps { children: ReactNode; config?: DenshokanClientConfig; // Create a new client from config client?: DenshokanClient; // Or pass an existing client } ``` You can pass either `config` (and a client will be created) or `client` (to reuse an existing instance). #### Accessing the Client ```tsx import { useDenshokanClient } from "@provable-games/denshokan-sdk/react"; function MyComponent() { const client = useDenshokanClient(); // Use client for custom queries or write operations } ``` ### Data Hooks #### Games ```tsx import { useGames, useGame, useGameStats } from "@provable-games/denshokan-sdk/react"; function GameList() { const { data: games, isLoading } = useGames({ limit: 20, offset: 0 }); return games?.data.map(game => (
{game.name}
)); } function GameDetail({ address }: { address: string }) { const { data: game } = useGame(address); const { data: stats } = useGameStats(address); return (

{game?.name}

{stats?.totalTokens} tokens minted

{stats?.uniquePlayers} unique players

); } ``` #### Tokens ```tsx import { useTokens, useToken, useTokenScores } from "@provable-games/denshokan-sdk/react"; function TokenList({ gameAddress }: { gameAddress: string }) { const { data: tokens, isLoadingUri } = useTokens({ gameAddress, gameOver: true, includeUri: true, limit: 50, }); return (
{isLoadingUri &&

Loading token images...

} {tokens?.data.map(token => (
Score: {token.score} {token.tokenUri && }
))}
); } function TokenDetail({ tokenId }: { tokenId: string }) { const { data: token, isLoading } = useToken(tokenId); const { data: scores } = useTokenScores(tokenId, 10); if (isLoading) return
Loading...
; return (

Score: {token?.score}

Game Over: {token?.gameOver ? "Yes" : "No"}

Playable: {token?.isPlayable ? "Yes" : "No"}

); } ``` :::note When `includeUri: true` is passed to `useTokens` or `usePlayerTokens`, an additional `isLoadingUri` flag is returned. It is `true` while token URIs are being fetched in the background via batch RPC. The main `data` is available immediately — URIs populate asynchronously. ::: #### Players ```tsx import { usePlayerStats, usePlayerTokens } from "@provable-games/denshokan-sdk/react"; function PlayerProfile({ address }: { address: string }) { const { data: stats } = usePlayerStats(address); const { data: tokens } = usePlayerTokens(address, { gameAddress: "0x1234...", }); return (

Total Tokens: {stats?.totalTokens}

Games Played: {stats?.gamesPlayed}

{tokens?.data.map(t => )}
); } ``` #### Minters ```tsx import { useMinters } from "@provable-games/denshokan-sdk/react"; function MinterList() { const { data: minters, isLoading } = useMinters({ limit: 20, offset: 0 }); if (isLoading) return
Loading...
; return minters?.data.map(minter => (
{minter.name} — {minter.contractAddress}
)); } ``` #### Token Decoding (Client-side) Decode a packed token ID without any network call: ```tsx import { useDecodeToken } from "@provable-games/denshokan-sdk/react"; function TokenInfo({ tokenId }: { tokenId: string }) { const decoded = useDecodeToken(tokenId); if (!decoded) return null; return (

Game ID: {decoded.gameId}

Settings: {decoded.settingsId}

Minted: {decoded.mintedAt}

Soulbound: {decoded.soulbound ? "Yes" : "No"}

); } ``` `useDecodeToken` returns a [`CoreToken`](/embeddable-game-standard/frontend/types#coretoken) directly (no loading state) since decoding is purely client-side. ### RPC Hooks Direct contract reads: ```tsx import { useBalanceOf, useOwnerOf, useTokenUri, useTokenUriBatch, useTokenMetadataBatch, useScoreBatch, useGameOverBatch, useObjectivesCount, useSettingsCount, } from "@provable-games/denshokan-sdk/react"; function OwnerInfo({ tokenId }: { tokenId: string }) { const { data: owner } = useOwnerOf(tokenId); const { data: balance } = useBalanceOf(owner ?? undefined); return

Owner {owner} has {balance?.toString()} tokens

; } ``` #### Batch RPC Hooks ```tsx function Scores({ tokenIds, gameAddress }: Props) { const { data: scores } = useScoreBatch(tokenIds, gameAddress); const { data: gameOvers } = useGameOverBatch(tokenIds, gameAddress); return tokenIds.map((id, i) => (
Score: {scores?.[i]?.toString() ?? "..."} | Over: {gameOvers?.[i] ? "Yes" : "No"}
)); } ``` #### Settings & Objectives Hooks ```tsx function GameSettings({ gameAddress }: { gameAddress: string }) { const { data: count } = useSettingsCount(gameAddress); const { data: settings } = useSettings(gameAddress); return (

{count} settings available

{settings?.data.map(s => (
{s.name}: {s.description}
))}
); } ``` #### Settings, Objectives & Activity ```tsx import { useSettings, useObjectives, useActivity, } from "@provable-games/denshokan-sdk/react"; function GameConfig({ gameAddress }: { gameAddress: string }) { const { data: settings } = useSettings(gameAddress); const { data: objectives } = useObjectives(gameAddress); const { data: activity } = useActivity({ gameAddress, limit: 20 }); return (

{settings?.data.length} settings

{objectives?.data.length} objectives

{activity?.data.length} recent actions

); } ``` ### Subscription Hooks Type-safe real-time event subscriptions. Each hook subscribes to a specific WebSocket channel and returns typed events with buffering. ```tsx import { useScoreUpdates, useGameOverEvents, useMintEvents, useTokenUpdates, useNewGames, useNewMinters, useNewSettings, useNewObjectives, useConnectionStatus, } from "@provable-games/denshokan-sdk/react"; function LiveDashboard({ gameId }: { gameId: number }) { const { lastEvent, events, isConnected, clear } = useScoreUpdates({ gameIds: [gameId], bufferSize: 100, onEvent: (event) => console.log("Score:", event.score), }); const { isConnected: wsConnected } = useConnectionStatus(); return (

{wsConnected ? "Connected" : "Reconnecting..."}

Latest: {lastEvent?.playerName} — {lastEvent?.score}

); } ``` | Hook | Channel | Payload Type | | ------------------- | ------------ | --------------------------------------------------------------------------------- | | `useScoreUpdates` | `scores` | [`ScoreEvent`](/embeddable-game-standard/frontend/types#scoreevent) | | `useGameOverEvents` | `game_over` | [`GameOverEvent`](/embeddable-game-standard/frontend/types#gameoverevent) | | `useMintEvents` | `mints` | [`MintEvent`](/embeddable-game-standard/frontend/types#mintevent) | | `useTokenUpdates` | `tokens` | [`TokenUpdateEvent`](/embeddable-game-standard/frontend/types#tokenupdateevent) | | `useNewGames` | `games` | [`NewGameEvent`](/embeddable-game-standard/frontend/types#newgameevent) | | `useNewMinters` | `minters` | [`NewMinterEvent`](/embeddable-game-standard/frontend/types#newminterevent) | | `useNewSettings` | `settings` | [`NewSettingEvent`](/embeddable-game-standard/frontend/types#newsettingevent) | | `useNewObjectives` | `objectives` | [`NewObjectiveEvent`](/embeddable-game-standard/frontend/types#newobjectiveevent) | Options and return types: ```typescript interface UseChannelOptions { gameIds?: number[]; // Server-side game ID filter bufferSize?: number; // Max events in memory (default: 50) enabled?: boolean; // Enable/disable subscription (default: true) onEvent?: (event: WSChannelPayloadMap[C]) => void; } interface UseChannelResult { lastEvent: WSChannelPayloadMap[C] | null; events: WSChannelPayloadMap[C][]; isConnected: boolean; clear: () => void; } ``` For full details on channels, event payloads, and the low-level `useSubscription` hook, see [WebSocket Subscriptions](/embeddable-game-standard/frontend/websockets). ### Hook Return Type All data hooks return the same shape: ```typescript interface UseAsyncResult { data: T | null; // The fetched data (null while loading) isLoading: boolean; // True during initial load error: Error | null; // Error if the fetch failed refetch: () => void; // Manually trigger a re-fetch } ``` `useTokens` and `usePlayerTokens` extend this with an additional field: ```typescript interface UseTokensResult extends UseAsyncResult> { /** True while token URIs are being fetched (when includeUri: true) */ isLoadingUri: boolean; } ``` See [`UseAsyncResult`](/embeddable-game-standard/frontend/types#useasyncresultt), [`UseTokensResult`](/embeddable-game-standard/frontend/types#usetokensresult), and [`UseChannelResult`](/embeddable-game-standard/frontend/types#usechannelresultc) for the full type definitions. ### Complete Hook Signatures #### Data Hooks | Hook | Parameters | Return | | ------------------------------------ | ----------------------------------------------------------------------------------- | ------------------------------------------------------- | | `useGames(params?)` | `{ limit?, offset? }` | `UseAsyncResult>` | | `useGame(address?)` | `string \| undefined` | `UseAsyncResult` | | `useGameStats(address?)` | `string \| undefined` | `UseAsyncResult` | | `useTokens(params?)` | [`TokensFilterParams`](/embeddable-game-standard/frontend/types#tokensfilterparams) | `UseTokensResult` | | `useToken(tokenId?)` | `string \| undefined` | `UseAsyncResult` | | `useTokenScores(tokenId?, limit?)` | `string \| undefined, number` | `UseAsyncResult` | | `useDecodeToken(tokenId?)` | `string \| undefined` | `CoreToken \| null` | | `usePlayerStats(address?)` | `string \| undefined` | `UseAsyncResult` | | `usePlayerTokens(address?, params?)` | `string \| undefined, PlayerTokensParams` | `UseTokensResult` | | `useMinters(params?)` | `{ limit?, offset? }` | `UseAsyncResult>` | | `useSettings(params?)` | [`SettingsParams`](/embeddable-game-standard/frontend/types#settingsparams) | `UseAsyncResult>` | | `useObjectives(params?)` | [`ObjectivesParams`](/embeddable-game-standard/frontend/types#objectivesparams) | `UseAsyncResult>` | | `useActivity(params?)` | [`ActivityParams`](/embeddable-game-standard/frontend/types#activityparams) | `UseAsyncResult>` | #### RPC Hooks | Hook | Parameters | Return | | ------------------------------------------- | -------------------------------------------- | --------------------------------- | | `useBalanceOf(account?)` | `string \| undefined` | `UseAsyncResult` | | `useOwnerOf(tokenId?)` | `string \| undefined` | `UseAsyncResult` | | `useTokenUri(tokenId?)` | `string \| undefined` | `UseAsyncResult` | | `useTokenUriBatch(tokenIds?)` | `string[] \| undefined` | `UseAsyncResult` | | `useTokenMetadataBatch(tokenIds?)` | `string[] \| undefined` | `UseAsyncResult` | | `useScoreBatch(tokenIds?, gameAddress?)` | `string[] \| undefined, string \| undefined` | `UseAsyncResult` | | `useGameOverBatch(tokenIds?, gameAddress?)` | `string[] \| undefined, string \| undefined` | `UseAsyncResult` | | `useObjectivesCount(gameAddress?)` | `string \| undefined` | `UseAsyncResult` | | `useSettingsCount(gameAddress?)` | `string \| undefined` | `UseAsyncResult` | ### Caching & Refetch Behavior Hooks do **not** auto-poll or auto-refresh. Data is fetched: 1. **On mount** — when the component first renders with defined parameters 2. **On parameter change** — when hook parameters change (referential equality) To manually refresh data, call `refetch()`: ```tsx function TokenView({ tokenId }: { tokenId: string }) { const { data: token, refetch } = useToken(tokenId); return (

Score: {token?.score}

); } ``` For live updates, combine data hooks with [subscription hooks](#subscription-hooks): ```tsx function LiveScore({ tokenId, gameId }: Props) { const { data: token, refetch } = useToken(tokenId); useScoreUpdates({ gameIds: [gameId], onEvent: (event) => { if (event.tokenId === tokenId) { refetch(); // Re-fetch when this token's score changes } }, }); return

Score: {token?.score}

; } ``` #### Conditional Fetching Hooks accept `undefined` parameters and won't fetch until all required params are defined: ```tsx function TokenDetail({ tokenId }: { tokenId?: string }) { // Won't fetch until tokenId is defined const { data } = useToken(tokenId); } ``` This makes it safe to chain hooks: ```tsx function GameInfo({ tokenId }: { tokenId: string }) { const { data: token } = useToken(tokenId); // Only fetches once token.gameAddress is available const { data: game } = useGame(token?.gameAddress); } ``` ## DenshokanClient > **Source**: [denshokan-sdk](https://github.com/Provable-Games/denshokan-sdk) The `DenshokanClient` is the core class for interacting with EGS data. It provides methods for querying games, tokens, players, and activity through REST API and Starknet RPC. ### Configuration ```typescript import { DenshokanClient } from "@provable-games/denshokan-sdk"; const client = new DenshokanClient({ chain: "mainnet", // "mainnet" | "sepolia" apiUrl: "https://...", // Override API URL wsUrl: "wss://...", // Override WebSocket URL rpcUrl: "https://...", // Override RPC URL primarySource: "api", // "api" | "rpc" (default: "api") viewerAddress: "0x...", // Viewer contract for batch filter queries denshokanAddress: "0x...", // Denshokan ERC721 contract address registryAddress: "0x...", // Minigame registry contract address fetch: { timeout: 10000, // Request timeout (ms) maxRetries: 3, // Max retry attempts baseBackoff: 1000, // Base backoff delay (ms) maxBackoff: 30000, // Max backoff delay (ms) tokenUriConcurrency: 0, // Max concurrent URI fetches (0 = unlimited) }, ws: { maxReconnectAttempts: 10, // Max reconnect attempts reconnectBaseDelay: 1000, // Base reconnect delay (ms) }, health: { initialCheckDelay: 1000, // ms before first health check checkInterval: 30000, // ms between health checks checkTimeout: 5000, // ms timeout for health check }, }); ``` See [`DenshokanClientConfig`](/embeddable-game-standard/frontend/types#denshokanclientconfig) for the full configuration type. ### Default Endpoints When you specify `chain: "mainnet"` or `chain: "sepolia"`, the SDK uses these defaults. All URLs can be overridden via `apiUrl`, `wsUrl`, and `rpcUrl` in the config. #### Mainnet | Service | URL | | --------- | -------------------------------------------------- | | API | `https://denshokan-api-production.up.railway.app` | | WebSocket | `wss://denshokan-api-production.up.railway.app/ws` | | RPC | `https://api.cartridge.gg/x/starknet/mainnet` | **Contract addresses:** | Contract | Address | | --------- | -------------------------------------------------------------------- | | Denshokan | `0x0029ffae8b0c4626e06395a947800bc89e76422107f6adff8937a6e9a1e01f28` | | Registry | `0x05b4a2ed39dfb28a33c2dd73cbedf02091a31dccb9ed4ed19201e3c255865851` | | Viewer | `0x01825fa210dc2abd02fa03d4eb37dabf1d6b69e9c4cd471ee402fa0fcc78611b` | #### Sepolia | Service | URL | | --------- | ----------------------------------------------- | | API | `https://denshokan-api-sepolia.up.railway.app` | | WebSocket | `wss://denshokan-api-sepolia.up.railway.app/ws` | | RPC | `https://api.cartridge.gg/x/starknet/sepolia` | **Contract addresses:** | Contract | Address | | --------- | -------------------------------------------------------------------- | | Denshokan | `0x0142712722e62a38f9c40fcc904610e1a14c70125876ecaaf25d803556734467` | | Registry | `0x040f1ed9880611bb7273bf51fd67123ebbba04c282036e2f81314061f6f9b1a1` | | Viewer | `0x025d92f18c6c1ed2114774adf68249a95fc468d9381ab33fa4b9ccfff7cf5f9f` | #### REST API Routes All REST endpoints are relative to the API base URL above. | Method | Path | Description | | ------ | ----------------------------------------------- | ---------------------------- | | GET | `/health` | Health check | | GET | `/games` | List games (paginated) | | GET | `/games/{gameAddress}` | Get a single game | | GET | `/games/{gameAddress}/stats` | Game statistics | | GET | `/games/{gameAddress}/settings` | Game settings | | GET | `/games/{gameAddress}/settings/{settingsId}` | Single game setting | | GET | `/games/{gameAddress}/objectives` | Game objectives | | GET | `/games/{gameAddress}/objectives/{objectiveId}` | Single game objective | | GET | `/settings` | Global settings | | GET | `/objectives` | Global objectives | | GET | `/tokens` | List tokens (filterable) | | GET | `/tokens/{tokenId}` | Get a single token | | GET | `/tokens/{tokenId}/scores` | Token score history | | GET | `/players/{address}/tokens` | Player's tokens | | GET | `/players/{address}/stats` | Player statistics | | GET | `/minters` | List minters | | GET | `/minters/{minterId}` | Get a single minter | | GET | `/activity` | Activity events (filterable) | | GET | `/activity/stats` | Activity statistics | #### WebSocket Channels Connect to the WebSocket URL and subscribe to real-time events: | Channel | Description | | ----------- | --------------------------- | | `tokens` | New token mints and updates | | `scores` | Score changes | | `game_over` | Game completion events | | `mints` | Mint events | ### Games ```typescript // List games with pagination const games = await client.getGames({ limit: 20, offset: 0 }); // Returns: PaginatedResult // Get a single game (API with RPC fallback) const game = await client.getGame("0x1234..."); // Returns: Game // Get game statistics const stats = await client.getGameStats("0x1234..."); // Returns: GameStats ``` ### Tokens ```typescript // List tokens with filtering const tokens = await client.getTokens({ gameAddress: "0x1234...", owner: "0x5678...", settingsId: 2, gameOver: true, playable: false, soulbound: false, includeUri: true, // Fetch token URIs via batch RPC limit: 50, offset: 0, }); // Returns: PaginatedResult // Get a single token (API with RPC fallback) const token = await client.getToken("0xabc..."); // Returns: Token // Get score history const scores = await client.getTokenScores("0xabc...", 10); // Returns: TokenScoreEntry[] ``` #### Token Filter Parameters | Parameter | Type | Description | | --------------- | --------- | --------------------------------- | | `gameAddress` | `string` | Filter by game contract address | | `gameId` | `number` | Filter by game ID | | `owner` | `string` | Filter by token owner | | `settingsId` | `number` | Filter by settings configuration | | `objectiveId` | `number` | Filter by objective | | `minterAddress` | `string` | Filter by who minted the token | | `soulbound` | `boolean` | Filter by soulbound status | | `playable` | `boolean` | Filter by playability | | `gameOver` | `boolean` | Filter by game completion | | `mintedAfter` | `number` | Filter by mint timestamp (after) | | `mintedBefore` | `number` | Filter by mint timestamp (before) | | `includeUri` | `boolean` | Fetch token URIs via batch RPC | | `limit` | `number` | Page size | | `offset` | `number` | Page offset | See [`TokensFilterParams`](/embeddable-game-standard/frontend/types#tokensfilterparams) for the full type. ### Players ```typescript // Get player's tokens const tokens = await client.getPlayerTokens("0x5678...", { gameAddress: "0x1234...", // Optional: filter by game includeUri: true, // Optional: fetch token URIs }); // Returns: PaginatedResult // Get player statistics const stats = await client.getPlayerStats("0x5678..."); // Returns: PlayerStats ``` ### Minters ```typescript // List minters with pagination const minters = await client.getMinters({ limit: 20, offset: 0 }); // Returns: PaginatedResult // Get a single minter const minter = await client.getMinter("minter-id"); // Returns: Minter ``` See [`Minter`](/embeddable-game-standard/frontend/types#minter) for the type definition. ### Activity ```typescript // List activity events const activity = await client.getActivity({ type: "score_update", // Optional: filter by event type limit: 50, offset: 0, }); // Returns: PaginatedResult // Get aggregate activity stats const stats = await client.getActivityStats(1); // Optional: gameId filter // Returns: ActivityStats ``` See [`ActivityEvent`](/embeddable-game-standard/frontend/types#activityevent) and [`ActivityStats`](/embeddable-game-standard/frontend/types#activitystats) for the type definitions. ### Settings & Objectives ```typescript // Get settings for a game const settings = await client.getSettings({ gameAddress: "0x1234...", limit: 10, offset: 0, }); // Returns: PaginatedResult // Get a specific setting const setting = await client.getSetting(2, "0x1234..."); // Returns: GameSettingDetails // Get objectives for a game const objectives = await client.getObjectives({ gameAddress: "0x1234...", settingsId: 1, // Optional: filter by settings }); // Returns: PaginatedResult // Get a specific objective const objective = await client.getObjective(1, "0x1234..."); // Returns: GameObjectiveDetails ``` ### RPC Methods Direct contract reads when you need on-chain truth. #### ERC721 ```typescript const balance = await client.balanceOf("0x5678..."); // bigint const owner = await client.ownerOf("0xabc..."); // string const uri = await client.tokenUri("0xabc..."); // string const name = await client.name(); // string const sym = await client.symbol(); // string const supply = await client.totalSupply(); // bigint // Batch URI fetch const uris = await client.tokenUriBatch(["0xa..", "0xb.."]); // Returns: string[] // ERC-2981 royalty info const royalty = await client.royaltyInfo("0xabc..", salePrice); // Returns: RoyaltyInfo { receiver, amount } ``` #### ERC721 Enumerable ```typescript // Get token ID by global index const tokenId = await client.tokenByIndex(0n); // Get token ID by owner index const tokenId = await client.tokenOfOwnerByIndex("0x5678...", 0n); // Enumerate all token IDs (paginated) const ids = await client.enumerateTokenIds({ limit: 100, offset: 0 }); // Enumerate token IDs owned by address const ids = await client.enumerateTokenIdsByOwner("0x5678...", { limit: 100, offset: 0, }); ``` #### Token Metadata Single and batch accessors for on-chain token metadata. Each method has a batch variant that accepts an array of token IDs. | Method | Returns | Batch Variant | | ---------------------------- | --------------------------------------------------------------------------------- | ---------------------------------- | | `tokenMetadata(tokenId)` | [`TokenMetadata`](/embeddable-game-standard/frontend/types#tokenmetadata) | `tokenMetadataBatch(tokenIds)` | | `tokenMutableState(tokenId)` | [`TokenMutableState`](/embeddable-game-standard/frontend/types#tokenmutablestate) | `tokenMutableStateBatch(tokenIds)` | | `isPlayable(tokenId)` | `boolean` | `isPlayableBatch(tokenIds)` | | `settingsId(tokenId)` | `number` | `settingsIdBatch(tokenIds)` | | `playerName(tokenId)` | `string` | `playerNameBatch(tokenIds)` | | `objectiveId(tokenId)` | `number` | `objectiveIdBatch(tokenIds)` | | `mintedBy(tokenId)` | `string` | `mintedByBatch(tokenIds)` | | `isSoulbound(tokenId)` | `boolean` | `isSoulboundBatch(tokenIds)` | | `rendererAddress(tokenId)` | `string` | `rendererAddressBatch(tokenIds)` | | `tokenGameAddress(tokenId)` | `string` | `tokenGameAddressBatch(tokenIds)` | ```typescript // Single const metadata = await client.tokenMetadata("0xabc..."); const playable = await client.isPlayable("0xabc..."); const name = await client.playerName("0xabc..."); // Batch const metadatas = await client.tokenMetadataBatch(["0xa..", "0xb.."]); const names = await client.playerNameBatch(["0xa..", "0xb.."]); ``` #### Game Contract Reads Read game-specific state directly from on-chain contracts. **Score & Game Over:** ```typescript const score = await client.score("0xabc...", "0x1234..."); // bigint const over = await client.gameOver("0xabc...", "0x1234..."); // boolean // Batch const scores = await client.scoreBatch(["0xa..", "0xb.."], "0x1234..."); const overs = await client.gameOverBatch(["0xa..", "0xb.."], "0x1234..."); ``` **Token Details (game-specific):** ```typescript const name = await client.tokenName("0xabc..", "0x1234.."); const desc = await client.tokenDescription("0xabc..", "0x1234.."); const details = await client.gameDetails("0xabc..", "0x1234.."); // Returns: GameDetail[] — array of { key, value } pairs // Batch variants const names = await client.tokenNameBatch(["0xa..", "0xb.."], "0x1234.."); const descs = await client.tokenDescriptionBatch(["0xa..", "0xb.."], "0x1234.."); const allDetails = await client.gameDetailsBatch(["0xa..", "0xb.."], "0x1234.."); ``` **Objectives (on-chain):** ```typescript const count = await client.objectivesCount("0x1234.."); // number const exists = await client.objectiveExists(1, "0x1234.."); // boolean const completed = await client.completedObjective( "0xabc..", 1, "0x1234.." ); // boolean const details = await client.objectivesDetails(1, "0x1234.."); // GameObjectiveDetails // Batch variants const existsAll = await client.objectiveExistsBatch([1, 2, 3], "0x1234.."); const completedAll = await client.completedObjectiveBatch( ["0xa..", "0xb.."], 1, "0x1234.." ); const detailsAll = await client.objectivesDetailsBatch([1, 2, 3], "0x1234.."); ``` **Settings (on-chain):** ```typescript const count = await client.settingsCount("0x1234.."); // number const exists = await client.settingsExists(1, "0x1234.."); // boolean const details = await client.settingsDetails(1, "0x1234.."); // GameSettingDetails // Batch variants const existsAll = await client.settingsExistsBatch([1, 2, 3], "0x1234.."); const detailsAll = await client.settingsDetailsBatch([1, 2, 3], "0x1234.."); ``` #### Registry RPC ```typescript // Get full on-chain game metadata from registry const meta = await client.gameMetadata(1); // GameMetadata const addr = await client.gameAddress(1); // string ``` ### Write Operations Write methods return transaction call data for use with starknet.js `account.execute()`. ```typescript // Mint a new game token const calls = client.mint({ gameId: 1, settingsId: 2, objectiveId: 0, playerName: "Player1", skillsAddress: undefined, // Optional: AI agent skills provider to: playerAddress, soulbound: false, }); // Batch mint (auto-assigns salts) const calls = client.mintBatch([ { gameId: 1, settingsId: 1, objectiveId: 0, playerName: "P1", to: addr, soulbound: false }, { gameId: 1, settingsId: 2, objectiveId: 0, playerName: "P2", to: addr, soulbound: false }, ]); // Update game state (sync score/game_over from game contract) const calls = client.updateGame(tokenId); const calls = client.updateGameBatch([tokenId1, tokenId2]); // Update player name const calls = client.updatePlayerName(tokenId, "NewName"); const calls = client.updatePlayerNameBatch([ { tokenId: "0xa..", name: "Name1" }, { tokenId: "0xb..", name: "Name2" }, ]); ``` #### Executing Transactions Write methods return `Call[]` arrays. Use starknet.js to execute them: ```typescript import { Account, RpcProvider } from "starknet"; const provider = new RpcProvider({ nodeUrl: "https://..." }); const account = new Account(provider, accountAddress, privateKey); // Execute a mint const mintCalls = client.mint({ gameId: 1, settingsId: 1, objectiveId: 0, playerName: "MyPlayer", to: account.address, soulbound: false, }); const { transaction_hash } = await account.execute(mintCalls); await provider.waitForTransaction(transaction_hash); ``` With Cartridge Controller: ```tsx import { useAccount } from "@starknet-react/core"; function MintButton({ client }) { const { account } = useAccount(); const handleMint = async () => { const calls = client.mint({ gameId: 1, settingsId: 1, objectiveId: 0, playerName: "Player", to: account.address, soulbound: false, }); await account.execute(calls); }; return ; } ``` See [`MintParams`](/embeddable-game-standard/frontend/types#mintparams) for the full parameter type. ### Utilities #### Decode Token ID Extract all 14 packed fields without any RPC call: ```typescript const decoded = client.decodeTokenId("0x123..."); // Returns: DecodedTokenId { // tokenId: bigint, // gameId: number, // mintedBy: bigint, // settingsId: number, // mintedAt: Date, // startDelay: number, // endDelay: number, // objectiveId: number, // soulbound: boolean, // hasContext: boolean, // paymaster: boolean, // txHash: number, // salt: number, // metadata: number, // } ``` The standalone `decodeCoreToken()` function returns a [`CoreToken`](/embeddable-game-standard/frontend/types#coretoken) without needing a client instance: ```typescript import { decodeCoreToken } from "@provable-games/denshokan-sdk"; const core = decodeCoreToken("0x123..."); // Returns: CoreToken — no RPC, no client needed ``` #### MintSaltCounter Manages auto-incrementing 10-bit salt values (0–1023) for token minting: ```typescript import { MintSaltCounter } from "@provable-games/denshokan-sdk"; const counter = new MintSaltCounter(); // starts at 0 const salt1 = counter.next(); // 0 const salt2 = counter.next(); // 1 const peeked = counter.peek(); // 2 (without advancing) counter.reset(); // back to 0 ``` #### assignSalts Assigns auto-incrementing salts to mint parameter arrays: ```typescript import { assignSalts } from "@provable-games/denshokan-sdk"; const params = [ { gameId: 1, settingsId: 1, objectiveId: 0, playerName: "P1", to: addr, soulbound: false }, { gameId: 1, settingsId: 2, objectiveId: 0, playerName: "P2", to: addr, soulbound: false }, ]; const withSalts = assignSalts(params); // withSalts[0].salt === 0 // withSalts[1].salt === 1 ``` :::note `mintBatch()` calls `assignSalts` automatically — you only need this for custom salt management. ::: #### Address Utilities ```typescript import { normalizeAddress, toHexTokenId } from "@provable-games/denshokan-sdk"; // Normalize to 0x-prefixed, 64-char hex (strips leading zeros, pads) normalizeAddress("0x00123"); // "0x0000000000000000000000000000000000000000000000000000000000000123" // Convert bigint/hex/decimal to 0x-prefixed hex (no padding) toHexTokenId(255n); // "0xff" ``` #### Connection Status ```typescript // Check WebSocket connection const connected = client.wsConnected; // boolean // Listen for connection changes const unsub = client.onWsConnectionChange((connected) => { console.log("WebSocket:", connected ? "up" : "down"); }); // Later: stop listening unsub(); ``` ### Health Monitoring The SDK performs background health checks to detect API or RPC availability and automatically switch data sources. ```typescript // ConnectionStatus modes type ConnectionMode = "api" | "rpc-fallback" | "offline"; ``` | Status | Meaning | | -------------- | ------------------------------------------- | | `api` | API is reachable, using primary data source | | `rpc-fallback` | API is unavailable, using RPC as fallback | | `offline` | Both API and RPC are unreachable | Health checks run at 30-second intervals (configurable via `health.checkInterval`). When the API goes down, the SDK auto-switches to RPC. When the API recovers, it switches back. ### Error Handling ```typescript import { DenshokanError, ApiError, RpcError, TokenNotFoundError, DataSourceError, } from "@provable-games/denshokan-sdk"; try { const token = await client.getToken(id); } catch (error) { if (error instanceof TokenNotFoundError) { console.log(`Token ${error.tokenId} not found`); } else if (error instanceof ApiError) { console.log(`API error: ${error.statusCode}`); } else if (error instanceof DataSourceError) { console.log("Both API and RPC failed"); } } ``` #### Error Hierarchy | Error | Description | | -------------------- | --------------------------------------------- | | `DenshokanError` | Base error class | | `ApiError` | REST API returned an error (has `statusCode`) | | `RpcError` | Starknet RPC call failed | | `RateLimitError` | Rate limit exceeded (has `retryAfter`) | | `TimeoutError` | Request timed out | | `AbortError` | Request was aborted | | `TokenNotFoundError` | Token doesn't exist | | `GameNotFoundError` | Game doesn't exist | | `InvalidChainError` | Invalid chain specified | | `DataSourceError` | Both primary and fallback sources failed | See [Error Types](/embeddable-game-standard/frontend/types#error-types) for the full class definitions. ### Cleanup ```typescript // Disconnect WebSocket and cleanup client.disconnect(); ``` ## Types Reference Complete TypeScript type definitions for the `@provable-games/denshokan-sdk` package. ```typescript import type { Token, CoreToken, DecodedTokenId, TokenMetadata, TokenMutableState, TokenScoreEntry, PaginatedResult, TokensFilterParams, Game, GameStats, // ... etc } from "@provable-games/denshokan-sdk"; ``` ### Core Token Types #### Token The full token representation returned by API and RPC queries. See [DenshokanClient](/embeddable-game-standard/frontend/sdk) for query methods. ```typescript interface Token { tokenId: string; gameId: number; owner: string; score: number; gameOver: boolean; playerName: string; /** 40-bit truncated minter address from token ID (not full address) */ mintedBy: number; mintedAt: string; settingsId: number; objectiveId: number; soulbound: boolean; isPlayable: boolean; gameAddress: string; clientUrl?: string; rendererAddress?: string; skillsAddress?: string; startDelay: number; endDelay: number; hasContext: boolean; paymaster: boolean; /** Only populated when includeUri option is passed to getTokens/useTokens */ tokenUri?: string; } ``` #### CoreToken Decoded purely from the packed token ID — no RPC calls required. Returned by [`useDecodeToken`](/embeddable-game-standard/frontend/react-hooks#token-decoding-client-side) and [`decodeCoreToken()`](/embeddable-game-standard/frontend/sdk#decode-token-id). ```typescript interface CoreToken { tokenId: string; gameId: number; settingsId: number; objectiveId: number; mintedAt: string; soulbound: boolean; startDelay: number; endDelay: number; hasContext: boolean; paymaster: boolean; /** 40-bit truncated minter address (not full address) */ mintedByTruncated: bigint; txHash: number; salt: number; metadata: number; } ``` #### DecodedTokenId Raw bigint decode of the packed token ID. Returned by [`client.decodeTokenId()`](/embeddable-game-standard/frontend/sdk#decode-token-id). See [Packed Token IDs](/embeddable-game-standard/advanced/packed-token-ids) for the bit layout. ```typescript interface DecodedTokenId { tokenId: bigint; gameId: number; mintedBy: bigint; settingsId: number; mintedAt: Date; startDelay: number; endDelay: number; objectiveId: number; soulbound: boolean; hasContext: boolean; paymaster: boolean; txHash: number; salt: number; metadata: number; } ``` #### TokenMetadata Immutable on-chain token metadata returned by [`client.tokenMetadata()`](/embeddable-game-standard/frontend/sdk#token-metadata). ```typescript interface TokenMetadata { gameId: number; settingsId: number; objectiveId: number; playerName: string; mintedBy: string; isPlayable: boolean; isSoulbound: boolean; rendererAddress: string; gameAddress: string; } ``` #### TokenMutableState Mutable token state that can change after minting. Returned by [`client.tokenMutableState()`](/embeddable-game-standard/frontend/sdk#token-metadata). ```typescript interface TokenMutableState { gameOver: boolean; completedObjective: boolean; } ``` #### TokenScoreEntry A single score history entry. Returned by [`client.getTokenScores()`](/embeddable-game-standard/frontend/sdk#tokens). ```typescript interface TokenScoreEntry { score: number; timestamp: string; } ``` #### PaginatedResult\ Wrapper for paginated API responses. All list queries return this shape. ```typescript interface PaginatedResult { data: T[]; total: number; } ``` #### TokensFilterParams Filter parameters for [`client.getTokens()`](/embeddable-game-standard/frontend/sdk#tokens) and [`useTokens()`](/embeddable-game-standard/frontend/react-hooks#tokens). ```typescript interface TokensFilterParams { gameId?: number; gameAddress?: string; owner?: string; settingsId?: number; objectiveId?: number; minterAddress?: string; soulbound?: boolean; playable?: boolean; gameOver?: boolean; mintedAfter?: number; mintedBefore?: number; limit?: number; offset?: number; /** When true, fetches token URIs via batch RPC */ includeUri?: boolean; } ``` ### Game Types #### Game A registered game in the EGS ecosystem. ```typescript interface Game { gameId: number; name: string; description: string; contractAddress: string; imageUrl?: string; developer?: string; publisher?: string; genre?: string; color?: string; clientUrl?: string; rendererAddress?: string; royaltyFraction?: string; skillsAddress?: string; version?: number; createdAt: string; } ``` #### GameStats Aggregate statistics for a game. Returned by [`client.getGameStats()`](/embeddable-game-standard/frontend/sdk#games). ```typescript interface GameStats { gameId: number; totalTokens: number; completedGames: number; activeGames: number; uniquePlayers: number; } ``` #### GameDetail A key-value pair from game-specific detail data. Returned by [`client.gameDetails()`](/embeddable-game-standard/frontend/sdk#game-contract-reads). ```typescript interface GameDetail { key: string; value: string; } ``` #### GameSettingDetails A complete settings configuration for a game. ```typescript interface GameSettingDetails { id: number; gameAddress: string; creatorAddress: string; name: string; description: string; settings: Record; blockNumber: string; createdAt: string; } ``` #### GameObjectiveDetails A complete objective definition for a game. ```typescript interface GameObjectiveDetails { id: number; settingsId: number; gameAddress: string; creatorAddress: string; name: string; description: string; objectives: Record; blockNumber: string; createdAt: string; } ``` #### SettingsParams Parameters for querying settings lists. ```typescript interface SettingsParams { limit?: number; offset?: number; gameAddress?: string; } ``` #### ObjectivesParams Parameters for querying objectives lists. ```typescript interface ObjectivesParams { limit?: number; offset?: number; gameAddress?: string; } ``` ### Player Types #### PlayerStats Aggregate player statistics. Returned by [`client.getPlayerStats()`](/embeddable-game-standard/frontend/sdk#players). ```typescript interface PlayerStats { address: string; totalTokens: number; gamesPlayed: number; completedGames: number; activeGames: number; totalScore: string; } ``` #### PlayerTokensParams Parameters for querying a player's tokens. ```typescript interface PlayerTokensParams { gameId?: number; limit?: number; offset?: number; /** When true, fetches token URIs via batch RPC */ includeUri?: boolean; } ``` ### Minter Types #### Minter A registered minter contract. Returned by [`client.getMinters()`](/embeddable-game-standard/frontend/sdk#minters). ```typescript interface Minter { id: string; minterId: string; name: string; contractAddress: string; createdAt: string; blockNumber: string; } ``` ### Activity Types #### ActivityEvent A single activity event in the system. ```typescript interface ActivityEvent { id: string; type: string; tokenId: string; gameId: number; player: string; data: Record; timestamp: string; } ``` #### ActivityParams Parameters for querying activity. ```typescript interface ActivityParams { type?: string; limit?: number; offset?: number; } ``` #### ActivityStats Aggregate activity statistics. ```typescript interface ActivityStats { gameId: number; totalTokens: number; completedGames: number; activeGames: number; uniquePlayers: number; } ``` ### RPC Types #### MintParams Parameters for [`client.mint()`](/embeddable-game-standard/frontend/sdk#write-operations). All fields are required except `salt` and `metadata`. ```typescript interface MintParams { gameId: number; settingsId: number; objectiveId: number; playerName: string; skillsAddress?: string; soulbound: boolean; to: string; salt?: number; metadata?: number; } ``` #### RoyaltyInfo ERC-2981 royalty information. Returned by [`client.royaltyInfo()`](/embeddable-game-standard/frontend/sdk#erc721). ```typescript interface RoyaltyInfo { receiver: string; amount: bigint; } ``` #### GameMetadata On-chain game metadata from the registry RPC. ```typescript interface GameMetadata { gameId: number; contractAddress: string; name: string; description: string; developer: string; publisher: string; genre: string; image: string; color: string; clientUrl: string; rendererAddress: string; skillsAddress: string; version: number; royaltyFraction: bigint; createdAt: number; } ``` #### PlayerNameUpdate Used with [`client.updatePlayerNameBatch()`](/embeddable-game-standard/frontend/sdk#write-operations). ```typescript interface PlayerNameUpdate { tokenId: string; name: string; } ``` #### FilterResult Result from viewer contract filter queries. ```typescript interface FilterResult { tokenIds: string[]; total: number; } ``` #### TokenFullState Full on-chain token state from the viewer contract. ```typescript interface TokenFullState { tokenId: string; owner: string; playerName: string; isPlayable: boolean; gameAddress: string; gameOver: boolean; completedObjective: boolean; lifecycle: Lifecycle; } ``` #### Lifecycle Start and end timestamps for token playability windows. ```typescript interface Lifecycle { start: number; end: number; } ``` ### WebSocket Event Types For subscription hooks usage, see [WebSocket Subscriptions](/embeddable-game-standard/frontend/websockets) and [React Hooks](/embeddable-game-standard/frontend/react-hooks#subscription-hooks). #### WSChannel ```typescript type WSChannel = | "tokens" | "scores" | "game_over" | "mints" | "games" | "minters" | "settings" | "objectives"; ``` #### WSMessage ```typescript interface WSMessage { channel: string; data: unknown; _timing?: { serverTs: number }; } ``` #### WSSubscribeOptions ```typescript interface WSSubscribeOptions { channels: WSChannel[]; gameIds?: number[]; } ``` #### WSEventHandler ```typescript type WSEventHandler = (message: WSMessage) => void; ``` #### ScoreEvent Emitted on the `scores` channel when a token's score changes. ```typescript interface ScoreEvent { tokenId: string; gameId: number; score: number; ownerAddress: string; playerName: string; } ``` #### GameOverEvent Emitted on the `game_over` channel when a game ends. ```typescript interface GameOverEvent { tokenId: string; gameId: number; score: number; ownerAddress: string; playerName: string; completedAllObjectives: boolean; } ``` #### MintEvent Emitted on the `mints` channel when a new token is minted. ```typescript interface MintEvent { tokenId: string; gameId: number; ownerAddress: string; mintedBy: string; settingsId: number; } ``` #### TokenUpdateEvent Union type emitted on the `tokens` channel for any token state change. ```typescript type TokenUpdateEvent = | { type: "scoreUpdate"; tokenId: string; gameId: number; score: number } | { type: "gameOver"; tokenId: string; gameId: number; score: number } | { type: "minted"; tokenId: string; gameId: number; ownerAddress: string }; ``` #### NewGameEvent ```typescript interface NewGameEvent { gameId: number; contractAddress: string; name: string; } ``` #### NewMinterEvent ```typescript interface NewMinterEvent { minterId: string; contractAddress: string; name: string; blockNumber: string; } ``` #### NewSettingEvent ```typescript interface NewSettingEvent { gameAddress: string; settingsId: number; creatorAddress: string; settingsData: string | null; } ``` #### NewObjectiveEvent ```typescript interface NewObjectiveEvent { gameAddress: string; objectiveId: number; settingsId: number; creatorAddress: string; objectiveData: string | null; } ``` #### WSChannelPayloadMap Maps each channel name to its typed payload. Used by subscription hooks for type safety. ```typescript interface WSChannelPayloadMap { scores: ScoreEvent; game_over: GameOverEvent; mints: MintEvent; tokens: TokenUpdateEvent; games: NewGameEvent; minters: NewMinterEvent; settings: NewSettingEvent; objectives: NewObjectiveEvent; } ``` ### Configuration Types #### DenshokanClientConfig Configuration for creating a [`DenshokanClient`](/embeddable-game-standard/frontend/sdk#configuration). ```typescript interface DenshokanClientConfig { chain?: "mainnet" | "sepolia"; apiUrl?: string; wsUrl?: string; rpcUrl?: string; provider?: unknown; /** Denshokan ERC721 contract address (defaults to chain address) */ denshokanAddress?: string; /** Minigame registry contract address (defaults to chain address) */ registryAddress?: string; /** Viewer contract address for batch filter queries (defaults to chain address) */ viewerAddress?: string; primarySource?: DataSource; fetch?: FetchConfig; ws?: WSConfig; health?: HealthConfig; } ``` #### DataSource ```typescript type DataSource = "api" | "rpc"; ``` #### FetchConfig ```typescript interface FetchConfig { timeout?: number; maxRetries?: number; baseBackoff?: number; maxBackoff?: number; /** Max concurrent token URI RPC calls (0 = unlimited) */ tokenUriConcurrency?: number; } ``` #### WSConfig ```typescript interface WSConfig { maxReconnectAttempts?: number; reconnectBaseDelay?: number; } ``` #### HealthConfig ```typescript interface HealthConfig { /** Delay before first health check in ms (default: 1000) */ initialCheckDelay?: number; /** Interval between health checks in ms (default: 30000) */ checkInterval?: number; /** Timeout for each health check in ms (default: 5000) */ checkTimeout?: number; } ``` ### Error Types All errors extend `DenshokanError`. See [Error Handling](/embeddable-game-standard/frontend/sdk#error-handling) for usage examples. ```typescript class DenshokanError extends Error {} class ApiError extends DenshokanError { statusCode: number; } class RpcError extends DenshokanError { contractAddress?: string; } class RateLimitError extends DenshokanError { retryAfter: number | null; } class TimeoutError extends DenshokanError {} class AbortError extends DenshokanError {} class TokenNotFoundError extends DenshokanError { tokenId: string; } class GameNotFoundError extends DenshokanError { gameId: string | number; } class InvalidChainError extends DenshokanError { chain: string; } class DataSourceError extends DenshokanError { primaryError: Error; fallbackError: Error; } ``` ### Hook Return Types #### UseAsyncResult\ Standard return type for all data [React hooks](/embeddable-game-standard/frontend/react-hooks#hook-return-type). ```typescript interface UseAsyncResult { data: T | null; isLoading: boolean; error: Error | null; refetch: () => void; } ``` #### UseTokensResult Extended return type for [`useTokens`](/embeddable-game-standard/frontend/react-hooks#tokens) and [`usePlayerTokens`](/embeddable-game-standard/frontend/react-hooks#players). Adds `isLoadingUri` for background URI fetching. ```typescript interface UseTokensResult extends UseAsyncResult> { /** True while token URIs are being fetched (when includeUri: true) */ isLoadingUri: boolean; } ``` #### UseChannelOptions\ Options for [subscription hooks](/embeddable-game-standard/frontend/react-hooks#subscription-hooks). ```typescript interface UseChannelOptions { gameIds?: number[]; bufferSize?: number; enabled?: boolean; onEvent?: (event: WSChannelPayloadMap[C]) => void; } ``` #### UseChannelResult\ Return type for [subscription hooks](/embeddable-game-standard/frontend/react-hooks#subscription-hooks). ```typescript interface UseChannelResult { lastEvent: WSChannelPayloadMap[C] | null; events: WSChannelPayloadMap[C][]; isConnected: boolean; clear: () => void; } ``` ## WebSocket Subscriptions The SDK provides real-time event subscriptions via WebSocket. Subscribe to game events for live leaderboards, activity feeds, and instant notifications. ### Architecture Events flow from the blockchain through a multi-layered pipeline: ``` Blockchain Event (e.g. score update) ↓ Apibara Indexer → writes to PostgreSQL ↓ PostgreSQL LISTEN/NOTIFY triggers ↓ API Server broadcasts to subscribed WebSocket clients ↓ denshokan-sdk WebSocketManager ↓ Event mappers (snake_case → camelCase) ↓ React hooks / callback handlers ``` The API server uses PostgreSQL triggers on the `tokens`, `games`, and `minters` tables. When data changes, triggers fire `NOTIFY` on named channels, and the API server broadcasts to all subscribed WebSocket clients. ### Channels | Channel | Event Type | Description | | ------------ | ------------------- | --------------------------------------------------------- | | `tokens` | `TokenUpdateEvent` | Any token state change (score update, game over, or mint) | | `scores` | `ScoreEvent` | When a token's score changes | | `game_over` | `GameOverEvent` | When a game session ends | | `mints` | `MintEvent` | When new game tokens are minted | | `games` | `NewGameEvent` | When a new game is registered | | `minters` | `NewMinterEvent` | When a new minter is registered | | `settings` | `NewSettingEvent` | When a new game setting is created | | `objectives` | `NewObjectiveEvent` | When a new game objective is created | ### Event Payloads Each channel delivers a typed event. The SDK automatically maps snake\_case server data to camelCase TypeScript types. ```typescript interface ScoreEvent { tokenId: string; gameId: number; score: number; ownerAddress: string; playerName: string; } interface GameOverEvent { tokenId: string; gameId: number; score: number; ownerAddress: string; playerName: string; completedAllObjectives: boolean; } interface MintEvent { tokenId: string; gameId: number; ownerAddress: string; mintedBy: string; settingsId: number; } // TokenUpdateEvent is polymorphic — it aggregates all token-related events interface TokenUpdateEvent { type: "scoreUpdate" | "gameOver" | "minted"; tokenId: string; gameId: number; score?: number; ownerAddress?: string; } interface NewGameEvent { gameId: number; contractAddress: string; name: string; } interface NewMinterEvent { minterId: string; contractAddress: string; name: string; blockNumber: string; } interface NewSettingEvent { gameAddress: string; settingsId: number; creatorAddress: string; settingsData: string | null; } interface NewObjectiveEvent { gameAddress: string; objectiveId: number; settingsId: number; creatorAddress: string; objectiveData: string | null; } ``` All types are exported from the SDK: ```typescript import type { WSChannel, WSMessage, ScoreEvent, GameOverEvent, MintEvent, TokenUpdateEvent, NewGameEvent, NewMinterEvent, NewSettingEvent, NewObjectiveEvent, WSChannelPayloadMap, } from "@provable-games/denshokan-sdk"; ``` ### Using the Client ```typescript import { DenshokanClient } from "@provable-games/denshokan-sdk"; const client = new DenshokanClient({ chain: "mainnet" }); // Subscribe to score updates for game ID 1 const unsubscribe = client.subscribe( { channels: ["scores", "game_over"], gameIds: [1], }, (message) => { console.log(`Channel: ${message.channel}`); console.log(`Data:`, message.data); }, ); // Later: unsubscribe unsubscribe(); // Cleanup all connections client.disconnect(); ``` ### Typed React Hooks (Recommended) The SDK provides eight typed hooks — one per channel. Each returns the last event, a buffered event history, and connection state. #### useScoreUpdates ```tsx import { useScoreUpdates } from "@provable-games/denshokan-sdk/react"; function LiveScores({ gameId }: { gameId: number }) { const { lastEvent, events, isConnected } = useScoreUpdates({ gameIds: [gameId], bufferSize: 100, }); return (

Connected: {isConnected ? "Yes" : "No"}

Latest: {lastEvent?.playerName} scored {lastEvent?.score}

    {events.map((e, i) => (
  • {e.playerName}: {e.score}
  • ))}
); } ``` #### All Typed Hooks | Hook | Channel | Payload | | ------------------- | ------------ | ------------------- | | `useScoreUpdates` | `scores` | `ScoreEvent` | | `useGameOverEvents` | `game_over` | `GameOverEvent` | | `useMintEvents` | `mints` | `MintEvent` | | `useTokenUpdates` | `tokens` | `TokenUpdateEvent` | | `useNewGames` | `games` | `NewGameEvent` | | `useNewMinters` | `minters` | `NewMinterEvent` | | `useNewSettings` | `settings` | `NewSettingEvent` | | `useNewObjectives` | `objectives` | `NewObjectiveEvent` | #### Hook Options ```typescript interface UseChannelOptions { gameIds?: number[]; // Filter to specific game IDs (server-side) bufferSize?: number; // Max events to keep in memory (default: 50) enabled?: boolean; // Enable/disable the subscription (default: true) onEvent?: (event: WSChannelPayloadMap[C]) => void; // Callback per event } ``` #### Hook Return Value ```typescript interface UseChannelResult { lastEvent: WSChannelPayloadMap[C] | null; // Most recent event events: WSChannelPayloadMap[C][]; // Buffered event history isConnected: boolean; // WebSocket connection state clear: () => void; // Clear event buffer } ``` #### useChannelSubscription (Generic) All typed hooks wrap `useChannelSubscription`. You can use it directly for type-safe access to any channel: ```tsx import { useChannelSubscription } from "@provable-games/denshokan-sdk/react"; const { lastEvent, events, isConnected } = useChannelSubscription("scores", { gameIds: [1, 2], bufferSize: 25, onEvent: (event) => { console.log("New score:", event.score); }, }); ``` ### Low-Level Hook For raw message access, use `useSubscription`: ```tsx import { useSubscription } from "@provable-games/denshokan-sdk/react"; function LiveLeaderboard({ gameId }: { gameId: number }) { const [scores, setScores] = useState>(new Map()); useSubscription( ["scores", "game_over"], (message) => { if (message.channel === "scores") { const data = message.data as ScoreEvent; setScores(prev => new Map(prev).set(data.tokenId, data.score)); } }, [gameId], ); return (
    {[...scores.entries()] .sort(([, a], [, b]) => b - a) .map(([id, score]) => (
  • Token {id}: {score}
  • ))}
); } ``` #### useSubscription Signature ```typescript function useSubscription( channels: WSChannel[], handler: WSEventHandler, // (message: WSMessage) => void gameIds?: number[], ): void; ``` The hook manages connection lifecycle automatically: * Connects when the component mounts * Re-subscribes if channels or gameIds change * Disconnects when the component unmounts ### Connection Status Monitor WebSocket connection state in your UI: ```tsx import { useConnectionStatus } from "@provable-games/denshokan-sdk/react"; function ConnectionBadge() { const { isConnected } = useConnectionStatus(); return ( {isConnected ? "Live" : "Reconnecting..."} ); } ``` For programmatic use outside React: ```typescript // Check current status const connected = client.wsConnected; // Listen for changes const unsub = client.onWsConnectionChange((connected) => { console.log("WebSocket:", connected ? "connected" : "disconnected"); }); ``` ### Message Format ```typescript interface WSMessage { channel: string; // "scores", "game_over", "tokens", "mints", "games", "minters", "settings", "objectives" data: unknown; // Channel-specific payload (use typed hooks for automatic typing) _timing?: { serverTs: number; // Server timestamp for latency measurement }; } ``` ### Connection Management The WebSocket manager handles: * **Auto-reconnect** with exponential backoff (1s, 2s, 4s, ... capped at 30s) * **Max 10 reconnect attempts** before giving up * **Re-subscription** on reconnect (all active subscriptions are restored) * **Smart retry** — only attempts reconnection if active subscriptions exist #### Manual Connection ```typescript // Connect manually (usually not needed - subscribe() auto-connects) client.connect(); // Check connection status const connected = client.wsConnected; // Disconnect client.disconnect(); ``` ### Combining with Data Hooks A common pattern is to use data hooks for initial load and typed subscription hooks for live updates: ```tsx function GameDashboard({ gameAddress }: { gameAddress: string }) { const { data: tokens, refetch } = useTokens({ gameAddress, gameOver: false, limit: 50, }); // Automatically refetch when a game completes useGameOverEvents({ onEvent: () => refetch(), }); // Show live score updates const { lastEvent: latestScore } = useScoreUpdates(); return (
{latestScore && (

Latest: {latestScore.playerName} scored {latestScore.score}

)}
); } ``` This gives you: * Instant initial render from the API * Automatic list refresh when games complete * Live score ticker from WebSocket ## 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 ```cairo pub const IMETAGAME_CALLBACK_ID: felt252 = 0x3b4312c1422de8c35936cc79948381ab8ef9fd083d8c8e20317164690aa1600; #[starknet::interface] pub trait IMetagameCallback { /// 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 ```cairo #[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; #[storage] struct Storage { #[substorage(v0)] src5: SRC5Component::Storage, leaderboard: Map, // token_id -> score completed: Map, } #[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 { 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 ## Building a Platform > **Source**: [game-components](https://github.com/Provable-Games/game-components) — the Cairo component library used throughout this guide. A platform is any application that embeds EGS games - tournament systems, quest platforms, social apps, MMOs, or arcades. This guide covers how to mint game tokens, read results, and optionally receive automatic callbacks. ### What Is a Platform? In EGS terms, a platform is a contract (or external caller) that: 1. **Mints game tokens** on behalf of users 2. **Validates results** by reading scores and game-over status 3. **Optionally receives callbacks** when game state changes Examples: * **Budokan**: Tournament platform that mints tokens for entrants, ranks by score * **Eternum**: MMO that embeds mini-games as quests, rewards completion * **Any dApp**: Social app that lets users play games and share results ### Minting a Game Token To start a game for a user, call `mint()` on the MinigameToken contract: ```cairo use game_components_token::interface::{ IMinigameTokenMixinDispatcher, IMinigameTokenMixinDispatcherTrait, }; let token = IMinigameTokenMixinDispatcher { contract_address: token_address }; let token_id: felt252 = token.mint(MintParams { game_address: number_guess_address, // Which game to play player_name: Option::Some('Alice'), // Optional display name settings_id: Option::Some(2), // Medium difficulty start: Option::None, // Playable immediately end: Option::None, // Never expires objective_id: Option::Some(1), // Track "First Win" objective context: Option::None, // No platform context client_url: Option::None, // No custom client renderer_address: Option::None, // Default renderer skills_address: Option::None, // No agent skills to: player_address, // Token recipient soulbound: false, // Transferable paymaster: false, // Not gas-sponsored salt: 0, // Uniqueness salt metadata: 0, // Game-specific metadata }); ``` #### MintParams | Field | Type | Description | | ------------------ | ---------------------------- | --------------------------------------------------------- | | `game_address` | `ContractAddress` | The game contract address | | `player_name` | `Option` | Display name for the player | | `settings_id` | `Option` | Game settings configuration | | `start` | `Option` | Delay before game becomes playable (seconds) | | `end` | `Option` | Delay until game expires (seconds) | | `objective_id` | `Option` | Objective to track for this session | | `context` | `Option` | Platform-specific context data | | `client_url` | `Option` | Custom game client URL | | `renderer_address` | `Option` | Custom token renderer | | `skills_address` | `Option` | AI agent skills provider | | `to` | `ContractAddress` | Who receives the token | | `soulbound` | `bool` | Prevent transfers after minting | | `paymaster` | `bool` | Whether minting gas is sponsored | | `salt` | `u16` | Uniqueness salt (allows multiple tokens with same params) | | `metadata` | `u16` | Game-specific metadata bits | #### Batch Minting For tournaments with many entrants: ```cairo let token_ids: Array = token.mint_batch(array![ MintParams { game_address, to: player1, settings_id: Option::Some(2), .. }, MintParams { game_address, to: player2, settings_id: Option::Some(2), .. }, MintParams { game_address, to: player3, settings_id: Option::Some(2), .. }, ]); ``` ### Validating Results After players finish, read their results: ```cairo use game_components_embeddable_game_standard::minigame::interface::{ IMinigameTokenDataDispatcher, IMinigameTokenDataDispatcherTrait, }; let game = IMinigameTokenDataDispatcher { contract_address: game_address }; // Single token let score: u64 = game.score(token_id); let is_over: bool = game.game_over(token_id); // Batch - more gas efficient for leaderboards let scores: Array = game.score_batch(token_ids); let game_overs: Array = game.game_over_batch(token_ids); ``` ### Direct Minting vs MetagameComponent **Direct minting**: Your contract calls `token.mint()` directly. Simple, no extra components needed. **MetagameComponent**: For deeper integration, compose the MetagameComponent to get automatic minter registration, callback support, and context management. | Feature | Direct Mint | MetagameComponent | | ------------------- | ----------- | ----------------- | | Mint tokens | Yes | Yes | | Read scores | Yes | Yes | | Automatic callbacks | No | Yes (via SRC5) | | Minter registration | Manual | Automatic | | Context data | No | Yes | See [MetagameComponent](/embeddable-game-standard/building-a-platform/metagame) for the component-based approach. ### Next Steps * [MetagameComponent](/embeddable-game-standard/building-a-platform/metagame) - Deep integration with automatic minter registration * [Callbacks & Automation](/embeddable-game-standard/building-a-platform/callbacks) - React to score updates and game completions * [Registry & Discovery](/embeddable-game-standard/building-a-platform/registry) - Find and display available games ## MetagameComponent The MetagameComponent provides deeper integration between a platform and the EGS token system. It handles minter registration, callback routing, and optional context management. ### When to Use It Use MetagameComponent when your platform: * Needs to receive automatic callbacks on score updates and game completions * Wants to attach per-token context data (quest info, tournament metadata) * Benefits from automatic minter registration For simple mint-and-read use cases, [direct minting](/embeddable-game-standard/building-a-platform) is sufficient. ### IMetagame Interface ```cairo pub const IMETAGAME_ID: felt252 = 0x7997c74299c045696726f0f7f0165f85817acbb0964e23ff77e11e34eff6f2; #[starknet::interface] pub trait IMetagame { /// Address of the optional context contract fn context_address(self: @TContractState) -> ContractAddress; /// Default MinigameToken address this metagame interacts with fn default_token_address(self: @TContractState) -> ContractAddress; } ``` ### Composing the Component ```cairo #[starknet::contract] mod MyPlatform { use game_components_metagame::metagame::MetagameComponent; use openzeppelin_introspection::src5::SRC5Component; component!(path: MetagameComponent, storage: metagame, event: MetagameEvent); component!(path: SRC5Component, storage: src5, event: SRC5Event); #[abi(embed_v0)] impl MetagameImpl = MetagameComponent::MetagameImpl; #[abi(embed_v0)] impl SRC5Impl = SRC5Component::SRC5Impl; #[storage] struct Storage { #[substorage(v0)] metagame: MetagameComponent::Storage, #[substorage(v0)] src5: SRC5Component::Storage, } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] MetagameEvent: MetagameComponent::Event, #[flat] SRC5Event: SRC5Component::Event, } #[constructor] fn constructor( ref self: ContractState, token_address: ContractAddress, context_address: ContractAddress, ) { self.metagame.initializer(token_address, context_address); } } ``` ### Initialization The MetagameComponent's `initializer` sets up: 1. The default MinigameToken address 2. An optional context contract address 3. Registers the `IMETAGAME_ID` interface via SRC5 ```cairo self.metagame.initializer( token_address, // MinigameToken contract context_address, // Optional context contract (zero = none) ); ``` ### Context Extension Metagames can attach per-token context data. This is useful for: * Tournament metadata (round number, bracket position) * Quest context (which quest, what reward tier) * Platform-specific data #### GameContextDetails Struct ```cairo #[derive(Drop, Serde, Clone)] pub struct GameContextDetails { pub name: ByteArray, // "Season 2 Tournament" pub description: ByteArray, // "Round of 16, Bracket A" pub id: Option, // Optional context ID pub context: Span, // Key-value pairs } #[derive(Drop, Serde, Clone)] pub struct GameContext { pub name: ByteArray, // "bracket" pub value: ByteArray, // "A" } ``` Pass context when minting: ```cairo let token_id = token.mint(MintParams { game_address, context: Option::Some(GameContextDetails { name: "Season 2 Tournament", description: "Round of 16", id: Option::Some(42), context: array![ GameContext { name: "bracket", value: "A" }, GameContext { name: "round", value: "16" }, ].span(), }), // ... other params }); ``` #### IMetagameContext Interface If your metagame implements context, register `IMETAGAME_CONTEXT_ID`: ```cairo pub const IMETAGAME_CONTEXT_ID: felt252 = 0x1633419b5abcc4c0bbed8bd37a363fbe6de5bd25908761ab6dcda6a9b598ca9; #[starknet::interface] pub trait IMetagameContext { fn has_context(self: @TState, token_id: felt252) -> bool; } #[starknet::interface] pub trait IMetagameContextDetails { fn context_details(self: @TState, token_id: felt252) -> GameContextDetails; } ``` ### Minting via MetagameComponent The component provides convenience methods for minting: ```cairo // Mint with context let params = MintGameParams { player_name: Option::Some('Alice'), settings_id: Option::Some(2), start: Option::None, end: Option::None, objective_id: Option::None, context: Option::Some(my_context), client_url: Option::None, renderer_address: Option::None, skills_address: Option::None, to: player_address, soulbound: true, paymaster: false, salt: 0, metadata: 0, }; let token_id = self.metagame.mint(game_address, params); ``` ## Registry & Discovery The MinigameRegistry is the central directory where games register themselves and platforms discover available games. ### IMinigameRegistry Interface ```cairo pub const IMINIGAME_REGISTRY_ID: felt252 = 0x2ff8aa8dda405faf0eb17c5f806d7482b7352cf91fa9668e9ddf030f14b2ee9; #[starknet::interface] pub trait IMinigameRegistry { // Discovery fn game_count(self: @TState) -> u64; fn game_id_from_address(self: @TState, contract_address: ContractAddress) -> u64; fn game_address_from_id(self: @TState, game_id: u64) -> ContractAddress; fn game_metadata(self: @TState, game_id: u64) -> GameMetadata; fn is_game_registered(self: @TState, contract_address: ContractAddress) -> bool; // Registration fn register_game( ref self: TState, creator_address: ContractAddress, name: ByteArray, description: ByteArray, developer: ByteArray, publisher: ByteArray, genre: ByteArray, image: ByteArray, color: Option, client_url: Option, renderer_address: Option, royalty_fraction: Option, skills_address: Option, version: u64, ) -> u64; // View fn skills_address(self: @TState, game_id: u64) -> ContractAddress; // Admin fn set_game_royalty(ref self: TState, game_id: u64, royalty_fraction: u128); // Batch queries fn game_metadata_batch(self: @TState, game_ids: Span) -> Array; fn games_registered_batch( self: @TState, addresses: Span, ) -> Array; fn get_games(self: @TState, start: u64, count: u64) -> Array; fn get_games_by_developer( self: @TState, developer: ByteArray, start: u64, count: u64, ) -> Array; fn get_games_by_publisher( self: @TState, publisher: ByteArray, start: u64, count: u64, ) -> Array; fn get_games_by_genre( self: @TState, genre: ByteArray, start: u64, count: u64, ) -> Array; } ``` ### GameMetadata Struct ```cairo #[derive(Drop, Serde, Clone, starknet::Store)] pub struct GameMetadata { pub contract_address: ContractAddress, pub name: ByteArray, // "Number Guess" pub description: ByteArray, // "Guess the secret number" pub developer: ByteArray, // "Provable Games" pub publisher: ByteArray, // "Provable Games" pub genre: ByteArray, // "Puzzle" pub image: ByteArray, // URL or IPFS hash pub color: ByteArray, // Hex color for UI theming pub client_url: ByteArray, // URL to game client pub renderer_address: ContractAddress, // Custom token renderer pub skills_address: ContractAddress, // AI agent skills provider pub royalty_fraction: u128, // Basis points (500 = 5%) pub created_at: u64, // Block timestamp pub version: u64, // Game version number } ``` ### How Registration Works Games register themselves during initialization. The Denshokan implementation calls `register_game()` from the minigame's `initializer()`: ```cairo fn initializer(ref self: ContractState, ...) { // Register game with the registry self.minigame.initializer( game_creator, game_name, game_description, game_developer, game_publisher, game_genre, game_image, game_color, client_url, renderer_address, settings_address, objectives_address, minigame_token_address, royalty_fraction, skills_address, version, ); } ``` The MinigameComponent internally calls the registry. Upon registration: 1. The game receives an auto-incrementing `game_id` 2. The creator receives an ERC721 "creator token" (token ID = game\_id) 3. The game\_id is packed into all future token IDs for that game #### Creator Tokens The registry contract is itself an ERC721. When a game registers, the registry mints a creator NFT to the `creator_address`. This NFT: * Represents ownership of the game registration * Controls royalty payments (the current owner receives royalties) * Can be transferred to change the royalty recipient ### Discovering Games #### List All Games ```cairo let registry = IMinigameRegistryDispatcher { contract_address: registry_address }; let total = registry.game_count(); let games: Array = registry.get_games(start: 0, count: 20); ``` #### Find by Category ```cairo // By developer let games = registry.get_games_by_developer("Provable Games", 0, 20); // By genre let games = registry.get_games_by_genre("Puzzle", 0, 20); // By publisher let games = registry.get_games_by_publisher("Provable Games", 0, 20); ``` #### Check Registration ```cairo // By address let is_registered: bool = registry.is_game_registered(game_address); // Batch check let statuses: Array = registry.games_registered_batch( array![addr1, addr2, addr3].span() ); ``` #### Resolve IDs and Addresses ```cairo // Address → ID let game_id: u64 = registry.game_id_from_address(game_address); // ID → Address let game_address: ContractAddress = registry.game_address_from_id(game_id); ``` ### Royalties Games can set a royalty fraction (in basis points, where 10000 = 100%): ```cairo // Set 5% royalty at registration register_game(..., royalty_fraction: Option::Some(500)); // Update royalty later (only creator token owner) registry.set_game_royalty(game_id, 300); // 3% ``` The MinigameToken implements ERC-2981 and uses the registry to resolve royalties dynamically: * The royalty receiver is the current owner of the creator NFT * If the creator token is transferred, royalties flow to the new owner ### Hooks Pattern The registry component provides hooks for custom behavior after registration: ```cairo fn after_register_game(game_id: u64, creator_address: ContractAddress) { // Denshokan mints a creator NFT here self.erc721.mint(creator_address, game_id.into()); } ``` This hook pattern allows different registry implementations to add custom logic (minting NFTs, emitting events, updating state) without modifying the core registry. ## Building a Game > **Source**: [game-components](https://github.com/Provable-Games/game-components) — the Cairo component library used throughout this guide. This guide walks you through making your Starknet game compatible with the Embeddable Game Standard. By the end, your game will be mintable, scoreable, and embeddable in any EGS platform. ### What Your Game Must Do At minimum, your game contract must: 1. **Implement `IMinigameTokenData`** - Expose `score()` and `game_over()` so the token contract can read your game's state 2. **Register `IMINIGAME_ID` via SRC5** - So the token contract can discover your game's capabilities 3. **Register with the game Registry** - So platforms can find and display your game 4. **Call `pre_action` and `post_action`** - Wrap every player-facing action function with these hooks to validate playability and sync state back to the token Optionally, your game can also implement: * **`IMinigameTokenSettings`** - Named difficulty configurations (Easy, Medium, Hard) * **`IMinigameTokenObjectives`** - Trackable achievements (First Win, Perfect Game) * **`IMinigameDetails`** - Rich game state for display (move history, board state) ### Key Concept: Token-Keyed Storage In a traditional on-chain game, you key storage by player address: ```cairo // Traditional pattern — keyed by player address scores: Map, ``` In EGS, **all game state is keyed by `token_id` instead of player address**. Each minted token represents a unique game session, and the token ID is the universal key across the entire system — your game contract, the token contract, platforms, and the SDK all reference the same token ID. ```cairo // EGS pattern — keyed by token ID scores: Map, ``` This means a single player can have multiple concurrent game sessions (one per token), and tokens can be transferred between players. To verify the caller owns the token they're acting on, use the `assert_token_ownership` helper in every player-facing function: ```cairo // Verify the caller owns this token before allowing any action self.minigame.assert_token_ownership(token_id); ``` This check calls the Denshokan token contract to confirm `ownerOf(token_id) == get_caller_address()`. ### Prerequisites * [Scarb](https://docs.swmansion.com/scarb/) 2.15.0+ * [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry/) 0.54.1+ * Familiarity with Cairo and `#[starknet::component]` patterns ### Dependency Setup Add game-components to your `Scarb.toml`: ```toml [dependencies] starknet = "2.15.1" game_components_embeddable_game_standard = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } game_components_interfaces = { git = "https://github.com/Provable-Games/game-components", tag = "v1.1.0" } openzeppelin_introspection = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v3.0.0" } [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.55.0" } ``` ### Minimal Contract Skeleton Here's the minimum viable game contract: ```cairo use starknet::ContractAddress; #[starknet::interface] pub trait IMyGame { fn play(ref self: TContractState, token_id: felt252); } #[starknet::contract] mod MyGame { use game_components_embeddable_game_standard::minigame::interface::{IMINIGAME_ID, IMinigameTokenData}; use game_components_embeddable_game_standard::minigame::minigame_component::MinigameComponent; use openzeppelin_introspection::src5::SRC5Component; use starknet::ContractAddress; component!(path: MinigameComponent, storage: minigame, event: MinigameEvent); component!(path: SRC5Component, storage: src5, event: SRC5Event); #[abi(embed_v0)] impl MinigameImpl = MinigameComponent::MinigameImpl; impl MinigameInternalImpl = MinigameComponent::InternalImpl; #[abi(embed_v0)] impl SRC5Impl = SRC5Component::SRC5Impl; #[storage] struct Storage { #[substorage(v0)] minigame: MinigameComponent::Storage, #[substorage(v0)] src5: SRC5Component::Storage, scores: Map, game_overs: Map, } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] MinigameEvent: MinigameComponent::Event, #[flat] SRC5Event: SRC5Component::Event, } #[constructor] fn constructor(ref self: ContractState) { // Register the minigame interface so the token can discover us self.src5.register_interface(IMINIGAME_ID); } // Required: implement IMinigameTokenData #[abi(embed_v0)] impl MinigameTokenDataImpl of IMinigameTokenData { fn score(self: @ContractState, token_id: felt252) -> u64 { self.scores.read(token_id) } fn game_over(self: @ContractState, token_id: felt252) -> bool { self.game_overs.read(token_id) } fn score_batch(self: @ContractState, token_ids: Span) -> Array { let mut results = array![]; for token_id in token_ids { results.append(self.scores.read(*token_id)); }; results } fn game_over_batch(self: @ContractState, token_ids: Span) -> Array { let mut results = array![]; for token_id in token_ids { results.append(self.game_overs.read(*token_id)); }; results } } // Your game logic #[abi(embed_v0)] impl MyGameImpl of super::IMyGame { fn play(ref self: ContractState, token_id: felt252) { // Verify caller owns this token self.minigame.assert_token_ownership(token_id); // Validate token is playable (exists, not expired, not game_over) self.minigame.pre_action(token_id); // Your game logic here self.scores.write(token_id, 100); self.game_overs.write(token_id, true); // Sync score and game_over back to the token contract self.minigame.post_action(token_id); } } } ``` ### Next Steps * [Quick Start](/embeddable-game-standard/building-a-game/quick-start) - Hands-on tutorial building a Number Guess game * [Score & Game Over](/embeddable-game-standard/building-a-game/score-and-game-over) - Deep dive on `IMinigameTokenData` * [Settings & Objectives](/embeddable-game-standard/building-a-game/settings-and-objectives) - Add configurable difficulty and achievements * [Lifecycle & Playability](/embeddable-game-standard/building-a-game/lifecycle) - Token lifecycle, timing, and soulbound tokens ## Lifecycle & Playability Each game token has a lifecycle that controls when it can be played. This is managed through the `Lifecycle` struct and associated checks. ### The Lifecycle Struct ```cairo #[derive(Copy, Drop, Serde)] pub struct Lifecycle { pub start: u64, // Unix timestamp when the game becomes playable (0 = immediately) pub end: u64, // Unix timestamp when the game expires (0 = never) } ``` The lifecycle is set at mint time. The minter provides `start` and `end` as **absolute Unix timestamps**. The contract then converts these to relative delays (`start_delay` from current time, `end_delay` as duration from start) for packing into the token ID. #### Lifecycle Methods ```cairo pub trait LifecycleTrait { /// Has the game expired? fn has_expired(self: @Lifecycle, current_time: u64) -> bool; /// Can the game start? (current time >= start time) fn can_start(self: @Lifecycle, current_time: u64) -> bool; /// Is the game currently playable? (can_start AND not expired) fn is_playable(self: @Lifecycle, current_time: u64) -> bool; /// Validate that start < end (if both are non-zero) fn validate(self: @Lifecycle); } ``` #### Implementation ```cairo impl LifecycleImpl of LifecycleTrait { fn has_expired(self: @Lifecycle, current_time: u64) -> bool { if *self.end == 0 { false } else { current_time >= *self.end } } fn can_start(self: @Lifecycle, current_time: u64) -> bool { if *self.start == 0 { true } else { current_time >= *self.start } } fn is_playable(self: @Lifecycle, current_time: u64) -> bool { self.can_start(current_time) && !self.has_expired(current_time) } fn validate(self: @Lifecycle) { if *self.end != 0 && *self.start > *self.end { panic!("Lifecycle: Start time cannot be greater than end time"); } } } ``` #### Special Values | Start | End | Meaning | | ----- | --- | ----------------------------------- | | 0 | 0 | Playable immediately, never expires | | 0 | T | Playable immediately, expires at T | | T | 0 | Playable after T, never expires | | T1 | T2 | Playable between T1 and T2 | ### Playability Checks The token contract enforces playability with specific error messages for each failure case: | Condition | Error message | | --------------------------- | ------------------------------------------------------------------- | | Game is over | `"Token is not playable - game is over"` | | Objective already completed | `"Token is not playable - objective already completed"` | | Game hasn't started yet | `"Token is not playable - game has not started (now={}, start={})"` | | Game has expired | `"Token is not playable - game has expired (now={}, end={})"` | The timestamp errors include the current block time and the relevant lifecycle bound so callers can see exactly why the check failed. #### Pre-action and Post-action Hooks **Every player-facing action in your game contract must call** `assert_token_ownership`, `pre_action`, and `post_action`. These are available via the `MinigameComponent`'s internal impl: ```cairo impl MinigameInternalImpl = MinigameComponent::InternalImpl; ``` Then in every action function: ```cairo fn play(ref self: ContractState, token_id: felt252) { // Verify the caller owns this token — always check first self.minigame.assert_token_ownership(token_id); // Validates the token is playable: // - game_over is false // - completed_objective is false // - Lifecycle has started and not expired self.minigame.pre_action(token_id); // ... your game logic ... // Syncs state back to the token contract: // - Reads score() and game_over() from your contract // - Emits ScoreUpdate events // - Manages game_over and completed_objective state transitions self.minigame.post_action(token_id); } ``` If your game spans **multiple contracts**, contracts that don't have the `MinigameComponent` can import the library functions directly and pass the token address: ```cairo use game_components_embeddable_game_standard::minigame::minigame::{ assert_token_ownership, pre_action, post_action, }; fn play(ref self: ContractState, token_id: felt252) { let token_address = self.minigame_token_address.read(); assert_token_ownership(token_address, token_id); pre_action(token_address, token_id); // ... game logic ... post_action(token_address, token_id); } ``` :::warning **Avoid early `return` statements** in action functions. If you return before `post_action`, score changes and game completions won't be synced to the token contract. Instead, store results in a variable and return after `post_action`: ```cairo fn guess(ref self: ContractState, token_id: felt252, number: u32) -> i8 { self.minigame.assert_token_ownership(token_id); self.minigame.pre_action(token_id); let result = if number == secret { // win logic... 0_i8 } else { if number < secret { -1_i8 } else { 1_i8 } }; self.minigame.post_action(token_id); result } ``` ::: Under the hood: * `assert_token_ownership` checks the caller is the ERC721 owner of the token * `pre_action` calls `assert_playable(token_id)` on the token contract, which checks game\_over, completed\_objective, and lifecycle bounds with specific error messages * `post_action` calls `update_game(token_id)` on the token contract, which reads `score()` and `game_over()` from your game and updates the token state ### Soulbound Tokens Tokens can be minted as soulbound, preventing transfer after minting: ```cairo #[derive(Drop, Serde)] pub struct MintParams { // ... pub soulbound: bool, // If true, token cannot be transferred // ... } ``` When `soulbound` is `true`: * The token is minted normally (Transfer event from zero address) * All subsequent transfer attempts are rejected * The `soulbound` flag is packed into the token ID (1 bit) This is enforced in the ERC721 `before_update` hook: ```cairo fn before_update( ref self: ContractState, to: ContractAddress, token_id: u256, auth: ContractAddress, ) { // Allow minting (from = zero address) let from = self.erc721._owner_of(token_id); if from.is_zero() { return; } // Block transfers for soulbound tokens let packed = unpack_token_id(token_id.try_into().unwrap()); if packed.soulbound { panic!("Token is soulbound and cannot be transferred"); } } ``` #### When to Use Soulbound * **Tournament entries**: Prevent selling a game session mid-tournament * **Personal achievements**: Achievements should stay with the player * **Anti-gaming**: Prevent score-selling by making tokens non-transferable ### Token Metadata The `TokenMetadata` struct contains all the immutable data about a token: ```cairo #[derive(Copy, Drop, Serde)] pub struct TokenMetadata { pub game_id: u64, // Which game this token is for pub minted_at: u64, // When the token was minted pub settings_id: u32, // Game settings configuration pub lifecycle: Lifecycle, // Start/end timing pub minted_by: u64, // Minter ID (truncated address) pub soulbound: bool, // Transfer restriction pub game_over: bool, // Is the game complete pub completed_objective: bool, // Has the objective been met pub has_context: bool, // Does this token have context data pub objective_id: u32, // Which objective (if any) pub paymaster: bool, // Was minting gas-sponsored pub metadata: u16, // Additional game-defined metadata } ``` Most of these fields are immutable - they're packed into the token ID at mint time. Only `game_over` and `completed_objective` are mutable (stored separately in `TokenMutableState`). ### Lifecycle in Practice ``` Time ────────────────────────────────────────────▶ │← not playable →│← playable →│← expired →│ ▲ ▲ ▲ minted start end ``` **Tournament use case**: A tournament creates tokens with `start` = tournament start time and `end` = tournament end time. Players can only play during the tournament window. After expiry, scores are finalized. **Quest use case**: A quest creates tokens with `start` = 0 (immediate) and `end` = quest deadline. Players can start anytime but must finish before the deadline. **Perpetual use case**: Tokens with `start` = 0 and `end` = 0 are always playable. Score accumulates indefinitely. ## Quick Start: Number Guess Game This tutorial walks through building a complete EGS-compliant game based on the [Number Guess](https://github.com/Provable-Games/number-guess) reference implementation. ### What We're Building A number-guessing game where: * The contract generates a secret number within a configurable range * The player guesses until they find it (or run out of attempts) * Score is calculated based on efficiency (fewer guesses = higher score) * Settings define difficulty (range size, max attempts) * Objectives track achievements (first win, quick thinker, perfect game) ### Step 1: Compose Components ```cairo #[starknet::contract] mod NumberGuess { use game_components_embeddable_game_standard::minigame::MinigameComponent; use game_components_embeddable_game_standard::minigame::objectives::ObjectivesComponent; use game_components_embeddable_game_standard::minigame::settings::SettingsComponent; use openzeppelin_introspection::src5::SRC5Component; component!(path: MinigameComponent, storage: minigame, event: MinigameEvent); component!(path: ObjectivesComponent, storage: objectives, event: ObjectivesEvent); component!(path: SettingsComponent, storage: settings, event: SettingsEvent); component!(path: SRC5Component, storage: src5, event: SRC5Event); #[abi(embed_v0)] impl SRC5Impl = SRC5Component::SRC5Impl; #[storage] struct Storage { #[substorage(v0)] minigame: MinigameComponent::Storage, #[substorage(v0)] objectives: ObjectivesComponent::Storage, #[substorage(v0)] settings: SettingsComponent::Storage, #[substorage(v0)] src5: SRC5Component::Storage, // ... your game-specific storage fields here } // ... } ``` ### Step 2: Implement IMinigameTokenData This is the core requirement. The token contract reads your game's state through this interface: ```cairo use game_components_embeddable_game_standard::minigame::interface::{IMINIGAME_ID, IMinigameTokenData}; #[abi(embed_v0)] impl MinigameTokenDataImpl of IMinigameTokenData { fn score(self: @ContractState, token_id: felt252) -> u64 { self.scores.read(token_id) } fn game_over(self: @ContractState, token_id: felt252) -> bool { self.game_overs.read(token_id) } fn score_batch(self: @ContractState, token_ids: Span) -> Array { let mut results = array![]; for token_id in token_ids { results.append(self.scores.read(*token_id)); }; results } fn game_over_batch(self: @ContractState, token_ids: Span) -> Array { let mut results = array![]; for token_id in token_ids { results.append(self.game_overs.read(*token_id)); }; results } } ``` ### Step 3: Wrap Actions with EGS Hooks Every player-facing action **must**: 1. **`assert_token_ownership`** — verify the caller owns the token. A player should never be able to act on a token they don't own. This should be the **first** check in every action. 2. **`pre_action`** — validate the token is playable (exists, not expired, game not over) 3. **`post_action`** — sync score and game\_over state back to the token contract :::warning Without these checks, players could act on tokens they don't own, the token contract won't know when scores change or games end, and players could take actions on expired or completed tokens. ::: #### Single-contract games (component methods) If your game logic lives in the same contract that was initialized with the `MinigameComponent`, you can use the component's internal methods directly: ```cairo fn play(ref self: ContractState, token_id: felt252) { // 1. Verify caller owns the token self.minigame.assert_token_ownership(token_id); // 2. Validate token is playable self.minigame.pre_action(token_id); // ... your game logic here ... // Update self.scores, self.game_overs, etc. // 3. Sync state back to the token contract self.minigame.post_action(token_id); } ``` :::note Avoid early `return` statements so that `post_action` always runs at the end. Store results in a variable and return after `post_action`. ::: #### Multi-contract games (library functions) If your game spans multiple contracts (e.g. separate combat, exploration, and market systems), the contracts that don't have the `MinigameComponent` won't have access to `self.minigame`. Instead, import and call the **library functions** directly, passing the Denshokan Token address (your `minigame_token_address`): ```cairo use game_components_embeddable_game_standard::minigame::minigame::{ assert_token_ownership, pre_action, post_action, }; ``` Each system contract stores the token address (set during construction) and passes it explicitly: ```cairo fn start_game(ref self: ContractState, adventurer_id: felt252, weapon: u8) { let minigame_token_address = self.minigame_token_address.read(); assert_token_ownership(minigame_token_address, adventurer_id); pre_action(minigame_token_address, adventurer_id); // ... game logic ... post_action(minigame_token_address, adventurer_id); } ``` This pattern lets any number of contracts validate and update token state without needing the component — they just need the token contract address. ### Step 4: Add Settings (Optional) Settings and objectives let platforms and players configure difficulty and track achievements. You define the schema that makes sense for your game — the `SettingsComponent` and `ObjectivesComponent` handle the registry-facing metadata while your contract stores the actual gameplay parameters. See [Settings & Objectives](/embeddable-game-standard/building-a-game/settings-and-objectives) for the full implementation guide, or the [Number Guess source code](https://github.com/Provable-Games/number-guess/blob/main/contracts/packages/number_guess/src/number_guess.cairo) for a working example. ### Step 5: Register with Registry Your game's initializer should register it with the game registry. The `minigame_token_address` parameter is the **Denshokan Token** contract — see [Deployed Contracts](/embeddable-game-standard/architecture#deployed-contracts) for the current Sepolia address. :::tip Use the shared Denshokan contract as your `minigame_token_address` to get automatic indexing, API access, SDK support, and platform compatibility. See [Denshokan Token = minigame\_token\_address](/embeddable-game-standard/architecture#denshokan-token--minigame_token_address) for details. ::: ```cairo use game_components_embeddable_game_standard::minigame::MinigameComponent; #[abi(embed_v0)] impl InitImpl of INumberGuessInit { fn initializer( ref self: ContractState, game_creator: ContractAddress, game_name: ByteArray, game_description: ByteArray, game_developer: ByteArray, game_publisher: ByteArray, game_genre: ByteArray, game_image: ByteArray, game_color: Option, client_url: Option, renderer_address: Option, settings_address: Option, objectives_address: Option, minigame_token_address: ContractAddress, // Denshokan Token address royalty_fraction: Option, skills_address: Option, version: u64, ) { // Register with the game registry via the MinigameComponent self.minigame.initializer( game_creator, game_name, game_description, game_developer, game_publisher, game_genre, game_image, game_color, client_url, renderer_address, settings_address, objectives_address, minigame_token_address, royalty_fraction, skills_address, version, ); // ... set up your game's default settings, objectives, etc. } } ``` ### Step 6: Test ```cairo #[test] fn test_number_guess_basic() { // Deploy the game and token contracts let (game, token) = deploy_number_guess_with_token(); // Mint a token with Easy settings (settings_id=1) let token_id = mint_token(token, settings_id: 1, to: PLAYER()); // Start a new game — settings are extracted from the packed token ID game.new_game(token_id); // Binary search for the secret number let (mut low, mut high) = game.get_range(token_id); loop { let mid = (low + high) / 2; let result = game.guess(token_id, mid); if result == 0 { break; } // Correct! if result == -1 { low = mid + 1; } // Too low else { high = mid - 1; } // Too high }; // Verify the game recorded a score let score = game.score(token_id); assert!(score > 0, "Should have a score after winning"); // Verify game state assert!(game.games_won(token_id) == 1, "Should have 1 win"); } ``` :::note This test is illustrative — the actual [Number Guess test suite](https://github.com/Provable-Games/number-guess/blob/main/contracts/packages/number_guess/src/tests/test_number_guess.cairo) covers deployment helpers, multiple difficulty levels, objective completion, and edge cases. ::: ### Summary To make your game EGS-compliant: 1. **Required**: Implement `IMinigameTokenData` (score + game\_over) 2. **Required**: Register `IMINIGAME_ID` via SRC5 3. **Required**: Register with the game Registry 4. **Required**: Call `assert_token_ownership`, `pre_action`, and `post_action` in every player-facing action 5. **Optional**: Implement `IMinigameTokenSettings` for custom difficulty 6. **Optional**: Implement `IMinigameTokenObjectives` for achievements 7. **Optional**: Implement `IMinigameDetails` for rich game state display For a complete reference implementation, see the [Number Guess source code](https://github.com/Provable-Games/number-guess/blob/main/contracts/packages/number_guess/src/number_guess.cairo). ## Score & Game Over The `IMinigameTokenData` trait is the core interface every EGS game must implement. It defines how the token contract reads your game's state. ### IMinigameTokenData ```cairo #[starknet::interface] pub trait IMinigameTokenData { /// Returns the current score for a token. fn score(self: @TState, token_id: felt252) -> u64; /// Returns whether the game is over for a token. fn game_over(self: @TState, token_id: felt252) -> bool; /// Batch version of score() for efficient multi-token queries. fn score_batch(self: @TState, token_ids: Span) -> Array; /// Batch version of game_over() for efficient multi-token queries. fn game_over_batch(self: @TState, token_ids: Span) -> Array; } ``` #### Score Semantics * **Type**: `u64` (0 to 18,446,744,073,709,551,615) * **Meaning**: Game-defined. Can represent points, time survived, distance traveled, or any numeric metric * **Cumulative vs per-game**: Your choice. Number Guess accumulates score across multiple rounds. Tic-Tac-Toe counts wins (1 per win) * **Zero is valid**: A score of 0 means the player hasn't scored yet (or the game defines 0 as a valid score) #### Game Over Semantics * **Type**: `bool` * **`false`**: The game session is still active. The player can continue playing * **`true`**: The game session has ended. The final score is locked * **Irreversible**: Once `game_over` returns `true` for a token, it should not revert to `false` #### Batch Methods Batch methods exist for gas efficiency. When a platform needs to check scores for 50 tokens, one `score_batch()` call is far cheaper than 50 individual `score()` calls. Your implementation should iterate over the span: ```cairo fn score_batch(self: @ContractState, token_ids: Span) -> Array { let mut results = array![]; for token_id in token_ids { results.append(self.scores.read(*token_id)); }; results } ``` ### The update\_game() Flow When `update_game(token_id)` is called on the MinigameToken contract: 1. Token looks up the game contract address for this token 2. Token calls `score(token_id)` on your game via `IMinigameTokenData` 3. Token calls `game_over(token_id)` on your game 4. If the score changed, token emits a `ScoreUpdate` event 5. If `game_over` is `true` and wasn't before, token emits a `GameOver` event 6. If the minter supports `IMetagameCallback` (SRC5 check), token dispatches callbacks ``` MinigameToken Your Game │ │ │── score(token_id) ────────────────▶│ │◀── returns 150 ───────────────────│ │ │ │── game_over(token_id) ────────────▶│ │◀── returns true ──────────────────│ │ │ │── [emit ScoreUpdate] ──────────▶ (indexed) │── [emit GameOver] ─────────────▶ (indexed) │── [callback to minter] ────────▶ (if supported) ``` #### Who Calls update\_game()? Anyone can call it. Common patterns: * **The game itself** calls it after each action (most responsive) * **The player** calls it when ready to finalize * **A platform** calls it to sync before checking results * **An automated keeper** calls it periodically ### Optional: IMinigameDetails For richer display in frontends, implement `IMinigameDetails`: ```cairo #[starknet::interface] pub trait IMinigameDetails { /// Human-readable name for a token's game state fn token_name(self: @TState, token_id: felt252) -> ByteArray; /// Description of the current game state fn token_description(self: @TState, token_id: felt252) -> ByteArray; /// Key-value pairs describing game state (e.g., moves, board, items) fn game_details(self: @TState, token_id: felt252) -> Array; /// Batch versions fn token_name_batch(self: @TState, token_ids: Span) -> Array; fn token_description_batch(self: @TState, token_ids: Span) -> Array; fn game_details_batch(self: @TState, token_ids: Span) -> Array>; } ``` The `GameDetail` struct: ```cairo #[derive(Drop, Serde)] pub struct GameDetail { pub name: ByteArray, // e.g., "Board State", "Guesses Remaining" pub value: ByteArray, // e.g., "X.O|..X|O..", "3" } ``` This allows frontends to display game-specific information without hardcoding knowledge of each game's internals. ### Design Guidance **Keep score() and game\_over() cheap.** These are view functions called frequently. Read from storage directly; don't compute on the fly. **Make game\_over permanent.** Once a game ends, the result should be immutable. This ensures leaderboards and tournament results are trustworthy. **Use cumulative scoring carefully.** If your game allows multiple rounds per token (like Number Guess), decide whether score accumulates or resets. Document this for platform integrators. ## Settings & Objectives Settings let you define configurable game modes. Objectives let you define trackable achievements. Both are optional but powerful ways to extend your game's composability. ### Settings Settings are named configurations that define how a game plays. For example, a Number Guess game might have: | Settings ID | Name | Range | Max Attempts | | ----------- | ------ | ------ | ------------ | | 1 | Easy | 1-10 | Unlimited | | 2 | Medium | 1-100 | 10 | | 3 | Hard | 1-1000 | 10 | When a token is minted, the minter specifies a `settings_id`. The game reads that ID to determine the rules for that session. #### IMinigameTokenSettings ```cairo #[starknet::interface] pub trait IMinigameTokenSettings { /// Create a new settings configuration. Returns the new settings_id. /// This is intentionally permissionless - anyone can create settings. fn create_settings( ref self: TState, name: ByteArray, description: ByteArray, settings: Span, ) -> u32; /// Check if a settings_id exists. fn settings_exists(self: @TState, settings_id: u32) -> bool; /// Get the total number of settings configurations. fn settings_count(self: @TState) -> u32; /// Get the details of a settings configuration. fn settings_details(self: @TState, settings_id: u32) -> GameSettingDetails; /// Batch version of settings_details. fn settings_details_batch( self: @TState, settings_ids: Span, ) -> Array; } ``` #### GameSettingDetails Struct ```cairo #[derive(Drop, Serde)] pub struct GameSettingDetails { pub name: ByteArray, // "Hard Mode" pub description: ByteArray, // "Range 1-1000, 10 attempts max" pub settings: Span, // Key-value pairs } #[derive(Drop, Serde)] pub struct GameSetting { pub name: ByteArray, // "range_min", "range_max", "max_attempts" pub value: ByteArray, // "1", "1000", "10" } ``` #### Implementation Pattern Your game stores the parsed setting values in its own storage, then delegates the metadata to `SettingsComponent`: ```cairo use game_components_embeddable_game_standard::minigame::settings::SettingsComponent; component!(path: SettingsComponent, storage: settings, event: SettingsEvent); #[abi(embed_v0)] impl SettingsImpl of IMinigameTokenSettings { fn create_settings( ref self: ContractState, name: ByteArray, description: ByteArray, settings: Span, ) -> u32 { // Parse the key-value pairs into your game's data let mut min: u32 = 1; let mut max: u32 = 100; let mut max_attempts: u32 = 0; for setting in settings { if *setting.name == "range_min" { min = parse_u32(*setting.value); } else if *setting.name == "range_max" { max = parse_u32(*setting.value); } else if *setting.name == "max_attempts" { max_attempts = parse_u32(*setting.value); } }; assert!(max > min, "max must be greater than min"); // Store in component (handles ID allocation and metadata) let settings_id = self.settings.create(name, description, settings); // Store parsed values in game-specific storage self.settings_data.write(settings_id, (min, max, max_attempts)); settings_id } fn settings_exists(self: @ContractState, settings_id: u32) -> bool { self.settings.exists(settings_id) } fn settings_count(self: @ContractState) -> u32 { self.settings.count() } fn settings_details(self: @ContractState, settings_id: u32) -> GameSettingDetails { self.settings.details(settings_id) } fn settings_details_batch( self: @ContractState, settings_ids: Span, ) -> Array { self.settings.details_batch(settings_ids) } } ``` #### Permissionless Creation Settings creation is intentionally permissionless. Anyone can create a new difficulty configuration. This enables: * Community-created challenge modes * Tournament organizers defining custom rules * Seasonal events with special settings The game validates inputs (e.g., `max > min`) but doesn't restrict who can create them. ### Objectives Objectives are trackable achievements that can be completed during gameplay. They provide structured goals beyond just scoring. #### IMinigameTokenObjectives ```cairo #[starknet::interface] pub trait IMinigameTokenObjectives { /// Create a new objective. Returns the new objective_id. /// Permissionless - anyone can create objectives. fn create_objective( ref self: TState, name: ByteArray, description: ByteArray, objectives: Span, ) -> u32; /// Check if an objective exists. fn objective_exists(self: @TState, objective_id: u32) -> bool; /// Get the total number of objectives. fn objectives_count(self: @TState) -> u32; /// Check if a token has completed a specific objective. fn completed_objective(self: @TState, token_id: felt252, objective_id: u32) -> bool; /// Get the details of an objective. fn objectives_details(self: @TState, objective_id: u32) -> GameObjectiveDetails; /// Batch versions fn objectives_details_batch( self: @TState, objective_ids: Span, ) -> Array; fn completed_objective_batch( self: @TState, token_ids: Span, objective_id: u32, ) -> Array; } ``` #### GameObjectiveDetails Struct ```cairo #[derive(Drop, Serde)] pub struct GameObjectiveDetails { pub name: ByteArray, // "Quick Thinker" pub description: ByteArray, // "Win a game in 5 or fewer guesses" pub objectives: Span, // Structured criteria } #[derive(Drop, Serde)] pub struct GameObjective { pub name: ByteArray, // "type", "threshold" pub value: ByteArray, // "2", "5" } ``` #### Example: Number Guess Objectives ``` Objective 1: "First Win" type = 1 (Win any game) Objective 2: "Quick Thinker" type = 2, threshold = 5 (Win in 5 or fewer guesses) Objective 3: "Lucky Guess" type = 3 (Perfect 1-guess win) ``` #### Checking Objective Completion When a game event occurs (e.g., player wins), check all active objectives for the token: ```cairo fn check_objectives(ref self: ContractState, token_id: felt252) { let objective_count = self.objectives.count(); let mut i: u32 = 1; loop { if i > objective_count { break; } // Skip already completed objectives for this token if !self.token_objectives_completed.read((token_id, i)) { let details = self.objective_data.read(i); let completed = match details.objective_type { 1 => true, // Win: always true if we got here 2 => self.current_guess_count.read(token_id) <= details.threshold, 3 => self.current_guess_count.read(token_id) == 1, _ => false, }; if completed { self.token_objectives_completed.write((token_id, i), true); } } i += 1; }; } ``` #### Objectives vs Settings | Aspect | Settings | Objectives | | ------------ | --------------------------------------------- | ------------------------------- | | **Purpose** | Define how the game plays | Define what the player achieves | | **When set** | At mint time (settings\_id in token) | Checked during gameplay | | **Scope** | Per-token (one settings\_id per game session) | Per-token per-objective | | **Creation** | Permissionless | Permissionless | | **Example** | "Hard Mode: range 1-1000, 10 attempts" | "Win in under 5 guesses" | ### Embedding Components To add settings and objectives to your game, compose the components: ```cairo component!(path: SettingsComponent, storage: settings, event: SettingsEvent); component!(path: ObjectivesComponent, storage: objectives, event: ObjectivesEvent); #[storage] struct Storage { #[substorage(v0)] settings: SettingsComponent::Storage, #[substorage(v0)] objectives: ObjectivesComponent::Storage, // ... your game storage } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] SettingsEvent: SettingsComponent::Event, #[flat] ObjectivesEvent: ObjectivesComponent::Event, // ... } ``` Initialize default settings and objectives in your constructor or initializer. ## Advanced Topics Deep dives into the technical details behind EGS. ### Topics * [Packed Token IDs](/embeddable-game-standard/advanced/packed-token-ids) - How 13 fields are encoded into a single `felt252`, the bit layout, storage optimization trade-offs, and client-side decoding * [SRC5 Interface Discovery](/embeddable-game-standard/advanced/src5) - How EGS uses Starknet's interface introspection standard, computing interface IDs, the complete ID table, and safe dispatch patterns ## Packed Token IDs EGS token IDs are not simple incrementing integers. They are `felt252` values (251 bits) that encode 13 fields of immutable game data directly into the ID. This means you can read a token's game, settings, timing, and flags without any storage lookups. ### Bit Layout Token IDs are packed into two `u128` halves for gas-efficient encoding/decoding via native Sierra `u128_safe_divmod` hints. #### Low u128 (bits 0–127) | Bits | Field | Size | Max Value | | ------- | ------------- | ------- | ----------------------- | | 0-29 | `game_id` | 30 bits | \~1 billion games | | 30-69 | `minted_by` | 40 bits | \~1 trillion minters | | 70-99 | `settings_id` | 30 bits | \~1 billion settings | | 100-124 | `start_delay` | 25 bits | \~388 days (in seconds) | | 125 | `soulbound` | 1 bit | true/false | | 126 | `has_context` | 1 bit | true/false | | 127 | `paymaster` | 1 bit | true/false | #### High u128 (bits 128–250) | Bits | Field | Size | Max Value | | ------- | -------------- | ------- | ------------------------- | | 128-162 | `minted_at` | 35 bits | Unix seconds until \~3058 | | 163-187 | `end_delay` | 25 bits | \~388 days (in seconds) | | 188-217 | `objective_id` | 30 bits | \~1 billion objectives | | 218-227 | `tx_hash` | 10 bits | Uniqueness from tx hash | | 228-237 | `salt` | 10 bits | User-provided salt | | 238-250 | `metadata` | 13 bits | Game-defined metadata | **Total: 251 bits** (fits exactly in a `felt252`) ### Why Pack Token IDs? #### Storage Optimization Without packing, each token would need 13 storage slots to hold its metadata. With packing, the metadata **is** the token ID - zero additional storage for immutable fields. #### Query Efficiency The Denshokan Viewer contract can filter tokens by game, settings, or minter by unpacking the ID in-memory, without reading any extra storage. This makes on-chain queries significantly cheaper. #### Client-side Decoding Frontends can decode a token ID instantly without any RPC calls: ```typescript import { decodePackedTokenId } from "@provable-games/denshokan-sdk"; const decoded = decodePackedTokenId("0x123abc..."); console.log(decoded.gameId); // 1 console.log(decoded.settingsId); // 2 console.log(decoded.mintedAt); // Date object console.log(decoded.soulbound); // false ``` ### Cairo Packing The packing splits fields across two `u128` halves, which aligns with Sierra's native division hints for efficient unpacking: ```cairo pub fn pack_token_id( game_id: u32, minted_by: u64, settings_id: u32, start_delay: u32, soulbound: bool, has_context: bool, paymaster: bool, minted_at: u64, end_delay: u32, objective_id: u32, tx_hash: u16, salt: u16, metadata: u16, ) -> felt252 { // Low u128: game_id | minted_by | settings_id | start_delay | flags let mut low: u128 = game_id.into(); low = low | (Into::::into(minted_by) * TWO_POW_30); low = low | (Into::::into(settings_id) * TWO_POW_70); low = low | (Into::::into(start_delay) * TWO_POW_100); if soulbound { low = low | TWO_POW_125; } if has_context { low = low | TWO_POW_126; } if paymaster { low = low | TWO_POW_127; } // High u128: minted_at | end_delay | objective_id | tx_hash | salt | metadata let mut high: u128 = minted_at.into(); high = high | (Into::::into(end_delay) * TWO_POW_35); high = high | (Into::::into(objective_id) * TWO_POW_60); high = high | (Into::::into(tx_hash) * TWO_POW_90); high = high | (Into::::into(salt) * TWO_POW_100); high = high | (Into::::into(metadata) * TWO_POW_110); let packed: u256 = u256 { low, high }; packed.try_into().unwrap() } ``` ### Cairo Unpacking ```cairo pub fn unpack_token_id(token_id: felt252) -> PackedTokenId { let packed: u256 = token_id.into(); let low: u128 = packed.low; let high: u128 = packed.high; // Unpack low u128 PackedTokenId { game_id: (low & MASK_30).try_into().unwrap(), minted_by: ((low / TWO_POW_30) & MASK_40).try_into().unwrap(), settings_id: ((low / TWO_POW_70) & MASK_30).try_into().unwrap(), start_delay: ((low / TWO_POW_100) & MASK_25).try_into().unwrap(), soulbound: ((low / TWO_POW_125) & 1) == 1, has_context: ((low / TWO_POW_126) & 1) == 1, paymaster: ((low / TWO_POW_127) & 1) == 1, // Unpack high u128 minted_at: (high & MASK_35).try_into().unwrap(), end_delay: ((high / TWO_POW_35) & MASK_25).try_into().unwrap(), objective_id: ((high / TWO_POW_60) & MASK_30).try_into().unwrap(), tx_hash: ((high / TWO_POW_90) & MASK_10).try_into().unwrap(), salt: ((high / TWO_POW_100) & MASK_10).try_into().unwrap(), metadata: ((high / TWO_POW_110) & MASK_13).try_into().unwrap(), } } ``` ### TypeScript Decoding ```typescript export function decodePackedTokenId(tokenId: string | bigint): DecodedTokenId { const id = BigInt(tokenId); return { tokenId: id, // Low u128 (bits 0-127) gameId: Number(id & 0x3FFFFFFFn), // 30 bits mintedBy: (id >> 30n) & 0xFFFFFFFFFFn, // 40 bits settingsId: Number((id >> 70n) & 0x3FFFFFFFn), // 30 bits startDelay: Number((id >> 100n) & 0x1FFFFFFn), // 25 bits soulbound: Boolean((id >> 125n) & 1n), // 1 bit hasContext: Boolean((id >> 126n) & 1n), // 1 bit paymaster: Boolean((id >> 127n) & 1n), // 1 bit // High u128 (bits 128-250) mintedAt: new Date(Number((id >> 128n) & 0x7FFFFFFFFn) * 1000), // 35 bits endDelay: Number((id >> 163n) & 0x1FFFFFFn), // 25 bits objectiveId: Number((id >> 188n) & 0x3FFFFFFFn), // 30 bits txHash: Number((id >> 218n) & 0x3FFn), // 10 bits salt: Number((id >> 228n) & 0x3FFn), // 10 bits metadata: Number((id >> 238n) & 0x1FFFn), // 13 bits }; } ``` ### Trade-offs **Advantages:** * Zero storage cost for immutable metadata * Instant client-side decoding * Efficient on-chain filtering * Token ID itself is a provable commitment to game parameters **Limitations:** * Fields have fixed bit widths (e.g., `game_id` limited to \~1 billion) * `minted_by` is truncated to 40 bits (not a full address) * `start` and `end` are stored as delays, not absolute timestamps * Token IDs are not human-readable * Cannot change immutable fields after minting ## SRC5 Interface Discovery [SRC5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) is Starknet's standard for interface introspection (equivalent to Ethereum's ERC-165). EGS uses SRC5 extensively for safe, optional integration between contracts. ### What SRC5 Does Every SRC5-compliant contract implements: ```cairo #[starknet::interface] pub trait ISRC5 { fn supports_interface(self: @TState, interface_id: felt252) -> bool; } ``` Before calling an interface method on another contract, you check if it supports that interface: ```cairo let src5 = ISRC5Dispatcher { contract_address: target }; if src5.supports_interface(IMETAGAME_CALLBACK_ID) { // Safe to dispatch callback let callback = IMetagameCallbackDispatcher { contract_address: target }; callback.on_game_action(token_id, score); } ``` ### How EGS Uses SRC5 #### Game Discovery The token contract checks if a game implements `IMinigameTokenData` before reading scores: ```cairo if src5.supports_interface(IMINIGAME_ID) { let game = IMinigameTokenDataDispatcher { contract_address: game_addr }; let score = game.score(token_id); } ``` #### Optional Extensions The token checks which extensions a game supports: ```cairo // Does this game have settings? if src5.supports_interface(IMINIGAME_TOKEN_SETTINGS_ID) { // Safe to query settings } // Does this game have objectives? if src5.supports_interface(IMINIGAME_TOKEN_OBJECTIVES_ID) { // Safe to query objectives } ``` #### Callback Routing When syncing game state, the token checks if the minter wants callbacks: ```cairo if src5.supports_interface(IMETAGAME_CALLBACK_ID) { // Dispatch score/game_over/objective callbacks } else { // Skip callbacks - minter doesn't support them } ``` ### Interface ID Table | Interface | ID | Used By | | -------------------------- | -------------------------------------------------------------------- | -------------------------------- | | `IMinigameToken` | `0x39b3cf4989f3d1493c059433fb1d41a763c166ef2f8f6bd801823f27414bdbc` | Token contracts | | `IMinigame` | `0x1050f9a792acfa175e26783e365e1b0b38ff3440b960d0ffdfc0ff9d7dc9f2a` | Game contracts | | `IMinigameTokenSettings` | `0x229ba85053f3653daaa2e0d3a9f9296e6d3eae099557c7610822f5b556f1bc8` | Games with settings | | `IMinigameTokenObjectives` | `0x2c9b37fb2982c9480e67f2da4c7730a8cde17b5fb021f3d530305f2f3a0b929` | Games with objectives | | `IMinigameTokenContext` | `0x02b329e82f6f6b94f8949b36c5dc95acf86c6083b08d99bc81e399b4b0e8d19a` | Tokens with context | | `IMinigameTokenMinter` | `0x2198424b9ee68499f53f33ad952598acbd6d141af6c6863c9c56b117063acca` | Token minting | | `IMetagame` | `0x7997c74299c045696726f0f7f0165f85817acbb0964e23ff77e11e34eff6f2` | Platform contracts | | `IMetagameCallback` | `0x3b4312c1422de8c35936cc79948381ab8ef9fd083d8c8e20317164690aa1600` | Platforms that receive callbacks | | `IMetagameContext` | `0x1633419b5abcc4c0bbed8bd37a363fbe6de5bd25908761ab6dcda6a9b598ca9` | Platforms with context data | | `IMinigameRegistry` | `0x2ff8aa8dda405faf0eb17c5f806d7482b7352cf91fa9668e9ddf030f14b2ee9` | Registry contracts | | `IMinigameSettings` | `0x1a58ab3ee416cc018f93236fd0bb995de89ee536626c268491121e51a46a0f4` | Settings components | | `IMinigameObjectives` | `0xac0aaa451454d78741d9fafe803b69c8b31d073156020b08496104356db5e5` | Objectives components | | `IMinigameTokenRenderer` | `0x2899a752da88d6acf4ed54cc644238f3956b4db3c9885d3ad94f6149f0ec465` | Custom token renderer | | `ISkills` | `0x39fae678a19cd9b999da1d9ad54f00e686406974a4ced6f7eb51c8959aabd98` | Agent skills provider | | `IMinigameTokenSkills` | `0x33846532a9b9e859675aaa1a6c3ae6a45ccf1920c83e2d34898fa2f116201b3` | Per-token skills override | | `ILeaderboard` | `0x381684b...` | Leaderboard components | | `IEntryRequirement` | `0x153355c...` | Entry gating | | `IEntryFee` | `0x2386e28...` | Entry fee handling | | `IPrize` | `0x2a7a3be...` | Prize management | ### Computing Interface IDs Interface IDs in Cairo are computed from the trait's selector. The constant is defined in the interface module: ```cairo // In game_components_embeddable_game_standard::minigame::interface pub const IMINIGAME_ID: felt252 = 0x1050f9a792acfa175e26783e365e1b0b38ff3440b960d0ffdfc0ff9d7dc9f2a; ``` ### Registering Interfaces When your contract implements an EGS interface, register it in the constructor: ```cairo use openzeppelin_introspection::src5::SRC5Component; use game_components_embeddable_game_standard::minigame::interface::IMINIGAME_ID; component!(path: SRC5Component, storage: src5, event: SRC5Event); #[constructor] fn constructor(ref self: ContractState) { self.src5.register_interface(IMINIGAME_ID); } ``` Components like `MinigameComponent` and `MetagameComponent` register their interfaces automatically during initialization. ### Safe Dispatch Pattern Always check before calling: ```cairo use openzeppelin_introspection::src5::{ISRC5Dispatcher, ISRC5DispatcherTrait}; fn safe_callback( minter_address: ContractAddress, token_id: u256, score: u64, ) { // Check if minter supports callbacks let src5 = ISRC5Dispatcher { contract_address: minter_address }; if !src5.supports_interface(IMETAGAME_CALLBACK_ID) { return; // Skip - minter doesn't support callbacks } // Safe to dispatch let callback = IMetagameCallbackDispatcher { contract_address: minter_address }; callback.on_game_action(token_id, score); } ``` This pattern prevents: * Reverts when calling unsupported interfaces * Wasted gas on contracts that don't need callbacks * Breaking changes when new interfaces are added ## Dark Shuffle: FAQ Find answers to the most common questions about Dark Shuffle. If you have a question not covered here, check the relevant guide section or reach out via the app's support channels. ### General **What is Dark Shuffle?** Dark Shuffle is a fully onchain deck-building roguelike game where you draft cards, battle monsters, and navigate a branching map—all powered by smart contracts for fairness and transparency. **Do I need a wallet to play?** Yes, you'll need a compatible wallet to play. See the [Onboarding Guide](/darkshuffle/guide/onboarding) for setup instructions. **Is Dark Shuffle free to play?** Yes! You can start and play games for free. Some [Budokan](/budokan) tournaments or special events may have entry requirements. ### Gameplay & Progression **How do I start a new game?** Go to the main menu and select "Start Game." You'll be guided through onboarding if it's your first time. See [Onboarding](/darkshuffle/guide/onboarding). **What happens if I lose all my health?** Your run ends and you'll need to start a new game. Try a different strategy or deck next time! **Can I play multiple games at once?** Yes, you can have multiple active games, each tracked separately. ### Drafting & Cards **How does the draft phase work?** You'll be presented with random card options each round. Pick one per round until your deck is complete. See the [Draft Guide](/darkshuffle/guide/draft). **What do card rarities mean?** Rarer cards are more powerful but appear less often in drafts. See [Cards Guide](/darkshuffle/guide/cards) for details. **What are card effects?** Cards can have effects that trigger on play, attack, or death. Combining effects can create powerful combos. More in the [Cards Guide](/darkshuffle/guide/cards). ### Battles **How do battles work?** Drag cards from your hand to the battlefield to attack the beast. Use your energy wisely and plan for card effects. See the [Battle Guide](/darkshuffle/guide/battle). **What happens when a card dies?** It's removed from your deck for the rest of the run. Some cards have death effects that trigger when destroyed. **How do I win a battle?** Defeat the beast before your hero's health drops to zero. Winning grants rewards and lets you progress on the map. ### Map & Nodes **How is the map generated?** Maps are randomly generated using secure onchain randomness. Each level has branching paths and different node types. See the [Map Guide](/darkshuffle/guide/map). **What are nodes?** Nodes are the beast encounters you see on the the map, each with connecting branches. Your choices affect the difficulty and rewards. ### Settings & Customization **Can I customize the game rules?** Yes! Use the settings menu to create or select custom game modes, draft rules, and more. See the [Settings Guide](/darkshuffle/settings). **What is Auto-Draft mode?** Auto-Draft skips the manual draft phase and gives you a pre-built deck. Great for quick games or special events. ### Tournaments & Competitions **How do I join a tournament?** Tournaments are hosted on [Budokan](/budokan), the permissionless tournament platform. There is a link to Budokan tournaments on the home page. See the [Enter Tournaments](/budokan/guide/enter) section on how to enter. **Are there prizes?** Yes! Many [Budokan](/budokan) tournaments offer prizes for top players. Details are listed in each event. *** If you have more questions, explore the rest of the [Dark Shuffle Guide](/darkshuffle/guide/index) or contact support through the app. import { ContractTable } from '../../components/ContractTable' import { Button } from 'vocs/components' ![Overview](docs/ds-banner.png) ## Dark Shuffle Dark Shuffle is a turn-based, collectible card game. Build your deck, battle monsters, and explore a procedurally generated world. ### Table of Contents * [Key Functions](/darkshuffle/key-functions) * [Guide](/darkshuffle/guide) * [Settings](/darkshuffle/settings) * [FAQ](/darkshuffle/faq) ### Contracts ## Dark Shuffle: Key Functions This section details the primary functions exposed by the Dark Shuffle game system. Each function is designed to be permissionless, composable, and secure. ### Game System (`contracts/src/systems/game/contracts.cairo`) #### `start_game` ```cairo fn start_game(ref self: T, game_id: u64); ``` **Purpose:** Initializes a new game instance for a player, setting up the draft or auto-drafting cards, and emits a game creation event. **Parameters:** * `game_id`: The unique identifier for the game. *** #### `pick_card` ```cairo fn pick_card(ref self: T, game_id: u64, option_id: u8); ``` **Purpose:** Allows the player to pick a card during the draft phase. Updates the draft and game state. **Parameters:** * `game_id`: The game instance. * `option_id`: The index of the card option to pick. *** #### `generate_tree` ```cairo fn generate_tree(ref self: T, game_id: u64); ``` **Purpose:** Generates a new map/tree for the current game level, advancing the game to the next map phase. **Parameters:** * `game_id`: The game instance. *** #### `select_node` ```cairo fn select_node(ref self: T, game_id: u64, node_id: u8); ``` **Purpose:** Selects a node on the map, which typically starts a new battle with a monster. **Parameters:** * `game_id`: The game instance. * `node_id`: The node to select. *** #### `player_name` ```cairo fn player_name(self: @T, game_id: u64) -> felt252; ``` **Purpose:** Returns the player's name for a given game. *** #### `health` ```cairo fn health(self: @T, game_id: u64) -> u8; ``` **Purpose:** Returns the hero's current health. *** #### `game_state` ```cairo fn game_state(self: @T, game_id: u64) -> GameState; ``` **Purpose:** Returns the current state of the game (Draft, Battle, Map, Over). *** #### `xp` ```cairo fn xp(self: @T, game_id: u64) -> u16; ``` **Purpose:** Returns the hero's experience points. *** #### `cards` ```cairo fn cards(self: @T, game_id: u64) -> Span; ``` **Purpose:** Returns the names of the cards in the player's deck. *** #### `monsters_slain` ```cairo fn monsters_slain(self: @T, game_id: u64) -> u16; ``` **Purpose:** Returns the number of monsters defeated in the game. *** #### `level` ```cairo fn level(self: @T, game_id: u64) -> u8; ``` **Purpose:** Returns the current map level. *** #### `map_depth` ```cairo fn map_depth(self: @T, game_id: u64) -> u8; ``` **Purpose:** Returns the current depth in the map. *** #### `last_node_id` ```cairo fn last_node_id(self: @T, game_id: u64) -> u8; ``` **Purpose:** Returns the last selected node on the map. *** #### `action_count` ```cairo fn action_count(self: @T, game_id: u64) -> u16; ``` **Purpose:** Returns the number of actions taken in the game. ### Battle System (`contracts/src/systems/battle/contracts.cairo`) #### `battle_actions` ```cairo fn battle_actions(ref self: T, game_id: u64, battle_id: u16, actions: Span>); ``` **Purpose:** Executes a sequence of battle actions (play cards, attack, end turn) for a given battle. Handles all game logic for a turn, including card effects, monster actions, and state updates. **Parameters:** * `game_id`: The game instance. * `battle_id`: The battle instance. * `actions`: A sequence of encoded actions (e.g., play card, attack, end turn). ### Config System (`contracts/src/systems/config/contracts.cairo`) #### `add_settings` ```cairo fn add_settings( ref self: T, name: felt252, description: ByteArray, starting_health: u8, start_energy: u8, start_hand_size: u8, draft_size: u8, max_energy: u8, max_hand_size: u8, draw_amount: u8, card_ids: Span, card_rarity_weights: CardRarityWeights, auto_draft: bool, persistent_health: bool, possible_branches: u8, level_depth: u8, enemy_attack_min: u8, enemy_attack_max: u8, enemy_health_min: u8, enemy_health_max: u8, enemy_attack_scaling: u8, enemy_health_scaling: u8, ) -> u32; ``` **Purpose:** Adds a new game settings profile, which defines the rules and parameters for a game instance. *** #### `add_random_settings` ```cairo fn add_random_settings(ref self: T) -> u32; ``` **Purpose:** Adds a new game settings profile with randomized parameters. *** #### `add_creature_card` ```cairo fn add_creature_card(ref self: T, name: felt252, rarity: u8, cost: u8, attack: u8, health: u8, card_type: u8, play_effect: CardEffect, attack_effect: CardEffect, death_effect: CardEffect); ``` **Purpose:** Adds a new creature card to the card pool. *** #### `add_spell_card` ```cairo fn add_spell_card(ref self: T, name: felt252, rarity: u8, cost: u8, card_type: u8, effect: CardEffect, extra_effect: CardEffect); ``` **Purpose:** Adds a new spell card to the card pool. *** #### `setting_details` ```cairo fn setting_details(self: @T, settings_id: u32) -> GameSettings; ``` **Purpose:** Returns the details of a game settings profile. *** #### `settings_exists` ```cairo fn settings_exists(self: @T, settings_id: u32) -> bool; ``` **Purpose:** Checks if a settings profile exists. *** #### `game_settings` ```cairo fn game_settings(self: @T, game_id: u64) -> GameSettings; ``` **Purpose:** Returns the game settings for a given game instance. ### Additional Utility Functions * **Draft/Deck Management:** * `get_weighted_draft_list`, `get_draft_options`, `auto_draft` (see `utils/draft.cairo`): Used for generating draft options and decks. * **Battle/Board Management:** * `attack_monster`, `remove_hand_card`, `draw_cards`, etc. (see `utils/board.cairo`, `utils/hand.cairo`): Used for in-battle logic and card management. * **Map/Node Management:** * `node_available`, `get_monster_node`, `start_battle` (see `utils/map.cairo`): Used for map navigation and starting battles. ### Data Models * **Game:** Tracks the state, health, XP, monsters slain, map progress, and actions. * **Battle:** Tracks the current battle, hero/monster stats, and effects. * **Draft:** Tracks the draft phase, available options, and selected cards. * **GameSettings:** Defines the rules for a game instance (health, energy, deck size, etc.). ## Settings Settings are one of the most powerful features of Dark Shuffle. They let you customize nearly every aspect of the game—deck size, draft rules, battle mechanics, map structure, and more. This flexibility is made possible by the [Embeddable Game Standard (EGS)](/embeddable-game-standard), allowing anyone to create, share, and use custom settings for new game modes, tournaments, and quests.

The settings viewer lets you browse and select from available settings profiles. Each profile can dramatically change how the game is played, from deck size to map structure.

Settings Viewer
Figure 1: Settings viewer in Dark Shuffle.
### What Can You Configure? Settings are grouped into four main categories: | Category | What You Can Change | | ---------- | ------------------------------------------------------ | | **Game** | Player health, energy, hand size, deck size, and more. | | **Battle** | Enemy stats, energy scaling, effect triggers, etc. | | **Draft** | Number of picks, rarity weights, auto-draft mode, etc. | | **Map** | Map depth, branching, node types, and progression. | > **Tip:** Custom settings let you create unique challenges, speedruns, or even entirely new game modes! ### Creating a Setting
  1. Open the settings menu and select Create New Setting.
  2. Choose which parameters to customize (game, battle, draft, map).
  3. Save and name your setting—it's now available for you and others to use!

Info: All settings are stored on-chain, making them permissionless and composable. Anyone can use your setting for their own games or tournaments.

Create Settings
Figure 2: Creating a new setting.
### Applying Settings: Real-World Examples Custom settings unlock a huge variety of applications: #### Unique Tournaments Any setting can be used by [Budokan](/budokan/guide/submission) to create a competitive structure. For example, you can design a tournament with special draft rules, unique battle mechanics, or custom rewards. #### Eternum Quests Dark Shuffle debuted in Eternum Season 1, where a specific set of settings created fast and varied experiences for players. One key setting was Auto-Draft mode, which skips the draft phase and drops players straight into battle—perfect for quick games and cross-game events.
Settings Example 1
Figure 3: Example custom settings in action.
Settings Example 2
Figure 4: Another custom settings view.
### Advanced: On-Chain Settings & Contract Functions Settings are managed by smart contracts, making them transparent and tamper-proof. Key contract functions include: * [`add_settings`](/darkshuffle/key-functions#add_settings): Create a new settings profile. * [`setting_details`](/darkshuffle/key-functions#setting_details): View the details of a setting. * [`game_settings`](/darkshuffle/key-functions#game_settings): See which settings are applied to a game. > **Info:** Developers and advanced users can use these functions to automate tournaments, create new game types, or integrate with other on-chain games. *** ### Related Guides * [Draft Guide](/darkshuffle/guide/draft): How settings affect the draft phase * [Battle Guide](/darkshuffle/guide/battle): How settings change battle mechanics * [Key Functions](/darkshuffle/key-functions): Technical reference for all contract functions * [Budokan Tournaments](/budokan/guide/submission): Using settings for competitive play If you have questions or want to share your custom settings, join the community or reach out via the app's support channels! ## Battle Battles are the heart of Dark Shuffle—where your drafted deck, strategy, and luck are put to the test against powerful beasts. Winning battles earns you rewards and brings you closer to victory, while defeat can end your run! ### Battle Screen Layout
Battle Screen
Figure 1: Battle screen layout.
| Area | Description | | ----------------------------------------- | ----------------------------------------------------------------------- | | **Reset Turn (Top Left)** | Button to reset your current turn and try a different play sequence. | | **Beast Opponent (Top Center)** | The beast you're battling. Hover to see its effects and stats. | | **Battle Field (Center)** | Place your cards here to attack the beast. | | **End Turn Button (Right)** | Ends your turn and simulates the battle phase. | | **Current Effect Rewards (Bottom Left)** | Shows rewards earned for triggering certain effects or type advantages. | | **Player Area (Bottom Center)** | Displays your hero (energy, health) and your current hand of cards. | | **Remaining Card Counter (Bottom Right)** | Shows how many cards are left in your deck. | ### How a Turn Works At the start of each turn: * Your hero's energy is refilled to the current max. * You draw cards up to your hand size (both are set by the [game settings](/darkshuffle/settings)). * You can play cards by dragging them to the battlefield, spending energy and triggering any play effects. > **Tip:** Some cards have effects that trigger on play, attack, or death. Hover over a card to see its attributes > and synergize your hand for maximum impact.
Playing Card
Figure 2: Dragging a card to the battlefield.
* When a card's health drops to zero, it dies and is removed from your deck (death effects may trigger). * You can reset your turn at any time before ending it. ### Ending Your Turn When you're satisfied with your plays, press **End Turn** to simulate the battle: * The beast and your cards exchange attacks. * Effects (play, attack, death) are resolved. * Your hero's energy is increased by 1 for the next turn. * Cards are drawn from your deck according to the game settings.
Beast Defeated
Figure 3: Card defeated at end of turn.
### End of Battle A battle ends when: * The beast is defeated (you win!), or * Your hero's health drops to zero (game over). If you win: * You earn rewards (shown bottom left and on the map). * Progress to the next map node or battle.
Reward Display
Figure 4: Rewards earned after a battle.
> **Info:** All battle actions and outcomes are managed by smart contracts, ensuring fairness and transparency. For technical details, see the [Key Functions](/darkshuffle/key-functions#battle_actions) reference. *** If you want to master battles, experiment with different decks and strategies, and check the [Cards Guide](/darkshuffle/guide/cards) for effect and synergy ideas! ## Cards Cards are the core of your strategy in Dark Shuffle. Each card brings unique abilities, stats, and synergies to your deck. Understanding card attributes, rarities, and types is key to building a winning deck and outsmarting your opponents. ### Card Anatomy Each card displays several important attributes:
Typhon Card Example
Figure: Example card layout (Typhon).
| Attribute | Location | Description | | ----------- | ------------- | ------------------------------------------------------------------------------------------- | | **Energy** | Top Left | Energy required to play the card. | | **Rarity** | Top Right | How rare the card is ([see Rarities](#rarities)). | | **Effects** | Center | Special abilities, triggered by play, attack, or death ([see Card Effects](#card-effects)). | | **Attack** | Bottom Left | The card's attack strength. | | **Type** | Bottom Center | Card's type, which determines strengths/weaknesses ([see Types](#types)). | | **Health** | Bottom Right | The card's health value. | ### Card Effects Each card can have one or more effects, which activate at different times: * **Play:** Triggers as soon as the card is played. * **Attack:** Triggers when the card attacks a beast; stat increases are compounded. * **Death:** Triggers when the card is destroyed. > **Tip:** Combining cards with synergistic effects can create powerful combos and turn the tide of battle! ### Rarities Rarity affects how often a card appears in drafts and its overall power level: | Rarity | Description | | --------- | ------------------------- | | Common | Most frequently seen. | | Uncommon | Less frequent, stronger. | | Rare | Powerful, appears rarely. | | Epic | Very strong, very rare. | | Legendary | The rarest and strongest. | > **Info:** Rarer cards are more powerful, but you'll see them less often during the draft. ### Types Card types determine strengths and weaknesses in battle: | Type | Strong Against | Weak Against | | ------- | -------------- | ------------ | | Magical | Brute | Hunter | | Brute | Hunter | Magical | | Hunter | Magical | Brute | | Spell | — | — | > **Tip:** Plan your deck and map route based on the types you draft. Check beast types and effects before choosing your path! *** If you want to see all available cards, check the in-game card browser or the [Draft Guide](/darkshuffle/guide/draft) for tips on building your deck. ## Draft Phase: Building Your Deck The draft phase is the first step in every Dark Shuffle game (unless Auto-Draft is enabled in your [Settings](/darkshuffle/settings)). Here, you select cards from randomly generated options to build your deck—the foundation for your strategy throughout the game. ### How Draft Works (Player Experience) During the draft, you'll be presented with several card options each round. Pick one card per round to add to your deck. The process repeats until your deck is complete. Your choices shape your playstyle and chances in battle! ![Draft Page](/docs/darkshuffle/draft.png) Figure: Drafting cards for your deck. > **Tip:** The cards you draft determine your deck for the entire game. Choose wisely—rarer cards are more powerful but less likely to appear! ### Draft Page Layout * **Draft Options (center):** Where you pick from the current card choices. * **Picks Analysis (bottom):** See stats like Draft Counter, Energy Chart, and Card Type Counter to help balance your deck. * **Picks Display (right):** Shows cards you've already chosen. Hover for details. ### Rarity & Card Types Cards are marked with rarity, which affects how often they appear. Rarer cards are generally stronger. For a full list of card types and rarities, see the [Cards Guide](/darkshuffle/guide/cards). ### How Draft Works On-Chain The draft phase is powered by smart contracts, ensuring fairness and transparency. Here's what happens under the hood: * **Game Start:** * The contract function [`start_game`](/darkshuffle/key-functions#start_game) initializes your game and sets up the draft (or auto-draft if enabled). * **Drafting Cards:** * Each time you pick a card, the [`pick_card`](/darkshuffle/key-functions#pick_card) function updates your deck and the draft state. * The contract uses [VRF](https://docs.cartridge.gg/vrf/overview) to generate one set of selections ensuring options are fair and tamper-proof. * **Deck Completion:** * Once all picks are made, your deck is locked in for the rest of the game. > **Info:** All draft actions are recorded on-chain, making the process provable and tamper-proof. You can view the full list of game contract functions in the [Key Functions](/darkshuffle/key-functions) reference. ### Advanced: Custom Draft Settings Game creators can define custom draft rules (deck size, rarity weights, etc.) using the [`add_settings`](/darkshuffle/key-functions#add_settings) contract function. This enables new game modes and experiments with deck-building mechanics. ### Next Steps & Related Guides * [Cards Guide](/darkshuffle/guide/cards): Learn about card types and rarities * [Battle Guide](/darkshuffle/guide/battle): See how your drafted deck is used in combat * [Key Functions](/darkshuffle/key-functions): Technical reference for all game contract functions * [Settings](/darkshuffle/settings): Enable Auto-Draft or customize your game experience If you have questions or want to dive deeper into the contract logic, check the [Key Functions](/darkshuffle/key-functions) page or reach out via the app's support channels. ## Dark Shuffle: Guide Welcome to the Dark Shuffle guide! Here you'll find everything you need to get started, master the mechanics, and discover advanced strategies for success. Whether you're a new player or looking to sharpen your skills, this guide covers every aspect of the game. ### Table of Contents * [Onboarding](/darkshuffle/guide/onboarding): Setting up your wallet and starting your first game * [Draft](/darkshuffle/guide/draft): Building your deck and understanding the draft phase * [Cards](/darkshuffle/guide/cards): Card types, rarities, and effects * [Battle](/darkshuffle/guide/battle): How battles work and tips for victory * [Map](/darkshuffle/guide/map): Navigating the map and planning your route * [Settings](/darkshuffle/settings): Customizing your game experience ### Overview Dark Shuffle is a fully onchain deck-building roguelike where every decision matters. In this guide, you'll learn: 1. **How to onboard and set up your wallet** for seamless play. 2. **The core gameplay loop**—from drafting cards to battling monsters and navigating the map. 3. **How to build and optimize your deck** through the draft phase and card synergies. 4. **Battle mechanics and strategies** to defeat increasingly tough opponents. 5. **How to use custom settings** to create unique game modes and challenges. 6. **How to join tournaments and competitions** for rewards and glory. Each section provides step-by-step instructions, visuals, and tips to help you become a Dark Shuffle master. Dive in and start your adventure! ## Map Maps are randomly generated and split into levels, where each level contains various nodes and branches to choose from. The path you take through the map shapes your encounters, rewards, and overall strategy.
Map
Figure 1: Example of a randomly generated map.
### How Maps Work * **Levels & Nodes:**\ Each map is divided into levels. Every level contains several nodes (encounters), and you choose which node to visit next. * **Branching Paths:**\ Each node consists of multiple branches to choose from to following nodes. Your choices affect the difficulty, types of encounters, and potential rewards. * **Progression:**\ As you advance, the map gets more challenging, with tougher monsters and better rewards. > **Tip:** Plan your route based on your deck's strengths and the types of nodes ahead. Some paths may offer healing, rare rewards, or easier battles. ### On-Chain Map Generation & Logic The map system is powered by smart contracts to ensure fairness and unpredictability: * **Random Generation:**\ When you start a new game or advance to a new level, the contract uses secure randomness (via [VRF](https://docs.cartridge.gg/vrf/overview)) to generate the map layout and available nodes. This means no one—not even the game creators—can predict or manipulate your map. * **Key Contract Functions:** * [`generate_tree`](/darkshuffle/key-functions#generate_tree): Generates a new map/tree for the current game level. * [`select_node`](/darkshuffle/key-functions#select_node): Lets you choose which node to visit next, triggering the appropriate encounter. * **Node Types:**\ The contract can generate different types of encounters based on the current level and your progress. The variety and frequency of nodes can be customized via [Settings](/darkshuffle/settings). > **Info:** All map actions and choices are recorded on-chain, making your journey provable and tamper-proof. For technical details, see the [Key Functions](/darkshuffle/key-functions) reference. ## Getting Started: Onboarding This guide will help you set up your [Controller](https://cartridge.gg/controller) and get ready to play Dark Shuffle. ### Setting Up Your Wallet Follow these steps to get started: 1. If you don't already have a wallet, select Controller and sign up. 2. Choose your preferred login method. The Cartridge Controller supports: * Social logins (Discord) * Other blockchain wallets (Metamask, Phantom, Rabby) * Passkey 3. Complete the sign-up process as guided. 4. Once your wallet is set up, connect it to Budokan through the platform's wallet connection interface.

The Cartridge Controller offers extended features ideal for on-chain games, including signerless transactions and multi-platform login options.

Budokan Wallets
Figure 1: Controller sign up page.
> **Tip:** Using the Cartridge Controller enables gas-less transactions and a smoother experience across all supported games. ### Start A Game

Dark Shuffle offers a range of ways to play. On the home page buttons allow you to jump into a game, manage games you currently have or go to [Budokan](/budokan) to see DS tournaments.

Dark Shuffle Actions
Figure 2: Dark Shuffle game actions.
#### Play Menu This is where you can start your Dark Shuffle journey by selecting a setting and minting a free game to play! Each game is associated with a particular setting, which is a permissionless evolution to expiriment with different mechanics of the game. The default which is pre-selected is the base game. #### My Games All games of Dark Shuffle are themselves an NFT, so this menu keeps track of the games that you currently own. * Games are marked with several different attributes: * Player Name (defaults to controller username) * Game stats (health, xp) * Setting * Expiration time * Whether they are a tournament game or free to play
Dark Shuffle Play
Figure 3: Play Menu.
Dark Shuffle Games
Figure 2: My Games Menu.
### Next Steps & Related Guides * [Draft](/darkshuffle/guide/draft) * [Cards](/darkshuffle/guide/cards) * [Battle](/darkshuffle/guide/battle) * [Budokan Tournaments](/budokan/guide/submission) * [FAQ](/darkshuffle/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Budokan: FAQ Welcome to the Budokan FAQ! Here you'll find answers to the most common questions about using the Budokan tournament platform. If you can't find what you're looking for, check our other guides or contact support through the app. ### General **What is Budokan?** Budokan is a permissionless tournament platform for on-chain games, allowing anyone to create, join, and sponsor tournaments using Starknet wallets and a variety of supported tokens. **Who can participate in Budokan tournaments?** Anyone with a supported Starknet wallet who meets the tournament's entry requirements can participate. ### Getting Started & Wallets **How do I get started on Budokan?** See our [Onboarding Guide](/budokan/guide/onboarding) for step-by-step instructions on setting up your wallet and connecting to Budokan. **Which wallets are supported?** Budokan supports Cartridge Controller (recommended), Argent, Braavos, and more. See the [Onboarding Guide](/budokan/guide/onboarding) for details. **Do I need a wallet to join a tournament?** Yes, a Starknet wallet is required to enter tournaments, claim prizes, and interact with the platform. ### Tournaments & Entry **How do I find and join a tournament?** Browse tournaments on the overview page, select one to view details, and follow the steps in [Entering Tournaments](/budokan/guide/enter) to join. **What are entry requirements?** Entry requirements can include holding specific tokens/NFTs, qualifying in previous tournaments, or being on a whitelist. See [Entry Requirements](/budokan/guide/create/entry-requirements) for more info. **Can I join multiple tournaments?** Yes, you can join as many tournaments as you qualify for. **What if I don't meet the entry requirements?** Review the requirements section on the tournament page for guidance. You may need to acquire certain tokens or qualify in another event. ### Prizes & Fees **How are prizes funded and distributed?** Prizes can be funded by tournament creators, sponsors, or the community. They are distributed according to the rules and splits set for each tournament. See [Prizes](/budokan/guide/prizes) for details. **What tokens can be used for entry fees and prizes?** Currently, Budokan uses a whitelist of supported tokens. In the future, any token on Ekubo and NFTs will be supported. See [Entry Fees](/budokan/guide/create/entry-fees) and [Prizes](./guide/prizes). **How do I claim my prize?** After the tournament ends and results are verified, use the **Claim Prizes** button on the tournament page. See [Prizes](/budokan/guide/prizes) for step-by-step instructions. **Can sponsors add prizes after a tournament has started?** Yes! Sponsors and community members can add prizes at any time before the tournament ends. See [Prizes](/budokan/guide/prizes). ### Creating & Managing Tournaments **How do I create a tournament?** Click the **Create Tournament** button in the navigation and follow the steps in [Creating a Tournament](/budokan/guide/create). **What customization options are available?** You can set game, schedule, entry requirements, fees, prize structure, and more. See [Creating a Tournament](/budokan/guide/create) for a full walkthrough. **Can I edit a tournament after creation?** Some settings (like prizes) can be updated before the tournament ends, but core settings are locked after creation. Double-check all details before confirming. ### Troubleshooting & Support **Why can't I enter a tournament?** Check that you meet all entry requirements and have a supported wallet connected. If you still have issues, contact support. **Why can't I claim my prize?** Ensure the tournament has ended, results are finalized, and you are eligible. If problems persist, see [Prizes](/budokan/guide/prizes) or contact support. **Where can I get more help?** Check our guides or contact support through the app for further assistance. ### More Resources * [Onboarding Guide](/budokan/guide/onboarding) * [Entering Tournaments](/budokan/guide/enter) * [Creating a Tournament](/budokan/guide/create) * [Prizes](/budokan/guide/prizes) If you have suggestions for this FAQ, let us know! import { ContractTable } from '../../components/ContractTable' import { Button } from 'vocs/components' ![Overview](/banner-1200x630.webp) ## Budokan Budokan is an onchain tournament system designed to power competitive gaming experiences on Starknet. It provides a robust, modular, and extensible framework for: * Creating and managing tournaments with flexible schedules and rules * Supporting a variety of entry requirements and fee structures * Handling prize pools and distribution (ERC20, ERC721, and custom tokens) * Integrating with embeddable game standards for seamless game onboarding * Leveraging Dojo world storage and event systems for composability and upgradability ### Table of Contents * [Key Functions](/budokan/key-functions) * [Guide](/budokan/guide) * [FAQ](/budokan/faq) ### Contracts ### Key Features * **Modular Architecture**: Components for tournaments, games, schedules, and prizes can be extended or replaced. * **Secure and Permissionless**: Anyone can create or enter tournaments, with robust checks for entry requirements and prize claims. * **Flexible Entry and Prize Logic**: Supports entry fees, qualification proofs, and multiple prize types. * **Leaderboard and Scoring**: Built-in leaderboard management and score submission. ### Use Cases * Esports tournaments for onchain games * Community competitions and challenges * Automated prize distribution for game events * Open standards for game developers to integrate with tournament infrastructure Budokan is designed to be the backbone for provable, transparent, and fair onchain competitions, enabling new forms of player engagement and game design. ## Budokan: Key Functions This section details the primary functions exposed by the Budokan tournament system. Each function is designed to be permissionless, composable, and secure. ### `create_tournament` ```cairo fn create_tournament( ref self: TState, creator_rewards_address: ContractAddress, metadata: Metadata, schedule: Schedule, game_config: GameConfig, entry_fee: Option, entry_requirement: Option, ) -> TournamentModel; ``` **Purpose:** Create a new tournament with specified metadata, schedule, game configuration, entry fee, and requirements. Mints a game token to the creator for reward distribution. **Parameters:** * `creator_rewards_address`: Address to receive the creator's game token. * `metadata`: Tournament metadata (name, description, etc.). * `schedule`: Tournament schedule (registration, submission, finalization phases). * `game_config`: Game configuration (game address, settings, prize spots). * `entry_fee`: Optional entry fee (ERC20/721, amount, etc.). * `entry_requirement`: Optional qualification requirement. **Returns:** Tournament model struct. *** ### `enter_tournament` ```cairo fn enter_tournament( ref self: TState, tournament_id: u64, player_name: felt252, player_address: ContractAddress, qualification: Option, ) -> (u64, u32); ``` **Purpose:** Register a player for a tournament, minting a game token and assigning an entry number. **Parameters:** * `tournament_id`: ID of the tournament to enter. * `player_name`: Name of the player (felt252). * `player_address`: Address to mint the game token to. * `qualification`: Optional qualification proof. **Returns:** Tuple of (game token ID, entry number). *** ### `submit_score` ```cairo fn submit_score( ref self: TState, tournament_id: u64, token_id: u64, position: u8, ); ``` **Purpose:** Submit a score/position for a tournament entry. Updates the leaderboard and marks the score as submitted. **Parameters:** * `tournament_id`: ID of the tournament. * `token_id`: Game token ID for the entry. * `position`: Leaderboard position (1-based index). *** ### `claim_prize` ```cairo fn claim_prize( ref self: TState, tournament_id: u64, prize_type: PrizeType, ); ``` **Purpose:** Claim a prize for a tournament after it is finalized. Handles entry fee and custom prize types. **Parameters:** * `tournament_id`: ID of the tournament. * `prize_type`: Type of prize to claim (entry fees, custom, etc.). *** ### `add_prize` ```cairo fn add_prize( ref self: TState, tournament_id: u64, token_address: ContractAddress, token_type: TokenType, position: u8, ) -> u64; ``` **Purpose:** Add a new prize to a tournament for a specific leaderboard position. **Parameters:** * `tournament_id`: ID of the tournament. * `token_address`: Address of the prize token. * `token_type`: Type of token (ERC20, ERC721, etc.). * `position`: Leaderboard position for the prize. **Returns:** Prize ID. *** ### `register_token` ```cairo fn register_token( ref self: TState, address: ContractAddress, token_type: TokenType, ); ``` **Purpose:** Register a new token (ERC20/ERC721) for use in tournaments. **Parameters:** * `address`: Token contract address. * `token_type`: Type of token. *** ### `get_leaderboard` ```cairo fn get_leaderboard( self: @TState, tournament_id: u64, ) -> Array; ``` **Purpose:** Retrieve the current leaderboard for a tournament. **Parameters:** * `tournament_id`: ID of the tournament. **Returns:** Array of game token IDs ordered by position. *** ### Additional Functions * `total_tournaments`: Returns the total number of tournaments created. * `tournament`: Returns the tournament model for a given ID. * `get_registration`: Returns registration details for a game token. * `get_prize`: Returns prize details for a given prize ID. * `tournament_entries`: Returns the number of entries for a tournament. * `is_token_registered`: Checks if a token is registered for tournaments. * `current_phase`: Returns the current phase of a tournament. * `get_tournament_id_for_token_id`: Returns the tournament ID for a given game token. ## Entering Tournaments This guide will walk you through how to find, enter, and track tournaments. Whether you're a new or returning player, follow these steps to get started. ### Browsing Tournaments On the **Overview** page, you can: * Filter tournaments by game, status (upcoming, live, ended), or your participation. * View summarized information on each tournament card, including: * Name * Duration * Number of participants * Time remaining * Entry fee (in $) * Prize pot (in $) * Key badges for important details (explained on the full details page) ![Overview](/docs/overview.png) *Figure 1: Tournament overview page.* ### Viewing Tournament Details To learn more about a tournament: 1. Click on a tournament card to open its details page. 2. Review the following sections: * **Requirements and Interactions** (top-right) * **Tournament Details** * **Timeline** * **Prizes** * **Scores (Entries)** * **My Entries** The **Timeline** shows the tournament's structure, including the submission period after the tournament ends. ![Tournament Details](/docs/tournament-details.png) *Figure 2: Tournament details page.* > **Note:** Scores must be submitted during the submission period after the tournament ends to be eligible for prizes. The platform provides tools to handle this automatically, but you can also submit scores manually through the app. ### How to Enter a Tournament

To join a tournament:

  1. On the tournament details page, click the Enter Tournament button.
  2. Review the entry fee breakdown, including:
    • Tournament creator fees
    • Game fees
    • Prize pool contribution
  3. Check any entry requirements and confirm you qualify.
  4. Enter your player name when prompted.
  5. Confirm your entry and pay any required fees.
Entering a tournament
Figure 3: Entering a tournament.
> **Tip:** If you do not meet the entry requirements, review the requirements section for guidance on how to qualify. ### After Entering Once you have entered a tournament: * Your entry will appear in the **Scores (Entries)** table. * You will see your game listed under **My Entries**. * Any entry fees you pay are instantly added to the prize pool. ![Entered Tournament](/docs/entered-tournament.png) *Figure 4: After entering a tournament.* ### Related Guides * [Submitting Scores](/budokan/guide/submission) * [Tournament Prizes](/budokan/guide/prizes) If you have questions or encounter issues, check the [FAQ](/budokan/faq) or contact support through the app. ## Budokan: Guide This guide walks you through the complete process of using Budokan, from creating tournaments to claiming prizes. ### Table of Contents * [Onboarding](/budokan/guide/onboarding) * [Entering a Tournament](/budokan/guide/enter) * [Creating a Tournament](/budokan/guide/create) * [Prizes](/budokan/guide/prizes) ### Overview Budokan provides a complete tournament management system with three main phases: 1. Player Registration and Participation 2. Tournament Creation and Setup 3. Prize Management and Distribution Each section of this guide provides detailed instructions and examples for these key processes. ## Getting Started: Onboarding Welcome to Budokan! This guide will help you set up your wallet and get ready to play on-chain games. Budokan supports a variety of Starknet wallets, with a special recommendation for the [Cartridge Controller](https://cartridge.gg/controller) due to its advanced features and signerless transactions. ### Choosing a Wallet Budokan currently supports connection through several wallets. For the best experience, we recommend using the Cartridge Controller, which enables gas-less transactions and seamless gameplay on the platform.
  • Cartridge Controller (recommended)
  • Argent
  • Braavos

The Cartridge Controller offers extended features ideal for on-chain games, including signerless transactions and multi-platform login options.

Budokan Wallets
Figure 1: Supported wallets on Budokan.
### Setting Up Your Wallet Follow these steps to get started: 1. If you don't already have a wallet, select Controller and sign up. 2. Choose your preferred login method. The Cartridge Controller supports: * Social logins (Discord) * Other blockchain wallets (Metamask, Phantom, Rabby) * Passkey 3. Complete the sign-up process as guided. 4. Once your wallet is set up, connect it to Budokan through the platform's wallet connection interface.

The Cartridge Controller makes onboarding easy, allowing you to use familiar social accounts or other wallets. After setup, you'll be ready to join tournaments and play games on Budokan.

Controller Onboarding
Figure 2: Signing up with the Cartridge Controller.
> **Tip:** Using the Cartridge Controller enables gas-less transactions and a smoother experience across all supported games. ### Next Steps & Related Guides * [Entering Tournaments](/budokan/guide/enter) * [Submitting Scores](/budokan/guide/submission) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Budokan Prizes Budokan features a flexible, permissionless prize system that allows anyone to add prizes to new or ongoing tournaments. This guide explains how prizes work, how to add and claim them, and best practices for both players and sponsors. ### Supported Prize Tokens

For security, Budokan currently uses a whitelist of tokens for prizes. In the future, any token supported on Ekubo, as well as NFTs (ERC721), will be available for use. This enables communities and sponsors to contribute a wide variety of rewards.

Prize Tokens
Figure 1: Supported ERC20s and NFTs for prizes.
### How to Add Prizes Prizes can be added at multiple stages during a tournament's lifecycle: 1. **During Tournament Creation:** * Set up prize tokens and distribution when creating your tournament. * Choose from supported ERC20s or NFTs. ![Prizes](/docs/prizes.png) *Figure 2: Tournament Prizes Form during creation.* 2. **After Tournament Creation:** * Add prizes at any time before the tournament ends. * Go to the tournament page and click the **Add Prizes** button. ![Add Prizes Button](/docs/add-prizes-button.png) *Figure 3: Add Prizes on the tournament page.* > **Tip:** Sponsors and community members can contribute prizes even after a tournament has started, increasing engagement and excitement. ### Prize Eligibility & Distribution Prizes are distributed based on the tournament's rules and leaderboard positions. Here's how it works: * **Eligibility:** * Players must meet all tournament requirements and achieve a winning position to be eligible for prizes. * **Distribution:** * Prizes will be allocated to winners after the tournament concludes, the submission period ends and the prizes have been claimed. * The distribution follows the splits set by the tournament creator or sponsor. > **Note:** Always check the tournament details for specific prize eligibility rules and distribution breakdowns. ### Claiming Prizes 1. **When to Claim:** * Prizes become claimable after the tournament ends and all results are verified. * You will be notified in the app or via your tournament dashboard. 2. **How to Claim:** * Go to the tournament page and look for the **Claim Prizes** button in the top right. * The will effectively distribute all prizes, game fees and creator fees in a single tx. ### For Sponsors & Community Contributors Budokan encourages sponsors and community members to add prizes to tournaments. Here's how you can get involved: * **Adding Prizes:** * Use the **Add Prizes** button on any tournament page to contribute tokens or NFTs. * Specify the prize type, amount, and any special eligibility requirements. * **Visibility:** * Your contribution will be visible on the tournament page, and you will be credited as a sponsor. > **Tip:** Adding prizes can boost tournament participation and promote your project or community. ### Related Guides * [Creating a Tournament](/budokan/guide/create) * [Entry Fees](/budokan/guide/create/entry-fees) * [Entry Requirements](/budokan/guide/create/entry-requirements) * [Onboarding](/budokan/guide/onboarding) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Submitting Scores Congratulations on achieving a high score! To ensure your results count and you remain eligible for prizes, you must submit your score to the contract during the designated submission period. This guide explains how and when to submit, how verification works, and how automation can help. ### Submission Period After a tournament ends, there is a dedicated window—called the **submission period**—for players to submit their scores on-chain. The tournament creator sets this window (minimum 15 minutes). It is crucial to submit your score during this time; otherwise, you may not qualify for prizes or leaderboard positions. > **Note:** If you miss the submission period, your score may not be recognized, and you could forfeit any prizes. ### How to Submit Your Score 1. When the tournament ends, check the tournament page for the submission window and instructions. 2. Submit your score through the app interface during the submission period. 3. Once your score is submitted and verified, a checkmark or "verified" mark will appear next to your name in the leaderboard.
Submission Dialog
Figure 1: Submit Scores Button.
Submission Dialog
Figure 2: Submission dialog for top scores.
### Score Verification * Submitted scores are validated by the contract to ensure they qualify for their claimed positions. * Only verified scores are eligible for prizes and final leaderboard placement. ![Submitted Scores](/docs/submitted-scores.jpeg) *Figure 3: Submitted scores table with verified entries.* ### Automated Submission Budokan provides an automated submission bot that detects when a tournament ends and schedules the top scores to be submitted on-chain. This helps ensure that scores are not missed due to user error or forgetfulness. You can find the code and instructions for running your own submission bot here: [Submission Bot Template](https://github.com/Provable-Games/budokan-submission-bot) > **Tip:** Even with automation, always double-check that your score has been submitted and verified before the submission window closes. *** ### Related Guides * [Entering Tournaments](/budokan/guide/enter) * [Prizes](/budokan/guide/prizes) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Best Practices This guide covers recommended practices for creating successful tournaments on Budokan. Following these guidelines will help protect your prize pools, attract genuine participants, and ensure a smooth experience for everyone. ### Sybil Resistance #### Always Charge Entry Fees for Open Tournaments If your tournament is open to everyone and has no entry requirements (token gating, qualification, or whitelist), **always charge a small entry fee**. This is critical for protecting your prize pool. **Why this matters:** * Without barriers to entry, bots and farmers can create unlimited wallets and play an infinite number of games to exploit prize distributions * Even a small fee (e.g., $0.10–$1.00) creates enough friction to make mass exploitation unprofitable * The fee doesn't need to be large—just enough to make the cost of running many accounts exceed potential rewards **Recommended approaches:** | Tournament Type | Recommendation | | ---------------------- | ------------------------------------------------------------ | | Open + Prize Pool | Charge entry fee (required) | | Open + No Prizes | Entry fee optional | | Token Gated | Entry fee optional (token requirement provides protection) | | Qualification Required | Entry fee optional (prior tournament provides protection) | | Whitelisted | Entry fee optional (address restriction provides protection) | > **Rule of thumb:** If anyone can join and there's money to win, require an entry fee. #### Combine Multiple Protections For high-value prize pools, consider layering protections: * Entry fee + token gating * Entry fee + qualification from a previous tournament * Token gating + whitelist for invite-only events ### Submission Period #### Set Realistic Deadlines The submission period determines how long players have to submit their final scores after the tournament ends. If no scores are submitted before the deadline, **the prize pool will go to the tournament creator**. **Recommendations:** * Set submission periods of at least 24–48 hours for casual tournaments * For competitive events, 1–7 days gives players flexibility across time zones * Monitor your tournament as the deadline approaches #### Use Auto-Submit Infrastructure Budokan provides infrastructure to help monitor and auto-submit scores. Take advantage of this: * Bookmark your tournament page * Set calendar reminders for the submission deadline * Consider enabling auto-submission if available ### Prize Distribution #### Plan Your Winner Distribution Before creating a tournament, decide how you want to distribute prizes: * **Top-heavy:** Rewards the best players heavily (e.g., 70/20/10 split) * **Flat:** Spreads rewards more evenly across winners * **Participation-focused:** Smaller prizes for more positions Consider your goals: * Competitive prestige → top-heavy * Community engagement → flatter distribution * Encouraging new players → participation rewards ### Timing and Scheduling #### Avoid Overlapping with Major Events Check for other tournaments, game updates, or community events that might compete for attention. Timing your tournament well can significantly impact participation. #### Consider Time Zones If your community is global, consider: * Longer tournament durations (days rather than hours) * Start times that work for multiple regions * Extended submission periods ### Communication #### Write Clear Descriptions Use the tournament description to communicate: * Any special rules or expectations * The theme or purpose of the tournament * Links to community channels for questions #### Announce Your Tournament After creation, share your tournament: * In relevant Discord channels * On social media * Through community newsletters ### Pre-Launch Checklist Before confirming your tournament, verify: * [ ] Entry requirements match your intended audience * [ ] Entry fee is set (if tournament is open with prizes) * [ ] Prize distribution percentages total 100% * [ ] Start and end times are correct * [ ] Submission period gives players enough time * [ ] Game settings are configured correctly * [ ] Description clearly explains the tournament > **Remember:** Once created, some tournament settings cannot be changed. Double-check everything before confirming. ### Related Guides * [Creating a Tournament](/budokan/guide/create) * [Entry Fees](/budokan/guide/create/entry-fees) * [Entry Requirements](/budokan/guide/create/entry-requirements) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Entry Fees Budokan allows you to set entry fees for your tournaments using any supported token on Starknet. This feature lets communities leverage their own ecosystem tokens as payment for games on the platform, adding flexibility and value. ### Supported Tokens

For security, Budokan currently uses a whitelist of tokens for tournament entry fees. In the future, any token supported on Ekubo will be available for use. Support for ERC721 (NFT) entry fees is also coming soon.

Entry Fee Tokens Figure 1: Entry fee tokens currently supported.
### Customizing Entry Fee Distribution The entry fee form offers flexible options for setting the price of entry and how fees are split among different parties. To make things simple, values are converted to USD where possible, so both players and creators can easily compare amounts—even if they're unfamiliar with the token. ![Entry Fees](/docs/entry-fees.png) *Figure 2: Entry Fees Form.* #### How Entry Fees Are Split * **Creator Fee:** * The percentage of each entry fee that goes to the tournament creator. This is automatically distributed to the creator's account (`Entry #0`) when a player enters. * **Game Fee:** * The percentage of each entry fee that goes to the game developer. The creator sets this percentage, though a minimum base fee may be enforced in the future. The game developer receives this cut via the account holding `Game #0`. * **Player Distribution (Prize Pool):** * The remaining amount is added to the tournament's prize pool. The creator decides how the prize is split among winners, based on the leaderboard size. Use the slider to adjust distribution weights, or manually enter the splits (they must total 100%). > **Tip:** Use familiar tokens for your audience to encourage participation. Double-check your fee splits before confirming, as they can't be changed after creation. *** ### Related Guides * [Creating a Tournament](/budokan/guide/create) * [Prize Structure](/budokan/guide/prizes) * [Onboarding](/budokan/guide/onboarding) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Entry Requirements Entry requirements let you control who can join your tournament, adding exclusivity and enabling unique playing conditions. Use these features to target specific communities, create multi-stage events, or restrict access as needed. ### Token Gating

You can require participants to own specific NFTs or tokens to enter your tournament. This is a great way to host exclusive events for certain communities without manually tracking eligible accounts.

Currently, Budokan uses a whitelist of supported tokens for gating, similar to entry fee tokens. In the future, all tokens will be supported.

Entry Requirement Tokens
Figure 1: Gated tokens currently supported.
![Token Gating](/docs/entry-requirements-token.png) *Figure 2: Token Entry Requirements Form.* > **Tip:** Use token gating to reward loyal holders or run special events for your community. ### Tournament Qualification Budokan allows you to chain tournaments together, unlocking advanced formats such as: * Multi-round tournaments * Multi-game tournaments * Multi-game, multi-round tournaments ![Tournament Qualification](/docs/entry-requirements-tournament-2.png) *Figure 3: Selecting participants of a previous tournament.*

By leveraging Starknet and Dojo interoperability, you can create tournament structures that were not possible before. For example, you can require players to qualify in one tournament before entering another, or combine results across multiple games.

Dark Shuffle World Championship
Figure 4: Dark Shuffle World Championship.
> **Note:** Chaining tournaments is a powerful way to build leagues, championships, or community-driven series. ### Whitelisting Addresses You can also restrict tournament access by whitelisting specific wallet addresses. Only those on the list will be able to join. ![Whitelisting](/docs/entry-requirements-whitelist.png) *Figure 5: Whitelisting addresses for tournament entry.* > **Tip:** Use whitelisting for invitation-only events or to reward select players. ### Related Guides * [Creating a Tournament](/budokan/guide/create) * [Entry Fees](/budokan/guide/create/entry-fees) * [Onboarding](/budokan/guide/onboarding) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Game Settings Budokan supports fully customizable game settings for all games that implement the [Embeddable Game Standard (EGS)](/embeddable-game-standard). This allows tournament creators to experiment with new game modes, tailor experiences for their communities, and offer a wide variety of play styles. ### Why Customize Game Settings? Custom game settings let you: * Create unique tournament experiences * Enable or disable specific game features * Adjust difficulty, rules, or other parameters * Encourage new strategies and community engagement > **Tip:** Experimenting with different settings can help you discover popular new game modes and keep your tournaments fresh. *** ### How to Select and Customize Settings

When creating a tournament, you can choose to customize the game settings. These settings are displayed as key-value pairs, read directly from the game contract on-chain. Click Select Settings to open the customization dialog.

Game Settings
Figure 1: Customizing Game Settings in Budokan.
*** ### Creating and Using Custom Settings * Settings can be created permissionlessly through the games themselves. * Any custom settings you create will be displayed and available for play within Budokan. * You can select from existing settings or define new ones for your tournament.
Game Settings Modal
Figure 2: Selecting Specific Settings.
Game Settings View
Figure 3: Viewing a selected setting.
> **Note:** All custom settings are transparent and visible to players, ensuring a fair and open tournament environment. *** ### Related Guides * [Creating a Tournament](/budokan/guide/create) * [Prizes](/budokan/guide/prizes) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app. ## Creating a Tournament Budokan lets anyone create tournaments for any game that supports the [Emedabble Game Standard](/embeddable-game-standard/index) (EGS). This guide walks you through the process, highlights customization options, and shares best practices for a successful tournament. ### Prerequisites * A Starknet wallet ([see Onboarding](../onboarding)) * Prize tokens (if you want to offer custom prizes) ### How to Create a Tournament #### 1. Open the Create Tournament Page * Click the **Create Tournament** button in the navigation menu. ![Create Tournament Button](/docs/navigation.png) *Figure 1: Create Tournament Button.* This opens the Create Tournament page, where you can configure all aspects of your tournament. ![Create Tournament Page](/docs/create-tournament.png) *Figure 2: Create Tournament Page.* *** #### 2. Configure Tournament Details * Select the game * Adjust game settings ([see Game Settings](../create/entry-requirements)) * Enter a name and description * Set the leaderboard size (number of winners) *** #### 3. Set the Tournament Schedule * Choose start and end times * Set the submission period * Select registration type: * Open (anyone can join) * Fixed (limited registration) ![Tournament Schedule](/docs/schedule.png) *Figure 3: Tournament Schedule Form.* *** #### 4. (Optional) Configure Entry Requirements * Set qualification requirements * Limit the number of players ![Tournament Entry Requirements](/docs/entry-requirements-tournament.png) *Figure 4: Entry Requirements Form.* For more details, see [Entry Requirements](../create/entry-requirements). *** #### 5. (Optional) Set Entry Fees * Choose entry fee tokens * Define fee distribution * Tournament Creator % * Game % * Winner distribution ![Entry Fees](/docs/entry-fees.png) *Figure 5: Entry Fees Form.* For more details, see [Entry Fees](../create/entry-fees). *** #### 6. (Optional) Set Up Prize Structure * Choose prize types (ERC20s, ERC721s) * Define how prizes are distributed * Set winner positions ![Prizes](/docs/prizes.png) *Figure 6: Tournament Prizes Form.* *** #### 7. Review and Confirm After completing the form, you'll see a tournament confirmation page. Review all details and confirm before submitting the transaction. This is your chance to double-check the structure and any prizes.

Once you confirm, your tournament will be created and visible to other players. Make sure you are happy with all settings before finalizing.

Tournament Confirmation Figure 7: Tournament confirmation dialog.
### Best Practices * **Sybil Resistance:** * Avoid making tournaments with open, free entry and a prize pool, as this can lead to abuse (multiple entries per wallet). Consider qualification requirements or entry fees to protect your tournament. * **Submission Period:** * Always note the submission period. If no scores are submitted before the deadline, the prize pool will be forfeited. * Budokan provides infrastructure to help monitor and auto-submit, but it's wise to bookmark your tournament and set reminders. > **Tip:** Double-check all tournament settings before confirming. Once created, some options cannot be changed. ### Related Guides * [Onboarding](/budokan/guide/onboarding) * [Entry Requirements](/budokan/guide/create/entry-requirements) * [Entry Fees](/budokan/guide/create/entry-fees) * [Submitting Scores](/budokan/guide/submission) * [FAQ](/budokan/faq) If you have questions or need help, check the FAQ or contact support through the app.