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

# Бандлы в Apex

> Отправляйте атомарные бандлы Solana от 1 до 4 транзакций через OrbitFlare Apex с помощью sendBundle или POST /send-bundle и отслеживайте их через getInflightBundleStatuses.

## Что такое бандл

Бандл представляет собой группу **от 1 до 4 транзакций**, которые исполняются **по порядку** и по принципу **«всё или ничего»**. Либо все транзакции бандла попадают в один блок в заданном вами порядке, либо не попадает ни одна.

Используйте бандл, когда более поздняя транзакция имеет смысл только при успехе более ранней, например подготовительная транзакция, за которой следует своп.

<Warning>
  **Бандлы попадают в блок только у лидеров с поддержкой Jito.** Группу транзакций невозможно сохранить атомарной на путях с весом по стейку и прямого TPU, поэтому бандлы идут только по пути block engine. Когда текущий лидер не использует Jito, бандл ждёт следующего, который использует. Для одиночной транзакции обычная [отправка](/ru/apex/sending-transactions) использует все три пути и попадает в блок быстрее.
</Warning>

## Правила

| Правило              | Подробности                                                                                                                                                                      |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Размер**           | От 1 до 4 транзакций                                                                                                                                                             |
| **Порядок**          | Исполняются в заданном порядке                                                                                                                                                   |
| **Атомарность**      | Всё или ничего                                                                                                                                                                   |
| **Чаевые**           | **Ровно одна** транзакция в бандле содержит чаевые Apex не ниже минимума вашего тарифа                                                                                           |
| **Размер участника** | Каждая транзакция ограничена 1232 байтами. Размеры [транзакции v1](/ru/apex/transaction-v1) внутри бандла не действуют                                                           |
| **Частота**          | 2 бандла в секунду на API-ключ с коротким всплеском до 4, сверх вашего лимита транзакций. При превышении возвращается HTTP `429` или `-32029`                                    |
| **Статус**           | `getInflightBundleStatuses` требует API-ключ, которым был отправлен бандл, и принимает до 100 id. Бандл, отправленный другим ключом, отображается как `Invalid`                  |
| **Повторы**          | Apex отправляет бандл повторно под тем же идентификатором, пока он не попадёт в блок, не истечёт blockhash **первой** транзакции или не будет исчерпан бюджет повторных отправок |

Чаевые подчиняются тому же [правилу чаевых](/ru/apex/tips), что и одиночная отправка: один перевод SystemProgram верхнего уровня на опубликованный аккаунт для чаевых, оплаченный подписантом, с аккаунтом для чаевых в статических ключах. Эти чаевые за вычетом базовой комиссии 5,000 lamports становятся ставкой Jito за весь бандл. Поскольку бандл атомарен, чаевые выплачиваются, только если в блок попал весь бандл.

## JSON-RPC sendBundle

| Параметр             | Тип       | Описание                                                                                        |
| -------------------- | --------- | ----------------------------------------------------------------------------------------------- |
| `params[0]`          | string\[] | От 1 до 4 подписанных транзакций в порядке исполнения, все в кодировке, указанной в `params[1]` |
| `params[1].encoding` | string    | `"base64"` или `"base58"`                                                                       |

Результатом является **идентификатор бандла**. Сохраните его, чтобы запрашивать статус бандла. `sendBundle` также принимает `{"encoding": "base58"}` для транзакций в кодировке base58. Примеры ниже используют base64.

<CodeGroup>
  ```bash cURL theme={null}
  # Two signed transactions from the helper in Sending transactions.
  # Exactly one of them may carry the tip, so build the first one without it.
  TX1=$(node -e 'import("./apex-tx.mjs").then(async (m) => console.log(Buffer.from((await m.buildTx({ tip: false, label: "apex bundle 1" })).serialize()).toString("base64")))')
  TX2=$(node -e 'import("./apex-tx.mjs").then(async (m) => console.log(Buffer.from((await m.buildTx({ tip: true, label: "apex bundle 2" })).serialize()).toString("base64")))')

  BUNDLE_ID=$(curl -s "$APEX_RPC" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $APEX_API_KEY" \
    -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"sendBundle\",\"params\":[[\"$TX1\",\"$TX2\"],{\"encoding\":\"base64\"}]}" \
    | jq -r '.result // .error')
  echo "bundle id: $BUNDLE_ID"

  curl -s "$APEX_RPC" \
    -H "Content-Type: application/json" \
    -H "x-api-key: $APEX_API_KEY" \
    -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getInflightBundleStatuses\",\"params\":[[\"$BUNDLE_ID\"]]}"
  ```

  ```javascript JavaScript theme={null}
  import { apexRpc, buildTx } from "./apex-tx.mjs";

  // Exactly one transaction carries the tip.
  const txs = [
    await buildTx({ tip: false, label: "apex bundle 1" }),
    await buildTx({ tip: true, label: "apex bundle 2" }),
  ];

  const bundleId = await apexRpc("sendBundle", [
    txs.map((tx) => Buffer.from(tx.serialize()).toString("base64")),
    { encoding: "base64" },
  ]);
  console.log("bundle id:", bundleId);

  // Poll until the bundle settles.
  for (;;) {
    const { value } = await apexRpc("getInflightBundleStatuses", [[bundleId]]);
    const { status, landed_slot } = value[0];
    if (status === "Landed") { console.log("landed in slot", landed_slot); break; }
    if (status === "Failed" || status === "Invalid") { console.log("bundle", status); break; }
    await new Promise((resolve) => setTimeout(resolve, 1000));
  }
  ```

  ```python Python theme={null}
  import base64
  import time

  from apex_tx import apex_rpc, build_tx

  # Exactly one transaction carries the tip.
  txs = [
      build_tx(tip=False, label="apex bundle 1"),
      build_tx(tip=True, label="apex bundle 2"),
  ]

  bundle_id = apex_rpc("sendBundle", [
      [base64.b64encode(bytes(tx)).decode() for tx in txs],
      {"encoding": "base64"},
  ])
  print("bundle id:", bundle_id)

  # Poll until the bundle settles.
  while True:
      entry = apex_rpc("getInflightBundleStatuses", [[bundle_id]])["value"][0]
      if entry["status"] == "Landed":
          print("landed in slot", entry["landed_slot"])
          break
      if entry["status"] in ("Failed", "Invalid"):
          print("bundle", entry["status"])
          break
      time.sleep(1)
  ```
</CodeGroup>

Примеры на JavaScript и Python импортируют хелпер из раздела [Отправка транзакций](/ru/apex/sending-transactions#соберите-подписанную-транзакцию-с-чаевыми).

Ответ:

```json theme={null}
{ "jsonrpc": "2.0", "id": 1, "result": "<bundle id>" }
```

## POST /send-bundle

Бинарный маршрут принимает тот же формат фреймов, что и [`/send-batch`](/ru/apex/sending-transactions#post-/send-batch): для каждой транзакции длина u16 в big-endian и следующие за ней сырые байты транзакции, от 1 до 4 фреймов в порядке исполнения.

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { APEX_RPC, API_KEY, buildTx } from "./apex-tx.mjs";

  const txs = [
    await buildTx({ tip: false, label: "apex bundle 1" }),
    await buildTx({ tip: true, label: "apex bundle 2" }),
  ];

  // Each frame: u16 big-endian length, then the transaction bytes.
  const frames = txs.map((tx) => {
    const wire = Buffer.from(tx.serialize());
    const length = Buffer.alloc(2);
    length.writeUInt16BE(wire.length);
    return Buffer.concat([length, wire]);
  });

  const res = await fetch(`${APEX_RPC}/send-bundle`, {
    method: "POST",
    headers: { "Content-Type": "application/octet-stream", "x-api-key": API_KEY },
    body: Buffer.concat(frames),
  });
  const body = await res.json();
  if (!body.bundle_id) throw new Error(`rejected ${res.status} ${body.error}: ${body.message}`);
  console.log("bundle id:", body.bundle_id);
  console.log("signatures:", body.signatures);
  ```

  ```python Python theme={null}
  import struct

  from apex_tx import APEX_RPC, build_tx, session

  txs = [
      bytes(build_tx(tip=False, label="apex bundle 1")),
      bytes(build_tx(tip=True, label="apex bundle 2")),
  ]
  # Each frame: u16 big-endian length, then the transaction bytes.
  payload = b"".join(struct.pack(">H", len(wire)) + wire for wire in txs)

  r = session.post(f"{APEX_RPC}/send-bundle", headers={"Content-Type": "application/octet-stream"},
                   data=payload, timeout=5)
  body = r.json()
  if "bundle_id" not in body:
      raise RuntimeError(f"rejected {r.status_code} {body['error']}: {body['message']}")
  print("bundle id:", body["bundle_id"])
  print("signatures:", body["signatures"])
  ```
</CodeGroup>

Ответ:

```json theme={null}
{
  "bundle_id": "<bundle id>",
  "signatures": ["<signature of transaction 1>", "<signature of transaction 2>"]
}
```

Ошибки используют тот же формат и те же коды статуса, что и остальные [простые HTTP-маршруты](/ru/apex/errors-and-rate-limits#ошибки-простых-http-маршрутов). Бандл, отклонённый целиком, возвращается с меткой `bundle`.

## Отслеживание бандла

Вызовите `getInflightBundleStatuses` с массивом идентификаторов бандлов:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getInflightBundleStatuses",
  "params": [["<bundle id>"]]
}
```

Результат содержит по одной записи на каждый идентификатор бандла в порядке запроса:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 367112345 },
    "value": [
      { "bundle_id": "<bundle id>", "status": "Landed", "landed_slot": 367112344 }
    ]
  }
}
```

`landed_slot` содержит слот, в который попал бандл, или `null`, если он ещё не попал в блок. `status` принимает одно из четырёх состояний:

| Статус    | Значение                                                                                                    |
| --------- | ----------------------------------------------------------------------------------------------------------- |
| `Pending` | Бандл находится в Apex, и его отправка продолжается                                                         |
| `Landed`  | Бандл попал в блок. Слот указан в `landed_slot`                                                             |
| `Failed`  | Бандл не попал в блок до истечения blockhash первой транзакции, либо был исчерпан бюджет повторных отправок |
| `Invalid` | Идентификатор бандла неизвестен этому эндпоинту Apex либо старше пяти минут                                 |

Идентификатор бандла не меняется, пока Apex отправляет его повторно, поэтому продолжайте опрашивать тот идентификатор, который получили при отправке.

Обращайтесь к тому же эндпоинту Apex, на который вы отправили бандл. Идентификаторы бандлов не разделяются между эндпоинтами.

Поскольку бандл атомарен, вы также можете подтвердить его как любую транзакцию: как только любая из его подписей появляется как подтверждённая в `getSignatureStatuses` на Solana RPC, весь бандл попал в блок.

## Бандл или пакет?

|                                   | Бандл                    | [Пакет](/ru/apex/sending-transactions#post-/send-batch) |
| --------------------------------- | ------------------------ | ------------------------------------------------------- |
| **Транзакции**                    | От 1 до 4                | До 16                                                   |
| **Атомарность**                   | Да                       | Нет, каждая независима                                  |
| **Порядок**                       | Да                       | Нет                                                     |
| **Чаевые**                        | Одни на весь бандл       | Одни на каждую транзакцию                               |
| **Пути**                          | Только block engine Jito | Все три                                                 |
| **Ограничение размера участника** | 1232 байта               | 1232 байта или 4096 для v1                              |
