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

# Events

> Real-time SSE events from the sidecar — subscribe with sinker.on().

## Overview

The `Sinker` class maintains a single persistent SSE connection to `GET /internal/events`. All event types are multiplexed over this one connection and dispatched to the appropriate handlers via `sinker.on()`.

```ts theme={null}
sinker.on('tx_status_changed', (e) => { /* ... */ });
sinker.on('bundle_settled',    (e) => { /* ... */ });
sinker.on('slot_update',       (e) => { /* ... */ });
sinker.on('tx_enqueued',       (e) => { /* ... */ });
sinker.on('submit_requested',  (e) => { /* ... */ });
```

The stream reconnects automatically with exponential backoff if the sidecar restarts or the connection drops.

***

## Event types

### `tx_status_changed`

Fired whenever a transaction moves to a new commitment state. This is the primary event for tracking transaction progress.

```ts theme={null}
sinker.on('tx_status_changed', (e) => {
  console.log(e.tx_id);          // "346e5b0e-..."
  console.log(e.state);          // "confirmed" | "finalized" | "failed" | ...
  console.log(e.bundle_id);      // "b9e8b294..." | null
  console.log(e.tx_signatures);  // ["3b9QQcUQ..."]
  console.log(e.failure);        // "fee_too_low" | null
});
```

<ResponseField name="tx_id" type="string">
  The transaction ID from `/tx/enqueue`.
</ResponseField>

<ResponseField name="state" type="TxState">
  New state: `pending` | `submitted` | `landed` | `processed` | `confirmed` | `finalized` | `failed` | `expired`
</ResponseField>

<ResponseField name="bundle_id" type="string | null">
  The bundle that produced this state transition.
</ResponseField>

<ResponseField name="tx_signatures" type="string[]">
  On-chain signatures, populated once the transaction lands.
</ResponseField>

<ResponseField name="failure" type="string | null">
  Failure class name when `state` is `"failed"`.
</ResponseField>

***

### `bundle_settled`

Fired when a bundle reaches a terminal state (finalized or failed). The AI agent listens to this event to decide whether to retry.

```ts theme={null}
sinker.on('bundle_settled', (e) => {
  console.log(e.bundle_id);     // "b9e8b294..."
  console.log(e.stage);         // "finalized" | "failed"
  console.log(e.failure_type);  // "fee_too_low" | null
});
```

<ResponseField name="bundle_id" type="string">
  The settled bundle's ID.
</ResponseField>

<ResponseField name="stage" type="string">
  Terminal stage: `"finalized"` or `"failed"`.
</ResponseField>

<ResponseField name="failure_type" type="string | null">
  Failure class name when `stage` is `"failed"`.
</ResponseField>

***

### `slot_update`

Fired on every slot event received from the Yellowstone gRPC stream.

```ts theme={null}
sinker.on('slot_update', (e) => {
  console.log(e.slot);    // 423602205
  console.log(e.leader);  // "GiYSnFRrXrm..." | null
});
```

<ResponseField name="slot" type="number">
  The new slot number.
</ResponseField>

<ResponseField name="leader" type="string | null">
  The validator identity pubkey scheduled for this slot, or `null` if not in the leader buffer.
</ResponseField>

***

### `tx_enqueued`

Fired immediately when `POST /tx/enqueue` succeeds. The AI agent uses this as the primary trigger to start a decision cycle.

```ts theme={null}
sinker.on('tx_enqueued', (e) => {
  console.log(e.tx_id);  // "346e5b0e-..."
  console.log(e.slot);   // 423602204
});
```

***

### `submit_requested`

Fired when `POST /internal/submit` is called — either by the AI agent or by a manual call to `sinker.submit()`. Useful for observability dashboards.

```ts theme={null}
sinker.on('submit_requested', (e) => {
  console.log(e.tip_lamports);  // 75000
  console.log(e.reason);        // "p75 — 1 prior fee_too_low"
  console.log(e.leader);        // "GiYSnFRrXrm..."
});
```

***

## Per-transaction streams

For a filtered stream scoped to a single transaction, `handle.waitFor()` uses the
`GET /tx/events/:tx_id` endpoint internally. This stream:

* Sends only `tx_status_changed` events for the requested `tx_id`
* Closes automatically after the first terminal state (`finalized`, `failed`, `expired`)
* Sends the current state immediately if the tx is already terminal when the client connects

You rarely need to use this endpoint directly — `handle.waitFor()` wraps it cleanly.

***

## SSE reconnection

The SDK reconnects automatically using exponential backoff:

| Attempt | Wait before retry          |
| ------- | -------------------------- |
| 1       | 500ms                      |
| 2       | 1,000ms                    |
| 3       | 2,000ms                    |
| 4       | 4,000ms                    |
| …       | doubles each time, cap 30s |

Events emitted during a reconnect gap may be missed. The 30-second agent watchdog exists specifically to handle this case — it re-checks queue state and re-submits if needed.
