mcpField
P
pyth

pyth

github.com/sendaifunデータ・データベース130204 回インストール13 週間前に更新
リポジトリ

pyth について

Pyth Networkの価格フィードをSolanaアプリに組み込むための開発ガイドです。Hermesによるオフチェーン取得とRustでのオンチェーン連携に加え、信頼区間、鮮度、EMA価格の扱いを説明します。

主な機能

  1. 01Hermesでの価格取得とリアルタイム更新
  2. 02Solana上での価格読み取り
  3. 03信頼区間とEMA価格
  4. 04価格の鮮度とアカウント検証の指針

活用シーン

  1. 01アプリで暗号資産などの価格を取得
  2. 02Solanaプログラムでオラクル価格を参照
  3. 03清算や担保評価にEMAを利用
  4. 04古い価格や広い信頼区間に対処

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sendaifun/skills --skill pyth

インストール後、Claude Code で/pythを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

docs/
troubleshooting.md
examples/on-chain/
anchor-integration.rs
price-validation.rs
examples/price-feeds/
fetch-price.ts
multiple-prices.ts
examples/streaming/
real-time-updates.ts
resources/
api-reference.md
program-addresses.md
templates/
anchor-oracle.rs
pyth-client.ts

詳細

Pyth Network 開発ガイド

Pyth Network は、暗号資産、株式、外国為替、コモディティのリアルタイム価格フィードを提供する分散型オラクルです。このガイドでは、Pyth の価格フィードを Solana アプリケーションに統合する方法を説明します。

概要

Pyth Network は以下を提供します。

  • リアルタイム価格フィード - プル型オラクルモデルで400msごとに更新
  • 信頼区間 - 各価格の統計的な不確実性の範囲
  • EMA 価格 - 指数移動平均価格(約1時間のウィンドウ)
  • マルチアセット対応 - 暗号資産、株式、外国為替、コモディティ、指数
  • オンチェーン統合 - Solana プログラム用 CPI
  • オフチェーン統合 - Hermes 経由の HTTP および WebSocket API

プログラム ID

プログラムアドレス説明
Solana Receiverrec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ価格更新を Solana に投稿します
Price FeedpythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT価格フィードデータを保存します

デプロイ先: Solana Mainnet、Devnet、Eclipse Mainnet/Testnet、Sonic ネットワーク

人気の価格フィード ID

資産16進数のフィード ID
BTC/USD0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
ETH/USD0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
SOL/USD0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d
USDC/USD0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a
USDT/USD0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b

全リスト: https://pyth.network/developers/price-feed-ids

クイックスタート

インストール
# TypeScript/JavaScript
npm install @pythnetwork/hermes-client @pythnetwork/pyth-solana-receiver

# Rust (add to Cargo.toml)
# pyth-solana-receiver-sdk = "0.3.0"
価格の取得(オフチェーン)
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)}`);
}
オンチェーンでの価格の使用(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(())
}

基本概念

価格の構造

Pyth の各価格には以下が含まれます。

フィールド型説明
pricei64固定小数点形式の価格値
confu64信頼区間(標準偏差)
expoi32スケーリング用の指数(例:-8 は 10^8 で割ることを意味します)
publish_timei64価格の Unix タイムスタンプ

表示価格への変換:

const displayPrice = price * Math.pow(10, expo);
// Example: price=19405100, expo=-2 → $194,051.00
信頼区間

信頼区間は、報告された価格の不確実性を表します。

// 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;

ベストプラクティス: 信頼区間が価格の2%を超える価格は拒否します。

const maxConfidenceRatio = 0.02; // 2%
const confidenceRatio = confidence / Math.abs(price);

if (confidenceRatio > maxConfidenceRatio) {
  throw new Error("Price confidence too wide");
}
EMA 価格

指数移動平均価格は、短期的なボラティリティを平滑化します。

  • 平均化ウィンドウは約1時間(Solana のスロット数で5921)
  • 信頼区間の逆数で重み付け(信頼区間が狭いほど重みが大きい)
  • 適した用途:清算、担保評価
  • ema_price と ema_conf として利用可能
// Use EMA for less volatile applications
const emaPrice = priceUpdate.emaPrice;
const emaConf = priceUpdate.emaConf;

オフチェーン統合

Hermes クライアント

Hermes は、オフチェーンで Pyth の価格を取得するために推奨される方法です。

パブリックエンドポイント: https://hermes.pyth.network

本番環境では、Pyth データプロバイダーから専用エンドポイントを取得してください。

最新価格の取得
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
]);
リアルタイム更新のストリーミング
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();
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);
}

オンチェーン統合(Rust)

セットアップ

Cargo.toml に追加します。

[dependencies]
pyth-solana-receiver-sdk = "0.3.0"
anchor-lang = "0.30.1"
Anchor プログラムでの価格の読み取り
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>,
}
計算での価格の使用
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,
}
複数の価格フィード
#[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)
    }
}

ベストプラクティス

1. 常に古さを確認する
// 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. 信頼区間を検証する
// 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. アカウントの所有者を確認する
// 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. 重要な処理では EMA を使用する
// For liquidations, use EMA to avoid manipulation
let ema_price = price_update.get_ema_price_no_older_than(&clock, 60)?;
5. 価格が利用できない場合に対処する
try {
  const price = await client.getLatestPriceUpdates([feedId]);
  // Use price
} catch (error) {
  // Fallback behavior or reject transaction
  console.error("Price unavailable:", error);
}
6. フロントランニングを考慮する
  • 攻撃者は、トランザクションより先に価格更新を確認できる場合があります
  • 価格更新との競争を前提とするロジックは設計しないでください
  • 適切なスリッページ許容度を設定してください

価格フィードの種類

固定価格フィードアカウント
  • Pyth によって継続的に維持されます
  • フィードごとに固定アドレスがあります
  • 常に最新の価格が設定されています
  • すべてのユーザーで共有されます(混雑する可能性があります)
エフェメラルな価格更新アカウント
  • トランザクションごとに作成されます
  • 並列化のためにシャード ID を指定できます
  • 使用後にレンタル料を回収できます
  • 高スループットのアプリケーションに適しています
// Use shard ID to avoid congestion
const transactionBuilder = pythReceiver.newTransactionBuilder({
  shardId: Math.floor(Math.random() * 65536), // Random shard
});

リソース

公式ドキュメント
GitHub リポジトリ
NPM パッケージ
Rust クレート

スキルの構成

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

コメント