Comparing aave with pyth

Author

@0xinit

Stars

53

Repository

0xinit/cryptoskills

skills/aave/SKILL.md

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 LendingPool with deposit(). V3 uses Pool with supply(). The function signatures, events, and return types differ. If you see ILendingPool or deposit(), you are writing V2 code. Stop.
  • aTokens rebase — balance changes every blockaToken.balanceOf(user) increases each block as interest accrues. This is not a transfer. Do not try to track balances with Transfer events alone. Use balanceOf() at read time or scaledBalanceOf() 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 = 2 for the interestRateMode parameter. Passing STABLE_RATE = 1 will revert on markets where it is disabled.
  • Health factor is 18-decimal fixed point, not a percentagegetUserAccountData() returns healthFactor as a uint256 with 18 decimals. A health factor of 1e18 means liquidation threshold. Below 1e18 = 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_TOTAL on 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 check getReserveData() for supplyCap before 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)

ChainAddress
Ethereum0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
Arbitrum0x794a61358D6845594F94dc1DB02A252b5b4814aD
Optimism0x794a61358D6845594F94dc1DB02A252b5b4814aD
Polygon0x794a61358D6845594F94dc1DB02A252b5b4814aD
Base0xA238Dd80C259a72e81d7e4664a9801593F98d1c5

PoolAddressesProvider

ChainAddress
Ethereum0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e
Arbitrum0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb
Optimism0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb
Polygon0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb
Base0xe20fCBdBfFC4Dd138cE8b2E6FBb6CB49777ad64D

Aave Oracle

ChainAddress
Ethereum0x54586bE62E3c3580375aE3723C145253060Ca0C2
Arbitrum0xb56c2F0B653B2e0b10C9b928C8580Ac5Df02C7C7
Optimism0xD81eb3728a631871a7eBBaD631b5f424909f0c77
Polygon0xb023e699F5a33916Ea823A16485e259257cA8Bd1
Base0x2Cc0Fc26eD4563A5ce5e8bdcfe1A2878676Ae156

Common Token Addresses (Ethereum Mainnet)

TokenAddress
WETH0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
USDC0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
USDT0xdAC17F958D2ee523a2206206994597C13D831ec7
DAI0x6B175474E89094C44Da98b954EedeAC495271d0F
WBTC0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599

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

IDLabelTypical LTVUse Case
0None (default)Varies per assetGeneral lending
1Stablecoins97%Borrow USDT against USDC
2ETH correlated93%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 CodeNameCauseFix
1CALLER_NOT_POOL_ADMINNon-admin calling admin functionUse correct admin account
26COLLATERAL_CANNOT_COVER_NEW_BORROWInsufficient collateral for borrowSupply more collateral or borrow less
27COLLATERAL_SAME_AS_BORROWING_CURRENCYCannot use same asset as collateral and borrow in isolation modeUse a different collateral
28AMOUNT_BIGGER_THAN_MAX_LOAN_SIZE_STABLEStable rate borrow exceeds limitUse variable rate (interestRateMode = 2)
29NO_DEBT_OF_SELECTED_TYPERepaying debt type that does not existCheck interestRateMode matches your debt
30NO_EXPLICIT_AMOUNT_TO_REPAY_ON_BEHALFRepaying on behalf with type(uint256).maxSpecify exact repay amount when paying for another user
35HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLDAction would make position liquidatableReduce borrow amount or add collateral
36INCONSISTENT_EMODE_CATEGORYBorrowing asset outside active E-Mode categorySwitch E-Mode or borrow a compatible asset
50SUPPLY_CAP_EXCEEDEDAsset supply cap reachedWait for withdrawals or use a different market
51BORROW_CAP_EXCEEDEDAsset borrow cap reachedWait for repayments or use a different market

Full error code list: contracts/protocol/libraries/helpers/Errors.sol in 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

  1. Simulate before executing — Always call simulateContract before writeContract to catch reverts without spending gas.
  2. Check receipt.status — A confirmed transaction can still revert. Always verify receipt.status === "success".
  3. Monitor health factor off-chain — Set up alerts when HF drops below 2.0. Automate repayment or collateral addition below 1.5.
  4. 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.
  5. Approve exact amounts — Avoid type(uint256).max approvals in production. Approve only what is needed per transaction.

References

Author

@0xinit

Stars

53

Repository

0xinit/cryptoskills

skills/pyth/SKILL.md

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

ProgramAddressDescription
Solana Receiverrec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJPosts price updates to Solana
Price FeedpythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsTStores price feed data

Deployed on: Solana Mainnet, Devnet, Eclipse Mainnet/Testnet, Sonic networks

Popular Price Feed IDs

AssetHex Feed ID
BTC/USD0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
ETH/USD0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
SOL/USD0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d
USDC/USD0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a
USDT/USD0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b

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:

FieldTypeDescription
pricei64Price value in fixed-point format
confu64Confidence interval (standard deviation)
expoi32Exponent for scaling (e.g., -8 means divide by 10^8)
publish_timei64Unix 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_price and ema_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:

AspectPyth Solana (this skill)Pyth EVM (pyth-evm skill)
Contract interfaceAnchor CPI to Pyth programSolidity IPyth interface
Price updatePull from Pyth accumulator accountSubmit bytes[] via updatePriceFeeds
Contract addressSingle Pyth program on SolanaVaries per EVM chain
Gas/computeCompute 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 chains
  • redstone — Another pull oracle for EVM chains

AI Skill Finder

Ask me what skills you need

What are you building?

Tell me what you're working on and I'll find the best agent skills for you.