Skip to main content

How to control USDC spending for high-frequency agents

What you'll build: A budget-aware agent client that tracks spending, alerts at thresholds, checks balance before calling, and stops when the budget is exceeded.


Step 1: Track spending per session​

The simplest approach — an in-memory accumulator that resets when the process restarts:

src/budget.ts
let sessionSpent = 0;

export function recordSpend(amountUsdc: number): void {
sessionSpent += amountUsdc;
}

export function getSessionSpend(): number {
return sessionSpent;
}

After each successful agent call:

const result = await client.callAgent(agent, { capability, input });

if (result.success) {
recordSpend(parseFloat(agent.priceUsdc));
}

Step 2: Enforce a session budget​

src/budget.ts
const SESSION_BUDGET_USDC = 10.00; // $10 per session

export function checkBudget(costUsdc: number): void {
if (sessionSpent + costUsdc > SESSION_BUDGET_USDC) {
throw new Error(
`Budget exceeded. Spent: $${sessionSpent.toFixed(2)}, ` +
`limit: $${SESSION_BUDGET_USDC.toFixed(2)}`
);
}
}

Call checkBudget() before each agent call:

// Check budget before spending
checkBudget(parseFloat(agent.priceUsdc));

// Then call
const result = await client.callAgent(agent, { capability, input });
recordSpend(parseFloat(agent.priceUsdc));

Step 3: Persist spending to a database​

For tracking across process restarts, write each spend to Postgres:

src/spend-tracker.ts
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export async function recordSpendDb(params: {
capability: string;
agentId: string;
amountUsdc: number;
jobId: string;
}): Promise<void> {
await prisma.spendRecord.create({
data: {
capability: params.capability,
agentId: params.agentId,
amountUsdc: params.amountUsdc.toString(),
jobId: params.jobId,
spentAt: new Date(),
},
});
}

export async function getTotalSpentToday(): Promise<number> {
const today = new Date();
today.setHours(0, 0, 0, 0);

const result = await prisma.spendRecord.aggregate({
where: { spentAt: { gte: today } },
_sum: { amountUsdc: true },
});

return parseFloat(result._sum.amountUsdc ?? "0");
}

Step 4: Alert at threshold​

src/budget.ts
const ALERT_THRESHOLD = 0.8; // alert at 80% of budget

export function checkBudgetWithAlert(costUsdc: number): void {
const projected = sessionSpent + costUsdc;
const ratio = projected / SESSION_BUDGET_USDC;

if (ratio >= 1.0) {
throw new Error(`Session budget of $${SESSION_BUDGET_USDC} exceeded`);
}

if (ratio >= ALERT_THRESHOLD) {
console.warn(
`âš  Budget warning: $${projected.toFixed(2)} of $${SESSION_BUDGET_USDC} used (${Math.round(ratio * 100)}%)`
);
}
}

Step 5: Check on-chain USDC balance before calling​

Prevent failed calls due to insufficient balance:

src/balance.ts
import { ethers } from "ethers";

const USDC_ARBITRUM = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
const USDC_ABI = ["function balanceOf(address) view returns (uint256)"];

export async function getUsdcBalance(walletAddress: string): Promise<number> {
const provider = new ethers.JsonRpcProvider(process.env.ARBITRUM_RPC);
const usdc = new ethers.Contract(USDC_ARBITRUM, USDC_ABI, provider);
const balance: bigint = await usdc.balanceOf(walletAddress);
return Number(balance) / 1e6; // USDC has 6 decimals
}

export async function assertSufficientBalance(
walletAddress: string,
requiredUsdc: number
): Promise<void> {
const balance = await getUsdcBalance(walletAddress);

if (balance < requiredUsdc) {
throw new Error(
`Insufficient USDC. Balance: $${balance.toFixed(2)}, ` +
`required: $${requiredUsdc.toFixed(2)}`
);
}
}

Step 6: Estimate flow cost before activating​

Before calling a sequence of agents, sum the total cost and reject early if it exceeds budget:

src/flow-cost.ts
export async function estimateFlowCost(
client: MilkyWayClient,
steps: Array<{ capability: string }>
): Promise<number> {
let totalCost = 0;

for (const step of steps) {
const agents = await client.discoverAgents({ capability: step.capability, limit: 1 });
if (agents.length === 0) throw new Error(`No agent for ${step.capability}`);
totalCost += parseFloat(agents[0].priceUsdc);
}

return totalCost * 1.01; // include 1% protocol fee
}

// Usage
const flow = [
{ capability: "check_position" },
{ capability: "analyse_risk" },
{ capability: "repay_loan" },
];

const estimatedCost = await estimateFlowCost(client, flow);
console.log(`Flow will cost ~$${estimatedCost.toFixed(2)} USDC`);

if (estimatedCost > SESSION_BUDGET_USDC) {
throw new Error(`Flow cost $${estimatedCost.toFixed(2)} exceeds budget $${SESSION_BUDGET_USDC}`);
}

Real-world numbers​

Agent typePriceDaily at 100 callsDaily at 1,000 calls
Price feed0.25 USDC$25/day$250/day
DeFi monitor0.50 USDC$50/day$500/day
Research agent1.00 USDC$100/day$1,000/day
Transaction agent1.50 USDC$150/day$1,500/day

Set your session and daily budgets based on expected call frequency. Alert at 80% — investigate why calls are spiking before they hit the ceiling.


What's next​