sanctum

Par elophanto · elophanto

SDK Sanctum complet pour le liquid staking, les swaps de LST et les opérations sur le pool Infinity sur Solana. À utiliser pour travailler avec des LST (mSOL, jitoSOL, bSOL, INF), staker du SOL, échanger des liquid staking tokens, ou intégrer l'infrastructure de liquidité de Sanctum.

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

Guide de Développement Sanctum

Un guide complet pour construire des applications Solana avec Sanctum - la plus grande plateforme LST (Liquid Staking Token) de Solana alimentant 1 361+ LSTs.

Aperçu

Sanctum fournit une infrastructure de liquid staking unifiée sur Solana :

  • Infinity Pool : Pool de liquidité multi-LST avec swaps sans slippage
  • Router : Swaps LST-vers-LST instantanés via stake account routing
  • Reserve : Liquidité SOL instantanée pour les retraits LST
  • Création LST : Lancer des liquid staking tokens personnalisés
  • Jeton INF : Token générateur de rendement représentant les parts du pool Infinity

Statistiques clés

  • 1 361+ LSTs supportés
  • 1,4 Md$+ de volume de swap facilité
  • ~8 000 SOL (1 M$+) de frais partagés avec les stakers
  • Partenaires : Jupiter (jupSOL), Bybit (bbSOL), Drift (dSOL), crypto.com (cdcSOL)

Démarrage rapide

Configuration de l'API

const SANCTUM_API_BASE = 'https://sanctum-api.ironforge.network';

// Tous les endpoints requièrent une clé API
const headers = {
  'Content-Type': 'application/json',
};

// Exemple : Obtenir toutes les métadonnées LST
const response = await fetch(
  `${SANCTUM_API_BASE}/lsts?apiKey=${API_KEY}`
);
const lsts = await response.json();

Adresses LST courantes

const LST_MINTS = {
  // Native SOL (wrapped)
  SOL: 'So11111111111111111111111111111111111111112',

  // Sanctum INF (Infinity pool token)
  INF: '5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm',

  // Major LSTs
  mSOL: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So',      // Marinade
  jitoSOL: 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn', // Jito
  bSOL: 'bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1',      // BlazeStake

  // Partner LSTs
  jupSOL: 'jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v',   // Jupiter
  bbSOL: 'bbso1MfE7KVL7DhqwZ6dVfKrD3oNV1PEykLNM4kk5dD',    // Bybit
  dSOL: 'Dso1bDeDjCQxTrWHqUUi63oBvV7Mdm6WaobLbQ7gnPQ',     // Drift

  // Other popular LSTs
  hSOL: 'he1iusmfkpAdwvxLNGV8Y1iSbj4rUy6yMhEA3fotn9A',     // Helius
  pwrSOL: 'pWrSoLAhue6jUxUMbWaY8izMhNpWfhiJk7M3Fy3p1Kt',   // Power
  laineSOL: 'LAinEtNLgpmCP9Rvsf5Hn8W6EhNiKLZQti1xfWMLy6X', // Laine
};

Concepts fondamentaux

1. LST (Liquid Staking Token)

Les LSTs représentent du SOL staké qui reste liquide. Quand vous stakez du SOL via un stake pool :

  • Vous recevez des tokens LST représentant votre stake
  • Le stake gagne les récompenses des validateurs
  • Les LSTs peuvent être tradés, utilisés en DeFi, ou swappés de nouveau en SOL

2. Jeton INF

INF est le token phare générateur de rendement de Sanctum :

  • Représente la part des avoirs multi-LST du pool Infinity
  • Générateur de rendement : La valeur augmente par rapport au SOL (non rebasing)
  • Gagne la moyenne pondérée de tous les rendements LST + frais de trading
  • Sans verrouillage - détenir pour gagner, swapper à tout moment
// INF augmente en valeur au fil du temps
// Exemple : 1 INF déposé à 1,0 SOL pourrait valoir 1,05 SOL plus tard
const infValue = await getInfSolValue(); // Retourne le taux actuel INF/SOL

3. Calculateurs de valeur SOL

Programmes on-chain qui convertissent les montants LST à la valeur SOL intrinsèque :

// Chaque LST a un calculateur dédié
const SOL_VALUE_CALCULATORS = {
  SPL: 'sp1V4h2gWorkGhVcazBc22Hfo2f5sd7jcjT4EDPrWFF',
  SanctumSpl: 'sspUE1vrh7xRoXxGsg7vR1zde2WdGtJRbyK9uRumBDy',
  SanctumSplMulti: 'ssmbu3KZxgonUtjEMCKspZzxvUQCxAFnyh1rcHUeEDo',
  Marinade: 'mare3SCyfZkAndpBRBeonETmkCCB3TJTTrz8ZN2dnhP',
  Lido: '1idUSy4MGGKyKhvjSnGZ6Zc7Q4eKQcibym4BkEEw9KR',
  wSOL: 'wsoGmxQLSvwWpuaidCApxN5kEowLe2HLQLJhCQnj4bE',
};

Référence API

URL de base

https://sanctum-api.ironforge.network

Tous les endpoints requièrent le paramètre de requête apiKey.

Obtenir les métadonnées LST

// Obtenir tous les LSTs
GET /lsts?apiKey={key}

// Obtenir un LST spécifique par mint ou symbole
GET /lsts/{mintOrSymbol}?apiKey={key}

// Réponse
interface LstMetadata {
  symbol: string;
  mint: string;
  decimals: number;
  tokenProgram: string;
  logoUri: string;
  name: string;
  tvl: number;         // Total Value Locked
  apy: number;         // Current APY
  holders: number;
}

Obtenir les données APY

// Obtenir les APYs des validateurs
GET /validators/apy?apiKey={key}

// Réponse
interface ValidatorApy {
  [validatorId: string]: {
    avgApy: number;
    timeseries: Array<{epoch: number; apy: number}>;
  };
}

// Obtenir l'historique des APYs LST
GET /lsts/{mintOrSymbol}/apys?apiKey={key}&limit={n}

// Réponse
interface LstApyHistory {
  apys: Array<{
    epoch: number;
    epochEndTs: number;
    apy: number;
  }>;
}

Swapper des LSTs

// Obtenir un devis de swap et une transaction non signée
GET /swap/token/order?apiKey={key}&inp={inputMint}&out={outputMint}&amt={amount}&mode={ExactIn|ExactOut}&signer={walletPubkey}&slippageBps={bps}

// Réponse
interface SwapOrderResponse {
  tx: string;           // Base64 encoded transaction
  inpAmt: string;       // Input amount
  outAmt: string;       // Output amount
  source: string;       // Swap source (Infinity, Router, etc.)
  feeAmt: string;       // Fee amount
  feeMint: string;      // Fee token mint
}

// Exécuter un swap signé
POST /swap/token/execute
Body: {
  signedTx: string;     // Base64 signed transaction
  orderResponse: object; // Original order response
}

// Réponse
interface SwapExecuteResponse {
  txSignature: string;
}

Déposer un compte de stake

// Convertir un compte de stake natif en LST
GET /swap/depositStake/order?apiKey={key}&stakeAccount={pubkey}&outputLstMint={mint}&signer={wallet}

POST /swap/depositStake/execute
Body: {
  signedTx: string;
  orderResponse: object;
}

Retrait vers un compte de stake

// Convertir un LST de nouveau en compte de stake
GET /swap/withdrawStake/order?apiKey={key}&lstMint={mint}&amount={amount}&signer={wallet}&deactivate={boolean}

POST /swap/withdrawStake/execute
Body: {
  signedTx: string;
  orderResponse: object;
}

Intégration TypeScript

Implémentation complète du client

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

class SanctumClient {
  private apiKey: string;
  private baseUrl = 'https://sanctum-api.ironforge.network';
  private connection: Connection;

  constructor(apiKey: string, connection: Connection) {
    this.apiKey = apiKey;
    this.connection = connection;
  }

  // Get all LSTs
  async getLsts(): Promise<LstMetadata[]> {
    const response = await fetch(
      `${this.baseUrl}/lsts?apiKey=${this.apiKey}`
    );
    return response.json();
  }

  // Get specific LST
  async getLst(mintOrSymbol: string): Promise<LstMetadata> {
    const response = await fetch(
      `${this.baseUrl}/lsts/${mintOrSymbol}?apiKey=${this.apiKey}`
    );
    return response.json();
  }

  // Get swap quote
  async getSwapQuote(params: {
    inputMint: string;
    outputMint: string;
    amount: string;
    mode: 'ExactIn' | 'ExactOut';
    signer: string;
    slippageBps?: number;
  }): Promise<SwapOrderResponse> {
    const url = new URL(`${this.baseUrl}/swap/token/order`);
    url.searchParams.set('apiKey', this.apiKey);
    url.searchParams.set('inp', params.inputMint);
    url.searchParams.set('out', params.outputMint);
    url.searchParams.set('amt', params.amount);
    url.searchParams.set('mode', params.mode);
    url.searchParams.set('signer', params.signer);
    if (params.slippageBps) {
      url.searchParams.set('slippageBps', params.slippageBps.toString());
    }

    const response = await fetch(url.toString());
    return response.json();
  }

  // Execute swap
  async executeSwap(
    orderResponse: SwapOrderResponse,
    signer: Keypair
  ): Promise<string> {
    // Decode and sign transaction
    const txBuffer = Buffer.from(orderResponse.tx, 'base64');
    const transaction = Transaction.from(txBuffer);
    transaction.sign(signer);

    // Send to API
    const response = await fetch(`${this.baseUrl}/swap/token/execute`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        signedTx: transaction.serialize().toString('base64'),
        orderResponse,
      }),
    });

    const result = await response.json();
    return result.txSignature;
  }

  // Swap LSTs (convenience method)
  async swapLst(params: {
    inputMint: string;
    outputMint: string;
    amount: string;
    signer: Keypair;
    slippageBps?: number;
  }): Promise<string> {
    const quote = await this.getSwapQuote({
      inputMint: params.inputMint,
      outputMint: params.outputMint,
      amount: params.amount,
      mode: 'ExactIn',
      signer: params.signer.publicKey.toBase58(),
      slippageBps: params.slippageBps || 50,
    });

    return this.executeSwap(quote, params.signer);
  }
}

Exemples d'utilisation

const connection = new Connection('https://api.mainnet-beta.solana.com');
const sanctum = new SanctumClient(API_KEY, connection);

// Get all LSTs
const lsts = await sanctum.getLsts();
console.log(`Found ${lsts.length} LSTs`);

// Get INF token info
const inf = await sanctum.getLst('INF');
console.log(`INF APY: ${inf.apy}%`);

// Swap 1 SOL to mSOL
const signature = await sanctum.swapLst({
  inputMint: LST_MINTS.SOL,
  outputMint: LST_MINTS.mSOL,
  amount: '1000000000', // 1 SOL (9 decimals)
  signer: wallet,
  slippageBps: 50,
});
console.log('Swap TX:', signature);

// Swap mSOL to INF
const infSwap = await sanctum.swapLst({
  inputMint: LST_MINTS.mSOL,
  outputMint: LST_MINTS.INF,
  amount: '500000000', // 0.5 mSOL
  signer: wallet,
});

Infinity Pool

Fonctionnement

Infinity est un pool de liquidité multi-LST permettant les swaps sans slippage :

  1. Calcul de la valeur SOL : La valeur de chaque LST est calculée à partir de l'état du stake pool on-chain
  2. Tarification équitable : Les swaps s'exécutent à la valeur SOL intrinsèque (pas de courbes AMM)
  3. Structure des frais : 8 bps par swap, 10 bps pour les retraits
  4. Distribution des frais : 90 % aux LPs (détenteurs d'INF), 10 % au protocole

Ajouter de la liquidité (Obtenir INF)

// Déposer du SOL ou n'importe quel LST pour obtenir de l'INF
async function addLiquidity(
  sanctum: SanctumClient,
  lstMint: string,
  amount: string,
  signer: Keypair
): Promise<string> {
  // Swap LST/SOL for INF
  return sanctum.swapLst({
    inputMint: lstMint,
    outputMint: LST_MINTS.INF,
    amount,
    signer,
  });
}

// Exemple : Ajouter 10 SOL à Infinity
const tx = await addLiquidity(
  sanctum,
  LST_MINTS.SOL,
  '10000000000',
  wallet
);

Retirer de la liquidité

// Retirer du pool Infinity
async function removeLiquidity(
  sanctum: SanctumClient,
  outputMint: string,
  infAmount: string,
  signer: Keypair
): Promise<string> {
  // Swap INF for desired LST/SOL
  return sanctum.swapLst({
    inputMint: LST_MINTS.INF,
    outputMint: outputMint,
    amount: infAmount,
    signer,
  });
}

// Exemple : Retirer en SOL
const tx = await removeLiquidity(
  sanctum,
  LST_MINTS.SOL,
  '5000000000', // 5 INF
  wallet
);

Router

Fonctionnement des swaps

Le Router permet les swaps LST-vers-LST via des stake accounts :

  1. StakeWrappedSol : Staker du SOL dans un pool, recevoir un LST
  2. WithdrawWrappedSol : Retirer du SOL undelegated de la reserve
  3. DepositStake : Déposer un stake account, recevoir un LST
  4. SwapViaStake : Router via des validateurs partagés

Structure des frais

Opération Frais
StakeWrappedSol 0 bps
WithdrawWrappedSol 1 bps
DepositStake 10 bps (exemptés pour la sortie SOL)
SwapViaStake Varie selon la route

Intégration Jupiter

Router Sanctum est intégré à Jupiter. Utilisez le SDK Jupiter pour un routage optimal :

import { Jupiter } from '@jup-ag/core';

const jupiter = await Jupiter.load({
  connection,
  cluster: 'mainnet-beta',
  user: wallet.publicKey,
});

// Jupiter route automatiquement via Sanctum quand c'est optimal
const routes = await jupiter.computeRoutes({
  inputMint: new PublicKey(LST_MINTS.mSOL),
  outputMint: new PublicKey(LST_MINTS.jitoSOL),
  amount: 1_000_000_000n,
  slippageBps: 50,
});

Staker du SOL

Staking direct

// Staker du SOL pour obtenir un LST
async function stakeSol(
  sanctum: SanctumClient,
  lstMint: string,
  solAmount: string,
  signer: Keypair
): Promise<string> {
  return sanctum.swapLst({
    inputMint: LST_MINTS.SOL,
    outputMint: lstMint,
    amount: solAmount,
    signer,
  });
}

// Staker 5 SOL pour jupSOL
const tx = await stakeSol(
  sanctum,
  LST_MINTS.jupSOL,
  '5000000000',
  wallet
);

Unstaking (Instantané)

// Unstake instantané via Infinity/Reserve
async function instantUnstake(
  sanctum: SanctumClient,
  lstMint: string,
  amount: string,
  signer: Keypair
): Promise<string> {
  return sanctum.swapLst({
    inputMint: lstMint,
    outputMint: LST_MINTS.SOL,
    amount,
    signer,
  });
}

Unstaking retardé (Frais réduits)

// Retirer vers un stake account (requiert une attente d'epoch)
async function delayedUnstake(
  sanctum: SanctumClient,
  lstMint: string,
  amount: string,
  signer: Keypair,
  deactivate: boolean = true
): Promise<string> {
  const url = new URL(`${sanctum.baseUrl}/swap/withdrawStake/order`);
  url.searchParams.set('apiKey', sanctum.apiKey);
  url.searchParams.set('lstMint', lstMint);
  url.searchParams.set('amount', amount);
  url.searchParams.set('signer', signer.publicKey.toBase58());
  url.searchParams.set('deactivate', deactivate.toString());

  const response = await fetch(url.toString());
  const orderResponse = await response.json();

  return sanctum.executeSwap(orderResponse, signer);
}

Requêtes de données

Obtenir les APYs

// Obtenir tous les APYs LST
async function getLstApys(sanctum: SanctumClient): Promise<Map<string, number>> {
  const lsts = await sanctum.getLsts();
  const apyMap = new Map();

  for (const lst of lsts) {
    apyMap.set(lst.symbol, lst.apy);
  }

  return apyMap;
}

// Obtenir l'historique des APYs pour un LST spécifique
async function getLstApyHistory(
  sanctum: SanctumClient,
  lstMint: string,
  limit: number = 30
): Promise<Array<{epoch: number; apy: number}>> {
  const response = await fetch(
    `${sanctum.baseUrl}/lsts/${lstMint}/apys?apiKey=${sanctum.apiKey}&limit=${limit}`
  );
  const data = await response.json();
  return data.apys;
}

Obtenir la TVL

// Obtenir la TVL pour des LSTs spécifiques
async function getTvl(
  sanctum: SanctumClient,
  symbols: string[]
): Promise<Map<string, number>> {
  const tvlMap = new Map();

  for (const symbol of symbols) {
    const lst = await sanctum.getLst(symbol);
    tvlMap.set(symbol, lst.tvl);
  }

  return tvlMap;
}

Obtenir les avoirs de l'utilisateur

import { getAccount, getAssociatedTokenAddress } from '@solana/spl-token';

async function getUserLstBalances(
  connection: Connection,
  wallet: PublicKey,
  lstMints: string[]
): Promise<Map<string, bigint>> {
  const balances = new Map();

  for (const mint of lstMints) {
    try {
      const ata = await getAssociatedTokenAddress(
        new PublicKey(mint),
        wallet
      );
      const account = await getAccount(connection, ata);
      balances.set(mint, account.amount);
    } catch {
      balances.set(mint, 0n);
    }
  }

  return balances;
}

IDs de programme

Programmes principaux

Programme ID Description
S Controller 5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx Gestion du pool Infinity
Flat Fee Pricing f1tUoNEKrDp1oeGn4zxr7bh41eN6VcfHjfrL3ZqQday Calcul des frais (déprécié)
Flat Slab s1b6NRXj6ygNu1QMKXh2H9LUR2aPApAAm1UQ2DjdhNV Programme de frais actuel

Calculateurs de valeur SOL

Calculateur ID
SPL sp1V4h2gWorkGhVcazBc22Hfo2f5sd7jcjT4EDPrWFF
SanctumSpl sspUE1vrh7xRoXxGsg7vR1zde2WdGtJRbyK9uRumBDy
SanctumSplMulti ssmbu3KZxgonUtjEMCKspZzxvUQCxAFnyh1rcHUeEDo
Marinade mare3SCyfZkAndpBRBeonETmkCCB3TJTTrz8ZN2dnhP
Lido 1idUSy4MGGKyKhvjSnGZ6Zc7Q4eKQcibym4BkEEw9KR
wSOL wsoGmxQLSvwWpuaidCApxN5kEowLe2HLQLJhCQnj4bE

Programmes Stake Pool

Programme ID
SanctumSpl Stake Pool SP12tWFxD9oJsVWNavTTBZvMbA6gkAmxtVgxdqvyvhY
SanctumSplMulti Stake Pool SPMBzsVUuoHA4Jm6KunbsotaahvVikZs1JyTW6iJvbn

Ressources

Structure du Skill

sanctum/
├── SKILL.md                              # This file
├── resources/
│   ├── api-reference.md                  # Complete API documentation
│   ├── lst-reference.md                  # LST addresses and metadata
│   └── program-ids.md                    # All program addresses
├── examples/
│   ├── lst-swap/README.md                # LST swap examples
│   ├── staking/README.md                 # Staking/unstaking examples
│   ├── infinity-pool/README.md           # Infinity operations
│   └── liquidity/README.md               # Add/remove liquidity
└── docs/
    └── troubleshooting.md                # Common issues

Vérification

  • Un vrai appel RPC/SDK a été émis (mainnet, devnet ou validateur local) et la charge utile de réponse est capturée dans la transcription, pas simplement 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 on-chain (statut retourné par getSignatureStatuses ou une URL d'explorateur)
  • Le slippage, la priority-fee et les limites de compute-unit 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 comptes, les mints et les IDs de programme utilisés dans l'exécution correspondent aux adresses sanctum-staking documentées pour le cluster ciblé (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 des erreurs de l'agent a produit un message lisible

Skills similaires