Environnement de développement#

Développer des programmes Solana requiert une chaine d’outils spécifique qui s’ajoute à l’environnement Rust classique. Le compilateur Rust produit du code natif pour l’architecture de la machine hôte, mais les programmes Solana s’éxecutent sur une machine virtuelle dédiée : il faut donc un compilateur qui cible le format BPF (Berkeley Packet Filter), un validateur local pour tester sans déployer sur le réseau, et un framework qui génère le code répétitif (boilerplate) inhérent au modèle de comptes.

Le framework Anchor est devenu le standard de facto pour le développement Solana. Il fournit un système de macros Rust qui simplifie la déclaration des comptes, la déserialisation des instructions et la vérification des contraintes de sécurité. Anchor génère également une IDL (Interface Definition Language) qui permet aux clients TypeScript ou Python d’intéragir avec le programme de manière typée.

Ce chapitre présente l’installation de chaque outil, la configuration de l’environnement, l’anatomie d’un projet Anchor, et le cycle complet de build, test et déploiement. A la fin de ce chapitre, vous disposerez d’un environnement fonctionnel pour écrire, tester et déployer votre premier programme Solana.

Installation de la toolchain#

Rust#

Le développement Solana nécessite une installation Rust récente. Si Rust n’est pas encore installé, la commande habituelle suffit :

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"

Le compilateur Solana utilise le backend BPF de LLVM, qui est inclus dans la toolchain Rust standard. Aucune cible supplémentaire n’est nécessaire : c’est Anchor et le SDK Solana qui gèrent la compilation vers BPF en interne.

Solana CLI#

La Solana CLI est l’interface en ligne de commande pour intéragir avec le réseau Solana. Elle contient le compilateur BPF, le validateur local, et les outils de gestion des clés et des comptes.

sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

Après installation, ajouter le binaire au PATH :

export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"

Définition 64 (Solana CLI)

La Solana CLI (solana) est l’outil en ligne de commande officiel du réseau Solana. Elle permet de gérer les clés cryptographiques, de configurer le cluster cible, d’envoyer des transactions, de déployer des programmes et de lancer un validateur local. C’est l’equivalent de geth pour Ethereum ou bitcoin-cli pour Bitcoin.

Anchor via AVM#

Anchor s’installe via AVM (Anchor Version Manager), qui permet de gérer plusieurs versions d’Anchor en parallèle, de manière analogue à rustup pour Rust.

cargo install --git https://github.com/coral-xyz/anchor avm --force
avm install latest
avm use latest

Définition 65 (Anchor Version Manager (AVM))

AVM est le gestionnaire de versions d’Anchor. Il permet d’installer, de basculer entre et de mettre à jour différentes versions du framework Anchor. Chaque projet peut spécifier sa version d’Anchor dans Anchor.toml, et AVM garantit que la bonne version est utilisée lors de la compilation.

Node.js et Yarn#

Les tests Anchor sont é crits en TypeScript et s’exécutent avec Node.js. Yarn est le gestionnaire de paquets recommandé par le projet Anchor.

# Installation de Node.js (via nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
nvm install node

# Installation de Yarn
corepack enable
corepack prepare yarn@stable --activate

Remarque 42

Après installation, vérifier que tous les outils sont accessibles et noter les versions pour le débogage :

solana --version    # ex: solana-cli 1.18.x
anchor --version    # ex: anchor-cli 0.30.x
rustc --version     # ex: rustc 1.78.x
node --version      # ex: v22.x
yarn --version      # ex: 4.x

La compatibilité entre les versions de Solana CLI et d’Anchor est importante. En cas d’erreur de compilation, vérifier d’abord que les versions sont cohérentes en consultant la matrice de compatibilité dans la documentation Anchor.

Configuration#

Génération d’une paire de clés#

Avant toute interaction avec le réseau, il faut générer une paire de clés cryptographiques. Cette clé servira d’identité pour signer les transactions et payer les frais.

solana-keygen new --outfile ~/.config/solana/id.json

Cette commande génère une clé privée Ed25519 au format JSON et affiche la clé publique correspondante. Par défaut, le fichier est stocké dans ~/.config/solana/id.json.

Remarque 43

Ne stockez jamais une clé privée de mainnet dans votre dépôt Git, dans votre code source ou dans une variable d’environnement non chiffrée. Les clés générées par solana-keygen new sont des clés de développement. Pour la production, utilisez un portefeuille matériel (hardware wallet) ou un service de gestion de cles (KMS). Ajoutez systématiquement *.json au .gitignore de vos projets Solana.

Choix du cluster#

Solana propose quatre clusters distincts, chacun avec un rôle spécifique dans le cycle de développement.

# Validateur local (par défaut pour le développement)
solana config set --url localhost

# Devnet (réseau de developpement public)
solana config set --url devnet

# Testnet (tests de performance)
solana config set --url testnet

# Mainnet (production)
solana config set --url mainnet-beta

Définition 66 (Localhost (validateur local))

Le cluster localhost correspond à un validateur Solana qui s’exécute localement sur la machine du développeur, lancé par solana-test-validator. Il offre une confirmation instantanée, un contrôle total de l’état de la chaine, et ne nécessite aucune connexion réseau. C’est l’environnement privilégie pour le développement itératif et le débogage.

Définition 67 (Devnet)

Le devnet est un réseau public de développement. Les SOL y sont gratuits (obtenus via airdrop) et les données sont périodiquement réinitialisés. C’est l’environnement recommandé pour tester les intéractions entre programmes et valider le comportement avant un déploiement sur testnet ou mainnet.

Définition 68 (Testnet)

Le testnet est un réseau public destiné aux tests de performance et de charge. Il simule les conditions du mainnet (latence réseau, congestion) sans engager de fonds réels. Les validateurs y participent pour tester leurs configurations avant de rejoindre le mainnet.

Définition 69 (Mainnet-beta)

Le mainnet-beta est le réseau de production de Solana. Les SOL y ont une valeur réelle, les transactions sont irréversibles, et les programmes déployés sont accessibles à tous les utilisateurs. Le suffixe beta est historique et ne reflète pas un manque de stabilité.

Airdrop de SOL#

Sur le devnet, il est possible d’obtenir des SOL gratuitement pour financer les transactions de test :

solana airdrop 2

Remarque 44

L’airdrop est limité à 2 SOL par requête sur le devnet, avec un rate limit par adresse IP. Si la commande échoue (ce qui arrive en période de forte demande), attendez quelques secondes et réessayez. Pour des montants plus importants, utilisez le faucet web disponible sur le site de Solana. Sur localhost, les SOL sont illimités : le validateur local crée des SOL à la demande.

Anatomie d’un projet Anchor#

La commande anchor init génère un projet complet avec une structure standardisée :

anchor init my_project
cd my_project

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(10, 9))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.axis("off")

colors = {
    "root": "#4C72B0",
    "dir": "#55A868",
    "file": "#C44E52",
    "config": "#8172B2",
}

def draw_node(ax, x, y, text, color, width=2.4, height=0.45):
    rect = mpatches.FancyBboxPatch(
        (x - width / 2, y - height / 2), width, height,
        boxstyle="round,pad=0.1", facecolor=color, edgecolor="white",
        linewidth=1.5, alpha=0.9
    )
    ax.add_patch(rect)
    ax.text(x, y, text, ha="center", va="center",
            fontsize=10, fontweight="bold", color="white", family="monospace")

def draw_edge(ax, x1, y1, x2, y2):
    ax.plot([x1, x1], [y1, y2 + 0.25], color="#AAAAAA", linewidth=1.5, solid_capstyle="round")
    ax.plot([x1, x2], [y2 + 0.25, y2 + 0.25], color="#AAAAAA", linewidth=1.5, solid_capstyle="round")
    ax.plot([x2, x2], [y2 + 0.25, y2], color="#AAAAAA", linewidth=1.5, solid_capstyle="round")

# Racine
draw_node(ax, 5, 9.2, "my_project/", colors["root"], width=2.8)

# Niveau 1
items_l1 = [
    (1.8, 7.8, "Anchor.toml", colors["config"]),
    (4.6, 7.8, "Cargo.toml", colors["config"]),
    (7.4, 7.8, "package.json", colors["config"]),
]
for x, y, text, color in items_l1:
    draw_edge(ax, 5, 9.0, x, y)
    draw_node(ax, x, y, text, color)

# Repertoires niveau 1
dirs_l1 = [
    (1.5, 6.4, "programs/", colors["dir"]),
    (4.5, 6.4, "tests/", colors["dir"]),
    (7.5, 6.4, "target/", colors["dir"]),
    (7.5, 5.0, "migrations/", colors["dir"]),
]
for x, y, text, color in dirs_l1:
    draw_edge(ax, 5, 9.0, x, y)
    draw_node(ax, x, y, text, color)

# Sous-repertoire programs
draw_edge(ax, 1.5, 6.15, 1.5, 5.0)
draw_node(ax, 1.5, 5.0, "my_project/", colors["dir"])

draw_edge(ax, 1.5, 4.75, 1.5, 3.6)
draw_node(ax, 1.5, 3.6, "src/", colors["dir"])

draw_edge(ax, 1.5, 4.75, 3.5, 3.6)
draw_node(ax, 3.5, 3.6, "Cargo.toml", colors["config"])

draw_edge(ax, 1.5, 3.35, 1.5, 2.2)
draw_node(ax, 1.5, 2.2, "lib.rs", colors["file"])

# Fichiers dans tests/
draw_edge(ax, 4.5, 6.15, 4.5, 5.0)
draw_node(ax, 4.5, 5.0, "my_project.ts", colors["file"])

# Fichiers dans target/
draw_edge(ax, 7.5, 6.15, 7.5, 5.0)

# Fichier dans migrations/
draw_edge(ax, 7.5, 4.75, 7.5, 3.6)
draw_node(ax, 7.5, 3.6, "deploy.ts", colors["file"])

# Sous-repertoires target
draw_node(ax, 7.5, 5.0, "idl/ types/", colors["dir"])

# Legende
legend_items = [
    (colors["root"], "Racine du projet"),
    (colors["dir"], "Répertoire"),
    (colors["file"], "Fichier source"),
    (colors["config"], "Configuration"),
]
for i, (color, label) in enumerate(legend_items):
    rect = mpatches.FancyBboxPatch(
        (0.3, 1.0 - i * 0.45), 0.3, 0.25,
        boxstyle="round,pad=0.05", facecolor=color, edgecolor="white", alpha=0.9
    )
    ax.add_patch(rect)
    ax.text(0.8, 1.0 - i * 0.45 + 0.12, label, fontsize=9, va="center", color="#333333")

ax.set_title("Structure d'un projet Anchor", fontsize=14, fontweight="bold", pad=15)
plt.show()
_images/1a4edc1c0f9a31c0a7b0de00679df36e2de9123fafa9d3da89b26b055bed1007.png

Définition 70 (Anchor.toml)

Le fichier Anchor.toml est le fichier de configuration principal d’un projet Anchor. Il spécifie le cluster ciblé, l’identifiant du programme (clé publique), la commande de test, et le chemin du portefeuille du déployeur. C’est l’équivalent du Cargo.toml pour l’écosystème Anchor.

Voici un exemple de Anchor.toml généré par anchor init :

[features]
resolution = true
skip-lint = false

[programs.localnet]
my_project = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

Définition 71 (programs/my_project/src/lib.rs)

Le fichier lib.rs contient le code source du programme Solana. C’est le point d’entrée du programme, ou l’on declare les instructions, les structures de comptes et la logique métier. Anchor utilise des macros procédurales pour générer automatiquement le code de désérialisation et de validation des comptes.

Définition 72 (programs/my_project/Cargo.toml)

Le fichier Cargo.toml du programme déclare les dépendances Rust, notamment anchor-lang. Il contient également la configuration spécifique au build BPF via la feature cpi (pour les appels inter-programmes) et le type de crate cdylib requis par le compilateur BPF.

Définition 73 (tests/)

Le répertoire tests/ contient les tests d’intégration écrits en TypeScript. Ces tests utilisent la bibliothèque @coral-xyz/anchor pour intéragir avec le programme via l’IDL générée. Ils s’exécutent contre un validateur local lancé automatiquement par anchor test.

Définition 74 (target/)

Le repertoire target/ contient les artefacts de compilation : le bytecode BPF (.so), l’IDL au format JSON, les types TypeScript générés, et les paires de clés des programmes. L’IDL est particulièrement importante : elle décrit l’interface du programme et permet la génération automatique de clients types.

Premier programme#

Voici le programme par defaut généré par anchor init. Malgré sa simplicité, il illustre toutes les conventions fondamentales d’Anchor.

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod my_project {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("Greetings from: {:?}", ctx.program_id);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

Exemple 20 (Anatomie du programme par défaut)

Examinons chaque élément du programme :

use anchor_lang::prelude::*;

Cette ligne importe toutes les macros, traits et types fondamentaux d’Anchor : Context, Result, Accounts, msg!, les dérives procédurales, etc. C’est l’équivalent du prelude standard de Rust, spécialisé pour le développement Solana.

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

La macro declare_id! fixe l’identifiant du programme (sa clé publique sur la blockchain). Cette valeur est générée lors du premier anchor build et stockée dans target/deploy/my_project-keypair.json. Elle doit correspondre exactement à la clé publique du compte programme sur le cluster cible.

#[program]
pub mod my_project {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        msg!("Greetings from: {:?}", ctx.program_id);
        Ok(())
    }
}

L’attribut #[program] marque le module comme point d’entrée du programme Solana. Chaque fonction publique du module devient une instruction que les clients peuvent appeler. Le paramètre ctx: Context<T> contient les comptes valides pour cette instruction (définis par la structure T). La macro msg! écrit dans les logs de la transaction, visibles dans l’explorateur.

#[derive(Accounts)]
pub struct Initialize {}

La dérive Accounts génère le code de désérialisation et de validation des comptes passés à l’instruction. Ici, Initialize est vide : l’instruction n’éxige aucun compte spécifique. Dans un programme réel, cette structure contiendrait des champs annotés avec des contraintes (#[account(init, ...)], #[account(mut)], etc.).

Remarque 45

La valeur dans declare_id! est un identifiant placeholder généré par Anchor. Lors du premier anchor build, une paire de clés est créée dans target/deploy/. La clé publique doit être recopiée dans declare_id! et dans Anchor.toml. La commande anchor keys sync automatise cette synchronisation.

Build, test, deploy#

Le cycle de développement Anchor suit trois étapes : compiler, tester, déployer. Chaque étape est encapsulée dans une commande unique.

Compilation#

anchor build

Cette commande compile le programme Rust en bytecode BPF (fichier .so), génère l’IDL au format JSON et produit les types TypeScript correspondants. Le bytecode est déposé dans target/deploy/, l’IDL dans target/idl/ et les types dans target/types/.

Exemple 21 (Artefacts de compilation)

Après anchor build, le répertoire target/ contient :

target/
├── deploy/
│   ├── my_project.so              # Bytecode BPF   └── my_project-keypair.json    # Clé du programme
├── idl/
│   └── my_project.json            # IDL (interface)
└── types/
    └── my_project.ts              # Types TypeScript

L’IDL est le contrat d’interface entre le programme on-chain et les clients off-chain. Elle décrit les instructions, les comptes attendus, les types de données et les erreurs possibles.

Tests#

anchor test

Cette commande exécute la séquence suivante : (1) lance un validateur local, (2) compile le programme, (3) le déploie sur le validateur local, (4) exécute les tests TypeScript, (5) arrête le validateur. C’est la commande la plus utilisée pendant le développement.

Remarque 46

Si un validateur local est déjà en cours d’exécution (lancé manuellement avec solana-test-validator), utilisez le drapeau --skip-local-validator pour éviter un conflit de port :

anchor test --skip-local-validator

Cela est utile lorsqu’on veut conserver l’état du validateur entre plusieurs exécutions de tests, ou lorsqu’on travaille avec plusieurs programmes interdépendants.

Déploiement#

anchor deploy

Le déploiement envoie le bytecode BPF au cluster configuré dans Anchor.toml. Sur le devnet, cela necessite des SOL (obtenus par airdrop). Sur le mainnet, les coûts de déploiement dépendent de la taille du programme : environ 0.01 SOL par kilo-octet de bytecode.

Exemple 22 (Déploiement sur le devnet)

Voici la séquence complête pour déployer un programme sur le devnet :

# Configurer le cluster
solana config set --url devnet

# Obtenir des SOL de test
solana airdrop 2

# Compiler et deployer
anchor build
anchor deploy

# Vérifier le déploiement
solana program show <PROGRAM_ID>

La commande solana program show affiche les metadonnées du programme déployé : taille du bytecode, autorité de mise à jour, et solde du compte programme.

Solana Playground#

Pour expérimenter sans installer la toolchain locale, Solana Playground (beta.solpg.io) offre un environnement de développement complet dans le navigateur. Il intègre un éditeur de code, un compilateur Anchor, et un déploiement direct sur le devnet.

Remarque 47

Solana Playground est un excellent outil de prototypage rapide : il ne nécessite aucune installation, génère automatiquement un portefeuille éphémère, et permet de tester des idées en quelques minutes. Cependant, pour un développement sérieux, l’environnement local reste indispensable : il offre un contrôle total sur les versions, permet le debogage avancé (logs detaillés du validateur, inspection des comptes), supporte les tests d’intégration complexes, et s’intègre aux outils de versionnement et d’intégration continue.

Résumé#

Ce chapitre a presenté l’ensemble de la chaine d’outils nécessaire au développement Solana avec Anchor. La toolchain se compose de quatre éléments principaux : Rust (compilateur), Solana CLI (interaction avec le réseau), Anchor (framework de développement) et Node.js (exécution des tests). La configuration passe par la génération d’une paire de clés et le choix d’un cluster adapté à l’étape de développement.

La structure d’un projet Anchor est standardisée : le code Rust du programme réside dans programs/, les tests TypeScript dans tests/, et la configuration dans Anchor.toml. Le cycle de développement s’articule autour de trois commandes : anchor build (compilation vers BPF et generation de l’IDL), anchor test (exécution automatisée des tests sur un validateur local) et anchor deploy (déploiement sur le cluster cible).

Le chapitre suivant explore en profondeur le framework Anchor : les macros de déclaration de comptes, les contraintes de validation, la gestion des erreurs et les patterns avancés de programmation on-chain.