solana-kit

Par elophanto · elophanto

Guide complet pour @solana/kit - le SDK JavaScript moderne, tree-shakeable et sans dépendances d'Anza. Couvre les connexions RPC, les signers, la construction de transactions avec pipe, la signature, l'envoi et la récupération de comptes, avec un support TypeScript complet.

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

Guide de développement Solana Kit

Un guide complet pour construire des applications Solana avec @solana/kit - le SDK JavaScript moderne, tree-shakeable et sans dépendances d'Anza.

Vue d'ensemble

Solana Kit (anciennement web3.js 2.0) est une réécriture complète du SDK Solana JavaScript avec :

  • Tree-shakeable : Ne livrez que le code que vous utilisez (-78% de taille de bundle)
  • Zéro dépendances : Aucun paquet tiers
  • Design fonctionnel : Composable, pas de classes
  • Crypto 10x plus rapide : Support Ed25519 natif
  • TypeScript-first : Sécurité de type complète

Démarrage rapide

Installation

npm install @solana/kit

Pour les interactions avec des programmes spécifiques :

npm install @solana-program/system @solana-program/token

Exemple minimal

import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  generateKeyPairSigner,
  lamports,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
  getSignatureFromTransaction,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";

const LAMPORTS_PER_SOL = BigInt(1_000_000_000);

async function transferSol() {
  // 1. Connexion à RPC
  const rpc = createSolanaRpc("https://api.devnet.solana.com");
  const rpcSubscriptions = createSolanaRpcSubscriptions("wss://api.devnet.solana.com");

  // 2. Créer les signers
  const sender = await generateKeyPairSigner();
  const recipient = await generateKeyPairSigner();

  // 3. Obtenir le blockhash
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

  // 4. Construire la transaction avec pipe
  const transactionMessage = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayer(sender.address, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
    (tx) => appendTransactionMessageInstruction(
      getTransferSolInstruction({
        amount: lamports(LAMPORTS_PER_SOL / BigInt(10)), // 0.1 SOL
        destination: recipient.address,
        source: sender,
      }),
      tx
    )
  );

  // 5. Signer
  const signedTx = await signTransactionMessageWithSigners(transactionMessage);

  // 6. Envoyer et confirmer
  const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
  await sendAndConfirm(signedTx, { commitment: "confirmed" });

  console.log("Signature:", getSignatureFromTransaction(signedTx));
}

Concepts fondamentaux

1. Connexions RPC

Kit sépare les connexions HTTP et WebSocket :

import { createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit";

// HTTP pour les requêtes
const rpc = createSolanaRpc("https://api.devnet.solana.com");

// WebSocket pour les souscriptions
const rpcSubscriptions = createSolanaRpcSubscriptions("wss://api.devnet.solana.com");

// Effectuer des appels RPC
const slot = await rpc.getSlot().send();
const balance = await rpc.getBalance(address).send();
const { value: blockhash } = await rpc.getLatestBlockhash().send();

2. Signers

Kit utilise des interfaces de signer plutôt que des keypairs directement :

import {
  generateKeyPairSigner,
  createKeyPairSignerFromBytes,
  address,
} from "@solana/kit";

// Générer un nouveau signer
const signer = await generateKeyPairSigner();
console.log("Address:", signer.address);

// À partir d'une clé secrète existante (Uint8Array)
const existing = await createKeyPairSignerFromBytes(secretKeyBytes);

// Créer une adresse à partir d'une chaîne
const addr = address("11111111111111111111111111111111");

3. Construction de transactions avec Pipe

Kit utilise la composition fonctionnelle via pipe :

import {
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
  appendTransactionMessageInstructions,
  prependTransactionMessageInstructions,
} from "@solana/kit";

const tx = pipe(
  createTransactionMessage({ version: 0 }),             // Créer un message v0
  (tx) => setTransactionMessageFeePayer(payer.address, tx),  // Définir le payeur de frais
  (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx), // Définir la durée de vie
  (tx) => appendTransactionMessageInstruction(instruction1, tx),      // Ajouter une instruction
  (tx) => appendTransactionMessageInstructions([instruction2, instruction3], tx), // Ajouter plusieurs
);

4. Signature de transactions

import {
  signTransactionMessageWithSigners,
  partiallySignTransactionMessageWithSigners,
  getSignatureFromTransaction,
} from "@solana/kit";

// Signer avec tous les signers de la transaction
const signedTx = await signTransactionMessageWithSigners(transactionMessage);

// Signature partielle (pour multisig)
const partiallySignedTx = await partiallySignTransactionMessageWithSigners(
  transactionMessage
);

// Obtenir la signature avant d'envoyer
const signature = getSignatureFromTransaction(signedTx);

5. Envoi de transactions

import {
  sendAndConfirmTransactionFactory,
  sendTransactionWithoutConfirmingFactory,
  getBase64EncodedWireTransaction,
} from "@solana/kit";

// Envoyer avec confirmation (recommandé)
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
await sendAndConfirm(signedTx, { commitment: "confirmed" });

// Envoyer sans attendre la confirmation
const send = sendTransactionWithoutConfirmingFactory({ rpc });
await send(signedTx, { commitment: "confirmed" });

// Encodage manuel (bas niveau)
const encoded = getBase64EncodedWireTransaction(signedTx);
await rpc.sendTransaction(encoded, { encoding: "base64" }).send();

6. Récupération de comptes

import {
  fetchEncodedAccount,
  fetchEncodedAccounts,
  assertAccountExists,
} from "@solana/kit";

// Récupérer un seul compte
const account = await fetchEncodedAccount(rpc, address);
if (account.exists) {
  console.log("Lamports:", account.lamports);
  console.log("Owner:", account.programAddress);
  console.log("Data:", account.data);
}

// Récupérer plusieurs comptes
const accounts = await fetchEncodedAccounts(rpc, [addr1, addr2, addr3]);

// S'assurer que le compte existe (lève une erreur sinon)
assertAccountExists(account);

Référence des paquets

Paquet principal

Import Description
@solana/kit Paquet principal - inclut tout ci-dessous

Paquets individuels

Paquet Objectif
@solana/rpc Création de client RPC
@solana/rpc-subscriptions Souscriptions WebSocket
@solana/signers Interfaces de signature
@solana/addresses Utilitaires d'adresse
@solana/keys Génération de clé
@solana/transactions Compilation de transaction
@solana/transaction-messages Construction de message
@solana/accounts Récupération de compte
@solana/codecs Encodage/décodage de données
@solana/errors Gestion des erreurs

Paquets de programmes

Paquet Programme
@solana-program/system System Program
@solana-program/token SPL Token
@solana-program/token-2022 Token Extensions
@solana-program/memo Memo Program
@solana-program/compute-budget Compute Budget
@solana-program/address-lookup-table Lookup Tables

Modèles courants

Modèle 1 : Fonction auxiliaire pour Send & Confirm

import {
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
  getSignatureFromTransaction,
  CompilableTransactionMessage,
  TransactionMessageWithBlockhashLifetime,
  Commitment,
} from "@solana/kit";

function createTransactionSender(rpc: Rpc, rpcSubscriptions: RpcSubscriptions) {
  const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });

  return async (
    txMessage: CompilableTransactionMessage & TransactionMessageWithBlockhashLifetime,
    commitment: Commitment = "confirmed"
  ) => {
    const signedTx = await signTransactionMessageWithSigners(txMessage);
    await sendAndConfirm(signedTx, { commitment, skipPreflight: false });
    return getSignatureFromTransaction(signedTx);
  };
}

// Utilisation
const sendTx = createTransactionSender(rpc, rpcSubscriptions);
const signature = await sendTx(transactionMessage);

Modèle 2 : Constructeur de transaction réutilisable

import {
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  IInstruction,
} from "@solana/kit";

async function buildTransaction(
  rpc: Rpc,
  feePayer: Address,
  instructions: IInstruction[]
) {
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

  return pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayer(feePayer, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
    (tx) => appendTransactionMessageInstructions(instructions, tx)
  );
}

Modèle 3 : Ajouter un budget de calcul

import {
  getSetComputeUnitLimitInstruction,
  getSetComputeUnitPriceInstruction,
} from "@solana-program/compute-budget";

const computeInstructions = [
  getSetComputeUnitLimitInstruction({ units: 200_000 }),
  getSetComputeUnitPriceInstruction({ microLamports: 1000n }),
];

const tx = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayer(payer.address, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
  (tx) => prependTransactionMessageInstructions(computeInstructions, tx), // Prepend!
  (tx) => appendTransactionMessageInstruction(mainInstruction, tx),
);

Modèle 4 : Transactions versionnées avec tables de lookup

import {
  setTransactionMessageAddressLookupTable,
} from "@solana/kit";

// Récupérer la table de lookup
const lookupTableAccount = await fetchAddressLookupTable(rpc, lookupTableAddress);

const tx = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayer(payer.address, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
  (tx) => setTransactionMessageAddressLookupTable(tx, lookupTableAccount),
  (tx) => appendTransactionMessageInstructions(instructions, tx),
);

Sécurité de type

Kit fournit des types TypeScript complets :

import type {
  Address,
  Signature,
  Lamports,
  TransactionMessage,
  Rpc,
  RpcSubscriptions,
  KeyPairSigner,
} from "@solana/kit";

// Les adresses sont des chaînes marquées
const addr: Address = address("11111111111111111111111111111111");

// Les Lamports sont des bigints marqués
const amount: Lamports = lamports(1_000_000_000n);

// Réponses RPC type-safe
const response = await rpc.getBalance(addr).send();
// response.value est typée comme Lamports

Conseils de performance

  1. Importez uniquement ce dont vous avez besoin - Kit est tree-shakeable

    // Bon - n'importe que ce qui est utilisé
    import { createSolanaRpc, generateKeyPairSigner } from "@solana/kit";
    
    // Aussi bon - utilisez les sous-paquets pour des bundles plus petits
    import { createSolanaRpc } from "@solana/rpc";
    import { generateKeyPairSigner } from "@solana/signers";
  2. Réutilisez les connexions RPC - Ne créez pas par requête

    // Créer une fois
    const rpc = createSolanaRpc(endpoint);
    
    // Réutiliser partout
    await rpc.getBalance(addr1).send();
    await rpc.getBalance(addr2).send();
  3. Traitez les requêtes par lots quand c'est possible

    // Récupérer plusieurs comptes en une seule requête
    const accounts = await fetchEncodedAccounts(rpc, [addr1, addr2, addr3]);
  4. Utilisez skipPreflight avec prudence - Plus rapide mais pas de simulation

    await sendAndConfirm(tx, { commitment: "confirmed", skipPreflight: true });

Gestion des erreurs

import { isSolanaError, SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS } from "@solana/errors";

try {
  await sendAndConfirm(signedTx, { commitment: "confirmed" });
} catch (error) {
  if (isSolanaError(error, SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS)) {
    console.error("Pas assez de SOL pour la transaction");
  } else if (isSolanaError(error)) {
    console.error("Erreur Solana:", error.context);
  } else {
    throw error;
  }
}

Migration depuis web3.js 1.x

Consultez la skill de migration séparée ou utilisez @solana/compat pour l'interopérabilité :

import {
  fromLegacyPublicKey,
  fromLegacyKeypair,
  fromVersionedTransaction,
  fromLegacyTransactionInstruction,
} from "@solana/compat";

// Convertir legacy PublicKey en Kit Address
const address = fromLegacyPublicKey(legacyPublicKey);

// Convertir legacy Keypair en Kit CryptoKeyPair (async)
const keyPair = await fromLegacyKeypair(legacyKeypair);

// Convertir legacy VersionedTransaction en Kit Transaction
const kitTransaction = fromVersionedTransaction(legacyVersionedTx);

// Convertir legacy TransactionInstruction en Kit Instruction
const kitInstruction = fromLegacyTransactionInstruction(legacyInstruction);

Note : Le paquet compat convertit les types legacy VERS Kit. Pour la conversion inverse, vous devrez peut-être construire manuellement des objets legacy.

Benchmarks de performance

Kit offre des améliorations de performance significatives par rapport à web3.js 1.x :

Métrique web3.js 1.x @solana/kit Amélioration
Génération de keypair ~50ms ~5ms 10x plus rapide
Signature de transaction ~20ms ~2ms 10x plus rapide
Taille de bundle 311KB 226KB 26% plus petit
Latence de confirmation ~400ms ~200ms ~200ms plus rapide

Benchmarks de Triton One's Ping Thing service et Solana Explorer testing

Pourquoi c'est plus rapide

  1. Ed25519 natif : Utilise les APIs crypto natives du navigateur/runtime
  2. Zéro dépendances : Pas de surcharge de bibliothèque tierce
  3. Tree-shakeable : N'importe que le code que vous utilisez
  4. Pas de classes : Le design fonctionnel permet une meilleure optimisation

Ressources

Structure de la skill

solana-kit/
├── SKILL.md                    # Ce fichier
├── resources/
│   ├── packages-reference.md   # Documentation complète des paquets
│   └── api-quick-reference.md  # Tableau de recherche rapide
├── examples/
│   ├── transfer-sol/           # Transfert SOL basique
│   ├── create-token/           # Création de token SPL
│   ├── fetch-accounts/         # Récupération et décodage de compte
│   └── subscriptions/          # Souscriptions en temps réel
├── templates/
│   └── project-template.ts     # Starter prêt à copier-coller
└── docs/
    ├── advanced-patterns.md    # Modèles complexes
    └── 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, non pas simplement paraphrasée
  • Chaque transaction a été simulée (simulateTransaction ou équivalent) avant toute étape de signature/envoi ; les logs de simulation sont joints
  • Pour toute transaction signée/envoyée, la signature résultante est enregistrée et confirmée en chaîne (statut retourné par getSignatureStatuses ou une URL explorateur)
  • Le slippage, les frais de priorité et les limites de compute unit ont été définis explicitement avec des valeurs numériques concrètes, non pas laissés aux défauts de la bibliothèque
  • Les adresses de compte, les mints et les ID de programme utilisés dans l'exécution correspondent aux adresses solana-kit-sdk 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'homme

Skills similaires