This document shows how to use the runtime packages with any API or service — not just TxODDS. Every code example is service-agnostic.
The typed message flow between agents:
import { formatWant, formatBid, formatAward, parseMessage } from '@pay/agent-runtime'
// Buyer posts a want
const want = formatWant({
service: 'weather-forecast',
budgetSol: 0.05,
details: { region: 'eu-west', hours: 48 },
})
// Seller responds with a bid
const bid = formatBid({
priceSol: 0.03,
note: 'Standard 48h forecast, EU-West coverage.',
})
// Buyer awards
const award = formatAward({
seller: 'seller-weather',
priceSol: 0.03,
})
// Parse any incoming message
const msg = parseMessage(rawText)
if (msg.verb === 'BID') {
console.log(msg.priceSol, msg.note)
}| Verb | Direction | Purpose |
|---|---|---|
WANT |
Buyer → thread | Request for a service at a budget. |
BID |
Seller → thread | Price offer for the want. |
AWARD |
Buyer → thread | Selects a seller and confirms price. |
PAYMENT_REQUIRED |
Seller → buyer | Payment terms for the award — the primary settlement leg, rail=x402 by default. |
PAYMENT_PROOF |
Buyer → seller | Buyer's signed-but-unsubmitted transfer, bound to the reference. |
PAYMENT_CONFIRMED |
Seller → buyer | Seller submitted + verified the payment on-chain. |
DELIVERED |
Seller → thread | Delivery payload with content hash. |
VERIFY |
Buyer → verifier | Request delivery verification (informational — payment already settled). |
VERIFIED |
Verifier → thread | pass or fail with reason. |
SETTLED |
Buyer → thread | Round complete. |
import { createRailRouter } from '@pay/payment-runtime'
const router = createRailRouter({
solanaPay: { connection, wallet },
escrow: { connection, wallet, programId, arbiterProgramId },
x402: { connection, wallet },
})
// Pick a rail
const rail = router.get('escrow')
// Quote
const quote = await rail.quote({
service: 'weather-forecast',
amountSol: 0.03,
})
// Request payment
const request = await rail.requestPayment({
buyer: buyerPubkey,
seller: sellerPubkey,
amountSol: 0.03,
reference: orderReference,
service: 'weather-forecast',
})
// Verify payment on-chain
const verification = await rail.verifyPayment(request)
// Release (escrow only)
const result = await rail.release(order)
// Refund (escrow only)
const result = await rail.refund(order)import type { PaymentRail, PaymentRailKind } from '@pay/payment-runtime'
const myRail: PaymentRail = {
kind: 'my-custom-rail' as PaymentRailKind,
async quote(input) {
return {
amountSol: input.amountSol,
feeSol: 0.001,
estimatedConfirmMs: 5000,
}
},
async requestPayment(order) {
// Build the payment request for your rail
const txSig = await submitPayment(order)
return { txSignature: txSig, reference: order.reference }
},
async verifyPayment(request) {
// Independently verify on-chain — never trust caller-supplied signatures
const confirmed = await verifyOnChain(request.txSignature)
return { verified: confirmed, txSignature: request.txSignature }
},
}import { toProofReceipt } from '@pay/payment-runtime'
const receipt = toProofReceipt({
rail: 'escrow',
txSignature: 'abc123...',
reference: orderReference,
recipient: sellerPubkey,
amountSol: 0.03,
service: 'weather-forecast',
})
// Write to run ledgerimport { PolicyEngine } from '@pay/agent-runtime'
const policy = new PolicyEngine({
maxSpendPerRoundSol: 0.1,
maxSpendPerSessionSol: 1.0,
serviceAllowlist: ['weather-forecast', 'sports-odds'],
payoutWallet: sellerPubkey,
})
// Gate any fund-moving action
const decision = policy.enforce({
action: 'deposit',
amountSol: 0.03,
service: 'weather-forecast',
recipient: sellerPubkey,
})
if (!decision.allowed) {
throw new Error(`Policy blocked: ${decision.reason}`)
}import { solanaConnection } from '@pay/agent-runtime'
// Rejects mainnet URLs unless ALLOW_MAINNET=1
const connection = solanaConnection(process.env.SOLANA_RPC_URL)import { WalletSigner } from '@pay/agent-runtime'
const signer = WalletSigner.fromBase58(process.env.BUYER_KEYPAIR_B58)
const pubkey = signer.publicKey
const signed = await signer.signTransaction(tx)import { getBalance, getTokenAccounts, getTransaction } from '@pay/solana-agent-tools'
const balance = await getBalance(connection, pubkey)
const tokens = await getTokenAccounts(connection, pubkey)
const tx = await getTransaction(connection, signature)import { CoralClient } from '@pay/agent-runtime'
const coral = new CoralClient({
url: process.env.CORAL_CONNECTION_URL,
agentName: 'my-agent',
})
// Wait for a mention in a thread
const mention = await coral.waitForMention()
// Post a message
await coral.postMessage(mention.threadId, formatBid({
priceSol: 0.03,
note: 'Ready to deliver.',
}))import { createHarness } from '@pay/harness-runtime'
// in-process adapter: runs deliverService() in this process
const harness = createHarness('in-process', {
deliverService: async (want, cfg) => {
const data = await fetch('https://api.example.com/data')
return { payload: await data.json(), contentHash: hash(data) }
},
})
// claude-code adapter: shells out to Claude Code CLI
const harness = createHarness('claude-code', {
prompt: 'Deliver the requested analysis.',
maxTurns: 5,
})Replace deliverService() to integrate any API:
// examples/my-service/agent/service.ts
import { createHash } from 'crypto'
export async function deliverService(
want: { service: string; details: Record<string, unknown> },
cfg: { apiKey: string },
): Promise<{ payload: unknown; contentHash: string }> {
// Call your API
const response = await fetch('https://api.example.com/v1/query', {
method: 'POST',
headers: {
'Authorization': `Bearer ${cfg.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(want.details),
})
const payload = await response.json()
const contentHash = createHash('sha256')
.update(JSON.stringify(payload))
.digest('hex')
return { payload, contentHash }
}# coral-agents/seller-myservice/coral-agent.toml
[agent]
name = "seller-myservice"
runtime = "docker"
image = "seller-agent"
[agent.env]
AGENT_NAME = "seller-myservice"
PERSONA = "myservice"
SERVICES = "my-api-service"
FLOOR_SOL = "0.01"
HARNESS = "in-process"
MY_API_KEY = "${MY_API_KEY}"// Minimal x402 middleware for any Express/Fastify endpoint
import { verifyX402Payment } from '@pay/payment-runtime'
app.get('/api/premium-data', async (req, res) => {
const payment = req.headers['x-payment']
if (!payment) {
return res.status(402).json({
price: 0.001,
recipient: sellerPubkey.toBase58(),
network: 'devnet',
})
}
const verified = await verifyX402Payment(connection, payment)
if (!verified.valid) {
return res.status(402).json({ error: 'Payment verification failed' })
}
const data = await fetchPremiumData()
res.json(data)
})| Variable | Package | Purpose |
|---|---|---|
SOLANA_RPC_URL |
agent-runtime | Solana RPC endpoint. Devnet enforced unless ALLOW_MAINNET=1. |
BUYER_KEYPAIR_B58 |
agent-runtime | Buyer wallet (base58 secret key). |
SELLER_WALLET |
agent-runtime | Seller's receive-only public key. |
SELLER_KEYPAIR_B58 |
payment-runtime | Seller's spend key (for x402 upstream procurement). |
CORAL_CONNECTION_URL |
agent-runtime | CoralOS server URL (set by CoralOS at container launch). |
PROCURE_RAIL |
payment-runtime | Set to x402 to enable upstream procurement. |
PROCURE_X402_URL |
payment-runtime | x402 endpoint URL. |
ALLOW_MAINNET |
agent-runtime | Set to 1 to allow mainnet RPC URLs. |
Use the payment runtime directly without CoralOS for simple pay-per-call:
import { solanaConnection, WalletSigner } from '@pay/agent-runtime'
import { createRailRouter } from '@pay/payment-runtime'
const connection = solanaConnection('https://api.devnet.solana.com')
const wallet = WalletSigner.fromBase58(process.env.KEYPAIR_B58)
const router = createRailRouter({ solanaPay: { connection, wallet } })
const rail = router.get('solana-pay')
const request = await rail.requestPayment(order)
const proof = await rail.verifyPayment(request)Wire CoralOS agents to any backend:
import { CoralClient, PolicyEngine } from '@pay/agent-runtime'
import { createRailRouter } from '@pay/payment-runtime'
const coral = new CoralClient({ url: process.env.CORAL_CONNECTION_URL, agentName: 'seller-api' })
const policy = new PolicyEngine({ maxSpendPerRoundSol: 0.1 })
const mention = await coral.waitForMention()
const want = parseMessage(mention.text)
// Call your API
const result = await myApiClient.query(want.details)
// Settle through escrow
const rail = router.get('escrow')
await rail.release(order)Any REST API can become a paid endpoint using x402:
// Server: return 402 with price, verify payment header, serve data
// Client: catch 402, sign payment, retry with X-PAYMENT header
import { fetchWithX402 } from '@pay/payment-runtime'
const data = await fetchWithX402('https://api.example.com/premium', {
wallet: signer,
connection,
})