Skip to content

axonplex/synapse-agent-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@synapse-network/agent-kit

Turn any API, scraper, or data source into a paid AI agent in under 5 minutes.

Synapse Agent Kit wraps your existing code and gives it a standard interface that the Synapse network (and any x402-compatible client) can discover, pay, and query. You keep your logic — we handle the plumbing.

Your code + Agent Kit = Paid agent on the Synapse network

Table of Contents


Install

npm install @synapse-network/agent-kit

Requires Node.js 18+ (uses native fetch).


30-Second Quick Start

import { SynapseAgent } from "@synapse-network/agent-kit";

const agent = new SynapseAgent({
  name: "My Agent",
  price: 0.02,       // $0.02 USD per query (set to 0 for free)
});

agent.handle(async (query) => {
  // Replace this with your actual logic
  return {
    results: [{ answer: `You asked: ${query}` }],
    results_count: 1,
  };
});

agent.start(4102);

Run it:

node my-agent.js

Test it:

# Health check
curl http://localhost:4102/health

# Send a query
curl -X POST http://localhost:4102/query \
  -H "Content-Type: application/json" \
  -d '{"query": "hello world"}'

# See pricing
curl http://localhost:4102/x402/price

# See agent metadata
curl http://localhost:4102/manifest

That's it. Your agent is running with health checks, metrics, rate limiting, and x402 pricing — all automatic.


How It Works

                        ┌─────────────────────────────────┐
                        │  @synapse-network/agent-kit      │
 Incoming query ──────► │                                  │
                        │  1. Rate limiting                │
                        │  2. Payment verification (x402)  │
                        │  3. Your middleware (optional)    │
                        │  4. YOUR HANDLER ◄── your code   │
                        │  5. Standard response format     │
                        │                                  │
                        └──────────┬──────────────────────┘
                                   │
                    Auto-generated endpoints:
                    GET  /health    — is the agent alive?
                    POST /query     — send a query, get results
                    GET  /manifest  — agent metadata for discovery
                    GET  /x402/price — how much does it cost?
                    GET  /metrics   — queries served, errors, revenue

You only write the handler — the function that takes a query and returns results. Everything else is handled by the kit.


Walkthrough: Web Scraper to Paid Agent

Let's say you have a scraper that pulls product data. Here's how to wrap it.

Before (plain scraper):

import * as cheerio from "cheerio";

async function scrapeProducts(query) {
  const html = await fetch("https://example.com/search?q=" + query).then(r => r.text());
  const $ = cheerio.load(html);
  const products = [];
  $(".product").each((i, el) => {
    products.push({
      name: $(el).find(".title").text(),
      price: $(el).find(".price").text(),
      rating: $(el).find(".rating").text(),
    });
  });
  return products;
}

After (Synapse agent):

import * as cheerio from "cheerio";
import { SynapseAgent } from "@synapse-network/agent-kit";

// Your existing scraper — unchanged
async function scrapeProducts(query) {
  const html = await fetch("https://example.com/search?q=" + query).then(r => r.text());
  const $ = cheerio.load(html);
  const products = [];
  $(".product").each((i, el) => {
    products.push({
      name: $(el).find(".title").text(),
      price: $(el).find(".price").text(),
      rating: $(el).find(".rating").text(),
    });
  });
  return products;
}

// +5 lines to make it a paid agent
const agent = new SynapseAgent({
  name: "Product Scraper",
  description: "Real-time product data with pricing and ratings",
  domain: "general",
  price: 0.02,
  capabilities: ["product_data", "pricing", "reviews"],
});

agent.handle(async (query) => {
  const products = await scrapeProducts(query);
  return { results: products, results_count: products.length };
});

agent.start(4102);

Your scraper code is completely unchanged. The agent kit just wraps it with a standard API.


Walkthrough: Add to Existing Express App

Already have an Express server? Don't create a new one — use the middleware.

import express from "express";
import { synapseMiddleware } from "@synapse-network/agent-kit/middleware";

const app = express();
app.use(express.json());

// ---- Your existing routes (unchanged) ----
app.get("/api/users", (req, res) => { /* ... */ });
app.post("/api/orders", (req, res) => { /* ... */ });

// ---- Add Synapse agent in one line ----
app.use(synapseMiddleware({
  name: "My Existing API",
  domain: "general",
  price: 0.05,
  handler: async (query, ctx) => {
    // Reuse any existing function from your codebase
    const data = await myDatabaseQuery(query);
    return { results: data, results_count: data.length };
  },
}));

app.listen(3000);

This adds the following routes alongside your existing ones:

  • GET /synapse/health
  • POST /synapse/query
  • GET /synapse/manifest
  • GET /synapse/metrics

Custom prefix:

app.use(synapseMiddleware({ prefix: "/v1/agent", /* ... */ }));
// Routes: /v1/agent/health, /v1/agent/query, etc.

Walkthrough: Paid Agent with Payment Verification

For agents that charge real money, add a payment verifier:

const agent = new SynapseAgent({
  name: "Premium Data Agent",
  price: 0.25,
  token: "USDC",
  network: "hedera",

  // This function runs BEFORE your handler.
  // Return { valid: true } to proceed, { valid: false } to reject.
  verifyPayment: async (paymentHeader, price, ctx) => {
    if (!paymentHeader) {
      return { valid: false, reason: "No payment provided" };
    }

    // In production: verify the x402 payment proof on-chain
    // For now: accept any non-empty payment header
    const txHash = paymentHeader; // parse from x402 proof
    return { valid: true, tx_hash: txHash };
  },
});

When verifyPayment is set and the payment is missing/invalid, the kit auto-returns:

{
  "status": "payment_required",
  "price_usd": 0.25,
  "token": "USDC",
  "network": "hedera",
  "message": "No payment provided"
}

HTTP status: 402 Payment Required.


Configuration Reference

new SynapseAgent({
  // --- Identity ---
  name: "My Agent",                    // Required. Display name.
  description: "What this agent does", // Shown in discovery/manifest.
  id: "my-custom-id",                  // Optional. Auto-generated if omitted.
  version: "1.0.0",                    // Optional. Shown in health/manifest.

  // --- Pricing ---
  price: 0.05,                         // USD per query. Default: 0 (free).
  token: "USDC",                       // Payment token. "USDC" or "HBAR".
  network: "hedera",                   // Settlement network. "hedera" or "base".

  // --- Discovery ---
  domain: "finance",                   // Agent's data domain. Used for routing.
                                       // Options: real_estate, finance, legal,
                                       //          medical, travel, general
  capabilities: ["stock_quotes"],      // Tags for search/matching.
  ensName: "myagent.synapse.eth",      // ENS subname for identity.

  // --- Behavior ---
  rateLimit: { maxPerMinute: 60 },     // Per-IP rate limit. Default: 60/min.

  // --- Network ---
  synapseServer: "https://...",        // Auto-register with Synapse on startup.
                                       // Also reads SYNAPSE_SERVER_URL env var.

  // --- Payment ---
  verifyPayment: async (header, price, ctx) => {
    // Optional. If set, x402 payment is required.
    // Return { valid: true, tx_hash: "0x..." } or { valid: false, reason: "..." }
  },
});

Handler API

The handler is the function that does your actual work.

agent.handle(async (query, context) => {
  // query   — string, the user's natural language query
  // context — object with parsed request data (see below)

  // Do your thing: scrape, query a DB, call an API, compute...
  const data = await doSomething(query);

  // Return results in this format:
  return {
    results: data,              // Required. Array of result objects.
    results_count: data.length, // Required. Total number of results.
    market_context: {           // Optional. Summary or metadata.
      source: "my-database",
      freshness: "real-time",
    },
  };
});

Context Object

The second argument to your handler contains everything about the request:

agent.handle(async (query, ctx) => {
  ctx.query;      // "find cheap flights"  — same as the first argument
  ctx.filters;    // { min_price: 100 }    — from request body
  ctx.limit;      // 10                    — max results requested
  ctx.sort_by;    // "price_asc"           — from request body
  ctx.headers;    // { "authorization": "Bearer ..." } — raw HTTP headers

  ctx.payment.verified;  // true/false — was x402 payment verified?
  ctx.payment.method;    // "x402" or null
  ctx.payment.tx_hash;   // "0x..." or null

  ctx.agent.id;     // "agent-abc123"
  ctx.agent.name;   // "My Agent"
  ctx.agent.domain;  // "finance"
  ctx.agent.price;   // 0.05
});

Callers send queries like:

curl -X POST http://localhost:4102/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "find cheap flights to Paris",
    "filters": { "max_price": 500 },
    "limit": 5,
    "sort_by": "price_asc"
  }'

Middleware

Add functions that run before your handler. Great for logging, validation, auth, or enrichment.

// Logging
agent.use(async (ctx, next) => {
  console.log(`[${new Date().toISOString()}] Query: "${ctx.query}"`);
  await next();
});

// Input validation
agent.use(async (ctx, next) => {
  if (!ctx.query || !ctx.query.trim()) {
    throw new Error("Query cannot be empty");  // Returns 400 automatically
  }
  if (ctx.query.length > 1000) {
    throw new Error("Query too long (max 1000 chars)");
  }
  await next();
});

// Custom auth (in addition to x402)
agent.use(async (ctx, next) => {
  const apiKey = ctx.headers["x-api-key"];
  if (!apiKey || apiKey !== process.env.MY_API_KEY) {
    throw new Error("Invalid API key");
  }
  await next();
});

// Enrich context
agent.use(async (ctx, next) => {
  ctx.customData = { region: "eu", tier: "premium" };
  await next();
});

Middleware runs in order. Throw an error to stop the chain and return 400.


Auto-Generated Endpoints

Every agent gets these automatically:

GET /health

{ "status": "ok", "agent": "agent-abc123", "name": "My Agent", "domain": "general", "version": "1.0.0", "uptime_ms": 45000 }

POST /query

Main endpoint. Send { "query": "...", "filters": {}, "limit": 10, "sort_by": "..." }.

Returns your handler's results wrapped in a standard format:

{
  "status": "ok",
  "agent": "My Agent",
  "query_parsed": { "original": "...", "filters": {}, "limit": 10 },
  "response_time_ms": 142,
  "results": [ ... ],
  "results_count": 5,
  "market_context": { ... }
}

GET /manifest

Agent metadata for discovery. Returned to Synapse server during registration.

{
  "id": "agent-abc123",
  "name": "My Agent",
  "description": "...",
  "domain": "general",
  "price_per_query_usd": 0.02,
  "token": "USDC",
  "network": "base",
  "capabilities": ["..."],
  "ens_name": "myagent.synapse.eth",
  "x402": { "enabled": true, "price_usd": 0.02 },
  "endpoints": { "query": "/query", "health": "/health", ... }
}

GET /x402/price

Payment negotiation. Clients check this before paying.

{ "price_usd": 0.02, "token": "USDC", "network": "base", "payment_required": true }

GET /metrics

Operational stats.

{ "totalQueries": 142, "totalErrors": 3, "totalRevenue": 2.84, "uptime_ms": 3600000 }

Response Format

Your handler should return:

{
  // Required
  results: [
    { title: "...", price: 29.99, url: "..." },
    { title: "...", price: 49.99, url: "..." },
  ],
  results_count: 2,

  // Optional — summary data about the results
  market_context: {
    source: "my-database",
    freshness: "real-time",
    total_available: 1500,
  },
}

The kit wraps this with status, agent, response_time_ms, and query_parsed automatically. You don't need to add those.


Payment & x402

Free agents

Set price: 0 (default). No payment verification. Anyone can query.

Paid agents without verification

Set price: 0.05 but don't set verifyPayment. The agent advertises its price (via /x402/price and /manifest) but doesn't enforce payment — the Synapse server handles payment on behalf of the caller.

Paid agents with verification

Set both price and verifyPayment. The agent enforces payment itself:

verifyPayment: async (paymentHeader, price, ctx) => {
  // paymentHeader: value of X-Payment or X-402-Payment HTTP header
  // price: your agent's price in USD
  // ctx: the full request context

  // Verify the payment proof on-chain, via Hedera SDK, etc.
  const isValid = await verifyOnChain(paymentHeader, price);

  return isValid
    ? { valid: true, tx_hash: "0x..." }
    : { valid: false, reason: "Payment proof invalid" };
}

Connecting to the Synapse Network

Option 1: Auto-registration on startup

const agent = new SynapseAgent({
  name: "My Agent",
  synapseServer: "https://synapse.yourserver.com",  // or set SYNAPSE_SERVER_URL env
  // ...
});

On agent.start(), the kit POSTs to /api/agents/register on the Synapse server. Your agent appears in the registry and can be discovered by queries.

Option 2: Manual registration

Start your agent, then register it with the Synapse server yourself:

curl -X POST https://synapse.yourserver.com/api/agents/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Agent",
    "description": "...",
    "domain": "general",
    "price_per_query_usd": 0.02,
    "x402_endpoint": "https://my-agent.example.com/query"
  }'

Option 3: Standalone

Don't connect to Synapse at all. Your agent works as an independent API that any HTTP client can call directly.


FAQ

Do I need to rewrite my existing code? No. Your existing function goes inside agent.handle() as-is. The kit wraps it.

Can I use this with Python/Go/Rust? Not directly — this is a Node.js SDK. But the protocol is just HTTP + JSON. You can implement the same endpoints (/query, /health, /manifest, /x402/price) in any language and it'll work with the Synapse network. See Response Format and Auto-Generated Endpoints for the spec.

What if my service is slow? The kit has no timeout on your handler. If your scraper takes 10 seconds, the response takes 10 seconds. Add your own timeout logic in the handler if needed.

What if my service crashes? The kit catches errors from your handler and returns a clean 500 { "status": "error", "message": "..." }. Your Express process stays alive.

How does rate limiting work? Per-IP sliding window. Default: 60 requests/minute. Returns 429 when exceeded. Configure with rateLimit: { maxPerMinute: 100 }.

Can I run multiple agents in one process? Yes — use .attach() instead of .start():

const app = express();
agent1.attach(app, "/agent1");
agent2.attach(app, "/agent2");
app.listen(4102);

License

MIT — ETHGlobal Cannes 2026

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors