@x402all/provider-search-contracts
v0.2.0
Published
Shared TypeScript types, runtime Zod validators, and JSON Schema artifact for the x402all <-> AXON provider-search bridge (docs/14-axon-bridge.md Phase A).
Readme
@x402all/provider-search-contracts
Shared TypeScript types, runtime Zod validators, and JSON Schema artifact for the x402all <-> AXON provider-search bridge.
The package is consumed by:
- x402all — produces
Candidaterows and validates inbound signed feedback envelopes from AXON. - AXON — pins the
Candidate,AggregateFeedbackEnvelope, and HMAC signing format when consuming x402all candidates and publishing batched feedback back to x402all.
This package is intentionally minimal: pure shapes, ID/HMAC helpers, and no transport, scoring, or business logic.
Install
pnpm add @x402all/provider-search-contractsModule resolution
The package ships dual ESM + CJS. Consumers resolve via the
exports map:
| Consumer kind | Resolves to |
| -------------------------------- | ---------------------- |
| import / "type": "module" | dist/index.js (ESM) |
| require() / "type": "commonjs" | dist/index.cjs (CJS) |
| TypeScript | dist/index.d.ts |
| @x402all/provider-search-contracts/schema.json | dist/provider-search-contracts.schema.json |
This is the only supported import surface. Deep imports
(@x402all/provider-search-contracts/dist/...) are not part of the
public API and may break in any minor release.
What's in the package
Schemas (Zod + JSON Schema)
CandidateSchema— the v0.2 candidate offer surfaced from x402all.CandidateRequestSchema— public, wallet-unaware filters AXON sends.CandidateResponseSchema/AbstentionResponseSchema— wire wrappers.ScoreComponentsSchema/PublicScoreComponentsSchema.RankingProfileSchema,FilterReasonSchema,QuoteStatusSchema.AggregateFeedbackEnvelopeSchema,FeedbackAggregateRowSchema— signed payload AXON POSTs back to x402all.
The bundled JSON Schema artifact lives at:
@x402all/provider-search-contracts/schema.jsonHelpers
OFFER_VERSION_ID_REGEX, buildOfferVersionId(...)
import {
OFFER_VERSION_ID_REGEX,
buildOfferVersionId,
} from "@x402all/provider-search-contracts";
const id = buildOfferVersionId({
slug: "agentquant-avantis-signals",
url: "https://04dcqgnj.nx.link/v1/avantis/signals",
payTo: "0x758d2532ff6a304d1a0c2fdf46264590e4ef0637",
network: "base",
asset: "0x833589FCD6eDb6E08f4c7C32D4f71b54bdA02913",
atomicAmount: "40000",
});
// → "x402all:bundle:agentquant-avantis-signals:0123456789abcdef"
OFFER_VERSION_ID_REGEX.test(id); // trueThe ID format is:
x402all:bundle:<slug>:<sha256(url|pay_to|network|asset|max_amount_required_atomic)[:16]>Inputs are canonicalized before hashing:
- URL trimmed.
networkandassetlowercased.- EVM-style
payTolowercased; non-EVM addresses left as-is. atomicAmountnormalized to a base-10 integer string.
The ID does not depend on title, summary, ranking signals, or build timestamp. It MUST stay stable for a given URL/payee/network/asset/amount, and MUST change when any of those changes.
HMAC over raw body
import {
signFeedbackBody,
verifyFeedbackSignature,
FEEDBACK_HMAC_HEADERS,
} from "@x402all/provider-search-contracts";
// AXON side
const sig = signFeedbackBody(secret, rawBody); // → "sha256=<lowercase hex>"
fetch(url, {
method: "POST",
body: rawBody,
headers: {
[FEEDBACK_HMAC_HEADERS.KEY_ID]: keyId,
[FEEDBACK_HMAC_HEADERS.SIGNATURE]: sig,
[FEEDBACK_HMAC_HEADERS.ISSUED_AT]: issuedAtIso,
"content-type": "application/json",
},
});
// x402all side (already implemented in web/lib/feedback)
verifyFeedbackSignature(secret, rawBody, presentedSignature);verifyFeedbackSignature uses crypto.timingSafeEqual against the
canonical sha256=<lowercase hex> representation.
Receiver semantics (x402all side)
The receiver enforces, in order:
Content-Lengthover 256 KB →413before reading the body.- Raw-body cap of 256 KB while reading →
413. x-axon-key-idis inAXON_FEEDBACK_KEY_ID_ALLOWLIST→401otherwise.- HMAC over the raw bytes verifies →
401otherwise. - JSON parses and the body validates against
AggregateFeedbackEnvelopeSchema→400otherwise (no Zod internals leak). issued_atis within ±5 minutes of server wall clock →400otherwise.x-axon-issued-at(when present) equals bodyissued_at→400otherwise.- Persistence is transactional with
INSERT ... ON CONFLICT DO NOTHING RETURNING:- duplicate
batch_idwith sameraw_body_sha256→200 { duplicate: true }, - duplicate
batch_idwith differentraw_body_sha256→409.
- duplicate
Releases
Versions follow semver. Each release of this package is immutable:
if 0.2.0 is published incorrectly, publish 0.2.1 — never re-publish
0.2.0.
Check the bundled JSON Schema $id to confirm which version a consumer is
pinned against:
https://x402all.dev/schemas/provider-search-contracts/0.2.0.jsonBuild / test
pnpm --dir packages/provider-search-contracts run build
pnpm --dir packages/provider-search-contracts testbuild runs tsup to produce dist/index.js (ESM), dist/index.cjs
(CJS), and dist/index.d.ts, then scripts/emit-json-schema.mjs writes
dist/provider-search-contracts.schema.json. dist/ is gitignored — it
is a build output, not source.
Pre-publish smoke
pnpm --dir packages/provider-search-contracts run test:packPacks the package and verifies a fresh ESM consumer, a fresh CJS
consumer, and a tsup-bundled CJS consumer (mirroring AXON's build) can
each signFeedbackBody(...) against the tarball. Required gate before
npm publish.
