mcpField
J
jupiter-swap-integration

jupiter-swap-integration

github.com/sanctifiedops開発347 回インストール40 週間前に更新
リポジトリ

jupiter-swap-integration について

Jupiter Swap連携の開発スキルで、見積もり、トランザクションの構築・送信、スリッページ、手数料、エラー処理を扱います。スワップ画面、取引ボット、バックエンドサービスの開発向けです。

主な機能

  1. 01Jupiter APIの見積もりフロー
  2. 02V0トランザクションの構築と送信
  3. 03トークン別のスリッページ設定
  4. 04優先手数料の戦略
  5. 05リトライとレート制限への対応
  6. 06スワップ画面とボットのテンプレート

活用シーン

  1. 01SOLから他トークンへのスワップ機能の開発
  2. 02取引ボットのスリッページとリトライ設定
  3. 03ネットワーク混雑時の優先手数料への対応
  4. 04状態表示付きスワップ画面の構築

インストール

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

$npx skills add https://github.com/sanctifiedops/solana-skills --skill jupiter-swap-integration

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

詳細

Jupiter スワップ統合

役割の設定:あなたは、スワップインターフェースやトレードボットを構築する Jupiter 統合のスペシャリストです。適切なエラー処理とユーザーエクスペリエンスを備えた、信頼性と効率性の高いスワップを実装することが目標です。

初期評価

  • 何を構築していますか:フロントエンドのスワップ UI、トレードボット、それともバックエンドサービスですか?
  • 想定取引量:一般ユーザーのスワップですか、それとも高頻度取引ですか?
  • トークンの種類:主要なトークン(SOL/USDC)ですか、それともマイナーなミームコインですか?
  • スリッページ許容度の要件:低め(0.5%)ですか、高め(5% 以上)ですか?
  • 高度な機能は必要ですか:指値注文、DCA、それとも現物スワップのみですか?
  • RPC の構成はどうなっていますか:パブリックエンドポイントですか、それとも専用プロバイダーですか?
  • エラー処理の要件は何ですか:再試行ロジック、フォールバック戦略などですか?

基本原則

  • Jupiter は集約しますが、実行はしません:DEX 全体から最適なルートを探し、トランザクションを送信するのはあなたです。
  • スリッページは提案ではなく保護策です:ボラティリティに応じて設定します。低すぎるとトランザクションが失敗し、高すぎると約定価格が悪くなります。
  • 優先手数料は不可欠です:優先手数料がないと、ネットワーク混雑時にスワップが失敗します。0.0001-0.001 SOL を予算に確保してください。
  • 見積もり ≠ 実行の保証:価格は変動するため、より良い約定のために常に onlyDirectRoutes: false を使用してください。
  • レート制限があります:無料プランは 60 リクエスト/分で、有料プランでは上限がさらに高くなります。可能な場合は見積もりをキャッシュしてください。
  • バージョン付きトランザクションが必要です:Jupiter は V0 トランザクションを返すため、ウォレットや SDK が処理できることを確認してください。

ワークフロー

1. API のセットアップと認証
// Base URLs
const JUPITER_API = 'https://quote-api.jup.ag/v6';
const JUPITER_PRICE_API = 'https://price.jup.ag/v6';

// No API key required for basic usage
// For high volume, contact Jupiter for dedicated endpoints

// Rate limits (free tier):
// - Quote API: 60 requests/minute
// - Price API: 600 requests/minute
2. 見積もりの取得
interface QuoteParams {
  inputMint: string;      // Token to sell
  outputMint: string;     // Token to buy
  amount: string;         // Amount in smallest units (lamports/base units)
  slippageBps: number;    // Slippage in basis points (100 = 1%)
  onlyDirectRoutes?: boolean;  // false = better routes, true = simpler
  asLegacyTransaction?: boolean; // false = versioned tx (recommended)
}

async function getQuote(params: QuoteParams) {
  const url = new URL(`${JUPITER_API}/quote`);
  url.searchParams.set('inputMint', params.inputMint);
  url.searchParams.set('outputMint', params.outputMint);
  url.searchParams.set('amount', params.amount);
  url.searchParams.set('slippageBps', params.slippageBps.toString());

  const response = await fetch(url.toString());
  if (!response.ok) {
    throw new Error(`Quote failed: ${response.status}`);
  }
  return response.json();
}

// Example: Swap 1 SOL to USDC
const quote = await getQuote({
  inputMint: 'So11111111111111111111111111111111111111112', // SOL
  outputMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
  amount: '1000000000', // 1 SOL in lamports
  slippageBps: 50, // 0.5%
});

// Quote response includes:
// - inAmount: input amount
// - outAmount: expected output (before slippage)
// - otherAmountThreshold: minimum output (after slippage)
// - routePlan: array of swap steps
// - priceImpactPct: price impact percentage
3. スワップトランザクションの構築
interface SwapParams {
  quoteResponse: any;
  userPublicKey: string;
  wrapAndUnwrapSol?: boolean;  // true = auto wrap/unwrap SOL
  computeUnitPriceMicroLamports?: number; // priority fee
  dynamicComputeUnitLimit?: boolean; // auto-size compute
}

async function getSwapTransaction(params: SwapParams) {
  const response = await fetch(`${JUPITER_API}/swap`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      quoteResponse: params.quoteResponse,
      userPublicKey: params.userPublicKey,
      wrapAndUnwrapSol: params.wrapAndUnwrapSol ?? true,
      computeUnitPriceMicroLamports: params.computeUnitPriceMicroLamports ?? 1000,
      dynamicComputeUnitLimit: params.dynamicComputeUnitLimit ?? true,
    }),
  });

  const { swapTransaction } = await response.json();
  return swapTransaction; // Base64 encoded versioned transaction
}
4. トランザクションへの署名と送信
import { VersionedTransaction, Connection } from '@solana/web3.js';

async function executeSwap(
  swapTransaction: string,
  wallet: any, // Wallet adapter
  connection: Connection
) {
  // Decode the transaction
  const txBuffer = Buffer.from(swapTransaction, 'base64');
  const tx = VersionedTransaction.deserialize(txBuffer);

  // Sign with wallet
  const signedTx = await wallet.signTransaction(tx);

  // Send with retry logic
  const signature = await connection.sendTransaction(signedTx, {
    skipPreflight: false,
    maxRetries: 3,
  });

  // Confirm transaction
  const confirmation = await connection.confirmTransaction(signature, 'confirmed');

  if (confirmation.value.err) {
    throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`);
  }

  return signature;
}
5. トークンの種類に応じたスリッページ戦略
トークンの種類推奨スリッページ理由
SOL/USDC0.1-0.3%流動性が高く、安定
主要なトークン(JUP、BONK)0.5-1%流動性が良好
中型ミームコイン1-3%流動性が変動
新規/小型トークン3-10%流動性が薄く、価格変動が大きい
Pump.fun トークン5-15%価格変動が非常に大きい
6. 優先手数料戦略
// Estimate priority fee based on network conditions
async function estimatePriorityFee(connection: Connection): Promise<number> {
  const recentFees = await connection.getRecentPrioritizationFees();

  if (recentFees.length === 0) return 1000; // default 1000 micro-lamports

  // Use 75th percentile for reliability
  const sorted = recentFees.map(f => f.prioritizationFee).sort((a, b) => a - b);
  const p75Index = Math.floor(sorted.length * 0.75);

  return Math.max(sorted[p75Index], 1000);
}

// Priority fee tiers:
// - 1,000 micro-lamports: Normal conditions
// - 10,000 micro-lamports: Moderate congestion
// - 100,000 micro-lamports: High congestion
// - 1,000,000+ micro-lamports: Extreme (minting events, etc.)
7. エラー処理と再試行
async function swapWithRetry(
  params: SwapParams,
  maxRetries: number = 3
): Promise<string> {
  let lastError: Error | null = null;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      // Get fresh quote (prices change)
      const quote = await getQuote(params.quoteParams);

      // Check if price moved too much
      if (quote.priceImpactPct > 5) {
        throw new Error(`Price impact too high: ${quote.priceImpactPct}%`);
      }

      // Get swap transaction
      const swapTx = await getSwapTransaction({
        quoteResponse: quote,
        userPublicKey: params.userPublicKey,
        computeUnitPriceMicroLamports: params.priorityFee * attempt, // Increase fee on retry
      });

      // Execute
      return await executeSwap(swapTx, params.wallet, params.connection);

    } catch (error: any) {
      lastError = error;

      // Don't retry on certain errors
      if (error.message.includes('insufficient funds')) throw error;
      if (error.message.includes('slippage')) throw error;

      // Wait before retry (exponential backoff)
      await new Promise(r => setTimeout(r, 1000 * attempt));
    }
  }

  throw lastError || new Error('Swap failed after retries');
}

テンプレート/プレイブック

フロントエンドのスワップコンポーネント構成
// State management
interface SwapState {
  inputToken: Token | null;
  outputToken: Token | null;
  inputAmount: string;
  quote: QuoteResponse | null;
  loading: boolean;
  error: string | null;
  txStatus: 'idle' | 'signing' | 'confirming' | 'success' | 'failed';
}

// Component flow:
// 1. User selects tokens → fetch quote
// 2. User enters amount → debounced quote refresh
// 3. Display: output amount, price impact, route
// 4. User clicks swap → sign → send → confirm
// 5. Show success/failure with tx link

// Quote refresh interval: 10-15 seconds (balance freshness vs rate limits)
ボットのスワップ設定
interface BotSwapConfig {
  // Execution
  maxSlippageBps: number;
  minPriorityFeeMicroLamports: number;
  maxPriorityFeeMicroLamports: number;
  maxRetries: number;

  // Safety
  maxTradeSize: number; // In USD
  maxPriceImpact: number; // Percentage
  cooldownMs: number; // Between trades

  // Monitoring
  logAllQuotes: boolean;
  alertOnFailure: boolean;
}

const defaultBotConfig: BotSwapConfig = {
  maxSlippageBps: 100,
  minPriorityFeeMicroLamports: 1000,
  maxPriorityFeeMicroLamports: 100000,
  maxRetries: 3,
  maxTradeSize: 1000,
  maxPriceImpact: 3,
  cooldownMs: 1000,
  logAllQuotes: true,
  alertOnFailure: true,
};
価格への影響のしきい値
function assessPriceImpact(impactPct: number): {
  level: 'low' | 'medium' | 'high' | 'extreme';
  warning: string | null;
} {
  if (impactPct < 0.5) return { level: 'low', warning: null };
  if (impactPct < 2) return { level: 'medium', warning: 'Moderate price impact' };
  if (impactPct < 5) return { level: 'high', warning: 'High price impact - consider smaller trade' };
  return { level: 'extreme', warning: 'Extreme price impact - trade size too large for liquidity' };
}

よくある失敗モードとデバッグ

「トランザクションのシミュレーションに失敗しました」
  • 原因:見積もりが古い、または価格がスリッページの範囲を超えて変動した
  • 検出方法:エラーメッセージに「slippage」または「amount out」が含まれる
  • 解決方法:スリッページを上げるか、実行前に見積もりを更新する
「トランザクションの期限が切れました」
  • 原因:確認前(60 秒超)にブロックハッシュの有効期限が切れた
  • 検出方法:エラーメッセージに「blockhash not found」が含まれる
  • 解決方法:優先手数料を上げる。skipPreflight: false を使用する
「手数料分の SOL が不足しています」
  • 原因:ユーザーの SOL が、レンタル料と優先手数料を支払うには不足している
  • 検出方法:スワップ前に残高を事前確認する
  • 解決方法:最低 0.01 SOL を確保し、ユーザーに警告する
「Jupiter によるレート制限」
  • 原因:1 分あたり 60 件のリクエスト上限を超えた
  • 検出方法:ステータスコード 429
  • 解決方法:リクエストキューを実装し、見積もりをキャッシュし、有料プランを利用する
「ルートが見つかりません」
  • 原因:トークン間に流動性のある経路がない
  • 検出方法:見積もりレスポンスのルートが空
  • 解決方法:トークンに流動性があるか確認する。中間トークン(SOL/USDC)を試す
「トランザクションが大きすぎます」
  • 原因:ホップ数の多い複雑なルート
  • 検出方法:エラーにトランザクションサイズについての記述がある
  • 解決方法:onlyDirectRoutes: true を使用するか、コンピュート予算を増やす

品質基準/検証

統合が完了したと判断できる条件:

  • 適切なエラー処理により、見積もりを安定して取得できる
  • トークンの種類に応じて適切なスリッページが設定されている
  • 優先手数料がネットワークの状態に合わせて調整される
  • 失敗したトランザクションがバックオフ付きで再試行される
  • 各段階(読み込み、署名、確認)でユーザーに明確なステータスが表示される
  • 大口取引では価格への影響に関する警告が表示される
  • レート制限が適切に処理される
  • 成功時にトランザクションへのリンクが表示される

出力形式

以下を提示してください。

  1. 実装コード:適切な型とエラーハンドリングを備えた TypeScript
  2. 設定値:スリッページ、優先手数料、タイムアウト
  3. エラーハンドリング戦略:再試行するエラーと、ユーザーに通知するエラー
  4. UX に関する考慮事項:読み込み状態、確認、警告
  5. テストチェックリスト:デプロイ前に検証するシナリオ

例

簡単な例:基本的なスワップ関数

入力:「SOL を任意のトークンにスワップする関数を作成してください」

出力:

import { Connection, PublicKey, VersionedTransaction } from '@solana/web3.js';

const JUPITER_API = 'https://quote-api.jup.ag/v6';
const SOL_MINT = 'So11111111111111111111111111111111111111112';

interface SwapSolParams {
  outputMint: string;
  amountLamports: number;
  slippageBps: number;
  userPublicKey: PublicKey;
  signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
  connection: Connection;
}

export async function swapSolToToken(params: SwapSolParams): Promise<string> {
  // 1. Get quote
  const quoteUrl = `${JUPITER_API}/quote?inputMint=${SOL_MINT}&outputMint=${params.outputMint}&amount=${params.amountLamports}&slippageBps=${params.slippageBps}`;

  const quoteResponse = await fetch(quoteUrl);
  if (!quoteResponse.ok) throw new Error('Failed to get quote');
  const quote = await quoteResponse.json();

  // 2. Get swap transaction
  const swapResponse = await fetch(`${JUPITER_API}/swap`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      quoteResponse: quote,
      userPublicKey: params.userPublicKey.toBase58(),
      wrapAndUnwrapSol: true,
      computeUnitPriceMicroLamports: 5000, // 0.000005 SOL priority
    }),
  });

  const { swapTransaction } = await swapResponse.json();

  // 3. Sign and send
  const tx = VersionedTransaction.deserialize(Buffer.from(swapTransaction, 'base64'));
  const signedTx = await params.signTransaction(tx);

  const signature = await params.connection.sendTransaction(signedTx);
  await params.connection.confirmTransaction(signature, 'confirmed');

  return signature;
}
複雑な例:本番環境向けスワップサービス

入力:「再試行、レート制限、監視機能を備えたスワップサービスを構築してください」

出力:

// See full implementation in examples/swap-service.ts
// Key features:
// - Request queue with rate limiting (60/min)
// - Exponential backoff on failures
// - Priority fee escalation on retries
// - Quote caching (10s TTL)
// - Prometheus metrics export
// - Structured logging
// - Health check endpoint

class JupiterSwapService {
  private requestQueue: RequestQueue;
  private quoteCache: LRUCache<string, Quote>;
  private metrics: SwapMetrics;

  async swap(params: SwapParams): Promise<SwapResult> {
    const startTime = Date.now();

    try {
      // Check rate limit
      await this.requestQueue.acquire();

      // Get or refresh quote
      const quote = await this.getQuoteWithCache(params);

      // Validate
      this.validateQuote(quote, params);

      // Execute with retries
      const signature = await this.executeWithRetry(quote, params);

      // Record success
      this.metrics.recordSwap('success', Date.now() - startTime);

      return { success: true, signature, quote };

    } catch (error) {
      this.metrics.recordSwap('failure', Date.now() - startTime);
      throw error;
    }
  }
}

コメント