> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Décodage et Analyse des Données de Transaction

> Apprenez à décoder et analyser les données de transaction de Laserstream pour une meilleure compréhension des transactions Solana.

**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.

```ts [expandable] theme={"system"}
import { subscribe, CommitmentLevel, SubscribeUpdate, LaserstreamConfig } from 'helius-laserstream';

async function runTransactionSubscription() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // pick the closest region
  };

  const request = {
    transactions: {
      "Jupiter-transactions": {
        vote: false,
        failed: false,
        accountInclude: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4']
      }
    },
    commitment: CommitmentLevel.PROCESSED,
    accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    (u: SubscribeUpdate) => console.log('💸 Transaction update', u),
    console.error
  );

  console.log(`✅ stream id → ${stream.id}`);
  process.on('SIGINT', () => { stream.cancel(); process.exit(0); });
}
runTransactionSubscription().catch(console.error);
```

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

```json theme={"system"}
{
 filters: [ 'Jupiter-transactions' ],
  account: undefined,
  transaction: {
    transaction: {
      signature: <Buffer 00 bf a0 e8 9f cc 84 0c a4 83 e3 97 cd b7 57 e2 2b bc 1d ca 8c a6 1b ce b5 57 d7 47 5e ec 1f 46 ae b2 2d 6a 12 cb 88 48 1d 07 bf f6 b2 d3 a8 0b c9 04 ... 14 more bytes>,
      transaction: [Object],
      meta: [Object],
      index: '1177'
    },
    slot: '351704819'
  },
  transactionStatus: undefined,
  block: undefined,
  blockMeta: undefined,
  entry: undefined,
  ping: undefined,
  pong: undefined,
  createdAt: 2025-07-07T10:58:44.403Z
}
```

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 :

```json [expandable] theme={"system"}
{
  "filters": ["Jupiter-transactions"],
  "account": undefined,
  "transaction": {
    "transaction": {
      "signature": "5u62i53R1Hdc4thm6DQTNWNkyypuJJSaXSMwwQDxNqKMaAw62H1Xa3Md7QDhYjoPk5dCPg18fwz83kUR6TrMviTx",
      "transaction": {
        "message": {
          "header": {
            "numRequiredSignatures": 1,
            "numReadonlySignedAccounts": 0,
            "numReadonlyUnsignedAccounts": 8
          },
          "accountKeys": [
            "AF9KFSWQeKVxd3kVvFvysWXmATHyYzrN8zN8GtXn4qTF",
            "G9VzXwhDPQ8KRbQAJN6TyGf2gWukYDAvmnXJhPZFev4f",
            "ES9qPxWQVMRZkobJ9yr3U6XSrXzGNLJdSe6p6fS7b82T",
            "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
            "ComputeBudget111111111111111111111111111111",
            "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
            "So11111111111111111111111111111111111111112",
            "11111111111111111111111111111111",
            "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
          ],
          "recentBlockhash": "8sGjRxJHLJVWqpHt5UdN8qxtLLdgcnLKBpFj9Qrn5PNF",
          "instructions": [
            {
              "programIdIndex": 4,
              "accounts": [],
              "data": "3bjaAzoXPjbY"
            },
            {
              "programIdIndex": 3,
              "accounts": [0, 1, 2, 5, 6, 7, 8],
              "data": "2L1xoA2KEqBgWfGt3fwFJK8k4FPJRJzYHRgH4R3xC8A7"
            }
          ]
        },
        "signatures": [
          "5u62i53R1Hdc4thm6DQTNWNkyypuJJSaXSMwwQDxNqKMaAw62H1Xa3Md7QDhYjoPk5dCPg18fwz83kUR6TrMviTx"
        ]
      },
      "meta": {
        "err": null,
        "fee": 12500,
        "preBalances": [1075517572, 0, 207594496815, 0, 0, 0, 0, 0, 0],
        "postBalances": [1075502572, 1461600, 207594496815, 2001231920, 2039280, 0, 0, 0, 0],
        "innerInstructions": [
          {
            "index": 1,
            "instructions": [
              {
                "programIdIndex": 5,
                "accounts": [1, 2, 0],
                "data": "3Bxs4h24hBtQy9rw"
              }
            ]
          }
        ],
        "logMessages": [
          "Program ComputeBudget111111111111111111111111111111 invoke [1]",
          "Program ComputeBudget111111111111111111111111111111 success",
          "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [1]",
          "Program log: Instruction: Swap",
          "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]",
          "Program log: Create",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
          "Program log: Instruction: GetAccountDataSize",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 242833 compute units",
          "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
          "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success"
        ],
        "preTokenBalances": [],
        "postTokenBalances": [],
        "computeUnitsConsumed": 182564
      },
      "index": "1177"
    },
    "slot": "351709933"
  },
  "transactionStatus": undefined,
  "block": undefined,
  "blockMeta": undefined,
  "entry": undefined,
  "ping": undefined,
  "pong": undefined,
  "createdAt": "2025-01-14T10:58:44.403Z"
}
```

***

## 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.

```ts [expandable] theme={"system"}
import bs58 from 'bs58';
import { subscribe, CommitmentLevel, SubscribeUpdate, LaserstreamConfig } from 'helius-laserstream';

// Recursive function to convert all Buffer/Uint8Array fields to base58
function convertBuffers(obj: any): any {
  if (!obj) return obj;
  if (Buffer.isBuffer(obj) || obj instanceof Uint8Array) {
    return bs58.encode(obj);
  }
  if (Array.isArray(obj)) {
    return obj.map(item => convertBuffers(item));
  }
  if (typeof obj === 'object') {
    return Object.fromEntries(
      Object.entries(obj).map(([key, value]) => [key, convertBuffers(value)])
    );
  }
  return obj;
}

async function runTransactionSubscription() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // pick the closest region
  };

  const request = {
    transactions: {
      "Jupiter-transactions": {
        vote: false,
        failed: false,
        accountInclude: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4']
      }
    },
    commitment: CommitmentLevel.PROCESSED,
    accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    (update: SubscribeUpdate) => {
      if (update.transaction) {
        // Convert all binary fields to human-readable format
        const decodedTransaction = convertBuffers(update.transaction);
        console.log('💸 Decoded transaction:', JSON.stringify(decodedTransaction, null, 2));
        
        // Or process specific fields
        processTransaction(update.transaction);
      }
    },
    console.error
  );

  console.log(`✅ stream id → ${stream.id}`);
  process.on('SIGINT', () => { stream.cancel(); process.exit(0); });
}

function processTransaction(txUpdate: any) {
  const tx = txUpdate.transaction;
  const meta = tx.meta;
  
  console.log('Transaction Details:');
  console.log('- Signature:', bs58.encode(tx.signature));
  console.log('- Slot:', txUpdate.slot);
  console.log('- Success:', meta.err === null);
  console.log('- Fee:', meta.fee, 'lamports');
  console.log('- Compute Units:', meta.computeUnitsConsumed);
  
  // Account keys are already available in the message
  const message = tx.transaction.message;
  if (message.accountKeys) {
    console.log('- Account Keys:');
    message.accountKeys.forEach((key: Uint8Array, index: number) => {
      console.log(`  ${index}: ${bs58.encode(key)}`);
    });
  }
  
  // Log messages are already UTF-8 strings
  if (meta.logMessages && meta.logMessages.length > 0) {
    console.log('- Log Messages:');
    meta.logMessages.forEach((log: string) => {
      console.log(`  ${log}`);
    });
  }
}

runTransactionSubscription();
```

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

```json theme={"system"}
{
  "header": {
    "numRequiredSignatures": 1,
    "numReadonlySignedAccounts": 0,
    "numReadonlyUnsignedAccounts": 5
  }
}
```

`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

```json theme={"system"}
{
  "accountKeys": [
    "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5",
    "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
    "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
    "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "So11111111111111111111111111111111111111112",
    "11111111111111111111111111111111"
  ]
}
```

`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

```json theme={"system"}
{
  "recentBlockhash": "8sGjRxJHLJVWqpHt5UdN8qxtLLdgcnLKBpFj9Qrn5PNF"
}
```

`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

```json theme={"system"}
{
  "instructions": [
    {
      "programIdIndex": 10,
      "data": "HnkkG7"
    },
    {
      "programIdIndex": 15,
      "accounts": "3vtmrQMafzDoG2CBz1iqgXPTnC",
      "data": "5jRcjdixRUDKQKUEt6oHJ747HCB3vWb5y"
    }
  ]
}
```

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

```json theme={"system"}
{
  "signatures": [
    "5u62i53R1Hdc4thm6DQTNWNkyypuJJSaXSMwwQDxNqKMaAw62H1Xa3Md7QDhYjoPk5dCPg18fwz83kUR6TrMviTx"
  ]
}
```

`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

```json theme={"system"}
{
  "addressTableLookups": [
    {
      "accountKey": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
      "writableIndexes": [0, 1],
      "readonlyIndexes": [2, 3, 4]
    }
  ],
  "versioned": true
}
```

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](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md), Agave 4.2) ajoute un champ supplémentaire au message : `transactionConfig`.

```json theme={"system"}
{
  "transactionConfig": {
    "computeUnitLimit": 200000,
    "heapSize": null,
    "loadedAccountsDataSizeLimit": 200000,
    "priorityFee": 50000
  },
  "versioned": true
}
```

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](/docs/fr/rpc/transaction-v1) 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 `programIdIndex` → `accountKeys[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**

```json theme={"system"}
{
  "err": null,
  "fee": 12500
}
```

* `err: null` = succès
* `err: {...}` = échec avec détails sur l'erreur
* `fee` = lamports facturés pour cette transaction

**Changements de Balance**

```json theme={"system"}
{
  "preBalances": [1075517572, 0, 207594496815, 0, 0, 0, 0, 0, 0],
  "postBalances": [1075502572, 1461600, 207594496815, 2001231920, 2039280, 0, 0, 0, 0]
}
```

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**

```json theme={"system"}
{
  "computeUnitsConsumed": 182564
}
```

Montre combien de budget de calcul a été utilisé (sur le montant demandé).

### Détails avancés de l'exécution

**Instructions Internes**

```json theme={"system"}
{
  "innerInstructions": [
    {
      "index": 1,
      "instructions": [
        {
          "programIdIndex": 5,
          "accounts": [1, 2, 0],
          "data": "3Bxs4h24hBtQy9rw"
        }
      ]
    }
  ]
}
```

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**

```json theme={"system"}
{
  "logMessages": [
    "Program ComputeBudget111111111111111111111111111111 invoke [1]",
    "Program ComputeBudget111111111111111111111111111111 success",
    "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [1]",
    "Program log: Instruction: Swap",
    "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]",
    "Program log: Create",
    "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
    "Program log: Instruction: GetAccountDataSize",
    "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 242833 compute units",
    "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=",
    "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
    "Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success"
  ]
}
```

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**

```json theme={"system"}
{
  "preTokenBalances": [],
  "postTokenBalances": [
    {
      "accountIndex": 1,
      "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "owner": "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5",
      "uiTokenAmount": {
        "amount": "1000000",
        "decimals": 6,
        "uiAmount": 1.0,
        "uiAmountString": "1"
      }
    }
  ]
}
```

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 :

```typescript theme={"system"}
// Transaction Success
function isTransactionSuccessful(meta: any): boolean {
  return meta.err === null;
}

function getTransactionFee(meta: any): number {
  return meta.fee;
}

function getComputeUnitsUsed(meta: any): number {
  return meta.computeUnitsConsumed;
}

// Balance Changes
function getBalanceChanges(meta: any, accountKeys: string[]): Array<{account: string, change: number}> {
  const changes = [];
  
  for (let i = 0; i < meta.preBalances.length; i++) {
    const change = meta.postBalances[i] - meta.preBalances[i];
    if (change !== 0) {
      changes.push({
        account: accountKeys[i],
        change: change
      });
    }
  }
  
  return changes;
}

// Program Calls
function getInvokedPrograms(meta: any, accountKeys: string[]): string[] {
  const programs = new Set<string>();
  
  meta.logMessages.forEach((log: string) => {
    const match = log.match(/Program ([1-9A-HJ-NP-Za-km-z]{32,}) invoke/);
    if (match) {
      programs.add(match[1]);
    }
  });
  
  return Array.from(programs);
}

// Token Transfers
function getTokenTransfers(meta: any): Array<{mint: string, from: string, to: string, amount: number}> {
  const transfers = [];
  
  // Compare pre and post token balances
  const preBalances = new Map();
  const postBalances = new Map();
  
  meta.preTokenBalances.forEach((balance: any) => {
    preBalances.set(balance.accountIndex, balance);
  });
  
  meta.postTokenBalances.forEach((balance: any) => {
    postBalances.set(balance.accountIndex, balance);
  });
  
  // Find changes
  for (const [accountIndex, postBalance] of postBalances) {
    const preBalance = preBalances.get(accountIndex);
    const preAmount = preBalance ? parseInt(preBalance.uiTokenAmount.amount) : 0;
    const postAmount = parseInt(postBalance.uiTokenAmount.amount);
    
    if (preAmount !== postAmount) {
      transfers.push({
        mint: postBalance.mint,
        account: postBalance.owner,
        change: postAmount - preAmount,
        decimals: postBalance.uiTokenAmount.decimals
      });
    }
  }
  
  return transfers;
}
```

***

## Exemple complet : décodeur d'échange Jupiter

Voici un exemple complet qui décode les transactions d'échange Jupiter et extrait des informations significatives :

```typescript [expandable] theme={"system"}
import bs58 from 'bs58';
import { subscribe, CommitmentLevel, SubscribeUpdate, LaserstreamConfig } from 'helius-laserstream';

interface SwapInfo {
  signature: string;
  slot: number;
  user: string;
  inputMint: string;
  outputMint: string;
  inputAmount: number;
  outputAmount: number;
  fee: number;
  success: boolean;
}

function convertBuffers(obj: any): any {
  if (!obj) return obj;
  if (Buffer.isBuffer(obj) || obj instanceof Uint8Array) {
    return bs58.encode(obj);
  }
  if (Array.isArray(obj)) {
    return obj.map(item => convertBuffers(item));
  }
  if (typeof obj === 'object') {
    return Object.fromEntries(
      Object.entries(obj).map(([key, value]) => [key, convertBuffers(value)])
    );
  }
  return obj;
}

function decodeJupiterSwap(txUpdate: any): SwapInfo | null {
  const tx = txUpdate.transaction;
  const meta = tx.meta;
  const message = tx.transaction.message;
  
  // Convert binary fields to readable format
  const signature = bs58.encode(tx.signature);
  const accountKeys = message.accountKeys.map((key: any) => bs58.encode(key));
  
  // Check if this is a Jupiter transaction
  const jupiterProgram = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
  if (!accountKeys.includes(jupiterProgram)) {
    return null;
  }
  
  // Extract user (first account is typically the fee payer/user)
  const user = accountKeys[0];
  
  // Get token balance changes
  const tokenChanges = getTokenTransfers(meta);
  
  // Find input (negative change) and output (positive change)
  const inputChange = tokenChanges.find(change => change.change < 0);
  const outputChange = tokenChanges.find(change => change.change > 0);
  
  if (!inputChange || !outputChange) {
    return null;
  }
  
  return {
    signature,
    slot: parseInt(txUpdate.slot),
    user,
    inputMint: inputChange.mint,
    outputMint: outputChange.mint,
    inputAmount: Math.abs(inputChange.change),
    outputAmount: outputChange.change,
    fee: meta.fee,
    success: meta.err === null
  };
}

function getTokenTransfers(meta: any): Array<{mint: string, change: number}> {
  const transfers = [];
  
  const preBalances = new Map();
  const postBalances = new Map();
  
  meta.preTokenBalances.forEach((balance: any) => {
    preBalances.set(balance.accountIndex, balance);
  });
  
  meta.postTokenBalances.forEach((balance: any) => {
    postBalances.set(balance.accountIndex, balance);
  });
  
  for (const [accountIndex, postBalance] of postBalances) {
    const preBalance = preBalances.get(accountIndex);
    const preAmount = preBalance ? parseInt(preBalance.uiTokenAmount.amount) : 0;
    const postAmount = parseInt(postBalance.uiTokenAmount.amount);
    
    if (preAmount !== postAmount) {
      transfers.push({
        mint: postBalance.mint,
        change: postAmount - preAmount
      });
    }
  }
  
  return transfers;
}

async function runJupiterSwapMonitor() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // pick the closest region
  };

  const request = {
    transactions: {
      "Jupiter-swaps": {
        vote: false,
        failed: false,
        accountInclude: ['JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4']
      }
    },
    commitment: CommitmentLevel.PROCESSED,
    accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: []
  };

  const stream = await subscribe(
    config,
    request,
    (update: SubscribeUpdate) => {
      if (update.transaction) {
        const swapInfo = decodeJupiterSwap(update.transaction);
        if (swapInfo) {
          console.log('🔄 Jupiter Swap:');
          console.log(`  User: ${swapInfo.user}`);
          console.log(`  Input: ${swapInfo.inputAmount} of ${swapInfo.inputMint}`);
          console.log(`  Output: ${swapInfo.outputAmount} of ${swapInfo.outputMint}`);
          console.log(`  Fee: ${swapInfo.fee} lamports`);
          console.log(`  Success: ${swapInfo.success}`);
          console.log(`  Signature: ${swapInfo.signature}`);
          console.log('---');
        }
      }
    },
    console.error
  );

  console.log(`✅ Jupiter swap monitor started (id: ${stream.id})`);
  process.on('SIGINT', () => { stream.cancel(); process.exit(0); });
}

runJupiterSwapMonitor().catch(console.error);
```

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.
