# Kaldera Quickstart

> A first Kaldera stream in TypeScript or Rust, with the client requirements and common errors.

Source: https://docs.constant-k.com/kaldera/quickstart/

This page opens a Kaldera stream that prints every Pump.fun transaction as it happens.

## Requirements

* A Blade Runner or Nexus plan, and your API key from [My Account](https://www.constant-k.com/my-account/).
* Node.js 20.18 or later, or a recent Rust toolchain.

```bash
export CK_API_KEY="YOUR_API_KEY"
```

> **Existing consumers: four upstream changes:** Four recent changes affect stream consumers, whoever they stream from: version 1 transactions, silent write-locked accounts, a new stake `rewardType`, and Token-2022 confidential transfer parsing. See [Changes in the data you receive](https://docs.constant-k.com/kaldera/filters/#changes-in-the-data-you-receive).

## A transaction stream

**TypeScript**

### Project setup

```bash
mkdir kaldera-quickstart && cd kaldera-quickstart
npm init -y
npm pkg set type=module
npm install @triton-one/yellowstone-grpc@9.0.0 bs58
```

### The subscriber

```ts title="stream.ts"
import Client, {
  CommitmentLevel,
  type SubscribeRequest,
  type SubscribeUpdate,
} from "@triton-one/yellowstone-grpc";
import bs58 from "bs58";

const client = new Client(
  "https://kaldera-newyork.constant-k.com", // or kaldera-frankfurt
  process.env.CK_API_KEY, // sent as the x-token header
  undefined,
);
await client.connect();

const request: SubscribeRequest = {
  commitment: CommitmentLevel.PROCESSED,
  transactions: {
    pumpfun: {
      vote: false,
      failed: false,
      accountInclude: ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
      accountExclude: [],
      accountRequired: [],
    },
  },
  // Every map must be present, even when empty
  accounts: {},
  slots: {},
  transactionsStatus: {},
  blocks: {},
  blocksMeta: {},
  entry: {},
  blockFooter: {},
  accountsDataSlice: [],
};

const stream = await client.subscribe(request);

stream.on("data", (update: SubscribeUpdate) => {
  const tx = update.transaction;
  if (tx?.transaction) {
    console.log(`slot ${tx.slot}  ${bs58.encode(tx.transaction.signature)}`);
  }
});

stream.on("error", (err) => console.error("stream error:", err));
stream.on("close", () => console.log("stream closed"));
```

### Running it

```bash
npx tsx stream.ts
```

The output holds one line per transaction:

```text
slot 447913193  4t8vS3…Qx9
slot 447913193  2hLmZa…u7K
```

**Rust**

### Dependencies

The project is `cargo new kaldera-quickstart`, with these dependencies:

```toml title="Cargo.toml"
[dependencies]
anyhow = "1"
bs58 = "0.5"
futures = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
# tls-ring supplies the rustls crypto provider; the client crate selects none.
# Without it, the client panics at connect.
tonic = { version = "0.14", features = ["tls-ring", "tls-native-roots"] }
# Declare both, and keep them together. These are the versions Kaldera runs;
# transaction v1 needs at least client 13.3.0 with proto 12.6.0.
yellowstone-grpc-client = "15.0.0"
yellowstone-grpc-proto = "14.0.1"
```

### The subscriber

```rust title="src/main.rs"
use std::collections::HashMap;

use futures::StreamExt;
use yellowstone_grpc_client::{ClientTlsConfig, GeyserGrpcClient};
use yellowstone_grpc_proto::prelude::{
    subscribe_update::UpdateOneof, CommitmentLevel, SubscribeRequest,
    SubscribeRequestFilterTransactions,
};

// Pump.fun program
const PUMP_FUN: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut client = GeyserGrpcClient::build_from_shared("https://kaldera-newyork.constant-k.com")?
        .x_token(Some(std::env::var("CK_API_KEY")?))?
        .tls_config(ClientTlsConfig::new().with_native_roots())?
        .connect()
        .await?;

    let request = SubscribeRequest {
        transactions: HashMap::from([(
            "pumpfun".to_string(),
            SubscribeRequestFilterTransactions {
                vote: Some(false),
                failed: Some(false),
                account_include: vec![PUMP_FUN.to_string()],
                ..Default::default()
            },
        )]),
        commitment: Some(CommitmentLevel::Processed as i32),
        ..Default::default()
    };

    let (_sink, mut stream) = client.subscribe_with_request(Some(request)).await?;

    while let Some(message) = stream.next().await {
        match message?.update_oneof {
            Some(UpdateOneof::Transaction(update)) => {
                if let Some(tx) = update.transaction {
                    let sig = bs58::encode(&tx.signature).into_string();
                    println!("slot {} signature {}", update.slot, sig);
                }
            }
            Some(UpdateOneof::Ping(_)) => {} // server keepalive
            _ => {}
        }
    }
    Ok(())
}
```

### Running it

```bash
cargo run --release
```

## Command-line test

[`grpcurl`](https://github.com/fullstorydev/grpcurl) needs the Yellowstone protocol files:

```bash
git clone --depth 1 --branch "v16.1.1+solana.4.3.0" https://github.com/rpcpool/yellowstone-grpc.git

grpcurl \
  -import-path yellowstone-grpc/yellowstone-grpc-proto/proto \
  -proto geyser.proto \
  -H "x-token: YOUR_API_KEY" \
  -d '{"slots": {"tip": {}}}' \
  kaldera-newyork.constant-k.com:443 geyser.Geyser/Subscribe
```

The command prints a stream of slot updates in JSON.

## Client requirements

* **`connect()` comes first.** In `@triton-one/yellowstone-grpc` 7.x and later, `subscribe()` fails until `connect()` has resolved.
* **Every map of `SubscribeRequest` is required by its TypeScript type.** `accounts`, `slots`, `transactions`, `transactionsStatus`, `blocks`, `blocksMeta`, `entry`, `blockFooter` and `accountsDataSlice` must all be present, even when empty. From 8.0.0 the client fills missing maps at run time, but `tsc` still rejects the request.
* **`ping` updates carry no data.** The server sends them to keep the stream open.
* **`update.filters`** names which of your filters matched. See [Filters](https://docs.constant-k.com/kaldera/filters/).
* **Handler speed.** Work that takes more than a few milliseconds belongs on a queue. On Blade Runner, a slow reader loses the oldest queued updates.
* **Reconnects.** Backoff, then resume with `fromSlot`. See [Replay](https://docs.constant-k.com/kaldera/replay/).
* **Unused streams.** `stream.destroy()` closes them. Open streams count toward your plan's stream limit.

## Troubleshooting

| Error                                                                                                                              | Cause and fix                                                                                                                                                                                            |
| :--------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Client not connected. Call connect() first`                                                                                       | `subscribe()` ran before `await client.connect()`.                                                                                                                                                       |
| TypeScript error `Property 'blockFooter' is missing in type`                                                                       | Add `blockFooter: {}` to `SubscribeRequest`. Client 8.0.0 added the map.                                                                                                                                 |
| `Cannot convert undefined or null to object` (7.x only)                                                                            | A map field is missing from `SubscribeRequest`. From 8.0.0 the client fills missing maps.                                                                                                                |
| `failed to open subscribe stream` (TypeScript), `unexpected HTTP status code … 204` (grpcurl), `Missing or Invalid API Key` (Rust) | The key is missing or wrong, or your plan does not include Kaldera.                                                                                                                                      |
| TypeScript error `This expression is not constructable` on `new Client`                                                            | Your `tsconfig.json` uses `"module": "NodeNext"`. Switch to `"module": "ESNext"` with `"moduleResolution": "bundler"`, or run with `tsx`, which does not type-check. The code runs correctly either way. |
| Rust panic: `Could not automatically determine the process-level CryptoProvider`                                                   | Add the `tonic` dependency with the `tls-ring` feature, as in the `Cargo.toml` above.                                                                                                                    |
| Updates arrive late or the stream falls behind                                                                                     | Narrow your filters, turn on [compression](https://docs.constant-k.com/kaldera/#compression), process updates off the main loop, and run close to the node.                                                                         |
