ERGO

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.

Share

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.

01
Agent: Node.js script with an Ergo wallet (keypair + testnet ERG)
02
Paid API: Express server requiring 0.001 ERG per request, checks on-chain payment
03
Payment flow: Agent sends Ergo TX → API verifies → API responds → Agent processes result
04
No human: Agent manages its own wallet. No user approves payments. Fully autonomous.

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

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

javascriptagent-wallet.js
// 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.

javascriptapi-server.js
// 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

javascriptagent-pay.js
// 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

javascriptagent.js
// 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

01
node api-server.js

Start the paid API service on port 3001

02
node agent.js

Run the agent — it builds and prints the unsigned TX

03
# Sign + submit TX via Nautilus or sigma-rust

Get the transaction ID from the explorer

04
# Re-run agent.js with the txId

API 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

Continue Learning

Join the Ergo Builders List

Weekly builder updates: guides, patterns, tools. No spam.

Follow for daily updates