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

# Giải mã và phân tích dữ liệu giao dịch

> Tìm hiểu cách giải mã và phân tích dữ liệu giao dịch từ Laserstream để hiểu rõ hơn về các giao dịch Solana.

**Khi nhận dữ liệu giao dịch từ Laserstream, bạn cần chú ý hai phần quan trọng:**

* **Thông điệp** → Điều người dùng muốn thực hiện (đề xuất đã ký của họ)
* **Siêu dữ liệu** → Điều thực sự đã xảy ra (kết quả thực thi)

**Thách thức:** Dữ liệu giao dịch thô được cung cấp dưới dạng các mảng byte nhị phân như `<Buffer 00 bf a0 e8...>` thay vì các địa chỉ và chữ ký có thể đọc được.

**Hướng dẫn này chỉ cho bạn cách:** Giải mã dữ liệu nhị phân đó thành định dạng con người có thể đọc được, trích xuất thông tin hữu ích và hiểu toàn bộ diễn biến giao dịch từ đề xuất đến thực thi.

***

## Luồng trực tiếp, không giải mã

Chạy máy khách tối giản bên dưới. Các cờ bộ lọc loại bỏ giao dịch biểu quyết và giao dịch thất bại, còn mảng `accountInclude` giới hạn kết quả ở hoạt động liên quan đến ID chương trình 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);
```

Bảng điều khiển hiện hiển thị một trình bao gồm `filters`, `createdAt` cùng với một nhánh `transaction` ẩn hai phần tử con:

* `transaction.transaction.transaction` → **thông điệp** đã ký
* `transaction.transaction.meta` → **siêu dữ liệu** thực thi

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

Mọi nội dung có dạng `Uint8Array` hiện vẫn chưa thể đọc được.

Khi chạy tập lệnh với hàm giải mã, bạn sẽ thấy cấu trúc lồng nhau thực tế cùng các địa chỉ có thể đọc được:

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

***

## Giải mã dữ liệu nhị phân

**Tại sao cần giải mã?** Dữ liệu Laserstream thô chứa chữ ký, khóa tài khoản và hàm băm dưới dạng các đối tượng nhị phân `Uint8Array` không thể đọc được. Bạn cần chuyển đổi chúng thành chuỗi base58 để hiểu giao dịch.

**Giải pháp:** Laserstream sử dụng Yellowstone gRPC, cung cấp các tiện ích giải mã tích hợp sẵn. Thay vì viết bộ giải mã riêng cho từng loại trường, chúng ta sử dụng một hàm đệ quy để chuyển đổi toàn bộ dữ liệu nhị phân sang định dạng con người có thể đọc được.

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

Phương pháp này tận dụng khả năng giải mã tích hợp sẵn, đồng thời xử lý các trường nhị phân cần chuyển đổi thủ công. Cấu trúc giao dịch đã được phân tích cú pháp — bạn chỉ cần chuyển đổi các trường nhị phân sang định dạng con người có thể đọc được.

***

## Tìm hiểu cấu trúc giao dịch

Bây giờ khi đã có thể xem dữ liệu được giải mã, hãy khám phá hai phần chính trong mỗi bản cập nhật giao dịch Laserstream. Như ví dụ ban đầu, mỗi giao dịch chứa hai đối tượng chính:

* **Thông điệp (Đề xuất)** → `transaction.transaction.transaction` → thông điệp đã ký (đề xuất của người dùng)
* **Siêu dữ liệu (Thực thi)** → `transaction.transaction.meta` → siêu dữ liệu thực thi (phản hồi của trình xác thực)

Cấu trúc hai phần này cho biết toàn bộ diễn biến: điều người dùng yêu cầu so với điều thực sự đã xảy ra. Hãy xem xét chi tiết từng phần.

***

## Đề xuất: mọi nội dung bên trong thông điệp

Người dùng tạo một thông điệp xác định *nội dung gì*, *ai* và *đến khi nào*. Sau đây là cách giải mã từng phần:

### Tiêu đề giao dịch

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

`numRequiredSignatures` cho trình xác thực biết cần xác minh bao nhiêu chữ ký, còn hai giá trị `numReadonly*` đánh dấu những tài khoản mà môi trường thực thi có thể xử lý ở chế độ chỉ đọc, qua đó cho phép thực thi song song.

### Từ điển khóa tài khoản

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

`accountKeys` là một danh sách khóa công khai đơn giản đóng vai trò bảng tra cứu. Mọi số nguyên xuất hiện sau đó trong giao dịch — `programIdIndex` và từng phần tử trong mảng `accounts` của một lệnh — đều trỏ ngược về danh sách này theo chỉ mục, giúp tiết kiệm hơn một kilobyte cho mỗi thông điệp.

### Bảo vệ chống phát lại

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

`recentBlockhash` hết hạn khi không còn nằm trong 150 hàm băm khối gần nhất, tương đương khoảng chín mươi giây trên mainnet.

### Lệnh: Các lệnh thực tế

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

Mỗi lệnh gồm ba phần chính:

* **ID chương trình** (`programIdIndex`): Trỏ đến một địa chỉ trong mảng `accountKeys` (ví dụ: chỉ mục 10 = `ComputeBudget111111111111111111111111111111`)
* **Tài khoản** (`accounts`): Một chuỗi được mã hóa bằng base58, biểu thị các chỉ mục tài khoản mà lệnh này tác động đến
* **Dữ liệu** (`data`): Dữ liệu lệnh thực tế được mã hóa bằng base58

Do hàm `convertBuffers`, các tài khoản xuất hiện dưới dạng base58 nhưng thực tế chứa các chỉ mục tài khoản (ví dụ: `"3vtmrQMafzDoG2CBz1iqgXPTnC"` được giải mã thành các chỉ mục \[21, 19, 12, 17, 2, 6, 1, 22])

Thiết kế này có nghĩa là thay vì lặp lại toàn bộ địa chỉ 32 byte, mỗi lệnh chỉ tham chiếu đến các vị trí trong bảng tra cứu.

### Chữ ký: Bằng chứng ủy quyền

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

`signatures` chứa các chữ ký mật mã chứng minh rằng những tài khoản bắt buộc đã ủy quyền cho giao dịch này. Số lượng chữ ký phải khớp với `header.numRequiredSignatures`.

### Tra cứu bảng địa chỉ

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

Nếu `versioned` là `true`, `addressTableLookups` sẽ xuất hiện cùng một bảng trên chuỗi và hai danh sách chỉ mục. Bảng tra cứu nâng giới hạn cứng về số lượng địa chỉ lên hàng chục, trong khi vẫn giữ gói tin dưới MTU 1.232 byte.

### Giao dịch v1: Ngân sách tính toán trong tiêu đề

Giao dịch v1 ([SIMD-0385](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-transaction-v1.md), Agave 4.2) bổ sung thêm một trường vào thông điệp: `transactionConfig`.

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

Giao dịch v1 chứa ngân sách tính toán tại đây thay vì trong các lệnh của chương trình ComputeBudget, vì vậy mảng `instructions` của giao dịch v1 không bao giờ chứa mục `ComputeBudget111111111111111111111111111111`. `priorityFee` là tổng phí tính bằng lamport cho toàn bộ giao dịch, không phải số micro-lamport trên mỗi đơn vị tính toán. Trường `null` có nghĩa là người gửi chưa thiết lập giá trị này. Các thông điệp cũ và v0 không có `transactionConfig`, vì vậy sự hiện diện của trường này xác định một giao dịch v1.

Hai điểm cần kiểm tra trong bộ giải mã:

* **Trích xuất phí ưu tiên.** Đọc `transactionConfig.priorityFee` khi trường này tồn tại và chỉ quay lại quét các lệnh ComputeBudget đối với giao dịch cũ và v0. Mã chỉ quét các lệnh sẽ xác định mọi giao dịch v1 là không trả phí ưu tiên.
* **Phiên bản Proto.** `yellowstone-grpc-proto` 12.6.0 là bản phát hành đầu tiên chứa các trường v1, còn `helius-laserstream` 0.8.4 (JavaScript), 0.6.3 (Rust) và 0.2.0 (Go) là các bản phát hành SDK đầu tiên được xây dựng trên phiên bản đó. Các phiên bản cũ âm thầm loại bỏ `transactionConfig`.

Xem [Hỗ trợ giao dịch v1](/docs/vi/rpc/transaction-v1) để biết danh sách đầy đủ các thay đổi.

### Cách tất cả kết nối với nhau: Luồng xử lý

Sau đây là những gì diễn ra theo nguyên lý cơ bản:

1. **Xây dựng bảng tra cứu**: `accountKeys` liệt kê tất cả địa chỉ mà giao dịch này sẽ tác động đến
2. **Đặt quy tắc**: `header` xác định số lượng chữ ký bắt buộc và những tài khoản nào ở chế độ chỉ đọc
3. **Tạo các lệnh**: Mỗi `instruction` trỏ đến:
   * Một chương trình (thông qua `programIdIndex` → `accountKeys[index]`)
   * Các tài khoản cần thiết (thông qua `accounts` → nhiều vị trí `accountKeys[index]`)
   * Dữ liệu lệnh (được mã hóa trong `data`)
4. **Thêm ủy quyền**: `signatures` chứng minh các tài khoản bắt buộc đã phê duyệt giao dịch này
5. **Đặt thời hạn**: `recentBlockhash` đảm bảo giao dịch này không thể được phát lại sau đó

***

## Thực thi: mọi nội dung bên trong siêu dữ liệu

Trong khi thông điệp cho biết người dùng muốn làm gì, siêu dữ liệu cho biết điều thực sự đã xảy ra khi các trình xác thực thực thi giao dịch.

### Thông tin thực thi cơ bản

**Thành công/Thất bại**

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

* `err: null` = thành công
* `err: {...}` = thất bại kèm thông tin chi tiết về lỗi
* `fee` = số lamport được tính phí cho giao dịch này

**Thay đổi số dư**

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

Các mảng số dư tương ứng với mảng `accountKeys` theo chỉ mục:

* Tài khoản 0: Mất 15000 lamport (thanh toán phí)
* Tài khoản 1: Nhận 1461600 lamport (tài khoản mới được tạo)
* Tài khoản 3: Nhận 2001231920 lamport (tài khoản chương trình)

**Mức sử dụng tài nguyên tính toán**

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

Cho biết lượng ngân sách tính toán đã được sử dụng (trong tổng số lượng được yêu cầu).

### Chi tiết thực thi nâng cao

**Lệnh bên trong**

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

Lệnh bên trong là các lệnh bổ sung do chương trình gọi trong quá trình thực thi. Chúng không thuộc giao dịch ban đầu mà được kích hoạt bởi các lệnh chính.

**Thông báo nhật ký**

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

Thông báo nhật ký cung cấp dấu vết theo trình tự thời gian của quá trình thực thi chương trình, cho biết những chương trình nào đã được gọi và mọi thông báo nhật ký tùy chỉnh mà chúng xuất ra.

**Thay đổi số dư token**

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

Các thay đổi số dư token cho biết trạng thái trước và sau của tài khoản token SPL, bao gồm số lượng ở dạng con người có thể đọc được với phần thập phân được xử lý chính xác.

***

## Các mẫu giải mã thực tế

Sau đây là các mẫu phổ biến để trích xuất thông tin hữu ích từ những giao dịch đã giải mã:

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

***

## Ví dụ hoàn chỉnh: Bộ giải mã giao dịch hoán đổi Jupiter

Sau đây là ví dụ hoàn chỉnh về cách giải mã các giao dịch hoán đổi Jupiter và trích xuất thông tin hữu ích:

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

Ví dụ này minh họa cách kết hợp việc giải mã thông điệp với phân tích siêu dữ liệu để trích xuất thông tin liên quan đến nghiệp vụ từ các giao dịch DeFi phức tạp.

***

## Những điểm chính cần ghi nhớ

* **Cấu trúc hai phần**: Mỗi giao dịch có một **thông điệp** (nội dung được yêu cầu) và **siêu dữ liệu** (điều thực sự đã xảy ra)
* **Giải mã nhị phân**: Sử dụng `bs58.encode()` để chuyển đổi các trường nhị phân thành chuỗi base58 có thể đọc được
* **Tra cứu khóa tài khoản**: Các lệnh tham chiếu đến tài khoản theo chỉ mục trong mảng `accountKeys`
* **Theo dõi số dư**: So sánh `preBalances` và `postBalances` để xem những gì đã thay đổi
* **Giao dịch v1**: Đọc ngân sách tính toán và phí ưu tiên từ `transactionConfig` khi trường này hiện diện; giao dịch v1 không có lệnh ComputeBudget

Chìa khóa để hiểu các giao dịch Solana là nhận ra rằng chúng được thiết kế để đạt hiệu quả cao: thay vì lặp lại địa chỉ, chúng sử dụng bảng tra cứu và chỉ mục để giảm thiểu kích thước giao dịch trong khi tối đa hóa mật độ thông tin.
