# @vaultfire/x402-guard

**Pre-payment trust check for x402 wallets.**

Drop-in middleware that verifies a counterparty's onchain identity, Street Cred reputation, and accountability bonds on [Vaultfire](https://theloopbreaker.com) **before** your wallet signs a USDC payment.

Works with [Coinbase Agentic Wallet (AWAL)](https://www.coinbase.com/developer-platform), AgentCash, agentkit, [Bazaar](https://bazaar.commerce.coinbase.com/), or any custom x402 client. Multi-chain across **Base, Avalanche, Arbitrum, and Polygon**.

> The x402 spec answers "how do I pay?" — Vaultfire answers **"should I pay?"**

[![npm](https://img.shields.io/npm/v/@vaultfire/x402-guard.svg)](https://www.npmjs.com/package/@vaultfire/x402-guard)
[![license](https://img.shields.io/npm/l/@vaultfire/x402-guard.svg)](./LICENSE)

---

## Why

x402 makes machine-to-machine payments cheap and instant. That's the problem too — your agent will happily sign a USDC transfer to a wallet that was created 4 seconds ago, has zero history, and is run by a scammer. There is no rep layer in the protocol itself.

`@vaultfire/x402-guard` adds one: a sub-second onchain check against the Vaultfire identity registry that returns a structured `allow / deny` decision **before** your wallet signs.

| Without guard | With `@vaultfire/x402-guard` |
| --- | --- |
| 402 → sign → send | 402 → **trust check** → sign → send |
| Trusts every `payTo` | Verifies identity, Street Cred, active bonds |
| One scam = one loss | Block on score / unknown / no bond |

---

## Install

```bash
npm install @vaultfire/x402-guard
```

> Requires Node 18+. Pulls in `@vaultfire/langchain` as a dependency for onchain reads (no API key, no env vars).

---

## Quick start

```ts
import { VaultfireGuard } from '@vaultfire/x402-guard';

const guard = new VaultfireGuard({ minScore: 40 });

// Before signing an x402 payment to `recipient`:
const decision = await guard.check(recipient);

if (!decision.allow) {
  throw new Error(`Vaultfire blocked payment: ${decision.reason}`);
}
// ...proceed to sign + send USDC
```

A `GuardDecision` is fully structured:

```ts
{
  allow: true,
  reason: 'Vaultfire trust check passed: Street Cred 55/95 (silver) on base, 1 active bond(s)',
  address: '0xfA15Ee...',
  chain: 'base',
  score: 55,
  tier: 'silver',
  verification: { /* full TrustVerification from @vaultfire/langchain */ }
}
```

---

## Wrap an existing x402 fetch

If your client already speaks x402 (e.g. `x402-fetch`, AWAL's `payAndFetch`, AgentCash, etc.), wrap it once and every payment is gated automatically:

```ts
import { VaultfireGuard } from '@vaultfire/x402-guard';
import { x402Fetch } from 'x402-fetch';

const guard = new VaultfireGuard({ minScore: 30 });
const guardedFetch = guard.wrapFetch(x402Fetch);

// Same API, but every 402 challenge runs through Vaultfire first.
const res = await guardedFetch('https://api.example.com/premium', {
  method: 'POST',
  body: JSON.stringify({ q: 'hello' }),
});
```

When a 402 comes back, `x402-guard`:

1. Reads `payTo` from the response body or `X-Payment-Address` header.
2. Calls `check(payTo)` against Vaultfire identity registries on Base / Avax / Arb / Polygon **in parallel**, picking the chain with the **highest** Street Cred score (so cross-chain agents aren't penalized).
3. Throws `VaultfireGuardError` if the decision is deny — your wallet never signs.
4. Otherwise, forwards the request to the underlying x402 fetch.

---

## Config

```ts
new VaultfireGuard({
  minScore?: number,                  // default 20 — block below this Street Cred (0–95)
  chains?: ('base'|'avalanche'|'arbitrum'|'polygon')[], // default: all four
  allowUnregistered?: boolean,        // default false — block agents with no Vaultfire identity
  rpcUrls?: { base?: string, ... },   // bring your own RPCs (Alchemy, Infura, etc.)
  warnOnly?: boolean,                 // default false — log instead of block
});
```

### RPC defaults

With no config, `x402-guard` uses free, no-key public RPCs from PublicNode
(generous burst limits). You can override per-chain via `rpcUrls`, or via env
vars: `VAULTFIRE_BASE_RPC`, `VAULTFIRE_ARBITRUM_RPC`, `VAULTFIRE_POLYGON_RPC`,
`VAULTFIRE_AVALANCHE_RPC`.

For production, point at your own Alchemy / Infura / QuickNode / Ankr endpoint
for lower latency and dedicated rate limits.

### Failure modes

`x402-guard` is **fail-closed** by default. If every chain RPC is unreachable, the decision is `deny`. Use `warnOnly: true` during rollout to log without blocking.

Reliability features:

- **Automatic retry on RPC errors:** transient rate-limits, timeouts, and 5xx errors are retried up to 3 times with exponential backoff (250ms → 750ms → 2s).
- **Smart revert classification:** contract reverts on `isAgentActive` (meaning "unknown agent") are correctly classified as **unregistered**, NOT as an RPC failure. JSON-RPC server errors (`-32000` to `-32099`) and rate-limit messages are correctly classified as **RPC failures**, NOT as unregistered — so a throttled node can never silently pass an unverified agent under `allowUnregistered: true`.
- **Multi-chain redundancy:** if one chain is down, the others still produce a decision. Only when *every* chain fails does the guard fail closed.

---

## One-shot helper

```ts
import { checkTrust } from '@vaultfire/x402-guard';

const decision = await checkTrust('0xabc...', { minScore: 50 });
```

Equivalent to `new VaultfireGuard(config).check(address)` — useful for ad-hoc checks outside a fetch pipeline.

---

## How it works

Every Vaultfire-registered agent has:

- **Identity:** an ERC-8004 record on the chain it minted on.
- **Street Cred:** a 0–95 reputation score derived from accountability bonds, age, and challenge outcomes.
- **Tier:** `unranked` → `bronze` → `silver` → `gold` → `platinum`.
- **Bonds:** USDC the agent has put up to back its behavior. Bond gets slashed on misbehavior.

`x402-guard` reads all of this directly from the Vaultfire identity registries — no off-chain API, no key required, no rate-limited gateway. If a counterparty fails any check, your wallet never signs.

The Vaultfire identity registry contracts are deployed on:

| Chain      | Identity Registry                                    |
| ---------- | ---------------------------------------------------- |
| Base       | `0xa7BD20bf5De63df949cA5Be2F20835978eCba81A`         |
| Arbitrum   | (see [vaultfire docs](https://theloopbreaker.com))   |
| Polygon    | (see [vaultfire docs](https://theloopbreaker.com))   |
| Avalanche  | (see [vaultfire docs](https://theloopbreaker.com))   |

---

## x402 Batch Settlement (v1.1+)

v1.1 adds voucher-aware trust gating for the [x402 batch-settlement scheme](https://docs.x402.org/schemes/batch-settlement) — agents escrow once, pay with off-chain vouchers, sellers settle in bulk.

The pitch is simple: **a Vaultfire accountability bond is the collateral that says the voucher is good.** Before a seller accepts a deposit, gate it on `checkChannel`. On every voucher, gate it on `verifyVoucherCounterparty` (cached, cheap).

```ts
import {
  checkChannel,
  verifyVoucherCounterparty,
  BATCH_SETTLEMENT_ADDRESS,
  BATCH_SETTLEMENT_DOMAIN,
} from '@vaultfire/x402-guard';

// Before the buyer signs a deposit OR the seller accepts one:
const decision = await checkChannel({
  payer:              '0x...',
  payerAuthorizer:    '0x...',  // signs vouchers
  receiver:           '0x...',
  receiverAuthorizer: '0x...',  // signs claim batches
  token:              '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
  withdrawDelay:      900,      // 15 min, the on-chain minimum
  salt:               '0x...64-hex...',
});

// recommendation: 'open' | 'open-with-cap' | 'single-payment-only' | 'decline'
if (!decision.allow) throw new Error(decision.reason);
```

```ts
// Hot-path on every incoming voucher (60s cache by default):
const v = await verifyVoucherCounterparty(channel.payerAuthorizer);
if (!v.allow) return res.status(402).json({ error: 'voucher-rejected-no-trust-bond' });
```

### Constants (verified against `x402-foundation/x402` @ commit `ca60063c`)

| Constant | Value |
| --- | --- |
| `BATCH_SETTLEMENT_ADDRESS` | `0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003` |
| `ERC3009_DEPOSIT_COLLECTOR_ADDRESS` | `0x4020806089470a89826cB9fB1f4059150b550004` |
| `PERMIT2_DEPOSIT_COLLECTOR_ADDRESS` | `0x4020425FAf3B746C082C2f942b4E5159887B0005` |
| `BATCH_SETTLEMENT_DOMAIN` | `{ name: 'x402 Batch Settlement', version: '1' }` |
| `MIN_WITHDRAW_DELAY_SECONDS` | `900` (15 min) |
| `MAX_WITHDRAW_DELAY_SECONDS` | `2_592_000` (30 days) |

### Open audit issues to be aware of

From the [Cantina audit](https://cantina.xyz/portfolio/5035772b-2800-4f00-bb62-e7994419f8dd) of x402 batch-settlement (Apr 27–29, 2026), two issues are **acknowledged but unfixed**:

1. **USDC/USDT blacklist of payer or receiver permanently traps escrow.** Mitigate by capping per-channel deposit size and using short `withdrawDelay`.
2. **Plain `refund()` invalidates pre-signed `refundWithSignature()` authorizations** (nonce collision). Don't expose `refundWithSignature` publicly without first coordinating a plain `refund()`.

We're not papering over these. If you ship batch settlement in production, you take these risks on top of whatever Vaultfire's bond gives you.

---

## Tests

The test suite hits the **real Base RPC** and the **real Vaultfire identity registry** — no mocks, no fixtures. A test pass means the package works in production right now.

```bash
npm test
```

---

## Mission

Morals over metrics. Privacy over surveillance. Freedom over control. Humanity over everything.

`x402-guard` is **Know-Your-Agent (KYA)** infrastructure: identity verification without surveillance. We don't collect government IDs, we don't doxx wallets, we don't track users. We give agents a way to **prove they have skin in the game** — and give other agents a way to **check** before they sign.

That's the opposite of KYC. That's the point.

---

## License

MIT — see [LICENSE](./LICENSE).

---

## Links

- **Vaultfire:** https://theloopbreaker.com
- **`@vaultfire/langchain` SDK:** https://www.npmjs.com/package/@vaultfire/langchain
- **Source:** https://github.com/Ghostkey316/vaultfire-x402-guard
- **Issues:** https://github.com/Ghostkey316/vaultfire-x402-guard/issues
