Guide Complet d'Intégration du Protocole Switchboard Oracle
Le guide définitif pour intégrer Switchboard - le protocole oracle le plus rapide, le plus personnalisable et le seul sans permission sur Solana.
Qu'est-ce que Switchboard ?
Switchboard est un protocole oracle sans permission permettant aux développeurs de mettre des données personnalisées on-chain avec des performances de pointe :
- Price Feeds - Tarification d'actifs en temps réel avec efficacité pull-based
- Oracle Quotes - Latence sub-seconde sans stockage on-chain (réduction de coût de 90%)
- Surge - Streaming WebSocket avec latence sub-100ms
- VRF Randomness - Fonctions aléatoires vérifiables cryptographiquement sécurisées
- Prediction Markets - Données de prévision basées sur le marché
Statistiques Clés
- Sécurise plus de 1 milliard de dollars de volume on-chain
- Utilisé par Kamino, Jito, MarginFi, Drift Protocol
- Latence 2-5ms avec tarification Surge
- Réduction de coût de 90% par rapport aux oracles traditionnels
Principes Fondamentaux
| Principe | Description |
|---|---|
| Rapidité | 2-5ms avec Surge, 400ms standard - leader de l'industrie pour DeFi |
| Efficacité des Coûts | Les feeds pull-based éliminent les frais de streaming constant |
| Sans Permission | Déployer des feeds instantanément sans approbations |
| Sécurité | TEE (Trusted Execution Environments) prévient la manipulation de données |
Approches d'Intégration
1. Oracle Quotes (Recommandé)
Flux de données direct oracle-to-program sans stockage on-chain :
- Latence sub-seconde
- Réduction de coût de 90%
- Pas de verrous en écriture (lectures parallèles)
- Conception stateless
2. Feeds Traditionnels
Mises à jour de feed pull-based classiques :
- Maintenance du compte feed
- Opérations de cranking
- Adapté aux cas d'usage simples
3. Surge (Temps Réel)
Streaming WebSocket pour applications haute fréquence :
- Latence sub-100ms
- Connexions persistantes
- Idéal pour les interfaces de trading
IDs de Programme
| Programme | Mainnet | Devnet |
|---|---|---|
| Oracle Program | SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f |
Aio4gaXjXzJNVLtzwtNVmSqGKpANtXhybbkhtAC94ji2 |
| Quote Program | orac1eFjzWL5R3RbbdMV68K9H6TaCVVcL6LjvQQWAbz |
- |
Files d'Attente Par Défaut
| Réseau | Adresse de la File d'Attente |
|---|---|
| Mainnet | A43DyUGA7s8eXPxqEjJY6EBu1KKbNgfxF8h17VAHn13w |
| Devnet | EYiAmGSdsQTuCw413V5BzaruWuCCSDgTPtBGvLkXHbe7 |
Démarrage Rapide
Installation
# SDK TypeScript
npm install @switchboard-xyz/on-demand @switchboard-xyz/common
# Rust (Cargo.toml)
# switchboard-on-demand = "0.8.0"
Configuration de Base
import { web3, AnchorProvider, Program } from "@coral-xyz/anchor";
import {
PullFeed,
CrossbarClient,
ON_DEMAND_MAINNET_PID,
ON_DEMAND_DEVNET_PID
} from "@switchboard-xyz/on-demand";
// Configuration de la connexion et du fournisseur
const connection = new web3.Connection("https://api.mainnet-beta.solana.com");
const wallet = useWallet(); // ou Keypair
const provider = new AnchorProvider(connection, wallet);
// Charger le programme Switchboard
const sbProgram = await Program.at(ON_DEMAND_MAINNET_PID, provider);
// Initialiser le client Crossbar pour la communication oracle
const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");
Price Feeds
Récupérer et Mettre à Jour un Feed
import { PullFeed, asV0Tx } from "@switchboard-xyz/on-demand";
// Créer une référence de compte feed
const feedPubkey = new web3.PublicKey("YOUR_FEED_PUBKEY");
const feedAccount = new PullFeed(sbProgram, feedPubkey);
// Récupérer l'instruction de mise à jour avec les signatures oracle
const { pullIx, responses, numSuccess, luts } = await feedAccount.fetchUpdateIx({
crossbarClient: crossbar,
chain: "solana",
network: "mainnet", // ou "devnet"
});
// Construire et envoyer la transaction
const tx = await asV0Tx({
connection,
ixs: [pullIx],
signers: [payer],
computeUnitPrice: 200_000,
computeUnitLimitMultiple: 1.3,
lookupTables: luts,
});
const signature = await connection.sendTransaction(tx);
console.log("Feed updated:", signature);
Lire la Valeur d'un Feed
// Obtenir la valeur actuelle du feed
const feedData = await feedAccount.loadData();
const value = feedData.value.toNumber();
const lastUpdated = feedData.lastUpdatedSlot;
console.log(`Price: ${value}, Last Updated: ${lastUpdated}`);
Oracle Quotes (Recommandé)
Oracle Quotes fournissent la façon la plus efficace de consommer les données oracle :
import { OracleQuote } from "@switchboard-xyz/on-demand";
// Hashes de feed (chaînes hex de 64 caractères)
const feedHashes = [
"0x...", // SOL/USD
"0x...", // BTC/USD
];
// Dériver le compte quote canonique
const queueKey = new web3.PublicKey("A43DyUGA7s8eXPxqEjJY6EBu1KKbNgfxF8h17VAHn13w");
const quotePubkey = OracleQuote.getCanonicalPubkey(queueKey, feedHashes);
// Récupérer l'instruction quote
const sigVerifyIx = await queue.fetchQuoteIx(crossbar, feedHashes, {
numSignatures: 1,
variableOverrides: {},
});
Intégration Rust (Oracle Quotes)
use anchor_lang::prelude::*;
use switchboard_on_demand::{default_queue, SwitchboardQuoteExt, SwitchboardQuote};
#[program]
pub mod my_program {
use super::*;
pub fn read_oracle_data(ctx: Context<ReadOracleData>) -> Result<()> {
let feeds = &ctx.accounts.quote_account.feeds;
let current_slot = ctx.accounts.sysvars.clock.slot;
let quote_slot = ctx.accounts.quote_account.slot;
// Vérifier l'obsolescence
let staleness = current_slot.saturating_sub(quote_slot);
require!(staleness < 100, ErrorCode::StaleFeed);
for feed in feeds.iter() {
msg!("Feed {}: Value = {}", feed.hex_id(), feed.value());
}
Ok(())
}
}
#[derive(Accounts)]
pub struct ReadOracleData<'info> {
#[account(address = quote_account.canonical_key(&default_queue()))]
pub quote_account: Box<Account<'info, SwitchboardQuote>>,
pub sysvars: Sysvars<'info>,
}
#[derive(Accounts)]
pub struct Sysvars<'info> {
pub clock: Sysvar<'info, Clock>,
}
Surge (Streaming Temps Réel)
Pour les applications nécessitant des mises à jour de prix en temps réel :
import { SwitchboardSurge } from "@switchboard-xyz/on-demand";
// Initialiser le client Surge
const surge = new SwitchboardSurge({
apiKey: "YOUR_API_KEY", // Optionnel
gatewayUrl: "wss://surge.switchboard.xyz",
autoReconnect: true,
maxReconnectAttempts: 5,
reconnectDelay: 1000,
});
// S'abonner aux feeds
surge.subscribe(["SOL/USD", "BTC/USD"]);
// Gérer les événements
surge.on("connected", () => {
console.log("Connected to Surge");
});
surge.on("data", (data) => {
console.log(`${data.symbol}: ${data.price}`);
});
surge.on("error", (error) => {
console.error("Surge error:", error);
});
surge.on("disconnected", () => {
console.log("Disconnected from Surge");
});
VRF Randomness
Aléa on-chain sécurisé cryptographiquement :
Client TypeScript
import { RandomnessService } from "@switchboard-xyz/on-demand";
// Demander l'aléa
const randomnessAccount = await RandomnessService.create(sbProgram, {
queue: queuePubkey,
callback: {
programId: myProgramId,
accounts: [...],
ixData: Buffer.from([...]),
},
});
// Révéler l'aléa (après exécution oracle)
const randomValue = await randomnessAccount.reveal();
console.log("Random value:", randomValue);
Intégration Rust
use switchboard_on_demand::RandomnessAccountData;
pub fn consume_randomness(ctx: Context<ConsumeRandomness>) -> Result<()> {
let randomness_data = RandomnessAccountData::parse(
ctx.accounts.randomness_account.to_account_info()
)?;
// Utiliser la valeur aléatoire
let random_value = randomness_data.get_value(&ctx.accounts.clock)?;
// Exemple : tirage au sort
let is_heads = random_value[0] % 2 == 0;
Ok(())
}
Créer des Feeds Personnalisés
Utiliser l'Interface Feed Builder
- Visiter ondemand.switchboard.xyz
- Cliquer sur « Create Feed »
- Configurer les sources de données et l'agrégation
- Déployer sur mainnet/devnet
- Copier le hash du feed pour l'intégration
Utiliser le SDK TypeScript
import { FeedBuilder } from "@switchboard-xyz/on-demand";
const feedConfig = new FeedBuilder()
.addJob({
tasks: [
{
httpTask: {
url: "https://api.example.com/price",
},
},
{
jsonParseTask: {
path: "$.price",
},
},
],
})
.setMinResponses(3)
.setMaxVariance(0.1);
const feedHash = await feedConfig.build();
Comparaison des Frameworks
| Aspect | Anchor (Basique) | Pinocchio (Avancé) |
|---|---|---|
| Courbe d'Apprentissage | Adapté aux débutants | Avancé uniquement |
| Unités de Calcul | ~2 000 CU | ~190 CU |
| Modèle de Sécurité | Validation complète | Cranker de confiance |
| Cas d'Usage | DeFi standard | Oracles AMM, HFT |
Bonnes Pratiques
1. Vérifications d'Obsolescence
Toujours vérifier la fraîcheur du feed :
let staleness = current_slot.saturating_sub(feed_slot);
require!(staleness < MAX_STALENESS_SLOTS, ErrorCode::StaleFeed);
2. Signatures Multiples
Demander plusieurs signatures oracle pour les opérations critiques :
const { pullIx } = await feedAccount.fetchUpdateIx({
numSignatures: 3, // Augmenter pour une sécurité plus élevée
});
3. Gestion d'Erreurs
try {
const { pullIx, numSuccess } = await feedAccount.fetchUpdateIx({...});
if (numSuccess < minRequired) {
throw new Error(`Insufficient oracle responses: ${numSuccess}`);
}
} catch (error) {
if (error.message.includes("timeout")) {
// Réessayer avec d'autres oracles
}
throw error;
}
4. Budget de Calcul
Pour les opérations complexes, augmenter le budget de calcul :
import { ComputeBudgetProgram } from "@solana/web3.js";
const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
units: 400_000,
});
const tx = new Transaction()
.add(modifyComputeUnits)
.add(pullIx)
.add(yourInstruction);
Ressources
Liens Officiels
- Documentation: https://docs.switchboard.xyz
- Feed Builder: https://ondemand.switchboard.xyz
Dépôts GitHub
| Dépôt | Description |
|---|---|
| switchboard | Dépôt SDK principal |
| sb-on-demand-examples | Exemples d'intégration |
| solana-sdk | SDK Rust |
| on-demand | SDK TypeScript |
Communauté
- Discord: https://discord.gg/TJAv6ZYvPC
- Twitter: @switchboardxyz
Structure des Compétences
switchboard/
├── SKILL.md # Ce fichier
├── resources/
│ ├── program-ids.md # Toutes les adresses de programme et files d'attente
│ ├── sdk-reference.md # Référence API SDK TypeScript
│ ├── rust-reference.md # Référence SDK Rust
│ └── github-repos.md # Liens des dépôts
├── examples/
│ ├── setup/
│ │ └── example.ts # Configuration de base
│ ├── feeds/
│ │ ├── pull-feed.ts # Mises à jour de pull feed
│ │ ├── oracle-quote.ts # Intégration oracle quote
│ │ └── read-feed.ts # Lire les valeurs de feed
│ ├── randomness/
│ │ └── vrf-example.ts # Aléa VRF
│ └── surge/
│ └── streaming.ts # Streaming temps réel
├── templates/
│ └── setup.ts # Modèle de démarrage complet
└── docs/
└── troubleshooting.md # Problèmes courants
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 on-chain (statut retourné par
getSignatureStatusesou une URL d'explorateur) - Le slippage, les frais de priorité et les limites d'unités de calcul ont été définis explicitement avec des valeurs numériques concrètes, pas laissés par défaut de la bibliothèque
- Les adresses de compte, les mints et les IDs de programme utilisés dans l'exécution correspondent aux adresses switchboard-oracle documentées pour le cluster cible (pas de mélange mainnet/devnet)
- Le chemin d'échec a été exercé au moins une fois (solde insuffisant, oracle obsolète, blockhash expiré, etc.) et la gestion d'erreurs de l'agent a produit un message lisible par l'homme