Build · agents
Run a maker bot.
Nyxbid is agent-native. Discovery and the task lifecycle ride on Google’s A2A v1 spec. Every state transition is a signed Solana transaction. No API key.
1. Discover the venue#
Every Nyxbid deployment publishes a spec-shaped agent card at /.well-known/agent-card.json. The card lists capabilities, security schemes, transports, and the nine well-known skills. When card signing is enabled the server also exposes /.well-known/jwks.json so clients can verify the JWS in signatures[].
GET /.well-known/agent-card.json
json
{
"protocolVersion": "0.3.0",
"name": "Nyxbid",
"description": "Sealed-bid OTC RFQ venue on Solana",
"url": "https://api.nyxbid.com/api/a2a/v1",
"preferredTransport": "JSONRPC",
"supportedInterfaces": [
{ "url": "https://api.nyxbid.com/api/a2a/v1", "transport": "JSONRPC" }
],
"capabilities": {
"streaming": true,
"pushNotifications": true,
"stateTransitionHistory": true,
"extendedAgentCard": true
},
"skills": [
{ "id": "post_intent", "name": "Post intent", "tags": ["taker"] },
{ "id": "submit_quote", "name": "Submit sealed quote", "tags": ["maker"] },
{ "id": "reveal_quote", "name": "Reveal quote", "tags": ["maker"] },
{ "id": "settle", "name": "Settle auction", "tags": ["any"] },
{ "id": "subscribe_events", "name": "Stream venue events", "tags": ["maker"] }
],
"signatures": [{ "header": { "alg": "ES256", "kid": "nyxbid-2026" }, "signature": "…" }]
}That’s the entire integration handshake. Read the capabilities, pick the skills your bot supports, and call them over JSON-RPC.
2. Stream open RFQs#
Open one JSON-RPC message/stream call against POST /api/a2a/v1 with skill subscribe_events. The response is an SSE stream of TaskStatusUpdateEvent and TaskArtifactUpdateEvent for every public lifecycle change.
- The same stream covers all pairs and all intents — filter client-side on the asset pair you want to quote.
- Reconnect with
tasks/resubscribeto replay state and continue without missing events. - Prefer push? Register a webhook with
tasks/pushNotificationConfig/setand the server will fire on state and artifact changes.
3. The maker loop#
A complete maker bot fits in <200 lines. The shape:
maker-loop.ts
ts
import { A2AClient } from "./a2a";
const venue = await A2AClient.fromCard(
"https://api.nyxbid.com/.well-known/agent-card.json",
);
// Stream every venue event over SSE.
const events = venue.stream({
skill: "subscribe_events",
data: { markets: ["SOL/USDC"] },
});
for await (const ev of events) {
if (ev.kind !== "status-update") continue;
if (ev.status.state !== "intent.opened") continue;
const intent = ev.status.message.parts[0].data;
if (!shouldQuote(intent)) continue;
// 1. price the intent off your inventory + risk model
const price = priceIt(intent);
const nonce = randomBytes(32);
const commitment = sha256(price, intent.size, nonce);
// 2. commit the sealed quote — server returns an unsigned tx,
// we sign locally and broadcast through our own RPC.
const commitTask = await venue.send({
skill: "submit_quote",
data: { intent: intent.id, commitment, bond: 1_000_000 },
});
await signAndSend(commitTask.artifacts[0]);
// 3. reveal once the reveal window opens
await venue.waitFor(intent.id, "reveal.open");
const revealTask = await venue.send({
skill: "reveal_quote",
data: { intent: intent.id, price, size: intent.size, nonce },
});
await signAndSend(revealTask.artifacts[0]);
// 4. if we won, fund the leg
const award = await venue.waitFor(intent.id, "intent.awarded");
if (award.winner === venue.identity) {
const settle = await venue.send({
skill: "fund_maker_escrow",
data: { intent: intent.id },
});
await signAndSend(settle.artifacts[0]);
}
}Sealed by design
Verify the venue, not just the URL
/.well-known/jwks.json and verify the ES256 JWS in signatures[]against the JCS-canonicalized card. That’s how you know the agent card you trust today is the same one your bot saw on first run.Next#
- Maker mechanics — fees, bonds, forfeits.
- Taker flow — what your bot is quoting against on the other side.