Comparing aave with pyth
aave
View full →Author
@0xinit
Stars
53
Repository
0xinit/cryptoskills
Aave V3
Aave V3 is the dominant on-chain lending protocol. Users supply assets to earn yield and borrow against collateral. The protocol runs on Ethereum, Arbitrum, Optimism, Base, Polygon, and other EVM chains with identical interfaces. All interaction goes through the IPool contract.
What You Probably Got Wrong
LLMs confuse V2 and V3 constantly. V3 has different interfaces, different addresses, and different behavior. These corrections are non-negotiable.
- V3 is not V2 — different interfaces everywhere — V2 uses
LendingPoolwithdeposit(). V3 usesPoolwithsupply(). The function signatures, events, and return types differ. If you seeILendingPoolordeposit(), you are writing V2 code. Stop. - aTokens rebase — balance changes every block —
aToken.balanceOf(user)increases each block as interest accrues. This is not a transfer. Do not try to track balances with Transfer events alone. UsebalanceOf()at read time orscaledBalanceOf()for the underlying non-rebasing amount. - Stable rate borrowing is deprecated on most markets — Aave governance disabled stable rate borrows on Ethereum mainnet and most L2 deployments. Use
VARIABLE_RATE = 2for theinterestRateModeparameter. PassingSTABLE_RATE = 1will revert on markets where it is disabled. - Health factor is 18-decimal fixed point, not a percentage —
getUserAccountData()returnshealthFactoras auint256with 18 decimals. A health factor of1e18means liquidation threshold. Below1e18= liquidatable. Do not divide by 100. - Flash loan fee is 0.05% on V3, not 0.09% — V3 reduced the default flash loan premium from 0.09% (V2) to 0.05%. The exact fee is configurable per market via governance. Check
FLASHLOAN_PREMIUM_TOTALon the Pool contract. - E-Mode changes collateral/borrow parameters — Enabling E-Mode (Efficiency Mode) overrides LTV, liquidation threshold, and liquidation bonus for assets in the same category (e.g., stablecoins). It does NOT change the underlying asset. Forgetting this causes incorrect health factor calculations.
- Supply caps exist in V3 — V3 introduced per-asset supply and borrow caps.
supply()will revert if the cap is reached. Always checkgetReserveData()forsupplyCapbefore large deposits. - V3 addresses are different per chain AND per market — Ethereum has a "Main" market and a "Lido" market with completely different Pool addresses. Always verify you are targeting the correct market.
Quick Start
Installation
npm install @aave/aave-v3-core viem
For TypeScript projects, the @aave/aave-v3-core package provides Solidity interfaces. For frontend/backend interaction, use viem directly with the ABIs below.
Minimal ABI Fragments
const poolAbi = [
{
name: "supply",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "onBehalfOf", type: "address" },
{ name: "referralCode", type: "uint16" },
],
outputs: [],
},
{
name: "borrow",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "interestRateMode", type: "uint256" },
{ name: "referralCode", type: "uint16" },
{ name: "onBehalfOf", type: "address" },
],
outputs: [],
},
{
name: "repay",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "interestRateMode", type: "uint256" },
{ name: "onBehalfOf", type: "address" },
],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "withdraw",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "to", type: "address" },
],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "getUserAccountData",
type: "function",
stateMutability: "view",
inputs: [{ name: "user", type: "address" }],
outputs: [
{ name: "totalCollateralBase", type: "uint256" },
{ name: "totalDebtBase", type: "uint256" },
{ name: "availableBorrowsBase", type: "uint256" },
{ name: "currentLiquidationThreshold", type: "uint256" },
{ name: "ltv", type: "uint256" },
{ name: "healthFactor", type: "uint256" },
],
},
{
name: "flashLoanSimple",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "receiverAddress", type: "address" },
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "params", type: "bytes" },
{ name: "referralCode", type: "uint16" },
],
outputs: [],
},
{
name: "setUserEMode",
type: "function",
stateMutability: "nonpayable",
inputs: [{ name: "categoryId", type: "uint8" }],
outputs: [],
},
{
name: "getReserveData",
type: "function",
stateMutability: "view",
inputs: [{ name: "asset", type: "address" }],
outputs: [
{
name: "",
type: "tuple",
components: [
{ name: "configuration", type: "uint256" },
{ name: "liquidityIndex", type: "uint128" },
{ name: "currentLiquidityRate", type: "uint128" },
{ name: "variableBorrowIndex", type: "uint128" },
{ name: "currentVariableBorrowRate", type: "uint128" },
{ name: "currentStableBorrowRate", type: "uint128" },
{ name: "lastUpdateTimestamp", type: "uint40" },
{ name: "id", type: "uint16" },
{ name: "aTokenAddress", type: "address" },
{ name: "stableDebtTokenAddress", type: "address" },
{ name: "variableDebtTokenAddress", type: "address" },
{ name: "interestRateStrategyAddress", type: "address" },
{ name: "accruedToTreasury", type: "uint128" },
{ name: "unbacked", type: "uint128" },
{ name: "isolationModeTotalDebt", type: "uint128" },
],
},
],
},
] as const;
const erc20Abi = [
{
name: "approve",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ name: "", type: "bool" }],
},
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "account", type: "address" }],
outputs: [{ name: "", type: "uint256" }],
},
] as const;
Basic Supply (TypeScript)
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
const POOL = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2" as const;
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" as const;
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({
chain: mainnet,
transport: http(process.env.RPC_URL),
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.RPC_URL),
});
const amount = parseUnits("1000", 6); // 1000 USDC
// Approve Pool to spend USDC
const approveHash = await walletClient.writeContract({
address: USDC,
abi: erc20Abi,
functionName: "approve",
args: [POOL, amount],
});
await publicClient.waitForTransactionReceipt({ hash: approveHash });
// Supply USDC to Aave
const supplyHash = await walletClient.writeContract({
address: POOL,
abi: poolAbi,
functionName: "supply",
args: [USDC, amount, account.address, 0],
});
const receipt = await publicClient.waitForTransactionReceipt({ hash: supplyHash });
if (receipt.status !== "success") {
throw new Error("Supply transaction reverted");
}
Core Operations
Supply (Solidity)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IPool} from "@aave/aave-v3-core/contracts/interfaces/IPool.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract AaveSupplier {
IPool public immutable pool;
constructor(address _pool) {
pool = IPool(_pool);
}
/// @notice Supply asset to Aave V3 on behalf of msg.sender
/// @param asset ERC20 token to supply
/// @param amount Amount in token's native decimals
function supplyToAave(address asset, uint256 amount) external {
IERC20(asset).transferFrom(msg.sender, address(this), amount);
IERC20(asset).approve(address(pool), amount);
pool.supply(asset, amount, msg.sender, 0);
}
}
Borrow (TypeScript)
// Variable rate = 2. Stable rate (1) is deprecated on most markets.
const VARIABLE_RATE = 2n;
const borrowHash = await walletClient.writeContract({
address: POOL,
abi: poolAbi,
functionName: "borrow",
args: [USDC, parseUnits("500", 6), VARIABLE_RATE, 0, account.address],
});
const borrowReceipt = await publicClient.waitForTransactionReceipt({ hash: borrowHash });
if (borrowReceipt.status !== "success") {
throw new Error("Borrow transaction reverted");
}
Repay (TypeScript)
const repayAmount = parseUnits("500", 6);
// Approve Pool to pull repayment
const repayApproveHash = await walletClient.writeContract({
address: USDC,
abi: erc20Abi,
functionName: "approve",
args: [POOL, repayAmount],
});
await publicClient.waitForTransactionReceipt({ hash: repayApproveHash });
// type(uint256).max to repay entire debt
const repayHash = await walletClient.writeContract({
address: POOL,
abi: poolAbi,
functionName: "repay",
args: [USDC, repayAmount, 2n, account.address],
});
const repayReceipt = await publicClient.waitForTransactionReceipt({ hash: repayHash });
if (repayReceipt.status !== "success") {
throw new Error("Repay transaction reverted");
}
Withdraw (TypeScript)
// type(uint256).max withdraws entire balance
const maxUint256 = 2n ** 256n - 1n;
const withdrawHash = await walletClient.writeContract({
address: POOL,
abi: poolAbi,
functionName: "withdraw",
args: [USDC, maxUint256, account.address],
});
const withdrawReceipt = await publicClient.waitForTransactionReceipt({
hash: withdrawHash,
});
if (withdrawReceipt.status !== "success") {
throw new Error("Withdraw transaction reverted");
}
Repay and Withdraw (Solidity)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IPool} from "@aave/aave-v3-core/contracts/interfaces/IPool.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract AavePositionManager {
IPool public immutable pool;
constructor(address _pool) {
pool = IPool(_pool);
}
/// @notice Repay variable-rate debt on behalf of msg.sender
function repayDebt(address asset, uint256 amount) external {
IERC20(asset).transferFrom(msg.sender, address(this), amount);
IERC20(asset).approve(address(pool), amount);
// interestRateMode 2 = variable rate
pool.repay(asset, amount, 2, msg.sender);
}
/// @notice Withdraw supplied asset back to msg.sender
/// @param amount Use type(uint256).max to withdraw entire balance
function withdrawFromAave(address asset, uint256 amount) external {
pool.withdraw(asset, amount, msg.sender);
}
}
Flash Loans
V3 provides flashLoanSimple for single-asset flash loans (simpler interface, lower gas) and flashLoan for multi-asset. The fee is 0.05% by default.
Flash Loan Receiver (Solidity)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IPool} from "@aave/aave-v3-core/contracts/interfaces/IPool.sol";
import {IFlashLoanSimpleReceiver} from
"@aave/aave-v3-core/contracts/flashloan/base/FlashLoanSimpleReceiver.sol";
import {IPoolAddressesProvider} from
"@aave/aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SimpleFlashLoan is IFlashLoanSimpleReceiver {
IPoolAddressesProvider public immutable override ADDRESSES_PROVIDER;
IPool public immutable override POOL;
constructor(address provider) {
ADDRESSES_PROVIDER = IPoolAddressesProvider(provider);
POOL = IPool(IPoolAddressesProvider(provider).getPool());
}
/// @notice Called by Aave Pool after flash loan funds are transferred
/// @dev Must approve Pool to pull back (amount + premium) before returning
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata /* params */
) external override returns (bool) {
if (msg.sender != address(POOL)) revert("Caller not Pool");
if (initiator != address(this)) revert("Initiator not this contract");
// --- Custom logic here ---
// You have `amount` of `asset` available in this contract.
// Do arbitrage, liquidation, collateral swap, etc.
// Repay flash loan: approve Pool to pull amount + fee
uint256 amountOwed = amount + premium;
IERC20(asset).approve(address(POOL), amountOwed);
return true;
}
/// @notice Trigger a flash loan
/// @param asset Token to borrow
/// @param amount Amount to flash borrow
function requestFlashLoan(address asset, uint256 amount) external {
POOL.flashLoanSimple(address(this), asset, amount, "", 0);
}
}
Trigger Flash Loan (TypeScript)
const flashLoanContractAddress = "0x...YOUR_DEPLOYED_CONTRACT..." as `0x${string}`;
const flashLoanAbi = [
{
name: "requestFlashLoan",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [],
},
] as const;
// Flash borrow 1M USDC
const txHash = await walletClient.writeContract({
address: flashLoanContractAddress,
abi: flashLoanAbi,
functionName: "requestFlashLoan",
args: [USDC, parseUnits("1000000", 6)],
});
const flashReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
if (flashReceipt.status !== "success") {
throw new Error("Flash loan reverted");
}
Reading Protocol State
Get User Account Data
const [
totalCollateralBase,
totalDebtBase,
availableBorrowsBase,
currentLiquidationThreshold,
ltv,
healthFactor,
] = await publicClient.readContract({
address: POOL,
abi: poolAbi,
functionName: "getUserAccountData",
args: [account.address],
});
// All "Base" values are in USD with 8 decimals (Aave oracle base currency)
const collateralUsd = Number(totalCollateralBase) / 1e8;
const debtUsd = Number(totalDebtBase) / 1e8;
// healthFactor has 18 decimals. Below 1e18 = liquidatable.
const hf = Number(healthFactor) / 1e18;
console.log(`Health Factor: ${hf}`);
console.log(`Collateral: $${collateralUsd}, Debt: $${debtUsd}`);
Get Reserve Data
const reserveData = await publicClient.readContract({
address: POOL,
abi: poolAbi,
functionName: "getReserveData",
args: [USDC],
});
// Supply APY: currentLiquidityRate is a ray (27 decimals)
const supplyRateRay = reserveData.currentLiquidityRate;
const supplyAPY = Number(supplyRateRay) / 1e27;
console.log(`USDC Supply APY: ${(supplyAPY * 100).toFixed(2)}%`);
// aToken address for this reserve
const aTokenAddress = reserveData.aTokenAddress;
Track aToken Balance
// aToken balance includes accrued interest (rebases every block)
const aUsdcBalance = await publicClient.readContract({
address: reserveData.aTokenAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [account.address],
});
// Balance in human-readable format (USDC has 6 decimals)
const balanceFormatted = Number(aUsdcBalance) / 1e6;
console.log(`aUSDC balance: ${balanceFormatted}`);
Contract Addresses
Last verified: 2025-05-01
All Aave V3 deployments share the same interface. Addresses sourced from @bgd-labs/aave-address-book and official Aave governance.
Pool (main entry point for all operations)
| Chain | Address |
|---|---|
| Ethereum | 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2 |
| Arbitrum | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| Optimism | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| Polygon | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| Base | 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5 |
PoolAddressesProvider
| Chain | Address |
|---|---|
| Ethereum | 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e |
| Arbitrum | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
| Optimism | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
| Polygon | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
| Base | 0xe20fCBdBfFC4Dd138cE8b2E6FBb6CB49777ad64D |
Aave Oracle
| Chain | Address |
|---|---|
| Ethereum | 0x54586bE62E3c3580375aE3723C145253060Ca0C2 |
| Arbitrum | 0xb56c2F0B653B2e0b10C9b928C8580Ac5Df02C7C7 |
| Optimism | 0xD81eb3728a631871a7eBBaD631b5f424909f0c77 |
| Polygon | 0xb023e699F5a33916Ea823A16485e259257cA8Bd1 |
| Base | 0x2Cc0Fc26eD4563A5ce5e8bdcfe1A2878676Ae156 |
Common Token Addresses (Ethereum Mainnet)
| Token | Address |
|---|---|
| WETH | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
| USDC | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| USDT | 0xdAC17F958D2ee523a2206206994597C13D831ec7 |
| DAI | 0x6B175474E89094C44Da98b954EedeAC495271d0F |
| WBTC | 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599 |
Verify any address before mainnet use:
cast code <address> --rpc-url $RPC_URL
E-Mode (Efficiency Mode)
E-Mode lets users achieve higher capital efficiency when borrowing and supplying correlated assets (e.g., stablecoins against stablecoins, ETH against stETH).
How It Works
Each E-Mode category defines:
- Higher LTV (e.g., 97% for stablecoins vs 75% default)
- Higher liquidation threshold (e.g., 97.5%)
- Lower liquidation bonus (e.g., 1% vs 5%)
- Optional oracle override for the category
Common E-Mode Categories
| ID | Label | Typical LTV | Use Case |
|---|---|---|---|
| 0 | None (default) | Varies per asset | General lending |
| 1 | Stablecoins | 97% | Borrow USDT against USDC |
| 2 | ETH correlated | 93% | Borrow WETH against wstETH |
Category IDs and parameters vary by chain and market. Query on-chain.
Enable E-Mode (TypeScript)
// Enable stablecoin E-Mode (category 1)
const emodeHash = await walletClient.writeContract({
address: POOL,
abi: poolAbi,
functionName: "setUserEMode",
args: [1],
});
await publicClient.waitForTransactionReceipt({ hash: emodeHash });
Enable E-Mode (Solidity)
// Enable E-Mode before supplying/borrowing for higher LTV
pool.setUserEMode(1); // 1 = stablecoins category
// To disable, set back to 0
// Reverts if current position would be undercollateralized without E-Mode
pool.setUserEMode(0);
E-Mode Constraint
You can only borrow assets that belong to the active E-Mode category. Supplying is unrestricted. Setting E-Mode to 0 reverts if your position would become unhealthy at default LTV/threshold.
Error Handling
| Error Code | Name | Cause | Fix |
|---|---|---|---|
| 1 | CALLER_NOT_POOL_ADMIN | Non-admin calling admin function | Use correct admin account |
| 26 | COLLATERAL_CANNOT_COVER_NEW_BORROW | Insufficient collateral for borrow | Supply more collateral or borrow less |
| 27 | COLLATERAL_SAME_AS_BORROWING_CURRENCY | Cannot use same asset as collateral and borrow in isolation mode | Use a different collateral |
| 28 | AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLE | Stable rate borrow exceeds limit | Use variable rate (interestRateMode = 2) |
| 29 | NO_DEBT_OF_SELECTED_TYPE | Repaying debt type that does not exist | Check interestRateMode matches your debt |
| 30 | NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALF | Repaying on behalf with type(uint256).max | Specify exact repay amount when paying for another user |
| 35 | HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD | Action would make position liquidatable | Reduce borrow amount or add collateral |
| 36 | INCONSISTENT_EMODE_CATEGORY | Borrowing asset outside active E-Mode category | Switch E-Mode or borrow a compatible asset |
| 50 | SUPPLY_CAP_EXCEEDED | Asset supply cap reached | Wait for withdrawals or use a different market |
| 51 | BORROW_CAP_EXCEEDED | Asset borrow cap reached | Wait for repayments or use a different market |
Full error code list:
contracts/protocol/libraries/helpers/Errors.solin aave-v3-core
Handling Reverts in TypeScript
import { BaseError, ContractFunctionRevertedError } from "viem";
try {
await publicClient.simulateContract({
address: POOL,
abi: poolAbi,
functionName: "borrow",
args: [USDC, parseUnits("500", 6), 2n, 0, account.address],
account: account.address,
});
} catch (err) {
if (err instanceof BaseError) {
const revertError = err.walk(
(e) => e instanceof ContractFunctionRevertedError
);
if (revertError instanceof ContractFunctionRevertedError) {
const errorName = revertError.data?.errorName;
console.error(`Aave revert: ${errorName}`);
}
}
}
Security
Health Factor Monitoring
A health factor below 1.0 means the position is liquidatable. Third-party liquidators actively monitor the mempool. Always maintain a buffer.
async function checkHealthFactor(
userAddress: `0x${string}`
): Promise<{ safe: boolean; healthFactor: number }> {
const [, , , , , healthFactor] = await publicClient.readContract({
address: POOL,
abi: poolAbi,
functionName: "getUserAccountData",
args: [userAddress],
});
const hf = Number(healthFactor) / 1e18;
// 1.5 is a conservative safety buffer
return { safe: hf > 1.5, healthFactor: hf };
}
Liquidation Risk Factors
- Oracle price movement — If collateral price drops or debt price increases, health factor drops. Aave uses Chainlink oracles; check feed freshness.
- Accruing interest — Variable borrow rates compound. A 50% APY borrow accumulates debt faster than most users expect.
- E-Mode exit — Disabling E-Mode instantly applies lower LTV/thresholds. A safe E-Mode position may become liquidatable at default parameters.
- Supply cap filling — If you need to add emergency collateral and the supply cap is full, you cannot. Diversify collateral types.
Best Practices
- Simulate before executing — Always call
simulateContractbeforewriteContractto catch reverts without spending gas. - Check
receipt.status— A confirmed transaction can still revert. Always verifyreceipt.status === "success". - Monitor health factor off-chain — Set up alerts when HF drops below 2.0. Automate repayment or collateral addition below 1.5.
- Never hardcode gas limits for Aave calls — Pool operations have variable gas costs depending on reserves touched, E-Mode state, and isolation mode. Let the node estimate.
- Approve exact amounts — Avoid
type(uint256).maxapprovals in production. Approve only what is needed per transaction.
References
pyth
View full →Author
@0xinit
Stars
53
Repository
0xinit/cryptoskills
Pyth Network Development Guide
Pyth Network is a decentralized oracle providing real-time price feeds for cryptocurrencies, equities, forex, and commodities. This guide covers integrating Pyth price feeds into Solana applications.
Overview
Pyth Network provides:
- Real-Time Price Feeds - 400ms update frequency with pull oracle model
- Confidence Intervals - Statistical uncertainty bounds for each price
- EMA Prices - Exponential moving average prices (~1 hour window)
- Multi-Asset Support - Crypto, equities, FX, commodities, indices
- On-Chain Integration - CPI for Solana programs
- Off-Chain Integration - HTTP and WebSocket APIs via Hermes
Program IDs
| Program | Address | Description |
|---|---|---|
| Solana Receiver | rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ | Posts price updates to Solana |
| Price Feed | pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT | Stores price feed data |
Deployed on: Solana Mainnet, Devnet, Eclipse Mainnet/Testnet, Sonic networks
Popular Price Feed IDs
| Asset | Hex Feed ID |
|---|---|
| BTC/USD | 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43 |
| ETH/USD | 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace |
| SOL/USD | 0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d |
| USDC/USD | 0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a |
| USDT/USD | 0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b |
Full list: https://pyth.network/developers/price-feed-ids
Quick Start
Installation
# TypeScript/JavaScript
npm install @pythnetwork/hermes-client @pythnetwork/pyth-solana-receiver
# Rust (add to Cargo.toml)
# pyth-solana-receiver-sdk = "0.3.0"
Fetch Price (Off-Chain)
import { HermesClient } from "@pythnetwork/hermes-client";
const client = new HermesClient("https://hermes.pyth.network");
const priceIds = [
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", // BTC/USD
];
const priceUpdates = await client.getLatestPriceUpdates(priceIds);
for (const update of priceUpdates.parsed) {
const price = update.price;
const displayPrice = Number(price.price) * Math.pow(10, price.expo);
console.log(`Price: $${displayPrice.toFixed(2)}`);
console.log(`Confidence: ±${Number(price.conf) * Math.pow(10, price.expo)}`);
}
Use Price On-Chain (Rust/Anchor)
use anchor_lang::prelude::*;
use pyth_solana_receiver_sdk::price_update::PriceUpdateV2;
#[derive(Accounts)]
pub struct UsePrice<'info> {
pub price_update: Account<'info, PriceUpdateV2>,
}
pub fn use_price(ctx: Context<UsePrice>) -> Result<()> {
let price_update = &ctx.accounts.price_update;
let clock = Clock::get()?;
// Get price no older than 60 seconds
let price = price_update.get_price_no_older_than(
&clock,
60, // max age in seconds
)?;
msg!("Price: {} × 10^{}", price.price, price.exponent);
msg!("Confidence: ±{}", price.conf);
Ok(())
}
Core Concepts
Price Structure
Each Pyth price contains:
| Field | Type | Description |
|---|---|---|
price | i64 | Price value in fixed-point format |
conf | u64 | Confidence interval (standard deviation) |
expo | i32 | Exponent for scaling (e.g., -8 means divide by 10^8) |
publish_time | i64 | Unix timestamp of price |
Converting to display price:
const displayPrice = price * Math.pow(10, expo);
// Example: price=19405100, expo=-2 → $194,051.00
Confidence Intervals
Confidence intervals represent the uncertainty in the reported price:
// Price is $50,000 ± $50 means:
// - 68% chance true price is between $49,950 - $50,050
// - Use confidence for risk management
const price = 50000;
const confidence = 50;
// Safe lower bound (conservative)
const safeLowerBound = price - confidence;
// Safe upper bound (conservative)
const safeUpperBound = price + confidence;
Best Practice: Reject prices with confidence > 2% of price:
const maxConfidenceRatio = 0.02; // 2%
const confidenceRatio = confidence / Math.abs(price);
if (confidenceRatio > maxConfidenceRatio) {
throw new Error("Price confidence too wide");
}
EMA Prices
Exponential Moving Average prices smooth out short-term volatility:
- ~1 hour averaging window (5921 Solana slots)
- Weighted by inverse confidence (tight confidence = more weight)
- Good for: liquidations, collateral valuation
- Available as
ema_priceandema_conf
// Use EMA for less volatile applications
const emaPrice = priceUpdate.emaPrice;
const emaConf = priceUpdate.emaConf;
Off-Chain Integration
Hermes Client
Hermes is the recommended way to fetch Pyth prices off-chain.
Public Endpoint: https://hermes.pyth.network
For production, get a dedicated endpoint from a Pyth data provider.
Fetching Latest Prices
import { HermesClient } from "@pythnetwork/hermes-client";
const client = new HermesClient("https://hermes.pyth.network");
// Single price
const btcPrice = await client.getLatestPriceUpdates([
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
]);
// Multiple prices in one request
const prices = await client.getLatestPriceUpdates([
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", // BTC
"0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", // ETH
"0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d", // SOL
]);
Streaming Real-Time Updates
import { HermesClient } from "@pythnetwork/hermes-client";
const client = new HermesClient("https://hermes.pyth.network");
const priceIds = [
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
];
// Subscribe to real-time updates via SSE
const eventSource = await client.getPriceUpdatesStream(priceIds, {
parsed: true,
});
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Price update:", data);
};
eventSource.onerror = (error) => {
console.error("Stream error:", error);
eventSource.close();
};
// Close when done
// eventSource.close();
Posting Prices to Solana
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
import { HermesClient } from "@pythnetwork/hermes-client";
import { Connection, Keypair } from "@solana/web3.js";
const connection = new Connection("https://api.mainnet-beta.solana.com");
const wallet = Keypair.fromSecretKey(/* your key */);
const hermesClient = new HermesClient("https://hermes.pyth.network");
const pythReceiver = new PythSolanaReceiver({ connection, wallet });
// Fetch price update data
const priceUpdateData = await hermesClient.getLatestPriceUpdates([
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
]);
// Build transaction to post price
const transactionBuilder = pythReceiver.newTransactionBuilder();
await transactionBuilder.addPostPriceUpdates(priceUpdateData.binary.data);
// Add your program instruction that uses the price
// transactionBuilder.addInstruction(yourInstruction);
// Send transaction
const transactions = await transactionBuilder.buildVersionedTransactions({
computeUnitPriceMicroLamports: 50000,
});
for (const tx of transactions) {
const sig = await connection.sendTransaction(tx);
console.log("Transaction:", sig);
}
On-Chain Integration (Rust)
Setup
Add to Cargo.toml:
[dependencies]
pyth-solana-receiver-sdk = "0.3.0"
anchor-lang = "0.30.1"
Reading Price in Anchor Program
use anchor_lang::prelude::*;
use pyth_solana_receiver_sdk::price_update::{PriceUpdateV2, get_feed_id_from_hex};
declare_id!("YourProgramId...");
// BTC/USD price feed ID
const BTC_USD_FEED_ID: &str = "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43";
#[program]
pub mod my_program {
use super::*;
pub fn check_price(ctx: Context<CheckPrice>) -> Result<()> {
let price_update = &ctx.accounts.price_update;
let clock = Clock::get()?;
// Verify this is the correct feed
let feed_id = get_feed_id_from_hex(BTC_USD_FEED_ID)?;
// Get price no older than 60 seconds
let price = price_update.get_price_no_older_than_with_custom_verification(
&clock,
60,
&feed_id,
ctx.accounts.price_update.to_account_info().owner,
)?;
msg!("BTC/USD Price: {} × 10^{}", price.price, price.exponent);
msg!("Confidence: ±{}", price.conf);
Ok(())
}
}
#[derive(Accounts)]
pub struct CheckPrice<'info> {
#[account(
constraint = price_update.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub price_update: Account<'info, PriceUpdateV2>,
}
Using Price for Calculations
pub fn swap_with_oracle(
ctx: Context<SwapWithOracle>,
amount_in: u64,
) -> Result<()> {
let price_update = &ctx.accounts.price_update;
let clock = Clock::get()?;
// Get price with staleness check
let price = price_update.get_price_no_older_than(&clock, 30)?;
// Validate confidence (max 1% of price)
let conf_ratio = (price.conf as u128 * 10000) / (price.price.unsigned_abs() as u128);
require!(conf_ratio <= 100, ErrorCode::ConfidenceTooWide);
// Convert price to usable format
// price.price is in fixed-point with price.exponent
let price_scaled = if price.exponent >= 0 {
(price.price as u128) * 10_u128.pow(price.exponent as u32)
} else {
(price.price as u128) / 10_u128.pow((-price.exponent) as u32)
};
// Calculate output amount using oracle price
let amount_out = (amount_in as u128)
.checked_mul(price_scaled)
.ok_or(ErrorCode::MathOverflow)?
/ 1_000_000; // Adjust for decimals
msg!("Swap {} -> {} using price {}", amount_in, amount_out, price_scaled);
Ok(())
}
#[error_code]
pub enum ErrorCode {
#[msg("Price confidence interval too wide")]
ConfidenceTooWide,
#[msg("Math overflow")]
MathOverflow,
}
Multiple Price Feeds
#[derive(Accounts)]
pub struct Liquidation<'info> {
#[account(
constraint = collateral_price.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub collateral_price: Account<'info, PriceUpdateV2>,
#[account(
constraint = debt_price.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub debt_price: Account<'info, PriceUpdateV2>,
}
pub fn check_liquidation(ctx: Context<Liquidation>) -> Result<bool> {
let clock = Clock::get()?;
let collateral = ctx.accounts.collateral_price
.get_price_no_older_than(&clock, 60)?;
let debt = ctx.accounts.debt_price
.get_price_no_older_than(&clock, 60)?;
// Normalize to same exponent for comparison
let collateral_value = normalize_price(collateral.price, collateral.exponent);
let debt_value = normalize_price(debt.price, debt.exponent);
// Check if undercollateralized
let is_liquidatable = collateral_value < debt_value * 150 / 100; // 150% ratio
Ok(is_liquidatable)
}
fn normalize_price(price: i64, expo: i32) -> i128 {
let target_expo = -8; // Normalize to 8 decimals
let adjustment = expo - target_expo;
if adjustment >= 0 {
(price as i128) * 10_i128.pow(adjustment as u32)
} else {
(price as i128) / 10_i128.pow((-adjustment) as u32)
}
}
Best Practices
1. Always Check Staleness
// Don't use old prices - set appropriate max age
let max_age_seconds = 60;
let price = price_update.get_price_no_older_than(&clock, max_age_seconds)?;
2. Validate Confidence Intervals
// Reject prices with wide confidence (high uncertainty)
const MAX_CONF_BPS: u64 = 200; // 2%
let conf_bps = (price.conf as u128 * 10000) / (price.price.unsigned_abs() as u128);
require!(conf_bps <= MAX_CONF_BPS as u128, ErrorCode::ConfidenceTooWide);
3. Verify Account Ownership
// Always verify the price account is owned by Pyth
#[account(
constraint = price_update.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub price_update: Account<'info, PriceUpdateV2>,
4. Use EMA for Sensitive Operations
// For liquidations, use EMA to avoid manipulation
let ema_price = price_update.get_ema_price_no_older_than(&clock, 60)?;
5. Handle Price Unavailability
try {
const price = await client.getLatestPriceUpdates([feedId]);
// Use price
} catch (error) {
// Fallback behavior or reject transaction
console.error("Price unavailable:", error);
}
6. Consider Frontrunning
- Adversaries may see price updates before your transaction
- Don't design logic that races against price updates
- Use appropriate slippage tolerances
Price Feed Types
Fixed Price Feed Accounts
- Maintained continuously by Pyth
- Fixed address per feed
- Always has most recent price
- Shared by all users (potential congestion)
Ephemeral Price Update Accounts
- Created per transaction
- Can specify shard ID for parallelization
- Rent can be recovered after use
- Better for high-throughput applications
// Use shard ID to avoid congestion
const transactionBuilder = pythReceiver.newTransactionBuilder({
shardId: Math.floor(Math.random() * 65536), // Random shard
});
Resources
Official Documentation
GitHub Repositories
NPM Packages
Rust Crates
Skill Structure
pyth/
├── SKILL.md # This file
├── resources/
│ ├── program-addresses.md # All program IDs and feed IDs
│ └── api-reference.md # SDK API reference
├── examples/
│ ├── price-feeds/
│ │ ├── fetch-price.ts # Basic price fetching
│ │ └── multiple-prices.ts # Multiple price feeds
│ ├── on-chain/
│ │ ├── anchor-integration.rs # Anchor program example
│ │ └── price-validation.rs # Price validation patterns
│ └── streaming/
│ └── real-time-updates.ts # WebSocket streaming
├── templates/
│ ├── pyth-client.ts # TypeScript client template
│ └── anchor-oracle.rs # Anchor program template
└── docs/
└── troubleshooting.md # Common issues and solutions
Pyth on EVM Chains
This skill covers Pyth integration for Solana applications using Anchor CPI. For EVM chain integration (Ethereum, Arbitrum, Base, Optimism, Polygon, and 50+ other chains), see the pyth-evm skill.
Key differences between Pyth Solana and Pyth EVM:
| Aspect | Pyth Solana (this skill) | Pyth EVM (pyth-evm skill) |
|---|---|---|
| Contract interface | Anchor CPI to Pyth program | Solidity IPyth interface |
| Price update | Pull from Pyth accumulator account | Submit bytes[] via updatePriceFeeds |
| Contract address | Single Pyth program on Solana | Varies per EVM chain |
| Gas/compute | Compute units | ~120-150K gas per feed update |
| SDK | @pythnetwork/pyth-solana-receiver | @pythnetwork/hermes-client v3.1.0 |
Price feed IDs (bytes32) are the same across all chains — a BTC/USD feed ID works on both Solana and Ethereum.
Related Skills
pyth-evm— Pyth oracle integration for EVM chains (Solidity + TypeScript)chainlink— Push oracle alternative on EVM chainsredstone— Another pull oracle for EVM chains