Skip to content

feat(strategy): x402-payable external data sources with per-call and daily spend caps - #1533

Open
SELAT-DEV wants to merge 3 commits into
NoFxAiOS:devfrom
SELAT-DEV:feature/x402-external-data-sources
Open

SELAT-DEV wants to merge 3 commits into
NoFxAiOS:devfrom
SELAT-DEV:feature/x402-external-data-sources

Conversation

@SELAT-DEV

Copy link
Copy Markdown

Summary

  • Problem: user-defined external_data_sources only support static headers, so x402-payable data APIs fail with an unhandled 402 — even though the x402 payment client and wallet key already exist for claw402 inference ([FEATURE] x402-payable external data sources (pay per call with the existing wallet, no API keys) #1532). The feature was also not wired into the trading loop, so configured sources never reached the AI context or the cost dashboard.
  • What changed: sources can opt in with payment: "x402" + both max_usd_per_call and max_usd_per_day. A 402 offer is validated before signing (scheme exact/upto, Base eip155:8453 only, authorized amount from accepts[0] ≤ per-call cap, daily budget not exhausted), then paid via the existing payment.DoX402Request with the trader's wallet key. Settled spend is logged per call, counted against the per-source UTC daily budget, and recorded into ai_charges (model = source name, provider = "x402-data") so data spend shows next to inference spend. External data is fetched in buildTradingContext and rendered into the AI context via a deterministic formatter section.
  • What did NOT change (scope boundary): sources without payment keep exact current behavior (regression-tested — a 402 stays an error, nothing is ever paid). No new dependencies. No Strategy Studio form UI (types round-trip only). Non-Base networks and other schemes are refused, not supported. Daily counter is in-memory (resets on restart) — persisting it (seeding from ai_charges) is offered as a follow-up.

Change Type

  • Feature

Scope

  • Trading engine / strategies
  • Web UI / frontend (types only)

Linked Issues

Testing

What you verified and how:

  • go build ./... passes
  • go test ./... passes (all packages; 9 new tests in kernel/engine_external_x402_test.go following the mcp/payment/x402_test.go httptest patterns)
  • Manual testing done (describe below)

New tests cover: happy path (402 → cap check → sign → paid retry → JSON in context, charge queued, daily counter incremented, drain-once semantics); per-call cap refusal with zero payment attempts; daily-budget exhaustion on the second call; UTC day-window rollover; both-caps-required validation; missing-wallet-key error; upto accepted with authorized amount as the worst-case bound; empty scheme defaulting to exact; unknown scheme refused; non-Base network refused; amount parsing (decimal + 0x-hex atomic units); unpaid-source 402 regression guard; deterministic sorted formatter output.

Manual: gofmt clean on touched files, go vet ./kernel/... clean, cd web && npm run build passes.

Security Impact

  • Secrets/keys handling changed? No — reuses the same wallet key already passed to NewStrategyEngine for claw402; parsed once, never logged.
  • New/changed API endpoints? No
  • User input validation affected? Yes (hardened) — paid sources go through the same security.ValidateURL + SafeHTTPClient as unpaid ones, including the paid retry; offers are validated (scheme/network/amount/caps) before anything is signed; both caps are mandatory, fail-closed.

Compatibility

  • Backward compatible? Yes — all new fields optional; unset payment is byte-for-byte current behavior.
  • Config/env changes? No new env. Three new optional JSON fields on external_data_sources entries.
  • Migration needed? No

Follow-ups I'm happy to pick up if wanted: persisted daily counter seeded from ai_charges; a Strategy Studio form field for the new options; a README example config.

🤖 Generated with Claude Code

@cla-assistant

cla-assistant Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

SELAT-DEV and others added 3 commits August 28, 2026 11:37
- Add optional `payment` ("" | "x402"), `max_usd_per_call` and
  `max_usd_per_day` to ExternalDataSource; all default to unset,
  existing configs are unaffected
- Mirror the optional fields in web/src/types/strategy.ts so Strategy
  Studio round-trips the config without stripping them

No engine behavior changes in this commit.

Refs NoFxAiOS#1532

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
External data sources can only send static headers today, so any
x402-payable API fails with an unhandled 402 even though the payment
client and wallet key already exist for claw402 inference.

- On 402 from a source with payment="x402", sign and retry via the
  existing payment.DoX402Request using the trader's wallet key
- Enforce max_usd_per_call against the authorized amount from
  accepts[0] before signing — under "upto" that amount is the worst
  case, so it remains the right bound; refuse above cap, never pay
  silently
- Require max_usd_per_day and enforce it client-side per source (UTC
  day window): polling via refresh_secs makes a per-call cap alone
  unbounded. Counter is in-memory (resets on restart; persisted
  counter offered as follow-up)
- Accept "exact" and "upto" on Base (eip155:8453) only; refuse other
  schemes and networks
- Keep SSRF URL validation and SafeHTTPClient on the paid path
- Queue settled payments (source name + USD) for cost tracking and
  render fetched external data into the AI context via a new
  deterministic formatter section

Sources without payment set keep exact current behavior
(regression-tested).

Refs NoFxAiOS#1532

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fetch configured external_data_sources in buildTradingContext and
  attach the result to the AI context
- Drain settled x402 data payments into ai_charges via RecordWithCost
  (model = source name, provider = "x402-data") so data spend appears
  in the cost dashboard next to inference spend

Refs NoFxAiOS#1532

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SELAT-DEV
SELAT-DEV force-pushed the feature/x402-external-data-sources branch from 5b7e08d to d280a64 Compare August 28, 2026 18:48
@SELAT-DEV

Copy link
Copy Markdown
Author

Rebased onto current dev (d280a64a) — conflicts resolved.

The only conflict was in kernel/engine.go: the recent vergex migration renamed vergex.SignalRankItem → vergex.DirectionChangeItem where this PR adds its fields to StrategyEngine. Resolved by keeping the new type name alongside the PR's additions; no behavior changes.

Re-verified on the rebased branch:

  • go build ./... and go test ./... pass (including the 9 new tests in kernel/engine_external_x402_test.go)
  • go vet ./kernel/... clean
  • cd web && npm run build passes

Ready for review.

This branch has not been deployed

No deployments
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.

[FEATURE] x402-payable external data sources (pay per call with the existing wallet, no API keys)

1 participant