# Tollbooth: Swap Execution for On-Chain Agents

Non-custodial swap transaction building on 8 chains: Solana, Base, Ethereum,
Arbitrum, Polygon, Optimism, BSC and Avalanche. You pay a small USDC toll per
build via x402; the returned transaction already routes the swap fee on-chain.
Tollbooth never holds your keys or funds. A built-in token safety screen
(honeypot / rug-pull checks) is available standalone or inline with builds.

## When to use this service

Use Tollbooth when you need to swap tokens on Solana (via Jupiter aggregation)
or any supported EVM chain (via 0x) and want a ready-to-sign transaction
without integrating DEX APIs yourself. Quotes are free, so you can
comparison-shop before paying. Use the safety check before buying any token
you did not hard-code: it catches honeypots, freezable mints and seizable
balances before you are holding the bag.

## Quickstart (copy-paste)

Tolls are payable in USDC on **Base** or **Solana mainnet** - use whichever
chain your wallet already holds USDC on. Both are gasless for you: Base uses
EIP-3009 signatures, Solana payments have gas sponsored by the facilitator
(you need zero SOL).

Install: `npm i @x402/fetch @x402/evm viem`. Fund the payer wallet with a
little USDC on Base (tolls are ~half a cent). Then:

```ts
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { toClientEvmSigner } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import { createPublicClient, http } from "viem";
import { base } from "viem/chains";

const account = privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`);
const client = new x402Client().register(
  "eip155:8453",
  new ExactEvmScheme(toClientEvmSigner(account, createPublicClient({ chain: base, transport: http() }))),
);
const payFetch = wrapFetchWithPayment(fetch, client);

// 1. Free quote (plain fetch works too - no payment needed)
const quote = await (await fetch(
  "https://api.tollboothswap.xyz/v1/quote?chain=solana" +
    "&inputToken=So11111111111111111111111111111111111111112" +
    "&outputToken=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&amount=1000000000",
)).json();

// 2. Paid build - payFetch handles the 402 automatically
const built = await (await payFetch("https://api.tollboothswap.xyz/v1/swap/build", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    chain: "solana",
    inputToken: "So11111111111111111111111111111111111111112",
    outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    amount: "1000000000",
    taker: "<your solana wallet>",
    safeMode: true,
  }),
})).json();
// built.transaction is unsigned - sign with the taker key and submit yourself.

// 3. Paid token safety check
const report = await (await payFetch(
  "https://api.tollboothswap.xyz/v1/check/solana/<mint address>",
)).json();
```

Paying from a Solana wallet instead: `npm i @x402/fetch @x402/svm @solana/kit`
and register the SVM scheme - the facilitator sponsors gas, so the wallet only
needs USDC (zero SOL required):

```ts
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { registerExactSvmScheme } from "@x402/svm/exact/client";
import { createKeyPairSignerFromBytes } from "@solana/kit";

const signer = await createKeyPairSignerFromBytes(secretKeyBytes); // 64-byte ed25519 secret key
const client = registerExactSvmScheme(new x402Client(), { signer });
const payFetch = wrapFetchWithPayment(fetch, client);
```

## Endpoints

### GET /v1/tokens/:chain/:symbol (free)

Resolve a ticker symbol to contract addresses. Curated canonical tokens
(USDC, wrapped natives, majors) return a single `verified: true` match with
decimals; anything else returns live DEX matches ranked by liquidity with
`liquidityUsd` and `volume24hUsd` so you can pick the real one over
imitations. Example: `GET /v1/tokens/base/PEPE`. Always screen unverified
matches with the safety check before trading.

### GET /v1/quote (free)

Indicative, fee-inclusive quote. Query parameters:

- `chain`: one of "solana", "base", "ethereum", "arbitrum", "polygon",
  "optimism", "bsc", "avalanche"
- `inputToken`: mint address (Solana) or ERC-20 address (EVM) being sold
- `outputToken`: mint address (Solana) or ERC-20 address (EVM) being bought
- `amount`: integer string, base units of the input token (lamports/wei-style)
- `slippageBps` (optional): max slippage in basis points, default 100

Response: `{ chain, inputToken, outputToken, inAmount, outAmount, feeBps, feeApplied, slippageBps }`.
`outAmount` already reflects Tollbooth's fee — compare it directly against
other venues.

### POST /v1/swap/build (x402 toll, USDC on Base)

Same fields as the quote in a JSON body, plus:

- `taker` (required): the wallet address that will sign and submit the swap
- `safeMode` (optional boolean): screen both tokens before building. If either
  token is rated `critical` (honeypot, freezable, seizable, soulbound), the
  build is refused with HTTP 422 and the full safety reports. Non-critical
  findings are returned alongside the transaction in a `safety` field.

Returns HTTP 402 with payment terms first; any x402-capable client (e.g.
`@x402/fetch` wrapped with a signer) pays and retries automatically.

Response after payment:

- `transaction`: unsigned transaction. Solana: base64 versioned transaction to
  sign with the taker keypair and submit. EVM chains: `{ to, data, value, gas }`
  calldata; if `allowanceSpender` is present, approve it for the sell token
  before sending.
- `quote`: the exact quote the transaction was built from
- `receipt`: HMAC-signed audit record with an `id`

### GET /v1/check/:chain/:token (x402 toll, USDC on Base)

Token safety screen. Path params: `chain` (same enum as quotes) and `token`
(mint or ERC-20 address). Response:

- `verdict`: `ok` | `warn` | `critical` | `unknown`. Treat `critical` as
  do-not-trade; `unknown` means no data exists (common for brand-new tokens).
- `score`: 0-100 risk score (higher = riskier)
- `findings`: itemized `{ id, severity, detail }` list explaining the verdict

Solana checks run against on-chain mint state (mint/freeze authority,
Token-2022 transfer hooks, permanent delegates, transfer fees, frozen default
state). EVM checks use GoPlus security data (honeypot, buy/sell tax,
blacklists, hidden owners, mintability, proxy upgrades). Results are cached
for ~5 minutes.

### GET /v1/receipts/:id (free)

Fetch any past receipt with `signatureValid` verification. Use receipt ids for
accounting and dispute trails.

### GET /.well-known/x402-service (free)

Machine-readable service manifest: pricing, chains, endpoints.

## Common token addresses (native USDC + wrapped gas token)

- Solana: USDC `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`,
  wSOL `So11111111111111111111111111111111111111112`
- Base: USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`,
  WETH `0x4200000000000000000000000000000000000006`
- Ethereum: USDC `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`,
  WETH `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2`
- Arbitrum: USDC `0xaf88d065e77c8cC2239327C5EDb3A432268e5831`,
  WETH `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`
- Polygon: USDC `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359`,
  WPOL `0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270`
- Optimism: USDC `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85`,
  WETH `0x4200000000000000000000000000000000000006`
- BSC: USDC (18 decimals!) `0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d`,
  WBNB `0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c`
- Avalanche: USDC `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E`,
  WAVAX `0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7`

## Fees and trust model

- Swap fee (default 75 bps) is embedded in the returned transaction and
  enforced on-chain — the quoted `outAmount` is what you compare and receive.
- The x402 toll (USDC on Base mainnet or Solana mainnet, your choice) is
  settled by the Coinbase CDP facilitator before the build executes. Solana
  toll payments are gas-sponsored - the paying wallet needs only USDC.
- Tollbooth is non-custodial: it never sees private keys and cannot move your
  funds. You sign and submit everything yourself.

## Error handling

- 400: invalid parameters (schema issues are itemized in `issues`)
- 402: payment required or payment failed — retry with a valid x402 payment
- 502: upstream router (Jupiter/0x) rejected the request; body includes detail
- 503: chain temporarily unavailable (e.g. router not configured)

Quotes expire quickly on volatile pairs; rebuild rather than reusing old
transactions. Solana transactions carry `lastValidBlockHeight` — submit before
it passes or rebuild.

## More

Full tutorial with step-by-step agent integration:
https://api.tollboothswap.xyz/guides/agent-swaps.md

Runnable example scripts (quote, build, safety check, Solana toll payment):
https://github.com/soundsparkaudiolab/tollbooth-examples
