tenzro-pay-middleware
v0.1.0
Published
Drop-in Express middleware that 402-gates merchant routes for agentic payments (x402 / MPP / AP2) and emits MandateRef-bound receipts tying each on-chain payment to the signed off-chain mandate that authorized it — closing the dispute/chargeback gap bare
Readme
tenzro-pay-middleware
Drop-in Express middleware that lets any merchant accept agentic payments (x402 / MPP / AP2) over HTTP 402 and emit a MandateRef-bound receipt for every paid request. The receipt ties the on-chain payment to the signed off-chain mandate that authorized it — closing the dispute/chargeback gap that bare HTTP 402 leaves open.
A relying party (or the merchant's own dispute desk) can re-check the binding: this payment cleared under this mandate, signed by this DID, for this resource.
- The middleware holds no keys and runs no trusted server of its own.
Payment verification is delegated to the Tenzro node via the published SDK
(
PaymentClientfor x402/MPP,Ap2Clientfor AP2 mandate-pair validation). - Receipt emission goes to a pluggable
ReceiptSink(default: in-memory + console) so the merchant decides where the audit trail lands. - The mandate hash is computed locally with a canonical (stable key-order) SHA-256, so the binding is reproducible without a node round-trip.
Install & build
npm install
npm run buildUse it
import express from "express";
import {
mandateReceiptMiddleware,
InMemoryReceiptSink,
} from "tenzro-pay-middleware";
const sink = new InMemoryReceiptSink();
const app = express();
app.get(
"/report",
mandateReceiptMiddleware({
endpoint: "https://rpc.tenzro.network",
amount: 0.25,
asset: "USDC",
protocol: "x402", // or "mpp"
resource: "premium-market-report",
sink,
}),
(_req, res) => {
// Reached only after payment is verified + a receipt is recorded.
res.json({ report: "…", paidUnder: res.locals.mandateReceipt?.mandate });
},
);Request flow
- No credential →
402 Payment Requiredwith a challenge describing protocol, resource, amount, asset, and recipient, plus aWWW-Authenticateheader. - Credential present (default header
X-Paymentfor x402,X-Mpp-Receiptfor MPP — a payable resource URL the node verifies and settles) → the middleware verifies it against the node and obtains aPaymentReceipt. - The middleware binds a
MandateRef(protocol + SHA-256 of the presented credential + payer DID) to the receipt, emits it to the sink, stashes it onres.locals.mandateReceipt, and callsnext().
Optionally send X-Payer-Did to bind the receipt to a TDIP identity.
AP2 mandate pairs
For merchants that accept AP2 Verifiable Digital Credentials in the request body rather than an x402/MPP credential URL:
import { recordAp2MandatePair } from "tenzro-pay-middleware";
const bound = await recordAp2MandatePair({
endpoint: "https://rpc.tenzro.network",
intentVdc, // principal-signed IntentMandate VDC
cartVdc, // agent-signed CartMandate VDC
resource: "premium-market-report",
enforceDelegation: true, // also run the TDIP DelegationScope gate
});This validates the pair against the node (cart references intent, amounts/items
match, both VDCs verify, and — when enforceDelegation — the agent's TDIP
DelegationScope admits the cart total) and records a MandateRef bound to the
cart.
Demo
npm run demo
# then, in another shell:
curl -i localhost:8099/report # 402 + x402 challenge
curl -s localhost:8099/receipts # recorded receipts
curl -i -H "X-Payment: <payable-url>" localhost:8099/reportLiveness (read-only, against live infra)
npm run livenessConfirms the node's payment gateway is reachable, lists the protocols + assets it accepts and the x402 scheme backends, shows a sample 402 challenge, and exercises the local mandate-hash helper.
Configuration
| Var | Default | Meaning |
|-----|---------|---------|
| TENZRO_RPC | https://rpc.tenzro.network | Tenzro JSON-RPC endpoint |
| PORT | 8099 | Demo merchant port |
Notes
- The on-chain artifact is the payment receipt; the MandateRef binding is the audit record the merchant keeps (and can forward to a relying party). The middleware is honest about that split — it never claims the binding is itself settled on-chain.
protocolselects the 402 challenge family. The credential header defaults follow each protocol's convention but are overridable viacredentialHeader.
