关于 SendCheck — EVM address verification (x402)
SendCheck 是一个零依赖客户端,可在 x402 客户端付款前检查 EVM 地址。它结合免费的校验和预检、签名服务卡验证与付款挑战验证。
主要功能
- 01免费的 EVM 地址预检
- 02离线地址格式校验
- 03ES256 签名卡验证
- 04挑战地址与价格检查
- 05检测固定密钥轮换
适用场景
- 01付款前拦截格式错误的地址
- 02依据签名条款检查 x402 挑战
- 03发现服务签名密钥变更
MCP 相关
此软件包是 x402 API 客户端,而非 MCP 服务;README 未说明其支持 MCP。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://api.pennyforge.org/mcpclaude mcp add --transport http org-pennyforge-api-sendcheck https://api.pennyforge.org/mcp{"mcpServers":{"org-pennyforge-api-sendcheck":{"type":"http","url":"https://api.pennyforge.org/mcp"}}}在 Cursor 中打开{"servers":{"org-pennyforge-api-sendcheck":{"type":"http","url":"https://api.pennyforge.org/mcp"}}}在 VS Code 中打开{"mcpServers":{"org-pennyforge-api-sendcheck":{"type":"http","url":"https://api.pennyforge.org/mcp"}}}{"mcpServers":{"org-pennyforge-api-sendcheck":{"serverUrl":"https://api.pennyforge.org/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
3| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| address | string | 是 | EVM 地址,0x + 40 个十六进制字符(大小写均可) |
详细介绍
sendcheck-verify
付款前先验证。 一个零依赖客户端,用于免费调用
GET /validate
对 SendCheck x402 API 进行预检查 —
这道仅需 10 行代码的防护,可阻止你的 Agent 向拼写错误、不匹配或遭篡改的结算地址签署付款。
x402 客户端向某项服务付款时,会向该服务告知的结算地址付款。只要有一个字符错误(大小写颠倒、漏掉一位数字、替换了地址),USDC 就会永远流向别处。/validate 是免费的、无需密钥、无需账户的预检查:在投入真金白银前,这个地址是否确实格式正确,且 EVM 地址大小写校验无误?
从 v0.2.0 起,这个软件包还会验证服务的签名服务卡片 — 这是一个针对 payTo 和每条路由 maxPrice 签署的 ES256 JWS,工作进程会在每次部署时重新签署 — 因此,只要客户端曾固定过公钥一次,就能在付款前以密码学方式确认挑战中的 payTo。这是防地址漂移检查的参考实现(地址漂移文章中的“签名服务卡片”)。
为什么需要它
SendCheck 的付费端点通过 x402 收费:单条链 $0.01,全部五条链 $0.05 — 使用 Base 上的 USDC,无需账户或 API 密钥。但大多数“这个地址是不是无效?”检查都不需要链上数据:仅 EIP-55 校验和就能发现最常见、代价最高的问题(拼写错误、字符缺失、大小写遭篡改)。因此 /validate 免费,而这个软件包让调用它只需一行代码。
签名服务卡片(v0.2.0)
付款前检查地址有一个漏洞:客户端用于比对的“预期 payTo”通常来自刚刚获取的同一份发现文档 — 因此,被攻陷的服务可以把你指向自己的新地址;对于经常轮换地址的服务,等检查完成时,每个记住的地址都已过时。
SendCheck 通过签署其付款详情来堵上这个漏洞。工作进程在每次部署时,都会使用稳定的 ES256(EC P-256)密钥签署结算地址和每条路由的最高价格,并在三个位置提供已签名区块 { jws, key }:
<origin>/.well-known/x402 → top-level "attestation"
<origin>/openapi.json → info["x-attestation"]
the 402 challenge itself → extensions["x-sendcheck-attestation"]
SendCheck 在每次部署时都使用稳定密钥签署其付款详情,因此,任何曾固定过该密钥一次的客户端,都能在付款前以密码学方式确认付款挑战中的地址确实由我们签署;首次联系仍遵循首次使用时信任(TOFU)。
签名带来的保障:为有状态的回访客户端提供密钥连续性 — 被攻陷的工作进程/DNS/CDN 无法悄悄重定向你的付款,密钥轮换也会成为可标记的显著事件。签名不提供的保障(我们有意不夸大):首次访问时的真实性(这属于 TOFU)、对没有固定密钥的临时 Agent 的保护,或低于已签名 maxPrice 的价格。
已签名负载还包含每条路由的 maxPrice,因此 verifyChallenge 会捕获价格悄然高于密钥签署上限的挑战。
防漂移接入方式
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 会检查挑战中的 payTo 是否与已签名地址一致,并检查挑战价格是否超过该路由已签名的 maxPrice。失败原因:signature_invalid、challenge_payto_mismatch、price_exceeded、route_not_signed、expired、iat_future、service_mismatch、kid_mismatch、malformed。
安装
# GitHub npm registry (v0.2.0 published 2026-09-08)
npm install @pennyforgeorg/sendcheck-verify --registry https://npm.pkg.github.com
即使是读取公开内容,GitHub 的 npm 注册表也会要求提供任意有效的 GitHub 令牌 — 在 .npmrc 中添加 //npm.pkg.github.com/:_authToken=<your GH token>(classic PAT 或具有 Packages:Read 权限的 fine-grained token 均可)。手头没有令牌?从公开仓库安装:
npm install github:pennyforgeorg/sendcheck-verify
需要 Node 18+(使用全局 fetch)。零依赖。MIT。
快速开始
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");
在 x402 客户端中的典型接线方式:
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();
}
如果你已在自己的环境中规范化地址,只想获取结果对象(不抛出异常):
const r = await verifyBeforePay(addr, { throwOnError: false });
// { valid, status: "valid" | "plain" | "mismatch" | "format" | "network" | "http",
// message, normalized, checked }
API
verifyBeforePay(address, opts?)
address— EVM 地址,格式为0x+ 40 位十六进制字符。opts.baseUrl— 覆盖 API 主机(默认:线上 Worker)。opts.fetchFn— 注入 fetch 实现(用于测试/代理)。opts.throwOnError— 默认值为true;传入false可始终获取结果。
行为说明:
- 不符合
0x+40 位十六进制格式的字符串会在本地格式检查时失败,并抛出异常(或返回checked: false),不会发送请求——你无需为无效输入支付一次往返请求。 status: "plain"= 有效地址,全小写或全大写(不包含大小写校验信息)。这类地址可以正常支付;响应中会包含规范化后的 EIP-55 格式。status: "mismatch"= 大小写与地址哈希不匹配——通常是输入错误或遭到篡改。这是唯一应视为硬性失败的情况。
looksLikeAddress(addr) — 离线格式检查,不访问网络。
verifyAttestation(attestation, opts?)
验证已签名区块 { jws, key }(针对规范 JSON 负载的 ES256 JWS)。
纯异步操作(WebCrypto);不访问网络。返回
{ valid, reason?, payload, keyId, signedAt, expiresAt, warning? }。
opts:origin、expectedPayTo、challengePayTo、route、
challengeMaxAmount(原子单位的 USDC 字符串)、now(Unix 秒,用于测试)。
时间窗口:exp 超时超过 60 秒时硬性拒绝;iat 最多可比当前时间晚
60 秒;iat 早于 30 天仍然有效,但会返回
warning: "stale"。reason 代码: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?)
获取 <origin>/.well-known/x402,根据文档自身公布的 payTo 验证其签名卡片,并报告已固定的密钥是否发生变化。返回
{ ok, origin, payTo, keyId, signedAt, expiresAt, warning?, attestation, keyChanged, endpoints: [{resource, amount}] }。opts.fetchFn(注入 fetch)、
opts.pinnedKey({kid} 或 {kid,x,y})、opts.throwOnError。keyChanged 是
明确的密钥轮换信号。
verifyChallenge(challenge, attestation, opts?)
将 402 挑战与签名卡片绑定。challenge 是解码后的 402
声明({ resource: { url }, accepts: [{ payTo, maxAmountRequired }] })
——使用
JSON.parse(Buffer.from(hdr, "base64").toString("utf8")) 解码 base64 PAYMENT-REQUIRED 标头。路由根据 resource.url 推导。返回
verifyAttestation 的结果。
ATTESTATION_EXTENSION — "x-sendcheck-attestation",签名区块在 402 挑战的 extensions 映射中使用的扩展名。
canonicalJson(value) — 类似 RFC-8785 的规范 JSON(递归排序键)——导出供任何重新实现签名器的人使用。
SendCheckVerifyError — 非有效结果时抛出;err.result 包含完整结果对象。
通俗解释这个端点
GET https://api.pennyforge.org/validate?address=0x…
→ 200 { "valid": true, "status": "valid", "message": "Valid EIP-55 checksum.",
"normalized": "0x…" }
免费。无需密钥。无需账户。输入 JSON,输出 JSON。付费的同类端点——POST /check
($0.01,一条链:钱包与合约对比、活动、余额、判定)和
POST /deep($0.05,全部五条链 + 错误网络检测)——使用 x402
(Base 上的 USDC)。机器可读文档:
同一主机上的 llms.txt 和
openapi.json。
由谁运营
SendCheck 是一家由个人运营的工作室(Pennyforge)。Worker 运行在 Cloudflare
Workers 上;地址引擎采用 MIT 许可证并公开托管于
此处。如果免费端点将来迁移,你只需更改 opts.baseUrl。
许可证
MIT — 参见 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。