Skip to content

feat(proxy-oracle): Pyth Lazer SEP-40 source and batcher for the Soroban stack (ENG-691) - #623

Draft
peer2f00l wants to merge 2 commits into
devfrom
feature/eng-691-soroban-pyth-lazer-source
Draft

feat(proxy-oracle): Pyth Lazer SEP-40 source and batcher for the Soroban stack (ENG-691)#623
peer2f00l wants to merge 2 commits into
devfrom
feature/eng-691-soroban-pyth-lazer-source

Conversation

@peer2f00l

@peer2f00l peer2f00l commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Closes ENG-691.

Pyth's Lazer (Pyth Pro) contract on Stellar is a stateless verifier — verify_update(Bytes) -> Bytes with no stored prices, no replay protection, no freshness check — so it cannot be a SEP-40 source for the Soroban proxy oracle directly. This adds the contract that sits in between, plus what a keeper and a deployment need around it.

New contracts

  • pyth-lazer-source-contract — SEP-40 source over Pyth's verifier. update_price_feeds(payload) is permissionless (authenticity is cryptographic): verify through the configured verifier, require the configured channel, gate the payload and every feed's own update time on a freshness window, and store each mapped feed whose update time strictly advances (anti-replay). One feed_id ↔ Asset map per instance, so one deployment backs every asset's proxy. Owner-gated add_feed / remove_feed / set_freshness / set_decimals / upgrade; permissionless extend_ttl. Optimized wasm ≈ 25.7 KiB.
  • batcher-contract — stateless fan-out of the runtime's permissionless refresh / extend_ttl (and sibling extend_ttl()s), renewing every target's instance and code TTL along the way. Stellar allows one Soroban op per transaction, so this gives a keeper two transactions per cycle regardless of asset count, with no separate stellar contract extend step. ≈ 6.6 KiB.

Shared changes

  • RefreshStatus, bucket_timestamp, extend_persistent_ttl, owner_upgrade, MAX_SEP40_DECIMALS, and a ProxyOracleMaintenanceTrait (refresh / extend_ttl) move into soroban/common; the runtime and adapter use them (ABI-neutral — same exports, same names).
  • Pyth's pyth-lazer-stellar-sdk 0.3.0 is vendored verbatim into the pyth-lazer-public fork on soroban-sdk 25 (crates.io needs soroban-sdk 26.1 → Rust ≥ 1.91; the 1.93 migration is blocked by a soroban-sdk/near-sdk arbitrary lockfile conflict, see ENG-691). Both git deps now pin fork tag templar/stellar-sdk-0.3.0-soroban25 (7ee94541…, recorded in Cargo.lock).
  • Justfile recipes parameterized (_build-wasm, _optimize, a size-check loop); release manifest and dry-run scripts share one artifact table (scripts/release_artifacts.py, manifest schema_version 3) and the manifest takes --wasm-dir.
  • scripts/e2e_live.sh: resumable live rehearsal (deploy → configure → push → refresh) with a mainnet write guard.
  • Docs: RedStone enters via its SEP-40 adapter CBMGLKUQ…WYRF (its per-feed contracts are not SEP-40); RUNBOOK constructor ordering and max_age_secs example fixed (the old example dropped Reflector on every refresh); AUDIT scope and AGENTS entry.

Verified

End to end on testnet and mainnet with the real contracts: a Pyth-signed leEcdsa payload verified on-chain → three-source SetProxy (Reflector + RedStone + Lazer) → refresh = Accepted (median-low of the three) → SEP-40 adapter lastprice; batcher fan-out incl. code-TTL renewal. Mainnet XLM/USD at refresh: Lazer 0.18624346, RedStone 0.18629441 (accepted), Reflector 0.18640258 — 0.085% spread. Mainnet rehearsal contracts (zero-timelock, deployer-admin; not the production instance): runtime CB2VNEE4…ZTQT, governance CBPZLC6K…7J7L, Lazer source CBR2JTRQ…GBHS, batcher CA2B2BN7…RRUS, adapter CDN7JPOO…YIVX.

Measured mainnet cost: WASM upload rent ≈ 1.3 XLM/KB (runtime 126 XLM, governance 51 XLM); budget ≈ 250 XLM for a fresh five-artifact deployment.

Unit: source 21, common 8, runtime 67, governance 27, adapter 25; integration suite incl. new lazer_source (source + batcher against the real runtime). Clippy clean with -D warnings; just size-check green.

Not in this PR

Mainnet deployment (needs a funded identity), the NEAR-free Lazer source crate extraction for the keeper, the keeper itself.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CJusKazQ9PNya7ntYaQL9U


This change is Reviewable

…ban stack (ENG-691)

Pyth's Lazer contract on Stellar is a stateless verifier (verify_update(Bytes)
-> Bytes, no stored prices, no replay or freshness protection), so it cannot be
a SEP-40 source for the proxy oracle directly.

- pyth-lazer-source-contract: SEP-40 source over the verifier. Permissionless
  update_price_feeds verifies, enforces channel and a freshness window on the
  payload and each feed's own update time, and stores mapped feeds whose update
  time strictly advances. One feed_id <-> Asset map per instance.
- batcher-contract: stateless fan-out of the runtime's permissionless refresh /
  extend_ttl and sibling extend_ttl()s, renewing each target's instance and
  code TTL, so a keeper needs one Soroban op per sweep.
- common: RefreshStatus, bucket_timestamp, extend_persistent_ttl, owner_upgrade,
  MAX_SEP40_DECIMALS and ProxyOracleMaintenanceTrait shared by all contracts
  (ABI-neutral for the runtime and adapter).
- pyth-lazer-stellar-sdk 0.3.0 vendored verbatim into the pyth-lazer-public
  fork on soroban-sdk 25 (crates.io needs soroban-sdk 26.1 / Rust >= 1.91);
  both git deps pin fork tag templar/stellar-sdk-0.3.0-soroban25.
- justfile, release manifest and dry-run cover five artifacts; scripts share
  one artifact table. scripts/e2e_live.sh drives a live rehearsal.
- Docs: RedStone's SEP-40 adapter is CBMGLKUQ...WYRF (its per-feed contracts
  are not SEP-40); RUNBOOK constructor ordering and max_age_secs example fixed.

Verified on Stellar testnet end to end against the real Pyth, Reflector and
RedStone contracts: signed payload -> three-source SetProxy -> refresh Accepted
-> adapter lastprice; batcher fan-out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJusKazQ9PNya7ntYaQL9U
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

… failed uploads

The stellar CLI's `mainnet` network alias is a placeholder ("Bring Your Own"
RPC), so `--network mainnet` fails; pass --rpc-url and the passphrase instead.
A failed `contract upload` used to be recorded as an empty hash and the
following deploy then targeted hash 0000…; record ids only on success.

Both surfaced on the mainnet rehearsal, which then passed end to end.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJusKazQ9PNya7ntYaQL9U
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.

1 participant