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

# 示例程序

> 大多数人在 OrbitFlare 上最先构建的功能的完整可运行程序：检查密钥、监听账户、等待交易确认、订阅程序的交易流以及读取钱包历史。涵盖 TypeScript、Rust、Go 和 curl。

本页的每个程序都是完整的。把它复制到文件中，设置好密钥，然后运行即可。每个程序在发布前都已在真实的 OrbitFlare 端点上运行过。

## 开始之前

先设置一次许可证密钥。三个 SDK 都会从环境变量中读取它，因此密钥永远不必出现在代码里。

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

示例使用法兰克福（`fra`）。请把主机名改为离您的服务器最近的[区域](/cn/infrastructure)。如果您的许可证处于 [API Key Only 模式](/cn/authentication#认证模式)，SDK 也会把密钥发送给 Jetstream 和 Yellowstone。在 IP Whitelist 模式下，运行流式示例之前，请先在[控制台](https://orbitflare.com/dashboard)中把服务器的 IP 添加到许可证。

**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` 和 `@grpc/grpc-js` 是 SDK 的可选 peer 依赖。缺少它们时，WebSocket 和 Jetstream 客户端会报错 `Cannot find package 'ws'` 或 `Cannot find package '@grpc/grpc-js'`。使用 `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"
```

把每个程序保存在 `examples/` 下，并用 `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
```

把每个程序放在各自的文件夹中，包含一个 `main.go`，并用 `go run ./folder` 运行。

## 检查密钥是否可用

这是排除配置问题最快的方法。如果它打印出一个 slot 编号，说明您的密钥、区域和网络路径都没有问题。

```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)
}
```

如果失败，错误信息会说明原因：

| 响应 | 含义 |
| - | - |
| `-32401 missing API key`（HTTP 401） | 没有发送密钥。请检查 `x-api-key` 请求头或 `?api_key=` 参数，或确认 `ORBITFLARE_LICENSE_KEY` 已在同一个 shell 中导出 |
| `-32402 invalid API key`（HTTP 403） | 密钥错误或已被重新生成。请从控制台重新复制 |
| `-32404 IP not whitelisted`（HTTP 403） | 您的许可证不允许这台机器的 IP 或这个区域。请在控制台中添加该 IP，或尝试许可证包含的区域 |

完整列表见[错误参考](/cn/rpc/error-codes)。

## 监听账户变化

通过 WebSocket 订阅并打印每一次更新。示例监听 Clock sysvar，因为它每个 slot 都会变化，您可以立即看到输出。可以换成任意账户地址。

```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))
	}
}
```

只有账户发生变化时才会收到更新。账户没有动静，订阅也就没有动静，并不代表订阅出了问题。如果一直收不到任何消息，请参阅 [WebSocket 调试](/cn/troubleshooting/websocket)。

## 等待交易确认

在命令行中传入签名。程序会轮询 `getSignatureStatuses`，直到交易已确认、失败，或者明显不会再上链为止。

```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")
}
```

使用 `searchTransactionHistory` 轮询适用于任何签名，无论新旧。`signatureSubscribe` 只知道最近的交易，因此订阅一笔几分钟前就已确认的交易永远不会收到通知。状态为 `null` 表示集群还没有见到这笔交易。[交易调试](/cn/troubleshooting/transactions)介绍了遇到这种情况该怎么办。

## 订阅程序的交易流

使用 Jetstream v2 打印每一笔涉及 pump.fun 的交易，并通过增强字段输出手续费支付者和计算单元价格。

```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())
	}
}
```

`grpcurl` 版本需要当前文件夹中有来自 [SDK 仓库](https://github.com/orbitflare/orbitflare-sdk-ts/tree/main/protos)的 `jetstream_v2.proto`。Jetstream 只过滤交易。如需监听账户状态，请使用 [Yellowstone](/cn/data-streaming/yellowstone-account-monitoring)。

## 读取钱包的最近交易

使用 `getTransactionsForAddress` 返回某个地址最近的 10 笔交易。

```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)
	}
}
```

每个条目都包含 `signature`、`slot`、`blockTime`、`err` 和 `confirmationStatus`。传入返回的 `paginationToken` 即可获取下一页。如需完整交易，请将 `transactionDetails` 设为 `"full"`，并同时设置 `"maxSupportedTransactionVersion": 1`。缺少后者时，版本 1 交易会被悄悄略过。过滤和排序选项请参阅 [getTransactionsForAddress](/cn/fetching-data/getTransactionsForAddress)。

## 更多示例

> **[错误参考](/cn/rpc/error-codes)**
>
> OrbitFlare 返回的每一种错误以及相应的处理方法。

> **[交易调试](/cn/troubleshooting/transactions)**
>
> 交易为何失败、被丢弃或始终没有上链。

> **[通过 Apex 发送](/cn/apex/quickstart)**
>
> 使用 OrbitFlare Apex 让交易更快上链。

> **[SDK 参考](/cn/sdk/overview)**
>
> SDK 中的每个客户端、构建器选项和方法。


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