Skip to main content
Lorsque vous recevez des données de transaction de Laserstream, il y a deux éléments importants à rechercher :
  • Message → Ce que l’utilisateur voulait faire (sa proposition signée)
  • Meta → Ce qui s’est réellement passé (le résultat de l’exécution)
Le défi : Les données de transaction brutes viennent sous forme de tableaux d’octets binaires comme <Buffer 00 bf a0 e8...> au lieu d’adresses et de signatures lisibles. Ce guide vous montre comment : Décoder ces données binaires en un format lisible, extraire des informations significatives, et comprendre toute l’histoire de la transaction de la proposition à l’exécution.

Un flux en direct, sans décodage

Exécutez le client minimal ci-dessous. Les indicateurs de filtre suppriment les transactions de vote et échouées, et le tableau accountInclude limite les résultats aux activités qui touchent l’ID de programme Jupiter.
Votre console montre maintenant un wrapper—filters, createdAt plus une branche transaction qui cache deux enfants :
  • transaction.transaction.transaction → le message signé
  • transaction.transaction.meta → le meta d’exécution
Tout ce qui ressemble à Uint8Array reste opaque pour le moment. Lorsque vous exécutez le script avec la fonction de décodage, vous verrez la structure réellement imbriquée avec les adresses lisibles :

Décodage des données binaires

Pourquoi décoder ? Les données brutes de Laserstream contiennent des signatures, des clés de compte et des hachages sous forme d’objets binaires Uint8Array qui sont illisibles. Vous devez les convertir en chaînes base58 pour comprendre la transaction. La solution : Laserstream utilise Yellowstone gRPC, qui fournit des utilitaires de décodage intégrés. Au lieu d’écrire des décodeurs séparés pour chaque type de champ, nous utilisons une fonction récursive qui convertit toutes les données binaires en format lisible.
Cette approche exploite le décodage intégré tout en traitant les champs binaires qui nécessitent une conversion manuelle. La structure de la transaction est déjà analysée - vous devez juste convertir les champs binaires en format lisible.

Comprendre la structure de la transaction

Maintenant que nous pouvons voir les données décodées, explorons les deux parties principales de chaque mise à jour de transaction Laserstream. Rappelez-vous de notre exemple initial que chaque transaction contient deux objets clés :
  • Message (Proposition)transaction.transaction.transaction → le message signé (proposition de l’utilisateur)
  • Meta (Exécution)transaction.transaction.meta → les métadonnées d’exécution (réponse du validateur)
Cette structure en deux parties raconte une histoire complète : ce que l’utilisateur a demandé par rapport à ce qui s’est réellement passé. Examinons chaque partie en détail.

La proposition : tout ce qui est à l’intérieur du message

L’utilisateur crée un message qui spécifie quoi, qui et jusqu’à quand. Voici comment décoder chaque partie :

En-tête de Transaction

numRequiredSignatures indique au validateur combien de signatures vérifier, tandis que les deux valeurs numReadonly* étiquettent les comptes que le runtime peut traiter comme en lecture seule, permettant une exécution parallèle.

Dictionnaire des Clés de Compte

accountKeys est une liste simple de clés publiques qui agit comme une table de recherche. Chaque entier ultérieur dans la transaction - programIdIndex, chaque élément dans le tableau accounts d’une instruction - renvoie à cette liste par index, économisant plus d’un kilooctet par message.

Protection Contre les Répétitions

recentBlockhash expire une fois qu’il défile hors des 150 derniers hachages de blocs, soit environ quatre-vingt-dix secondes sur le mainnet.

Instructions : Les Commandes Réelles

Chaque instruction contient trois parties clés :
  • ID de Programme (programIdIndex) : Pointe vers une adresse dans le tableau accountKeys (par exemple, index 10 = ComputeBudget111111111111111111111111111111)
  • Comptes (accounts) : Une chaîne encodée en base58 représentant quels indexes de compte cette instruction touche
  • Données (data) : Les données réelles de l’instruction encodées en base58
En raison de la fonction convertBuffers, les comptes apparaissent en base58 mais contiennent en réalité des indices de compte (par exemple, "3vtmrQMafzDoG2CBz1iqgXPTnC" décode en indices [21, 19, 12, 17, 2, 6, 1, 22]) Ce design signifie qu’au lieu de répéter des adresses complètes de 32 octets, chaque instruction fait juste référence à des positions dans la table de recherche.

Signatures : Preuve d’Autorisation

signatures contient les signatures cryptographiques prouvant que les comptes requis ont autorisé cette transaction. Le nombre de signatures doit correspondre à header.numRequiredSignatures.

Recherches de Tables d’Adresses

Si versioned est true, addressTableLookups apparaît avec une table en chaîne et deux listes d’index. Les tables de recherche lèvent la limite de l’adresse à des dizaines tout en gardant le paquet sous le MTU de 1 232 octets.

Transaction v1 : Budget de Calcul dans l’En-tête

La Transaction v1 (SIMD-0385, Agave 4.2) ajoute un champ supplémentaire au message : transactionConfig.
Une transaction v1 porte son budget de calcul ici au lieu d’instructions de programme ComputeBudget, donc le tableau instructions d’une transaction v1 ne contient jamais d’entrée ComputeBudget111111111111111111111111111111. priorityFee est la totalité des frais en lamports pour toute la transaction, non des micro-lamports par unité de calcul. Un champ null signifie que l’expéditeur ne l’a pas défini. Les messages Legac et v0 n’ont pas de transactionConfig, donc sa présence identifie une transaction v1. Deux choses à vérifier dans votre décodeur :
  • Extraction des frais de priorité. Lire transactionConfig.priorityFee quand il existe, et se rabattre sur l’analyse des instructions de ComputeBudget uniquement pour les transactions legacy et v0. Le code qui ne scanne que les instructions lit chaque transaction v1 comme ne payant aucun frais de priorité.
  • Version proto. yellowstone-grpc-proto 12.6.0 est la première version qui transporte les champs v1, et helius-laserstream 0.8.4 (JavaScript), 0.6.3 (Rust), et 0.2.0 (Go) sont les premières versions de SDK construites dessus. Les versions plus anciennes abandonnent transactionConfig silencieusement.
Voir Transaction v1 support pour la liste complète des changements.

Comment Tout se Connecte : Le Flux

Voici ce qui se passe depuis les premiers principes :
  1. Construire la table de recherche : accountKeys liste toutes les adresses que cette transaction va toucher
  2. Définir les règles : header spécifie combien de signatures sont requises et quels comptes sont en lecture seule
  3. Créer les commandes : Chaque instruction pointe vers :
    • Un programme (via programIdIndexaccountKeys[index])
    • Les comptes dont elle a besoin (via accounts → plusieurs positions accountKeys[index])
    • Les données de l’instruction (encodées dans data)
  4. Ajouter l’autorisation : signatures prouve que les comptes requis ont approuvé cette transaction
  5. Définir l’expiration : recentBlockhash assure que cette transaction ne peut pas être rejouée plus tard

L’exécution : tout ce qui est à l’intérieur du meta

Tandis que le message montre ce que l’utilisateur voulait faire, le meta montre ce qui s’est réellement passé lorsque les validateurs ont exécuté la transaction.

Informations de base sur l’exécution

Succès/Échec
  • err: null = succès
  • err: {...} = échec avec détails sur l’erreur
  • fee = lamports facturés pour cette transaction
Changements de Balance
Les tableaux de balance correspondent au tableau accountKeys par index :
  • Compte 0 : Perd 15 000 lamports (paiement de frais)
  • Compte 1 : Gagne 1 461 600 lamports (nouveau compte créé)
  • Compte 3 : Gagne 2 001 231 920 lamports (compte programme)
Utilisation du Calcul
Montre combien de budget de calcul a été utilisé (sur le montant demandé).

Détails avancés de l’exécution

Instructions Internes
Les instructions internes sont des instructions supplémentaires que les programmes ont appelées pendant l’exécution. Elles ne font pas partie de la transaction originale mais ont été déclenchées par les instructions principales. Messages de Log
Les messages de log fournissent une trace chronologique de l’exécution du programme, montrant quels programmes ont été appelés et tous les messages de log personnalisés qu’ils ont sortis. Changements de Balance de Token
Les changements de balance de token montrent les états avant/après pour les comptes de token SPL, y compris les montants lisibles par l’homme avec le traitement décimal approprié.

Modèles pratiques de décodage

Voici des modèles courants pour extraire des informations utiles des transactions décodées :

Exemple complet : décodeur d’échange Jupiter

Voici un exemple complet qui décode les transactions d’échange Jupiter et extrait des informations significatives :
Cet exemple montre comment combiner le décodage de message avec l’analyse de meta pour extraire des informations commerciales pertinentes des transactions complexes de DeFi.

Points clés

  • Structure en deux parties : Chaque transaction a un message (ce qui a été demandé) et un meta (ce qui s’est réellement passé)
  • Décodage binaire : Utilisez bs58.encode() pour convertir les champs binaires en chaînes base58 lisibles
  • Recherche de clés de compte : Les instructions référencent les comptes par index dans le tableau accountKeys
  • Suivi des balances : Comparez preBalances et postBalances pour voir ce qui a changé
  • Transaction v1 : Lisez le budget de calcul et les frais de priorité dans transactionConfig lorsqu’il est présent ; les transactions v1 n’ont pas d’instructions ComputeBudget
La clé pour comprendre les transactions Solana est de reconnaître qu’elles sont conçues pour l’efficacité : au lieu de répéter des adresses, elles utilisent des tables de recherche et des index pour minimiser la taille des transactions tout en maximisant la densité d’information.