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

@axiru/x402-receipt-verifier

v0.1.1

Published

Verify x402 SignedOffers and SignedReceipts (JWS and EIP-712) independently of the party that issued them. Zero runtime dependencies beyond the Axiru spec types.

Readme

@axiru/x402-receipt-verifier

Independent verification of x402 Signed Offers and Signed Receipts, the evidence records defined by the upstream x402 offer and receipt extension.

Apache-2.0. Zero runtime dependencies beyond @axiru/spec.

Why this exists

A payment receipt that only the payer can verify is not evidence. It is a claim.

x402 settlement produces two signed artifacts: an offer from the resource server describing what is being sold and for how much, and a receipt confirming that settlement happened. If your auditor, your counterparty, or a regulator has to take your word for the contents of those artifacts, the audit trail is worth very little. This package lets anyone with the envelopes and a network connection check both signatures and confirm that the receipt actually settles the offer it claims to settle. No Axiru account, no API key, no hosted service.

That is the point. A format becomes a standard when third parties can verify it without asking the issuer for permission.

Install

npm install @axiru/x402-receipt-verifier

Node 18 or later. Works in Edge and Workers runtimes when you inject fetchDidDocument.

What it verifies

Two wire formats:

| Format | Identity | Signature check | | ----------- | ----------------------------------------- | ------------------------------------------------------------ | | JWS | did:web:<host> | Resolved from https://<host>/.well-known/did.json. ES256 (P-256), EdDSA (Ed25519), and ES256K (secp256k1). | | EIP-712 | did:pkh:eip155:<chainId>:<address> | Recovered from the secp256k1 signature. Injected verifier, see below. |

The JWS path is fully self-contained and uses only the Node crypto and fetch globals. The EIP-712 path requires you to inject an Eip712Verifier, because this package deliberately carries no keccak or secp256k1 dependency; a zero-dependency verifier is easier to audit and easier to trust.

Quickstart

import {
  verifySignedOffer,
  verifySignedReceipt,
  verifyEvidenceChain
} from "@axiru/x402-receipt-verifier";

// Verify one artifact.
const offer = await verifySignedOffer(signedOffer);
if (!offer.ok) {
  console.error("offer rejected:", offer.reason, offer.detail);
} else {
  console.log("signed by", offer.value.signerDid, offer.value.payload);
}

// Or verify the whole chain in one call: both signatures, plus the
// receipt-matches-offer check, plus the payer allowlist.
const chain = await verifyEvidenceChain(
  signedOffer,
  signedReceipt,
  ["did:pkh:eip155:8453:0xAgentWalletAddress"], // authorized payers
);

if (!chain.ok) {
  // `stage` tells you exactly where it failed: "offer" | "receipt" | "match".
  console.error(`rejected at ${chain.stage}: ${chain.reason}`);
}

Nothing throws on adversarial input. Every public function returns a Result-shaped union, because this code sits on an ingestion path where an unhandled exception turns a hostile payload into a 5xx.

Failure reasons

structural_invalid, unsupported_algorithm, unsupported_format, did_resolution_failed, did_key_not_found, signature_invalid, kid_did_mismatch, eip712_not_implemented, internal.

These are stable identifiers. Log them, alert on them, and assert on them in tests.

Binding evidence to an authorization

Verifying that an offer and receipt are well formed and correctly signed is necessary but not sufficient. A valid receipt for the wrong amount is still the wrong payment. Pass an expected settlement to bind the evidence to what you actually authorized:

const chain = await verifyEvidenceChain(signedOffer, signedReceipt, authorizedPayers, deps, {
  amount_minor_units: "12000000",
  asset: "USDC",
  pay_to: "0xMerchantAddress"
});

Without that argument the chain check confirms internal consistency only. With it, the chain check confirms that the settled value is the value your policy allowed.

Injecting dependencies

const deps = {
  // Cache did:web documents however your host caches things.
  fetchDidDocument: async (did: string) => myCache.getOrFetch(did),
  // Bring your own keccak/secp256k1 for EIP-712 envelopes.
  verifyEip712: async (typedData, signature) => recoverAddress(typedData, signature),
  // Deterministic clock for tests and replay.
  now: () => 1_760_000_000
};

There is no default singleton. did:web caching strategy is host specific and a bad default here would quietly shadow yours.

Related packages

License

Apache-2.0. Copyright 2026 Axiru. See LICENSE.