Open-source B2B and marketplace ecommerce; new stores include an MCP server for AI agents.
SendCheck — EVM address verification (x402)
概览
作者提供的说明(英文原文)
Pre-send EVM address checks: free pre-check, $0.01 check, $0.05 deep scan. x402: payment is auth.
PopNx MCP 的三大支柱
这三项永远不会合并成一个分数:意见、测量和人气是不同的东西。
- 用户评价i暂无评价评价功能将在后续阶段与账户功能一起开放
- 我们的基础检查i1 项中通过 1 项依据公开信息检查;尚未扫描代码
- 使用情况i尚未统计★ –
它能帮你做什么
我们还没有为它写通俗的介绍。下面是我们从注册中心读取到的事实。
- 连接方式: 在云端运行
- 传输方式: Streamable HTTP
- 分类: 网店与支付
安装
选择你使用的 AI 应用,然后复制设置。
安装之前
- 基础检查:1 项中通过 1 项
- 在作者的服务器上运行:你向它提出的内容会经过他们的服务器。
- 注册中心没有列出需要填写的密钥。
你需要
- 在云端运行无需安装任何东西,通过互联网连接
你用的是哪个应用?
在上方选择你的应用。我们会记住它,下次按钮会显示“安装到 …”。
适用于网页版、桌面版和手机版 Claude:
- 打开 Settings → Connectors
- 点击 Add custom connector
- 粘贴下面的地址,然后点击 Add
- 如果要求登录,请登录作者的服务
https://api.pennyforge.org/mcp在终端中运行这条命令
claude mcp add --transport http sendcheck https://api.pennyforge.org/mcp或者自己复制设置
粘贴到项目中的 .cursor/mcp.json,或 ~/.cursor/mcp.json
{
"mcpServers": {
"sendcheck": {
"url": "https://api.pennyforge.org/mcp"
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
或者自己复制设置
粘贴到项目中的 .vscode/mcp.json
{
"servers": {
"sendcheck": {
"type": "http",
"url": "https://api.pennyforge.org/mcp"
}
}
}浏览器会先询问是否打开应用,然后应用会显示设置并请你确认。密钥的值会留空,由你自己填写。
ChatGPT 只能连接互联网上的服务器:
- 打开网页版 ChatGPT → Settings → Connectors (Apps)
- 点击 Create 或 Add(部分套餐需要先在 Advanced 中开启 Developer mode)
- 粘贴下面的地址,起个名字,然后保存
- 如果要求登录,请登录作者的服务
https://api.pennyforge.org/mcp菜单名称可能因套餐和版本不同而略有差异。
你的选择通过 Cookie 保存在这台设备上,无需账户。
这些设置是根据作者在注册中心的条目生成的。如果 README 的写法不同,请以 README 为准。
它能做什么
我们还没有读取这个服务器的工具列表;我们会轮流检查各个端点。
基础检查i
我们检查的是任何人都能看到的信息;我们没有阅读代码。不适用的检查项不计入总数。 · 检查于 2026年11月6日
- ✓ 通过
作者与仓库一致
注册名称已通过域名 api.pennyforge.org 验证(注册中心会核实域名所有权)
- … 尚未检查
仍在维护
我们还没有从 GitHub 读取这个仓库
- … 尚未检查
有许可证
我们还没有从 GitHub 读取这个仓库
- … 尚未检查
端点有响应
尚未检查;我们会轮流检查各个端点
- … 尚未检查
使用 OAuth 登录
尚未检查;我们会轮流检查各个端点
- … 尚未检查
工具描述中没有隐藏指令
尚未检查;我们会在检查端点时读取工具列表
与“网店与支付”中的其他服务器比较i
- 星标数多于此分类中其他 1742 个服务器的 0%
- 发布到注册中心的时间比此分类中其他 1742 个服务器的 38% 更新
- 基础检查结果等于或优于此分类中其他 1742 个服务器的 100%
与注册中心里这个分类中所有活跃的服务器比较。只有当发布者拥有该仓库时,星标数才计入。
常见问题
SendCheck — EVM address verification (x402) 安全吗?
它在 1 项适用的基础检查中通过了 1 项。我们没有阅读代码。在添加密钥或令牌之前,请先看看这个服务器要求哪些权限,并使用只具备所需权限的密钥。
它需要哪些密钥或代码?
注册中心没有列出必需的密钥,不过有些服务器会在首次使用时要求你登录。
它在我的电脑上运行还是在云端?
它在作者的云端运行。通过互联网连接,无需安装,但你发送的内容会经过作者的服务器。
它还在维护吗?
GitHub 仓库最后更新于–,最新版本(1.0.0)于2个月前进入注册中心。
哪些应用可以使用它?
支持 MCP 的应用,例如 Claude Desktop、Claude Code、Cursor 和 VS Code。它通过 URL 连接,所以接受远程 MCP 服务器的应用也可以使用,例如开启开发者模式的 ChatGPT。
有什么可以替代它?
同一分类中有:Spree Commerce: open-source B2B and marketplace ecommerce, Stripe MCP, MCP Cn Commerce
替代选择
同一分类中其他作者的服务器,按星标数排序。
如何看卡片:★ GitHub 星标i三大支柱:评价 · 检查 · 使用情况i更新 = 最新版本发布到注册中心的时间i
Stripe MCP
MCP server integrating with Stripe - tools for customers, products, payments, and more.
MCP Cn Commerce
8 read-only MCP platform entries; support is operation-specific; merchant live acceptance pending
作者的 README
打开 README
按原文从作者的仓库复制。PopNx MCP 没有审查或背书其内容。HTML、图片和脚本已被移除。链接会离开本站。
sendcheck-verify
Verify before you pay. A zero-dependency client for the free GET /validate pre-check on the SendCheck x402 API — the 10-line guard that stops your agent from signing a payment to a mistyped, mismatched, or tampered settlement address.
When an x402 client pays a service, it pays the settlement address the service told it about. A single wrong character (a case-flip, a dropped digit, a substituted address) and the USDC goes somewhere else forever. /validate is the free, no-key, no-account pre-check: is this address even a well-formed, correctly-cased EVM address before you put real money on it?
Since v0.2.0 the package does one more thing: it verifies the service's signed service card — an ES256 JWS over the payTo and the per-route maxPrice that the worker re-signs at every deploy — so a client that pinned the public key once can confirm the challenge's payTo cryptographically, before paying. That is the reference implementation of the drift-proof check (the "signed service card" from the address-drift article).
Why this exists
SendCheck's paid endpoints cost $0.01 (one chain) or $0.05 (all five chains) via x402 — USDC on Base, no account, no API key. But most "is this address garbage?" checks don't need chain data: the EIP-55 checksum alone catches the most common, most expensive failures (typos, dropped characters, tampered casing). So /validate is free, and this package makes calling it one line.
The signed service card (v0.2.0)
Pre-payment address checks have a hole: the "expected payTo" a client compares against usually comes from the same discovery doc it just fetched — so a compromised service can point you at its own new address, and for a service that rotates its address often, every remembered address is stale before the check finishes.
SendCheck closes that hole by signing its payment details. The worker signs the settlement address and the per-route max prices with a stable ES256 (EC P-256) key at every deploy, and serves the signed block { jws, key } in three places:
<origin>/.well-known/x402 → top-level "attestation"
/openapi.json → info["x-attestation"] the 402 challenge itself → extensions["x-sendcheck-attestation"]
**SendCheck signs its payment details with a stable key at every deploy, so
any client that has pinned that key once can cryptographically confirm —
before paying — that the address in the payment challenge is one we signed;
first contact is still trust-on-first-use.**
What the signature buys: key continuity for returning, stateful clients — a compromised worker/DNS/CDN cannot silently redirect your payments, and a rotated key is a *loud* event you can flag. What it does not buy (we under-claim on purpose): first-visit authenticity (that is TOFU), protection for ephemeral agents with no pin, or a price *below* the signed maxPrice.
The signed payload also carries a per-route maxPrice, so verifyChallenge catches a challenge that quietly raises the price above what the key signed.
The drift-proof wiring
import { checkService, verifyChallenge, ATTESTATION_EXTENSION } from "@pennyforgeorg/sendcheck-verify";
const origin = "https://api.pennyforge.org";
// 1) Once: pin the public key (first contact = trust-on-first-use). const first = await checkService(origin); // { ok, keyId, key: JWK, payTo, … } // persist { kid: first.keyId, x: first.key.x, y: first.key.y } somewhere durable
// 2) Every call: confirm the card still verifies and the key has not rotated. const svc = await checkService(origin, { pinnedKey: storedPin }); if (svc.keyChanged) { /* loud: the key rotated — inspect before trusting */ } if (!svc.ok) throw new Error("service card not valid: " + svc.attestation.reason);
// 3) At payment time: bind the 402 CHALLENGE to the signed card. // (challenge = decoded base64 PAYMENT-REQUIRED header, or the 402 JSON body) const verdict = await verifyChallenge(challenge, svc.block, { origin }); if (!verdict.valid) throw new Error("challenge not signed: " + verdict.reason); // …now sign the EIP-3009 payment to challenge.accepts[0].payTo.
verifyChallenge checks the challenge's payTo against the signed one and the challenge price against the signed maxPrice for the challenged route. Failure reasons: signature_invalid, challenge_payto_mismatch, price_exceeded, route_not_signed, expired, iat_future, service_mismatch, kid_mismatch, malformed.
Install
# GitHub npm registry (v0.2.0 published 2026-09-08)
npm install @pennyforgeorg/sendcheck-verify --registry https://npm.pkg.github.com
GitHub's npm registry asks for any valid GitHub token even on public reads — put //npm.pkg.github.com/:_authToken= in your .npmrc (classic PAT or fine-grained with Packages:Read works). No token handy? Install from the public repo:
npm install github:pennyforgeorg/sendcheck-verify
Requires Node 18+ (uses global fetch). Zero dependencies. MIT.
Quickstart
import { verifyBeforePay } from "@pennyforgeorg/sendcheck-verify";
// Throws SendCheckVerifyError unless the address passes the free pre-check. await verifyBeforePay("0x9504A5939AB5be2B2B1F8beA7D7ebeCcd96c485D");
// …then sign your x402 payment.
CommonJS:
const { verifyBeforePay } = require("@pennyforgeorg/sendcheck-verify");
Typical wiring inside an x402 client:
import { verifyBeforePay } from "@pennyforgeorg/sendcheck-verify";
import { wrapFetchWithPayment } from "x402-fetch"; // or your own x402 stack
const pay = wrapFetchWithPayment(fetch, { wallet });
async function call(serviceUrl, address, body) { await verifyBeforePay(address); // free — catch bad addresses here const res = await pay(serviceUrl, { // paid only when the address is clean method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), }); return res.json(); }
If you already normalize addresses on your side and just want the result object (no throw):
const r = await verifyBeforePay(addr, { throwOnError: false });
// { valid, status: "valid" | "plain" | "mismatch" | "format" | "network" | "http", // message, normalized, checked }
API
verifyBeforePay(address, opts?)
address— EVM address,0x+ 40 hex.opts.baseUrl— override the API host (default: the live worker).opts.fetchFn— inject a fetch implementation (tests / proxies).opts.throwOnError— defaulttrue; passfalseto always get the result back.
Behavior notes:
- A string that isn't even
0x+40-hex fails the local format check and throws (or returnschecked: false) without sending a request — you never pay a round trip for garbage input. status: "plain"= valid address, all-lowercase or all-uppercase (no case information). That's fine to pay; the response includes the normalized EIP-55 form.status: "mismatch"= the casing does not match the address hash — usually a typo or tampering. This is the one to treat as hard-fail.
looksLikeAddress(addr) — offline format check, no network.
verifyAttestation(attestation, opts?)
Verify a signed block { jws, key } (ES256 JWS over a canonical-JSON payload). Pure + async (WebCrypto); no network. Returns { valid, reason?, payload, keyId, signedAt, expiresAt, warning? }. opts: origin, expectedPayTo, challengePayTo, route, challengeMaxAmount (atomic USDC string), now (unix seconds, for tests). Time windows: exp hard-reject with a 60s clock-skew grace; iat may be up to 60s in the future; an iat older than 30 days is valid but returns warning: "stale". reason codes: malformed, header_mismatch, kid_mismatch, signature_invalid, service_mismatch, payto_mismatch, challenge_payto_mismatch, route_not_signed, price_exceeded, expired, iat_future.
checkService(origin, opts?)
Fetch /.well-known/x402, verify its signed card against the doc's own advertised payTo, and report whether a pinned key changed. Returns { ok, origin, payTo, keyId, signedAt, expiresAt, warning?, attestation, keyChanged, endpoints: [{resource, amount}] }. opts.fetchFn (inject fetch), opts.pinnedKey ({kid} or {kid,x,y}), opts.throwOnError. keyChanged is the loud rotation signal.
verifyChallenge(challenge, attestation, opts?)
Bind a 402 challenge to a signed card. challenge is the decoded 402 declaration ({ resource: { url }, accepts: [{ payTo, maxAmountRequired }] }) — decode the base64 PAYMENT-REQUIRED header with JSON.parse(Buffer.from(hdr, "base64").toString("utf8")). The route is derived from resource.url. Returns the verifyAttestation result.
ATTESTATION_EXTENSION — "x-sendcheck-attestation", the extension name
the signed block rides in under inside the 402 challenge's extensions map.
canonicalJson(value) — RFC-8785-style canonical JSON (recursively sorted
keys) — exported for anyone re-implementing the signer.
SendCheckVerifyError — thrown on non-valid results; err.result carries
the full result object.
The endpoint, in plain terms
GET https://api.pennyforge.org/validate?address=0x…
→ 200 { "valid": true, "status": "valid", "message": "Valid EIP-55 checksum.", "normalized": "0x…" }
Free. No key. No account. JSON in, JSON out. The paid siblings — POST /check ($0.01, one chain: wallet-vs-contract, activity, balances, verdict) and POST /deep ($0.05, all five chains + wrong-network detection) — use x402 (USDC on Base). Machine-readable docs: llms.txt and openapi.json on the same host.
Who runs it
SendCheck is a one-person studio (Pennyforge). The worker runs on Cloudflare Workers; the address engine is MIT and public here. If the free endpoint ever moves, opts.baseUrl is the only thing you change.
License
MIT — see LICENSE.