back to Guard

recipe updated 2026-05-13

Safe x402 agent with Lucid

Lucid agents can pay x402 endpoints through the same fetch path any Node or Bun agent uses: construct a payment-enabled fetch, hand it to the agent action, and let the model choose the target URL.

That last step is the dangerous part. A model-selected URL may be a decoy priced at thousands of USDC, a zombie service that always errors after settlement, or an endpoint with no successful payment history. This recipe inserts Preflight by x402station.io before the wallet signs PAYMENT-SIGNATURE.

The pattern is deliberately small: build x402Fetch, wrap it with wrapWithPreflight, then expose only the guarded function to the Lucid action.

What you need

Install

bun add x402station-middleware @x402/fetch @x402/evm viem zod

Build a guarded paid-call helper

import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import { wrapWithPreflight } from "x402station-middleware";

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);

const x402Fetch = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [
    { network: "eip155:8453", client: new ExactEvmScheme(account) },
    { network: "eip155:84532", client: new ExactEvmScheme(account) },
  ],
});

export const safeX402Fetch = wrapWithPreflight(x402Fetch, {
  creditId: process.env.X402STATION_CREDIT_ID,
});

By default the guard is fail-closed. It refuses to sign on critical signals such as dead, zombie, decoy_price_extreme, never_paid_zombie, dead_7d, and mostly_dead.

Expose one Lucid action

Use the exact action helper your Lucid project already uses. The important boundary is that the action calls safeX402Fetch, not raw fetch or raw x402Fetch.

import { z } from "zod";
import { safeX402Fetch } from "./safe-x402-fetch";

export const payX402Endpoint = {
  name: "pay_x402_endpoint",
  description:
    "Pay an x402 HTTP endpoint after x402station.io Preflight clears the target. Refuses decoys, zombies, dead endpoints, and never-paid zombies before PAYMENT-SIGNATURE.",
  schema: z.object({
    url: z.string().url(),
    body: z.record(z.unknown()).optional(),
  }),
  handler: async ({ url, body }: { url: string; body?: Record<string, unknown> }) => {
    try {
      const response = await safeX402Fetch(url, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(body ?? {}),
      });

      const text = await response.text();
      try {
        return JSON.parse(text);
      } catch {
        return { ok: response.ok, status: response.status, body: text };
      }
    } catch (error) {
      if ((error as Error).name === "PreflightBlockedError") {
        return {
          blocked: true,
          blocked_by: "x402station.io Preflight",
          message: (error as Error).message,
        };
      }
      throw error;
    }
  },
};

Then register payX402Endpoint with your Lucid agent exactly as you register other actions. The model should see one payment action, and that action should be the only path to x402 URLs.

Agent instruction

Add a short system instruction near the tool definition:

When pay_x402_endpoint returns blocked=true, do not retry the same URL.
Read the block reason and choose another endpoint or ask for an alternative.
The block happens before payment, so there is no refund to request.

This prevents the model from treating a blocked preflight as a transient HTTP failure.

Optional: direct x402station client

If you want the agent to inspect risk before choosing whether to pay, expose separate tools backed by the public x402station APIs:

The preserved Lucid client snapshot in contrib/daydreams-lucid-x402station/ shows the same method shape: x402Station({ account }).preflight({ url }) returns { result, paymentReceipt }, so the agent can audit both the safety verdict and the settled payment receipt.

Bulk credits

For high-volume agents, buy one credit bundle and pass the returned creditId into wrapWithPreflight:

export const safeX402Fetch = wrapWithPreflight(x402Fetch, {
  creditId: process.env.X402STATION_CREDIT_ID,
});

$0.50 buys 1000 prepaid preflights, lowering effective preflight cost from $0.001 to $0.0005 per call.

Rollout checklist

Source links

back to GuardAPI docs