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é
- Ne jamais exposer les clés privées - Utilisez des variables d'environnement ou une gestion sécurisée des clés
- Vérifier les adresses de pool - Confirmez toujours que vous interagissez avec des pools légitimes
- Utiliser la protection contre le slippage - Définissez une tolérance de slippage appropriée pour prévenir le front-running
- Tester d'abord sur devnet - Validez toutes les opérations avant le déploiement mainnet
Performance
- Utiliser les frais de priorité - Essentiels pour les transactions mainnet en cas de congestion élevée
- Regrouper les opérations - Combinez plusieurs instructions si possible
- 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é
- Surveiller les positions - Suivez quand les positions sortent de portée
- Rééquilibrer stratégiquement - Considérez les coûts en gaz lors de l'ajustement des positions
- Collecter régulièrement - Récoltez les frais et récompenses pour composer les rendements
- 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
- @orca-so/whirlpools - Nouveau SDK
- @orca-so/whirlpools-sdk - SDK legacy
- @orca-so/whirlpools-core - Utilitaires principaux
- @orca-so/whirlpools-client - Client bas niveau
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 (
simulateTransactionou é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
getSignatureStatusesou 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