SIP-329: Pull Oracle Update Node

NetworkEthereum & Optimism
ProposalLoading status...

Simple Summary

This proposal entails adding a new node type to the oracle manager such that clients can automatically replace a transaction with a multicall that prepends fresh price updates from Pyth’s pull-based price oracles as necessary.


This new node type involves a combination of logic from the existing Pyth Node and the Staleness Circuit Breaker Node. Rather than reading the latest available Pyth price and then reverting plainly when a price is too stale, it can point the client to an off-chain URI where a fresh price can be retrieved.


Nearly the entire DeFi ecosystem has been dependent on push oracles, where decentralized oracle networks like Chainlink write prices on-chain at a regular interval (a “heartbeat”) or when significant price deviations are observed. Although this is ideal for on-chain composability, these prices may not be fresh enough for practical use cases. Also, even if these oracle networks transition to proof of stake blockchains, it’s still not obvious how revenue could be collected from consumers such that nodes could be profitable after paying the cost of gas to write prices on-chain.

Pyth’s On-Demand Updates (and Chainlink’s forthcoming Low-Latency Oracle Solution) offer an alternative pattern where price consumers must retrieve a signed price update off-chain, cryptographically verify it using smart contracts, pay the oracle network, and pay the cost to write the price data on-chain. This has been integrated into Synthetix V2 Perpetual Futures Markets, Synthetix V3 Spot Markets, and the work-in-progress Synthetix V3 Perpetual Futures Markets for asynchronous order settlement. They implement EIP-3668 to retrieve and use prices associated with the time orders are committed.

This proposal would add functionality such that client applications for protocols that are integrated with the oracle manager are able to anticipate the need for fresh price data and automatically queue price update calls at the top of a multicall before the desired transaction.

Note that a similar pattern could be used to implement cross-chain pool synthesis if a decentralized oracle network were able to provide a similar service to read data from arbitrary functions on other chains at specified timestamps (rather than prices).



The oracle manager is a standalone system in Synthetix V3 which allows users to specify a node (of a variety of types), add configuration parameters, and an array of parent nodes when relevant. This proposal involves the addition of a new node type to achieve the pattern described above.


The oracle manager is used to retrieve the latest available price on-chain from a variety of sources, perform any pre-processing, and add safety checks. It is a stateless system.

The system may be called multiple times in a single transaction. (For instance, processing a liquidation request for an account in a perpetual futures market with positions in multiple markets with multiple collateral types could require fresh prices for many different assets.) Accordingly, we can’t rely on the EIP-3668 standard, as there wouldn’t be a sensible method to generate callback functions in very dynamic contexts.

Instead, this proposal requires custom logic in client applications to prepare successful transactions. It would be possible to develop an SDK to automatically to identify and include any necessary price data. Pseudocode is provided in the Technical Specification section below. This also limits composability, though if price updates are being provided frequently enough, it would be possible to have other transactions succeed without an off-chain look up.

Usage of the node is optional. Deployments of the Synthetix core system and market implementations could choose to rely on it for only some price feeds. Notably, with this node available, it would be possible to create a fully-functional deployment of the Synthetix core system and markets to a chain with only Pyth support and no push oracles.

Technical Specification

The parameters for this node would consist of:

  • address pythAddress - This is the same as is currently implemented in the Pyth Node.
  • bytes32 priceFeedId - This is the same as is currently implemented in the Pyth Node.
  • bool useEma - This is the same as is currently implemented in the Pyth Node.
  • uint stalenessTolerance - Like the Staleness Circuit Breaker Node, if the latest available on-chain price is older than the staleness tolerance, the node will revert with an OffchainDataRequired error. Otherwise, the latest price data will be provided.

The error would include a verifier contract address (pythAddress above) and function ("updatePriceFeeds"), such that the error would be resolved if it were called with an array of bytes data where one of the items includes a payload corresponding to the lookup type and lookup data. The lookup type should signify what type of gateway needs to be used (e.g. a Pythnet price lookup or a decentralized oracle network with support for cross-chain reads) and the lookup data would be encoded with a scheme that corresponds to the lookup type (e.g. "Retrieve the latest price for the ETH price feed ID" for Pyth).

error OffchainDataRequired(address verifierAddress, bytes4 verifierFunction, bytes16 lookupType, bytes lookupData)

Unlike EIP-3668, this system does not involve encoding off-chain URIs into chain state. It should be the responsibility of client applications to derive an appropriate endpoint based on the lookup type and data.

An issue could arise if the a price seems to be fresh enough on simulation, but then becomes stale once the actual transaction is being processed. The node could emit an event with the same data as the error and also include the anticipated expiration time. The client application could listen for this event on simulation and proactively include fresh price data if necessary.

event OffchainDataRequired(address verifierAddress, bytes4 verifierFunction, bytes16 lookupType, bytes lookupData, uint expirationTime)

The following pseudocode illustrates an oversimplified version of the client SDK that could take a desired transaction and convert it into a multicall with the necessary price updates prepended such that the OffChainDataRequired errors would be avoided:

function generatePricePrependedMulticall(originalTx) {
  let multicallTx = [originalTx]
  try {
    return multicallTx
  } catch (error) {
    if (error instanceof OffchainDataRequired) {
      const signedPriceData = fetchOffchainData(
      const priceUpdateTx = generatePriceUpdateTx(
      return generatePricePrependedMulticall(multicallTx)

Test Cases

Relevant tests will be developed during implementation.

Configurable Values (Via SCCP)


Copyright and related rights waived via CC0.