Skip to main content
ClaudeWave

Verify-before-pay client for the SendCheck x402 API — free address pre-check + the signed service card (ES256 JWS over payTo + per-route maxPrice). Zero deps.

ToolsRegistry oficial0 estrellas0 forksJavaScriptMITActualizado today
ClaudeWave Trust Score
87/100
Trusted
Passed
  • Open-source license (MIT)
  • Actively maintained (<30d)
  • Clear description
  • Documented (README)
Last scanned: 9/9/2026
Get started
Method: Clone
Terminal
git clone https://github.com/pennyforgeorg/sendcheck-verify
1. Clone the repository.
2. Follow the README for installation and usage instructions.
Casos de uso

Resumen de Tools

# sendcheck-verify

**Verify before you pay.** A zero-dependency client for the free
[`GET /validate`](https://api.pennyforge.org/validate?address=0x…)
pre-check on the [SendCheck x402 API](https://api.pennyforge.org/) —
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](https://dev.to/pennyforgehq/one-x402-service-rotated-its-pay-address-964-times-in-10-days-i-measured-90-days-of-the-drift-feed-3cbp)).

## 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"
<origin>/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

```js
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

```bash
# 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=<your GH token>` in your `.npmrc` (classic PAT or
fine-grained with Packages:Read works). No token handy? Install from the public repo:

```bash
npm install github:pennyforgeorg/sendcheck-verify
```

Requires Node 18+ (uses global `fetch`). Zero dependencies. MIT.

## Quickstart

```js
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:

```js
const { verifyBeforePay } = require("@pennyforgeorg/sendcheck-verify");
```

Typical wiring inside an x402 client:

```js
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):

```js
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` — default `true`; pass `false` to always get the result back.

Behavior notes:

- A string that isn't even `0x`+40-hex fails the **local** format check and
  throws (or returns `checked: 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 `<origin>/.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`](https://api.pennyforge.org/llms.txt) and
[`openapi.json`](https://api.pennyforge.org/openapi.json)
on the same host

Lo que la gente pregunta sobre sendcheck-verify

¿Qué es pennyforgeorg/sendcheck-verify?

+

pennyforgeorg/sendcheck-verify es tools para el ecosistema de Claude AI. Verify-before-pay client for the SendCheck x402 API — free address pre-check + the signed service card (ES256 JWS over payTo + per-route maxPrice). Zero deps. Tiene 0 estrellas en GitHub y su última actualización registrada es del 2026-09-08.

¿Cómo se instala sendcheck-verify?

+

Puedes instalar sendcheck-verify clonando el repositorio (https://github.com/pennyforgeorg/sendcheck-verify) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.

¿Es seguro usar pennyforgeorg/sendcheck-verify?

+

Nuestro agente de seguridad ha analizado pennyforgeorg/sendcheck-verify y le ha asignado un Trust Score de 87/100 (tier: Trusted). Revisa el desglose completo de comprobaciones superadas y flags en esta página.

¿Quién mantiene pennyforgeorg/sendcheck-verify?

+

pennyforgeorg/sendcheck-verify es mantenido por pennyforgeorg. La última actividad registrada en GitHub es del 2026-09-08, con 0 issues abiertos.

¿Hay alternativas a sendcheck-verify?

+

Sí. En ClaudeWave puedes explorar tools similares en /categories/tools, ordenados por popularidad o actividad reciente.

Despliega sendcheck-verify en tu cloud

Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.

¿Mantienes este repo? Añade un badge a tu README

Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.

Featured on ClaudeWave: pennyforgeorg/sendcheck-verify
[![Featured on ClaudeWave](https://claudewave.com/api/badge/pennyforgeorg-sendcheck-verify)](https://claudewave.com/repo/pennyforgeorg-sendcheck-verify)
<a href="https://claudewave.com/repo/pennyforgeorg-sendcheck-verify"><img src="https://claudewave.com/api/badge/pennyforgeorg-sendcheck-verify" alt="Featured on ClaudeWave: pennyforgeorg/sendcheck-verify" width="320" height="64" /></a>

Más Tools

Alternativas a sendcheck-verify