# Crypto — SDK methods

> Subscribe to Solana BDN feed tracks with the raw MoQT client and deliver transactions over QUIC; the Preview typed Crypto surface.

The crypto modality does **not** ship a dedicated typed client yet. You consume
the BDN with two surfaces that exist today:

- **Feeds** — subscribe to MoQT feed tracks with the raw MoQT client,
  `@clutchcall/sdk/moqt` (`MoqtClient`).
- **Transactions** — deliver a signed tx to the BDN over the **HTTP/3 submit
  API** (`/bdn/submit` and `/tpu`), callable from any HTTP/3 client.

> **NOTE:**
> A typed `Crypto` class that wraps both surfaces is a **Preview** — see
> [Preview: the typed Crypto surface](#preview-the-typed-crypto-surface) at the
> bottom. Don't build against it as a shipping API.

## Subscribe to feeds with the MoQT client

A feed is an ordinary MoQT **frame track**, so you subscribe with the same
`MoqtClient` the rest of the platform uses. Import it from the `moqt` subpath.

  <Tab title="TypeScript">
```ts
import { MoqtClient, ConnectionState } from "@clutchcall/sdk/moqt";

const client = await MoqtClient.connect(
  "quic://feeds.clutchcall.dev",
  process.env.CLUTCHCALL_CREDENTIALS,               // bearer token
  (state, reason) => console.log("state", state, reason ?? ""),
);
```
  </Tab>
  <Tab title="Python">
```python
from clutchcall.moqt import MoqtClient

client = MoqtClient.connect(
    "quic://feeds.clutchcall.dev",
    auth_token="…",                               # bearer token
    on_state=lambda s: print("state", s),
)
```
  </Tab>

`connect` returns immediately and dials in the background. The client
**auto-reconnects** with capped backoff and **replays every subscription** on
reconnect, so a feed consumer survives a relay flap without re-subscribing.

### `MoqtClient.connect`

<ParamField path="url" type="string" required>
  The feed relay, e.g. `quic://feeds.clutchcall.dev`. The QUIC port is discovered
  from the host's DNS `HTTPS` record.
</ParamField>
<ParamField path="token" type="string">
  Bearer token for the feed scope. Required for private (`sol/feed/<orgId>/…`)
  tracks; the public mainnet feed may be open.
</ParamField>
<ParamField path="onState" type="(state, reason?) => void">
  Lifecycle callback. `ConnectionState` is one of `Connecting`, `Connected`,
  `Reconnecting`, `Closed`, `Failed`.
</ParamField>
<ParamField path="options" type="MoqtConnectOptions">
  `{ token?, serverCertificateHash?, webTransport? }`. Pass
  `serverCertificateHash` when pinning a self-signed dev cert.
</ParamField>

**Returns** `Promise<MoqtClient>` — resolves once the first session is up.

### `client.subscribeFrame(ns, name, onFrame)`

Subscribe to a feed track. The namespace and name together address the track
(`sol/feed/mainnet` + `slots`).

<ParamField path="ns" type="string" required>
  Track namespace, e.g. `sol/feed/mainnet` (public) or `sol/feed/<orgId>`
  (private).
</ParamField>
<ParamField path="name" type="string" required>
  Track name within the namespace: `slots`, `blockhash`, or `priorityFee`.
</ParamField>
<ParamField path="onFrame" type="(timestampUs: bigint, priority: number, data: Uint8Array) => void" required>
  Called for every object the relay fans out. `data` is the raw frame payload —
  decode it as the feed's JSON or binary schema.
</ParamField>
<ParamField path="filterType" type="number">
  Optional MoQT filter — defaults to "largest object" (live tail). Pass a
  start/end window to replay from a point in the durable origin.
</ParamField>

**Returns** a `FrameSubscription`. Call `.close()` to unsubscribe.

  <Tab title="TypeScript">
```ts
const sub = client.subscribeFrame(
  "sol/feed/mainnet",
  "slots",
  (tsUs, _priority, data) => {
    const { slot } = JSON.parse(new TextDecoder().decode(data));
    console.log("slot", slot, "at", tsUs);
  },
);
// later: sub.close();
```
  </Tab>
  <Tab title="Python">
```python
def on_slot(ts_us: int, priority: int, data: bytes) -> None:
    import json
    print("slot", json.loads(data)["slot"], "at", ts_us)

sub = client.subscribe_frame("sol/feed/mainnet", "slots", on_slot)
# later: sub.close()
```
  </Tab>

### Other `MoqtClient` members you'll use here

| Member | Purpose |
| ------ | ------- |
| `subscribeFrame(ns, name, onFrame)` / `subscribe_frame(...)` | Subscribe to a feed track (above). |
| `subscribeObject(ns, name, onObject)` / `subscribe_object(...)` | Subscribe to a **raw** object track (verbatim payload, no frame header) — for a custom binary feed. |
| `close()` | Tear down the session and every subscription. |

> **NOTE:**
> `publishFrame` exists on `MoqtClient` too, but **you don't publish feed tracks**
> — the BDN ingest does. Your ingest endpoint (public RPC or your own
> gossip/Geyser) is configured server-side; you only ever subscribe.

## Deliver a transaction (HTTP/3 submit API)

There is no typed submit method yet — deliver the signed tx over HTTP/3 with the
raw bytes as the body. Both routes accept `application/octet-stream`.

<ParamField path="POST /bdn/submit" type="octet-stream body">
  **Fan-out.** Query `leader=auto` (resolve schedule) or `leader=<host>:<port>`.
  Ingests once, dedups on the 64-byte signature, fans across all edges; each edge
  lands on its nearby leaders. Returns the signature on success.
</ParamField>
<ParamField path="POST /tpu" type="octet-stream body">
  **Direct.** Query `host=auto` or `host=<validator-host>&port=<quic-port>`. Sends
  straight to the current + next leader TPU from this node, no fan-out.
</ParamField>

**cURL (HTTP/3):**
```bash
# BDN fan-out — lands on all edges, auto leader
curl --http3 \
  "https://crypto.clutchcall.dev/bdn/submit?leader=auto" \
  -H "Authorization: Bearer $CLUTCHCALL_CREDENTIALS" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @tx.bin
```
**TypeScript (fetch over HTTP/3):**
```ts
// Any HTTP/3-capable fetch. `signedTx` is the raw transaction bytes.
const res = await fetch("https://crypto.clutchcall.dev/bdn/submit?leader=auto", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CLUTCHCALL_CREDENTIALS}`,
    "Content-Type": "application/octet-stream",
  },
  body: signedTx,           // Uint8Array, raw signed Solana tx
});
const { ok, sig } = await res.json();
```
**Python (httpx, HTTP/3):**
```python
import os, httpx
res = httpx.post(
    "https://crypto.clutchcall.dev/bdn/submit",
    params={"leader": "auto"},
    headers={
        "Authorization": f"Bearer {os.environ['CLUTCHCALL_CREDENTIALS']}",
        "Content-Type": "application/octet-stream",
    },
    content=signed_tx,        # bytes, raw signed Solana tx
)
print(res.json())             # {"ok": true, "sig": "…"}
```

### Submit query parameters

<ParamField path="leader" type="string (/bdn/submit)" default="auto">
  `auto` resolves the leader schedule; or `<host>:<port>` to pin a validator TPU.
</ParamField>
<ParamField path="host" type="string (/tpu)" default="auto">
  `auto` resolves the leader schedule; or an explicit validator host. Pair with
  `port=<quic-port>` when explicit.
</ParamField>

The leg out to the validator uses Solana's native TPU QUIC protocol
(`ALPN solana-tpu`) with an **ephemeral ed25519** client identity per connection.

## Events & lifecycle

The feed surface is event-driven through the MoQT callbacks:

| Source | Event | When |
| ------ | ----- | ---- |
| `onState` | `Connecting` / `Connected` / `Reconnecting` / `Closed` / `Failed` | Session lifecycle; tracks re-subscribe automatically on `Connected`. |
| `onFrame` | per feed object | Every slot / blockhash / fee update the relay fans out. |
| `FrameSubscription.close()` | — | Unsubscribe a single feed track. |
| `MoqtClient.close()` | — | Tear down everything. |

## Preview: the typed Crypto surface

> **WARNING:**
> **Preview — not shipping.** The shapes below are forward-looking. Build against
> the MoQT client + HTTP/3 submit API above for anything real.

A typed `Crypto` client would wrap feed subscription and tx delivery behind one
handle, so you don't hand-roll `subscribeFrame` calls or `fetch` bodies:

```ts
// PREVIEW — illustrative shape only, not a shipping API.
import { Crypto } from "@clutchcall/sdk/crypto";

const crypto = new Crypto({
  key: process.env.CLUTCHCALL_CREDENTIALS,
  cluster: "mainnet",
});

// Feeds: an async iterator over a decoded feed track.
for await (const m of crypto.feeds.subscribe("slots")) {
  console.log("slot", m.slot);
}

// Submit: a typed result instead of a raw octet-stream POST.
const res = await crypto.submit(signedTx, { route: "bdn", leader: "auto" });
console.log("landed", res.sig, "in", res.ms, "ms");
```

Until that ships, the methods on this page are the supported surface.
