Price Feeds
Price feeds enabled by an oracle serve as a bridge between real-world data feed and the blockchain. They provide real time pricing data that is aggregated from multiple trusted external sources ( e.g. crypto exchanges, financial data providers, etc. ) to the blockchain network.
For the example in this book section, we will use Pragma Oracle to read the price feed for ETH/USD
asset pair and also showcase a mini application that utilizes this feed.
Pragma Oracle is a leading zero knowledge oracle that provides access to off-chain data on Starknet blockchain in a verifiable way.
Setting Up Your Contract for Price Feeds
Add Pragma as a Project Dependency
To get started with integrating Pragma on your Cairo smart contract for price feed data, edit your project's Scarb.toml
file to include the path to use Pragma.
[dependencies]
pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" }
Creating a Price Feed Contract
After adding the required dependencies for your project, you'll need to define a contract interface that includes the required pragma price feed entry point.
#[starknet::interface]
pub trait IPriceFeedExample<TContractState> {
fn buy_item(ref self: TContractState);
fn get_asset_price(self: @TContractState, asset_id: felt252) -> u128;
}
Of the two public functions exposed in the IPriceFeedExample
, the one necessary to interact with the pragma price feed oracle is the get_asset_price
function, a view function that takes in the asset_id
argument and returns a u128
value.
Import Pragma Dependencies
use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait};
use pragma_lib::types::{DataType, PragmaPricesResponse};
The snippet above shows the necessary imports you need to add to your contract module in order to interact with the Pragma oracle.
Required Price Feed Function Impl in Contract
fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 {
// Retrieve the oracle dispatcher
let oracle_dispatcher = IPragmaABIDispatcher {
contract_address: self.pragma_contract.read(),
};
// Call the Oracle contract, for a spot entry
let output: PragmaPricesResponse = oracle_dispatcher
.get_data_median(DataType::SpotEntry(asset_id));
return output.price;
}
The get_asset_price
function is responsible for retrieving the price of the asset specified by the asset_id
argument from Pragma Oracle. The get_data_median
method is called from the IPragmaDispatcher
instance by passing the DataType::SpotEntry(asset_id)
as an argument and its output is assigned to a variable named output
of type PragmaPricesResponse
. Finally, the function returns the price of the requested asset as a u128
.
Example Application Using Pragma Price Feed
#[starknet::contract]
mod PriceFeedExample {
use core::starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use super::{ContractAddress, IPriceFeedExample};
use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait};
use pragma_lib::types::{DataType, PragmaPricesResponse};
use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait};
use core::starknet::contract_address::contract_address_const;
use core::starknet::get_caller_address;
const ETH_USD: felt252 = 19514442401534788;
const EIGHT_DECIMAL_FACTOR: u256 = 100000000;
#[storage]
struct Storage {
pragma_contract: ContractAddress,
product_price_in_usd: u256,
}
#[constructor]
fn constructor(ref self: ContractState, pragma_contract: ContractAddress) {
self.pragma_contract.write(pragma_contract);
self.product_price_in_usd.write(100);
}
#[abi(embed_v0)]
impl PriceFeedExampleImpl of IPriceFeedExample<ContractState> {
fn buy_item(ref self: ContractState) {
let caller_address = get_caller_address();
let eth_price = self.get_asset_price(ETH_USD).into();
let product_price = self.product_price_in_usd.read();
// Calculate the amount of ETH needed
let eth_needed = product_price * EIGHT_DECIMAL_FACTOR / eth_price;
let eth_dispatcher = ERC20ABIDispatcher {
contract_address: contract_address_const::<
0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7,
>() // ETH Contract Address
};
// Transfer the ETH to the caller
eth_dispatcher
.transfer_from(
caller_address,
contract_address_const::<
0x0237726d12d3c7581156e141c1b132f2db9acf788296a0e6e4e9d0ef27d092a2,
>(),
eth_needed,
);
}
fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 {
// Retrieve the oracle dispatcher
let oracle_dispatcher = IPragmaABIDispatcher {
contract_address: self.pragma_contract.read(),
};
// Call the Oracle contract, for a spot entry
let output: PragmaPricesResponse = oracle_dispatcher
.get_data_median(DataType::SpotEntry(asset_id));
return output.price;
}
}
}
Note: Pragma returns the value of different token pairs using the decimal factor of 6 or 8. You can convert the value to the required decimal factor by dividing the value by \( {10^{n}} \), where
n
is the decimal factor.
The code above is an example implementation of an applications consuming a price feed from the Pragma oracle. The contract imports necessary modules and interfaces, including the IPragmaABIDispatcher
for interacting with the Pragma oracle contract and the ERC20ABIDispatcher
for interacting with the ETH ERC20 token contract.
The contract has a const
that stores the token pair ID of ETH/USD
, and a Storage
struct that holds two fields pragma_contract
and product_price_in_usd
. The constructor function initializes the pragma_contract
address and sets the product_price_in_usd
to 100.
The buy_item
function is the main entry point for a user to purchase an item. It retrieves the caller's address. It calls the get_asset_price
function to get the current price of ETH in USD using the ETH_USD
asset ID. It calculates the amount of ETH needed to buy the product based on the product price in USD at the corresponding ETH price. It then checks if the caller has enough ETH by calling the balance_of
method on the ERC20 ETH contract. If the caller has enough ETH, it calls the transfer_from
method of the eth_dispatcher
instance to transfer the required amount of ETH from the caller to another contract address.
The get_asset_price
function is the entry point to interact with the Pragma oracle and has been explained in the section above.
You can get a detailed guide on consuming data using Pragma price feeds on their documentation.