Selling to agents on Bonded

Bill agents in arrears against a bond you can read on chain, settle when you like, take quoted jobs in escrow.

What you set up

As a merchant you sell a metered service or a job to agents. Bonded lets you serve them on credit: an operator opens a credit line to you, backed by its bond; the agent signs a voucher with each request; you settle the latest voucher while it's still valid (vouchers expire 30 minutes after they're signed by default, and never after the line), and the middleware's worker does that for you. If the vault ever can't pay, the bond pays your claim.

You need:

  • A Solana wallet as your merchant identity. Operators open lines to its address; settlements pay its USDC account. Sign in with it in the app to see your lines.
  • The merchant middleware in front of your service. It answers agents with the tab offer, checks each voucher, meters each request and runs the settlement worker.
  • A Postgres database for the middleware's meter: the vouchers you hold, the requests you served, and the settlement jobs.

The merchant view

Sign in with your merchant wallet. The app shows:

  • Lines to you: each operator's line, with its limit, what's unsettled and settled, its expiry, and the operator's coverage ratio and record (tabs settled, defaults).
  • Settlements: every settlement and payment you've received.
  • Claims: claims open on a defaulted bond, with the amount you're due.
  • Escrow jobs: jobs funded to you, with their deadline. Job … › Mark delivered puts the delivery receipt on chain; Collect after the dispute window releases the payment once the window has passed.

Give operators your wallet address so they can open lines to you; the line appears here when it confirms.

Install the middleware

The packages are served by this site:

npm install https://bonded.cash/packages/bonded-merchant-middleware-0.1.0.tgz @solana/[email protected] @noble/[email protected] @noble/[email protected] hono

Create the meter's tables in your database once:

DATABASE_URL=postgresql://… npx -y https://bonded.cash/packages/bonded-merchant-middleware-0.1.0.tgz migrate

Generate a keypair for the merchant wallet on your server, or use an existing one, and keep the file there; the worker signs settlements with it:

solana-keygen new --no-bip39-passphrase -o merchant.json
solana-keygen pubkey merchant.json

Connect

import fs from 'node:fs'
import { Hono } from 'hono'
import { createBondedMerchant } from '@bonded/merchant-middleware/node'

const merchant = await createBondedMerchant({
  apiUrl: 'https://api-production-b0c5e.up.railway.app',
  authenticationOrigin: 'https://bonded.cash',
  rpcUrl: process.env.SOLANA_RPC_URL!,
  programId: process.env.BONDED_PROGRAM_ID!,
  genesisHash: process.env.BONDED_GENESIS_HASH!,
  keypair: JSON.parse(fs.readFileSync('merchant.json', 'utf8')),
  databaseUrl: process.env.DATABASE_URL!,
  // What one request costs, in USDC base units, and how many units it meters.
  price: async (request) => ({ amount: 25_000n, units: 1n }),
  // Who's calling: your own authentication of the client. Return false to refuse.
  admit: async (request) => {
    const client = await authenticate(request)
    return client ? { clientId: client.id } : false
  },
})

const app = new Hono()
app.use('/v1/*', merchant.hono())
app.post('/v1/search', (c) => c.json(search(c.req)))

merchant.start() // the settlement and claims worker
  • programId and genesisHash are the pins from the service's values. The middleware checks both against the API and its own RPC before it accepts a voucher.
  • Express: app.use('/v1', merchant.express()), mounted before body parsers. Workers and other fetch hosts: merchant.handle(request, serve).
  • Agents name the line they pay on in an X-Bonded-Line header.
  • The middleware's timing thresholds (how long a read is cached, how long a voucher lives, how far ahead the worker settles before an expiry) have defaults that fit together; each can be set.

How a request is paid

  1. The agent calls your endpoint with an Idempotency-Key and its line in X-Bonded-Line.
  2. The middleware answers 402 Payment Required with one bonded-tab offer: the price, the new cumulative amount, the next nonce, an expiry, and a digest of this exact request. The offer is in the JSON body and, base64-encoded, in the Payment-Required header.
  3. The agent's SDK checks the offer and repeats the request with the signed voucher in Payment-Signature.
  4. The middleware verifies the signature, the line's limit, the counters and the digest, records the voucher, and calls your handler. Your response is stored with the request key, so a retry with the same key gets the same response and is never charged twice.

New credit stops on a line that's frozen, expired, defaulted or at its limit; the agent gets a refusal it can read.

For browsers, allow your site's exact origins, the Idempotency-Key and Payment-Signature request headers, and expose Payment-Required.

Settlement

The worker started by merchant.start() settles each line's latest voucher on its schedule, one line per transaction, and earlier whenever a line or a voucher nears its expiry. Each settlement moves voucher − settled from the agent's vault to your USDC account, less the line's settlement fee.

If a settlement finds the vault short, nothing moves from the vault; the program opens a default event on the operator's bond. The worker files your claim with your latest voucher right away. When the 72-hour claim window closes, the bond pays each claim min(voucher − settled, line limit), in full or pro-rata. See Fees and settlement.

Keep the worker running while you have open lines: it's also what attests vouchers when an agent's key rotates, before the switch.

Escrow jobs

For a job you deliver later, send the agent a signed quote. The agent's vault funds it, and the payment is held until you deliver:

const quote = await merchant.quote({
  vaultId,                       // from the agent's request
  amount: 5_000_000n,            // $5.00
  specification: 'Summarise the 2026 filings for ACME, 2 pages',
  deliverBy: new Date(Date.now() + 6 * 3600_000),
  expiresInSeconds: 300,
})
// … the agent funds it; then, once the work is done:
await merchant.deliver(escrowId, 'https://files.example.com/acme-2026.pdf')
  • Funding moves the price and a 25 bps fee from the vault into the job.
  • deliver puts the deliverable's SHA-256 on chain as the receipt.
  • The agent accepts it, which releases the payment to you. If the quote names an arbiter, the agent may instead dispute it within 48 hours, and the arbiter decides. With no dispute, collect after the window from the app or with merchant.release(escrowId).
  • If you don't deliver by deliverBy, the job refunds to the vault.

Fees

The tab settlement fee is 10 bps of each settlement, taken from the settled amount, and waived during the beta for every line opened during the waiver. Each settlement also pays Solana's network fee from your wallet: a 10,000-lamport base fee for your signature and the voucher's Ed25519 check, plus a small priority fee. See Fees and settlement.

The service's values

APIhttps://api-production-b0c5e.up.railway.app
Network…
Program—
Genesis hash—
USDC mint—

NextAgent developers