BLOG — Use cases

328 days ago

Real-Time Data Automation with Custom Oracles

TL;DR

The integration of Pyth and Gelato's Web3 Functions enables the creation of finely-tuned, customized oracles, enhancing the automation and accuracy of on-chain price updates. Pyth, a specialized oracle network, provides real-time financial data, while Web3 Functions connect smart contracts to off-chain data & computation.

In this exploration, we will deep dive into how Pyth uses Gelato Web3 Functions for the creation of automated oracles that push prices on-chain every hour or whenever there is a price change of 2% or more in either direction, according to a predefined logic. This ensures that blockchain data remains fresh and reliable, revolutionizing how smart contracts interact with real-world data.

Blockchain technology has initiated a significant shift across many sectors with its smart contracts - self-executing protocols with terms directly embedded in the code. However, a key constraint is their inability to interact with the extensive range of real-world data outside their blockchain ecosystem. Here, oracles come into play as they bridge this gap.

Oracles, in essence, are connectors between the blockchain world and the reality beyond. They compile off-chain data and bring it on-chain, making it accessible to smart contracts.

Gelato's Web3 Functions, in collaboration with Pyth, has taken oracles to the next level. They've created smart, customized oracles that aren't just bridges, but smart ones. These oracles fetch data from the real world and push prices on-chain following predefined logic within the Web3 Function and verifying prices on-chain through the Pyth network.

Pyth Network

Pyth is a specialized oracle network that focuses on providing precise, real-time financial data to smart contracts. This network has over 80 first-party publishers, some of which are the world's largest exchanges and market-making firms. From cryptocurrencies to US equities to commodities, Pyth offers price feeds for various asset classes.

This network doesn't merely provide data; it goes a step further by delivering robust aggregates of publisher prices, ensuring a more accurate and comprehensive data feed. The price feed updates multiple times per second, ensuring that smart contracts have access to the most recent data.

Web3 Functions

Think of Web3 Functions as the decentralized equivalent of services like AWS Lambda or Google Cloud. They enable developers to execute on-chain transactions based on arbitrary off-chain data (APIs / subgraphs, etc) & computation.

When these Web3 Functions team up with the Pyth Network, magic happens! This integration empowers developers to create custom oracles that automate data updates, fetching prices from Pyth and pushing them on-chain according to a predefined logic.

Integration

In this Pyth and Web3 Functions article, we have two demos:

  • W3F to Consumer Contract using a consumer contract
  • W3F to Pyth that directly interacts with the Pyth network

W3F to Consumer Contract

This Web3 Function is designed to fetch the latest price data from the Pyth network, determine when to update the price in the smartOracle contract, and prepare the data for the update. The update happens only if the price difference is more than 2% compared to the last fetched price, or if the last price update was more than 24 hours ago.

Now, let's look into the specific sections of the code:

Data Initialization

The function starts by retrieving some context data such as userArgs, storage, and secrets. The userArgs contains arguments provided by the user in this case they are SmartOracle contract address and priceIDs which is ETH/USD price id. The userArgs are stored in separate JSON configuration files These values are accessed by Web3 Functions code and stored in variables smartOracle and priceIds respectively.

Web3Functions are stateless scripts, that will run in a new & empty memory context on every execution. To manage some state variables, we provide a simple key/value store that you can access from your web3 function context.

In this, we have the lastPrice variable that retrieves the most recent price data stored from the previous function execution.

Web3Function.onRun(async (context: Web3FunctionContext) => {
  const { userArgs, storage, secrets, multiChainProvider } = context;

  // User Storage
  const lastPrice = JSON.parse(
    (await storage.get("lastPrice")) ?? "{}"
  ) as IPRICE;

  const smartOracle = (userArgs.SmartOracle as string) ?? "";
  const priceIds = (userArgs.priceIds ?? "") as string[];

Fetching the Latest Price Data

The function fetches the latest price data from the Pyth network using their APIs.

const connection = new EvmPriceServiceConnection(
  "https://xc-testnet.pyth.network"
);
const check = (await connection.getLatestPriceFeeds(priceIds)) as any[];

Initialization on First Run

If the function is running for the first time, it sets the current price as the last price and pushes this price to the smart contract.

if (lastPrice.price == undefined) { 
  await storage.set("lastPrice", JSON.stringify(currentPrice));
  const updatePriceData = await connection.getPriceFeedsUpdateData(priceIds);
  const callData = iface.encodeFunctionData("updatePrice", [updatePriceData]);
  return {
    canExec: true,
    callData: [
      {
        to: smartOracle,
        data: callData,
      },
    ],
  };
}

Price Update Logic

This section of the code compares the current price with the last price. If the price difference is greater than or equal to 2% or if the last update was over 24 hours ago, then the smart contract's price is updated. The price update is performed by preparing the data for the smart contract function updatePrice and setting the execution flag canExec to true.

  const dayInSec = 24 * 60 * 60;
  const diff = Math.abs(lastPrice.price - currentPrice.price) / lastPrice.price;

  // Price Update if 24h are elapsed or price diff >2%
  if (diff >= 0.02 || currentPrice.timestamp - lastPrice.timestamp > dayInSec) {
    const iface = new utils.Interface([
      "function updatePrice(bytes[] memory updatePriceData) external",
    ]);

    const updatePriceData = await connection.getPriceFeedsUpdateData(priceIds);

    const callData = iface.encodeFunctionData("updatePrice", [updatePriceData]);
    console.log(
      `Updating Price:${currentPrice.price}, timestamp: ${currentPrice.timestamp} `
    );

    await storage.set("lastPrice", JSON.stringify(currentPrice));
    return {
      canExec: true,
      callData: [
        {
          to: smartOracle,
          data: callData,
        },
      ],
    };
  } else {
    return {
      canExec: false,
      message: `No conditions met for price Update, price diff = ${(
        100 * diff
      ).toFixed(2)}%`,
    };
  }
});

W3F to Pyth

The "W3F to Pyth" script is designed to gather price data from the Pyth Network and update the corresponding on-chain prices within the Pyth contracts themselves.

Let's dive deeper into each section of the code:

Data Initialization

Web3Function.onRun(async (context: Web3FunctionContext) => {
  const { userArgs, storage, secrets, multiChainProvider } = context;
  
  // User Storage
  const lastPrice = JSON.parse(
    (await storage.get("lastPrice")) ?? "{}"
  ) as IPRICE;

At the start of the script, Web3Function.onRun specifies a function to be executed when the script runs. The lastPrice variable retrieves the most recent price data from the previous function execution using the storage.get method. This will be used later to check if the current price fetched from the Pyth network has a significant change.

Fetching Prices from Pyth Network

The Pyth network is connected using an EvmPriceServiceConnection, where the URL specifies the Pyth network endpoint. The most recent price feeds are obtained using the getLatestPriceFeeds method. If no valid price is found, the function returns a message stating that no price is available. If a valid price is found, it's stored in the currentPrice variable.

const connection = new EvmPriceServiceConnection(
    "https://xc-testnet.pyth.network"
  ); // See Price Service endpoints section below for other endpoints

const check = (await connection.getLatestPriceFeeds(priceIds)) as any[];
if (
    check.length == 0 ||
    check[0].price == undefined ||
    check[0].price.price == undefined
) {
  return { canExec: false, message: "No price available" };
}
const currentPrice: IPRICE = {
  price: +check[0].price.price,
  timestamp: +check[0].price.publishTime,
};

Price Update Logic:

The last section of the code compares the current price with the previous price fetched. If the price difference is greater than or equal to 2% or if the last update was over 24 hours ago, Pyth's price feeds are updated. The price data is updated using the "updatePriceFeeds" function of the Pyth contract. This update incurs a fee, which is also calculated here. If the conditions are not met, a message indicating the same is returned.

The on-chain transaction executed through the Web3 Function is routed through a proxy smart contract owned solely by the task creator of the Web3 Function. This proxy, also known as dedicatedMsgSender, is responsible for transferring the fee to the Pyth contract. Therefore, the dedicatedMsgSender address must be funded adequately to cover these transaction fees.

const dayInSec = 24 * 60 * 60;
const diff = Math.abs(lastPrice.price - currentPrice.price) / lastPrice.price;

// Price Update if 24h are elapsed or price diff >2%
if (diff >= 0.02 || currentPrice.timestamp - lastPrice.timestamp > dayInSec) {
    const updatePriceData = await connection.getPriceFeedsUpdateData(priceIds);
    const callData = await pythnetwork.interface.encodeFunctionData("updatePriceFeeds" ,[updatePriceData])
    const fee = (await pythnetwork.getUpdateFee(updatePriceData)).toString();
    console.log(
      `Updating Price:${currentPrice.price} `
    );

    await storage.set("lastPrice", JSON.stringify(currentPrice));
    return {
      canExec: true,
      callData: [
        {
          to: pythNetworkAddress,
          data: callData,
          value: fee
        },
      ],
    };
} else {
  return {
    canExec: false,
    message: `No conditions met for price Update, price diff = ${(
      100 * diff
    ).toFixed(2)}%`,
  };
}

Conclusion

In conclusion, the Gelato Web3 Functions integration with Pyth enables the seamless and trustworthy transfer of real-world data to smart contracts, while ensuring timely updates. It revolutionizes how developers interact with off-chain data, paving the way for more complex and sophisticated decentralized applications. This evolution is a tremendous stride towards a more efficient, automated, and interconnected blockchain world.

About Gelato

Get ready to witness a new era of web3 as Gelato, relied upon by over 400 web3 projects, powers the execution of millions of transactions in DeFi, NFT, and Gaming.

Gelato currently offers four services:

  • Web3 Functions: Connect your smart contracts to off-chain data & computation by running decentralized cloud functions.

  • Automate: Automate your smart contracts by executing transactions automatically in a reliable, developer-friendly & decentralized manner

  • Relay: Give your users access to reliable, robust, and scalable gasless transactions via a simple-to-use API

  • Gasless Wallet: A powerful SDK that enables developers to provide a world-class UX by combining Gelato Relay + Safe's Smart Contract Wallet, enabling Account Abstraction

Witness the ongoing journey towards a decentralized future led by Gelato!