surfpool

Par elophanto · elophanto

Environnement de développement Surfpool complet pour Solana - remplacement direct de solana-test-validator avec forking mainnet, cheatcodes, Infrastructure as Code et Surfpool Studio. La façon la plus rapide de développer et tester des programmes Solana.

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

Surfpool - Environnement de développement Solana

Le guide définitif pour Surfpool - où les développeurs commencent leur parcours Solana. Un remplaçant clé en main pour solana-test-validator qui permet la simulation locale de programmes en utilisant des comptes Mainnet récupérés juste-à-temps.

Qu'est-ce que Surfpool ?

Surfpool est un environnement de développement complet qui combine les tests locaux avec un accès aux données Mainnet en direct :

  • Mainnet Forking - Clonez les comptes, programmes et soldes de jetons depuis Mainnet instantanément
  • Cheatcodes - Méthodes RPC spéciales pour les voyages dans le temps, la manipulation de soldes et le contrôle d'état
  • Infrastructure as Code - Déploiements reproductibles et vérifiables en utilisant le DSL txtx
  • Surfpool Studio - Tableau de bord intégré avec inspection et profilage des transactions
  • Universal Faucet - Obtenez SOL, USDC, USDT, BONK à partir d'une interface unique

Avantages clés

Fonctionnalité Description
Démarrage instantané Pas de snapshots de 2 To, fonctionne sur Raspberry Pi
Forking paresseux La stratégie copy-on-read récupère les données mainnet au besoin
Compatibilité totale Fonctionne avec solana-cli, Anchor, les portefeuilles, les explorateurs
Zéro configuration Détecte automatiquement les projets Anchor et déploie les programmes

Statistiques

  • 460+ étoiles GitHub
  • 100+ forks
  • Licence Apache 2.0
  • Version actuelle : v1.0.0

Installation

Installateur automatisé (recommandé)

curl -sL https://run.surfpool.run/ | bash

Homebrew (macOS)

brew install txtx/taps/surfpool

Depuis les sources

git clone https://github.com/txtx/surfpool.git
cd surfpool
cargo surfpool-install

Docker

docker pull surfpool/surfpool
docker run -p 8899:8899 -p 18488:18488 surfpool/surfpool

Démarrage rapide

Démarrer le réseau local

# Démarrer avec la configuration par défaut
surfpool start

# Démarrer avec une source RPC personnalisée
surfpool start -u https://api.mainnet-beta.solana.com

# Démarrer sans interface utilisateur de terminal
surfpool start --no-tui

# Démarrer avec journalisation de débogage
surfpool start --debug

Points d'accès

Service URL Description
Endpoint RPC http://127.0.0.1:8899 RPC Solana standard
WebSocket ws://127.0.0.1:8900 Abonnements en temps réel
Surfpool Studio http://127.0.0.1:18488 Tableau de bord web

Commandes CLI

surfpool start

Démarrez le réseau Surfnet local.

surfpool start [OPTIONS]

Options :

Option Par défaut Description
-m, --manifest-file-path ./Surfpool.toml Chemin du fichier manifeste
-p, --port 8899 Port RPC
-o, --host 127.0.0.1 Adresse de l'hôte
-s, --slot-time 400 Durée du slot en ms
-u, --rpc-url https://api.mainnet-beta.solana.com URL RPC source
--no-tui - Désactiver l'interface utilisateur de terminal
--debug - Activer les journaux de débogage
--no-deploy - Désactiver les déploiements automatiques
-r, --runbook deployment Runbooks à exécuter
-a, --airdrop - Clés publiques à airdropper
-q, --airdrop-amount 10000000000000 Montant de l'airdrop (lamports)
-k, --airdrop-keypair-path - Chemin de la paire de clés pour l'airdrop
--no-explorer - Désactiver l'explorateur

Exemples d'utilisation

# Démarrer avec airdrop à une adresse spécifique
surfpool start -a YOUR_PUBKEY -q 100000000000

# Démarrer avec durée de slot personnalisée (blocs plus rapides)
surfpool start -s 100

# Démarrer avec runbook spécifique
surfpool start -r deployment -r setup

Configuration Surfpool.toml

Créez un Surfpool.toml dans la racine de votre projet :

[network]
slot_time = 400
epoch_duration = 432000
rpc_url = "https://api.mainnet-beta.solana.com"

[behavior]
# Fork depuis la genèse mainnet
genesis = false
# Fork depuis un point spécifique
point_fork = true

[accounts]
# Pré-cloner des comptes spécifiques
clone = [
  "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",  # Token Program
  "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", # ATA Program
]

[programs]
# Déployer automatiquement les programmes locaux
deploy = ["./target/deploy/my_program.so"]

[airdrop]
# Destinataires d'airdrop par défaut
addresses = ["YOUR_PUBKEY"]
amount = 10000000000000  # 10 000 SOL

Cheatcodes

Surfpool fournit des méthodes RPC spéciales pour la manipulation d'état avancée pendant les tests.

Manipulation de comptes

surfnet_setAccount

Définissez des données de compte arbitraires :

await connection.send("surfnet_setAccount", [
  {
    pubkey: "AccountPubkey...",
    lamports: 1000000000,
    data: "base64EncodedData",
    owner: "OwnerPubkey...",
    executable: false,
  },
]);

surfnet_setTokenAccount

Créez ou modifiez des comptes de jetons :

await connection.send("surfnet_setTokenAccount", [
  {
    owner: "OwnerPubkey...",
    mint: "MintPubkey...",
    tokenProgram: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
    update: {
      amount: "1000000000",
      delegate: null,
      state: "initialized",
    },
  },
]);

surfnet_cloneProgramAccount

Clonez un programme depuis mainnet :

await connection.send("surfnet_cloneProgramAccount", [
  {
    source: "SourceProgramPubkey...",
    destination: "DestinationPubkey...",
  },
]);

surfnet_resetAccount

Réinitialisez un compte à l'état mainnet :

await connection.send("surfnet_resetAccount", [
  {
    pubkey: "AccountPubkey...",
    includeOwnedAccounts: true,
  },
]);

Contrôle du temps

surfnet_timeTravel

Avancez le temps réseau :

await connection.send("surfnet_timeTravel", [
  {
    epoch: 100,
    slot: 50000,
    timestamp: 1700000000,
  },
]);

surfnet_pauseClock / surfnet_resumeClock

Contrôlez la production de blocs :

// Pause
await connection.send("surfnet_pauseClock", []);

// Reprendre
await connection.send("surfnet_resumeClock", []);

surfnet_advanceClock

Avancez l'horloge progressivement :

await connection.send("surfnet_advanceClock", [
  { slots: 100 },
]);

Profilage des transactions

surfnet_profileTransaction

Profilez l'exécution d'une transaction :

const result = await connection.send("surfnet_profileTransaction", [
  {
    transaction: "base64EncodedTx",
    tag: "my-test-tag",
  },
]);

console.log("Compute units:", result.computeUnits);
console.log("Account changes:", result.accountChanges);

surfnet_getProfileResults

Obtenez les résultats de profilage par tag :

const results = await connection.send("surfnet_getProfileResults", [
  { tag: "my-test-tag" },
]);

Contrôle du réseau

surfnet_resetNetwork

Réinitialisez le réseau entier à son état initial :

await connection.send("surfnet_resetNetwork", []);

surfnet_getClock

Obtenez l'heure réseau actuelle :

const clock = await connection.send("surfnet_getClock", []);
console.log("Slot:", clock.slot);
console.log("Epoch:", clock.epoch);
console.log("Timestamp:", clock.timestamp);

Surfpool Studio

Accédez au tableau de bord web à http://127.0.0.1:18488 pour :

  • Transaction Inspector - Afficher les détails des transactions avec diffs au niveau des octets
  • Account Browser - Explorer l'état et l'historique des comptes
  • Compute Profiler - Analyser l'utilisation des unités de calcul par instruction
  • Universal Faucet - Demander SOL et des jetons
  • Network Status - Monitorer les slots, les epochs et la production de blocs

Infrastructure as Code

Surfpool intègre le DSL txtx pour les déploiements reproductibles.

Structure du runbook

# deployment.tx

// Définir les signataires
signer "deployer" "svm::secret_key" {
  secret_key = env.DEPLOYER_KEY
}

// Déployer le programme
action "deploy_program" "svm::deploy_program" {
  program_path = "./target/deploy/my_program.so"
  signer = signer.deployer
}

// Initialiser le programme
action "initialize" "svm::send_transaction" {
  transaction {
    instruction {
      program_id = action.deploy_program.program_id
      data = encode_instruction("initialize", {})
    }
  }
  signers = [signer.deployer]
}

Exécuter des runbooks

# Exécuter un runbook spécifique
surfpool start -r deployment

# Exécuter en mode sans surveillance
surfpool start -r deployment --unsupervised

Scénarios et fixtures

Scénarios

Définissez des séquences d'état de compte pour les tests :

await connection.send("surfnet_registerScenario", [
  {
    name: "high-volume-trading",
    slots: [
      {
        slot: 100,
        accounts: {
          "PoolPubkey...": { lamports: 1000000000000 },
        },
      },
      {
        slot: 200,
        accounts: {
          "PoolPubkey...": { lamports: 500000000000 },
        },
      },
    ],
  },
]);

Fixtures

Exportez des fixtures de transaction pour des tests reproductibles :

const fixture = await connection.send("surfnet_exportSnapshot", [
  {
    transaction: "txSignature...",
    format: "json",
  },
]);

// Enregistrez la fixture pour CI/CD
fs.writeFileSync("fixtures/my-test.json", JSON.stringify(fixture));

Intégration avec Anchor

Surfpool détecte automatiquement les projets Anchor et gère le déploiement :

# Dans un répertoire de projet Anchor
surfpool start
# Les programmes dans target/deploy/ sont déployés automatiquement

Tests avec Anchor

import * as anchor from "@coral-xyz/anchor";

describe("My Program", () => {
  // Utilisez Surfnet local
  const provider = anchor.AnchorProvider.local("http://127.0.0.1:8899");
  anchor.setProvider(provider);

  it("works with mainnet state", async () => {
    // Vos tests ont automatiquement accès aux comptes mainnet
  });
});

Bonnes pratiques

1. Utilisez les cheatcodes pour la configuration

// Configurez l'état de test avant chaque test
beforeEach(async () => {
  await connection.send("surfnet_resetNetwork", []);
  await connection.send("surfnet_setTokenAccount", [...]);
});

2. Profilez les chemins critiques

// Taguez les transactions pour le profilage
const result = await connection.send("surfnet_profileTransaction", [
  { transaction: tx, tag: "swap-operation" },
]);

expect(result.computeUnits).toBeLessThan(200000);

3. Utilisez les scénarios pour les cas limites

// Testez avec des conditions mainnet spécifiques
await connection.send("surfnet_registerScenario", [
  { name: "low-liquidity", slots: [...] },
]);

4. Exportez les fixtures pour CI

// Créez des fixtures de test reproductibles
const fixture = await connection.send("surfnet_exportSnapshot", [...]);

Ressources

Liens officiels

Communauté

Tutoriels

Structure de la skill

surfpool/
├── SKILL.md                    # Ce fichier
├── resources/
│   ├── cheatcodes.md           # Référence complète des cheatcodes
│   ├── cli-reference.md        # Référence des commandes CLI
│   └── github-repos.md         # Liens des dépôts
├── examples/
│   ├── basic/
│   │   └── getting-started.ts  # Exemple de configuration de base
│   ├── cheatcodes/
│   │   └── state-manipulation.ts # Exemples de cheatcodes
│   └── iac/
│       └── deployment.tx       # Exemple Infrastructure as Code
├── templates/
│   ├── Surfpool.toml           # Modèle de configuration
│   └── test-setup.ts           # Modèle de configuration des tests
└── docs/
    └── troubleshooting.md      # Problèmes courants

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, non seulement 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 sur chaîne (statut retourné par getSignatureStatuses ou 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, non laissés aux valeurs par défaut de la bibliothèque
  • Les adresses de compte, les jetons et les ID de programme utilisés dans l'exécution correspondent aux adresses surfpool-devenv documentées pour le cluster cible (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 compréhensible

Skills similaires