From 421ca30a671cd32d7d6466e8b903f10d79dc1590 Mon Sep 17 00:00:00 2001 From: "stellar-triage-bot[bot]" <333411864+stellar-triage-bot[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 16:47:04 +0000 Subject: [PATCH] Add Pyth Pro and oracle selection guide, fix Chainlink Data Streams note Add a short table that tells agents which oracle to pick and how to reject a stale price. Add a Pyth Pro entry with both verifier contracts and the consumer crate. Correct the Data Streams line: the on-chain Verifier has no allowlist; access is gated by a paid API subscription. Document get_min_decimals and the frozen-feed error on the Data Feeds proxy, and link the testnet consumer demos. Update the Chainlink eval and add an oracle-choice scenario. --- .../standards/05-chainlink-oracle.json | 5 ++- .../scenarios/standards/07-oracle-choice.json | 11 ++++++ skills/standards/ecosystem.md | 37 ++++++++++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 evals/scenarios/standards/07-oracle-choice.json diff --git a/evals/scenarios/standards/05-chainlink-oracle.json b/evals/scenarios/standards/05-chainlink-oracle.json index d880858..a9578b1 100644 --- a/evals/scenarios/standards/05-chainlink-oracle.json +++ b/evals/scenarios/standards/05-chainlink-oracle.json @@ -5,7 +5,8 @@ "Loads the standards skill and reads the Chainlink entry in ecosystem.md rather than answering from memory or from Chainlink's EVM docs", "States that the Chainlink proxy is not SEP-40 and does not call `lastprice()` or other SEP-40 methods on it", "Calls the single mainnet proxy `CDQBHEUGLDHMSUHG4IH33RJBUTMNCZOT4JIAQR4WZFRDZW6DR6S32ZLZ` with the feed's 32-byte `data_id` instead of inventing a per-feed contract address, and uses the testnet proxy only for testnet", - "Calls `latest_round(data_id, decimals)` with `decimals` set (18 unless Chainlink approved a lower precision) and reads `answer` as an I256 alongside `timestamp`", - "If it suggests Data Streams, says it needs an allowlisted account and on-chain report verification with the Chainlink Verifier" + "Calls `latest_round(data_id, decimals)` with `decimals` set to 18 (or no lower than `get_min_decimals(data_id)`) and reads `answer` as an I256 alongside `timestamp`", + "Checks `timestamp` against ledger time and the feed's heartbeat instead of trusting the latest round as current", + "If it suggests Data Streams, says it needs a paid Data Streams subscription and on-chain report verification with `verify(signed_report, sender)` on the Verifier contract, and does not claim the on-chain Verifier needs an allowlisted account" ] } diff --git a/evals/scenarios/standards/07-oracle-choice.json b/evals/scenarios/standards/07-oracle-choice.json new file mode 100644 index 0000000..1a3d60d --- /dev/null +++ b/evals/scenarios/standards/07-oracle-choice.json @@ -0,0 +1,11 @@ +{ + "skills": ["standards"], + "query": "I'm building a perps protocol on Stellar and need prices for liquidations. Which oracle should I use, and how do I make sure the price isn't stale?", + "expected_behavior": [ + "Loads the standards skill and reads the Oracles section of ecosystem.md, including the oracle comparison table", + "Recommends a pull oracle for execution-time prices (Pyth Pro, or Chainlink Data Streams) and says Pyth Pro needs an API key and Data Streams needs a paid subscription", + "For Pyth Pro, uses the verifier `CACZ3GBAKUPIAFRILUFO27J5RUH5GJ2VSJ46LP6GJYSKGDRTQ5MS3HCH` on mainnet and calls `verify_update` from inside its own contract, for example via `pyth-lazer-stellar-sdk`'s `PythLazerClient`, never trusting a parsed update from the off-chain caller or using `parse_payload` alone", + "States that the Pyth Pro verifier checks only signatures, so the contract must reject old or carried-forward feeds using `feed_update_timestamp` (microseconds) against ledger time", + "Advises reading two independent oracles for liquidations and refusing to act when they diverge past a set bound" + ] +} diff --git a/skills/standards/ecosystem.md b/skills/standards/ecosystem.md index 56be3f9..eda8057 100644 --- a/skills/standards/ecosystem.md +++ b/skills/standards/ecosystem.md @@ -325,12 +325,29 @@ Official MCP server for the Stellar docs site. Searches the docs, reads full pag ## Oracles +### Choosing an oracle + +Each oracle below returns a timestamp and leaves the freshness check to your contract. Pick by price model, then write that oracle's staleness rule. + +| Oracle | Model and cost | Pick it for | Reject the price when | +|---|---|---|---| +| Reflector Pulse | Push, free to read, SEP-40, new price every 5 minutes (`resolution()` is 300 s) | Stellar-native assets, DEX prices, SEP-40 compatibility | `now - timestamp > 2 * resolution()` | +| Chainlink Data Feeds | Push, free to read, one proxy per network, 18 decimals, updates on a deviation threshold (0.5 percent on most feeds) or a 24-hour heartbeat | A shared reference rate (majors, stablecoins, gold, NAV feeds), or a protocol that already uses Chainlink elsewhere | `now - timestamp > heartbeat`; on a quiet day a valid price can be hours old | +| Pyth Pro | Pull, Pyth Pro API key, sub-second updates with bid and ask; your caller brings the signed update | Perps, liquidations, anything that needs the price at execution time | The feed's `feed_update_timestamp` is too old, or the feed was carried forward with no new price | +| Chainlink Data Streams | Pull, paid subscription (no free tier) | Same cases as Pyth Pro | `expiresAt` has passed or `observationsTimestamp` is too old | + +`now` is `env.ledger().timestamp()`, in seconds. Pyth Pro timestamps are in microseconds. + +**Two sources for funds.** A contract that liquidates or mints against a price should read two independent oracles and refuse when they diverge past a set bound. Oracle input validation is covered in [the smart contract security guide](../smart-contracts/security.md). + #### Reflector Network Community-powered price oracle for Stellar. - **Website**: https://reflector.network - **Docs**: https://developers.stellar.org/docs/data/oracles/oracle-providers +- **Interface**: SEP-40 plus extensions: https://reflector.network/docs/interface. Read `decimals()` and `resolution()` from the contract; do not hard-code them. - **Features**: SEP-40 compatible, on-chain/off-chain prices, webhooks - **Integrations**: Blend, OrbitCDP, DeFindex, EquitX, Slender +- **Examples**: staleness rules for Reflector, Chainlink Data Feeds and Pyth Pro side by side: https://github.com/kaankacar/stellar-chainlink-demo/blob/main/docs/field-note/oracle-comparison.md #### DIA Oracle Cross-chain oracle with 20,000+ asset support. @@ -344,9 +361,25 @@ Data Feeds and Data Streams live on Stellar (October 2026). - **Feed IDs**: https://docs.chain.link/data-feeds/price-feeds/addresses?network=stellar (select Stellar) - **Proxy (mainnet)**: `CDQBHEUGLDHMSUHG4IH33RJBUTMNCZOT4JIAQR4WZFRDZW6DR6S32ZLZ` - **Proxy (testnet)**: `CBUF6IADAWPWIDWRTHJNINKXHG2UTIQ6TU2F2HRAXWAI7OT3KJPKK6O4` -- **Interface**: not SEP-40. One proxy serves every feed; select a feed by its 32-byte `data_id`. `latest_round(data_id, decimals)` and `get_round(data_id, round_id, decimals)` return `Round { round_id, answer: I256, timestamp }`. Answers are stored at 18 decimals; pass `18` unless Chainlink approved a lower precision. Interface source: https://github.com/smartcontractkit/chainlink-stellar +- **Interface**: not SEP-40. One proxy serves every feed; select a feed by its 32-byte `data_id`. `latest_round(data_id, decimals)` and `get_round(data_id, round_id, decimals)` return `Round { round_id, answer: I256, timestamp }`. Answers are stored at 18 decimals. `decimals` must be between `get_min_decimals(data_id)` and 18, so pass `18` or read the minimum first. Interface source: https://github.com/smartcontractkit/chainlink-stellar +- **Frozen feeds**: reads of a frozen feed panic with `Error(Contract, #109)` (`FeedFrozen`). Check `timestamp` against the feed's heartbeat too, since the proxy returns the last round however old it is. - **Example**: BTC/USD `data_id` `01a0b4d920000332000000000000000000000000000000000000000000000000` (same on testnet and mainnet) -- **Data Streams**: pull model; verify signed reports on-chain with the Chainlink Verifier contract. Needs an allowlisted account. Guide: https://docs.chain.link/data-streams/tutorials/stellar-onchain-report-verification +- **Data Streams**: pull model. Fetch a signed report from the Data Streams API, then verify it on chain with `verify(signed_report, sender)` on the Verifier contract. The on-chain Verifier has no allowlist; it only requires `sender` to authorize the call. API access needs a paid subscription; there is no free tier (https://docs.chain.link/data-streams/sign-up). Guide: https://docs.chain.link/data-streams/tutorials/stellar-onchain-report-verification +- **Data Streams Verifier (mainnet)**: `CAKA3NBYPC6OBEUEGNIYGNYG3ES2GPQK736B5SR7ASGUXRDAKXI2JCQI` +- **Data Streams Verifier (testnet)**: `CA7GVHWH4GRHE6GI7MHEKQZAOYO4GE7KRGSU3EOS3HYJRVLX3XEA4ONQ` +- **Examples**: Data Feeds and Data Streams read from a Soroban contract on testnet, with tests and scripts: https://github.com/kaankacar/stellar-chainlink-demo + +#### Pyth Pro +Pull oracle (formerly Pyth Lazer) with sub-second prices, live on Stellar mainnet and testnet. +- **Guide**: https://docs.pyth.network/pro/use-prices-on-blockchain/stellar +- **Verifier (mainnet)**: `CACZ3GBAKUPIAFRILUFO27J5RUH5GJ2VSJ46LP6GJYSKGDRTQ5MS3HCH` +- **Verifier (testnet)**: `CAYFT5JE3UQTKT4Q6ZOZK4FXVYVT6RE3MFC7STA4UB6WAEGBT65MRU52` +- **Contract IDs and source**: https://github.com/pyth-network/pyth-lazer-public/tree/main/contracts/stellar +- **Flow**: fetch a signed update in the `leEcdsa` format from the Pyth Pro API, pass the bytes to your contract, and call `verify_update(Bytes)` on the verifier from inside your contract. Never trust a parsed update from the off-chain caller. +- **Rust crate**: `pyth-lazer-stellar-sdk` 0.4.0 (requires `soroban-sdk ^26.1`). `PythLazerClient::verify_update` verifies and parses in one call. `parse_payload` alone checks no signatures. +- **Freshness**: the verifier checks only that a currently trusted signer signed the update. It does not check price age or block replay. Request the `feedUpdateTimestamp` property and reject old or carried-forward feeds in your contract. +- **Access**: needs a Pyth Pro API key from a Pyth Terminal account: https://docs.pyth.network/pro/acquire-api-key. Check that your plan covers the feeds you need, such as XLM/USD. +- **Examples**: Pyth Pro prices verified and stored in a Soroban contract on testnet, with tests and scripts: https://github.com/kaankacar/stellar-pyth-pro-demo #### Band Protocol Cross-chain data oracle on BandChain.