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

# Parsed Streams 빠른 시작

> Parsed Streams에 연결하여 처음 필터를 보내고 디코딩된 알림을 읽습니다. 또한 전체 JSON-RPC 2.0 프로토콜 참조가 포함되어 있습니다.

<Tip>
  Parsed Streams가 처음이신가요? [정신 모델](/docs/ko/parsed-streams#정신-모델)을 먼저 읽어보세요. 필터가 왜 그런 모양인지 설명합니다.
</Tip>

## 빠른 시작

<Steps>
  <Step title="Get Access">
    Parsed Streams는 폐쇄형 베타 버전입니다. Helius 팀이 프로젝트 ID를 화이트리스트에 추가하고 연결 엔드포인트를 공유합니다. 폐쇄형 베타에 참여하려면 [여기에서 신청하세요](https://form.typeform.com/to/BlFWKbC9).

    `api-key` 쿼리 매개변수(또는 `x-api-key` 헤더)로 전달되는 프로젝트의 API 키로 인증합니다.
  </Step>

  <Step title="Connect">
    ```bash wscat theme={"system"}
    wscat -c "wss://<ENDPOINT>/?api-key=YOUR_API_KEY"
    ```

    누락된, 잘못된, 또는 화이트리스트에 없는 키는 HTTP 401로 거부됩니다. 연결 한도에 도달한 프로젝트는 HTTP 429를 받게 됩니다.
  </Step>

  <Step title="Subscribe with a Filter">
    필터와 선택적 옵션으로 `parsedTransactionSubscribe`을(를) 보냅니다:

    ```json theme={"system"}
    {"jsonrpc":"2.0","id":1,"method":"parsedTransactionSubscribe","params":[{"programs":["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}]}
    ```

    응답 `result`은(는) 정수 **구독 ID**입니다:

    ```json theme={"system"}
    { "jsonrpc": "2.0", "id": 1, "result": 23 }
    ```
  </Step>

  <Step title="Read a Notification">
    일치하는 각 거래는 이미 디코딩된 `parsedTransactionNotification`으로 도착하며, 필터의 명령어를 가리키는 `matchedIndexes`가 있습니다. 전체 형식을 보려면 [Notifications](#알림)을 참조하세요.
  </Step>

  <Step title="Unsubscribe">
    ```json theme={"system"}
    { "jsonrpc": "2.0", "id": 2, "method": "parsedTransactionUnsubscribe", "params": [23] }
    ```

    또는 연결을 닫기만 하면 모든 구독이 제거됩니다.
  </Step>
</Steps>

## 가이드

<CardGroup cols={2}>
  <Card title="Jupiter 스왑 추적" icon="arrow-right-arrow-left" href="/docs/ko/parsed-streams/guides/track-jupiter-swaps">
    구독 전에 신뢰할 수 있는 필터를 만들기 위해 `describeProgram`을 사용하세요.
  </Card>

  <Card title="Pump.fun 민트 추적" icon="rocket" href="/docs/ko/parsed-streams/guides/track-pumpfun-mints">
    새로운 Pump.fun 토큰 배포를 모두 기록하는 재연결 안전 리스너.
  </Card>

  <Card title="재연결 처리" icon="rotate" href="/docs/ko/parsed-streams/guides/handling-reconnects">
    유휴 시간 초과 및 배포를 극복하고, 놓친 부분을 정확하게 백필합니다.
  </Card>
</CardGroup>

## 프로토콜 참조

Parsed Streams는 단일 WebSocket 연결을 통해 **JSON-RPC 2.0**을 사용합니다. 각 요청은 동일한 `id`로 응답합니다. 그런 다음 구독은 당신이 구독 취소하거나 연결을 끊을 때까지 `parsedTransactionNotification` 메시지를 푸시합니다.

| 메서드                            | 목적                        |
| ------------------------------ | ------------------------- |
| `parsedTransactionSubscribe`   | 필터와 함께 구독 시작              |
| `parsedTransactionUnsubscribe` | 구독 중지                     |
| `ping`                         | 생존 검사; 현재 슬롯 반환           |
| `describeProgram`              | 프로그램의 명령어, 이벤트 및 계정 역할 목록 |

### 구독

필터와 선택적 옵션으로 `parsedTransactionSubscribe`을(를) 전송합니다. 응답 `result`은(는) 정수 **구독 id**입니다.

```json Request theme={"system"}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "parsedTransactionSubscribe",
  "params": [
    {
      "programs": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
      "instructionNames": ["route", "shared_accounts_route"],
      "accounts": {
        "include": ["So11111111111111111111111111111111111111112"],
        "roles": { "user_transfer_authority": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" }
      },
      "includeFailed": false,
      "includeCpi": true
    },
    { "commitment": "confirmed", "details": "full" }
  ]
}
```

```json Response theme={"system"}
{ "jsonrpc": "2.0", "id": 1, "result": 23 }
```

#### 필터 필드

`programs` 또는 `accounts.include` 중 하나 이상이 필요합니다. 설정한 필드는 **AND**와 결합됩니다: 명령어는 일치하기 위해 모든 조건을 만족해야 합니다.

<ParamField body="programs" type="string[]">
  매칭할 프로그램 ID(이름이 아닌 base58 주소). 명령어의 프로그램이 이 목록에 있을 경우 일치합니다. 목록 내에서는 OR입니다.
</ParamField>

<ParamField body="instructionNames" type="string[]">
  `route`과 같은 디코딩된 명령어 이름입니다. 목록 내에서는 OR입니다. 카탈로그에서 식별할 수 있는 이름만 매치될 수 있으므로 `describeProgram`에서 이름을 가져가세요.
</ParamField>

<ParamField body="accounts.include" type="string[]">
  계정 주소. 명령어의 계정 목록에 이들 중 하나가 나타나면 일치합니다. 목록 내에서는 OR입니다. 디코딩 여부와 관계없이 모든 명령어에서 작동합니다. 프로그램 ID 자체는 여기서 계정으로 간주되지 않습니다.
</ParamField>

<ParamField body="accounts.roles" type="object">
  디코딩된 계정 역할 이름과 주소의 맵입니다. 각 항목은 성립해야 하며(항목 간 AND) 이 기능에는 명령어가 디코딩되어야 합니다. 역할 이름은 대소문자 변경 없이 정확히 맞아야 하므로 추측하지 말고 `describeProgram`에서 복사하세요.
</ParamField>

<ParamField body="includeFailed" type="boolean" default="false">
  실패한 거래의 명령어 포함.
</ParamField>

<ParamField body="includeCpi" type="boolean" default="true">
  내부(CPI) 명령어도 매칭에 적합합니다. 상위 수준 명령어만 매칭하려면 `false`를 설정하세요.
</ParamField>

필터나 옵션의 어디에서든 알 수 없는 필드는 묵과되는 대신 `-32602`으로 거부되므로 오타는 아무것도 매칭되지 않는 대신 크게 실패합니다.

#### 옵션

두 번째 매개변수는 선택사항입니다.

<ParamField body="commitment" type="string" default="confirmed">
  지원되는 유일한 값은 `confirmed`입니다.
</ParamField>

<ParamField body="details" type="string" default="full">
  각 알림에 포함되는 내용입니다. `full`: 전체 거래, 모든 명령어, 그리고 필터 히트를 가리키는 `matchedIndexes`을 포함합니다. `matched`: 매칭된 명령어만, 인덱스 목록 없음. `raw`: 매칭된 명령어만, 각 위치까지 줄어든 상태, `programId`, base58의 `data` 블롭(디코딩 필드 및 `accountKeys` 배열 없음). 대역폭이 컨텍스트보다 중요하면 `matched`을 사용하고, 명령어 데이터를 직접 디코딩하고 바이트만 필요할 때는 `raw`을 사용하세요.
</ParamField>

프로젝트는 모든 API 키에 걸쳐 **100 개의 동시 연결**을 유지할 수 있습니다.

### 알림

구독당 매칭된 거래당 하나의 알림. 기본 `details: "full"`:

```json theme={"system"}
{
  "jsonrpc": "2.0",
  "method": "parsedTransactionNotification",
  "params": {
    "subscription": 23,
    "result": {
      "context": { "slot": 430172053 },
      "value": {
        "transaction": {
          "signature": "3riSYL4HTRxgQjLayt6L2JPaDR3oaEQg1H4v3fnjUxNU...",
          "slot": 430172053,
          "blockTime": null,
          "feePayer": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX",
          "fee": 5000,
          "accountKeys": ["6jduWNCT...", "..."],
          "status": "ok",
          "error": null,
          "summary": {
            "type": "swap",
            "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
            "parsedData": {
              "type": "swap",
              "protocol": "jupiter",
              "kind": "swap",
              "in_amount": "1000000",
              "actual_out_amount": "183985",
              "input_mint": "So11111111111111111111111111111111111111112",
              "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
            }
          },
          "nativeTransfers": [
            { "fromUserAccount": "6jduWNCT...", "toUserAccount": "DfXygSm4...", "amount": 1000000 }
          ],
          "tokenTransfers": [
            {
              "fromUserAccount": "6jduWNCT...",
              "toUserAccount": "AeUfFU6L...",
              "fromTokenAccount": "HLaEoW1s...",
              "toTokenAccount": "G13P9kSY...",
              "rawTokenAmount": 183985,
              "decimals": 6,
              "tokenStandard": "Fungible",
              "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
            }
          ]
        },
        "instructions": [
          {
            "topIndex": 4,
            "innerIndex": null,
            "stackHeight": 1,
            "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
            "programName": "jupiter",
            "instructionName": "route",
            "summary": {
              "type": "swap",
              "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
              "parsedData": {
                "type": "swap",
                "protocol": "jupiter",
                "kind": "swap",
                "in_amount": "1000000",
                "actual_out_amount": "183985",
                "input_mint": "So11111111111111111111111111111111111111112",
                "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
              }
            },
            "decoded": {
              "args": { "in_amount": "1000000", "slippage_bps": 50 },
              "accounts": [
                { "name": "user_transfer_authority", "pubkey": "9xQe...", "isSigner": true, "isWritable": false }
              ]
            }
          }
        ],
        "matchedIndexes": [8, 13]
      }
    }
  }
}
```

읽는 방법:

* \*\*`transaction`\*\*은(는) 전체 컨텍스트입니다. `fee`은(는) lamports 단위로 있습니다. `accountKeys`은(는) 주소 조회 테이블에서 로드된 키를 포함하여 체인이 보고하는 순서대로 완전한 키 목록입니다. `feePayer`은(는) 항상 `accountKeys[0]`입니다. `error`은(는) 구조화된 JSON으로 거래 오류를 전달합니다. 예: `{"InstructionError": [2, {"Custom": 6001}]}`, `status`이(가) `"error"`일 때.
* \*\*`summary`\*\*은(는) 모든 곳에서 하나의 형태입니다: `type`(예: `swap` 또는 `transfer`), 사람이 읽을 수 있는 `description`, 프로토콜, 금액 및 민트를 대상으로 하는, 분석기가 행동을 인식할 때의 구조화된 `parsedData` 페이로드. `transaction.summary`은(는) 트랜잭션의 헤드라인 동작을 레이블로 표시합니다: 인식된 각 명령어는 동일한 형식의 자체 `summary`을(를) 가지고 있습니다. 거래 내 모든 스왑을 수집하려면 `instructions`을 반복하고 `summary.parsedData`, `summary.type`이(가) `"swap"`일 때를 읽습니다.
* **`nativeTransfers`** 및 \*\*`tokenTransfers`\*\*은(는) 분석기가 전체 거래에서 추출한 SOL 및 토큰 이동을 나열하여 스트림 및 API 소비자가 처리 코드를 공유할 수 있도록 합니다. 두 가지 모두 항상 존재하며 비어 있을 수도 있습니다.
* \*\*`instructions`\*\*은(는) 실행 순서에 따라 거래의 모든 명령어입니다: 각 최상위 명령어는 내부 명령어를 따릅니다. 각 항목은 자체 위치를 가지고 있습니다: `topIndex`은(는) 속한 최상위 명령어입니다(0부터 시작), `innerIndex`은(는) 해당 명령어의 내부 호출 중 위치입니다(`null`은(는) 최상위 명령어 자체를 의미), `stackHeight`은(는) 호출 깊이입니다(최상위인 경우 1). 배열 위치가 아닌 이러한 요소를 사용합니다.
* \*\*`matchedIndexes`\*\*은(는) 필터가 실제로 히트한 항목을 알려주는 `instructions`으로의 인덱스입니다. 나머지는 컨텍스트를 위해 있습니다. `details: "matched"`에서는 배열에 히트만 포함되며 `matchedIndexes`은(는) 없습니다.
* **`decoded` 이름은 snake\_case**로 구성되어 있습니다(`in_amount`, `user_transfer_authority`), 프로그램의 IDL에 게시된 대로입니다. 정수 인수는 일반적으로 문자열입니다(`"1000000"`), u64 값은 JavaScript 숫자에 맞지 않기 때문입니다.
* \*\*`blockTime`\*\*은(는) 현재 항상 `null`입니다. 이것을 기반으로 구축하지 마세요.
* 하나의 거래 내에 디코딩된 명령어와 디코딩되지 않은 명령어가 혼합되어 있을 수 있습니다: 완전히 디코딩된 스왑은 인식되지 않은 메모와 나란히 존재할 수 있습니다. `decoded`을 기준으로 분기하세요: `null`일 때 명령어는 `rawData`(base58 바이트)와 `rawAccounts`(평문 공개키 목록)를 대신 운반하여 항상 작업할 수 있는 항목을 제공합니다.

`details: "raw"`에서는 `value`가 트랜잭션 메타와 블롭으로 축소됩니다. `accountKeys`, `nativeTransfers`, `tokenTransfers`, `matchedIndexes` 및 모든 디코딩 필드가 사라집니다. 매칭된 모든 명령어는 위치, 프로그램 및 체인에 표시되는 것과 정확히 같은 base58의 `data` 바이트입니다(카탈로그가 디코딩할 수 있던 명령어에도 표시).

```json theme={"system"}
"value": {
  "transaction": {
    "signature": "3riSYL4HTRxgQjLayt6L2JPaDR3oaEQg1H4v3fnjUxNU...",
    "slot": 430172053,
    "blockTime": null,
    "feePayer": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX",
    "fee": 5000,
    "status": "ok",
    "error": null,
    "summary": {
      "type": "swap",
      "description": "6jduWNCTQzG91JGBchfGGxd55Vi5FxJCCJEV18RkXzJX swapped 0.001 SOL for 0.183985 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v via Jupiter",
      "parsedData": {
        "type": "swap",
        "protocol": "jupiter",
        "kind": "swap",
        "in_amount": "1000000",
        "actual_out_amount": "183985",
        "input_mint": "So11111111111111111111111111111111111111112",
        "output_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
      }
    }
  },
  "instructions": [
    { "topIndex": 4, "innerIndex": null, "stackHeight": 1, "programId": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", "data": "3Bxs4h24hBtQy9rw" }
  ]
}
```

### 구독 취소

```json theme={"system"}
{ "jsonrpc": "2.0", "id": 2, "method": "parsedTransactionUnsubscribe", "params": [23] }
```

구독이 존재하고 당신의 것이라면 `true`을 반환합니다. 알림은 즉시 중지됩니다. 연결을 닫으면 모든 구독이 제거됩니다.

### 디스커버리

이러한 유형의 API에서 가장 일반적인 실패는 유효하지만 아무것도 매칭되지 않는 필터입니다. 일반적으로 추측한 명령어나 역할 이름 때문입니다. `describeProgram`은(는) 비교하는 정확한 이름을 반환함으로써 이를 방지합니다:

```json Request theme={"system"}
{ "jsonrpc": "2.0", "id": 1, "method": "describeProgram", "params": [{ "program": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" }] }
```

```json Response theme={"system"}
{
  "jsonrpc": "2.0", "id": 1,
  "result": {
    "id": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
    "name": "jupiter",
    "instructions": ["route", "shared_accounts_route", "exact_out_route"],
    "events": ["SwapEvent"],
    "roles": ["user_transfer_authority", "destination_token_account"]
  }
}
```

프로그램 주소나 카탈로그 이름을 전달할 수 있지만, **주소를 선호하세요**: 이름은 프로그램 버전 간에 모호할 수 있습니다(여러 카탈로그 항목이 `jupiter`로 명명되어 있으며, 이름 조회는 이전 것에 대해 해결될 수 있습니다). 이름으로 조회하는 경우, 구독하려는 프로그램이 `result.id`이(가) 맞는지 확인하세요.

추천 흐름: 정확한 명령어 및 역할 이름을 얻으려면 `describeProgram`을 사용하고, 해당 이름으로 필터를 작성한 다음 구독하세요. [Track Jupiter Swaps](/docs/ko/parsed-streams/guides/track-jupiter-swaps) 가이드는 이 전체 과정을 처음부터 끝까지 안내합니다.

### 제한 사항

| 제한                       | 값                    |
| ------------------------ | -------------------- |
| 프로젝트당 동시 연결              | 100                  |
| 연결당 구독                   | 25                   |
| 클라이언트 메시지                | 초당 10개, 20개의 버스트     |
| 클라이언트 메시지 크기             | 64 KiB               |
| 각 필터당 `programs`         | 10                   |
| 각 필터당 `instructionNames` | 최대 64자, 50           |
| 각 필터당 `accounts.include` | 100                  |
| 각 필터당 `accounts.roles`   | 최대 64자, 20           |
| 연결당 출력 버퍼                | 2048개의 알림, 이후 연결이 닫힘 |

### 오류

오류는 JSON-RPC 2.0을 따릅니다: `{ "error": { "code": <int>, "message": "<text>" }, "id": <id> }`. 메시지는 정확히 무엇이 잘못되었는지 어디에서 문제가 발생했는지 알려줍니다.

| 코드       | 의미                                                 |
| -------- | -------------------------------------------------- |
| `-32700` | 구문 분석 오류 (유효하지 않은 JSON)                            |
| `-32600` | 잘못된 요청                                             |
| `-32601` | 메서드를 찾을 수 없음                                       |
| `-32602` | 잘못된 매개변수: 잘못된 공개키, 알 수 없는 필드, 지원되지 않는 커밋 또는 세부정보 값 |
| `-32000` | 필터 제한 초과                                           |
| `-32001` | 서버 준비가 되지 않음; 백오프로 재시도                             |
| `-32002` | 속도 제한 (초당 10개 메시지)                                 |
| `-32006` | 너무 많은 구독 (연결당 25개)                                 |

연결은 WebSocket 종료 코드로도 닫을 수 있습니다 — 각 의미와 복구 방법은 [Handling Reconnects](/docs/ko/parsed-streams/guides/handling-reconnects)를 참조하세요.

## 클라이언트 예제

<CodeGroup>
  ```bash wscat theme={"system"}
  wscat -c "wss://<ENDPOINT>/?api-key=<API_KEY>"
  # then send:
  {"jsonrpc":"2.0","id":1,"method":"parsedTransactionSubscribe","params":[{"programs":["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}]}
  ```

  ```typescript TypeScript theme={"system"}
  import WebSocket from "ws";

  const ws = new WebSocket("wss://<ENDPOINT>/?api-key=<API_KEY>");

  ws.on("open", () => {
    ws.send(JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "parsedTransactionSubscribe",
      params: [{ programs: ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"] }],
    }));
  });

  ws.on("message", (data) => {
    const msg = JSON.parse(data.toString());
    if (msg.method === "parsedTransactionNotification") {
      const { transaction, instructions, matchedIndexes } = msg.params.result.value;
      for (const i of matchedIndexes ?? instructions.keys()) {
        const ix = instructions[i];
        console.log(transaction.signature, ix.programName, ix.instructionName, ix.decoded?.args);
      }
    }
  });
  ```

  ```python Python theme={"system"}
  import asyncio, json, websockets

  URL = "wss://<ENDPOINT>/?api-key=<API_KEY>"

  async def main():
      async with websockets.connect(URL) as ws:
          await ws.send(json.dumps({
              "jsonrpc": "2.0", "id": 1, "method": "parsedTransactionSubscribe",
              "params": [{"programs": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"]}],
          }))
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get("method") == "parsedTransactionNotification":
                  value = msg["params"]["result"]["value"]
                  for i in value.get("matchedIndexes") or range(len(value["instructions"])):
                      ix = value["instructions"][i]
                      print(ix.get("programName"), ix.get("instructionName"), (ix.get("decoded") or {}).get("args"))

  asyncio.run(main())
  ```
</CodeGroup>
