How to Build an Agent That Pays for Its Own API Calls
In this tutorial, we build a minimal but complete agent payment system: an autonomous agent that holds an Ergo wallet, calls a paid API, sends an on-chain payment, and receives the API response — all without human intervention. End-to-end, on Ergo testnet, with full source code.
TL;DR
Complete System in ~100 Lines
Agent wallet, paid API service, payment transaction, and on-chain verification — all with working source code on Ergo testnet.
Agent Holds Its Own Wallet
The agent generates a keypair, fetches UTxOs from the testnet API, builds a transaction autonomously. No human approves payments.
API Verifies Payment On-Chain
The API service queries the Ergo blockchain for a payment UTxO with the correct call ID in R4. When found, it returns the response.
No Stripe, No KYC, No Merchant Account
One Fleet SDK transaction. The agent pays, the API verifies on-chain, the response is delivered. Fully autonomous.
What We're Building
A minimal but complete agent payment system — no Stripe, no KYC, no merchant account. Just a Node.js agent with a wallet, a paid Express API, and Fleet SDK to wire it all together.
Prerequisites: Node.js 18+, npm, and testnet ERG. Get testnet ERG free at testnet.ergofaucet.org after creating a testnet address in Nautilus (Settings → Testnet mode).
Step 1: Setup
mkdir ergo-agent-api && cd ergo-agent-api npm init -y npm install @fleet-sdk/core express node-fetch
You'll also need testnet ERG. Create a testnet address in Nautilus (Settings → Testnet mode) and fund it at testnet.ergofaucet.org.
Step 2: Agent Wallet
The agent needs a wallet — just a keypair and a way to fetch UTxOs. In production, use a proper key management service. For testnet, hardcode the address.
// agent-wallet.js
const TESTNET_API = "https://api-testnet.ergoplatform.com";
export const wallet = {
address: "YOUR_TESTNET_ADDRESS", // ← paste your testnet address
// In production: store private key in KMS, HSM, or env var
// privateKey: process.env.AGENT_PRIVATE_KEY
async getInputs() {
const res = await fetch(
`${TESTNET_API}/api/v1/boxes/unspent/byAddress/${this.address}`
);
const { items } = await res.json();
return items ?? [];
},
async getHeight() {
const res = await fetch(`${TESTNET_API}/api/v1/info`);
const { fullHeight } = await res.json();
return fullHeight;
}
};Step 3: Paid API Service
The API service checks for an on-chain payment before responding. It queries the Ergo blockchain for a UTxO at its address with the correct call ID in R4.
// api-server.js
import express from "express";
const app = express();
app.use(express.json());
const TESTNET_API = "https://api-testnet.ergoplatform.com";
const API_ADDRESS = "YOUR_API_SERVICE_ADDRESS"; // ← API's receiving address
const PRICE_NANOERG = 1000000; // 0.001 ERG per call
// Verify payment: check blockchain for UTxO at API_ADDRESS
// with callId encoded in R4
async function verifyPayment(callId) {
const res = await fetch(
`${TESTNET_API}/api/v1/boxes/unspent/byAddress/${API_ADDRESS}`
);
const { items } = await res.json();
return items?.some(box => {
const r4 = box.additionalRegisters?.R4;
if (!r4) return false;
// R4 is hex-encoded: "0e" prefix + hex(callId)
const decodedR4 = Buffer.from(r4.slice(4), "hex").toString();
return decodedR4 === callId && box.value >= PRICE_NANOERG;
}) ?? false;
}
// Paid endpoint
app.post("/api/analyze", async (req, res) => {
const { callId, txId, data } = req.body;
if (!callId || !txId) {
return res.status(400).json({ error: "callId and txId required" });
}
// Check payment (with simple polling for testnet)
let paid = false;
for (let attempt = 0; attempt < 5; attempt++) {
paid = await verifyPayment(callId);
if (paid) break;
await new Promise(r => setTimeout(r, 3000)); // wait 3s between checks
}
if (!paid) {
return res.status(402).json({
error: "Payment required",
payTo: API_ADDRESS,
price: PRICE_NANOERG,
callId,
});
}
// Payment confirmed — return result
res.json({
callId,
result: `Analysis complete for: ${data}`,
timestamp: Date.now(),
});
});
app.listen(3001, () => console.log("API service running on :3001"));Step 4: Agent Sends Payment
// agent-pay.js
import { TransactionBuilder, OutputBuilder, SColl, SByte } from "@fleet-sdk/core";
import { wallet } from "./agent-wallet.js";
const TESTNET_API = "https://api-testnet.ergoplatform.com";
const API_ADDRESS = "YOUR_API_SERVICE_ADDRESS";
const PRICE_NANOERG = 1000000; // 0.001 ERG
export async function payForAPICall(callId) {
const height = await wallet.getHeight();
const inputs = await wallet.getInputs();
if (!inputs.length) throw new Error("No UTxOs — fund your agent wallet");
// Encode callId in R4 so the API can match payment to call
const paymentOutput = new OutputBuilder(PRICE_NANOERG.toString(), API_ADDRESS)
.setAdditionalRegisters({
R4: SColl(SByte, Buffer.from(callId, "utf8")),
});
const unsignedTx = new TransactionBuilder(height)
.from(inputs)
.to(paymentOutput)
.sendChangeTo(wallet.address)
.payMinFee()
.build()
.toEIP12Object();
// NOTE: In this tutorial you must sign externally (Nautilus or server key)
// For production server-side signing, see: sigma-rust or ergo-lib-wasm
console.log("Unsigned TX (sign and submit):");
console.log(JSON.stringify(unsignedTx, null, 2));
// After signing and submitting, return the txId
// const txId = await submitSignedTx(signedTx);
// return txId;
}Step 5: Agent Calls the API
// agent.js — the autonomous agent
import { payForAPICall } from "./agent-pay.js";
async function runAgent(data) {
const callId = `call-${Date.now()}-${Math.random().toString(36).slice(2)}`;
console.log(`[Agent] Task: analyze "${data}"`);
console.log(`[Agent] Call ID: ${callId}`);
// 1. Pay for the API call
console.log("[Agent] Sending payment...");
const txId = await payForAPICall(callId);
// (in this tutorial: manually sign and submit the printed TX)
// txId = "the submitted transaction ID"
// 2. Call the paid API (with callId + txId as proof of payment)
console.log("[Agent] Calling API...");
const res = await fetch("http://localhost:3001/api/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ callId, txId, data }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`API error: ${err.error}`);
}
const result = await res.json();
console.log("[Agent] Result:", result);
return result;
}
// Run
runAgent("the latest Ergo block data").catch(console.error);Step 6: Run It End-to-End
node api-server.jsStart the paid API service on port 3001
node agent.jsRun the agent — it builds and prints the unsigned TX
# Sign + submit TX via Nautilus or sigma-rustGet the transaction ID from the explorer
# Re-run agent.js with the txIdAPI verifies payment on-chain, returns result
Production next step: Add server-side signing using ergo-lib-wasm or sigma-rust so the agent signs transactions without a browser wallet. Then the entire flow runs headlessly.
Frequently Asked Questions
Share this post
Help spread the word about Ergo's innovative blockchain technology