Skip to content

feat(payments): add a USDC settlement rail for Circle's Arc network - #25

Open
anders94 wants to merge 1 commit into
radiustechsystems:mainfrom
anders94:arc
Open

anders94 wants to merge 1 commit into
radiustechsystems:mainfrom
anders94:arc

Conversation

@anders94

Copy link
Copy Markdown
Member

Circle runs a hosted x402 facilitator for Arc at https://api.circle.com/v1/facilitator/x402, so the rail needs no relayer, no gas wallet and no key — it verifies the EIP-3009 authorization, screens both addresses, pays the gas and broadcasts. Auth is an ordinary bearer token, which facilitator_headers already covers, and the base URL carries a path, which checkFacilitatorURL already permits and NewCallbackVerifier already trims. Rail is an opaque CAIP-2 string and the gate never contacts a chain, so this is config, tests and chart only: no production Go changes and the non-custodial invariant is untouched.

Values are taken from Circle's own /supported response. Note asset_name is "USDC" and not the "USD Coin" that Base and Polygon USDC use for the same field: a wrong value there yields an offer that validates and parses but can never be paid, because the client signs over a different EIP-712 domain and the facilitator's rejection names no field. The asset address is the ERC-20 view of native USDC at six decimals; the same funds are eighteen decimals natively, which is only a trap for an operator reading a balance over rpc_url, since the gate never calls it.

Arc gets its own single-rail test fixture rather than becoming a third rail in payGateTwoFacilitators, so the two-rail premise of TestOfferHeaderFitsDefaultProxyBuffers and the header budget in docs/nginx.md both stay intact.

The chart block stays commented: the sidecar runs readOnlyRootFilesystem with only a read-only ConfigMap mount, so the bbolt grant ledger has nowhere to live, and CIRCLE_API_KEY is not wired from a Secret. Both are injection policy changes.

Still unverified with a real credential: whether Circle additionally requires the Facilitator-Seller-Proof EIP-712 header on /settle, and whether it echoes network verbatim as eip155:5042 — callback.go compares that as an exact string, and a mismatch turns every settlement Ambiguous.

  Circle runs a hosted x402 facilitator for Arc at
  https://api.circle.com/v1/facilitator/x402, so the rail needs no relayer,
  no gas wallet and no key — it verifies the EIP-3009 authorization, screens
  both addresses, pays the gas and broadcasts. Auth is an ordinary bearer
  token, which facilitator_headers already covers, and the base URL carries a
  path, which checkFacilitatorURL already permits and NewCallbackVerifier
  already trims. Rail is an opaque CAIP-2 string and the gate never contacts a
  chain, so this is config, tests and chart only: no production Go changes and
  the non-custodial invariant is untouched.

  Values are taken from Circle's own /supported response. Note asset_name is
  "USDC" and not the "USD Coin" that Base and Polygon USDC use for the same
  field: a wrong value there yields an offer that validates and parses but can
  never be paid, because the client signs over a different EIP-712 domain and
  the facilitator's rejection names no field. The asset address is the ERC-20
  view of native USDC at six decimals; the same funds are eighteen decimals
  natively, which is only a trap for an operator reading a balance over
  rpc_url, since the gate never calls it.

  Arc gets its own single-rail test fixture rather than becoming a third rail
  in payGateTwoFacilitators, so the two-rail premise of
  TestOfferHeaderFitsDefaultProxyBuffers and the header budget in
  docs/nginx.md both stay intact.

  The chart block stays commented: the sidecar runs readOnlyRootFilesystem
  with only a read-only ConfigMap mount, so the bbolt grant ledger has nowhere
  to live, and CIRCLE_API_KEY is not wired from a Secret. Both are injection
  policy changes.

  Still unverified with a real credential: whether Circle additionally
  requires the Facilitator-Seller-Proof EIP-712 header on /settle, and whether
  it echoes network verbatim as eip155:5042 — callback.go compares that as an
  exact string, and a mismatch turns every settlement Ambiguous.
@anders94
anders94 requested a review from madars September 25, 2026 16:22
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