We’re building a prediction game on Solana where every shot settles on Pyth, and we ended up doing
the account validation by hand rather than through the SDK. Sharing what we found, plus four
questions we couldn’t answer from the docs.
Why by hand. Our first version declared the price account as an Anchor Account<'info, PriceUpdateV2> with #[account] on the inlined struct. That compiles and looks right, and it
fails on-chain with:
Code
Obvious in hindsight — #[account] makes Anchor demand our program own the account — but the
error names the correct Pyth receiver as “Left”, which reads like the Pyth account is wrong rather
than our declaration. Posting it here mostly so it’s searchable for the next person.
The fix was UncheckedAccount plus explicit checks, which also let us drop the SDK dependency
entirely (the whole program is 288 lines, zero external crates):
Rust
Plus a staleness bound at seal (price must be < 60s old) and a publish-time bound at settle.
The settlement problem. Settlement is permissionless — anyone can settle any expired shot — so
whoever cranks it must not get to choose the outcome. v0 required
expiry <= publish_time <= expiry + 60, which bounds the cranker but still leaves a choice among
updates inside the window.
v1 removes the choice using prev_publish_time:
Code
Exactly one update in existence satisfies that — the first price published at or after expiry — so
there is nothing left to pick. We keep a relaxed fallback after an hour so a shot can’t be stranded
if nobody cranks it in time, and the account records which rule was used.
One field observation. On devnet the sponsored SOL/USD account
(7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE, shard 0) publishes in bursts roughly every 15–20
seconds and runs a few seconds behind the chain clock. Our program correctly refused to settle for
about a minute waiting for a publish time at or after expiry — good to know if you’re testing
timing-sensitive logic against devnet and wondering why nothing settles.
Questions:
Is prev_publish_time safe to rely on for a first-crossing proof? Can it ever be zero,
equal to publish_time, or otherwise non-monotonic — for example on a feed’s first update after
an outage, or across a shard?
For settling against a specific past timestamp, is the intended pattern to post the crossing
update from Hermes via post_price_update, rather than reading whatever the sponsored account
currently holds? Ours works both ways today, but we’d rather follow the intended path.
Is there a documented heartbeat / max staleness for sponsored feed accounts, and does it
differ between mainnet and devnet? We’re choosing a staleness bound and would rather derive it
from your guarantees than from our own measurements.
Is shard 0 canonical for sponsored feeds, and is there a case where a consumer should prefer
another shard?
Program is on devnet and the source is public if anyone wants to pick holes in the validation —
particularly the settlement rule, which is the part I’d attack first:
GitHub - 3esign/ratchetx · GitHub (onchain/ratchet_seal_lib.rs)
Thanks for the pull oracle — the fact that a 288-line program with no dependencies can verify a
price properly is the whole reason this design works.