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

@themobiusstrip/agentpay-proxy

v0.0.4

Published

Guarded x402 payment proxy for AI agents: x402 client + agentpay-guard + signer in one out-of-agent process. Standalone per-payment ceiling and cumulative caps apply below the model, fail-closed.

Readme

@themobiusstrip/agentpay-proxy

Guarded x402 payment proxy for AI agents. The x402 client, agentpay-guard, and the signing key live in ONE process, out of the agent's reach; the agent gets a single capability — paid_fetch(url) over HTTP or MCP. Every payment is decided by policy below the model, fail-closed: optional independent per-payment ceiling, rolling-window budget with atomic reserve-before-sign, optional payee/amount mandate binding, duplicate-authorization guard, deny-by-default envelope (exact + Base Sepolia + USDC).

agent (Claude, no keys)
   │ paid_fetch(url)                       <- HTTP or MCP
   ▼
agentpay-proxy                              <- x402 client + agentpay-guard + signer
   │ 402 → policy decides → sign EIP-3009 → retry with payment
   ▼
x402-gated site → facilitator → USDC settles on Base Sepolia

Why a proxy: the guard's deployment precondition is an out-of-process signer — a prompt-injected agent that can run code can bypass any in-process hook. Behind the proxy, injection can at most ask for a payment; policy answers.

Quick start

npx @themobiusstrip/agentpay-proxy          # starts on 127.0.0.1:4020

First run generates a testnet payer wallet (.agentpay-proxy-wallet.json, mode 600 — keep it out of git) and prints faucet instructions. Fund the payer with Base Sepolia USDC at https://faucet.circle.com (no ETH needed — the facilitator pays settlement gas). Then:

curl -s -X POST http://127.0.0.1:4020/paid-fetch \
  -H 'content-type: application/json' \
  -d '{"url":"https://x402.org/protected"}'

Funded → {"status":200,"body":"…","settlement":"<base64 receipt with tx hash>"}. Unfunded → the guard still runs the whole pipeline (reserved → allow → signed) and the merchant re-402s at verification; nothing moves.

Wire into Claude

claude mcp add paywall -- npx @themobiusstrip/agentpay-proxy mcp

The mcp command is a stdio forwarder to a running proxy (PROXY_URL, default http://127.0.0.1:4020) — it holds no keys. Claude then has one tool, paid_fetch; blocked payments return the machine-readable guard reason (cap_exceeded, intent_payto_mismatch, …) instead of content.

Configuration (env)

| var | default | meaning | | --- | --- | --- | | PAYER_PK | — | payer private key; else WALLET_FILE (default ./.agentpay-proxy-wallet.json, auto-generated, TESTNET ONLY) | | HOST / PORT | 127.0.0.1 / 4020 | bind address. Loopback by default — this process holds a key; authenticate the hop before exposing it | | CAP | 100000 | per-mandate cap per window, atomic USDC ($0.10) | | AGG_CAP | 200000 | aggregate cap across all mandates ($0.20) — salami-drain stop | | MAX_PAYMENT | off | hard maximum for each payment, atomic USDC; works without MANDATE or PIN_PAYTO; 0 blocks every positive payment | | WINDOW_MS | 300000 | rolling budget window | | CEILING_S | 300 | max authorization lifetime the proxy signs; effective ceiling min(CEILING_S, WINDOW_MS/1000) | | STORE | sqlite | sqlite for restart-safe state; memory only for disposable demos/tests | | STATE_DB | ./.agentpay-proxy-state.sqlite | SQLite state file, mode 0600 | | MAX_ACCOUNTING_WINDOW_MS | current WINDOW_MS | durable upper bound for future rolling windows; must be at least WINDOW_MS | | MANDATE=1 + PIN_PAYTO, PIN_MAX | off | mandate-required profile: bind every payment to this payee/max ($0.01 default max) | | ALLOWED_HOSTS | any | comma-separated allowlist of hosts paid_fetch may call. When set, the proxy also refuses HTTP redirects so an allowed host cannot redirect it to a disallowed one |

Malformed money knobs throw at startup — no silent fallback. Programmatic maxPaymentAmount must be a non-negative bigint; invalid runtime values throw before proxy creation.

MAX_PAYMENT is not cumulative and does not bind a payee. Keep CAP / AGG_CAP for split-drain bounds; use mandate mode when payee or quoted-intent binding is available. With MAX_PAYMENT and PIN_MAX, both checks apply.

SQLite opens, migrates, and recovers before listener starts. Recovered reserved / signed / submitted rows become unknown and keep full cap through authorization recovery deadline. A longer WINDOW_MS extends that deadline before expiry; shortening never forgets old spend early. Set MAX_ACCOUNTING_WINDOW_MS before first run if later window expansion is planned. The persisted value becomes immutable; later window contraction reuses it, while requests above it fail closed. Expired dedup rows, aged terminal rows, and settled rows beyond every permitted window are pruned. Corrupt, unreadable, locked, or newer schema state stops startup; proxy never falls back to memory. SQLite supports one active proxy process per database.

GET /healthz returns 503 when store/lifecycle readiness latches unhealthy. After possible transmission, failed lifecycle mutation blocks new paid requests until restart/recovery. If merchant response already arrived, proxy returns that paid response before reconciliation; reconciliation failure then blocks later paid requests instead of replacing paid content with 503. On SIGINT / SIGTERM, listener closes before database checkpoint/close.

Idempotency / retries

Each paid-fetch request runs as one purchase intent. Pass the same intentId ({"url": …, "intentId": "order-42"}) to retry the SAME purchase — the duplicate-authorization guard refuses to sign it twice. Omit intentId for a fresh intent (a UUID is generated and echoed in the response). The budget cap is the backstop either way.

Programmatic use

import { createPaymentProxy, configFromEnv } from "@themobiusstrip/agentpay-proxy";

const { app, guard, account } = createPaymentProxy(process.env.PAYER_PK as `0x${string}`, {
  ...configFromEnv(),
  mandate: { payTo: "0xmerchant…", maxAmount: 10_000n },
});
app.listen(4020, "127.0.0.1");

ProxyHooks (third argument) lets embedded use supply an onAudit sink, a real mandateVerifier (verify a signed mandate's provenance — constraints must come from outside the model), and an explicit AtomicStore. Embedded createPaymentProxy() keeps memory default and performs no hidden filesystem writes. Inject a persistent adapter deliberately.

Scope honesty

MVP envelope is exact + Base Sepolia (eip155:84532) + USDC — everything else fails closed. The guard hardens tool-mediated content-level injection with an out-of-process signer (this proxy); it is not a sandbox for arbitrary code execution, and an agent with shell access to the proxy's host can read the wallet file — isolate accordingly.

paid_fetch is a server-side fetch: without ALLOWED_HOSTS the proxy will fetch any http(s) URL it is handed (an SSRF surface — it can reach hosts on its own network). That is why it binds to loopback by default; set ALLOWED_HOSTS and authenticate the agent→proxy hop before exposing it. The guard still bounds what can be paid regardless — envelope + caps + mandate — but a plain GET of an internal URL returns its body without payment. Full threat model: agentpay-guard.

Related