← All documentation

build / @flop-lab/x402kit

Pay-per-call kit

Suite entry: flop x402. The README below lists the package options.

Generated from the source README at 2026-11-08T00:10:54.140Z. SHA-256: d884821a1e6d05157b4c3387528efa6740db1dd0513a9ef55a2ea1b15a65e03c.

Reading progress

FLOP pay-per-call kit

Does my paywalled API speak FLOP's 402 protocol correctly, from both sides? Check a recorded exchange or a local server against quoted SDK rules. Run a mock client and server to inspect the full handshake. The kit also provides request-bound payment handling with a client ceiling and replay store.

Quick start

From the repository root with Node 22 and pnpm. This inspects recorded payment handshake without sending a payment:

pnpm install --frozen-lockfile
pnpm --filter @flop-lab/x402kit build
node packages/x402kit/dist/cli.js check packages/x402kit/fixtures/handshake.json

Payment boundary

Payment execution is UNVERIFIED. The reference demo fails closed. The shipped SDK does not establish recipient binding for an arbitrary merchant in the HTLC helper. See NOTES.md for the exact code and protocol differences.

Run

Mock handshake and checker

After the quick start, run the server:

node packages/x402kit/dist/cli.js serve-mock 3590

In another terminal:

node packages/x402kit/dist/cli.js client http://127.0.0.1:3590/paid
node packages/x402kit/dist/cli.js check http://127.0.0.1:3590/paid

The client prints the captured exchange, counts and checker report. Its token is a deterministic mock value, not a payment. The server releases a labelled mock response. The check command sends one unpaid probe, so its paid-half coverage is incomplete and it exits 3. Use a captured full exchange to check both sides. Redirects are refused. Local requests have a five-second total deadline. Failed probes and proof retries are never repeated automatically. Capture bodies are bounded at 64 KiB. This local diagnostic does not poll shared public services.

flop x402 forwards these same commands after building the umbrella CLI. The checker accepts the transcript itself or the client output's exchange field. Headers are arrays of [name, value] pairs, preserving duplicates for diagnosis. See fixtures/handshake.json for the capture schema. Input is bounded at 128 KiB, 16 rounds and 64 headers per message. Empty and partial captures stay incomplete.

Exit codes: 0 means no deviations among the checked rules, 1 means invalid input or a client failure, 2 means deviations and 3 means incomplete coverage. Findings identify the round, client or server, code, quoted rule and source. A failed paid response stays incomplete unless a supported rule identifies a deviation.

The sdk-0.4.0 profile checks the legacy SDK flow with strict syntax. The reference profile also requires local request binding and devnet headers. Stricter local rules are labelled in NOTES.md and src/rules.ts. The pinned SDK class recording reproduces the repeated unpaid request in network #89. The refusal response is scripted. It is not evidence of a live gateway incident or double payment.

Read-only settlement demo

From the repository root with Node 22 and pnpm:

pnpm install
pnpm --filter @flop-lab/x402kit check
pnpm --filter @flop-lab/x402kit build
node packages/x402kit/dist/cli.js --help
node packages/x402kit/dist/cli.js inspect packages/x402kit/fixtures/headers.json
node packages/x402kit/dist/cli.js serve 3590

In another terminal:

curl http://127.0.0.1:3590/preview
curl -i -H 'X-Flop-Request-Id: example-request-0001' http://127.0.0.1:3590/settlements

/preview returns compact settlement rows with exact escrow arithmetic. It retains the source and observation time. It caches success and unavailable results for 60 seconds and coalesces concurrent requests. MCP calls use the shared client with one retry, backoff on 429 and server errors and request spacing. Empty lists remain empty. /settlements emits a quote but cannot release data for a payment proof. Prices in quotes are configured examples, not observed network prices.

The template folder runs inside this checkout after installation and building the package. pnpm dev starts the same Hono reference service.

Importable API

import { checkExchange, runMockClient } from "@flop-lab/x402kit";

const report = checkExchange(recordedExchange);
const local = await runMockClient("http://127.0.0.1:3590/paid");
import { paywall, payAndFetch, FileStore } from "@flop-lab/x402kit";

const wall = paywall({
  priceFlop: "0.01",
  payTo: "UNVERIFIED-NO-RECEIVING-ACCOUNT",
  ttl: 60_000,
  store: new FileStore("./payments.json"),
});
// No readPayment adapter: every proof fails closed.
const response = await wall(request, () => Response.json({ result: "protected" }));

// The caller must supply an independently audited throwaway-devnet payment adapter.
await payAndFetch(request, {
  maxFlop: "0.01",
  expectedPayTo: receivingAddress,
  authorize: async (quote) => policy.authorize(quote),
  pay: auditedDevnetPaymentAdapter,
});

honoPaywall(options) wraps the same handler. authorize(quote) must explicitly return true. It can adapt a spendguard policy without a package dependency. The quote is frozen. Expiry and cancellation are checked again after authorization. Redirects are refused. Only one payment callback and one proof retry can occur in one payAndFetch invocation. A retry failure requires reconciliation using the existing proof. Calling the helper again without that reconciliation can pay again. The ceiling covers the quoted FLOP amount. It does not cover chain fees.

The server's readPayment(proof) callback is a trust boundary. It returns:

{ network: "flop-devnet", asset: "FLOP", status: "SETTLED",
  finalized: true, canonical: true, hashLock, htlcId, txHash,
  amountFlop: "0.01", payTo, blockHash, source, observedAt }

Every field must come from independent canonical chain observations. Never construct this object from client headers. verifyObservedPayment validates the object and its agreement with the stored quote. It does not read the chain itself.

The quote's random hash-lock field is a reference challenge. This package has no HTLC preimage lifecycle or redemption path. Adding readPayment alone does not make a working merchant. The executable flag indicates that the callback contract was supplied, not that live payment support has been established.

Threat model

The middleware rejects malformed proofs, changed requests, exact-amount mismatches, wrong payees, expired quotes, partial/unfinalized observations and reused transaction hashes. Tests use synthetic payment evidence and recorded real MCP responses.

The request ID is an unguessable bearer capability. Use TLS and keep it out of logs. A leaked ID and proof can retrieve the cached result. This is not payer authentication. Transport headers excluded from request binding are listed in bindRequest.

MemoryStore lasts only for the process lifetime. FileStore uses an exclusive directory lock, flushed writes, atomic rename and directory fsync on local Linux storage. Multiple workers must use the same store file. A stale lock is never stolen. Stop all writers and reconcile pending payments before removing one. Network filesystems and hostile filesystem writers are outside this contract.

Claims are persisted before the handler. Same-process concurrent duplicates share the result. Competing processes can return 409 payment_pending_reconciliation. Retry the same proof after the first request completes. Handler errors are cached. A crash between the claim and response needs manual reconciliation. Exactly-once external side effects are not promised. There is no automatic refund.

Request bodies are limited to 64 KiB and responses to 1 MiB. Stores admit at most 10,000 quotes and do not evict replay tombstones. Add service-level rate limits and authenticate callers before exposing a paid adapter. This reference does not provide distributed storage, retention compaction or denial-of-service protection.

Checks and evidence

pnpm --filter @flop-lab/x402kit record refreshes the raw SDK evidence, MCP fixture and local handler flow. Fixtures keep source URLs and UTC timestamps. Unit tests are offline. FLOP_LIVE=1 enables the optional read-only smoke check.

Failure matrix assertions cover underpayment, overpayment, wrong payee, expiry, replay, changed path/method, partial settlement, reorg, missing finality, concurrent duplicates and malformed headers. These tests do not simulate a real chain payment.

pnpm exec tsx packages/x402kit/test/record-handshake.ts captures a real loopback exchange on port 3592 plus the pinned SDK's standalone and class flows against a scripted transport. Fixture dates are capture times, not current quote validity. The checker compares expiry with the recorded round time. The fuzz corpus exercises malformed challenge headers before the client callback and malformed proofs before server release. A real HTTP test covers transport-added headers.

What this does not verify

This kit does not verify real merchant settlement, runtime finality, TEE appraisal, inference correctness or standard x402 v2 interoperability. It has no live payment adapter and creates no wallet. Mock ledger fields and mock success responses are synthetic. The checker does not authenticate captures, recompute the server's request hash, prove exactly-once execution or test every possible SDK extension. Old replay stores need reconciliation of missing HTLC tombstones. The site shows recorded local flows and dated MCP observations. Real payment and confirmation remain UNVERIFIED.