Query Price Data on Berachain using Pyth Price Feeds
Building On-Demand Price Feeds with Pyth đź
Oracles help connect blockchains to the real world. The existing oracle paradigm revolves around oracle operators continuously pushing price updates on-chain (and paying associated fees). This can scale poorly with an increasing number of price feeds and supported chains.
Pyth Network aims to eliminate this tradeoff using on-demand price updates, where users pull on-chain prices only when neededâââlearn more here.
This article guides you through the process of creating a smart contract which leverages Pyth oracles to consume an ETH/USD price feed.
đ Requirements
Before we move on, make sure you have the following installed and setup on your computer.
- NodeJS
v20.11.0
or greater - pnpm
- jqâââused for handling JSON data
- A wallet configured with Berachain Artio Network
$BERA
or Berachain Testnet Tokens in that walletâââsee Berachain Faucet
Foundry
This guide requires Foundry to be installed. In a terminal window, run:
curl -L https://foundry.paradigm.xyz | bash;
foundryup;
# foundryup installs the 'forge' and 'cast' binaries, used later
For more installation instructions, see Foundryâs installation guide. For more details using Foundry on Berachain, see this guide.
Creating Our Pyth Oracle Project
First, weâre going to set up your development environment using Foundry.
Start by creating a new project folder and initializing Foundry:
forge init pyth-oracle --no-git --no-commit;
cd pyth-oracle;
# We observe the following basic layout
# .
# âââ foundry.toml
# âââ script
# â âââ Counter.s.sol
# âââ src
# â âââ Counter.sol
# âââ test
# âââ Counter.t.sol
Installing Pyth Dependencies
We will be leveraging Pythâs contracts, so you must first install Pythâs contract interfaces as a node dependency:
# FROM: ./pyth-oracle
pnpm init;
pnpm add @pythnetwork/pyth-sdk-solidity;
Forge can remap dependencies to make imports more readable. So letâs remap our Pyth import:
# FROM: ./pyth-oracle
echo "remappings = ['@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity']" >> ./foundry.toml
Writing the Oracle Consumer Contract
Now weâre ready to jump into the exciting part of consuming Pyth price feeds in a Solidity smart contract.
Create a new file ./src/ConsumerContract.sol
and paste in the following:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
contract ConsumerContract {
IPyth pyth;
/**
* Network: Berachain Artio (testnet)
* Address: 0x8D254a21b3C86D32F7179855531CE99164721933
*/
constructor() {
pyth = IPyth(0x8D254a21b3C86D32F7179855531CE99164721933);
}
function updatePrice(bytes[] calldata priceUpdateData) public payable {
uint fee = pyth.getUpdateFee(priceUpdateData);
pyth.updatePriceFeeds{value: fee}(priceUpdateData);
}
function getPrice() public view returns (PythStructs.Price memory) {
// ETH/USD priceID
bytes32 priceID = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace;
return pyth.getPrice(priceID);
}
function getLatestPrice(
bytes[] calldata priceUpdateData
) public payable returns (PythStructs.Price memory) {
updatePrice(priceUpdateData);
return getPrice();
}
}
Now letâs break this all down:
We first import IPyth
and PythStructs
, which provides interfaces for interacting with deployed Pyth oracle contracts. More information on working with the Pyth SDK.
In the constructor, we connect a pyth
instance to the Pyth oracle at 0x8D254a21b3C86D32F7179855531CE99164721933
, deployed on the Berachain Artio Testnet.
The updatePrice
function takes an input of priceUpdateData
which is a signed price update, which is streamed from Pyth off-chain (discussed below).
Following a price update, getPrice()
may be called, which returns a Price
object corresponding to the requested priceId
. Here, we are using a priceId
of 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
, which corresponds to Pythâs ETH/USD
price feed. See here for the full list of available feeds.
getLatestPrice
simply aggregates the above two calls.
(You may deletesrc/Counter.sol
, test/Counter.t.sol
and script/Counter.s.sol
which were generated with the project)
Deploying the smart contract
Youâre almost ready to deploy your contract to Berachain. First, you will need to set up a wallet for deploying.
Setting up for Deployment
Run the following to import your walletâs private key into Foundryâs keystore (with the deployer
alias):
cast wallet import deployer --interactive;
# [Example Output]
# Enter private key:
# Enter password:
# `deployer` keystore was saved successfully. Address: <YOUR_WALLET_ADDRESS>
Confirm that your wallet was imported by running:
cast wallet list;
# [Example Output]
# deployer (Local)
Letâs load the Berachain RPC into our terminal session:
export BERACHAIN_ARTIO_RPC="https://rpc.ankr.com/berachain_testnet"
Deploying to Berachain Testnet
First, compile the smart contract:
# FROM: ./pyth-oracle
forge build;
You will notice a number of build outputs appears in the ./out
directory.
We will be leveraging the forge create
command for deploying our new contract on Berachain Testnet (read more about the command here):
# FROM: ./pyth-oracle
forge create ./src/ConsumerContract.sol:ConsumerContract --rpc-url $BERACHAIN_ARTIO_RPC --account deployer
# [Expected Similar Output]:
# Enter keystore password:
# Deployer: 0x529CA3A690E1bB4e9F04d132bd99D4398f626A44
# Deployed to: 0x9106b2041C896224Af2142ea9C7349aa283Df7C6
# Transaction hash: 0xc8efdd3132080491b42c469fb2219bc6f0432981a46cdd3f6ae73b9e834ff4e4
Take note of the deployed contract address, you will need it in the following section.
You will be prompted for the keystore password you set earlier. You are required to have $BERA to pay for the deployment fees. Faucet funds are available at https://artio.faucet.berachain.com/.
Interacting with your Smart Contract
Finally, weâre going to fetch the ETH/USD
price from within your deployed smart contract.
Recall that we had to obtainpriceUpdateData
before calling updatePrice
. We obtain this payload by using Hermes, a web service which listens to the Pyth Network for price updates and serves them via REST API.
Make a request to fetch the priceUpdateData
with our ETH/USD
priceId:
# FROM: ./pyth-oracle
curl -s "https://hermes.pyth.network/v2/updates/price/latest?&ids[]=0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" | jq -r ".binary.data[0]" > price_update.txt
The jq
utility is used for writing the output to a price_update.txt
file, which makes for easy retrieval later on.
Once we have this data, we can invoke updatePrice
with the following command, passing in the payload we received from Pyth (replacing the placeholder with your deployed contract):
# FROM: ./pyth-oracle
cast send <YOUR_DEPLOYED_CONTRACT> --rpc-url $BERACHAIN_ARTIO_RPC "updatePrice(bytes[])" "[0x`cat price_update.txt`]" --account deployer --value 0.0001ether
# [Expected Similar Output]:
# blockHash 0xf00e38ea8197d088973dc51502b9fb62d089ac31b6fe01002e83a969e9c05f93
# blockNumber 1037572
# contractAddress
# cumulativeGasUsed 208351
# effectiveGasPrice 3000000017
# from 0x529CA3A690E1bB4e9F04d132bd99D4398f626A44
# ...
Next, we query the price with getPrice()
:
cast call <YOUR_DEPLOYED_CONTRACT> --rpc-url $BERACHAIN_ARTIO_RPC "getPrice()"
# [Expected Similar Output]
# 0x0000000000000000000000000000000000000000000000000000005eb4cf6d2800000000000000000000000000000000000000000000000000000000130c6cd8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000000000000000000000000000000000000000000000065ef9853
This provides us the ETH/USD
price in terms of the Solidity Price
struct (from ./pyth-oracle/lib/pyth-sdk-solidity/PythStructs.sol
):
struct Price {
// Price
int64 price;
// Confidence interval around the price
uint64 conf;
// Price exponent
int32 expo;
// Unix timestamp describing when the price was published
uint publishTime;
}
To decode the hexadecimal output to a human readable format, you can use the abi-decode
command below:
# FROM: ./pyth-oracle
cast abi-decode "getPrice()(int64,uint64,int32,uint)" <YOUR_GETPRICE_OUTPUT>
# [Example Decoded Output of ETH/USD Feed]
# 406760418600 [4.067e11]
# 319581400 [3.195e8]
# -8
# 1710200915 [1.71e9]
Troubleshooting
- If you donât act quick enough, you may encounter the error
0x19abf40e
representing aStalePrice
error. This means that theprice_update.txt
was too old to be used by the contract. Re-run the sequence of commands in this subsection to retry. - The error code
0x025dbdd4
represents anInsufficientFee
error. Try raising the value of $BERA in theupdatePrice
call e.g.0.0005ether
.
Recap
Congratulations, youâve successfully deployed a smart contract which pulls price data from Pyth oracles on the Berachain Testnet đ
đ» Full Code Repository
If you want to see the final code and see other guides, check out Berachain Pyth Guide Code.
đ ïž Want To Build More?
Want to build more on Berachain and see more examples. Take a look at our Berachain GitHub Guides Repo for a wide variety of implementations that include NextJS, Hardhat, Viem, Foundry, and more.
If youâre looking to dive deeper into the details, take a look at our Berachain Docs.
Looking For Dev Support?
Make sure to join our Berachain Discord server and check out our developer channels to ask questions.
â€ïž Donât forget to show some love for this article đđŒ