orca

Par elophanto · elophanto

Guide complet pour Orca - le principal AMM à liquidité concentrée (CLMM) de Solana. Couvre le SDK Whirlpools pour les swaps, la fourniture de liquidité, la création de pools, la gestion de positions et la collecte des frais sur les réseaux Solana et Eclipse.

npx skills add https://github.com/elophanto/elophanto --skill orca

Guide de Développement Orca Whirlpools

Orca est le DEX le plus fiable sur Solana et Eclipse, construit sur un market maker automatisé à liquidité concentrée (CLMM) appelé Whirlpools. Ce guide couvre le SDK Whirlpools pour construire des applications de trading, provision de liquidité et gestion de pool.

Vue d'ensemble

Orca Whirlpools fournit :

  • Échanges de tokens - Échanges de tokens efficaces avec faible slippage et taux compétitifs
  • Liquidité concentrée - Fournissez de la liquidité dans des plages de prix personnalisées pour une efficacité capitale supérieure
  • Splash Pools - Provision de liquidité simple et pleine portée pour les débutants
  • Gestion de positions - Ouvrir, augmenter, diminuer et fermer des positions de liquidité
  • Collecte de frais - Collectez les frais de trading et les récompenses des positions
  • Création de pools - Création sans permission de nouveaux pools de liquidité

IDs de programme

Réseau ID de programme
Solana Mainnet whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc
Solana Devnet whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc
Eclipse Mainnet whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc
Eclipse Testnet whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc

Démarrage rapide

Installation

Nouveau SDK (Solana Web3.js v2) :

npm install @orca-so/whirlpools @solana/kit

SDK legacy (Solana Web3.js v1) :

npm install @orca-so/whirlpools-sdk @orca-so/common-sdk @coral-xyz/anchor@0.29.0 @solana/web3.js @solana/spl-token decimal.js

Utilitaires principaux (optionnel) :

npm install @orca-so/whirlpools-core @orca-so/whirlpools-client

Configuration basique (Nouveau SDK)

import {
  setWhirlpoolsConfig,
  setRpc,
  setPayerFromBytes
} from "@orca-so/whirlpools";
import { createSolanaRpc, devnet } from "@solana/kit";
import fs from "fs";

// Configurer pour Solana Devnet
await setWhirlpoolsConfig("solanaDevnet");
await setRpc("https://api.devnet.solana.com");

// Charger le portefeuille depuis le fichier de clés
const keyPairBytes = new Uint8Array(
  JSON.parse(fs.readFileSync("./keypair.json", "utf8"))
);
const wallet = await setPayerFromBytes(keyPairBytes);

// Créer une connexion RPC
const rpc = createSolanaRpc(devnet("https://api.devnet.solana.com"));

console.log("Portefeuille :", wallet.address);

Configuration basique (SDK legacy)

import { WhirlpoolContext, buildWhirlpoolClient, ORCA_WHIRLPOOL_PROGRAM_ID } from "@orca-so/whirlpools-sdk";
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { Connection, Keypair } from "@solana/web3.js";
import fs from "fs";

// Configurer la connexion
const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");

// Charger le portefeuille
const secretKey = JSON.parse(fs.readFileSync("./keypair.json", "utf8"));
const wallet = new Wallet(Keypair.fromSecretKey(new Uint8Array(secretKey)));

// Créer le fournisseur et le contexte
const provider = new AnchorProvider(connection, wallet, {});
const ctx = WhirlpoolContext.from(connection, wallet, ORCA_WHIRLPOOL_PROGRAM_ID);
const client = buildWhirlpoolClient(ctx);

console.log("Portefeuille :", wallet.publicKey.toString());

Échanges de tokens

Échange avec le nouveau SDK

import { swap, swapInstructions, setWhirlpoolsConfig } from "@orca-so/whirlpools";
import { address } from "@solana/kit";

await setWhirlpoolsConfig("solanaMainnet");

const poolAddress = address("POOL_ADDRESS_HERE");
const inputMint = address("INPUT_TOKEN_MINT");
const amount = 1_000_000n; // Montant en unités les plus petites
const slippageTolerance = 100; // 100 bps = 1%

// Option 1 : Utiliser la fonction d'échange simple (construit et envoie)
const txId = await swap(
  rpc,
  { inputAmount: amount, mint: inputMint },
  poolAddress,
  slippageTolerance,
  wallet
);

console.log("Transaction d'échange :", txId);

// Option 2 : Obtenir les instructions pour la construction personnalisée de transaction
const { instructions, quote } = await swapInstructions(
  rpc,
  { inputAmount: amount, mint: inputMint },
  poolAddress,
  slippageTolerance,
  wallet
);

console.log("Sortie attendue :", quote.tokenEstOut);
console.log("Sortie minimale :", quote.tokenMinOut);
console.log("Impact de prix :", quote.priceImpact);

Échange avec sortie exacte

// Échanger pour obtenir un montant exact en sortie
const { instructions, quote } = await swapInstructions(
  rpc,
  { outputAmount: 1_000_000n, mint: outputMint },
  poolAddress,
  slippageTolerance,
  wallet
);

console.log("Entrée maximale requise :", quote.tokenMaxIn);

Échange avec SDK legacy

import { WhirlpoolContext, swapQuoteByInputToken, buildWhirlpoolClient } from "@orca-so/whirlpools-sdk";
import { DecimalUtil, Percentage } from "@orca-so/common-sdk";
import Decimal from "decimal.js";

// Obtenir le pool
const whirlpool = await client.getPool(poolAddress);
const whirlpoolData = whirlpool.getData();

// Obtenir un devis d'échange
const inputAmount = new Decimal("1.0"); // 1 token
const quote = await swapQuoteByInputToken(
  whirlpool,
  whirlpoolData.tokenMintA,
  DecimalUtil.toBN(inputAmount, 9), // 9 décimales
  Percentage.fromFraction(1, 100), // 1% de slippage
  ctx.program.programId,
  ctx.fetcher
);

console.log("Sortie estimée :", quote.estimatedAmountOut.toString());

// Exécuter l'échange
const tx = await whirlpool.swap(quote);
const signature = await tx.buildAndExecute();
console.log("Signature d'échange :", signature);

Provision de liquidité

Types de positions

Position à liquidité concentrée : Fournissez de la liquidité dans une plage de prix spécifique pour une efficacité capitale supérieure.

Position pleine portée (Splash Pool) : Fournissez de la liquidité sur toute la plage de prix, similaire aux AMM traditionnels.

Ouvrir une position à liquidité concentrée

import { openPosition, openPositionInstructions } from "@orca-so/whirlpools";
import { address } from "@solana/kit";

const poolAddress = address("POOL_ADDRESS");
const lowerPrice = 0.001;  // Limite inférieure de la plage de prix
const upperPrice = 100.0;  // Limite supérieure de la plage de prix
const slippageTolerance = 100; // 1%

// Spécifier la liquidité par montant de token
const param = { tokenA: 1_000_000_000n }; // 1 token avec 9 décimales

// Option 1 : Fonction simple qui construit et envoie
const txId = await openPosition(
  rpc,
  poolAddress,
  param,
  lowerPrice,
  upperPrice,
  slippageTolerance,
  wallet
);

// Option 2 : Obtenir les instructions pour une transaction personnalisée
const {
  instructions,
  quote,
  positionMint,
  initializationCost
} = await openPositionInstructions(
  rpc,
  poolAddress,
  param,
  lowerPrice,
  upperPrice,
  slippageTolerance,
  wallet
);

console.log("Position mint :", positionMint);
console.log("Token A requis :", quote.tokenEstA);
console.log("Token B requis :", quote.tokenEstB);
console.log("Coût d'initialisation :", initializationCost);

Ouvrir une position pleine portée

import { openFullRangePosition, openFullRangePositionInstructions } from "@orca-so/whirlpools";

const poolAddress = address("POOL_ADDRESS");
const param = { tokenA: 1_000_000_000n };
const slippageTolerance = 100;

const {
  instructions,
  quote,
  positionMint,
  callback: sendTx
} = await openFullRangePositionInstructions(
  rpc,
  poolAddress,
  param,
  slippageTolerance,
  wallet
);

console.log("Position mint :", positionMint);
console.log("Token B max :", quote.tokenMaxB);

// Envoyer la transaction
const txId = await sendTx();
console.log("Transaction :", txId);

Augmenter la liquidité de la position

import { increaseLiquidity, increaseLiquidityInstructions } from "@orca-so/whirlpools";

const positionMint = address("POSITION_MINT");
const param = { tokenA: 500_000_000n }; // Ajouter 0,5 tokens
const slippageTolerance = 100;

const { instructions, quote } = await increaseLiquidityInstructions(
  rpc,
  positionMint,
  param,
  slippageTolerance,
  wallet
);

console.log("Token A supplémentaire :", quote.tokenEstA);
console.log("Token B supplémentaire :", quote.tokenEstB);

Diminuer la liquidité de la position

import { decreaseLiquidity, decreaseLiquidityInstructions } from "@orca-so/whirlpools";

const positionMint = address("POSITION_MINT");
const param = { liquidity: 1000000n }; // Ou utiliser tokenA/tokenB
const slippageTolerance = 100;

const { instructions, quote } = await decreaseLiquidityInstructions(
  rpc,
  positionMint,
  param,
  slippageTolerance,
  wallet
);

console.log("Token A reçu :", quote.tokenEstA);
console.log("Token B reçu :", quote.tokenEstB);

Collecter les frais et récompenses

import { harvestPosition, harvestPositionInstructions, harvestAllPositionFees } from "@orca-so/whirlpools";

// Collecter une position unique
const positionMint = address("POSITION_MINT");

const { instructions, feesQuote, rewardsQuote } = await harvestPositionInstructions(
  rpc,
  positionMint,
  wallet
);

console.log("Frais Token A :", feesQuote.feeOwedA);
console.log("Frais Token B :", feesQuote.feeOwedB);
console.log("Récompenses :", rewardsQuote);

// Collecter toutes les positions à la fois
const { instructions: harvestAllIx, fees, rewards } = await harvestAllPositionFees(
  rpc,
  wallet.address
);

Fermer la position

import { closePosition, closePositionInstructions } from "@orca-so/whirlpools";

const positionMint = address("POSITION_MINT");
const slippageTolerance = 100;

const { instructions, quote, feesQuote } = await closePositionInstructions(
  rpc,
  positionMint,
  slippageTolerance,
  wallet
);

console.log("Token A retourné :", quote.tokenEstA);
console.log("Token B retourné :", quote.tokenEstB);
console.log("Frais collectés :", feesQuote);

Gestion de pool

Créer un Splash Pool (pleine portée)

import { createSplashPool, createSplashPoolInstructions } from "@orca-so/whirlpools";
import { address } from "@solana/kit";

const tokenMintA = address("TOKEN_A_MINT");
const tokenMintB = address("TOKEN_B_MINT");
const initialPrice = 1.5; // Prix du token B en termes de token A

const {
  instructions,
  poolAddress,
  initializationCost
} = await createSplashPoolInstructions(
  rpc,
  tokenMintA,
  tokenMintB,
  initialPrice,
  wallet
);

console.log("Adresse du pool :", poolAddress);
console.log("Coût d'initialisation :", initializationCost, "lamports");

Créer un pool à liquidité concentrée

import { createConcentratedLiquidityPool, createConcentratedLiquidityPoolInstructions } from "@orca-so/whirlpools";

const tokenMintA = address("TOKEN_A_MINT");
const tokenMintB = address("TOKEN_B_MINT");
const tickSpacing = 64; // Valeurs communes : 1, 8, 64, 128
const initialPrice = 1.5;

const {
  instructions,
  poolAddress,
  initializationCost
} = await createConcentratedLiquidityPoolInstructions(
  rpc,
  tokenMintA,
  tokenMintB,
  tickSpacing,
  initialPrice,
  wallet
);

console.log("Adresse du pool :", poolAddress);

Récupérer les données du pool

import {
  fetchSplashPool,
  fetchConcentratedLiquidityPool,
  fetchWhirlpoolsByTokenPair
} from "@orca-so/whirlpools";

// Récupérer un pool spécifique
const pool = await fetchConcentratedLiquidityPool(rpc, poolAddress);
console.log("Prix actuel :", pool.price);
console.log("Liquidité :", pool.liquidity);
console.log("Taux de frais :", pool.feeRate);

// Récupérer tous les pools pour une paire de tokens
const pools = await fetchWhirlpoolsByTokenPair(rpc, tokenMintA, tokenMintB);
for (const pool of pools) {
  console.log("Pool :", pool.address, "Espacement tick :", pool.tickSpacing);
}

Récupérer les positions

import { fetchPositionsForOwner, fetchPositionsInWhirlpool } from "@orca-so/whirlpools";

// Obtenir toutes les positions possédées par un portefeuille
const positions = await fetchPositionsForOwner(rpc, wallet.address);
for (const position of positions) {
  console.log("Position :", position.positionMint);
  console.log("Liquidité :", position.liquidity);
  console.log("Tick inférieur :", position.tickLowerIndex);
  console.log("Tick supérieur :", position.tickUpperIndex);
}

// Obtenir toutes les positions dans un pool spécifique
const poolPositions = await fetchPositionsInWhirlpool(rpc, poolAddress);

Configuration du SDK

Configuration réseau

import { setWhirlpoolsConfig } from "@orca-so/whirlpools";

// Présets intégrés
await setWhirlpoolsConfig("solanaMainnet");
await setWhirlpoolsConfig("solanaDevnet");
await setWhirlpoolsConfig("eclipseMainnet");
await setWhirlpoolsConfig("eclipseTestnet");

// Configuration personnalisée
await setWhirlpoolsConfig({
  whirlpoolsConfigAddress: address("CUSTOM_CONFIG_ADDRESS"),
  // ... autres paramètres personnalisés
});

Paramètres de transaction

import {
  setRpc,
  setPriorityFeeSetting,
  setJitoTipSetting,
  setComputeUnitMarginMultiplier,
  setDefaultSlippageToleranceBps,
  setDefaultFunder,
  setNativeMintWrappingStrategy
} from "@orca-so/whirlpools";

// Définir le point de terminaison RPC
await setRpc("https://api.mainnet-beta.solana.com");

// Configurer les frais de priorité
await setPriorityFeeSetting({
  type: "dynamic",
  percentile: 75, // Utiliser le 75e percentile des frais récents
});

// Ou définir un frais de priorité fixe
await setPriorityFeeSetting({
  type: "fixed",
  microLamports: 10000,
});

// Configurer les conseils Jito pour la protection MEV
await setJitoTipSetting({
  type: "dynamic",
  percentile: 50,
});

// Définir la marge d'unité de calcul (pour les transactions plus grandes)
await setComputeUnitMarginMultiplier(1.2);

// Définir la tolérance de slippage par défaut (en bps)
await setDefaultSlippageToleranceBps(100); // 1%

// Définir le financeur par défaut pour les transactions
await setDefaultFunder(wallet);

// Configurer le comportement d'enveloppe SOL
await setNativeMintWrappingStrategy("ata"); // ou "none"

Réinitialiser la configuration

import { resetConfiguration } from "@orca-so/whirlpools";

// Réinitialiser tous les paramètres aux valeurs par défaut
await resetConfiguration();

Référence d'espacement tick

L'espacement tick détermine la granularité des plages de prix :

Espacement tick Cas d'usage Niveau de frais
1 Stablecoins (ex : USDC/USDT) 0,01%
8 Paires corrélées 0,04%
64 Paires standards 0,30%
128 Paires exotiques/volatiles 1,00%

Meilleures pratiques

Sécurité

  1. Ne jamais exposer les clés privées - Utilisez des variables d'environnement ou une gestion sécurisée des clés
  2. Vérifier les adresses de pool - Confirmez toujours que vous interagissez avec des pools légitimes
  3. Utiliser la protection contre le slippage - Définissez une tolérance de slippage appropriée pour prévenir le front-running
  4. Tester d'abord sur devnet - Validez toutes les opérations avant le déploiement mainnet

Performance

  1. Utiliser les frais de priorité - Essentiels pour les transactions mainnet en cas de congestion élevée
  2. Regrouper les opérations - Combinez plusieurs instructions si possible
  3. Récupérer les données au préalable - Mettez en cache les données de pool et de position pour réduire les appels RPC

Provision de liquidité

  1. Surveiller les positions - Suivez quand les positions sortent de portée
  2. Rééquilibrer stratégiquement - Considérez les coûts en gaz lors de l'ajustement des positions
  3. Collecter régulièrement - Récoltez les frais et récompenses pour composer les rendements
  4. Comprendre la perte impermanente - Les positions concentrées ont un risque de perte impermanente amplifié

Gestion des erreurs

try {
  const txId = await swap(rpc, swapParams, poolAddress, slippage, wallet);
} catch (error) {
  if (error.message.includes("SlippageExceeded")) {
    console.error("Tolérance de slippage dépassée, essayez d'augmenter le slippage");
  } else if (error.message.includes("InsufficientFunds")) {
    console.error("Pas assez de tokens dans le portefeuille");
  } else if (error.message.includes("TickArrayNotInitialized")) {
    console.error("Les tableaux tick de la plage de prix ne sont pas initialisés");
  } else {
    throw error;
  }
}

Ressources

Documentation officielle

Dépôts GitHub

Paquets NPM

Audits de sécurité

  • Audit Kudelski Security
  • Audit Neodyme

Structure de compétence

orca/
├── SKILL.md                              # Ce fichier
├── resources/
│   ├── api-reference.md                  # Référence complète des fonctions SDK
│   └── program-addresses.md              # IDs de programme et adresses de pool communes
├── examples/
│   ├── swap/
│   │   └── token-swap.ts                 # Exemples d'échange de tokens
│   ├── liquidity/
│   │   ├── open-position.ts              # Ouvrir des positions de liquidité
│   │   ├── manage-position.ts            # Augmenter/diminuer la liquidité
│   │   └── harvest-fees.ts               # Collecter les frais et récompenses
│   └── pools/
│       └── create-pool.ts                # Exemples de création de pool
├── templates/
│   └── setup.ts                          # Modèle de client prêt à l'emploi
└── docs/
    └── troubleshooting.md                # Problèmes courants et solutions

Vérifier

  • Un appel RPC/SDK réel a été émis (mainnet, devnet ou validateur local) et la charge utile de réponse est capturée dans la transcription, pas seulement paraphrasée
  • Chaque transaction a été simulée (simulateTransaction ou équivalent) avant toute étape de signature/envoi ; les journaux de simulation sont joints
  • Pour toute transaction signée/envoyée, la signature résultante est enregistrée et confirmée sur la chaîne (statut retourné par getSignatureStatuses ou une URL d'explorateur)
  • Le slippage, les frais de priorité et les limites d'unité de calcul ont été définis explicitement avec des valeurs numériques concrètes, pas laissés aux défauts de la bibliothèque
  • Les adresses de compte, les mints et les IDs de programme utilisés dans l'exécution correspondent aux adresses orca-liquidity documentées pour le cluster ciblé (pas de mélange mainnet/devnet)
  • Le chemin d'erreur a été exercé au moins une fois (solde insuffisant, oracle obsolète, blockhash expiré, etc.) et la gestion des erreurs de l'agent a produit un message lisible par l'humain

Skills similaires