Skip to content

Facilator Improvements#307

Merged
fretchen merged 3 commits intomainfrom
facilator_eth
Feb 8, 2026
Merged

Facilator Improvements#307
fretchen merged 3 commits intomainfrom
facilator_eth

Conversation

@fretchen
Copy link
Owner

@fretchen fretchen commented Feb 8, 2026

No description provided.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds x402 fee disclosure metadata/receipts (“facilitatorFees”, per coinbase/x402#1016) to capability discovery and settlement responses in the x402_facilitator service, enabling fee-aware routing across facilitators.

Changes:

  • Advertise a new facilitatorFees extension from /supported for both the standard facilitator and the splitter facilitator.
  • Return an extensions.facilitatorFees receipt from settlement responses (standard settle + splitter settle) and plumb it through HTTP handlers.
  • Expand Vitest coverage for /supported, settle responses, and facilitator instance behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
x402_facilitator/x402_supported.ts Adds facilitatorFees extension to /supported response.
x402_facilitator/x402_splitter_supported.js Adds facilitatorFees extension to splitter /supported response.
x402_facilitator/x402_splitter_settle.js Adds facilitatorFees receipt to successful splitter settlement responses.
x402_facilitator/x402_splitter_facilitator.js Includes extensions in splitter /settle HTTP response when present.
x402_facilitator/x402_settle.ts Adds typed extensions.facilitatorFees receipt to standard settle results.
x402_facilitator/x402_fee.ts Minor logging formatting tweak in fee parsing.
x402_facilitator/x402_facilitator.ts Includes extensions in standard facilitator /settle HTTP response when present.
x402_facilitator/test/x402_supported.test.js Tests presence/shape of facilitatorFees in /supported.
x402_facilitator/test/x402_splitter_supported.test.js Updates splitter /supported tests to expect facilitatorFees.
x402_facilitator/test/x402_settle.test.js Asserts facilitatorFees receipt behavior in settle results.
x402_facilitator/test/x402_facilitator.test.ts Ensures handler includes extensions in HTTP response.
x402_facilitator/test/facilitator_instance.test.ts Adds private key validation + singleton tests; updates imports.

Comment on lines 94 to 102
const facilitatorFeesExtension: FacilitatorFeesExtension = {
name: "facilitatorFees",
version: "1",
model: "flat",
asset: "USDC",
flatFee: feeAmount.toString(),
decimals: 6,
networks: getSupportedNetworks(),
};
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

facilitatorFees.networks is populated from getSupportedNetworks(), which is a separate source of truth from the actual supported.kinds you just built. To avoid drift (e.g., if the facilitator ever filters networks), derive this from supported.kinds.map(k => k.network) so the extension always matches the response’s supported networks.

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +153
test("includes facilitatorFees extension for fee-aware routing (#1016)", () => {
process.env.FACILITATOR_WALLET_PRIVATE_KEY =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";

const capabilities = getSupportedCapabilities();

const feesExtension = capabilities.extensions.find((e) => e.name === "facilitatorFees");
expect(feesExtension).toBeDefined();
expect(feesExtension.version).toBe("1");
expect(feesExtension.model).toBe("flat");
expect(feesExtension.asset).toBe("USDC");
expect(feesExtension.flatFee).toBe("10000");
expect(feesExtension.decimals).toBe(6);
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This test asserts a hard-coded fee amount ("10000") but doesn’t set FACILITATOR_FEE_AMOUNT. If the test runner environment overrides that env var, this will fail. Set process.env.FACILITATOR_FEE_AMOUNT = "10000" (and restore it in afterEach) or mock getFeeAmount() for determinism.

Copilot uses AI. Check for mistakes.
Comment on lines 539 to 545
// Verify facilitatorFees extension (per x402 Fee Disclosure proposal #1016)
expect(result.extensions).toBeDefined();
expect(result.extensions.facilitatorFees).toBeDefined();
expect(result.extensions.facilitatorFees.info.version).toBe("1");
expect(result.extensions.facilitatorFees.info.facilitatorFeePaid).toBe("10000");
expect(result.extensions.facilitatorFees.info.asset).toBeDefined();
expect(result.extensions.facilitatorFees.info.model).toBe("flat");
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

These new assertions depend on the default FACILITATOR_FEE_AMOUNT being 10000 via getFeeAmount(). If FACILITATOR_FEE_AMOUNT is set differently in the environment running tests, this becomes flaky. Prefer explicitly setting process.env.FACILITATOR_FEE_AMOUNT in the test setup (or mocking getFeeAmount) before asserting facilitatorFeePaid.

Copilot uses AI. Check for mistakes.
info: {
version: "1",
facilitatorFeePaid: FIXED_FEE,
asset: usdcAddress,
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The facilitatorFees receipt here reports asset: usdcAddress (an address), while /supported for the same facilitator advertises asset: "USDC". Pick one asset identifier format and use it consistently across /supported and settlement receipts so clients can parse this reliably (CAIP-19 is already used elsewhere in kinds.extra.asset).

Suggested change
asset: usdcAddress,
asset: "USDC",

Copilot uses AI. Check for mistakes.
// Alias for backward compatibility
const SPLITTER_ABI = EIP3009SplitterV1ABI;
const getSplitterAddress = getEIP3009SplitterAddress;
const FIXED_FEE = process.env.FIXED_FEE || "10000"; // 0.01 USDC
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This introduces/relies on a FIXED_FEE environment variable to populate the fee receipt. The repository’s .env.example only documents FACILITATOR_FEE_AMOUNT, so it’s easy to misconfigure the splitter fee in deployments. Consider documenting FIXED_FEE (or renaming it to something more specific like SPLITTER_FIXED_FEE) and keeping it consistent across supported/verify/settle.

Copilot uses AI. Check for mistakes.
Comment on lines 123 to 135
test("extensions includes facilitatorFees for fee-aware routing (#1016)", () => {
const capabilities = getSplitterCapabilities();

expect(capabilities.extensions).toEqual([]);
expect(capabilities.extensions.length).toBeGreaterThan(0);
const feesExtension = capabilities.extensions.find((e) => e.name === "facilitatorFees");
expect(feesExtension).toBeDefined();
expect(feesExtension.version).toBe("1");
expect(feesExtension.model).toBe("flat");
expect(feesExtension.asset).toBe("USDC");
expect(feesExtension.flatFee).toBe("10000");
expect(feesExtension.decimals).toBe(6);
expect(feesExtension.collection).toBe("on_chain_split");
expect(feesExtension.networks).toContain("eip155:11155420");
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This test expects flatFee to be "10000" but doesn’t set process.env.FIXED_FEE. If the env var is set differently in the test environment, this will fail. Set process.env.FIXED_FEE = "10000" within the test (and restore in afterEach) to keep it deterministic.

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +236
return {
success: true,
payer: buyer,
transaction: hash,
network,
// x402 Fee Disclosure receipt (coinbase/x402#1016)
extensions: {
facilitatorFees: {
info: {
version: "1",
facilitatorFeePaid: FIXED_FEE,
asset: usdcAddress,
model: "flat",
},
},
},
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

New behavior: successful splitter settlements now include an extensions.facilitatorFees receipt. There are existing tests for settleSplitterPayment (see test/x402_splitter_settle.test.js), but none appear to assert this new response field. Adding an assertion for the extension (including facilitatorFeePaid, model, and asset) would prevent regressions.

Copilot uses AI. Check for mistakes.
info: {
version: "1",
facilitatorFeePaid: feeResult.success ? feeAmountStr : "0",
asset: chainConfig.USDC_ADDRESS,
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The facilitatorFees receipt uses asset: chainConfig.USDC_ADDRESS (a bare address), while /supported advertises asset: "USDC" for the same extension. This makes the extension harder to consume because clients can’t rely on a stable asset identifier format. Consider using the same asset identifier in both places (e.g., consistently "USDC", or consistently a CAIP-19 string like ${network}/erc20:${USDC_ADDRESS}).

Suggested change
asset: chainConfig.USDC_ADDRESS,
// Use a stable asset identifier consistent with /supported
asset: "USDC",

Copilot uses AI. Check for mistakes.
@fretchen fretchen merged commit 38473d8 into main Feb 8, 2026
4 checks passed
@fretchen fretchen deleted the facilator_eth branch February 8, 2026 15:31
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