The Bolyra protocol over HTTP
A 402 response carries a Bolyra-Challenge. The client returns a Bolyra-Credential — a zero-knowledge proof that a human authorized this agent for this spend, bound to the server's nonce. The server verifies, grades the credential A–F, and lets the call run.
Drop-in alongside Coinbase x402, Visa TAP, and Google AP2. No identity leak, no spend-limit leak, no delegation-chain leak — just did the human consent for this.
// Phase 1 — server returns 402 with a fresh challenge if (!req.headers['bolyra-credential']) { const nonce = freshNonce(); res.writeHead(402, { 'PAYMENT-REQUIRED': serializePaymentRequired(REQS), 'Bolyra-Challenge': nonce.toString(16), }); return res.end(JSON.stringify({ accepts: [serializePaymentRequired(REQS)], challenge: nonce.toString(16), })); } // Phase 2 — verify the credential bound to that nonce const { verified, score, grade } = await verifyX402Authorization( req.headers['bolyra-credential'], REQS, resolveCredential, ); if (!verified || score < 70) { return sendJson(res, 403, { score, grade }); } sendJson(res, 200, { result: runTool(req.body) });
The flow
Three messages. One round-trip. Bolyra rides alongside the standard x402 PAYMENT-REQUIRED header — it doesn't replace the payment, it gates the authorization to make it.
Server → Client
No credential, no entry. The server issues a fresh single-use nonce with a 60s TTL and emits it on the Bolyra-Challenge response header alongside the standard PAYMENT-REQUIRED envelope.
Client (agent)
The agent generates parallel Groth16 proofs of mutual handshake + spend-policy fit, binds them to the server's nonce, and ships everything in one base64url header. ~200ms with rapidsnark.
Server → Client
Verifier checks proofs, nonce binding, replay status, and policy fit. Returns a score 0–100 (A/B/C/D/F). Score ≥ 70 → 200 OK, the tool runs. Otherwise 403 with the breakdown.
Adapters
The same challenge–credential–verify primitive plugs into every agentic-commerce and agent-tooling rail. Bolyra never replaces them — it adds the ZK authorization layer they're each missing.
Bolyra-Credential rides alongside the standard PAYMENT-SIGNATURE header. Gated USDC settlement on Base, ~200ms USDC flow unchanged.
Replaces the centralized agent registry lookup with a ZKP proof of human authorization. Produces an opaque Payment Signal token for TAP's Payment Signals API.
integrations/payment-protocols → PaymentsBolyra handshake encodes the Intent + Cart Mandate as a single ZK proof. Delegation chains supported with hop tracking — no plain-text mandate ever leaves the agent.
integrations/payment-protocols → ToolingDrop-in middleware for MCP servers. One wrapper gates every tool call behind the mutual handshake — withBolyraAuthStdio(server, …).
ZKP-native trust verification for onAgentVerify. Five-dimension scoring (validity, expiry, perms, freshness, scope) — drop-in for any OpenClaw deployment.
BolyraAuthTool for LangChain; delegation adapter for CrewAI. Authenticate agents before sensitive operations in any framework, in any language with an SDK.
Quickstart
The TypeScript adapter ships in integrations/payment-protocols. createX402Authorization on the client, verifyX402Authorization on the server — both bound to the Bolyra-Challenge nonce.
Samples below are TypeScript. Save as .ts and run with npx tsx server.ts (no build step) or compile via tsc. Node 22.6+ also supports node --experimental-strip-types server.ts directly.
import { createServer, type ServerResponse } from 'node:http';
import { randomBytes } from 'node:crypto';
import {
verifyX402Authorization,
serializePaymentRequired,
X402_BOLYRA_CREDENTIAL_HEADER,
X402_BOLYRA_CHALLENGE_HEADER,
X402_PAYMENT_REQUIRED_HEADER,
type X402PaymentRequirements,
} from '@bolyra/payment-protocols';
const REQS: X402PaymentRequirements = {
chain: 'eip155:84532', // Base Sepolia
asset: 'USDC',
amount: 10_000, // $100.00 in cents
recipient: '0x000000000000000000000000000000000000beef',
};
const nonces = new Map<string, { issuedAt: number; consumed: boolean }>();
// You provide: resolve a did:bolyra:* identifier to an AgentCredential.
// Typically a read from your registry or cache.
async function resolveCredential(did: string) { return null; }
function sendJson(res: ServerResponse, status: number, body: unknown) {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(body));
}
createServer(async (req, res) => {
const cred = req.headers[X402_BOLYRA_CREDENTIAL_HEADER.toLowerCase()];
// Phase 1 — issue challenge, return 402.
if (!cred) {
const nonce = BigInt('0x' + randomBytes(8).toString('hex'));
const hex = nonce.toString(16);
nonces.set(hex, { issuedAt: Date.now(), consumed: false });
res.writeHead(402, {
[X402_PAYMENT_REQUIRED_HEADER]: serializePaymentRequired(REQS),
[X402_BOLYRA_CHALLENGE_HEADER]: hex,
'Content-Type': 'application/json',
});
return res.end(JSON.stringify({ accepts: [serializePaymentRequired(REQS)], challenge: hex }));
}
// Phase 2 — verify proof + nonce binding + replay.
const result = await verifyX402Authorization(cred, REQS, resolveCredential);
const rec = nonces.get(result.sessionNonce.toString(16));
if (!rec || rec.consumed) return sendJson(res, 403, { error: 'replay or unknown nonce' });
rec.consumed = true;
if (!result.verified || result.score < 70) {
return sendJson(res, 403, { score: result.score, grade: result.grade, warnings: result.warnings });
}
sendJson(res, 200, { result: { tool: 'demo.echo' }, bolyra: { did: result.did, grade: result.grade } });
}).listen(4402);
import { request as httpRequest } from 'node:http';
import {
createX402Authorization,
parsePaymentRequired,
X402_BOLYRA_CREDENTIAL_HEADER,
X402_BOLYRA_CHALLENGE_HEADER,
} from '@bolyra/payment-protocols';
import { createHumanIdentity, createAgentCredential } from '@bolyra/sdk';
// Minimal POST helper — replace with fetch/axios in production.
function post(headers: Record<string, string> = {}) {
return new Promise<{ status: number; headers: any; body: string }>((resolve, reject) => {
const req = httpRequest({ host: 'localhost', port: 4402, method: 'POST', headers }, (res) => {
let body = '';
res.on('data', (c) => (body += c));
res.on('end', () => resolve({ status: res.statusCode!, headers: res.headers, body }));
});
req.on('error', reject);
req.end();
});
}
// Build human + agent fixtures. See @bolyra/sdk QUICKSTART.md for full setup.
async function buildFixtures() {
const human = await createHumanIdentity('demo-secret');
const agent = await createAgentCredential({ /* modelHash, operatorPrivKey, perms, expiry */ } as any);
const spendPolicy = { maxTransactionAmount: 10_000, currency: 'USD' as const };
return { human, agent, spendPolicy };
}
// === Phase 1: unauthorized request ===
const first = await post();
// → status: 402, header Bolyra-Challenge: <hex>
const requirements = parsePaymentRequired(JSON.parse(first.body).accepts[0]);
const bolyraChallenge = BigInt('0x' + first.headers[X402_BOLYRA_CHALLENGE_HEADER.toLowerCase()]);
// === Phase 2: build Bolyra-Credential bound to the challenge ===
const { human, agent, spendPolicy } = await buildFixtures();
const auth = await createX402Authorization(
human, agent, spendPolicy,
{ requirements, bolyraChallenge },
);
// auth.verified: true
// auth.score: 100 (grade A)
// auth.did: did:bolyra:base-sepolia:<commitment>
// === Phase 3: retry with credential ===
const second = await post({
[X402_BOLYRA_CREDENTIAL_HEADER]: auth.headers[X402_BOLYRA_CREDENTIAL_HEADER],
});
// → status: 200, body.result = { tool: 'demo.echo', … }
console.log('x402 round trip OK: 402 -> 200');
import {
verifyX402Authorization,
type X402PaymentRequirements,
} from '@bolyra/payment-protocols';
// You provide: resolve did:bolyra:* to the registered agent credential.
// Typically a DB lookup or cache hit.
async function lookupAgent(did: string) { return null; }
// Off-chain verifier — no gas, no on-chain registry lookup.
// Use this for batch verification or pre-flight checks before
// the standard x402 PAYMENT-SIGNATURE settlement runs.
export async function verifyOffChain(
credentialHeader: string,
requirements: X402PaymentRequirements,
) {
const decision = await verifyX402Authorization(
credentialHeader,
requirements,
async (did) => lookupAgent(did),
);
if (!decision.verified || decision.score < 70) {
throw new Error('Payment authorization rejected');
}
// → continue to standard x402 settlement.
// The merchant never learned identity, exact spend cap,
// or delegation chain — only that consent was valid.
console.log({
did: decision.did,
score: decision.score, // 0..100
grade: decision.grade, // 'A' | 'B' | 'C' | 'D' | 'F'
scopeCommitment: decision.scopeCommitment,
sessionNonce: decision.sessionNonce,
});
return decision;
}
SDKs
TypeScript ships today with full proving (snarkjs + rapidsnark). The Python SDK is a thin shell — pure-Python types and validation, with proving delegated to the TS prover via subprocess. Rust, Go, and Ruby are not yet scheduled.
Benchmarks
Groth16 across all three, rapidsnark-accelerated for sub-200ms client-side proving. Verified through a single on-chain transaction on Base L2 — or fully off-chain for batched commerce.
Circuit 1
Circuit 2
Circuit 3
Standards
IETF-style draft, W3C DID method, and a conformance test suite. spec/conformance-runner.js runs 48 vectors covering handshake, delegation, signatures, Merkle inclusion, cumulative encoding, nonce reuse, and boundary values.
draft-bolyra-mutual-zkp-auth-01
Message flows, proof formats, and verification procedures for mutual ZKP auth over HTTP 402. v0.3.0 release locks the canonical 6-element Delegation public-signals layout for IETF submission.
did:bolyra
Decentralized Identifier method for Bolyra identities. Human and agent DID documents with ZKP-aware verification methods.
48 vectors, all passing
Handshake, delegation, signature verification, Merkle inclusion, cumulative encoding, nonce reuse, boundary values.