Skip to content

feat: facilitator#1

Merged
lucky-ivanius merged 9 commits intomainfrom
facilitator
Apr 16, 2026
Merged

feat: facilitator#1
lucky-ivanius merged 9 commits intomainfrom
facilitator

Conversation

@lucky-ivanius
Copy link
Copy Markdown
Contributor

@lucky-ivanius lucky-ivanius commented Apr 16, 2026

Summary by CodeRabbit

  • New Features

    • Example client demonstrating payment-integrated HTTP requests with automatic settlement handling.
    • Example server showcasing an API route that enforces payment requirements and returns paid responses.
    • Facilitator service exposing endpoints for payment settlement, verification, and listing supported schemes.
    • Support added for Zero Gravity (0G) mainnet and testnet networks.
  • Chores

    • Added example env files, ignore rules, package manifests, TypeScript, linting, testing, and deployment configs.

@lucky-ivanius lucky-ivanius self-assigned this Apr 16, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Added two example apps (client and server) demonstrating x402 payment flows, and a new Cloudflare Worker package (@newt0n/facilitator) implementing facilitator middleware, handlers (verify/settle/supported), Zero Gravity chain support, and associated tooling/config files.

Changes

Cohort / File(s) Summary
Client Example
examples/client/.env.example, examples/client/.gitignore, examples/client/package.json, examples/client/tsconfig.json, examples/client/index.ts
New client example: env var for EVM_PRIVATE_KEY, package/TS config, fetch wrapper with payment integration, EVM signer creation, request to /weather, and payment settlement extraction.
Server Example
examples/server/.gitignore, examples/server/package.json, examples/server/tsconfig.json, examples/server/index.ts
New server example: Hono app with payment middleware on GET /weather, ExactEvmScheme configuration, and returns weather JSON with payment headers.
Facilitator Package — Config
packages/facilitator/.env.example, packages/facilitator/.gitignore, packages/facilitator/biome.json, packages/facilitator/tsconfig.json, packages/facilitator/vitest.config.ts, packages/facilitator/wrangler.jsonc, packages/facilitator/package.json
Tooling and runtime config for Cloudflare Worker facilitator: env examples, ignore rules, Biome, TypeScript, Vitest (Cloudflare pool) and Wrangler deployment config.
Facilitator Package — Core & Types
packages/facilitator/src/env.ts, packages/facilitator/src/errors.ts, packages/facilitator/src/index.ts
App entry: Hono app with logging/CORS/trailing-slash middleware, centralized error handling, exported Env/Variables types, and internalServerError constant.
Facilitator Handlers
packages/facilitator/src/handlers/settle.ts, packages/facilitator/src/handlers/verify.ts, packages/facilitator/src/handlers/supported.ts
New Hono routers: POST / settle and verify endpoints with JSON validation/parsing and error handling; GET / supported endpoint returning supported schemes.
Zero Gravity Chain & Signer
packages/facilitator/src/lib/chains/0g.ts, packages/facilitator/src/lib/x402/signer.ts, packages/facilitator/src/lib/x402/scheme.ts
Zero Gravity chain definitions and viem wallet client factory; facilitator signer adapter bridging viem wallet to facilitator APIs; helpers to register Exact/Upto schemes for 0G chains.
Facilitator Middleware
packages/facilitator/src/middlewares/x402-facilitator-client.ts
Middleware factory that creates an x402Facilitator per request, builds ZeroG wallet clients from env RPCs and private key, registers schemes (exact/upto) for mainnet/testnet, and injects facilitator into request context.
Workspace
package.json
Added vitest and zod to workspace catalog entries.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant Facilitator
  participant ZeroG_RPC

  Client->>Server: GET /weather (wrapped fetch triggers payment)
  Server->>Facilitator: validate/require payment (middleware calls X402_FACILITATOR)
  Facilitator->>ZeroG_RPC: sign/verify/settle txs / read chain state
  ZeroG_RPC-->>Facilitator: tx result / proof
  Facilitator-->>Server: verification/settlement result
  Server-->>Client: 200 with payment-settle headers and JSON body
  Client->>Client: extract payment settlement from headers (x402 client)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code and built a bride,
Small examples strung each payment side—
Zero Gravity threads now neatly spun,
Facilitator hums until the job's done,
A carrot for the deploy, then hop, we're done! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: facilitator' is overly vague and does not meaningfully convey the specific changes made in this pull request, which include adding a facilitator server, client examples, and supporting infrastructure. Use a more descriptive title that captures the main purpose, such as 'feat: add facilitator server with client examples and setup' or similar to better communicate the changeset scope.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch facilitator

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (8)
examples/client/package.json (1)

7-7: Avoid using "latest" for better build reproducibility.

Using "latest" for @types/bun prevents reproducible builds and may introduce breaking changes unexpectedly. Pin to a specific version or use a version range instead.

♻️ Recommended fix
   "devDependencies": {
-    "@types/bun": "latest"
+    "@types/bun": "^1.3.12"

Alternatively, reference the workspace catalog (if this package is added to the workspace):

   "devDependencies": {
-    "@types/bun": "latest"
+    "@types/bun": "catalog:"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/client/package.json` at line 7, The dependency entry for
"@types/bun" in package.json is pinned to "latest", which breaks reproducible
builds; update the dependency value from "latest" to a specific version or a
constrained semver range (e.g., "0.x" or "0.1.2") in the
dependencies/devDependencies block so the package.json (and any workspace
reference) uses a fixed, tested version instead of "latest".
examples/client/.gitignore (1)

16-16: Unusual glob pattern for report files.

The pattern report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json matches filenames with underscores after each digit (e.g., report.1_.2_.3_.4_.json), which is uncommon. If you intend to match typical versioned reports like report.1.2.3.4.json, consider using a wildcard pattern instead.

♻️ Alternative pattern
 # logs
 logs
 _.log
-report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+report.*.json

Or if you need digit matching:

-report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/client/.gitignore` at line 16, The glob pattern
"report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json" is incorrect (it expects underscores
after each digit); replace it with a pattern that matches versioned report
files, e.g. use "report.*.*.*.*.json" to match any dotted segments or
"report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json" to restrict to numeric segments,
updating the existing line containing "report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json".
packages/facilitator/src/lib/chains/0g.ts (1)

13-16: Use computed keys to avoid network-id drift.

The mapping duplicates literal network IDs that already exist as constants. Prefer computed keys so a future chain-id change can’t desync this object.

♻️ Proposed refactor
 export const ZERO_GRAVITY_CHAIN_MAPPING = {
-  "eip155:16661": zeroGMainnet,
-  "eip155:16602": zeroGTestnet,
+  [zeroGMainnetNetworkId]: zeroGMainnet,
+  [zeroGTestnetNetworkId]: zeroGTestnet,
 } satisfies Record<ZeroGChain, Chain>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/facilitator/src/lib/chains/0g.ts` around lines 13 - 16, The mapping
ZERO_GRAVITY_CHAIN_MAPPING uses hard-coded network ID strings; change the object
keys to computed keys that derive from the canonical ZeroG chain id
constants/enum instead of literal strings so they can’t drift (e.g., replace
"eip155:16661" and "eip155:16602" with computed property keys built from the
existing ZeroGChain/chain id constants such as [`eip155:${ZeroGChain.MAINNET}`]
and [`eip155:${ZeroGChain.TESTNET}`] or the corresponding numeric constants,
while keeping the values zeroGMainnet and zeroGTestnet and the satisfies
Record<ZeroGChain, Chain> assertion).
packages/facilitator/wrangler.jsonc (1)

7-10: Consider making sampling rate environment-specific.

head_sampling_rate: 0.1 is fine to start, but making it env-dependent (dev/staging/prod) helps control telemetry cost as traffic grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/facilitator/wrangler.jsonc` around lines 7 - 10, The fixed value for
observability.head_sampling_rate should be made environment-specific: update the
configuration around the "observability" object to read a per-environment
override (e.g., an env var like OBSERVABILITY_HEAD_SAMPLING_RATE or
per-environment sections used by wrangler) and fall back to 0.1 if not provided;
change the "head_sampling_rate" key to reference that env/config value so
dev/staging/prod can each set lower/higher sampling without editing the file
directly (look for the "observability" object and the "head_sampling_rate" key
to implement this).
packages/facilitator/src/env.ts (1)

1-8: Consider adding an explicit reference to CloudflareBindings for defensive clarity.

Env currently relies on TypeScript's default .d.ts file discovery, which works fine with the current tsconfig. However, if the generated declaration is ever excluded in a future CI/package context, the type would be missing. Adding an explicit /// <reference> comment makes the dependency unambiguous.

♻️ Optional explicit reference
+/// <reference path="../worker-configuration.d.ts" />
 import type { X402FacilitatorClientVariables } from "./middlewares/x402-facilitator-client";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/facilitator/src/env.ts` around lines 1 - 8, Add an explicit
triple-slash reference to the Cloudflare types so the Env interface does not
implicitly depend on ambient discovery: add a top-level reference comment that
points to the Cloudflare types (so CloudflareBindings is unambiguous) above the
existing imports; ensure the file still exports Variables and Env (referencing
the existing Variables and CloudflareBindings symbols) and that no other code
changes are required.
examples/server/package.json (1)

6-8: Pin @types/bun to a specific version instead of "latest".

Using a floating version in package.json can cause non-deterministic type behavior, especially if the lockfile is regenerated or removed. Even with lockfiles providing determinism, pinning directly in package.json is a best practice. The project appears to use workspace catalogs; consider adding an entry for @types/bun in the catalog or pinning to a fixed version (e.g., "1.3.12").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/server/package.json` around lines 6 - 8, Update the devDependency
entry for `@types/bun` in package.json to use a fixed, specific version (e.g.,
"1.3.12") instead of "latest" to avoid non-deterministic type changes; locate
the "devDependencies" object and change the "@types/bun" value accordingly or
add a pinned entry for `@types/bun` to the workspace catalog if you prefer central
management.
packages/facilitator/src/lib/x402/scheme.ts (1)

17-20: Remove stale commented registration paths.

Keeping registerV1(...) commented blocks here makes the intended registration contract ambiguous and invites drift during future edits.

Suggested cleanup
 export const registerZeroGExactScheme = (
   facilitator: x402Facilitator,
   chain: ZeroGChain,
   signer: FacilitatorEvmSigner
 ) => {
   facilitator.register(
     chain,
     new ExactEvmScheme(signer, { deployERC4337WithEIP6492: true })
   );
-  // facilitator.registerV1(
-  //   chain,
-  //   new ExactEvmScheme(signer, { deployERC4337WithEIP6492: true })
-  // );
 };
@@
 export const registerZeroGUptoScheme = (
   facilitator: x402Facilitator,
   chain: ZeroGChain,
   signer: FacilitatorEvmSigner
 ) => {
   facilitator.register(chain, new UptoEvmScheme(signer));
-  // facilitator.registerV1(chain, new UptoEvmScheme(signer));
 };

Also applies to: 29-29

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/facilitator/src/lib/x402/scheme.ts` around lines 17 - 20, Remove the
stale commented registration calls so the codebase doesn't retain ambiguous,
dead registration paths: delete the commented facilitator.registerV1(...) blocks
that reference ExactEvmScheme (including the variant with
deployERC4337WithEIP6492) at the indicated spots (the commented block around
registerV1/ExactEvmScheme and the second commented line at 29) so only the
active registration code remains; ensure no other commented registerV1 usages
are left in this file to avoid future drift.
packages/facilitator/src/middlewares/x402-facilitator-client.ts (1)

22-64: Cache the facilitator and signers outside the request handler to avoid per-request reconstruction.

The middleware re-initializes and re-registers the facilitator, clients, signers, and schemes for every incoming request. Since x402FacilitatorClient() is instantiated once during app setup, move the client and signer initialization outside the async callback so they're created once and reused across all requests.

export const x402FacilitatorClient = () => {
  const facilitator = new x402Facilitator();
  const signer = privateKeyToAccount(c.env.FACILITATOR_PRIVATE_KEY as Hex);
  // ... initialize clients and register schemes once ...
  
  return createMiddleware<Env>(async (c, next) => {
    // Reuse cached instances
    await next();
  });
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/facilitator/src/middlewares/x402-facilitator-client.ts` around lines
22 - 64, The middleware currently constructs x402Facilitator, signer and wallet
clients and re-registers schemes on every request; move the one-time
initialization out of the async request handler in x402FacilitatorClient so
these are created once and reused: instantiate x402Facilitator, call
privateKeyToAccount with FACILITATOR_PRIVATE_KEY, createZeroGWalletClient for
zeroGMainnetNetworkId and zeroGTestnetNetworkId, createZeroGFacilitatorSigner
for each client, and call registerZeroGExactScheme/registerZeroGUptoScheme
during middleware creation (before calling createMiddleware), then inside the
returned async (c, next) handler attach the ready facilitator/signers to the
request context (for example c.state or c.locals) and simply await next(); this
prevents per-request reconstruction while preserving access to the initialized
instances.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/client/index.ts`:
- Around line 7-9: The code currently force-casts process.env.EVM_PRIVATE_KEY
into `0x${string}` and passes it directly to privateKeyToAccount, which will
crash with an unclear error if the env var is missing/invalid; add an explicit
guard before calling privateKeyToAccount: read process.env.EVM_PRIVATE_KEY into
a variable, verify it exists (and optionally normalize/validate that it starts
with "0x" and has the expected length for a 32-byte key), and throw a clear
Error (or exit) with a helpful message if it's absent/invalid; then pass the
validated/normalized value to privateKeyToAccount to create signer.

In `@examples/client/package.json`:
- Around line 9-10: Update the peer dependency declaration so the package
accepts TypeScript 6 to match the workspace catalog: change the
peerDependencies.typescript entry in examples/client/package.json from "^5" to a
range that includes 6 (for example "^6" or ">=6.0.2"), ensuring the peer
dependency range aligns with the workspace TypeScript version.

In `@examples/server/index.ts`:
- Around line 8-12: Replace the hardcoded unsafe example values with validated
runtime configuration: read and validate the on-chain recipient into the
variable evmAddress (used as payTo) from env/config and ensure it is a
well-formed EVM address before use, and construct HTTPFacilitatorClient using a
secure, validated facilitator URL (prefer https and validate the origin) pulled
from runtime config; add explicit validation/error handling around evmAddress
and the URL (e.g., in initialization or a validateConfig helper) so the example
fails fast with a clear message if payTo or facilitator URL are missing or
invalid.
- Around line 18-23: The conversion to token units using Math.floor(amount *
1_000_000).toString() does not validate the input; guard the input `amount` in
the async handler (the arrow function taking (amount: number, _network: string))
by checking Number.isFinite(amount) and amount > 0 (and optionally
!Number.isNaN(amount)); if the check fails return an appropriate error/throw or
a rejected response instead of computing `tokenAmount`, ensuring you do not
produce invalid token strings.

In `@packages/facilitator/.env.example`:
- Around line 2-3: Rename the non-standard env vars that start with a digit
(0G_MAINNET_RPC_URL, 0G_TESTNET_RPC_URL) to identifiers that start with a letter
or underscore (for example OG_MAINNET_RPC_URL and OG_TESTNET_RPC_URL) by
updating .env.example and every code reference; update the TypeScript
declaration worker-configuration.d.ts and the middleware
x402-facilitator-client.ts to use the new names, and replace bracket-access
patterns like c.env["0G_MAINNET_RPC_URL"] with the new identifier access
(c.env.OG_MAINNET_RPC_URL or c.env["OG_MAINNET_RPC_URL"]) so all usages compile
and runtime env lookups still work.

In `@packages/facilitator/src/handlers/settle.ts`:
- Line 42: Remove the direct logging of the full settleResponse in
handlers/settle.ts (the console.log({ settleResponse }) call) because it can
leak payment/signature metadata; either delete that line or replace it with a
sanitized log that only includes safe fields (e.g., status, transactionId) or
use a debug-level logger (e.g., processLogger.debug) after stripping sensitive
fields before logging. Ensure you reference the settleResponse variable in your
change so the handler (settle.ts) no longer emits raw settlement objects on the
request path.
- Around line 20-26: The handler currently returns validation errors with HTTP
200 by calling c.json(...) on failures; update both failure branches (the check
of resolvedPaymentPayload.success and the check of
resolvedPaymentRequirements.success after parsePaymentRequirements) to return a
4xx status (e.g., use c.status(400).json(...) or c.status(422).json(...))
instead of c.json(...). Ensure you change both the resolvedPaymentPayload
rejection path and the resolvedPaymentRequirements rejection path so clients
receive an appropriate client-error status code.

In `@packages/facilitator/src/handlers/supported.ts`:
- Around line 11-13: Replace the raw console.error(err) in the catch block
inside the payment-path handler (supported.ts) with structured, sanitized
logging: capture only safe scalar fields (e.g., err.message, err.code) and
relevant safe context (e.g., paymentId or requestId from the handler) and omit
full error objects/stacks; ideally call a dedicated helper like
sanitizeError(err) before logging and use the module logger (e.g., logger.error)
with a descriptive message and the sanitized fields rather than printing the raw
err.

In `@packages/facilitator/src/handlers/verify.ts`:
- Around line 20-26: The handlers currently return validation errors with a 200
status because they call c.json(...) directly; update both validation failure
paths that check resolvedPaymentPayload.success and
resolvedPaymentRequirements.success to return a client error status (e.g., use
c.status(400).json(...) or c.status(422).json(...)) instead of c.json(...).
Specifically, change the return behavior where parsePaymentRequirements(...) and
the payment payload resolution (resolvedPaymentPayload) are inspected so the
response uses c.status(400).json(resolvedPaymentPayload.error) and
c.status(400).json(resolvedPaymentRequirements.error) (or 422 if you prefer) to
signal a 4xx validation failure to clients.

In `@packages/facilitator/src/middlewares/x402-facilitator-client.ts`:
- Around line 26-36: The middleware initializes clients with unvalidated env
vars causing cryptic runtime errors; before calling privateKeyToAccount and
createZeroGWalletClient, validate that process/env FACILITATOR_PRIVATE_KEY
exists and matches the expected hex pattern/length (and normalize prefix if
needed), wrap the privateKeyToAccount call in a try/catch to rethrow a clear
error referencing FACILITATOR_PRIVATE_KEY and privateKeyToAccount, and validate
0G_MAINNET_RPC_URL and 0G_TESTNET_RPC_URL by parsing them with the URL
constructor (or simple regex) and throw clear errors naming the offending env
var; perform these checks prior to creating mainnetClient/testnetClient
(references: privateKeyToAccount, createZeroGWalletClient,
FACILITATOR_PRIVATE_KEY, 0G_MAINNET_RPC_URL, 0G_TESTNET_RPC_URL,
zeroGMainnetNetworkId, zeroGTestnetNetworkId).

---

Nitpick comments:
In `@examples/client/.gitignore`:
- Line 16: The glob pattern "report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json" is
incorrect (it expects underscores after each digit); replace it with a pattern
that matches versioned report files, e.g. use "report.*.*.*.*.json" to match any
dotted segments or "report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json" to restrict to
numeric segments, updating the existing line containing
"report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json".

In `@examples/client/package.json`:
- Line 7: The dependency entry for "@types/bun" in package.json is pinned to
"latest", which breaks reproducible builds; update the dependency value from
"latest" to a specific version or a constrained semver range (e.g., "0.x" or
"0.1.2") in the dependencies/devDependencies block so the package.json (and any
workspace reference) uses a fixed, tested version instead of "latest".

In `@examples/server/package.json`:
- Around line 6-8: Update the devDependency entry for `@types/bun` in package.json
to use a fixed, specific version (e.g., "1.3.12") instead of "latest" to avoid
non-deterministic type changes; locate the "devDependencies" object and change
the "@types/bun" value accordingly or add a pinned entry for `@types/bun` to the
workspace catalog if you prefer central management.

In `@packages/facilitator/src/env.ts`:
- Around line 1-8: Add an explicit triple-slash reference to the Cloudflare
types so the Env interface does not implicitly depend on ambient discovery: add
a top-level reference comment that points to the Cloudflare types (so
CloudflareBindings is unambiguous) above the existing imports; ensure the file
still exports Variables and Env (referencing the existing Variables and
CloudflareBindings symbols) and that no other code changes are required.

In `@packages/facilitator/src/lib/chains/0g.ts`:
- Around line 13-16: The mapping ZERO_GRAVITY_CHAIN_MAPPING uses hard-coded
network ID strings; change the object keys to computed keys that derive from the
canonical ZeroG chain id constants/enum instead of literal strings so they can’t
drift (e.g., replace "eip155:16661" and "eip155:16602" with computed property
keys built from the existing ZeroGChain/chain id constants such as
[`eip155:${ZeroGChain.MAINNET}`] and [`eip155:${ZeroGChain.TESTNET}`] or the
corresponding numeric constants, while keeping the values zeroGMainnet and
zeroGTestnet and the satisfies Record<ZeroGChain, Chain> assertion).

In `@packages/facilitator/src/lib/x402/scheme.ts`:
- Around line 17-20: Remove the stale commented registration calls so the
codebase doesn't retain ambiguous, dead registration paths: delete the commented
facilitator.registerV1(...) blocks that reference ExactEvmScheme (including the
variant with deployERC4337WithEIP6492) at the indicated spots (the commented
block around registerV1/ExactEvmScheme and the second commented line at 29) so
only the active registration code remains; ensure no other commented registerV1
usages are left in this file to avoid future drift.

In `@packages/facilitator/src/middlewares/x402-facilitator-client.ts`:
- Around line 22-64: The middleware currently constructs x402Facilitator, signer
and wallet clients and re-registers schemes on every request; move the one-time
initialization out of the async request handler in x402FacilitatorClient so
these are created once and reused: instantiate x402Facilitator, call
privateKeyToAccount with FACILITATOR_PRIVATE_KEY, createZeroGWalletClient for
zeroGMainnetNetworkId and zeroGTestnetNetworkId, createZeroGFacilitatorSigner
for each client, and call registerZeroGExactScheme/registerZeroGUptoScheme
during middleware creation (before calling createMiddleware), then inside the
returned async (c, next) handler attach the ready facilitator/signers to the
request context (for example c.state or c.locals) and simply await next(); this
prevents per-request reconstruction while preserving access to the initialized
instances.

In `@packages/facilitator/wrangler.jsonc`:
- Around line 7-10: The fixed value for observability.head_sampling_rate should
be made environment-specific: update the configuration around the
"observability" object to read a per-environment override (e.g., an env var like
OBSERVABILITY_HEAD_SAMPLING_RATE or per-environment sections used by wrangler)
and fall back to 0.1 if not provided; change the "head_sampling_rate" key to
reference that env/config value so dev/staging/prod can each set lower/higher
sampling without editing the file directly (look for the "observability" object
and the "head_sampling_rate" key to implement this).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 22970419-ab89-46ef-bf8d-f9bef90e7176

📥 Commits

Reviewing files that changed from the base of the PR and between 0d951cb and 1445d5a.

⛔ Files ignored due to path filters (3)
  • bun.lock is excluded by !**/*.lock
  • examples/client/bun.lock is excluded by !**/*.lock
  • examples/server/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • examples/client/.env.example
  • examples/client/.gitignore
  • examples/client/index.ts
  • examples/client/package.json
  • examples/client/tsconfig.json
  • examples/server/.gitignore
  • examples/server/index.ts
  • examples/server/package.json
  • examples/server/tsconfig.json
  • package.json
  • packages/facilitator/.env.example
  • packages/facilitator/.gitignore
  • packages/facilitator/biome.json
  • packages/facilitator/package.json
  • packages/facilitator/src/env.ts
  • packages/facilitator/src/errors.ts
  • packages/facilitator/src/handlers/settle.ts
  • packages/facilitator/src/handlers/supported.ts
  • packages/facilitator/src/handlers/verify.ts
  • packages/facilitator/src/index.ts
  • packages/facilitator/src/lib/chains/0g.ts
  • packages/facilitator/src/lib/x402/scheme.ts
  • packages/facilitator/src/lib/x402/signer.ts
  • packages/facilitator/src/middlewares/x402-facilitator-client.ts
  • packages/facilitator/tsconfig.json
  • packages/facilitator/vitest.config.ts
  • packages/facilitator/worker-configuration.d.ts
  • packages/facilitator/wrangler.jsonc

Comment thread examples/client/index.ts
Comment thread examples/client/package.json
Comment thread examples/server/index.ts
Comment thread examples/server/index.ts
Comment thread packages/facilitator/.env.example Outdated
Comment thread packages/facilitator/src/handlers/settle.ts Outdated
Comment thread packages/facilitator/src/handlers/settle.ts Outdated
Comment thread packages/facilitator/src/handlers/supported.ts
Comment thread packages/facilitator/src/handlers/verify.ts Outdated
Comment thread packages/facilitator/src/middlewares/x402-facilitator-client.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/facilitator/src/middlewares/x402-facilitator-client.ts (1)

26-36: ⚠️ Potential issue | 🟠 Major

Add runtime env validation before constructing signer/clients.

as Hex is compile-time only; invalid or missing bindings can still fail at runtime with unclear errors in the request path. Please validate FACILITATOR_PRIVATE_KEY, ZEROG_MAINNET_RPC_URL, and ZEROG_TESTNET_RPC_URL before calling privateKeyToAccount / createZeroGWalletClient.

Proposed hardening
 export const x402FacilitatorClient = () =>
   createMiddleware<Env>(async (c, next) => {
     const facilitator = new x402Facilitator();

-    const signer = privateKeyToAccount(c.env.FACILITATOR_PRIVATE_KEY as Hex);
+    const privateKey = c.env.FACILITATOR_PRIVATE_KEY;
+    if (!/^0x[a-fA-F0-9]{64}$/.test(privateKey)) {
+      throw new Error("Invalid FACILITATOR_PRIVATE_KEY: expected 0x + 64 hex chars");
+    }
+    try {
+      privateKeyToAccount(privateKey as Hex);
+    } catch {
+      throw new Error("Invalid FACILITATOR_PRIVATE_KEY for privateKeyToAccount");
+    }
+    for (const [name, value] of [
+      ["ZEROG_MAINNET_RPC_URL", c.env.ZEROG_MAINNET_RPC_URL],
+      ["ZEROG_TESTNET_RPC_URL", c.env.ZEROG_TESTNET_RPC_URL],
+    ] as const) {
+      try {
+        new URL(value);
+      } catch {
+        throw new Error(`Invalid ${name}: must be a valid URL`);
+      }
+    }
+    const signer = privateKeyToAccount(privateKey as Hex);

     const mainnetClient = createZeroGWalletClient(
       zeroGMainnetNetworkId,
       c.env.ZEROG_MAINNET_RPC_URL,
       signer
     );
According to the current viem docs, what runtime validation/error behavior does privateKeyToAccount apply to malformed hex private keys?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/facilitator/src/middlewares/x402-facilitator-client.ts` around lines
26 - 36, Validate the FACILITATOR_PRIVATE_KEY, ZEROG_MAINNET_RPC_URL, and
ZEROG_TESTNET_RPC_URL environment values at runtime before calling
privateKeyToAccount or createZeroGWalletClient: check that
FACILITATOR_PRIVATE_KEY is present and a valid hex string (and reject/throw a
clear error if not), and that ZEROG_MAINNET_RPC_URL and ZEROG_TESTNET_RPC_URL
are non-empty valid URLs; perform these checks in the middleware initialization
(before calling privateKeyToAccount and createZeroGWalletClient) and surface a
descriptive error/log if validation fails so signer/client construction won’t
produce unclear runtime failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/facilitator/src/middlewares/x402-facilitator-client.ts`:
- Around line 26-36: Validate the FACILITATOR_PRIVATE_KEY,
ZEROG_MAINNET_RPC_URL, and ZEROG_TESTNET_RPC_URL environment values at runtime
before calling privateKeyToAccount or createZeroGWalletClient: check that
FACILITATOR_PRIVATE_KEY is present and a valid hex string (and reject/throw a
clear error if not), and that ZEROG_MAINNET_RPC_URL and ZEROG_TESTNET_RPC_URL
are non-empty valid URLs; perform these checks in the middleware initialization
(before calling privateKeyToAccount and createZeroGWalletClient) and surface a
descriptive error/log if validation fails so signer/client construction won’t
produce unclear runtime failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e4b1a523-a838-417b-ad36-eead21dafa1c

📥 Commits

Reviewing files that changed from the base of the PR and between 1445d5a and dec1614.

📒 Files selected for processing (6)
  • packages/facilitator/.env.example
  • packages/facilitator/src/handlers/settle.ts
  • packages/facilitator/src/handlers/supported.ts
  • packages/facilitator/src/handlers/verify.ts
  • packages/facilitator/src/middlewares/x402-facilitator-client.ts
  • packages/facilitator/worker-configuration.d.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/facilitator/.env.example
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/facilitator/src/handlers/supported.ts
  • packages/facilitator/src/handlers/verify.ts

@lucky-ivanius lucky-ivanius merged commit 1da936a into main Apr 16, 2026
1 check passed
@lucky-ivanius lucky-ivanius deleted the facilitator branch April 16, 2026 22:24
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