Skip to Content

Asset Management

Agent8 provides a comprehensive asset management system for handling in-game currencies, points, resources, and any fungible (interchangeable) items. Assets are automatically synchronized across clients and persist between sessions.

Understanding the Asset System

The asset system should be used for any fungible resources in your game—items that are identical and interchangeable with others of the same type. Assets are automatically validated, synchronized in real-time across all clients, and persist in the server.

Assets are perfect for game currencies, experience points, resources, and any stackable items. For unique items like individual weapons or NFTs, use User State or Collections instead.

When to Use Assets

✅ Use Assets For:

  • Game Currencies: Gold, coins, gems, credits
  • Points & Scores: Experience points, skill points, loyalty points
  • Resources: Wood, stone, food, energy
  • Fungible Tokens: Any ERC-20-like tokens, tradeable items
  • Consumables: Potions, ammunition, fuel
  • Stackable Items: Items that can have quantities (arrows, materials)

❌ Don’t Use Assets For:

  • Unique Items: Individual weapons, NFTs, character-specific items
  • User Profiles: Player names, avatars, settings
  • Game State: Room configurations, world state
  • Collections: Use Collection APIs for complex data structures

Server API

Access assets through the $asset variable in server code. All operations are automatically validated and synchronized:

  • $asset.get(assetId: string): Promise<number> - Gets specific asset amount
  • $asset.getAll(): Promise<Record<string, number>> - Gets all assets for current user
  • $asset.has(assetId: string, amount: number): Promise<boolean> - Checks if user has sufficient assets
  • $asset.mint(assetId: string, amount: number): Promise<Record<string, number>> - Creates new assets
  • $asset.burn(assetId: string, amount: number): Promise<Record<string, number>> - Destroys assets
  • $asset.transfer(toAccount: string, assetId: string, amount: number): Promise<Record<string, number>> - Transfers assets between users
server.js
class Server { async getMyAssets() { return await $asset.getAll(); } getMissionReward(missionId) { const rewardTable = { 'mission_tutorial': 50, 'mission_daily': 100, 'mission_boss': 500 }; return rewardTable[missionId] || 0; } async completeMission(missionId) { const reward = this.getMissionReward(missionId); if (reward === 0) throw new Error('Invalid mission'); return await $asset.mint('gold', reward); } async buyItem(itemId) { const prices = { 'sword': 100, 'shield': 80, 'potion': 25 }; const price = prices[itemId]; if (!price) throw new Error('Item not found'); if (!await $asset.has('gold', price)) { throw new Error('Insufficient gold'); } await $asset.burn('gold', price); return await $asset.mint(itemId, 1); } }
🚫

Security Best Practice: Never expose mintAsset(assetId, amount) or burnAsset(assetId, amount) directly to clients. Always wrap asset operations in server-controlled logic where the server determines the amounts based on game rules.

⚠️

All asset operations are atomic and automatically emit change notifications to connected clients. Operations with invalid amounts (negative, non-integer, or non-finite) will throw errors.

Client API

Subscriptions

  • server.subscribeAsset(account: string, (assets: Record<string, number>) => {}): UnsubscribeFunction

Hooks

  • const { assets, getAsset, hasAsset } = useAsset()
⚠️

The useAsset hook provides read-only access to assets. All asset modifications (mint, burn, transfer) must be performed server-side through the $asset API in server.js. This ensures proper validation and prevents client-side manipulation.

Subscribe to Asset Updates

// Example using the useGameServer hook: import { useGameServer } from "@agent8/gameserver"; import { useEffect } from "react"; function AssetComponent() { const { connected, server } = useGameServer(); useEffect(() => { if (!connected) return; const unsubscribe = server.subscribeAsset(server.account, (assets) => { // Handle asset updates here console.log("Assets updated:", assets); }); // Cleanup subscription when component unmounts return () => unsubscribe(); }, [connected, server]); return <div>Asset Component</div>; }

Sync Assets with React Hooks

For real-time asset synchronization in React components:

import { useAsset, useGameServer } from "@agent8/gameserver"; function AssetDisplay() { const { connected, server } = useGameServer(); const { assets, getAsset, hasAsset } = useAsset(); // Asset modifications must go through server functions const handleBuyItem = async () => { try { const result = await server.remoteFunction("buyItem", ["sword"]); console.log(result.message); } catch (error: any) { console.error("Purchase failed:", error.message); } }; if (!connected) return <div>Connecting...</div>; return ( <div> <h2>My Assets</h2> {Object.entries(assets).map(([assetId, amount]) => ( <div key={assetId}>{assetId}: {amount}</div> ))} <p>Gold: {getAsset("gold")}</p> <p>Can afford sword (100 gold): {hasAsset("gold", 100) ? "Yes" : "No"}</p> <button onClick={handleBuyItem} disabled={!hasAsset("gold", 100)} > Buy Sword (100 gold) </button> </div> ); }

The useAsset hook automatically handles subscriptions and provides real-time asset updates. Asset modifications (mint/burn/transfer) should be triggered through server.remoteFunction() calls to your server-side logic.


Example 1: Game Shop System

This example demonstrates a complete game shop where players can buy items using gold currency.

server.js
class Server { // Initialize new players with starting gold async initializePlayer() { const currentGold = await $asset.get('gold'); if (currentGold === 0) { await $asset.mint('gold', 1000); return { message: 'Welcome! You received 1000 gold!' }; } return { message: 'Welcome back!' }; } // Shop system async buyItem(itemId) { const prices = { 'sword': 100, 'shield': 80, 'potion': 25, 'armor': 200 }; const price = prices[itemId]; if (!price) { throw new Error('Item not found'); } // Check if player has enough gold if (!await $asset.has('gold', price)) { throw new Error(`Insufficient gold. Need ${price} gold.`); } // Execute the purchase await $asset.burn('gold', price); await $asset.mint(itemId, 1); return { success: true, message: `Purchased ${itemId} for ${price} gold!`, remainingGold: await $asset.get('gold') }; } // Sell items back to shop async sellItem(itemId) { const sellPrices = { 'sword': 50, 'shield': 40, 'potion': 12, 'armor': 100 }; const sellPrice = sellPrices[itemId]; if (!sellPrice) { throw new Error('Cannot sell this item'); } if (!await $asset.has(itemId, 1)) { throw new Error('You do not own this item'); } // Execute the sale await $asset.burn(itemId, 1); await $asset.mint('gold', sellPrice); return { success: true, message: `Sold ${itemId} for ${sellPrice} gold!`, totalGold: await $asset.get('gold') }; } }

Example 2: Player Trading System

This example shows how to implement a trading system where players can transfer assets to each other.

server.js
class Server { async sendGift(targetAccount, assetId, amount, message) { // Validation if (!targetAccount || !assetId || amount <= 0) { throw new Error('Invalid gift parameters'); } if (targetAccount === $sender.account) { throw new Error('Cannot send gifts to yourself'); } // Check if sender has enough assets if (!await $asset.has(assetId, amount)) { throw new Error(`Insufficient ${assetId}. You need ${amount} but have ${await $asset.get(assetId)}.`); } // Execute the transfer await $asset.transfer(targetAccount, assetId, amount); // Log the transaction (you could also use Collections to store transaction history) console.log(`Gift sent: ${$sender.account} → ${targetAccount}: ${amount} ${assetId}`); return { success: true, message: `Successfully sent ${amount} ${assetId} to ${targetAccount}`, remainingBalance: await $asset.get(assetId) }; } async getPlayerAssets(targetAccount) { // This would require access to other players' assets // Note: In a real application, you might want to limit this or require permission return await $asset.getAll(); } // Daily reward system async claimDailyReward() { const dailyRewards = { 'gold': 100, 'experience_points': 50 }; // In a real implementation, you'd check if the player already claimed today // using global user state or collections for (const [assetId, amount] of Object.entries(dailyRewards)) { await $asset.mint(assetId, amount); } return { success: true, message: 'Daily reward claimed!', rewards: dailyRewards, totalAssets: await $asset.getAll() }; } }

Best Practices

  1. Use Descriptive Asset IDs

    • Use clear names like "gold", "experience_points", "crafting_materials"
    • Avoid generic names like "item1" or "currency"
  2. Always Validate Before Operations

    • Use $asset.has() to check availability before spending
    • Validate amounts are positive integers
    • Handle insufficient balance errors gracefully
  3. Consider Player Experience

    • Provide clear feedback for asset operations
    • Show loading states during transactions
    • Display current balances prominently
  4. Security Considerations

    • Server-side validation prevents cheating
    • All operations are atomic and consistent
    • Consider rate limiting for certain operations
  5. Performance Tips

    • Assets are automatically optimized for frequent updates
    • Use useAsset hook for automatic React synchronization
    • Use hasAsset() for efficient balance checks before operations
⚠️

Remember that assets are designed for fungible items only. For unique items, character data, or complex game objects, use User State or Collections instead.

Web3 CrossRamp Integration

Agent8 supports seamless Web3 token exchange through CrossRamp integration. Players can purchase in-game assets using real cryptocurrencies through a simple popup interface.

How CrossRamp Works

CrossRamp provides a bridge between Web3 wallets and your game’s asset system. When players want to purchase assets with cryptocurrency, they open a popup window that handles the entire transaction process securely.

import { useGameServer } from "@agent8/gameserver"; function TokenPurchaseButton() { const { connected, server } = useGameServer(); const handleOpenCrossRampShop = async () => { if (!connected) return; try { // Get the CrossRamp shop URL const shopUrl = await server.getCrossRampShopUrl("en"); // Open in popup window const popupWindow = window.open( shopUrl, "CrossRampShop", "width=1024,height=768,scrollbars=yes,resizable=yes" ); // Optional: Listen for popup close const checkClosed = setInterval(() => { if (popupWindow?.closed) { clearInterval(checkClosed); console.log("CrossRamp popup closed"); // Optionally refresh player assets here } }, 1000); } catch (error) { console.error("Failed to open CrossRamp shop:", error); } }; return ( <button onClick={handleOpenCrossRampShop} disabled={!connected}> Buy Tokens with Crypto </button> ); }

CrossRamp Features

  1. Popup-Based Integration: Simple URL generation and popup window - no complex SDK required
  2. Multi-Language Support: Available in English, Korean, Chinese, Japanese, and Spanish
  3. Secure Transactions: All cryptocurrency transactions are handled securely by CrossRamp
  4. Automatic Asset Delivery: Purchased tokens are automatically added to the player’s assets
  5. Real-Time Updates: Asset changes are immediately synchronized across all connected clients

Supported Languages

  • "en" - English
  • "ko" - Korean (한국어)
  • "zh" - Chinese (中文)
  • "ja" - Japanese (日本語)
  • "es" - Spanish (Español)

Server-Side CrossRamp URL Generation

server.js
class Server { async getShopUrl(language = "en") { // This is handled automatically by the SDK // The server.getCrossRampShopUrl() method manages all the details return await server.getCrossRampShopUrl(language); } async handleTokenPurchase(tokenType, amount) { // This would be called by CrossRamp after successful purchase // to mint the corresponding in-game assets await $asset.mint(tokenType, amount); return { success: true, newBalance: await $asset.get(tokenType) }; } }

Security & Authentication

  • User Account Linking: CrossRamp transactions are automatically linked to the player’s game account
  • Secure Token Exchange: All cryptocurrency transactions use secure, audited smart contracts
  • Transaction Verification: Each purchase is verified before assets are minted in-game
  • No Direct Wallet Access: Your game never handles cryptocurrency directly - only the resulting assets

CrossRamp integration requires proper configuration in your game server. The system automatically handles wallet connections, transaction processing, and asset delivery without requiring complex Web3 implementation in your game client.

Conclusion

The Asset Management system provides a robust foundation for handling any fungible resources in your game. By leveraging server-side validation, real-time synchronization, and convenient React hooks, you can build sophisticated in-game economies while maintaining data consistency and security.

Use assets for currencies, points, resources, and consumables, and combine them with other Agent8 features like Global State and Collections to create rich, interactive multiplayer experiences.

Last updated on