Integration Patterns
Battle-tested patterns for common game economy scenarios. Each pattern shows the SDK calls and explains the economic reasoning.
🎯 Quest Rewards (Faucet)
The simplest faucet — grant currency when a player completes an objective. Tag with metadata so you can track which faucets are pumping too much gold.
async function onQuestComplete(playerId: string, questId: string, reward: number) {
const player = eco.player(playerId);
await player.grant("gold", reward, {
reason: "quest_complete",
questId,
});
}🛒 Item Shop (Sink)
Atomic purchases — currency is deducted and item added in one operation. If the player can't afford it, nothing happens (no partial state).
async function buyItem(playerId: string, itemId: string) {
const player = eco.player(playerId);
try {
await player.purchase(itemId);
return { success: true };
} catch (err) {
if (err instanceof EconomyError && err.status === 422) {
return { success: false, reason: "insufficient_funds" };
}
throw err;
}
}💎 Loot Drops (Random Faucet)
Roll loot on your server, then use the economy to grant it. Keep loot logic separate from the economy — the economy just tracks ownership.
async function onMonsterKill(playerId: string, monsterId: string) {
const loot = rollLootTable(monsterId); // your loot logic
const player = eco.player(playerId);
// Grant currency drops
if (loot.gold > 0) {
await player.grant("gold", loot.gold, {
reason: "monster_kill",
monsterId,
});
}
// Grant item drops (items must be pre-defined via defineItem)
for (const item of loot.items) {
await player.grant(item.currencyId ?? "gold", 0); // ensure player exists
// Items are granted through your own inventory system
// or via a custom "loot_grant" transaction
}
}⚔️ PvP Looting (Transfer)
When one player defeats another, transfer a percentage of the loser's gold. This is currency-neutral — no new gold enters the system.
async function onPvPKill(winnerId: string, loserId: string) {
const loser = eco.player(loserId);
const balances = await loser.balance();
const lootAmount = Math.floor((balances.gold ?? 0) * 0.1); // 10% loot
if (lootAmount > 0) {
await loser.spend("gold", lootAmount, { reason: "pvp_death" });
const winner = eco.player(winnerId);
await winner.grant("gold", lootAmount, {
reason: "pvp_kill",
victimId: loserId,
});
}
}🏛️ Taxation (Periodic Sink)
A progressive tax on wealthy players prevents runaway concentration. Run this on a schedule (e.g. daily). The tax gold is destroyed (pure sink) or redistributed.
async function collectTaxes(playerIds: string[]) {
for (const id of playerIds) {
const player = eco.player(id);
const bal = await player.balance();
const gold = bal.gold ?? 0;
// Progressive: 0% under 1000, 2% over 1000, 5% over 10000
let taxRate = 0;
if (gold > 10000) taxRate = 0.05;
else if (gold > 1000) taxRate = 0.02;
if (taxRate > 0) {
const tax = Math.floor(gold * taxRate);
await player.spend("gold", tax, { reason: "daily_tax" });
}
}
}🤝 Player Trading (Marketplace)
Implement trading as a spend from buyer + grant to seller, with an optional transaction fee as a sink. The fee prevents infinite trading loops.
async function executeTrade(
buyerId: string,
sellerId: string,
price: number,
feePercent = 0.05
) {
const fee = Math.floor(price * feePercent);
const sellerReceives = price - fee;
const buyer = eco.player(buyerId);
const seller = eco.player(sellerId);
// Deduct from buyer (full price)
await buyer.spend("gold", price, { reason: "trade_buy" });
// Credit seller (minus fee)
await seller.grant("gold", sellerReceives, { reason: "trade_sell" });
// Fee is destroyed (sink) — not granted to anyone
}📅 Daily Login Bonus
A small, predictable faucet that keeps players engaged. Scale the bonus with consecutive days to incentivize streaks.
async function dailyLogin(playerId: string, consecutiveDays: number) {
const bonus = Math.min(50 + consecutiveDays * 10, 200); // cap at 200
const player = eco.player(playerId);
await player.grant("gold", bonus, {
reason: "daily_login",
day: consecutiveDays,
});
}🔧 Repair Costs (Usage Sink)
Charge players to maintain equipment. This creates a constant gold drain proportional to player activity — active players spend more, which counteracts their higher income.
async function repairEquipment(playerId: string, repairCost: number) {
const player = eco.player(playerId);
await player.spend("gold", repairCost, { reason: "equipment_repair" });
}💡 Economy Design Tips
- Balance faucets and sinks. If faucets > sinks, you get inflation. Track this on the dashboard.
- Tag every transaction. Use the
metadata.reasonfield religiously. It's how the AI Advisor identifies which faucet is causing inflation. - Stress-test before launch. Run the 1,000-agent simulation against your economy design. If it breaks in simulation, it'll break in production.
- Watch the Gini. A Gini above 0.6 means your economy has a wealth concentration problem. Add redistribution mechanics (taxes, login bonuses) to bring it down.