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

# Recipes

> Complete, runnable programs for the things most people build first on OrbitFlare: checking a key, watching an account, waiting for a transaction, streaming a program, and reading wallet history. TypeScript, Rust, Go and curl.

Every program on this page is complete. Copy it into a file, set your key, and run it. Each one was run against real OrbitFlare endpoints before it was published.

## Before you start

Set your license key once. All three SDKs read it from the environment, so it never has to appear in your code.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
export ORBITFLARE_LICENSE_KEY=ORBIT-XXXXXX-NNNNNN-NNNNNN
```

The examples use Frankfurt (`fra`). Change the host to the [region](/infrastructure) closest to your servers. If your license is in [API Key Only mode](/authentication#authentication-modes), the SDKs also send the key to Jetstream and Yellowstone. In IP Whitelist mode, add your server's IP to the license in the [dashboard](https://orbitflare.com/dashboard) before running the streaming recipe.

**TypeScript**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
npm init -y && npm pkg set type=module
npm install @orbitflare/sdk ws @grpc/grpc-js yaml bs58
```

`ws` and `@grpc/grpc-js` are optional peer dependencies of the SDK. Without them the WebSocket and Jetstream clients fail with `Cannot find package 'ws'` or `Cannot find package '@grpc/grpc-js'`. Run a file with `npx tsx file.ts`.

**Rust**

```toml Cargo.toml theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
[package]
name = "recipes"
version = "0.1.0"
edition = "2024"

[dependencies]
orbitflare-sdk = { version = "0.4", features = ["ws", "jetstream"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1"
bs58 = "0.5"
```

Save each program under `examples/` and run it with `cargo run --example name`.

**Go**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
go mod init recipes
go get github.com/orbitflare/orbitflare-sdk-go@latest github.com/mr-tron/base58
```

Put each program in its own folder with a `main.go` and run it with `go run ./folder`.

## Check that your key works

The fastest way to rule out setup problems. If this prints a slot number, your key, region and network path are fine.

```bash curl theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSlot"}'
```

```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { RpcClientBuilder } from '@orbitflare/sdk';

// The key comes from ORBITFLARE_LICENSE_KEY, so it never has to appear in code.
const rpc = new RpcClientBuilder().url('https://fra.rpc.orbitflare.com').build();

// A slot number back means the key, region and network path all work.
console.log('slot:', await rpc.getSlot());
```

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
use orbitflare_sdk::RpcClientBuilder;

#[tokio::main]
async fn main() -> orbitflare_sdk::Result<()> {
    // The key comes from ORBITFLARE_LICENSE_KEY, so it never has to appear in code.
    let rpc = RpcClientBuilder::new()
        .url("https://fra.rpc.orbitflare.com")
        .build()?;

    // A slot number back means the key, region and network path all work.
    println!("slot: {}", rpc.get_slot().await?);
    Ok(())
}
```

```go Go theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/orbitflare/orbitflare-sdk-go/rpc"
)

func main() {
	// The key comes from ORBITFLARE_LICENSE_KEY, so it never has to appear in code.
	client, err := rpc.NewBuilder().URL("https://fra.rpc.orbitflare.com").Build()
	if err != nil {
		log.Fatal(err)
	}

	// A slot number back means the key, region and network path all work.
	slot, err := client.GetSlot(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("slot:", slot)
}
```

If it fails, the error says why:

| Response | Meaning |
| - | - |
| `-32401 missing API key` (HTTP 401) | The key was not sent. Check the `x-api-key` header or `?api_key=` parameter, or that `ORBITFLARE_LICENSE_KEY` is exported in the same shell |
| `-32402 invalid API key` (HTTP 403) | The key is wrong or was regenerated. Copy it again from the dashboard |
| `-32404 IP not whitelisted` (HTTP 403) | This machine's IP, or this region, is not allowed on your license. Add the IP in the dashboard or try a region your license includes |

The full list is in the [error reference](/rpc/error-codes).

## Watch an account for changes

Subscribes over WebSocket and prints every update. The example watches the Clock sysvar because it changes every slot, so you see output immediately. Swap in any account address.

```bash wscat theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
wscat -c "wss://fra.rpc.orbitflare.com?api_key=$ORBITFLARE_LICENSE_KEY"
> {"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["SysvarC1ock11111111111111111111111111111111",{"encoding":"base64","commitment":"confirmed"}]}
```

```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { WsClientBuilder } from '@orbitflare/sdk/ws';

// The SDK pings every 10 s and resubscribes on its own after a reconnect.
const ws = await new WsClientBuilder().url('wss://fra.rpc.orbitflare.com').build();

// Notifies only when the account changes. The Clock sysvar changes every slot.
const sub = await ws.accountSubscribe('SysvarC1ock11111111111111111111111111111111', 'confirmed');

for (let account = await sub.next(); account !== undefined; account = await sub.next()) {
  // Clock layout: the first 8 bytes are the current slot as a little-endian u64.
  const data = Buffer.from(account.data[0], 'base64');
  console.log(`clock slot ${data.readBigUInt64LE(0)}, ${account.lamports} lamports`);
}
```

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
use orbitflare_sdk::WsClientBuilder;

#[tokio::main]
async fn main() -> orbitflare_sdk::Result<()> {
    // The SDK pings every 10 s and resubscribes on its own after a reconnect.
    let ws = WsClientBuilder::new()
        .url("wss://fra.rpc.orbitflare.com")
        .build()
        .await?;

    // Notifies only when the account changes. The Clock sysvar changes every slot.
    let mut sub = ws
        .account_subscribe("SysvarC1ock11111111111111111111111111111111", "confirmed")
        .await?;

    // Each update is the account's full new state: lamports, owner and base64 data.
    while let Some(account) = sub.next().await {
        println!(
            "{} lamports, data {}",
            account["lamports"], account["data"][0]
        );
    }
    Ok(())
}
```

```go Go theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/orbitflare/orbitflare-sdk-go/ws"
)

func main() {
	ctx := context.Background()

	// The SDK pings every 10 s and resubscribes on its own after a reconnect.
	client, err := ws.NewBuilder().URL("wss://fra.rpc.orbitflare.com").Build(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// Notifies only when the account changes. The Clock sysvar changes every slot.
	sub, err := client.AccountSubscribe(ctx, "SysvarC1ock11111111111111111111111111111111", "confirmed")
	if err != nil {
		log.Fatal(err)
	}
	defer sub.Unsubscribe()

	// Each update is the account's full new state: lamports, owner and base64 data.
	for {
		account, ok := sub.Next(ctx)
		if !ok {
			return
		}
		fmt.Println(string(account))
	}
}
```

Updates arrive only when the account changes. A quiet account means a quiet subscription, not a broken one. See [WebSocket debugging](/troubleshooting/websocket) if nothing arrives.

## Wait for a transaction to confirm

Pass a signature on the command line. The program polls `getSignatureStatuses` until the transaction is confirmed, fails, or is clearly not coming.

```bash curl theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["SIGNATURE"],{"searchTransactionHistory":true}]}'
```

```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { RpcClientBuilder } from '@orbitflare/sdk';

const signature = process.argv[2];
const rpc = new RpcClientBuilder().url('https://fra.rpc.orbitflare.com').build();

// 120 polls 500 ms apart is 60 s, well past the ~30 s a blockhash stays valid.
for (let attempt = 0; attempt < 120; attempt++) {
  // searchTransactionHistory also finds signatures older than the node's recent status cache.
  const { value } = await rpc.request('getSignatureStatuses', [
    [signature],
    { searchTransactionHistory: true },
  ]);
  const status = value[0];
  // err set means it landed and failed. The fee was still charged.
  if (status?.err) {
    console.log('landed but failed:', JSON.stringify(status.err));
    process.exit(1);
  }
  if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') {
    console.log(`${status.confirmationStatus} in slot ${status.slot}`);
    process.exit(0);
  }
  // null means the cluster has not seen it yet, so keep polling.
  await new Promise((r) => setTimeout(r, 500));
}
console.log('not found after 60s: the transaction was dropped or its blockhash expired');
```

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
use std::time::Duration;

use orbitflare_sdk::RpcClientBuilder;
use serde_json::json;

#[tokio::main]
async fn main() -> orbitflare_sdk::Result<()> {
    let signature = std::env::args()
        .nth(1)
        .expect("usage: wait_for_signature <SIGNATURE>");
    let rpc = RpcClientBuilder::new()
        .url("https://fra.rpc.orbitflare.com")
        .build()?;

    // 120 polls 500 ms apart is 60 s, well past the ~30 s a blockhash stays valid.
    for _ in 0..120 {
        // searchTransactionHistory also finds signatures older than the node's recent status cache.
        let res = rpc
            .request(
                "getSignatureStatuses",
                json!([[signature], {"searchTransactionHistory": true}]),
            )
            .await?;
        let status = &res["value"][0];
        // err set means it landed and failed. The fee was still charged.
        if !status["err"].is_null() {
            println!("landed but failed: {}", status["err"]);
            std::process::exit(1);
        }
        if let Some(level @ ("confirmed" | "finalized")) = status["confirmationStatus"].as_str() {
            println!("{level} in slot {}", status["slot"]);
            return Ok(());
        }
        // null means the cluster has not seen it yet, so keep polling.
        tokio::time::sleep(Duration::from_millis(500)).await;
    }
    println!("not found after 60s: the transaction was dropped or its blockhash expired");
    Ok(())
}
```

```go Go theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/orbitflare/orbitflare-sdk-go/rpc"
)

type statuses struct {
	Value []*struct {
		Slot               uint64          `json:"slot"`
		Err                json.RawMessage `json:"err"`
		ConfirmationStatus string          `json:"confirmationStatus"`
	} `json:"value"`
}

func main() {
	signature := os.Args[1]
	client, err := rpc.NewBuilder().URL("https://fra.rpc.orbitflare.com").Build()
	if err != nil {
		log.Fatal(err)
	}

	// 120 polls 500 ms apart is 60 s, well past the ~30 s a blockhash stays valid.
	for range 120 {
		// searchTransactionHistory also finds signatures older than the node's recent status cache.
		raw, err := client.Request(context.Background(), "getSignatureStatuses",
			[]any{[]string{signature}, map[string]bool{"searchTransactionHistory": true}})
		if err != nil {
			log.Fatal(err)
		}
		var res statuses
		if err := json.Unmarshal(raw, &res); err != nil {
			log.Fatal(err)
		}
		if s := res.Value[0]; s != nil {
			// err set means it landed and failed. The fee was still charged.
			if string(s.Err) != "null" && len(s.Err) > 0 {
				fmt.Println("landed but failed:", string(s.Err))
				os.Exit(1)
			}
			if s.ConfirmationStatus == "confirmed" || s.ConfirmationStatus == "finalized" {
				fmt.Printf("%s in slot %d\n", s.ConfirmationStatus, s.Slot)
				return
			}
		}
		// null means the cluster has not seen it yet, so keep polling.
		time.Sleep(500 * time.Millisecond)
	}
	fmt.Println("not found after 60s: the transaction was dropped or its blockhash expired")
}
```

Polling with `searchTransactionHistory` works for any signature, old or new. `signatureSubscribe` only knows about recent transactions, so subscribing to one that confirmed a few minutes ago never notifies. A `null` status means the cluster has not seen the transaction. [Transaction debugging](/troubleshooting/transactions) covers what to do about that.

## Stream a program's transactions

Uses Jetstream v2 to print every transaction that touches pump.fun, with the fee payer and compute unit price from the enrichment fields.

```bash grpcurl theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
grpcurl -plaintext -import-path . -proto jetstream_v2.proto \
  -d '{"add_filters":{"filters":[{"filter_id":"pump","filter":{"account_include":["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],"include_enrichment":true}}]}}' \
  fra.jetstream.orbitflare.com:80 jetstream.v2.JetstreamV2/SubscribeTransactions
```

```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import bs58 from 'bs58';
import { JetstreamClientBuilder, TransactionFilter } from '@orbitflare/sdk/jetstream/v2';

const PUMP_FUN = '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P';

// In API Key Only mode the SDK sends ORBITFLARE_LICENSE_KEY as x-token.
const jetstream = new JetstreamClientBuilder().url('http://fra.jetstream.orbitflare.com').build();

// Matches every transaction that mentions pump.fun. Enrichment adds the fee payer and CU price.
const filter = new TransactionFilter().accountInclude([PUMP_FUN]).includeEnrichment(true).withId('pump');

for await (const update of jetstream.subscribeTransactions([filter])) {
  // Other updates are filter validations, heartbeats and pongs.
  const tx = update.transaction?.transaction;
  if (!tx) continue;
  // compute_unit_price is micro-lamports per CU, not the total priority fee.
  console.log(
    `${tx.slot} ${bs58.encode(tx.signature)} payer=${bs58.encode(tx.feePayer)} cu_price=${tx.computeUnitPrice}`,
  );
}
```

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
use orbitflare_sdk::jetstream::v2::{JetstreamClientBuilder, TransactionFilter};
use orbitflare_sdk::proto::jetstream::v2::subscribe_transactions_response::Payload;

const PUMP_FUN: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";

#[tokio::main]
async fn main() -> orbitflare_sdk::Result<()> {
    // In API Key Only mode the SDK sends ORBITFLARE_LICENSE_KEY as x-token.
    let jetstream = JetstreamClientBuilder::new()
        .url("http://fra.jetstream.orbitflare.com")
        .build()?;

    // Matches every transaction that mentions pump.fun. Enrichment adds the fee payer and CU price.
    let filter = TransactionFilter::new()
        .account_include([PUMP_FUN])
        .include_enrichment(true)
        .with_id("pump");

    let mut stream = jetstream.subscribe_transactions(vec![filter]);

    while let Some(update) = stream.next().await {
        // Other updates are filter validations, heartbeats and pongs.
        if let Some(Payload::Transaction(ft)) = update?.payload
            && let Some(tx) = ft.transaction
        {
            // compute_unit_price is micro-lamports per CU, not the total priority fee.
            println!(
                "{} {} payer={} cu_price={}",
                tx.slot,
                bs58::encode(&tx.signature).into_string(),
                bs58::encode(&tx.fee_payer).into_string(),
                tx.compute_unit_price
            );
        }
    }
    Ok(())
}
```

```go Go theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/mr-tron/base58"
	jetstreamv2 "github.com/orbitflare/orbitflare-sdk-go/jetstream/v2"
)

const pumpFun = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"

func main() {
	// In API Key Only mode the SDK sends ORBITFLARE_LICENSE_KEY as x-token.
	client, err := jetstreamv2.NewBuilder().URL("http://fra.jetstream.orbitflare.com").Build()
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// Matches every transaction that mentions pump.fun. Enrichment adds the fee payer and CU price.
	filter := jetstreamv2.NewTransactionFilter().
		AccountInclude(pumpFun).
		IncludeEnrichment(true).
		WithID("pump")

	stream := client.SubscribeTransactions(filter)
	defer stream.Close()

	for {
		update, err := stream.Next(context.Background())
		if err != nil {
			log.Fatal(err)
		}
		// Other updates are filter validations, heartbeats and pongs.
		tx := update.GetTransaction().GetTransaction()
		if tx == nil {
			continue
		}
		// compute_unit_price is micro-lamports per CU, not the total priority fee.
		fmt.Printf("%d %s payer=%s cu_price=%d\n",
			tx.GetSlot(),
			base58.Encode(tx.GetSignature()),
			base58.Encode(tx.GetFeePayer()),
			tx.GetComputeUnitPrice())
	}
}
```

The `grpcurl` version needs `jetstream_v2.proto` from the [SDK repository](https://github.com/orbitflare/orbitflare-sdk-ts/tree/main/protos) in the current folder. Jetstream filters transactions only. To watch account state, use [Yellowstone](/data-streaming/yellowstone-account-monitoring).

## Read a wallet's recent transactions

Returns the 10 most recent transactions for an address using `getTransactionsForAddress`.

```bash curl theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
curl -s https://fra.rpc.orbitflare.com \
  -H "x-api-key: $ORBITFLARE_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getTransactionsForAddress","params":["WALLET",{"limit":10}]}'
```

```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { RpcClientBuilder } from '@orbitflare/sdk';

type Entry = { signature: string; slot: number; blockTime: number; err: unknown };

const wallet = process.argv[2];
const rpc = new RpcClientBuilder().url('https://fra.rpc.orbitflare.com').build();

// Returns signatures by default. Ask for "full" with maxSupportedTransactionVersion 1 to get bodies.
const { data } = await rpc.getTransactionsForAddress(wallet, { limit: 10 });

// Newest first. Pass paginationToken from the response to get the next page.
for (const tx of data as Entry[]) {
  const when = new Date(tx.blockTime * 1000).toISOString();
  console.log(when, tx.slot, tx.signature, tx.err ? 'failed' : 'ok');
}
```

```rust Rust theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
use orbitflare_sdk::{GetTransactionsOptions, RpcClientBuilder};

#[tokio::main]
async fn main() -> orbitflare_sdk::Result<()> {
    let wallet = std::env::args().nth(1).expect("usage: history <WALLET>");
    let rpc = RpcClientBuilder::new()
        .url("https://fra.rpc.orbitflare.com")
        .build()?;

    let options = GetTransactionsOptions {
        limit: Some(10),
        ..Default::default()
    };
    // Returns signatures by default. Ask for "full" with maxSupportedTransactionVersion 1 to get bodies.
    let page = rpc.get_transactions_for_address(&wallet, options).await?;

    // Newest first. Pass paginationToken from the response to get the next page.
    for tx in page.data {
        let status = if tx["err"].is_null() { "ok" } else { "failed" };
        println!(
            "{} {} {}",
            tx["slot"],
            tx["signature"].as_str().unwrap_or(""),
            status
        );
    }
    Ok(())
}
```

```go Go theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"

	"github.com/orbitflare/orbitflare-sdk-go/rpc"
)

type entry struct {
	Signature string          `json:"signature"`
	Slot      uint64          `json:"slot"`
	Err       json.RawMessage `json:"err"`
}

func main() {
	wallet := os.Args[1]
	client, err := rpc.NewBuilder().URL("https://fra.rpc.orbitflare.com").Build()
	if err != nil {
		log.Fatal(err)
	}

	limit := uint32(10)
	// Returns signatures by default. Ask for "full" with maxSupportedTransactionVersion 1 to get bodies.
	page, err := client.GetTransactionsForAddress(context.Background(), wallet, &rpc.GetTransactionsOptions{Limit: &limit})
	if err != nil {
		log.Fatal(err)
	}

	// Newest first. Pass paginationToken from the response to get the next page.
	for _, raw := range page.Data {
		var tx entry
		if err := json.Unmarshal(raw, &tx); err != nil {
			log.Fatal(err)
		}
		status := "ok"
		if string(tx.Err) != "null" {
			status = "failed"
		}
		fmt.Println(tx.Slot, tx.Signature, status)
	}
}
```

Each entry has `signature`, `slot`, `blockTime`, `err` and `confirmationStatus`. Pass the returned `paginationToken` to get the next page. For complete transactions, set `transactionDetails` to `"full"` together with `"maxSupportedTransactionVersion": 1`. Without it, version 1 transactions are silently left out. See [getTransactionsForAddress](/fetching-data/getTransactionsForAddress) for filters and sorting.

## More examples

> **[Error reference](/rpc/error-codes)**
>
> Every error OrbitFlare returns and what to do about it.

> **[Transaction debugging](/troubleshooting/transactions)**
>
> Why a transaction failed, was dropped, or never landed.

> **[Sending through Apex](/apex/quickstart)**
>
> Land transactions faster with OrbitFlare Apex.

> **[SDK reference](/sdk/overview)**
>
> Every client, builder option and method in the SDKs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.