npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@receipt-graph/provider-middleware

v0.1.0

Published

Provider-side ReceiptGraph middleware (x402 route hooks, lifecycle events to ReceiptGraph API).

Readme

@receipt-graph/provider-middleware

Composable Hono middleware that observes x402 payment flow and emits lifecycle events to the ReceiptGraph HTTP API. Observation points and ReceiptGraph mapping are specified in code (OBSERVATION_HOOK_SPECS); this package includes the HTTP client and hashing helpers used by those hooks.

Install order (Hono)

Mount ReceiptGraph first, then your existing x402 middleware, then the business handler. The ReceiptGraph layer wraps the downstream stack so it can see the standard x402 PAYMENT-REQUIRED challenge header, the paid retry, and the final response.

import { Hono } from "hono";
import { receiptGraphProvider } from "@receipt-graph/provider-middleware";

const app = new Hono();

app.use(
  "/api/weather",
  receiptGraphProvider({
    serviceId: "weather-api",
    merchantName: "Weather Service",
    receiptGraphUrl: process.env.RECEIPTGRAPH_URL!,
    apiKey: process.env.RECEIPTGRAPH_API_KEY!,
  }),
  existingX402Middleware,
  weatherHandler,
);

Settlement (onSettled)

Phase 3 checklist 5: your x402 resource server calls onSettled(txHash) when settlement succeeds. This package binds that callback to the current HTTP request via Node AsyncLocalStorage so you configure onSettled once at process startup and still emit to the correct receiptId.

Use receiptGraphProviderArtifacts (same config as receiptGraphProvider) and call the returned onSettled from the standard @x402/hono resource-server hook:

import { Hono } from "hono";
import {
  receiptGraphProviderArtifacts,
} from "@receipt-graph/provider-middleware";
import { x402ResourceServer } from "@x402/hono";

const { middleware: receiptGraph, onSettled } = receiptGraphProviderArtifacts({
  serviceId: "weather-api",
  merchantName: "Weather Service",
  receiptGraphUrl: process.env.RECEIPTGRAPH_URL!,
  apiKey: process.env.RECEIPTGRAPH_API_KEY!,
});

const resourceServer = new x402ResourceServer(facilitatorClient)
  .register("eip155:84532", exactEvmScheme)
  .onAfterSettle(async ({ result }) => {
    if (result.success) await onSettled(result.transaction);
  });

app.use(
  "/api/weather",
  receiptGraph,
  x402HonoMiddleware,
  weatherHandler,
);

While handling a paid request, the middleware sets receiptId and verifyExpected automatically when paymentAccept is configured (static x402 v2 accept metadata for paid retries). You can still use assignReceiptGraphRequestContext for advanced cases. If onSettled runs outside receiptGraphProvider / AsyncLocalStorage, the default emitMode: "best_effort" path reports via onError and does not throw.

Emit order: payment_seen runs before await next() so the ReceiptGraph state machine stays ahead of x402 onSettled (which runs during next()). Configure x402 so onSettled fires after payment validation (typical). delivery runs after next() when the response is not 402.

Verification after txHash

When onSettled(txHash) successfully posts POST /api/receipts/:receiptId/settlement, the default verifySettlement: true path immediately posts POST /api/verify-settlement with:

{
  receiptId,
  txHash,
  expectedPayTo,
  expectedAsset,
  expectedAmount,
}

expectedPayTo and expectedAsset are lowercased, and expectedAmount is the trimmed x402 v2 amount string from the selected accept row. In ordinary paid retries, those fields come from paymentAccept; custom stacks can set them with assignReceiptGraphRequestContext({ verifyExpected }) before calling onSettled(txHash).

MVP behavior for not_indexed_yet: the ReceiptGraph API owns its Phase 2 retry policy. If the API still returns not_indexed_yet or any other non-verified outcome, the middleware reports it through onError under the settlement phase. With default emitMode: "best_effort", the paid response still returns to the user; a later reconciler can finish verification. Because the current API accepts delivery only after verified, a deferred verification can also make the later delivery emit return 409/currentStatus until reconciliation advances the receipt. Use emitMode: "strict" for demo or test flows that should fail closed when verification does not reach verified.

Configuration

All keys are typed on ReceiptGraphProviderConfig (exported from the package entry).

| Field | Required | Purpose | | --- | --- | --- | | serviceId | yes | Stable slug for service registration and analytics. | | merchantName | yes | Display default; middleware uses x402 resource serviceName when present, otherwise this fallback. | | receiptGraphUrl | yes | ReceiptGraph API base URL (no trailing slash; normalized at runtime). | | apiKey | yes | Secret for authenticating to ReceiptGraph (header shape defined with the HTTP client). | | network | no | Static network label when not inferred from accepts[].network. | | paymentAccept | no | Paid retries: static x402 v2 accept metadata (scheme, network, payTo, asset, amount, plus route resource) so receiptId, payment_seen, onSettled, and delivery work when this request has no fresh PAYMENT-REQUIRED header. Must match the row used for receiptId (including optional merchantName). | | onError | no | Callback for failed emits; default is best-effort (errors do not block payment). | | verifySettlement | no | Default true: call POST /api/verify-settlement after settlement in demo-style flows. Set false to defer verification. | | emitMode | no | "best_effort" (default) or "strict". Controls whether a failed ReceiptGraph emit aborts the request after onError. |

Observation hooks

The middleware observes the stack without mutating x402 payment authorization. Each step has a stable ObservationHookPhase (used in onError) and a mapping to ReceiptGraph HTTP routes and Prisma ReceiptEventType values. The canonical table is OBSERVATION_HOOK_SPECS in src/observation-hooks.ts.

| Hook | ReceiptGraph HTTP | Resulting ReceiptEventType (on success) | | --- | --- | --- | | request_start | (none — local capture for pending) | — | | pending | POST /api/receipts/pending | pending_created | | payment_seen | POST /api/receipts/:receiptId/payment-seen | payment_seen | | settlement | POST /api/receipts/:receiptId/settlement | settlement_submitted, settled (provider path from payment_seen; see ReceiptGraph postSettlement) | | delivery | POST /api/receipts/:receiptId/delivery | delivered or delivery_failed (from HTTP status on the business response) |

Emits should use runReceiptGraphEmit so emitMode and onError stay consistent. delivery requires the receipt to be verified on the API; when verifySettlement is true, the middleware should complete verification before emitting delivery.

Hashing and requestHash

Canonical implementations live in src/hashing.ts and src/receipt-id.ts and are re-exported from the package entry:

| Function | Role | | --- | --- | | computeRequestHash(method, fullUrl) | SHA-256(method + normalizedUrl + sortedQueryParams) — scheme/host lowercased, trailing slash stripped from path (except /), query names sorted. | | computeReceiptId(...) | SHA-256 over network \|\| resource \|\| … \|\| requestHash \|\| timestampBucket with field normalization (see buildReceiptIdPreimage). | | floorTimestampToMinuteBucket | 1-minute unix bucket for timestampBucket. | | computeRequestBodyHash / computeResponseBodyHash / computePaymentHeaderHash / computeOutputSchemaHash | Privacy and schema hashing (UTF-8 or stable JSON). |

The ./api workspace includes tests/request-hash-alignment.test.ts so ReceiptGraph and middleware stay bit-identical on golden vectors.

ReceiptGraphClient

ReceiptGraphClient calls:

  • POST /api/receipts/pending
  • POST /api/receipts/:receiptId/payment-seen
  • POST /api/receipts/:receiptId/settlement
  • POST /api/receipts/:receiptId/delivery
  • POST /api/verify-settlement

Authentication header: Authorization: Bearer <apiKey> (see api/.env.example for RECEIPTGRAPH_API_KEY).

  • pendingNetworkRetries: only postPending retries when fetch rejects (e.g. TypeError); HTTP 409 and other response errors are not retried.
  • onDebug: optional callback; on 409 responses receives receipt_graph_409 with currentStatus and parsed JSON body for operator logs.
  • ReceiptGraphHttpError: thrown on non-success status; exposes status, body, and currentStatus when present.

Scripts

  • npm run typechecktsc --noEmit
  • npm test — Vitest unit tests (no network, no Postgres)
  • npm run release:dry-run — run the full npm publish dry run
  • npm run release — publish to npm