跳转到主要内容

可用端点

Jetstream v2 与 v1 在相同的端点上提供服务——没有单独的主机。JetstreamV2 服务通过同一 HTTP/2 连接暴露,并按 gRPC 方法路径进行路由,因此您可以像连接 v1 一样连接到离您基础设施最近的区域。
城市区域标识端点
New Yorknyhttp://ny.jetstream.orbitflare.com
Salt Lake Cityslchttp://slc.jetstream.orbitflare.com
Jetstream 的 gRPC 连接使用纯 http://(而非 https://)。gRPC 会在 HTTP/2 上协商自己的传输层安全。仅当您的专用节点明确要求时才使用 https://

身份验证

身份验证与 v1 相同:在 x-token gRPC 元数据头中提供您的 API 密钥,或使用 IP 白名单身份验证。详情请参见身份验证

Protocol Buffer 规范

本文档概述了 OrbitFlare Jetstream v2 所使用的 Protocol Buffer(protobuf)规范。完整的规范可在我们的 GitHub 仓库中找到——在那里下载 jetstream_v2.proto 以生成您的客户端,或使用下面转载的定义。jetstream.v2 包完全独立于 v1——请从它生成一个单独的客户端。

服务定义

syntax = "proto3";

package jetstream.v2;

import "google/protobuf/timestamp.proto";

service JetstreamV2 {
  rpc SubscribeTransactions(stream SubscribeTransactionsRequest) returns (stream SubscribeTransactionsResponse) {}

  // Server-streaming slot lifecycle events.
  rpc SubscribeSlots(SubscribeSlotsRequest) returns (stream SlotEvent) {}

  // Unary health/version probes.
  rpc Ping(PingRequest) returns (PongResponse) {}
  rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
}

请求消息

SubscribeTransactions 是一个双向流。客户端发送一系列请求消息——每条恰好携带一个负载——以在数据流打开期间管理其订阅。

SubscribeTransactionsRequest

message SubscribeTransactionsRequest {
  oneof payload {
    Ping          ping           = 1;
    AddFilters    add_filters    = 2;
    RemoveFilters remove_filters = 3;
  }
}

message Ping {
  int32 ping_id = 1;
}

添加过滤器

过滤器在数据流打开期间动态添加。每个过滤器都携带一个由客户端选择的 filter_id,它会在每笔匹配的交易上以及在验证确认中回显。
message AddFilters {
  repeated TxFilter filters = 1;
}

message TxFilter {
  string                             filter_id = 1;  // client-provided, max 127 chars
  SubscribeRequestFilterTransactions filter    = 2;
}

message SubscribeRequestFilterTransactions {
  repeated string account_include  = 1;
  repeated string account_exclude  = 2;
  repeated string account_required = 3;
  // account_required matches a tx that references AT LEAST ONE of these accounts
  // (any-of — identical to account_include, and identical to v1). It is NOT the
  // "tx must contain ALL of these accounts" meaning.

  // Opt-in enrichment. Default false = lean response (core fields only). Set
  // true to also receive fee_payer / writable_accounts / program_ids /
  // compute_unit_price / compute_limit / tx_size. Applies to the whole
  // subscription: if any of your active filters sets this true, every
  // transaction you receive is enriched.
  bool include_enrichment = 4;
}
过滤器必须指定 account_include / account_exclude / account_required 中的至少一个。空的(匹配一切的)过滤器会被拒绝——参见 FilterResult

移除过滤器

message RemoveFilters {
  repeated string filter_ids = 1;
}

响应消息

SubscribeTransactionsResponse

数据流上的每条消息都携带一个服务器时间戳和一个单调递增的 sequence 编号(每个流),以便客户端可以检测间隙。恰好设置一个负载。
message SubscribeTransactionsResponse {
  google.protobuf.Timestamp created_at = 1;

  oneof payload {
    FilteredTransaction transaction       = 2;
    Pong                pong              = 3;
    FilterResult        filter_validation = 5;
    Heartbeat           heartbeat         = 6;
  }

  uint64 sequence = 4;
}

message Pong {
  int32 ping_id = 1;
}

过滤器验证

在每次 AddFilters / RemoveFilters 之后,服务器为每个过滤器返回一个 FilterResult,确认接受或说明拒绝的原因。
message FilterResult {
  string filter_id        = 1;
  bool   accepted         = 2;
  string rejection_reason = 3;  // empty string when accepted=true (proto3 default elision)
}

心跳

message Heartbeat {
  // Server wall-clock at emit time, milliseconds since Unix epoch. Lets
  // clients measure one-way skew / network delay and age out stale
  // connections.
  uint64 server_ts_ms = 1;
}

已过滤交易

每笔匹配的交易都会列出它所匹配的 filter_id 值,随后是交易本身。
message FilteredTransaction {
  repeated string    filter_ids  = 1;
  TransactionMessage transaction = 2;
}

// TransactionMessage — a flat transaction representation (all fields inline).
// Fields 1-13 are the core transaction and are always present (the default
// "lean" output). Fields 14-22 are optional enrichment, emitted only when the
// session opts in via SubscribeRequestFilterTransactions.include_enrichment.
message TransactionMessage {
  // Identity / placement
  uint64 slot      = 1;
  bytes  signature = 2;  // primary signature (signatures[0])
  uint32 tx_index  = 3;  // position of this tx within its entry

  // Full signature set (repeated bytes)
  repeated bytes signatures = 4;

  // Always false: vote transactions are filtered out and never delivered (v1 and v2).
  bool is_vote = 5;

  // Message header
  uint32 num_required_signatures        = 6;
  uint32 num_readonly_signed_accounts   = 7;
  uint32 num_readonly_unsigned_accounts = 8;

  // Recent blockhash
  bytes recent_blockhash = 9;

  // Versioned-transaction flag.
  bool versioned = 10;

  // Core body contents
  repeated bytes                     account_keys          = 11;
  repeated CompiledInstruction       instructions          = 12;
  repeated MessageAddressTableLookup address_table_lookups = 13;

  // Enrichment — emitted only when the session opts in via
  // SubscribeRequestFilterTransactions.include_enrichment.
  bytes          fee_payer         = 14;
  repeated bytes writable_accounts = 15;
  repeated bytes program_ids       = 16;
  // Compute-unit price (micro-lamports per CU) from SetComputeUnitPrice; 0 if unset.
  // The per-CU price, NOT the total priority fee — total priority fee in lamports
  // ~= compute_unit_price * compute_limit / 1_000_000.
  uint64         compute_unit_price = 17;
  // Explicit SetComputeUnitLimit only; 0 if the tx sets none (no implicit
  // 200k-CU-per-instruction default is applied).
  uint32         compute_limit     = 18;
  uint32         tx_size           = 19;

  // Resolved addresses from a versioned tx's address_table_lookups. Emitted
  // only when enrichment is opted in (same gate as fields 14-19). Empty for a
  // legacy (non-versioned) tx; when a lookup can't be fully resolved,
  // alt_resolution_incomplete (field 22) is set and any addresses that did
  // resolve are still included.
  repeated bytes loaded_writable_addresses = 20;
  repeated bytes loaded_readonly_addresses = 21;

  // Set true when one or more of this transaction's address_table_lookups could
  // NOT be fully resolved, so the loaded_writable_addresses /
  // loaded_readonly_addresses above are INCOMPLETE. When true, resolve the tx's
  // accounts yourself from address_table_lookups (fetch the tables via
  // RPC). Default false = fully resolved. Emitted only under
  // enrichment (same gate as fields 14-21).
  bool alt_resolution_incomplete = 22;
}

共享类型

message CompiledInstruction {
  uint32 program_id_index = 1;
  bytes  accounts         = 2;
  bytes  data             = 3;
}

message MessageAddressTableLookup {
  bytes account_key      = 1;
  bytes writable_indexes = 2;
  bytes readonly_indexes = 3;
}

槽位流式传输

SubscribeSlots 是一个槽位生命周期事件的服务器流。它不接受任何参数——打开数据流即可随着槽位推进接收事件。
// SubscribeSlots takes no request parameters.
message SubscribeSlotsRequest {
}

enum SlotStatus {
  SLOT_STATUS_UNSPECIFIED = 0;
  SLOT_STATUS_ALIVE       = 1;  // first shred of this slot received
  SLOT_STATUS_COMPLETE    = 2;  // last shred of this slot received
  SLOT_STATUS_DEAD        = 3;  // slot skipped, or superseded by later slots without completing
}

message SlotEvent {
  uint64     slot           = 1;
  SlotStatus status         = 2;
  // 32-byte pubkey. Empty bytes (proto3 default) means the current leader is
  // unavailable.
  bytes      current_leader = 3;
  // Parent slot, from the first shred received for this slot. 0 means
  // "unknown parent" (e.g. a DEAD event for a slot that was never observed).
  uint64     parent_slot    = 4;
  uint64     sequence       = 5;  // monotonic per-stream
}

非流式方法

message PingRequest {
  int32 count = 1;
}

message PongResponse {
  int32 count = 1;
}

message GetVersionRequest {}

message GetVersionResponse {
  string version = 1;
}

使用该协议

一个典型的 v2 客户端:
  1. 从上面的 jetstream.v2 定义生成客户端代码。
  2. 打开 SubscribeTransactions 双向流并进行身份验证(x-token 头或 IP 白名单)。
  3. 发送一个包含一个或多个 TxFilter 条目的 AddFilters 请求,每个条目都有唯一的 filter_id
  4. 读取 FilterResult 确认,然后读取 FilteredTransaction 消息流。
  5. 随时添加或移除过滤器,无需重新打开数据流。
  6. 跟踪 sequence 以检测间隙,并将连接关闭视为重连的信号。
  7. 可选地打开 SubscribeSlots(一个独立的数据流)以接收槽位生命周期事件。

代码生成

对于 TypeScript/JavaScript:
protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \
  --ts_proto_out=. \
  --ts_proto_opt=esModuleInterop=true \
  jetstream_v2.proto
对于 Rust:
protoc --rust_out=. jetstream_v2.proto

最佳实践

  1. 过滤
    • 始终设置至少一个账户约束;空过滤器会被拒绝。
    • 为每个过滤器使用稳定且唯一的 filter_id,以便您可以关联匹配结果并在之后移除过滤器。
  2. 富化
    • 除非您需要额外字段,否则将 include_enrichment 关闭——精简输出更小。启用它不会增加延迟;唯一的代价是消息略大一些。
    • 富化是面向整个订阅的:在任一过滤器上启用它会富化该会话接收的每笔交易。
  3. 序列与重连
    • 跟踪 sequence 编号以检测丢失的消息。
    • 实现自动重连;重连时,重新发送您的 AddFilters
  4. 存活检测
    • 使用 HeartbeatPing/Pong 来确认数据流健康并测量延迟。

另请参阅

Jetstream v2 概述

v2 相比 v1 新增了什么、流式传输模型以及入门。

Jetstream (v1) 参考

v1 Protocol Buffer 规范。

支持

如需有关 v2 协议或实现细节的技术问题咨询,请加入我们的 Discord 社区