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

# Mendekode dan Mengurai Data Transaksi

> Pelajari cara mendekode dan mengurai data transaksi dari Laserstream agar dapat memahami transaksi Solana dengan lebih baik.

**Saat menerima data transaksi dari Laserstream, ada dua hal penting yang perlu diperhatikan:**

* **Pesan** → Apa yang ingin dilakukan pengguna (usulan yang mereka tandatangani)
* **Meta** → Apa yang sebenarnya terjadi (hasil eksekusi)

**Tantangannya:** Data transaksi mentah hadir sebagai array byte biner seperti `<Buffer 00 bf a0 e8...>`, bukan alamat dan tanda tangan yang dapat dibaca.

**Panduan ini menunjukkan cara:** Mendekode data biner tersebut menjadi format yang dapat dibaca manusia, mengekstrak informasi penting, dan memahami keseluruhan alur transaksi dari usulan hingga eksekusi.

***

## Stream langsung, tanpa pendekodean

Jalankan klien minimal di bawah ini. Flag filter menghapus transaksi voting dan transaksi yang gagal, sedangkan array `accountInclude` membatasi hasil pada aktivitas yang berinteraksi dengan ID program 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);
```

Konsol Anda kini menampilkan pembungkus—`filters`, `createdAt`, serta cabang `transaction` yang menyembunyikan dua turunan:

* `transaction.transaction.transaction` → **pesan** yang ditandatangani
* `transaction.transaction.meta` → **meta** eksekusi

```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
}
```

Semua yang terlihat seperti `Uint8Array` tetap tidak dapat dibaca untuk sementara.

Saat menjalankan skrip dengan fungsi pendekodean, Anda akan melihat struktur bertingkat yang sebenarnya dengan alamat yang dapat dibaca:

```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"
}
```

***

## Mendekode data biner

**Mengapa perlu mendekode?** Data mentah Laserstream berisi tanda tangan, kunci akun, dan hash sebagai objek biner `Uint8Array` yang tidak dapat dibaca. Anda perlu mengonversinya menjadi string base58 agar dapat memahami transaksi.

**Solusinya:** Laserstream menggunakan Yellowstone gRPC, yang menyediakan utilitas pendekodean bawaan. Alih-alih menulis pendekode terpisah untuk setiap jenis bidang, kami menggunakan satu fungsi rekursif yang mengonversi semua data biner ke format yang dapat dibaca manusia.

```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();
```

Pendekatan ini memanfaatkan pendekodean bawaan sekaligus menangani bidang biner yang memerlukan konversi manual. Struktur transaksi sudah diurai—Anda hanya perlu mengonversi bidang biner ke format yang dapat dibaca manusia.

***

## Memahami struktur transaksi

Setelah data yang didekode dapat dilihat, mari kita pelajari dua bagian utama dari setiap pembaruan transaksi Laserstream. Ingat dari contoh awal bahwa setiap transaksi berisi dua objek utama:

* **Pesan (Usulan)** → `transaction.transaction.transaction` → pesan yang ditandatangani (usulan pengguna)
* **Meta (Eksekusi)** → `transaction.transaction.meta` → metadata eksekusi (respons validator)

Struktur dua bagian ini memberikan alur lengkap: apa yang diminta pengguna dibandingkan dengan apa yang sebenarnya terjadi. Mari kita periksa setiap bagian secara mendetail.

***

## Usulan: semua yang ada di dalam pesan

Pengguna membuat pesan yang menentukan *apa*, *siapa*, dan *sampai kapan*. Berikut cara mendekode setiap bagiannya:

### Header Transaksi

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

`numRequiredSignatures` memberi tahu validator jumlah tanda tangan yang harus diverifikasi, sedangkan dua nilai `numReadonly*` menandai akun yang dapat diperlakukan runtime sebagai hanya-baca sehingga memungkinkan eksekusi paralel.

### Kamus Kunci Akun

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

`accountKeys` adalah daftar biasa berisi kunci publik yang berfungsi sebagai tabel pencarian. Setiap bilangan bulat berikutnya dalam transaksi—`programIdIndex` dan setiap elemen dalam array `accounts` milik instruksi—merujuk kembali ke daftar ini berdasarkan indeks sehingga menghemat lebih dari satu kilobyte per pesan.

### Perlindungan terhadap Pemutaran Ulang

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

`recentBlockhash` kedaluwarsa setelah keluar dari 150 hash blok terakhir, kira-kira sembilan puluh detik di mainnet.

### Instruksi: Perintah Sebenarnya

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

Setiap instruksi berisi tiga bagian utama:

* **ID Program** (`programIdIndex`): Menunjuk ke alamat dalam array `accountKeys` (misalnya, indeks 10 = `ComputeBudget111111111111111111111111111111`)
* **Akun** (`accounts`): String yang dikodekan dengan base58 dan merepresentasikan indeks akun yang digunakan instruksi ini
* **Data** (`data`): Data instruksi sebenarnya yang dikodekan dengan base58

Karena fungsi `convertBuffers`, akun muncul sebagai base58, tetapi sebenarnya berisi indeks akun (misalnya, `"3vtmrQMafzDoG2CBz1iqgXPTnC"` didekode menjadi indeks \[21, 19, 12, 17, 2, 6, 1, 22])

Desain ini berarti bahwa alih-alih mengulangi alamat lengkap berukuran 32 byte, setiap instruksi cukup merujuk posisi dalam tabel pencarian.

### Tanda Tangan: Bukti Otorisasi

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

`signatures` berisi tanda tangan kriptografis yang membuktikan bahwa akun yang diperlukan telah mengotorisasi transaksi ini. Jumlah tanda tangan harus sesuai dengan `header.numRequiredSignatures`.

### Pencarian Tabel Alamat

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

Jika `versioned` adalah `true`, `addressTableLookups` akan muncul bersama tabel on-chain dan dua daftar indeks. Tabel pencarian meningkatkan batas maksimum jumlah alamat menjadi puluhan sekaligus menjaga paket tetap di bawah MTU 1.232 byte.

### Transaksi v1: Anggaran Komputasi dalam Header

Transaksi v1 ([SIMD-0385](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md), Agave 4.2) menambahkan satu bidang lagi ke pesan: `transactionConfig`.

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

Transaksi v1 menyertakan anggaran komputasinya di sini, bukan dalam instruksi program ComputeBudget, sehingga array `instructions` milik transaksi v1 tidak pernah memuat entri `ComputeBudget111111111111111111111111111111`. `priorityFee` adalah total biaya dalam lamport untuk seluruh transaksi, bukan mikro-lamport per unit komputasi. Bidang `null` berarti pengirim tidak menetapkannya. Pesan lama dan v0 tidak memiliki `transactionConfig`, sehingga keberadaannya mengidentifikasi transaksi v1.

Dua hal yang perlu diperiksa dalam pendekode Anda:

* **Ekstraksi biaya prioritas.** Baca `transactionConfig.priorityFee` jika tersedia, dan gunakan pemindaian instruksi ComputeBudget sebagai cadangan hanya untuk transaksi lama dan v0. Kode yang hanya memindai instruksi akan membaca setiap transaksi v1 seolah-olah membayar biaya prioritas nol.
* **Versi proto.** `yellowstone-grpc-proto` 12.6.0 adalah rilis pertama yang menyertakan bidang v1, sedangkan `helius-laserstream` 0.8.4 (JavaScript), 0.6.3 (Rust), dan 0.2.0 (Go) adalah rilis SDK pertama yang dibuat berdasarkan versi tersebut. Versi lama menghapus `transactionConfig` tanpa pemberitahuan.

Lihat [Dukungan transaksi v1](/docs/id/rpc/transaction-v1) untuk daftar lengkap perubahan.

### Cara Semua Bagian Terhubung: Alurnya

Berikut yang terjadi berdasarkan prinsip dasar:

1. **Buat tabel pencarian**: `accountKeys` mencantumkan semua alamat yang akan digunakan transaksi ini
2. **Tetapkan aturan**: `header` menentukan jumlah tanda tangan yang diperlukan dan akun mana yang hanya-baca
3. **Buat perintah**: Setiap `instruction` menunjuk ke:
   * Program (melalui `programIdIndex` → `accountKeys[index]`)
   * Akun yang diperlukan (melalui `accounts` → beberapa posisi `accountKeys[index]`)
   * Data instruksi (dikodekan dalam `data`)
4. **Tambahkan otorisasi**: `signatures` membuktikan bahwa akun yang diperlukan telah menyetujui transaksi ini
5. **Tetapkan kedaluwarsa**: `recentBlockhash` memastikan transaksi ini tidak dapat diputar ulang nanti

***

## Eksekusi: semua yang ada di dalam meta

Pesan menunjukkan apa yang ingin dilakukan pengguna, sedangkan meta menunjukkan apa yang sebenarnya terjadi saat validator mengeksekusi transaksi.

### Informasi eksekusi dasar

**Berhasil/Gagal**

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

* `err: null` = berhasil
* `err: {...}` = gagal dengan detail kesalahan
* `fee` = lamport yang dibebankan untuk transaksi ini

**Perubahan Saldo**

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

Array saldo berkaitan dengan array `accountKeys` berdasarkan indeks:

* Akun 0: Kehilangan 15000 lamport (pembayaran biaya)
* Akun 1: Mendapatkan 1461600 lamport (akun baru dibuat)
* Akun 3: Mendapatkan 2001231920 lamport (akun program)

**Penggunaan Komputasi**

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

Menunjukkan jumlah anggaran komputasi yang digunakan (dari jumlah yang diminta).

### Detail eksekusi lanjutan

**Instruksi Internal**

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

Instruksi internal adalah instruksi tambahan yang dipanggil program selama eksekusi. Instruksi ini bukan bagian dari transaksi asli, tetapi dipicu oleh instruksi utama.

**Pesan 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"
  ]
}
```

Pesan log memberikan jejak kronologis eksekusi program yang menunjukkan program mana yang dipanggil dan setiap pesan log khusus yang dihasilkannya.

**Perubahan Saldo Token**

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

Perubahan saldo token menunjukkan status sebelum dan sesudah untuk akun token SPL, termasuk jumlah yang dapat dibaca manusia dengan penanganan desimal yang tepat.

***

## Pola pendekodean praktis

Berikut beberapa pola umum untuk mengekstrak informasi berguna dari transaksi yang telah didekode:

```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;
}
```

***

## Contoh lengkap: pendekode swap Jupiter

Berikut contoh lengkap yang mendekode transaksi swap Jupiter dan mengekstrak informasi penting:

```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);
```

Contoh ini menunjukkan cara menggabungkan pendekodean pesan dengan analisis meta untuk mengekstrak informasi yang relevan bagi bisnis dari transaksi DeFi yang kompleks.

***

## Poin-poin penting

* **Struktur dua bagian**: Setiap transaksi memiliki **pesan** (apa yang diminta) dan **meta** (apa yang sebenarnya terjadi)
* **Pendekodean biner**: Gunakan `bs58.encode()` untuk mengonversi bidang biner menjadi string base58 yang dapat dibaca
* **Pencarian kunci akun**: Instruksi merujuk akun berdasarkan indeks dalam array `accountKeys`
* **Pelacakan saldo**: Bandingkan `preBalances` dan `postBalances` untuk melihat perubahan yang terjadi
* **Transaksi v1**: Baca anggaran komputasi dan biaya prioritas dari `transactionConfig` jika tersedia; transaksi v1 tidak memiliki instruksi ComputeBudget

Kunci untuk memahami transaksi Solana adalah mengenali bahwa transaksi tersebut dirancang demi efisiensi: alih-alih mengulangi alamat, transaksi menggunakan tabel pencarian dan indeks untuk meminimalkan ukuran transaksi sekaligus memaksimalkan kepadatan informasi.
