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
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.
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.
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
-
Use Descriptive Asset IDs
- Use clear names like
"gold","experience_points","crafting_materials" - Avoid generic names like
"item1"or"currency"
- Use clear names like
-
Always Validate Before Operations
- Use
$asset.has()to check availability before spending - Validate amounts are positive integers
- Handle insufficient balance errors gracefully
- Use
-
Consider Player Experience
- Provide clear feedback for asset operations
- Show loading states during transactions
- Display current balances prominently
-
Security Considerations
- Server-side validation prevents cheating
- All operations are atomic and consistent
- Consider rate limiting for certain operations
-
Performance Tips
- Assets are automatically optimized for frequent updates
- Use
useAssethook 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.
CROSS Mini Hub Integration
Players can exchange game items, coins, and CROSS through CROSS Mini Hub. Asset changes from the hub are automatically synchronized — no additional handling is required on the asset side.
// Open CROSS Mini Hub — assets update automatically after exchange
const url = await server.getCrossRampShopUrl("en");
window.open(url, "CrossRampShop", "width=1024,height=768");See CROSS Mini Hub for full integration details and supported languages.
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.