Skip to content

fix: honour the response cache on paid GETs - #17

Merged
VickyXAI merged 1 commit into
mainfrom
fix/paid-get-cache
Aug 4, 2026
Merged

fix: honour the response cache on paid GETs#17
VickyXAI merged 1 commit into
mainfrom
fix/paid-get-cache

Conversation

@VickyXAI

@VickyXAI VickyXAI commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #10.

What

doGetWithPayment never touched bc.cache. doGet reads it on the way in (base_client.go:305) and writes it on the way out (:338); the paid path did neither.

So WithCache(true) was a silent no-op on exactly the endpoints that cost money. cache.go:16 gives /v1/pm/ a 30-minute TTL and prediction_market.go:22 routes through doGetWithPayment — every repeat call re-signed and re-paid real USDC while the caller believed caching was on.

This consults the cache before issuing the request and populates it on both the free-200 branch and the post-payment branch.

The part that isn't obvious

The cache keys on (endpoint, body), and this path carries a query map that doGet doesn't have. Passing nil for the body would make every query on an endpoint share one entry — ?q=bitcoin would serve ?q=ethereum's response. That is strictly worse than no caching, so cacheQueryBody folds the query into the key.

Endpoints without a configured TTL are untouched. ttlFor returns 0 for /v1/zerox/, /v1/defillama/ and /v1/surf/, so those still hit the network every time rather than serving stale swap quotes. Only /v1/pm/ and /v1/search have TTLs today; whether the others should is a product call, not this PR's.

Impact

Repeat prediction-market queries stop costing money. This became live in v0.19.2, which opened these endpoints to Solana clients for the first time (#8, #9).

Testing

All offline, verified with egress blocked:

  • TestPaidGetServesRepeatFromCache — second identical paid GET makes zero further requests, exactly one payment, GetSpending() reports one call at $0.001.
  • TestPaidGetCacheKeyIncludesQuery — two queries on one endpoint stay distinct and each pays once.
  • TestPaidGetWithoutCacheStillWorks — a client with no cache is unaffected.
  • TestPaidGetNoTTLEndpointNotCached/v1/zerox/ still hits the network every time.

Mutation-checked: reverting the cache read fails only the repeat test; keying on the endpoint alone fails only the collision test.

Full suite green with -race -count=2 -shuffle=on and no network. go vet clean. sync-brand-numbers.mjs --check passes.

VERSION/CHANGELOG untouched — left for the release.

Provenance

Surfaced by the adversarial pass during /review of #9.

doGetWithPayment never touched bc.cache. doGet reads it on the way in and
writes it on the way out; the paid path did neither, so WithCache(true)
was a silent no-op on exactly the endpoints that cost money. cache.go
gives /v1/pm/ a 30-minute TTL and prediction_market.go routes through
doGetWithPayment, so every repeat call re-signed and re-paid USDC while
the caller believed caching was on.

Consult the cache before issuing the request and populate it on both the
free-200 and the post-payment branch.

The cache keys on (endpoint, body), and this path has a query map doGet
does not. Passing nil would make every query on an endpoint share one
entry and serve another query's response — worse than not caching at
all — so cacheQueryBody folds the query into the key.

Endpoints without a configured TTL are untouched: ttlFor returns 0 for
/v1/zerox/, /v1/defillama/ and /v1/surf/, so those still hit the network
every time rather than serving stale quotes.

Tests, all offline: a repeat paid GET is served from cache with zero
further requests and exactly one payment; two queries on one endpoint
stay distinct; a client with no cache is unaffected; a TTL-less endpoint
is not cached. Mutation-checked — reverting the cache read fails only the
repeat test, keying on the endpoint alone fails only the collision test.

Fixes #10.
@VickyXAI
VickyXAI merged commit 26d2809 into main Aug 4, 2026
1 check passed
@VickyXAI
VickyXAI deleted the fix/paid-get-cache branch August 4, 2026 04:39
VickyXAI pushed a commit that referenced this pull request Aug 4, 2026
Ships the paid-GET cache fix from #17 (fixes #10). doGetWithPayment never
consulted bc.cache, so WithCache(true) was a silent no-op on the endpoints
that cost money — /v1/pm/ has a 30-minute TTL yet every repeat call
re-signed and re-paid.

Also on main since 0.19.3, test-only: #18 pins that a paid GET with a
server-provided blockhash makes zero client RPC calls.
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.

doGetWithPayment bypasses the response cache — every repeat paid GET re-pays USDC

1 participant