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

# LaserStream을 통한 거래 모니터링

> LaserStream을 사용하여 Solana 거래를 실시간으로 스트리밍하세요 — 프로그램 필터링, 실행 세부 정보, 토큰 잔액 변동, 재연결 안전 재생 포함.

거래 모니터링을 통해 거래 실행, 성공/실패 상태, 프로그램 상호 작용 및 토큰 잔액 변경을 Solana 전반에 걸쳐 실시간으로 추적할 수 있습니다. 이 가이드에서는 [`helius-laserstream`](/docs/ko/laserstream/clients) SDK를 사용한 필터링 전략 및 실제 구현을 다룹니다.

<Info>
  **사전 조건:** 이 가이드는 [LaserStream gRPC 빠른 시작](/docs/ko/laserstream/grpc)을 완료하고 API 키를 보유하고 있다고 가정합니다.
</Info>

***

## 거래 필터링 옵션

LaserStream은 Yellowstone gRPC와 동일한 필터 모양을 사용하며 `tokenAccounts` (ATA 확장) 필터를 포함합니다. 설정할 필드는 다음과 같습니다:

* **`accountInclude`** — 이러한 계정 중 하나가 나타나는 경우 일치 (논리 OR)
* **`accountRequired`** — 모든 계정이 나타나는 경우에만 일치 (논리 AND)
* **`accountExclude`** — 이러한 계정 중 하나가 나타나면 드롭
* **`vote` / `failed`** — 투표 및 실패한 거래를 위한 boolean 플래그
* **`tokenAccounts`** — 선택적 연결된 토큰 계정(ATA) 확장 (`"balanceChanged"`, `"all"`, 또는 `"none"`), 따라서 `accountInclude` 지갑도 SPL 토큰 잔고를 소유한 거래와 일치합니다. [토큰 계정(ATA) 필터링](/docs/ko/laserstream/token-account-filtering) 및 아래 **지갑 모니터링** 탭을 참조하세요.

<Tabs>
  <Tab title="프로그램 필터링">
    **특정 프로그램 관련 거래 모니터링**

    관심 있는 프로그램과 관련된 모든 거래 추적:

    ```typescript theme={"system"}
    import { subscribe, CommitmentLevel, LaserstreamConfig, SubscribeRequest } from 'helius-laserstream';

    const subscriptionRequest: SubscribeRequest = {
      transactions: {
        "program-filter": {
          accountInclude: [
            "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // Token Program
            "11111111111111111111111111111111",              // System Program
            "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"  // Your program
          ],
          accountExclude: [],
          accountRequired: [],
          vote: false,
          failed: false
        }
      },
      commitment: CommitmentLevel.CONFIRMED,
      accounts: {},
      slots: {},
      transactionsStatus: {},
      blocks: {},
      blocksMeta: {},
      entry: {},
      accountsDataSlice: [],
    };
    ```

    **최적의 용도:** 프로그램 특정 모니터링, DeFi 프로토콜 추적, 스마트 계약 상호 작용.
  </Tab>

  <Tab title="계정 특정">
    **특정 계정에 영향을 미치는 거래 모니터링**

    ```typescript theme={"system"}
    const subscriptionRequest: SubscribeRequest = {
      transactions: {
        "wallet-filter": {
          accountInclude: [
            "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
            "YourWalletAddress"                                // Your wallet
          ],
          accountExclude: [],
          accountRequired: [],
          vote: false,
          failed: true // Include failures to track errors
        }
      },
      commitment: CommitmentLevel.CONFIRMED,
      accounts: {}, slots: {}, transactionsStatus: {},
      blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
    };
    ```

    **사용 사례:** 지갑 모니터링, 토큰 발행 추적, 계정 활동 대시보드.
  </Tab>

  <Tab title="고급 필터링">
    **여러 필터 기준 결합**

    ```typescript theme={"system"}
    const subscriptionRequest: SubscribeRequest = {
      transactions: {
        "advanced-filter": {
          accountInclude: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
          accountRequired: ["YourProgramId"], // Must include this program
          accountExclude: ["VoteProgram"],     // Exclude vote-related txs
          vote: false,
          failed: false
        }
      },
      commitment: CommitmentLevel.CONFIRMED,
      accounts: {}, slots: {}, transactionsStatus: {},
      blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
    };
    ```

    **필터 논리:** `accountInclude` (OR) **AND** `accountRequired` (AND) **AND NOT** `accountExclude`.
  </Tab>

  <Tab title="지갑 모니터링">
    **직접적인 활동뿐만 아니라 들어오는 토큰 전송까지 포착**

    `accountInclude`는 지갑이 계정 키에 직접 나타나는 거래와만 일치합니다. 누군가 지갑에 SPL 토큰을 보낼 때 전송은 지갑의 \*\*연결된 토큰 계정(ATA)\*\*을 건드리고 지갑 공개키가 아닙니다. 그래서 단순 `accountInclude: [wallet]`는 이를 볼 수 없습니다.

    지갑이 **토큰 잔고를 소유**하는 거래까지 일치시킬 수 있도록 `tokenAccounts`를 설정하세요. 문자열을 받습니다:

    * **`"balanceChanged"`** — 소유한 토큰 잔고가 변했을 때 (또는 해당 토큰 계정이 폐쇄되었을 때) 일치. "실제로 돈이 움직였을 때 알려주세요"에 최적 — 좁고, 낮은 볼륨, 권장 기본값입니다.
    * **`"all"`** — 소유한 토큰 잔고와 관련된 모든 거래와 일치, 변경되지 않은 경우에도. 상당히 높은 볼륨.
    * **`"none"`** — 확장 없음 (필드를 생략한 것과 동일).

    ```typescript theme={"system"}
    const subscriptionRequest: SubscribeRequest = {
      transactions: {
        "wallet-with-tokens": {
          accountInclude: ["YourWalletAddress"],
          accountExclude: [],
          accountRequired: [],
          vote: false,
          failed: false,
          tokenAccounts: "balanceChanged" // also match the wallet's ATAs
        }
      },
      commitment: CommitmentLevel.CONFIRMED,
      accounts: {}, slots: {}, transactionsStatus: {},
      blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
    };
    ```

    <Note>
      일치는 소유자 기반입니다 — 지갑이 소유한 모든 토큰 계정(비표준 계정 포함)을 포착합니다, 파생된 ATA 주소뿐만 아니라. SDK는 문자열을 네트워크 수준 `TokenAccountExpansionControlFlag` 열거형으로 변환합니다.
    </Note>
  </Tab>
</Tabs>

***

## 실용적인 예시

### 예시 1: DEX 거래 모니터링

인기 있는 DEX 프로그램과 관련된 거래 추적:

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

async function monitorDEXTransactions() {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "dex-filter": {
        accountInclude: [
          "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", // Raydium
          "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", // Raydium CLMM
          "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"   // Jupiter
        ],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // Choose your closest region
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction) return;
    const tx = data.transaction.transaction;
    console.log(`\n🔄 DEX Transaction:`);
    console.log(`  Signature: ${bs58.encode(tx.signature)}`);
    console.log(`  Slot: ${data.transaction.slot}`);
    console.log(`  Status: ${tx.meta?.err ? 'Failed' : 'Success'}`);
    console.log(`  Fee: ${tx.meta?.fee || 0} lamports`);
    console.log(`  Compute Units: ${tx.meta?.computeUnitsConsumed || 0}`);

    // Token balance changes
    if (tx.meta?.preTokenBalances?.length > 0) {
      console.log(`  Token Balance Changes:`);
      tx.meta.preTokenBalances.forEach((preBalance: any, index: number) => {
        const postBalance = tx.meta.postTokenBalances[index];
        if (preBalance && postBalance) {
          const change = postBalance.uiTokenAmount.uiAmount - preBalance.uiTokenAmount.uiAmount;
          if (change !== 0) {
            console.log(`    ${preBalance.mint}: ${change > 0 ? '+' : ''}${change}`);
          }
        }
      });
    }
  }, async (error) => {
    console.error('Stream error:', error);
  });
}

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

### 예시 2: 실패한 거래 모니터링

실패한 거래를 추적하여 애플리케이션 문제 발견:

```typescript [expandable] theme={"system"}
async function monitorFailedTransactions() {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "failures": {
        accountInclude: ["YourProgramId"],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: true // Only failed transactions
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction?.meta?.err) return;
    const tx = data.transaction.transaction;
    console.log(`\n❌ Failed Transaction:`);
    console.log(`  Signature: ${bs58.encode(tx.signature)}`);
    console.log(`  Slot: ${data.transaction.slot}`);
    console.log(`  Error: ${JSON.stringify(tx.meta.err)}`);
    console.log(`  Fee: ${tx.meta.fee} lamports`);
    console.log(`  Compute Units: ${tx.meta.computeUnitsConsumed || 0}`);
  }, async (error) => {
    console.error('Stream error:', error);
  });
}
```

### 예시 3: 고가 거래 모니터링

중요한 SOL 전송이 포함된 거래 추적:

```typescript [expandable] theme={"system"}
async function monitorHighValueTransactions() {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "system-program": {
        accountInclude: ["11111111111111111111111111111111"],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: false
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction?.meta) return;
    const tx = data.transaction.transaction;
    const preBalances = tx.meta.preBalances || [];
    const postBalances = tx.meta.postBalances || [];

    let maxChange = 0;
    preBalances.forEach((preBalance: number, index: number) => {
      const postBalance = postBalances[index] || 0;
      maxChange = Math.max(maxChange, Math.abs(postBalance - preBalance));
    });

    const changeInSOL = maxChange / 1e9;
    if (changeInSOL > 10) {
      console.log(`\n💰 High-Value Transaction:`);
      console.log(`  Signature: ${bs58.encode(tx.signature)}`);
      console.log(`  Slot: ${data.transaction.slot}`);
      console.log(`  Max SOL Transfer: ${changeInSOL.toFixed(2)} SOL`);
      console.log(`  Fee: ${tx.meta.fee / 1e9} SOL`);
    }
  }, async (error) => {
    console.error('Stream error:', error);
  });
}
```

### 예시 4: 지갑 모니터링(토큰 전송 포함)

지갑의 돈 이동에 관한 모든 것 — ATA를 건드리는 들어오는 SPL 토큰 전송 포함 — 기본 `accountInclude` 필터에 `tokenAccounts`를 추가하여 모니터링하세요:

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

async function watchWallet(wallet: string) {
  const subscriptionRequest: SubscribeRequest = {
    transactions: {
      "wallet-activity": {
        accountInclude: [wallet],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: false,
        // Also match txs touching token accounts this wallet owns.
        // "balanceChanged" = only when an owned token balance actually moved.
        tokenAccounts: "balanceChanged"
      }
    },
    commitment: CommitmentLevel.CONFIRMED,
    accounts: {}, slots: {}, transactionsStatus: {},
    blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [],
  };

  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com',
  };

  await subscribe(config, subscriptionRequest, async (data) => {
    if (!data.transaction?.transaction) return;
    const tx = data.transaction.transaction;
    console.log(`\n👛 Wallet activity:`);
    console.log(`  Signature: ${bs58.encode(tx.signature)}`);
    console.log(`  Slot: ${data.transaction.slot}`);

    // Surface token balances this wallet owns that changed in the tx
    const owned = (tx.meta?.postTokenBalances || []).filter((b: any) => b.owner === wallet);
    owned.forEach((post: any) => {
      const pre = (tx.meta.preTokenBalances || []).find(
        (b: any) => b.accountIndex === post.accountIndex
      );
      const before = pre?.uiTokenAmount?.uiAmount || 0;
      const after = post.uiTokenAmount?.uiAmount || 0;
      if (after !== before) {
        console.log(`  ${post.mint}: ${after - before > 0 ? '+' : ''}${after - before}`);
      }
    });
  }, async (error) => {
    console.error('Stream error:', error);
  });
}
```

***

## 거래 데이터 구조

<Accordion title="거래 메시지 구조">
  ```typescript theme={"system"}
  {
    signature: string;
    isVote: boolean;
    transaction: {
      message: {
        accountKeys: string[];        // All accounts involved
        instructions: Instruction[];  // Program instructions
        recentBlockhash: string;
      };
      signatures: string[];
    };
    meta: {
      err: any;                      // Error details if failed
      fee: number;                   // Transaction fee in lamports
      computeUnitsConsumed: number;
      preBalances: number[];
      postBalances: number[];
      preTokenBalances: TokenBalance[];
      postTokenBalances: TokenBalance[];
      logMessages: string[];
    };
  }
  ```
</Accordion>

<Accordion title="토큰 잔액 변경">
  ```typescript theme={"system"}
  {
    accountIndex: number;
    mint: string;
    owner: string;
    uiTokenAmount: {
      amount: string;
      decimals: number;
      uiAmount: number;
      uiAmountString: string;
    };
  }
  ```
</Accordion>

<Accordion title="명령 세부 정보">
  ```typescript theme={"system"}
  {
    programIdIndex: number; // Index in accountKeys array
    accounts: number[];
    data: string;           // Instruction data (base58)
  }
  ```
</Accordion>

***

## 필터 논리 참고 자료

<CardGroup cols={2}>
  <Card title="포함 논리 (OR)" icon="plus">
    **`accountInclude`:** 거래는 이러한 계정 중 하나라도 포함해야 합니다.

    `["A", "B"]`는 계정 A 또는 계정 B를 포함하는 거래와 일치합니다.
  </Card>

  <Card title="필수 논리 (AND)" icon="check">
    **`accountRequired`:** 거래는 이러한 모든 계정을 포함해야 합니다.

    `["A", "B"]`는 계정 A와 계정 B를 포함하는 거래와 일치합니다.
  </Card>

  <Card title="제외 논리 (NOT)" icon="minus">
    **`accountExclude`:** 거래는 이러한 계정 중 어떠한 것도 포함해서는 안 됩니다.
  </Card>

  <Card title="결합 논리" icon="code">
    최종 필터: `(accountInclude OR empty) AND (accountRequired AND all) AND NOT (accountExclude OR any)`.
  </Card>
</CardGroup>

***

## 성능 고려 사항

<Tabs>
  <Tab title="볼륨 관리">
    거래 스트림은 대용량일 수 있습니다. 따라잡으려면:

    * 특정 프로그램 필터로 시작하세요 ("모든 거래"에 가입하지 마십시오)
    * 약 1.5초의 추가 지연을 용인할 수 있을 때는 `confirmed` 사용
    * 카운터로 처리 용량 모니터링
    * 큐 뒤에 병렬 소비자를 실행하는 것을 고려

    ```typescript theme={"system"}
    let count = 0;
    const startTime = Date.now();
    // inside your subscribe handler:
    count++;
    if (count % 100 === 0) {
      const elapsed = (Date.now() - startTime) / 1000;
      console.log(`Processing ${(count / elapsed).toFixed(1)} tx/sec`);
    }
    ```
  </Tab>

  <Tab title="데이터 처리">
    메모리를 낮게 유지하기 위해 필요한 것만 추출:

    ```typescript theme={"system"}
    import bs58 from 'bs58';

    function extractTransactionData(tx: any) {
      return {
        signature: bs58.encode(tx.signature),
        slot: tx.slot,
        success: !tx.meta?.err,
        fee: tx.meta?.fee || 0,
        computeUnits: tx.meta?.computeUnitsConsumed || 0,
      };
    }
    ```
  </Tab>
</Tabs>

***

## 오류 처리

<Accordion title="너무 많은 거래">
  **증상:** 압도적인 거래 볼륨.

  **해결책:** 더 엄격한 필터 추가 (`accountRequired`, `accountExclude`); 높은 커밋 사용; 샘플링 또는 속도 제한 구현; 비동기적으로 처리.
</Accordion>

<Accordion title="거래 누락">
  **증상:** 예상 거래가 나타나지 않음.

  **해결책:** 프로그램 주소가 정확한지 확인; 거래가 실제로 존재하는지 확인; 더 빠른 업데이트를 위해 `processed` 시도; 엄격한 `accountRequired`/`accountExclude` 필터 완화.
</Accordion>

<Accordion title="구문 분석 오류">
  **증상:** 거래 데이터를 분석할 수 없음.

  **해결책:** 누락된 필드를 그레이스풀하게 처리; 처리 전에 구조 확인; 구문 분석을 try/catch로 감싸기; [거래 데이터 디코딩](/docs/ko/laserstream/guides/decoding-transaction-data) 참조.
</Accordion>

***

## 다음 단계

<CardGroup cols={2}>
  <Card title="슬롯 및 블록 모니터링" icon="cube" href="/docs/ko/laserstream/guides/slot-and-block-monitoring">
    네트워크 합의 및 블록 생산 추적.
  </Card>

  <Card title="AMM 데이터 스트림 펌프" icon="chart-line" href="/docs/ko/laserstream/guides/stream-pump-amm-data">
    실제 예시: Pump.fun AMM 거래 모니터링.
  </Card>

  <Card title="거래 데이터 디코딩" icon="binary" href="/docs/ko/laserstream/guides/decoding-transaction-data">
    바이너리 거래 페이로드를 읽을 수 있는 Solana 거래로 구문 분석.
  </Card>

  <Card title="Yellowstone 프로토콜 참조" icon="book" href="/docs/ko/grpc/transaction-monitoring">
    원시 Yellowstone gRPC 프로토콜에 대한 동일한 워크플로우.
  </Card>
</CardGroup>
