Repository navigation
fix(x402): validate business params before issuing the payment challenge - #18
Merged
Merged
Conversation
OKX.AI delisted CLV Scout because both services "only prompt for missing
or incorrect parameters after the buyer completes the payment signature
and deduction" β review requires validation to complete before the x402
challenge is returned.
Root cause: okxPayGate (the SDK's paymentMiddleware) knows nothing about
our business params, so it issued a challenge, then verified and settled
the replay, and only then did gradeHandler/auditHandler parse the body β
answering an invalid call with HTTP 200 and a usage note. The buyer paid
for an error message.
Fix: api/validate.ts::paramPreflight, mounted ahead of okxPayGate.
1. unpaid + param-less GET -> pass through to the gate (the marketplace
reachability probe / `onchainos payment quote` must keep receiving a
well-formed PAYMENT-REQUIRED challenge, or the listing reads as
endpoint_unreachable β see d4bb5fa).
2. invalid params, paid or not -> 400 here. Unpaid, no challenge is ever
issued. Paid, the PAYMENT-SIGNATURE is never verified and never
settled, so a buyer holding a challenge from case 1 is NOT charged.
3. valid params -> next(), payment proceeds exactly as before.
The preflight parses with the SAME schemas api/routes.ts uses (moved into
api/validate.ts), so a request can never clear preflight and then fail in
a handler that has already been paid for. Handlers now answer a bad body
with 400 + the copyable example instead of 200; that path is defence in
depth, unreachable through the mounted stack.
Also: `bets` sent as a JSON string on a GET is now parsed, so paid GET
/api/audit works the same as POST.
Tests: test/prepay-validation.test.ts (22 cases) pins all three cases plus
the deduction guarantee β after a rejected paid call the authorization
nonce is still unspent, so the very same signature still buys a real
grade. Suites that probed the challenge with an empty `{}` body now probe
with valid params, and the two tests that asserted the old behaviour
("402 before validation", "200 usage response for a paid-but-malformed
body") are inverted. 155 tests green.
Stage 4 probed the paid routes with `-d '{}'` and asserted 402 β the old
contract. Under the new preflight a param-less POST is answered 400 by
design, so the smoke stage failed on its own success criterion.
- grade/audit challenge probes now send VALID params to earn the 402.
- Two new steps assert the listing rule itself: an invalid call returns
400 and carries NO PAYMENT-REQUIRED response header. The check greps
the header line (`^payment-required:`) rather than the string, because
the CORS Access-Control-Expose-Headers line also names that header.
- The bare-GET probe assertion is unchanged: discovery still gets 402 +
PAYMENT-REQUIRED, which is what the marketplace validator reads.
Verified locally against the real entrypoint: 402/402/400/400/402/200.
Owner
Author
|
π This PR is included in version 1.0.7 π The release is available on GitHub release Your semantic-release bot π¦π |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the OKX.AI delisting of agent #6150 (2026-08-20): both services CLV Grade and CLV Audit "only prompt for missing or incorrect parameters after the buyer completes the payment signature and deduction."
Root cause
okxPayGate(the SDK'spaymentMiddleware) knows nothing about our business params. It issued the challenge, verified thePAYMENT-SIGNATURE, and settled β and only then did the handlers parse the body, answering an invalid call with HTTP 200 and a usage note. The buyer paid for an error message.Fix β
api/validate.ts::paramPreflight, mounted ahead ofokxPayGateonchainos payment quote)next(), payment proceeds unchangedSchemas moved out of
api/routes.tsintoapi/validate.ts, so a request can never clear preflight and then fail inside a handler that has already been paid for.betssent as a JSON string on a GET is now parsed, so paidGET /api/auditmatches POST.Verification
test/prepay-validation.test.ts(22 cases) pins all three cases plus the deduction guarantee: after a rejected paid call the same signature still buys a real grade, proving nothing was settled.onchainos agent x402-check --endpoint β¦ [--body β¦]βvalid: true.Behaviour changes
Two tests asserted the rejected contract by name and are inverted β
"a malformed body still gets 402 before validation"and"returns a 200 usage response for a paid-but-malformed body". Suites that probed the challenge with an empty{}body now probe with valid params.