# Weakspot — agent integration

Commission a smart contract security audit over HTTP. No account, no API
key, no wallet-connect flow — payment itself is the authentication,
via the [x402 protocol](https://docs.payai.network/x402/introduction)
(HTTP 402 Payment Required), settled through PayAI's facilitator.

## Endpoints

Price scales $1 per 10 files, rounded up — same formula the browser
payment path uses (`AuditPayments.sol`'s `requiredAmount`). Capped at 40
files (4 tiers); for more than that, split the upload.

| Method | Path | Price | Use for |
| --- | --- | --- | --- |
| `POST` | `/api/agent/audit` | $1.00 | 1-10 source files |
| `POST` | `/api/agent/audit/tier2` | $2.00 | 11-20 source files |
| `POST` | `/api/agent/audit/tier3` | $3.00 | 21-30 source files |
| `POST` | `/api/agent/audit/tier4` | $4.00 | 31-40 source files |
| `GET` | `/api/agent/audit/:jobId` | free | poll status/result — no auth |

Network: Base (mainnet) or Base Sepolia (testnet), depending on
deployment. USDC only — required by x402's `exact` payment scheme
(EIP-3009 `transferWithAuthorization`). MAGIC (the browser path's token)
was tried here too and doesn't work: it implements EIP-3009 in spirit,
but `transferWithAuthorization` takes a single packed `bytes signature`
argument rather than the split `(v, r, s)` Circle's reference
implementation (and thus this scheme) expects — a real signed
authorization against it reverts on simulation. Confirmed directly
against the facilitator, not assumed.

Pick the endpoint by your actual file count *before* paying — the price
is fixed per route, and the server rejects (with a plain 400, before any
payment is requested) a request sent to the wrong tier for its file
count.

## Request body

Both `POST` routes take the same JSON shape:

```json
{
  "description": "What this project does, trust assumptions, anything a reviewer should know.",
  "verify": true,
  "files": [
    { "path": "contracts/Token.sol", "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n..." }
  ]
}
```

- `files` — required, non-empty. Each `path` must end in `.sol` or `.rs`.
- `description` — optional but recommended; the more context, the fewer
  false positives.
- `verify` — optional, defaults to `true`. A second adversarial LLM pass
  that re-checks every finding against the actual code before it's
  returned — cuts false positives substantially, roughly doubles audit
  time. Set `false` for a faster, noisier first look.

## Flow

1. `POST` your request with no payment. You'll get back `402 Payment
   Required` with the payment requirements in the `PAYMENT-REQUIRED`
   header (base64-encoded JSON — amount, asset, `payTo`, network).
2. Construct and sign an x402 `exact`-scheme payment authorization for
   that amount (most x402 client libraries — `@x402/evm`, PayAI's own
   client tooling, coinbase's `x402-fetch` — do this for you given a
   signer).
3. Resubmit the same request with the signed authorization in a
   **`Payment-Signature`** header (base64-encoded JSON, same shape as the
   `PAYMENT-REQUIRED` response but with a `payload` added — see the
   example below for the exact structure; this was worth spelling out
   explicitly since `X-Payment` — a header name that appears in some x402
   client examples — is *not* read by this server's actual payment
   extraction, only `Payment-Signature` is, confirmed by testing both
   directly). On success you get back `202` and a `jobId`:

```json
{ "jobId": "3f9c2a1e-..." }
```

4. Poll `GET /api/agent/audit/:jobId` (no auth needed — the jobId itself
   is your bearer credential, so keep it if you care about who else can
   read the result) until `stage` is `"done"` or `"error"`:

```json
{
  "stage": "done",
  "cancelled": false,
  "progress": null,
  "summary": "...",
  "findings": [
    {
      "title": "Reentrancy in withdraw() allows vault drainage",
      "severity": "critical",
      "file": "contracts/Token.sol",
      "lines": "40-52",
      "description": "...",
      "recommendation": "...",
      "source": "llm",
      "verified": true
    }
  ]
}
```

`stage` moves through `static-analysis` → `batches` → (`verify`, if
enabled) → `summary` → `done`. `progress` is non-null during `batches`
and `verify`, showing `{ index, total }`.

## Example

```bash
curl -X POST https://<host>/api/agent/audit \
  -H "Content-Type: application/json" \
  -d '{
    "description": "A simple ETH vault. Users deposit and withdraw their own balance.",
    "files": [{"path": "Vault.sol", "content": "..."}]
  }'
# -> 402 Payment Required, PAYMENT-REQUIRED header decodes to:
# {"x402Version":2,"error":"Payment required","resource":{...},
#  "accepts":[{"scheme":"exact","network":"eip155:8453","amount":"1000000",
#              "asset":"<USDC address>","payTo":"<treasury>",
#              "maxTimeoutSeconds":300,"extra":{"name":"USDC","version":"2"}}]}
```

Sign an EIP-3009 `TransferWithAuthorization` for that `accepts[0]` entry
(domain `{name, version}` from `extra`, `chainId` from the network,
`verifyingContract` = `asset`), then build the `Payment-Signature` header
as base64 of:

```json
{
  "x402Version": 2,
  "accepted": { "...": "the exact accepts[0] object from above" },
  "payload": {
    "signature": "0x...",
    "authorization": {
      "from": "<your address>", "to": "<payTo>", "value": "1000000",
      "validAfter": "0", "validBefore": "<now + maxTimeoutSeconds>",
      "nonce": "0x<32 random bytes>"
    }
  }
}
```

```bash
curl -X POST https://<host>/api/agent/audit \
  -H "Content-Type: application/json" \
  -H "Payment-Signature: <base64 of the JSON above>" \
  -d '{...same body...}'
# -> 202 { "jobId": "..." }

curl https://<host>/api/agent/audit/<jobId>
# -> poll until stage is "done" or "error"
```

See `worker/agent.ts` in the source repo for the exact server-side
implementation, and the root `CLAUDE.md` for how this fits into the rest
of the app — there's a separate, unrelated browser-based payment path
(wallet connect + Sign-In With Ethereum, paying in MAGIC through
`AuditPayments.sol`) that this API has no dependency on.
