fix(x402): resolve missing SPL decimals from mint accounts - #324
EfeDurmaz16 wants to merge 7 commits into
Conversation
Carry forward the cross-SDK decimals work from solana-foundation#297 onto current main. Preserve explicit hints and fetch mint decimals when offers omit them. Co-authored-by: latent-9 <296084221+latent-9@users.noreply.github.com>
Handle the string address and tuple response used by SolanaRpc while preserving solana-py support. Exercise the built-in RPC through a mock HTTP transport and verify the encoded decimals byte.
|
| fun x402( | ||
| rpcBlockhashProvider: () -> ByteArray, | ||
| selection: ChallengeSelection = ChallengeSelection(), | ||
| mintDecimalsProvider: ((String) -> UByte)? = null, |
There was a problem hiding this comment.
Trailing lambda compatibility breaks
Adding mintDecimalsProvider as the final function-typed parameter breaks the public builder's existing trailing-lambda form. A consumer that previously wrote .x402 { fetchBlockhash() } had that lambda bound to rpcBlockhashProvider; it is now matched against (String) -> UByte and fails to compile. Preserve the old overload separately or move the new provider before the existing trailing-lambda parameter.
Knowledge Base Used: Kotlin SDK
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
The original main signature also ends in selection: ChallengeSelection = ChallengeSelection(), so .x402 { fetchBlockhash() } did not compile before this PR either. I verified that with the Kotlin compiler. The existing named and parenthesized call forms remain supported; 7dc3498 adds a behavioral regression test for both, including a successful payment retry. Full Kotlin suite: 318 tests passed. Could you re-evaluate this finding against the original signature?
There was a problem hiding this comment.
You're right — I withdraw this finding. With the original signature, selection was the last parameter and was not function-typed, so .x402 { fetchBlockhash() } did not compile before this PR. After the change, the new final parameter is function-typed, but rpcBlockhashProvider is still required, so that expression still cannot compile because it would not satisfy the required first argument. The supported forms covered by the regression test — .x402({ fetchBlockhash() }) and .x402(rpcBlockhashProvider = { fetchBlockhash() }) — remain compatible, including the successful retry behavior. No change is required for this comment.
| decimals_raw = req.get("decimals") | ||
| if not isinstance(decimals_raw, int) or isinstance(decimals_raw, bool): | ||
| decimals_raw = extra.get("decimals") |
There was a problem hiding this comment.
Malformed hint bypasses validation
When a challenge contains an invalid top-level decimals and a valid extra.decimals, these lines discard the invalid value before validation. For example, "decimals": "9" is silently ignored and the payment is signed using the nested hint instead of rejecting the malformed offer. Only an absent or null top-level value should fall back to extra.decimals.
| decimals_raw = req.get("decimals") | |
| if not isinstance(decimals_raw, int) or isinstance(decimals_raw, bool): | |
| decimals_raw = extra.get("decimals") | |
| decimals_raw = req.get("decimals") | |
| if decimals_raw is None: | |
| decimals_raw = extra.get("decimals") |
Knowledge Base Used:
There was a problem hiding this comment.
Fixed in 38aa832. Only an absent or null top-level value falls back now. Added malformed-hint cases with valid and absent nested hints, plus null fallback coverage. Full Python suite: 1,280 tests passed.
Only absent or null hints may fall back to extra.decimals. Cover malformed hints with and without a valid nested value, plus the null fallback.
|
Thanks for carrying this forward, @EfeDurmaz16 — and for the co-author credit. 🙏 I skimmed the carry-forward: Rust mint fetch, Kotlin wiring, Go/Swift strictness, and the Python mask removal are all preserved, and your follow-ups (built-in RPC support, Swift mock isolation, Kotlin trailing-lambda compat) address exactly the gaps I'd have hit next. Happy to help with any review fallout on the decimals path. I'll leave #297 open until this merges, then close it as superseded. |
|
Kael Pulse protocol-watch $0.05 Exact SVM — Solana L1/runtime/policy diffs since your cursor → https://kael-ecosystem-pulse.onrender.com/v1/pulse/watch?src=cash19 (402→PayAI); free teaser https://kael-ecosystem-pulse.onrender.com/v1/pulse/sample?src=cash19 |
When an x402 SPL offer omits
extra.decimals, clients now read the mint instead of assuming six decimals. Explicit hints remain range-checked, and native SOL avoids the lookup.Continues #297 by @latent-9, whose original cross-SDK implementation is carried forward onto current main with co-author credit. This PR also fixes the Python fallback to support the built-in
SolanaRpc, isolates Swift HTTP mocks so tests can run in parallel, and preserves Kotlin trailing-lambda callers.Validation: