Skip to content

Add x402station preflight example#1633

Open
sF1nX wants to merge 1 commit into
daydreamsai:masterfrom
sF1nX:codex/x402station-preflight-cookbook
Open

Add x402station preflight example#1633
sF1nX wants to merge 1 commit into
daydreamsai:masterfrom
sF1nX:codex/x402station-preflight-cookbook

Conversation

@sF1nX
Copy link
Copy Markdown

@sF1nX sF1nX commented May 24, 2026

Summary

  • add a runnable payments/x402station-preflight example
  • show the sequence: x402station.io Preflight first, Lucid fetchWithPayment second
  • document how local Lucid payment policies remain the budget/recipient enforcement layer

Validation

  • bun run type-check in packages/examples
  • bun run lint in packages/examples (existing warnings only)
  • bun run format:check in packages/examples
  • commit hook also ran repo-wide lint/type-check/format checks successfully

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 24, 2026

Greptile Summary

Adds a new payments/x402station-preflight example that layers an external endpoint-risk check (x402station.io Preflight) in front of Lucid's existing fetchWithPayment payment flow, along with a README section and env.example.

  • index.ts defines a single delegate-with-preflight entrypoint: it runs a preflight POST to x402station.io, blocks the call early if the verdict is negative, and falls through to fetchWithPayment + Lucid payment policies otherwise.
  • runPreflight accepts a fetchImpl parameter (for swapping between plain and payment-capable fetch) but reads the preflight URL from a module-level constant, creating an inconsistency; additionally, the shared payment-policies.json only allows http://localhost:3001 as a recipient, so switching to the production paid preflight endpoint would be silently blocked by Lucid's own policy layer unless allowedRecipients is updated.

Confidence Score: 4/5

Safe to merge as a self-contained example; the default trial endpoint works end-to-end, and the two design concerns only surface on the production upgrade path.

The example functions correctly with its default trial URL and Hardhat wallet. The two concerns — inconsistent URL parameterization in runPreflight and the allowedRecipients gap for the production preflight endpoint — are real but only affect developers who follow the production upgrade path documented in the README without realising the shared policy file needs updating.

packages/examples/src/payments/x402station-preflight/index.ts — the runPreflight abstraction and the payment-policy interaction on the production path deserve a second look.

Important Files Changed

Filename Overview
packages/examples/src/payments/x402station-preflight/index.ts New example agent demonstrating x402station.io preflight before outbound payment; two design concerns around parameter consistency and production upgrade path through payment policies.
packages/examples/src/payments/x402station-preflight/env.example Environment template consistent with other payment examples; uses the well-known Hardhat account #0 key (same as policy-agent/env.example).
packages/examples/src/README.md README addition correctly documents setup, curl invocation, and expected behaviour; the localhost-will-block note sets accurate expectations.

Sequence Diagram

sequenceDiagram
    participant C as Caller
    participant A as x402station-preflight-agent
    participant X as x402station.io (preflight API)
    participant T as Target Paid Endpoint

    C->>A: POST /entrypoints/delegate-with-preflight/invoke
    A->>A: buildEntrypointInvokeUrl(targetUrl, endpoint)
    A->>X: "POST /api/v1/preflight-trial { url: targetUrl } via fetchWithPayment"
    X-->>A: "PreflightVerdict { ok, warnings, recommended_action, ... }"
    alt "shouldBlockPayment(verdict) == true"
        A-->>C: "{ blocked: true, preflight: summary }"
    else preflight allows target
        A->>T: "POST /entrypoints/{endpoint}/invoke via fetchWithPayment"
        Note over A,T: Lucid payment policies enforced here (budget, rate-limit, allowedRecipients)
        T-->>A: Response (may include 402 auto-pay)
        A-->>C: "{ blocked: false, preflight: summary, result: ... }"
    end
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/examples/src/payments/x402station-preflight/index.ts:52-56
**`runPreflight` silently captures the preflight URL from outer scope**

`runPreflight` accepts `fetchImpl` as a parameter to allow swapping between plain `fetch` and `fetchWithPayment`, but always reads `DEFAULT_PREFLIGHT_URL` directly from the module-level constant. This means the caller has no way to pass a different preflight URL without mutating the env var, and the two "configurable" dimensions (how vs. where) follow inconsistent patterns. A future caller passing a custom fetch would still be silently routed to whichever URL the env happened to set at module load time. Consider adding a `preflightUrl` parameter, or switching to a single config object.

### Issue 2 of 2
packages/examples/src/payments/x402station-preflight/index.ts:155-158
**Production preflight via `fetchWithPayment` will be blocked by existing payment policies**

`runPreflight` passes `paymentContext.fetchWithPayment` as the fetch implementation. The shared `payment-policies.json` used by this example has `"allowedRecipients": ["http://localhost:3001"]`. If a user follows the README's upgrade path and sets `X402STATION_PREFLIGHT_URL=https://x402station.io/api/v1/preflight` (the production endpoint that requires payment), `fetchWithPayment` would try to settle a 402 for `x402station.io` — but that host is not in `allowedRecipients`, so Lucid's own policy layer would block it. The trial endpoint (default) is free and unaffected, but the README should explicitly call out the need to add x402station.io to `allowedRecipients` before switching to the paid preflight tier.

Reviews (1): Last reviewed commit: "Add x402station preflight example" | Re-trigger Greptile

Comment on lines +52 to +56
const response = await fetchImpl(DEFAULT_PREFLIGHT_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: targetUrl }),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 runPreflight silently captures the preflight URL from outer scope

runPreflight accepts fetchImpl as a parameter to allow swapping between plain fetch and fetchWithPayment, but always reads DEFAULT_PREFLIGHT_URL directly from the module-level constant. This means the caller has no way to pass a different preflight URL without mutating the env var, and the two "configurable" dimensions (how vs. where) follow inconsistent patterns. A future caller passing a custom fetch would still be silently routed to whichever URL the env happened to set at module load time. Consider adding a preflightUrl parameter, or switching to a single config object.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/examples/src/payments/x402station-preflight/index.ts
Line: 52-56

Comment:
**`runPreflight` silently captures the preflight URL from outer scope**

`runPreflight` accepts `fetchImpl` as a parameter to allow swapping between plain `fetch` and `fetchWithPayment`, but always reads `DEFAULT_PREFLIGHT_URL` directly from the module-level constant. This means the caller has no way to pass a different preflight URL without mutating the env var, and the two "configurable" dimensions (how vs. where) follow inconsistent patterns. A future caller passing a custom fetch would still be silently routed to whichever URL the env happened to set at module load time. Consider adding a `preflightUrl` parameter, or switching to a single config object.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +155 to +158
const preflight = await runPreflight(
paymentContext.fetchWithPayment,
target
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Production preflight via fetchWithPayment will be blocked by existing payment policies

runPreflight passes paymentContext.fetchWithPayment as the fetch implementation. The shared payment-policies.json used by this example has "allowedRecipients": ["http://localhost:3001"]. If a user follows the README's upgrade path and sets X402STATION_PREFLIGHT_URL=https://x402station.io/api/v1/preflight (the production endpoint that requires payment), fetchWithPayment would try to settle a 402 for x402station.io — but that host is not in allowedRecipients, so Lucid's own policy layer would block it. The trial endpoint (default) is free and unaffected, but the README should explicitly call out the need to add x402station.io to allowedRecipients before switching to the paid preflight tier.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/examples/src/payments/x402station-preflight/index.ts
Line: 155-158

Comment:
**Production preflight via `fetchWithPayment` will be blocked by existing payment policies**

`runPreflight` passes `paymentContext.fetchWithPayment` as the fetch implementation. The shared `payment-policies.json` used by this example has `"allowedRecipients": ["http://localhost:3001"]`. If a user follows the README's upgrade path and sets `X402STATION_PREFLIGHT_URL=https://x402station.io/api/v1/preflight` (the production endpoint that requires payment), `fetchWithPayment` would try to settle a 402 for `x402station.io` — but that host is not in `allowedRecipients`, so Lucid's own policy layer would block it. The trial endpoint (default) is free and unaffected, but the README should explicitly call out the need to add x402station.io to `allowedRecipients` before switching to the paid preflight tier.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant