Working with Pyth Oracles
Euler V2 supports pull-based oracles like Pyth, which require special handling compared to traditional push-based oracles. This guide explains how to work with these oracles effectively, both for querying data and executing transactions.
Understanding Pull-Based Oracles
Traditional oracles (like Chainlink) are "push-based" - they automatically update prices on-chain at regular intervals. Pull-based oracles like Pyth work differently:
- Short staleness period: Pyth prices are only valid for 2-3 minutes
- User-driven updates: Users must fetch fresh price data and update the oracle before interacting with contracts
- Lower latency: Prices can be updated with sub-second latency when needed
- Cost efficiency: Updates only happen when needed, reducing overall gas costs
The Challenge
When working with Pyth-powered vaults, you may encounter situations where querying contract data and transactions fail because prices are stale.
Solution: Update Prices Before Interactions
The solution is to update Pyth prices before any interaction that requires them. This applies to both:
- State-changing operations (transactions)
- Data querying (read operations)
For State-Changing Operations
When executing transactions that involve Pyth-powered vaults, you need to:
- Fetch price updates from Pyth API
- Include price update in your EVC batch
- Execute your intended operations
Example: Borrowing from a Pyth-Powered Vault
// 1. Fetch price updates from Pyth API
// Note: Price feed IDs can typically be obtained by calling the Pyth oracle adapter
// that the vault relies on
const priceIds = [
'0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace', // ETH/USD
// Add other price IDs as needed
]
// 2. Get price update data from Hermes API
const pythApiUrl = 'https://hermes.pyth.network/v2/updates/price/latest'
const response = await fetch(`${pythApiUrl}?ids[]=${priceIds.join('&ids[]=')}&encoding=hex`)
const data = await response.json()
const priceUpdateData = data.binary.data
// Alternative using curl:
// curl "https://hermes.pyth.network/v2/updates/price/latest?ids[]=0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace&encoding=hex"
// 3. Prepare your batch items
const batchItems = [
// First: Update Pyth prices. Note that Pyth might require you to pass value as an update fee
{
targetContract: pythOracleAddress,
onBehalfOfAccount: account,
value: 0n,
data: encodeFunctionData({
abi: pythAbi,
functionName: 'updatePriceFeeds',
args: [priceUpdateData]
})
},
// Then: Your borrow operation
{
targetContract: vaultAddress,
onBehalfOfAccount: account,
value: 0n,
data: encodeFunctionData({
abi: vaultAbi,
functionName: 'borrow',
args: [amount, receiver]
})
}
]
// 4. Execute the batch
const result = await walletClient.writeContract({
address: evcAddress,
abi: evcAbi,
functionName: 'batch',
args: [batchItems]
})
For Data Querying
When you need to query data from contracts that rely on Pyth oracles, use the EVC's batchSimulation
function. This allows you to simulate price updates without actually executing them.
Example: Querying Account Health
// 1. Fetch fresh price data
// Note: Price feed IDs can typically be obtained by calling the Pyth oracle adapter
// that the vault relies on
const priceIds = ['0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace']
// Get price update data from Hermes API
const pythApiUrl = 'https://hermes.pyth.network/v2/updates/price/latest'
const response = await fetch(`${pythApiUrl}?ids[]=${priceIds.join('&ids[]=')}&encoding=hex`)
const data = await response.json()
const priceUpdateData = data.binary.data
// 2. Prepare simulation batch
const simulationItems = [
// First: Simulate price update. Note that Pyth might require you to pass value as an update fee
{
targetContract: pythOracleAddress,
onBehalfOfAccount: account,
value: 0n,
data: encodeFunctionData({
abi: pythAbi,
functionName: 'updatePriceFeeds',
args: [priceUpdateData]
})
},
// Then: Query the data you need
{
targetContract: lensAddress,
onBehalfOfAccount: account,
value: 0n,
data: encodeFunctionData({
abi: lensAbi,
functionName: 'getAccountLiquidity',
args: [account, vault]
})
}
]
// 3. Simulate the batch
const { result } = await publicClient.simulate({
address: evcAddress,
abi: evcAbi,
functionName: 'batchSimulation',
args: [simulationItems]
})
// 4. Extract the query result from the simulation
const [, queryResult] = result
const accountLiquidity = decodeFunctionResult({
abi: lensAbi,
functionName: 'getAccountLiquidity',
data: queryResult.result
})
Using Viem's simulateContract
Viem provides a convenient wrapper for simulations:
const { result } = await simulateContract(publicClient, {
address: evcAddress,
abi: evcAbi,
functionName: 'batchSimulation',
args: [simulationItems]
})
For more information about Pyth oracles, visit the official Pyth documentation.