@receipt-graph/agent-collector
v0.1.0
Published
Agent-side collector (HTTP/x402 client wrapper, partial-trust receipts).
Downloads
152
Readme
@receipt-graph/agent-collector
Agent-side fetch wrapper that observes x402 payment flow from the client perspective and emits lifecycle events to the ReceiptGraph HTTP API with source: "agent". Works against any x402-capable provider without requiring provider middleware to be installed.
Partial-trust contract: the collector records what the HTTP client can observe through TLS termination — request URL, the x402 challenge, the payment-signature header hash, and the client-observed response body hash. These are never co-signed by the provider. After Kitescan on-chain verification, trustLevel resolves to agent_observed, never provider_verified, unless provider middleware also contributes delivery events.
Quick start — with settlement
Use receiptGraphAgentCollector when your payment client can provide txHash after a successful paid call:
import { receiptGraphAgentCollector } from "@receipt-graph/agent-collector";
import { wrapWithX402 } from "@x402/client"; // or your x402 payment wrapper
const collector = receiptGraphAgentCollector(fetch, {
agentId: process.env.AGENT_ID!,
sessionId: process.env.SESSION_ID!,
receiptGraphUrl: process.env.RECEIPTGRAPH_URL!,
apiKey: process.env.RECEIPTGRAPH_API_KEY!,
});
// Pass collector.fetch as the underlying transport for your x402 payment client.
// Both the initial 402 and the paid retry must go through the wrapper.
const paidFetch = wrapWithX402(collector.fetch, paymentOptions);
const response = await paidFetch("https://example.com/api/resource");
// Emit settlement when the payment client provides txHash:
const txHash = paymentClient.lastTxHash();
const receiptId = "..."; // returned from the 402 step via onError ctx, or computed independently
await collector.settle(txHash, receiptId);Quick start — fetch-only (no settlement)
Use receiptGraphAgentFetch when you do not need to emit settlement. Returns a plain typeof fetch — no settle handle:
import { receiptGraphAgentFetch } from "@receipt-graph/agent-collector";
const paidFetch = receiptGraphAgentFetch(fetch, {
agentId: process.env.AGENT_ID!,
sessionId: process.env.SESSION_ID!,
receiptGraphUrl: process.env.RECEIPTGRAPH_URL!,
});
const response = await paidFetch("https://example.com/api/resource");Configuration
All keys are typed on AgentCollectorConfig (exported from the package entry).
| Field | Required | Purpose |
|---|---|---|
| agentId | yes | Stable agent identifier; stored as SHA-256(agentId) — raw value never persisted. |
| sessionId | yes | Correlates multiple paid calls in one run; stored as SHA-256(sessionId). |
| receiptGraphUrl | yes | ReceiptGraph API base URL (no trailing slash; normalized at runtime). |
| apiKey | no | Authorization: Bearer <apiKey> on ReceiptGraph writes. Defaults to RECEIPTGRAPH_API_KEY env var. |
| emitMode | no | "best_effort" (default) or "strict". In best_effort, ReceiptGraph failures never block the paid response. |
| verifySettlement | no | Default true: call POST /api/verify-settlement after settle(). Set false to defer on-chain verification to a background worker. |
| onError | no | (ctx: AgentCollectorOnErrorContext) => void — called on any failed emit. Must not throw in best_effort mode. |
txHash availability matrix
| Situation | What to do |
|---|---|
| txHash known right after fetch returns | await collector.settle(txHash, receiptId) immediately |
| txHash arrives later (async callback, event, polling) | call await collector.settle(txHash, receiptId) when it arrives; internal state is retained |
| txHash is never available to the agent | do not call settle; the receipt stays at delivered for Phase 5 reconciliation |
When settle is never called, the receipt already contains the fields the Phase 5 reconciler needs to find the on-chain transfer:
payTo— normalized EVM address of the payment recipientasset— EVM address of the payment tokenmaxAmountRequired— token amount in atomic unitsnetwork— chain id (e.g.eip155:84532)requestedUrl— canonicalized URL of the paid endpointpaymentHeaderHash— SHA-256 of thepayment-signatureheaderagentIdHash/sessionIdHash— hashed identities for correlation
Observation hooks
Each hook documents which ReceiptGraph HTTP route it calls and which ReceiptEventType it produces. The canonical table is AGENT_OBSERVATION_HOOK_SPECS in src/observation-hooks.ts.
| Hook | ReceiptGraph HTTP | Resulting ReceiptEventType |
|---|---|---|
| request_start | (none — local capture for pending) | — |
| pending | POST /api/receipts/pending | pending_created |
| payment_seen | POST /api/receipts/:receiptId/payment-seen | payment_seen |
| delivery | POST /api/receipts/:receiptId/delivery | delivered or delivery_failed |
| settlement | POST /api/receipts/:receiptId/settlement then POST /api/verify-settlement | settled + (on verify) verified |
payment_seen is emitted before the paid retry goes out; delivery is emitted after the final response arrives. Both run through runAgentCollectorEmit so emitMode and onError stay consistent.
Hashing
All hash functions are re-exported from @receipt-graph/provider-middleware to guarantee bit-identical receiptId values across agent and provider paths. Golden-vector alignment is enforced by tests/hashing-alignment.test.ts.
| Function | Role |
|---|---|
| computeRequestHash(method, url) | SHA-256(method + normalizedUrl + sortedQueryParams) |
| computeReceiptId(input) | Deterministic receipt identity — same inputs always produce the same id |
| computePaymentHeaderHash(headerValue) | SHA-256 of the raw payment-signature header bytes |
| computeResponseBodyHash(body) | SHA-256 of stable-JSON-stringified body (or raw UTF-8 for strings) |
| sha256HexUtf8(s) | General-purpose UTF-8 SHA-256 used for agentIdHash / sessionIdHash |
Scripts
npm run typecheck—tsc --noEmitnpm test— Vitest unit tests (no network, no Postgres, no real API keys)npm run release:dry-run— run the full npm publish dry runnpm run release— publish to npm
