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.
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
- Popup-Based Integration: Simple URL generation and popup window - no complex SDK required
- Multi-Language Support: Available in English, Korean, Chinese, Japanese, and Spanish
- Secure Transactions: All cryptocurrency transactions are handled securely by CrossRamp
- Automatic Asset Delivery: Purchased tokens are automatically added to the player’s assets
- 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
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.