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.

typescript
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).

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.

typescript
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.reason field 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.