Skip to content

Chapter 6: DeFi Trading

Mercante’s game actions map naturally to DeFi bridge operations:

Game ActionDeFi Bridge CallWhat Happens
Swap (silk for gold)DeFiBridge.swap()Exchange goods via AMM adapter, fee deducted, price impact applied
Invest (deposit wine)DeFiBridge.supply()Supply assets to lending protocol, earn yield per turn
Withdraw (from lending)DeFiBridge.withdraw()Pull assets with accrued interest
Quote checkDeFiBridge.getSwapQuote()Preview swap output before committing
Rate checkDeFiBridge.getSupplyRate()Check current lending yield

In the pure game logic (Chapter 1), we implemented simplified versions. Now we show how the full DeFi bridge powers those mechanics.

Each Mercante good maps to a DeFi Token descriptor:

import type { Token } from "@ludus/defi-bridge";
import type { Good } from "./types";
export function goodToToken(good: Good): Token {
const hex = Buffer.from(good).toString("hex").padEnd(40, "0");
return {
address: `0x${hex}`,
symbol: good.toUpperCase(),
decimals: 18,
chainId: 84532, // Base Sepolia
};
}

When a merchant swaps goods, the DeFi bridge calculates the output:

export function calculateSwapOutput(
goodIn: Good,
goodOut: Good,
amountIn: number,
prices: Record<Good, number>,
feeBps: number = 30,
): { amountOut: number; fee: number; priceImpact: number } {
const valueIn = prices[goodIn] * amountIn;
// 1. Deduct swap fee (default: 30 bps = 0.3%)
const fee = valueIn * (feeBps / 10000);
const netValue = valueIn - fee;
// 2. Calculate raw output at market prices
const rawOutput = netValue / prices[goodOut];
// 3. Apply price impact (0.5% per unit — simulates AMM curve)
const impactMultiplier = 1 - 0.005 * rawOutput;
const adjustedOutput = rawOutput * Math.max(impactMultiplier, 0.9);
return {
amountOut: Math.floor(adjustedOutput),
fee: Math.round(fee * 100) / 100,
priceImpact: Math.round((1 - adjustedOutput / rawOutput) * 10000) / 100,
};
}

Why this matters for gameplay:

  • Small swaps are nearly free (0.3% fee, minimal impact)
  • Large swaps suffer price impact — the AMM curve penalizes size
  • This creates a strategy tension: many small swaps vs. one big swap

In a production game, the swap action would flow through the full DeFiBridge pipeline:

import { DeFiBridge, AdapterRegistry, TransactionPipeline } from "@ludus/defi-bridge";
import { UniswapV4Adapter } from "@ludus/defi-bridge";
import { PermissionEngine, PermissionStage } from "@ludus/defi-bridge";
// 1. Register protocol adapters
const adapters = new AdapterRegistry();
adapters.register(new UniswapV4Adapter());
// 2. Set up permission engine (tier-based access control)
const permissions = new PermissionEngine();
// 3. Build the transaction pipeline
const pipeline = new TransactionPipeline({
customStages: [new PermissionStage(permissions)],
});
// 4. Create the bridge facade
const bridge = new DeFiBridge(adapters, pipeline);
// 5. Execute a swap
const result = await bridge.swap({
protocol: "uniswap-v4",
tokenIn: goodToToken("silk"),
tokenOut: goodToToken("gold"),
amountIn: BigInt(5e18), // 5 tokens (18 decimals)
slippageBps: 50,
});

The pipeline validates permissions, estimates gas, simulates the transaction, and then signs + broadcasts it through the agent’s wallet.

export function calculateLendingReturn(
amount: number,
turnsHeld: number,
yieldRate: number,
): { principal: number; yield: number; total: number } {
const yieldAmount = amount * yieldRate * turnsHeld;
return {
principal: amount,
yield: Math.floor(yieldAmount),
total: amount + Math.floor(yieldAmount),
};
}

At the default 2% per turn:

  • 10 Wine invested for 5 turns = 10 + floor(10 * 0.02 * 5) = 11 Wine
  • 10 Gold invested for 10 turns = 10 + floor(10 * 0.02 * 10) = 12 Gold

The DeFi bridge tracks every agent’s exposure:

import { PositionTracker } from "@ludus/defi-bridge";
const tracker = new PositionTracker();
// Open a position when merchant invests
const posId = tracker.openPosition("cosimo", {
protocol: "aave-v3",
tokenIn: goodToToken("wine"),
amountIn: BigInt(10e18),
});
// Check exposure
const exposure = tracker.getExposure("cosimo");
// exposure.byProtocol: { "aave-v3": 10 }
// exposure.byToken: { "WINE": 10 }
// exposure.totalExposure: 10
// Portfolio snapshot
const snapshot = tracker.getPortfolioSnapshot("cosimo");
// snapshot.openCount: 1, snapshot.realizedPnl: 0

This feeds into the spectator UI — viewers can see each merchant’s DeFi exposure in real-time.

The DeFi bridge includes 5 circuit breakers that protect agents from catastrophic losses:

BreakerTrips WhenDefault
DailyLossBreakerCumulative losses exceed threshold5 ETH / 24h
ProtocolAnomalyBreakerToo many reverts or excessive slippage3 reverts or 5%+ slippage
GasSpikeBreakerGas price exceeds baseline multiplier3x normal
PositionSizeBreakerSingle transaction too large50 ETH
CorrelationBreakerCorrelated asset group exposure too high100 ETH
describe("DeFi — Swap Calculation", () => {
it("calculates output with fee deduction", () => {
const result = calculateSwapOutput("silk", "wool", 5, BASE_PRICES, 30);
expect(result.amountOut).toBeGreaterThan(0);
expect(result.fee).toBeGreaterThan(0);
});
it("higher fees reduce output", () => {
const low = calculateSwapOutput("gold", "silk", 1, BASE_PRICES, 10);
const high = calculateSwapOutput("gold", "silk", 1, BASE_PRICES, 100);
expect(high.amountOut).toBeLessThanOrEqual(low.amountOut);
});
});
describe("DeFi — Lending Returns", () => {
it("calculates principal + yield", () => {
const result = calculateLendingReturn(10, 5, 0.02);
expect(result.total).toBe(11); // 10 + floor(10 * 0.02 * 5)
});
});

Mercante’s economy is powered by real DeFi mechanics:

  • Swaps with fees and price impact (AMM curve simulation)
  • Lending with compounding yield
  • Position tracking for spectator visibility
  • Circuit breakers for agent safety
  • Full pipeline: permissions -> gas estimation -> simulation -> signing -> broadcast

Next: Chapter 7 — Place Your Bets