# CLI Source: https://docs.orbitflare.com/agents/cli Use the OrbitFlare CLI in shell scripts, CI/CD pipelines, and agent-driven automation. Every command supports --json for machine-readable output. The [OrbitFlare CLI](/cli) is a single binary that wraps RPC queries, gRPC and Jetstream streaming with YAML configs, project scaffolding, and account management. For agent and automation workflows, the key feature is `--json`: every command emits structured output that composes cleanly with `jq`, shell pipes, GitHub Actions, and cron jobs. For interactive AI workflows in Claude Code, Cursor, or other MCP hosts, prefer the [MCP server](/agents/mcp). The CLI is best for shell-driven automation where you want deterministic exit codes and parseable output. ## Install ```bash theme={null} cargo install orbitflare # requires Rust 1.85+ orbitflare --version ``` See the [full CLI reference](/cli) for installation alternatives, auth methods, and the complete command surface. ## Headless setup For non-interactive environments (CI, containers, scripts), authenticate with an API key directly, no browser flow needed: ```bash theme={null} orbitflare auth login --x-orbit-key "$ORBITFLARE_API_KEY" orbitflare config set rpc.url "$ORBITFLARE_RPC_URL" orbitflare config set grpc.url "$ORBITFLARE_GRPC_URL" orbitflare config set jetstream.url "$ORBITFLARE_JETSTREAM_URL" orbitflare ping --json ``` Credentials are stored in the OS keychain by default. In containers without a keychain, set `ORBITFLARE_CONFIG_DIR` to a writable path and the CLI will fall back to a file-based config. ## JSON output Every command supports `--json`: ```bash theme={null} $ orbitflare rpc slot --json {"slot": 312456789} $ orbitflare rpc balance Gh9ZwEm... --json {"address": "Gh9ZwEm...", "lamports": 12500000000, "sol": 12.5} ``` Compose with `jq` for one-liners: ```bash theme={null} # Print just the slot orbitflare rpc slot --json | jq -r '.slot' # Alert if balance drops below 1 SOL SOL=$(orbitflare rpc balance "$WALLET" --json | jq -r '.sol') if (( $(echo "$SOL < 1" | bc -l) )); then echo "Balance low: $SOL SOL" exit 1 fi ``` ## Streaming with YAML configs The CLI streams Jetstream and Yellowstone gRPC using a declarative YAML config, no code required: ```yaml theme={null} # pump-trades.yaml transactions: pump_trades: account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" reconnect: initial_delay_ms: 100 max_delay_ms: 30000 multiplier: 2.0 max_retries: 0 ``` Run it (Jetstream or Yellowstone gRPC): ```bash theme={null} orbitflare jet --config pump-trades.yaml | your-indexer orbitflare grpc --config pump-trades.yaml | your-indexer ``` Have an MCP-equipped agent generate the YAML for you. The [MCP server](/agents/mcp) `subscribeTransactions` tool emits a CLI-ready config from a natural-language description. ## Scripting patterns ### Tail recent signatures and process them ```bash theme={null} orbitflare rpc history "$WALLET" --limit 50 --json \ | jq -r '.[].signature' \ | while read sig; do orbitflare rpc tx "$sig" --json > "txs/$sig.json" done ``` ### CI health check ```yaml theme={null} # .github/workflows/rpc-health.yml name: OrbitFlare RPC health on: schedule: - cron: '*/15 * * * *' jobs: ping: runs-on: ubuntu-latest steps: - uses: actions-rs/toolchain@v1 with: { toolchain: stable } - run: cargo install orbitflare - run: orbitflare auth login --x-orbit-key ${{ secrets.ORBITFLARE_API_KEY }} - run: orbitflare config set rpc.url ${{ secrets.ORBITFLARE_RPC_URL }} - run: orbitflare ping --json ``` ### Scaffold a project from an agent The CLI ships templates for common Solana projects. An agent can list them and spin one up in one shot: ```bash theme={null} orbitflare template --list --json orbitflare template --install solana-copy-trader --dir ./bot ``` ## Profiles Switch between accounts or environments without re-authenticating each time: ```bash theme={null} orbitflare auth login --profile prod --x-orbit-key "$PROD_KEY" orbitflare auth login --profile dev --x-orbit-key "$DEV_KEY" orbitflare auth switch --profile prod orbitflare auth status --json ``` ## Source * CLI repo: [github.com/orbitflare/orbit-cli](https://github.com/orbitflare/orbit-cli) * Full reference: [CLI documentation](/cli) # MCP Server Source: https://docs.orbitflare.com/agents/mcp 53 OrbitFlare tools exposed via the Model Context Protocol. Works in Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Gemini CLI, and Codex CLI. The OrbitFlare MCP server exposes Shredstream-optimized Solana RPC, gRPC streaming, dedicated nodes, and validator infrastructure as tools your AI assistant can call directly. Each capability is its own tool with a focused input schema: no internal action routers, no result-id pagination dance. Pair the MCP server with the [Skill](/agents/skills) for the best experience. The skill teaches the agent *how* OrbitFlare works; the MCP server lets it actually call OrbitFlare without leaving the chat. ## Install Most hosts take the same JSON shape; only the file location differs: ```json theme={null} { "mcpServers": { "orbitflare": { "command": "npx", "args": ["@orbitflare/mcp@latest"], "env": { "ORBITFLARE_API_KEY": "your-license-key", "ORBITFLARE_NETWORK": "mainnet" } } } } ``` | Host | Where to put it | | - | - | | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) · `%APPDATA%\Claude\claude_desktop_config.json` (Windows) | | Claude Code | run `claude mcp add orbitflare -- npx @orbitflare/mcp@latest`, or drop a `.mcp.json` at your project root | | Cursor | `~/.cursor/mcp.json` (global) or `/.cursor/mcp.json` | | Gemini CLI | `~/.gemini/settings.json` (global) or `/.gemini/settings.json` | | Windsurf | `~/.codeium/windsurf/mcp_config.json` | | VS Code | depends on extension (Continue, Cline, GitHub Copilot agent mode) | **Codex CLI** uses TOML at `~/.codex/config.toml` (or `/.codex/config.toml`): ```toml theme={null} [mcp_servers.orbitflare] command = "npx" args = ["@orbitflare/mcp@latest"] ``` ### Get a license key Sign up at [orbitflare.com](https://orbitflare.com) and copy your license key from the **Licenses** section of the dashboard. The Free plan gives you 10 RPS, enough to start building immediately. If you'd rather not put the key in the host config, leave the env var unset and call `setApiKey` from chat: > Set my OrbitFlare API key to `xxxx-xxxx-xxxx`. ## Configuration | Env var | Purpose | | - | - | | `ORBITFLARE_API_KEY` | RPC license key (used as `?api_key=` for JSON-RPC) | | `ORBITFLARE_LICENSE_KEY` | Alias for `ORBITFLARE_API_KEY` | | `ORBITFLARE_CUSTOMER_API_KEY` | Customer API v2 key (`X-ORBIT-KEY` header). Falls back to `ORBITFLARE_API_KEY` if unset | | `ORBITFLARE_NETWORK` | `mainnet` (default) or `devnet` | | `ORBITFLARE_RPC_URL` | Optional explicit RPC endpoint URL (overrides region/network). Useful for dedicated nodes. | | `ORBITFLARE_BEARER_TOKEN` | Bearer token for the Customer API. Auto-populated by `pollDeviceToken` / `verifyWalletSignature` and persisted at `~/.orbitflare/mcp-session.json`. | All values can be overridden at runtime via the `setApiKey` tool, which is handy when switching networks or pinning a region without restarting the host. ### Regional endpoints `mainnet` is the default network. Endpoints are region-pinned; pin one by calling `setApiKey` with a region code: `fra`, `ams`, `lon`, `ny`, `la`, `slc`, `sg`, `va`, `jp`. ## Tools 53 tools, grouped by domain. Every tool is independently callable. ### RPC reads | Tool | What it does | | - | - | | `getBalance` | SOL balance for a pubkey, in lamports and SOL | | `getAccountInfo` | Full account info with `jsonParsed` decoding when available | | `getMultipleAccounts` | Batch up to 100 accounts in one call | | `getProgramAccounts` | Accounts owned by a program (filters + dataSlice required for popular programs) | | `getTokenAccountsByOwner` | All SPL token accounts a wallet owns | | `getTokenAccountBalance` | Balance of a specific SPL token account | | `getTokenSupply` | Total supply for an SPL mint | | `getTransaction` | Parsed transaction by signature | | `getSignaturesForAddress` | Recent signatures (paginated via `before` / `until`) | | `getBlock` | Block metadata + transactions at a slot | | `getSlot` | Current slot | | `getEpochInfo` | Epoch number, slot index, slots-in-epoch, progress | | `getNetworkStatus` | Health + version + identity + slot in one call | | `getLatestBlockhash` | Recent blockhash + lastValidBlockHeight | | `getFeeForMessage` | Lamport fee for a compiled message | | `getMinimumBalanceForRentExemption` | Min lamports for rent exemption | | `getStakeActivation` | Active/inactive lamports + state for a stake account | | `getRecentPrioritizationFees` | Priority-fee samples + P50/P75/P90/P99 percentiles | | `simulateTransaction` | Simulate a tx; returns logs + units consumed | | `sendTransaction` | Submit an already-signed base-64 transaction | ### Account, profile, and plans | Tool | What it does | | - | - | | `setApiKey` | Store an RPC or Customer API key for the session, optionally switch network/region | | `getProfile` | Authenticated user profile + account balance | | `updateProfile` | Update name, email, payment wallet | | `listLicenses` | All RPC licenses on the account | | `listRpcPlans` | Catalog of available plans with pricing | ### Auth (device code + wallet challenge) | Tool | What it does | | - | - | | `requestDeviceCode` | Start device-authorization flow; returns `verification_uri` + `user_code` | | `pollDeviceToken` | Poll for completion; auto-stores Bearer token on success | | `revokeToken` | Logout / clear Bearer token | | `requestWalletChallenge` | Get a one-time challenge string for a Solana wallet | | `verifyWalletSignature` | Sign in an existing account by signature | | `registerWithWallet` | Create a new account from a wallet signature | ### API keys and licenses | Tool | What it does | | - | - | | `listApiKeys` | List all API keys (masked) | | `createApiKey` | Create a new API key (full key returned **once**) | | `regenerateApiKey` | Regenerate by id (old key invalidated immediately) | | `deleteApiKey` | Permanently delete an API key | | `addLicenseIp` | Whitelist an IP on an IP-mode license | | `removeLicenseIp` | Remove a whitelisted IP | | `regenerateLicenseKey` | Rotate a license's RPC key | ### Billing | Tool | What it does | | - | - | | `prepareTopUp` | Build an unsigned USDC top-up transaction | | `confirmTopUp` | Submit the signed top-up; OrbitFlare verifies on-chain and credits balance | | `listInvoices` | Paginated invoice list | | `purchaseRpcPlan` | Purchase or `validateOnly`-dry-run a plan from your account balance | ### Streaming, staking, swap | Tool | What it does | | - | - | | `subscribeTransactions` | Build a ready-to-use Jetstream or Yellowstone gRPC config for the OrbitFlare CLI | | `getStakeInfo` | OrbitFlare validator identity, vote account, commission, est. APY, optional reward calculator | | `getSwapQuote` | Jupiter Metis routing quote (auto-resolves `SOL` / `USDC` / `USDT` symbols) | ### Wallet and write | Tool | What it does | | - | - | | `generateKeypair` | Generate Ed25519 keypair, optionally persist to `~/.orbitflare/mcp-keypair.json` | | `loadKeypair` | Load from base58 secret or Solana CLI keypair file | | `clearKeypair` | Remove from session (and optionally disk) | | `getWalletAddress` | Returns the loaded wallet's address | | `signMessage` | Ed25519-sign an arbitrary message (utf8 / base58 / base64) | | `buildSolTransfer` | Build an unsigned versioned tx transferring SOL | | `buildStakeDelegation` | Build an unsigned tx delegating to OrbitFlare's vote account (or any vote account) | | `signAndSendTransaction` | Sign with the session keypair, broadcast via OrbitFlare RPC | ## Flows ### Auth (device code) `requestDeviceCode` returns `verification_uri` + `user_code`. The model shows them to you. `pollDeviceToken { device_code }` repeats every `interval` seconds until success. The Bearer token is stored automatically and used for subsequent Customer API calls. `revokeToken` clears the Bearer token. ### Auth (wallet) `generateKeypair` (creates fresh) or `loadKeypair` (from base58 / Solana CLI file). `requestWalletChallenge { wallet_address }`. `signMessage { message: }` then `verifyWalletSignature` (existing account) or `registerWithWallet` (new account). ### Wallet send `generateKeypair` or `loadKeypair`. `buildSolTransfer { recipient, amountSol }` returns a base64 unsigned tx. `signAndSendTransaction { transaction }` signs with the session keypair, broadcasts, and returns signature + Solscan link. ### Top-up `prepareTopUp { amount, wallet_address }` returns a base64 unsigned USDC transfer + memo. Sign with the loaded wallet via `signAndSendTransaction`, or sign externally. `confirmTopUp { transaction: }`. OrbitFlare verifies on-chain and credits your balance. ## Resources The server exposes OrbitFlare's documentation as MCP resources, so the model can pull current docs on demand: | URI | Page | | - | - | | `orbitflare://docs/index` | Documentation index (`llms.txt`) | | `orbitflare://docs/quickstart` | Quickstart | | `orbitflare://docs/authentication` | Auth, endpoints, rate limits, connection limits | | `orbitflare://docs/products` | Products & pricing | | `orbitflare://docs/rpc-http` | HTTP RPC reference | | `orbitflare://docs/jetstream` | Jetstream streaming | | `orbitflare://docs/yellowstone` | Yellowstone gRPC | | `orbitflare://docs/staking` | Validator staking | | `orbitflare://docs/metis-swap` | Metis Swap API | | `orbitflare://docs/cli` | OrbitFlare CLI | | `orbitflare://docs/customer-api` | Customer API v2 | ## System prompts Pre-built system / developer prompts ship in the [`system-prompts/orbitflare/`](https://github.com/orbitflare/orbitflare-mcp/tree/main/system-prompts/orbitflare) directory: * `claude.system.md`: concise system prompt for Claude * `openai.developer.md`: concise developer message for OpenAI models * `full.md`: long-form operator playbook (auth flows, top-up, hard guardrails) Drop one into your host config to give the model end-to-end guidance on when to reach for which tool. ## Try it Once installed, the model picks the right tool automatically: > "What is the SOL balance of `Gh9ZwEm…`?" > "Show me the last 20 transactions for `7xKXt…`." > "What's a competitive priority fee right now for an account that touches Raydium?" > "Build me a Yellowstone gRPC config that streams Jupiter swap transactions from Frankfurt." > "Quote 1 SOL → USDC with 0.3% slippage." > "What is OrbitFlare's validator vote account, and what would 250 SOL earn per year?" ## Source * MCP server: [github.com/orbitflare/orbitflare-mcp](https://github.com/orbitflare/orbitflare-mcp) * npm: [`@orbitflare/mcp`](https://www.npmjs.com/package/@orbitflare/mcp) # AI Agents on OrbitFlare Source: https://docs.orbitflare.com/agents/overview Build AI agents on OrbitFlare. Skills, MCP server, CLI, and SDK: everything an agent needs to query Solana, stream data, and manage an OrbitFlare account. OrbitFlare ships a complete agent surface for Solana. Drop a skill into Claude Code or Cursor, wire up the MCP server in any compatible host, script the CLI from a shell, or call the Rust SDK directly. All four talk to the same Shredstream-optimized RPC, gRPC streaming, and Customer API. **Start with the MCP server.** It exposes 53 typed tools that cover RPC reads, streaming config, account management, billing, swaps, and on-chain wallet operations, and it works in Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Gemini CLI, and Codex CLI out of the box. ## What's available Drop-in instruction sets that teach Claude Code, Cursor, and Codex how to use OrbitFlare correctly: RPC, streaming, dedicated nodes, trading APIs, CLI, and SDK in one skill. 53 tools across RPC, streaming, billing, swaps, staking, and wallet ops. Works in any MCP-compatible host. Includes auth flows, top-up, and live docs as resources. Single binary for streaming, querying, scaffolding, and account management. Every command supports `--json` so output is parseable in shell scripts and CI/CD. Typed Rust clients for RPC, WebSocket, Yellowstone gRPC, and Jetstream. Built-in retry, failover, and reconnection. Reads endpoints from environment variables. ## Skill vs MCP vs CLI vs SDK Pick the surface that matches the workflow: | Surface | Best for | When to reach for it | | - | - | - | | **Skill** | Coding agents (Claude Code, Cursor, Codex) writing OrbitFlare code | You want the agent to **write** correct OrbitFlare code (endpoint URLs, gRPC filters, SDK setup) without you fetching docs first | | **MCP** | Agents that need to **call** OrbitFlare directly from chat | You want the agent to query balances, send txs, build streaming configs, or manage your account interactively | | **CLI** | Shell scripts, CI/CD, terminal automation | You're writing bash, GitHub Actions, or cron jobs, and `--json` output composes cleanly with `jq` and shell pipes | | **SDK** | Production Rust services | You're building a long-running daemon, indexer, or trading bot and want type-safe access with retry/failover baked in | Skills and MCP are complementary. Install both: the skill teaches the agent *how* OrbitFlare works; the MCP server lets it actually call OrbitFlare without leaving the chat. ## Recommended setup The fastest path for a coding agent on Solana: Sign up at [orbitflare.com](https://orbitflare.com) and copy your license key from the **Licenses** section. The Free plan gives you 10 RPS / 1 TPS forever. ```bash theme={null} npx @orbitflare/skills@latest # Claude Code (personal) ``` See [Skills](/agents/skills) for Cursor, Codex, and project-scoped installs. Add to your host's MCP config: ```json theme={null} { "mcpServers": { "orbitflare": { "command": "npx", "args": ["@orbitflare/mcp@latest"], "env": { "ORBITFLARE_API_KEY": "your-license-key", "ORBITFLARE_NETWORK": "mainnet" } } } } ``` See [MCP Server](/agents/mcp) for per-host file paths and the Codex CLI TOML variant. Restart Claude Code (or your host of choice) so it picks up the new skill and MCP server. > "Stream pump.fun trades using OrbitFlare Jetstream from Frankfurt" > "What is the SOL balance of `Gh9ZwEm…`? Then show me the last 20 transactions for that address." > "Quote 1 SOL → USDC with 0.3% slippage and build me a Yellowstone config to watch the pool." ## Common workflows | Goal | Surfaces to use | | - | - | | Build a trading bot | **MCP** for prototyping in chat → **Rust SDK** for the production daemon | | Stream real-time data | **MCP** `subscribeTransactions` to generate the YAML config → **CLI** `orbitflare stream` to run it | | Index historical data | **Skill** to learn `getTransactionsForAddress` and the archive endpoint → **SDK** for the indexer | | Manage account / billing | **MCP** auth flows + `prepareTopUp` / `confirmTopUp` from chat | | Quote a swap | **MCP** `getSwapQuote` (Jupiter Metis routing, auto-resolves SOL/USDC/USDT symbols) | | CI / scripted automation | **CLI** with `--json` flag piped into `jq` | ## Source code Everything is open source: * Skills: [github.com/orbitflare/orbitflare-skills](https://github.com/orbitflare/orbitflare-skills) * MCP server: [github.com/orbitflare/orbitflare-mcp](https://github.com/orbitflare/orbitflare-mcp) * CLI: [github.com/orbitflare/orbit-cli](https://github.com/orbitflare/orbit-cli) * Rust SDK: [github.com/orbitflare/orbitflare-sdk-rs](https://github.com/orbitflare/orbitflare-sdk-rs) The agent surfaces are derived from the same source of truth: [docs.orbitflare.com/llms.txt](https://docs.orbitflare.com/llms.txt). Point any agent at that index when you want it to ground answers in the official docs. # Rust SDK Source: https://docs.orbitflare.com/agents/sdk Build production agents and indexers in Rust with type-safe clients for RPC, WebSocket, Yellowstone gRPC, and Jetstream, with retry, failover, and reconnection built in. When you're past the prototype stage and shipping a production daemon, indexer, trading bot, or long-running agent worker, reach for the Rust SDK. Instead of managing raw HTTP requests, gRPC channels, and WebSocket connections yourself, the SDK handles connection lifecycle, authentication, retries, failover, and reconnection behind a clean API. For interactive agent workflows in Claude Code or Cursor, prefer the [MCP server](/agents/mcp). For shell scripts and CI, prefer the [CLI](/agents/cli). The SDK is for code your agents *write*: production services that run independently. ## Install ```bash theme={null} cargo add orbitflare-sdk # RPC only cargo add orbitflare-sdk --features ws # + WebSocket cargo add orbitflare-sdk --features grpc # + Yellowstone gRPC cargo add orbitflare-sdk --features jetstream # + Jetstream ``` Combine features as needed, e.g. `--features "ws grpc"` for an indexer that subscribes to both surfaces. ## What's included | Service | What it does | | - | - | | **RPC** | JSON-RPC client with typed helpers for common Solana methods, plus raw escape hatches | | **WebSocket** | Subscriptions for accounts, logs, slots, signatures with auto-resubscribe on reconnect | | **gRPC (Yellowstone)** | Yellowstone Geyser streaming for transactions, accounts, slots, and blocks. YAML config or programmatic filters | | **JetStream** | OrbitFlare's decoded shreds delivered as gRPC streams. Same client pattern as Yellowstone, different proto | ## Environment variables The SDK reads endpoints from the environment if you don't pass them explicitly. This is the recommended pattern for agent-generated code: the agent never has to hard-code URLs or keys, and the same binary works across regions and networks. | Variable | Used by | Purpose | | - | - | - | | `ORBITFLARE_LICENSE_KEY` | RPC, WebSocket | API key appended to endpoint URLs | | `ORBITFLARE_RPC_URL` | RPC | Default endpoint if `.url()` is not called | | `ORBITFLARE_WS_URL` | WebSocket | Default endpoint if `.url()` is not called | | `ORBITFLARE_GRPC_URL` | gRPC | Default endpoint if `.url()` is not called | | `ORBITFLARE_JETSTREAM_URL` | JetStream | Default endpoint if `.url()` is not called | ## Quick example A minimal RPC agent that reads its endpoint from the environment and queries balance with retry/failover handled automatically: ```rust theme={null} use orbitflare_sdk::{RpcClientBuilder, Result}; #[tokio::main] async fn main() -> Result<()> { let client = RpcClientBuilder::new().build()?; // reads ORBITFLARE_RPC_URL + LICENSE_KEY let lamports = client.get_balance("Gh9ZwEm...").await?; println!("{} SOL", lamports as f64 / 1e9); Ok(()) } ``` With explicit failover regions: ```rust theme={null} let client = RpcClientBuilder::new() .urls(&[ "http://ny.rpc.orbitflare.com", "http://fra.rpc.orbitflare.com", "http://ams.rpc.orbitflare.com", ]) .build()?; ``` The first URL is primary; the rest are fallbacks tried in order on failure. ## Streaming with Yellowstone gRPC ```rust theme={null} use orbitflare_sdk::{GeyserClientBuilder, Result}; #[tokio::main] async fn main() -> Result<()> { let client = GeyserClientBuilder::new() .url("http://fra.rpc.orbitflare.com:10000") .build() .await?; let mut stream = client.subscribe_yaml("stream.yml")?; while let Some(update) = stream.next().await { // process update } Ok(()) } ``` `subscribe_yaml` reads filters from a YAML config; for programmatic filters use `client.subscribe(SubscribeRequest { .. })` instead. The Jetstream client mirrors this pattern. Swap `GeyserClientBuilder` for `JetstreamClientBuilder`. ## Why use the SDK over raw HTTP For agent-written production code, the SDK handles the cross-cutting concerns that are easy to get wrong: * **Retries with exponential backoff**, configurable per client * **Multi-region failover**: primary fails, fallbacks tried in order * **Auth injection**: license key added to every request, never stored in the endpoint * **WebSocket reconnect**: subscriptions auto-resubscribe after disconnect * **gRPC channel management**: keepalive, reconnect, backoff * **Typed responses**: common methods deserialize into Rust structs, with raw escape hatches available for everything else When you ask an agent to write an OrbitFlare integration, point it at the SDK and the resulting code stays small, idiomatic, and resilient. ## Source * SDK repo: [github.com/orbitflare/orbitflare-sdk-rs](https://github.com/orbitflare/orbitflare-sdk-rs) * Full reference: [SDK overview](/sdk/overview), [RPC](/sdk/rust-rpc), [WebSocket](/sdk/rust-websocket), [gRPC](/sdk/rust-grpc), [Jetstream](/sdk/rust-jetstream) # Skills Source: https://docs.orbitflare.com/agents/skills Drop-in skill packages that teach Claude Code, Cursor, and Codex how to build on OrbitFlare correctly. OrbitFlare ships agent skills (instruction sets the model loads at the start of a session) so coding agents write OrbitFlare code correctly without you fetching docs first. The main skill covers HTTP RPC, WebSockets, Jetstream, Yellowstone gRPC, raw Shredstream, Dedicated Nodes (Solana + BNB Chain), `getTransactionsForAddress`, historical archive data, Metis Swap, Jito Bundle Simulation, the Customer API, the OrbitFlare CLI, and the `orbitflare-sdk-rs` Rust SDK. Skills are read-only context; they don't make API calls. Pair a skill with the [MCP server](/agents/mcp) when you want the agent to actually query OrbitFlare from chat. ## Install One line, no clone required. Pick the host: ```bash theme={null} npx @orbitflare/skills@latest ``` Installs to `~/.claude/skills/orbitflare`. Available in every Claude Code session. ```bash theme={null} npx @orbitflare/skills@latest --project ``` Installs to `./.claude/skills/orbitflare`. Only loaded when Claude Code runs in this directory, so commit it and your team picks it up automatically. ```bash theme={null} npx @orbitflare/skills@latest --cursor ``` Installs to `~/.cursor/skills-cursor/orbitflare`. ```bash theme={null} npx @orbitflare/skills@latest --codex ``` Installs to `~/.codex/skills/orbitflare`. ```bash theme={null} npx @orbitflare/skills@latest --path /your/custom/dir ``` Installs to `/your/custom/dir/orbitflare`. After install, **restart your agent** so it picks up the new skill. Updates are one command: `npx @orbitflare/skills@latest` again. The installer overwrites the skill files in place. ### From source If you want to clone the repo (e.g. to modify the skill), the underlying installer is also exposed as a shell script: ```bash theme={null} git clone https://github.com/orbitflare/orbitflare-skills cd orbitflare-skills/orbitflare ./install.sh # same flags: --project, --cursor, --codex, --path ``` ## What's in the skill `SKILL.md` is the entry point. The model loads it at the start of every session and uses it to route to the right reference file. References cover: | Reference | Topic | | - | - | | `rpc.md` | HTTP JSON-RPC: endpoint URLs, headers, common methods | | `websockets.md` | WebSocket subscriptions, reconnection patterns | | `jetstream.md` | OrbitFlare's Shredstream-decoded gRPC stream | | `yellowstone.md` | Yellowstone Geyser gRPC: accounts, txs, slots, blocks | | `shredstream.md` | Raw UDP Shredstream feed | | `fetching-data.md` | `getTransactionsForAddress`, historical archive | | `dedicated-nodes.md` | Solana + BNB Chain dedicated infrastructure | | `trading-apis.md` | Metis Swap, Jito Bundle Simulation | | `customer-api.md` | Customer API v2: licenses, billing, top-up | | `cli-sdk.md` | OrbitFlare CLI command surface + Rust SDK setup | | `onboarding.md` | Auth keys, dashboard signup, first call | | `repos.md` | Pointers to template repos and example projects | The skill instructs the agent to read the relevant reference **before** writing code, so you get correct endpoint URLs, gRPC filter shapes, and SDK feature flags on the first try. ## Try it After installing the skill and restarting your agent: > "Stream pump.fun trades using OrbitFlare Jetstream from Frankfurt" > "Quote 1 SOL → USDC with Metis on OrbitFlare and write a Rust binary that polls every 30 seconds" > "Build a Yellowstone gRPC subscription that streams Jupiter swap transactions, with retry on disconnect" The agent will pull the relevant reference (`jetstream.md`, `trading-apis.md`, `yellowstone.md`) and produce code that uses the right endpoint URL, headers, and SDK patterns. ## Source * Skills repo: [github.com/orbitflare/orbitflare-skills](https://github.com/orbitflare/orbitflare-skills) * npm: [`@orbitflare/skills`](https://www.npmjs.com/package/@orbitflare/skills) * Source of truth: [docs.orbitflare.com/llms.txt](https://docs.orbitflare.com/llms.txt) Everything in the skill is derived from this documentation site. When in doubt, point the agent at the `llms.txt` index. # Authentication & Limits Source: https://docs.orbitflare.com/authentication Authenticate Solana JSON-RPC with your license key on the query string, understand per-plan rate and connection limits, HTTP semantics, and errors for production workloads on OrbitFlare. ## RPC Authentication All OrbitFlare RPC requests are authenticated by appending your license key as a query parameter to the endpoint URL. ```bash theme={null} curl -X POST "https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockHeight" }' ``` Your RPC license key is provided with your OrbitFlare service subscription. You can find it in the **Licenses** section of your [OrbitFlare Dashboard](https://orbitflare.com/dashboard). ## Customer API Authentication The [Customer API](/api-documentation/welcome) uses a different authentication method. Pass your API key via the `X-ORBIT-KEY` HTTP header: ```bash theme={null} curl -X GET https://orbitflare.com/api/customer/v2/licenses \ -H "X-ORBIT-KEY: YOUR_API_KEY" ``` Customer API v2 also supports Bearer token authentication obtained through the device flow or wallet signature auth. See the [Customer API docs](/api-documentation/welcome) for details. ## RPC Endpoints OrbitFlare provides geo-distributed RPC endpoints so you can route requests to the region closest to your users or infrastructure. The URL format is: ``` https://{region}.rpc.orbitflare.com?api_key=YOUR_API_KEY ``` Replace `{region}` with one of the region codes below. ### Available Regions | City | Region Code | Endpoint | | - | - | - | | Ashburn | `va` | `https://va.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | New York | `ny` | `https://ny.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | Los Angeles | `la` | `https://la.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | Salt Lake City | `slc` | `https://slc.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | City | Region Code | Endpoint | | - | - | - | | Amsterdam | `ams` | `https://ams.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | Frankfurt | `fra` | `https://fra.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | London | `lon` | `https://lon.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | City | Region Code | Endpoint | | - | - | - | | Tokyo | `jp` | `https://jp.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | Singapore | `sg` | `https://sg.rpc.orbitflare.com?api_key=YOUR_API_KEY` | ### Choosing a Region Endpoints are region-pinned. Pick the region closest to your infrastructure for the lowest latency, for example Frankfurt: ``` https://fra.rpc.orbitflare.com?api_key=YOUR_API_KEY ``` Latency to the nearest region is what matters most. If you are unsure which region is closest to your servers, test a few with the `/health` endpoint and compare round-trip times. ## WebSocket Endpoints WebSocket connections use the same region codes with the `wss://` scheme: ``` wss://{region}.rpc.orbitflare.com?api_key=YOUR_API_KEY ``` For example, Frankfurt: ``` wss://fra.rpc.orbitflare.com?api_key=YOUR_API_KEY ``` Authentication is handled through the same `api_key` query parameter. ## Devnet OrbitFlare provides a dedicated Devnet endpoint for development and testing: ``` https://devnet.rpc.orbitflare.com?api_key=YOUR_API_KEY ``` The Devnet endpoint connects to the Solana Devnet. Do not use it for production workloads. Devnet tokens have no monetary value. ## Rate Limits by Plan Each plan defines a maximum number of **requests per second (RPS)** and **transactions per second (TPS)**. | Plan | Requests per Second (RPS) | Transactions per Second (TPS) | | - | :-: | :-: | | Free | 10 | 1 | | Developer | 50 | 10 | | Growth | 200 | 75 | | Scale | 400 | 150 | | Pro | 600 | 200 | | Dedicated | Unlimited | Unlimited | Requests that exceed your plan's RPS or TPS limit will receive an HTTP `429 Too Many Requests` response. Implement exponential backoff in your client to handle these gracefully. No. OrbitFlare does not impose monthly credit limits or request caps. You can send unlimited total requests within your plan's per-second rate limit. OrbitFlare plans have **no credit limits** -- you can make unlimited requests as long as you stay within your plan's per-second rate tier. There are no surprise overage charges. ## Data Streaming Connection Limits Connection limits apply independently of your RPC rate limit tier and are enforced per IP address across all streaming interfaces. ### gRPC (Jetstream / Yellowstone) | Limit | Value | | - | - | | Concurrent connections per IP | **50** | | Subscriptions per connection | Unlimited | | Idle connection timeout | 10 minutes (use ping to keep alive) | The 50-connection cap is shared across all gRPC endpoints (Jetstream and Yellowstone) originating from the same IP address. This applies regardless of which region you connect to. Dedicated gRPC nodes are not subject to the shared connection limit. If you need more than 50 concurrent gRPC connections, contact the team about a dedicated gRPC node. **Keeping connections alive** Cloud load balancers and proxies (including Cloudflare) may close idle gRPC streams after \~10 minutes. Send a ping every 30 seconds to prevent disconnection: ```typescript theme={null} const pingRequest: SubscribeRequest = { ping: { id: 1 }, accounts: {}, accountsDataSlice: [], transactions: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, slots: {}, entry: {}, }; setInterval(() => { stream.write(pingRequest, (err) => { if (err) console.error("Ping failed:", err); }); }, 30_000); ``` **What happens when the limit is exceeded** When your IP has 50 active gRPC connections and you attempt to open a new one, the server returns a gRPC `RESOURCE_EXHAUSTED` status: ``` StatusCode: RESOURCE_EXHAUSTED Message: connection limit exceeded ``` Close unused streams before opening new ones. Implement exponential backoff when reconnecting after a `RESOURCE_EXHAUSTED` error. ### WebSocket | Limit | Value | | - | - | | Concurrent connections per IP | **50** | | Subscriptions per connection | Varies by plan | | Idle connection timeout | 60 seconds without a message | WebSocket connections use the same 50 concurrent connections per IP limit. Connections idle for more than 60 seconds without any message exchange will be closed by the server. **What happens when the limit is exceeded** New WebSocket connections attempted beyond the limit are rejected with a `1008 Policy Violation` close code: ``` WebSocket closed: 1008 Policy Violation - connection limit exceeded ``` The 50-connection limit applies to all shared plans. If your workload requires more concurrent streaming connections, a **Dedicated gRPC Node** or **Dedicated RPC Node** removes the shared limit entirely. Contact the team on [Discord](https://discord.gg/orbitflare) to discuss options. The limit is enforced globally per IP address, across all regions. A single IP with 30 connections to Frankfurt and 20 connections to New York has reached the 50-connection cap. Use exponential backoff when reconnecting after a connection error. Start with a 1-second delay, doubling on each failed attempt up to a maximum of 30 seconds. Always close streams cleanly before reconnecting to avoid exhausting your connection quota. # BNB Chain Nodes Source: https://docs.orbitflare.com/bnb-chain Dedicated BNB Chain and BSC nodes for DeFi, trading desks, and high-throughput apps—predictable latency, archival options, and enterprise operations from OrbitFlare. ## Overview OrbitFlare provides enterprise-grade dedicated node infrastructure for BNB Chain (formerly Binance Smart Chain). Purpose-built for DeFi protocols, trading operations, and high-throughput applications that require reliable, low-latency access to the BNB/BSC network. ## Key Features Throughput scales with your plan, from 25 RPS on Free to 1,000 RPS on Performance. Dedicated instances are unmetered. Your node runs on isolated, high-performance hardware — no noisy neighbors, no shared resources. Enterprise-grade uptime guarantee backed by a service level agreement with financial commitments. Deploy nodes across 11 regions worldwide to minimize latency for your users and trading infrastructure. Around-the-clock support from the infrastructure team for monitoring, incidents, and configuration changes. Complete API coverage including WebSocket subscriptions and full archive node data access. ## Pricing Throughput scales by plan. RPC, WebSockets, and archival data are included on every tier, and there are no monthly credit or request caps. | Plan | Price | Requests / Second | | - | - | :-: | | Free | \$0/mo | 25 | | Starter | \$49/mo | 100 | | Growth | \$149/mo | 300 | | Pro | \$299/mo | 600 | | Performance | \$499/mo | 1,000 | Isolated infrastructure for high-throughput workloads: | Feature | Details | | - | - | | **Throughput** | Custom, unmetered | | **Hardware** | 100% dedicated resources, isolated environment | | **Rate Limits** | Custom, configurable | | **Support** | Priority engineering support, 24/7 | | **Uptime SLA** | 99.99%, custom terms available | | **Pricing** | Quoted on throughput, region, and SLA | For teams that need more than a single node or require tailored configurations: | Feature | Details | | - | - | | **Nodes** | Multiple dedicated nodes | | **Configuration** | Custom node configs and parameters | | **Account Management** | Dedicated account manager | | **SLA** | Custom uptime and response SLA | | **Pricing** | Volume discounts available | Reach out to the sales team to discuss your requirements and get a custom quote. ## Use Cases Power AMMs, lending platforms, and yield aggregators with dedicated, low-latency node access. Run arbitrage, MEV, and high-frequency trading strategies on infrastructure that won't throttle you. Query multiple liquidity sources in parallel without hitting shared rate limits. Handle minting events, metadata indexing, and high-traffic marketplace operations reliably. Operate cross-chain bridges with the uptime guarantees and monitoring that critical infrastructure demands. ## Connecting BNB Chain is served from a single global endpoint that follows the same API structure as the official BSC RPC. ### Authentication Append your license key as a query parameter: ``` https://bsc.rpc.orbitflare.com?api_key=YOUR_API_KEY ``` ### Making RPC Calls BNB Chain uses the same JSON-RPC 2.0 format as Ethereum. Pass `Content-Type: application/json` with a POST body: ```bash cURL theme={null} curl -X POST "https://bsc.rpc.orbitflare.com?api_key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": 1 }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://bsc.rpc.orbitflare.com?api_key=YOUR_API_KEY", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1, }), } ); const data = await response.json(); console.log("Latest block:", parseInt(data.result, 16)); ``` ```python Python theme={null} import requests response = requests.post( "https://bsc.rpc.orbitflare.com?api_key=YOUR_API_KEY", headers={"Content-Type": "application/json"}, json={ "jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": 1, }, ) data = response.json() print("Latest block:", int(data["result"], 16)) ``` ### WebSocket Subscriptions Real-time subscriptions use the `wss://` scheme with the same endpoint: ```javascript theme={null} const ws = new WebSocket( "wss://bsc.rpc.orbitflare.com?api_key=YOUR_API_KEY" ); ws.on("open", () => { ws.send(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_subscribe", params: ["newHeads"], })); }); ws.on("message", (data) => { const msg = JSON.parse(data); console.log("New block header:", msg.params?.result); }); ``` ### Supported Methods Your dedicated node exposes the complete BNB Chain / BSC JSON-RPC API including: | Category | Methods | | - | - | | **Blocks** | `eth_blockNumber`, `eth_getBlockByHash`, `eth_getBlockByNumber` | | **Transactions** | `eth_sendRawTransaction`, `eth_getTransactionByHash`, `eth_getTransactionReceipt` | | **Accounts** | `eth_getBalance`, `eth_getCode`, `eth_getStorageAt`, `eth_getTransactionCount` | | **Contracts** | `eth_call`, `eth_estimateGas`, `eth_getLogs` | | **Network** | `net_version`, `eth_chainId`, `eth_gasPrice`, `eth_feeHistory` | | **WebSocket** | `eth_subscribe` (newHeads, logs, newPendingTransactions) | BNB Chain uses `chainId: 56` for mainnet and `chainId: 97` for testnet (BSC Testnet). Your dedicated node connects to mainnet by default. ## Rate Limits and Quotas These limits apply to all OrbitFlare EVM chains (Robinhood, Polygon, BSC). They are enforced per license, and HTTP and WebSocket traffic on the same key share a single rate limit and in-flight budget. | Control | Free | Paid | | - | - | - | | Requests / second | plan RPS | plan RPS | | Batch size (HTTP + WS) | 1 item | 100 items | | `eth_getLogs` in flight | 1 | 10 | | `eth_getLogs` block range | 150 blocks | 10,000 blocks | | `debug_*` / `trace_*` methods | blocked | allowed | | WebSocket connections | 1 | from 10 | | Subscriptions per WebSocket connection | 5 | 25 | | `logs` subscription without a filter | refused | allowed | | WebSocket data per connection | 1 GiB | unlimited | Applies to both tiers: WebSocket connection lifetime of 24 hours; idle timeout of 30 minutes with no data frames; frame size of 4 MiB inbound and 32 MiB outbound; each batch item counts as one request. ### Error Codes | HTTP | JSON-RPC | Meaning | | - | - | - | | 429 | `-32429` | Rate limit exceeded | | 429 | `-32005` | Concurrent, batch, connection, or subscription limit exceeded | | 400 | `-32602` | `eth_getLogs` block range too large (the message names the span) | | 403 | `-32601` | Method not available on this plan | | 403 | `-32406` | WebSockets not enabled for this key | `-32005` and `-32429` are transient. Retry with exponential backoff. If `-32005` persists on isolated calls, another client is likely using the same key concurrently, since HTTP and WebSocket share one in-flight budget. ## Get Started Talk to the OrbitFlare team about dedicated BNB Chain nodes, custom configurations, and enterprise pricing. Learn how API keys work, endpoint formats, and connection limits. # OrbitFlare CLI Source: https://docs.orbitflare.com/cli Full-featured command-line interface for OrbitFlare. Streaming, querying, scaffolding, account management, and a built-in TUI dashboard, all from your terminal. The OrbitFlare CLI is the single entry point for everything a developer needs: streaming on-chain data via gRPC, querying the blockchain, scaffolding projects from templates, and managing your account through an interactive dashboard. The CLI stores credentials in your OS keychain and supports multiple auth profiles, so you can switch between accounts or networks instantly. ## Installation ```bash theme={null} cargo install orbitflare ``` ```bash theme={null} git clone https://github.com/orbitflare/orbit-cli.git cd orbit-cli cargo install --path . ``` Requires **Rust 1.85+**. Verify the installation: ```bash theme={null} orbitflare --version ``` ## Quick Start: Existing Users If you already have an OrbitFlare account and API key from the [dashboard](https://orbitflare.com/dashboard): ```bash theme={null} # Authenticate with your API key orbitflare auth login --x-orbit-key YOUR_API_KEY # Point the CLI to your RPC endpoint (from your dashboard) orbitflare config set rpc.url ORBITFLARE_RPC_URL # Verify everything works orbitflare ping orbitflare rpc slot ``` A fresh install starts with an empty config. After login, set at least `rpc.url` so commands like `ping` and `rpc` know where to connect. Your endpoint URLs are available in the **Services** section of your [dashboard](https://orbitflare.com/dashboard). Endpoint URLs follow this format: | Protocol | Format | Example | | - | - | - | | RPC | `http://{region}.rpc.orbitflare.com` | `http://ams.rpc.orbitflare.com` | | WS | `ws://{region}.rpc.orbitflare.com` | `ws://ams.rpc.orbitflare.com` | | gRPC (Yellowstone) | `http://{region}.rpc.orbitflare.com:10000` | `http://ams.rpc.orbitflare.com:10000` | | Jetstream | `http://{region}.jetstream.orbitflare.com` | `http://ams.jetstream.orbitflare.com` | ## Quick Start: New Users Sign up at [orbitflare.com](https://orbitflare.com) and choose a plan. The CLI supports three login methods. Pick the one that suits your workflow: ```bash theme={null} # API key (from orbitflare.com/dashboard) orbitflare auth login --x-orbit-key YOUR_API_KEY # Solana wallet keypair orbitflare auth login --wallet ~/.config/solana/id.json # Device flow (opens browser) orbitflare auth login ``` The CLI fetches and stores your RPC license key automatically during login. To set it manually: ```bash theme={null} orbitflare auth set-license-key YOUR_LICENSE_KEY ``` Keep your API key and license key secret. Do not commit them to version control or expose them in client-side code. A fresh install starts with an empty config. Point the CLI to your OrbitFlare endpoints: Your endpoint URLs are in the **Services** section of your [dashboard](https://orbitflare.com/dashboard). They follow the format `http://{region}.rpc.orbitflare.com`. ```bash theme={null} # Set your RPC endpoint orbitflare config set rpc.url ORBITFLARE_RPC_URL # (Optional) Set streaming endpoints orbitflare config set grpc.url ORBITFLARE_GRPC_URL orbitflare config set jetstream.url ORBITFLARE_JETSTREAM_URL # Verify connectivity orbitflare ping ``` See [Authentication & Limits](/authentication) for all available region endpoints. ```bash theme={null} # Query your SOL balance orbitflare rpc balance YOUR_WALLET_ADDRESS # Get current slot orbitflare rpc slot # Scaffold a project orbitflare template --list orbitflare template --install solana-copy-trader # Launch the TUI dashboard orbitflare dashboard ``` ## Authentication Credentials are stored securely in your OS keychain. The CLI supports multiple named profiles so you can manage separate accounts or environments. ```bash theme={null} # Check current auth status orbitflare auth status # Switch profile orbitflare auth switch --profile work # Log out orbitflare auth logout ``` ### Auth Methods Authenticate with your API key from the [dashboard](https://orbitflare.com/dashboard). The key is passed via the `X-ORBIT-KEY` header to the Customer API. ```bash theme={null} orbitflare auth login --x-orbit-key YOUR_API_KEY ``` Authenticate by signing a challenge with your Solana keypair. If the wallet is not linked to an account, the CLI will walk you through registration. ```bash theme={null} orbitflare auth login --wallet ~/.config/solana/id.json ``` Opens your browser for OAuth-style authentication. Ideal for interactive use. ```bash theme={null} orbitflare auth login ``` The CLI displays a verification URL and code, polls for completion, then stores the session token automatically. ### Key Resolution Order 1. `--x-orbit-key` flag on the command (highest priority) 2. `--profile` flag to select a stored profile 3. Default profile in `~/.orbitflare/config.yml` ## Command Reference The CLI has top-level commands spanning authentication, configuration, blockchain queries, real-time streaming, project scaffolding, and account management. ### Core Commands | Command | Description | | - | - | | `auth login` | Authenticate (API key, wallet, or device flow) | | `auth logout` | Remove stored credentials | | `auth status` | Show current authentication status | | `auth switch --profile ` | Switch to a different auth profile | | `auth set-license-key ` | Store RPC license key for current profile | | `config show` | Display all configuration values | | `config set ` | Set a configuration value | | `config remove ` | Remove a value from a list config | | `config reset` | Reset config to defaults (preserves auth) | | `ping` | Check connectivity to all configured services | | `ping --service ` | Ping a specific service (`rpc`, `jetstream`, `grpc`) | | `docs [TOPIC...]` | Open documentation in your browser | | `plan list` | List available RPC plans | | `plan view ` | View plan details | | `plan compare --all` | Compare all plans side by side | | `pay check-balance` | Check account balance | | `pay topup ` | Top up balance with USDC (on-chain) | | `pay history` | Top-up / credit history | | `pay purchase ` | Purchase a plan from balance | | `pay order --history` | List orders | | `pay order ` | View order details | | `pay invoice --list` | List invoices | | `pay invoice ` | View invoice details | | `pay renew ` | Pay invoice from balance (renewal) | ### RPC Commands Query the Solana blockchain directly from your terminal. ```bash theme={null} orbitflare rpc account
orbitflare rpc tokens orbitflare rpc balance
orbitflare rpc tx orbitflare rpc history --limit 20 orbitflare rpc program-accounts --limit 10 orbitflare rpc slot orbitflare rpc epoch orbitflare rpc blockhash orbitflare rpc stats orbitflare rpc priority-fees --account
orbitflare rpc raw '{"jsonrpc":"2.0","id":1,"method":"getSlot","params":[]}' ``` ### WebSocket Commands Subscribe to live Solana pubsub events over WebSocket. Each subscription runs until you press `Ctrl+C` and prints events as they arrive (use `--json` for machine-readable output). ```bash theme={null} # Live slot updates orbitflare ws slot # Account changes for a specific address orbitflare ws account
# Transaction logs mentioning one or more addresses (omit --mentions for all) orbitflare ws logs --mentions
--mentions # Confirmation of a single transaction signature orbitflare ws signature ``` The endpoint is derived from your configured `rpc.url`: `http://` becomes `ws://` and `https://` becomes `wss://`. To target a different host for a single command, pass `--ws-url`: ```bash theme={null} orbitflare ws slot --ws-url ws://ams.rpc.orbitflare.com ``` ### Streaming Commands Stream real-time Solana data over gRPC. Both commands take a YAML config file that defines your filters and reconnection strategy. ```bash theme={null} orbitflare jet --config stream.yml ``` Jetstream provides ultra-low-latency transaction and account streaming. ```yaml stream.yml theme={null} transactions: pump_trades: account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" accounts: my_wallet: account: - "YOUR_WALLET_ADDRESS" reconnect: initial_delay_ms: 100 max_delay_ms: 30000 multiplier: 2.0 max_retries: 0 # 0 = unlimited ``` ```bash theme={null} orbitflare grpc --config grpc-stream.yml ``` Yellowstone provides the complete Geyser plugin data stream including slots and block metadata. ```yaml grpc-stream.yml theme={null} transactions: jupiter_swaps: account_include: - "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" vote: false failed: false slots: all_slots: filter_by_commitment: true commitment: confirmed reconnect: initial_delay_ms: 100 max_delay_ms: 30000 multiplier: 2.0 max_retries: 0 ``` Both streaming commands support automatic endpoint failover and exponential backoff reconnection. Environment variables can be used in config files with `${VAR}` syntax. ### Template Commands Scaffold projects from production-ready starter templates. ```bash theme={null} # List all templates orbitflare template --list # Filter by language orbitflare template --list --filter rust # View template details orbitflare template --view solana-copy-trader # Install a template orbitflare template --install solana-copy-trader --dir ./my-bot ``` Templates are fetched from the [orbitflare/templates](https://github.com/orbitflare/templates) repository and cached locally for offline use. ### Plan Commands Browse and compare available RPC plans. Plan data is cached locally for 6 hours. ```bash theme={null} # List all plans (sorted by price) orbitflare plan list # View plan details orbitflare plan view # Compare all plans side by side orbitflare plan compare --all # Compare specific plans orbitflare plan compare dev growth ``` ### Payment Commands Manage your account balance, purchase plans, and handle invoices. Only `topup` requires a Solana wallet — everything else uses your account balance. ```bash theme={null} # Check account balance orbitflare pay check-balance # Top up balance with USDC (on-chain) orbitflare pay topup --wallet ~/.config/solana/id.json # Top-up / credit history orbitflare pay history # Purchase a plan from balance orbitflare pay purchase --coupon # View orders orbitflare pay order --history orbitflare pay order # View invoices orbitflare pay invoice --list orbitflare pay invoice # Pay an invoice from balance (renewal) orbitflare pay renew ``` The `topup` command defaults to `~/.config/solana/id.json` for the wallet keypair. Use `--wallet` to specify a different path. Billing periods: `monthly`, `quarterly`, `semi-annual`, `annual`. The `purchase` command runs a dry-run validation first, showing you the pricing breakdown and whether your balance is sufficient before confirming. ### Dashboard Launch an interactive TUI for managing your OrbitFlare account without leaving the terminal. ```bash theme={null} orbitflare dashboard ``` The dashboard provides full keyboard-driven access to your profile, services, API keys, and invoices. | Key | Action | | - | - | | `q` | Quit | | `Esc` | Back | | `Tab` | Switch tab / focus | | `↑↓` / `jk` | Navigate | | `Enter` | Select / expand / toggle | | `t` / `T` | Cycle theme | | `[` | Toggle sidebar | | `R` | Refresh data | | Key | Tab | Action | | - | - | - | | `m` | Details | Toggle auth mode (IP / API Key) | | `w` | Details | Toggle auto-renewal | | `r` | Details | Regenerate license key | | `n` | IPs / Domains | Switch Mainnet / Devnet | | `a` | IPs / Domains | Add IP or domain | | `d` | IPs / Domains | Remove IP or domain | | Key | Action | | - | - | | `c` | Create new API key | | `e` | Toggle enabled / disabled | | `r` | Regenerate key | | `x` | Delete key | | `a` | Add IP (when expanded) | | `d` | Remove IP (when expanded) | | Key | Section | Action | | - | - | - | | `p` | Invoices | Pay with account balance | | `e` | Profile | Edit name and payment wallet | ## JSON Output All commands support `--json` for machine-readable output, making the CLI easy to integrate into scripts and automation pipelines. ```bash theme={null} orbitflare rpc slot --json orbitflare rpc balance Gh9ZwEm... --json orbitflare auth status --json ``` Example output: ```json theme={null} { "confirmed": 312849571, "finalized": 312849539 } ``` ## Configuration Configuration is stored in `~/.orbitflare/config.yml`. Manage it with the `config` command: ```bash theme={null} # View current config orbitflare config show # Set network orbitflare config set network devnet # Set RPC endpoint orbitflare config set rpc.url http://ams.rpc.orbitflare.com # Set gRPC and Jetstream endpoints orbitflare config set grpc.url http://ams.rpc.orbitflare.com:10000 orbitflare config set jetstream.url http://ams.jetstream.orbitflare.com # Set commitment level orbitflare config set commitment finalized # Change TUI theme orbitflare config set theme dracula # Set template repository orbitflare config set templates.repo https://github.com/your-org/templates # Reset to defaults orbitflare config reset ``` Devnet endpoints are preconfigured by default: * **RPC:** `http://devnet.rpc.orbitflare.com` (fallback: `https://api.devnet.solana.com`) * **gRPC:** `http://devnet.rpc.orbitflare.com:10000` Use `--network devnet` to switch at runtime. ### File Locations ```bash theme={null} ~/.orbitflare/ ├── config.yml # Main configuration ├── templates_cache.json # Template registry cache └── cache/ └── rpc-plans.json # Plans cache (6-hour TTL) ``` API keys, tokens, and license keys are stored in your **OS Keychain**, not on disk. ## Global Flags These flags work with every command and override the corresponding config value for that invocation. | Flag | Description | | - | - | | `--json` | Output raw JSON | | `--raw` | Minimal output, no formatting | | `--network ` | Override network (`mainnet` / `devnet` / `testnet`) | | `--commitment ` | Override commitment (`processed` / `confirmed` / `finalized`) | | `--rpc-url ` | Override RPC endpoint | | `--grpc-url ` | Override gRPC endpoint | | `--ws-url ` | Override WebSocket endpoint (default: derived from RPC URL) | | `--fallback-url ` | Fallback URLs (repeatable) | | `--profile ` | Use a specific auth profile | | `--quiet` | Suppress non-essential output | | `--no-color` | Disable colored output | ## Full Workflow Example A complete walkthrough from installation to querying: ```bash theme={null} # 1. Install the CLI cargo install orbitflare # 2. Authenticate orbitflare auth login --x-orbit-key YOUR_API_KEY # 3. Configure endpoints (from your dashboard) orbitflare config set rpc.url ORBITFLARE_RPC_URL orbitflare config set grpc.url ORBITFLARE_GRPC_URL # 4. Verify connectivity orbitflare ping # 5. Query the blockchain orbitflare rpc slot orbitflare rpc balance YOUR_WALLET_ADDRESS # 6. Scaffold a project orbitflare template --install solana-copy-trader --dir ./my-bot # 7. Stream real-time data orbitflare jet --config stream.yml ``` ## What's Next? Explore the full Solana RPC method reference. Learn about real-time gRPC data streaming. Complete Geyser plugin data via Yellowstone. Browse production-ready project templates. ## Support Join the community for help and discussions. Report issues or contribute to the CLI. # OrbitFlare Jetstream Source: https://docs.orbitflare.com/data-streaming/jetstream High-performance gRPC streaming for Solana — enrichment, slot events, and dynamic filters Jetstream is OrbitFlare's low-latency stream of Solana transactions, with **opt-in per-transaction enrichment**, **slot lifecycle events**, and **dynamic filters** you can change without reconnecting — so you can build realtime apps (alerts, trading, analytics, monitoring) without running your own indexing pipeline. ## Overview OrbitFlare Jetstream is a high-performance gRPC service for real-time streaming of Solana transactions. It delivers a minimal-latency feed over a rich, flexible protocol — letting you adjust your subscription on the fly and receive pre-computed transaction metadata so your client does less work. ## Highlights * **Dynamic filters** — add and remove filters while the stream is open, each with a client-chosen `filter_id` that is echoed on every matching transaction. No reconnect required, and each change is acknowledged with a `FilterResult`. * **Opt-in enrichment** — request `fee_payer`, `writable_accounts`, `program_ids`, `compute_unit_price` (the compute-unit price, micro-lamports/CU — not the total fee), `compute_limit`, `tx_size`, and resolved address-lookup-table addresses directly on each transaction, instead of re-deriving them client-side. * **Slot lifecycle streaming** — a dedicated `SubscribeSlots` stream emits `ALIVE` / `COMPLETE` / `DEAD` events per slot, with the current leader and parent slot. * **Built-in liveness** — every message carries a monotonic `sequence` number for gap detection, and the server emits a `Heartbeat` on an idle transaction stream. * **Enrichment on demand** — a transaction is one flat message with the core fields by default; switch on enrichment per subscription to add the extra fields whenever you want them. ## Authentication Supply your API key in the `x-token` gRPC metadata header, or use IP-whitelist authentication. See [Authentication](/authentication) for setup details. ## Connecting 1. Pick the [region endpoint](/data-streaming/jetstream-reference#available-endpoints) closest to your infrastructure. 2. Generate a client from the `jetstream.v2` protobuf definition — see [Code Generation](/data-streaming/jetstream-reference#code-generation) in the reference. 3. Authenticate with your `x-token` API key (or from a whitelisted IP) and open a stream. ## How streaming works `SubscribeTransactions` is a **bidirectional** stream — you manage your subscription with the same connection you receive data on: 1. Open the stream and send an `AddFilters` request containing one or more filters. Each filter needs at least one of `account_include` / `account_exclude` / `account_required`. (Empty match-everything filters are rejected.) `account_required` is **any-of** — a transaction matches if it references at least one of the listed accounts, not all of them. 2. The server replies with a `FilterResult` per filter (`accepted`, or a `rejection_reason`). 3. Matching transactions arrive as `FilteredTransaction` messages, each listing the `filter_id` values it matched. 4. Send `AddFilters` / `RemoveFilters` at any time to change what you receive — no reconnect. 5. On an idle transaction stream the server sends `Heartbeat` messages; use them (and `Ping`/`Pong`) to confirm liveness. 6. Track the `sequence` number to detect any gap, and reconnect (re-sending your filters) if the stream closes. To receive enriched transactions, set `include_enrichment: true` on a filter. Enrichment is **subscription-wide**: if any one of your active filters sets `include_enrichment`, every transaction the session receives is enriched. Turn it on per subscription whenever you want the extra fields — it adds **no latency** to the stream; the only difference is a modestly larger message. ## Slot events Open `SubscribeSlots` (a separate, server-streaming RPC, no parameters) to receive a `SlotEvent` for each slot transition: * `ALIVE` — the first shred of the slot was received. * `COMPLETE` — the slot's last shred was received. * `DEAD` — the slot was skipped, or was superseded by later slots without completing. Each event includes the `current_leader` (32-byte pubkey, when the current leader is available) and the `parent_slot`. ## Migrating from v1 The earlier Jetstream v1 protocol is **deprecated** but still available for existing integrations. If you are on v1, both services run side by side on the same endpoints, so migration is a client-side protocol change with no network changes. See [Migrating from v1](/data-streaming/jetstream-v2-vs-v1) for a field-by-field mapping, or the [deprecated v1 overview](/data-streaming/jetstream-v1). ## Best Practices 1. **Filtering** — always set at least one account constraint, and use a stable unique `filter_id` per filter so you can correlate matches and remove filters later. 2. **Enrichment** — keep it off unless you need the extra fields; it applies to the whole subscription. 3. **Reconnection** — track `sequence` for gaps and reconnect automatically, re-sending your filters. 4. **Liveness** — rely on `Heartbeat` / `Ping` to detect a stalled connection. ## See Also The field-by-field v1-to-Jetstream mapping and what each capability saves you. Full Protocol Buffer specification, endpoints, and code generation. The deprecated v1 overview, client examples, and filtering guide. Full-fidelity Geyser-based streaming with inner instructions and complete metadata. ## Support For technical support or questions about Jetstream, please contact our support team or join our [Discord community](https://discord.gg/orbitflare). # Dedicated Nodes Source: https://docs.orbitflare.com/dedicated-nodes Isolated Solana dedicated hardware with unlimited RPS and TPS, private RPC and streaming endpoints, priority support, and enterprise SLA—no shared-cluster noisy neighbors on OrbitFlare. No rate limits on your dedicated infrastructure Enterprise SLA-backed availability guarantee Global deployment across major data centers Dedicated engineering support around the clock ## Overview OrbitFlare Dedicated Nodes provide 100% isolated Solana infrastructure reserved exclusively for your workloads. Unlike shared RPC services, dedicated nodes guarantee that your performance is never impacted by other users. Every node runs on bare-metal servers allocated solely to your organization. There are no noisy neighbors, no shared CPU cycles, and no contention for memory or network bandwidth. Tailor your node to your exact requirements -- choose your Solana client, configure RPC methods, adjust cache sizes, enable specific APIs, and fine-tune performance parameters to match your application's needs. Your node operates in a completely isolated environment with dedicated network interfaces, storage volumes, and compute resources. This ensures consistent, predictable performance at all times. ## Pricing **From \$2,500/mo** * Unlimited RPS and TPS * Dedicated bare-metal hardware * Choice of Solana client * Custom configuration * Priority engineering support * 99.99% uptime SLA **From \$1,800/mo** * Dedicated streaming resources * Unlimited real-time data * Optimized for high-frequency trading * Low-latency gRPC streaming * Priority engineering support * 99.99% uptime SLA **Contact Sales** * Multiple dedicated nodes * Custom SLA agreements * On-premise deployment options * Dedicated account manager * Custom networking and peering * Volume discounts available ## Available Solana Clients Choose the Solana validator client that best fits your use case. The standard Solana validator client maintained by Solana Labs. Battle-tested and widely deployed across the network, this is the default choice for most workloads. * Broadest RPC method support * Most extensively documented * Default choice for general-purpose applications A modified validator client with integrated MEV capabilities. Jito Labs client includes bundle support, Shredstream, and tip distribution -- ideal for trading applications and MEV strategies. * Native bundle submission and simulation * Shredstream for faster block propagation * MEV tip distribution * Optimized for trading and DeFi workloads A high-performance validator client developed by Jump Crypto. Frankendancer combines components from the Firedancer project with the standard Solana runtime for significantly improved throughput and lower latency. * Higher transaction throughput * Lower end-to-end latency * Optimized networking stack * Ideal for performance-critical applications ## Dedicated vs Shared Comparison | Feature | Dedicated Node | Shared RPC | | - | - | - | | **RPS** | Unlimited | Rate-limited per plan | | **TPS** | Unlimited | Shared capacity | | **Hardware** | Bare-metal, isolated | Multi-tenant servers | | **Shredstream** | Available (Jito client) | Not available | | **Uptime SLA** | 99.99% | 99.9% | | **Client Selection** | Solana Labs, Jito, Frankendancer | Solana Labs only | | **Custom Config** | Full control | Not available | | **Support** | Priority 24/7 engineering | Standard support | ## Deployment Timeline Standard dedicated nodes are provisioned and fully operational within **24-48 hours** of order confirmation. This includes hardware allocation, client installation, configuration, and validation. Custom configurations, multi-node clusters, and enterprise deployments require **3-5 business days**. This additional time accounts for custom networking, specialized hardware requirements, and tailored configuration tuning. ## Trading APIs on Dedicated Nodes Dedicated nodes support OrbitFlare's full suite of trading APIs for maximum performance and lowest latency. Access [Metis Swap](/trading-apis/metis-swap) for optimized token swaps and [Jito Bundle Simulation](/trading-apis/jito-bundle-simulation) for MEV bundle testing -- both running directly on your dedicated infrastructure. ## Get Started Ready to deploy dedicated Solana infrastructure? Reach out to our team on Discord to discuss your requirements, get a custom quote, or schedule a technical consultation. # FAQs Source: https://docs.orbitflare.com/faqs Frequently asked questions about OrbitFlare products and services ## General OrbitFlare is a high-performance Solana infrastructure provider offering RPC nodes, gRPC streaming, dedicated nodes, and more. All infrastructure is Shredstream-optimized from top turbine validators, delivering the fastest data propagation and lowest latency for your applications. OrbitFlare supports **Solana** (mainnet and devnet), **BNB Chain (BSC)**, [**Polygon**](/polygon), and **Robinhood Chain**. OrbitFlare operates across **11 global regions** spanning the United States, Europe, and Asia Pacific. All regions are available on every plan. See the [Global Infrastructure](/infrastructure) page for the full list of locations. ## Billing and Plans Every OrbitFlare plan includes: * Devnet endpoint * WebSocket support * Access to all 11 global regions * Historical data (full archive since genesis) * Shredstream optimization from top turbine validators * 99.99% uptime SLA Yes, you can upgrade or downgrade your plan at any time from the dashboard. Upgrades take effect immediately. Downgrades apply at the start of your next billing cycle. OrbitFlare accepts **credit cards** and **cryptocurrency** (USDC on Solana). Enterprise invoicing is also available for larger accounts. OrbitFlare offers a free tier with 10 RPS and no time limit. You can use it indefinitely to evaluate the platform before upgrading to a paid plan. No. OrbitFlare provides unlimited requests within your RPS tier. There are no overage charges and no surprise bills. OrbitFlare offers three billing periods: * **Monthly** — Standard pricing * **Quarterly** — Save 5% * **Yearly** — Save 15% ## Technical **Jetstream** is designed for ultra-low latency and is ideal for trading applications where speed is critical. **Yellowstone** provides complete data including inner instructions, making it the better choice for indexing, analytics, and applications that need full transaction detail. Yes. WebSocket support is included on all plans, enabling real-time subscriptions for account changes, transaction confirmations, and other on-chain events. Dedicated nodes can be provisioned with the following Solana clients: * **Solana Labs** — The reference client * **Jito Labs** — MEV-optimized client * **Frankendancer** — High-performance validator client Shredstream is an optimization sourced from top turbine validators that ensures the fastest possible data propagation across the network. It provides a competitive advantage for latency-sensitive applications such as trading bots, arbitrage systems, and real-time data feeds. Yes. OrbitFlare provides full archive data since Solana genesis (March 2020). Historical data access is included with all RPC plans at no extra cost. ## Support You can reach OrbitFlare support through: * **Discord** — [discord.gg/orbitflare](https://discord.gg/orbitflare) * **Telegram** — [t.me/orbitflare](https://t.me/orbitflare) * **Email** — [support@orbitflare.com](mailto:support@orbitflare.com) Response times depend on your plan: * **Free** — Community support * **Developer** — Chat support * **Growth and above** — 24/7 support with under 1 hour response time * **Dedicated / NFT Pass** — Priority and VIP support * **Enterprise** — Dedicated account team Yes. OrbitFlare offers dedicated RPC nodes starting at **2500 USD** per month and dedicated gRPC nodes starting at **1800 USD** per month. Dedicated infrastructure provides isolated resources, custom configurations, and priority support. # getTransactionsForAddress Source: https://docs.orbitflare.com/fetching-data/getTransactionsForAddress Batch-fetch full Solana transaction history for any address in one JSON-RPC call—pagination, filters, and detail levels replace getSignaturesForAddress plus many getTransaction requests. ## Overview `getTransactionsForAddress` combines transaction signature lookup and full transaction retrieval into one efficient call. Instead of making separate requests to [getSignaturesForAddress](/rpc/methods/getSignaturesForAddress) and then [getTransaction](/rpc/methods/getTransaction) for each signature, you get everything in a single request. This method supports: * Bidirectional sorting (oldest-first or newest-first) * Time and slot-based filtering via range operators * Transaction status filtering (succeeded/failed) * Token account filtering (`none`, `balanceChanged`, `all`) * Cursor-based pagination via `paginationToken` * Four detail levels: `signatures`, `none`, `accounts`, `full` ## Why Use This Method ### The N+1 Problem With standard Solana RPC methods, fetching transaction history requires multiple calls: 1. Call `getSignaturesForAddress` to get a list of transaction signatures 2. Call `getTransaction` for **each** signature to get full transaction data For a wallet with 100 transactions, that's 101 API calls. For 1,000 transactions, it's 1,001 calls. This creates latency, complexity, and higher costs. `getTransactionsForAddress` solves this by returning full transaction data in a single call—up to 100 complete transactions or 1,000 signatures per request. ### Additional Capabilities Beyond efficiency, this method provides features not available in standard RPC: * **Bidirectional sorting**: Query oldest-first (`asc`) to trace an address from its first transaction * **Time-based filtering**: Retrieve transactions within a specific date range using `filters.blockTime` with range operators * **Slot-based filtering**: Query transactions from specific block ranges using `filters.slot` with range operators * **Status filtering**: Get only successful or only failed transactions * **Token account inclusion**: Capture transactions from associated token accounts ## Token Account Support Standard `getSignaturesForAddress` only returns transactions that directly reference the queried address. This misses an important category: **token transfers**. When tokens are sent to a wallet, the transaction references the wallet's Associated Token Account (ATA), not the wallet address itself. This means standard methods give you an incomplete picture. `getTransactionsForAddress` with `tokenAccounts: "all"` automatically includes transactions from all token accounts owned by the address, giving you complete transaction history. ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionsForAddress", "params": [ "WALLET_ADDRESS", { "filters": { "tokenAccounts": "all" } } ] }' ``` Set `tokenAccounts: "none"` (the default) if you only want transactions that directly reference the address. ## Parameters The Solana address to query (base-58 encoded public key, 32 bytes) Level of detail to return: * `"signatures"` - Signature info with memo and status (up to 1000 results). **Default.** * `"none"` - Same fields as `signatures` (up to 1000 results) * `"accounts"` - Signature info plus account keys (up to 100 results) * `"full"` - Complete transaction data (up to 100 results) Default: `"signatures"` Sort direction: * `"desc"` - Newest transactions first (default) * `"asc"` - Oldest transactions first Maximum number of results to return. * For `transactionDetails: "signatures"` or `"none"`: max 1000 * For `transactionDetails: "full"` or `"accounts"`: max 100 Default: 100 Pagination cursor from a previous response. Format: `"slot:txIndex"` (e.g. `"387936002:541"`). The commitment level for the query: * `"finalized"` - Query finalized blocks only (default) * `"confirmed"` - Query confirmed blocks Note: `"processed"` is not supported and returns an error. Encoding format for transaction data (only applies when `transactionDetails: "full"`): * `"json"` (default) * `"jsonParsed"` * `"base64"` * `"base58"` Maximum transaction version to return. In `"full"` mode this defaults to `0` (includes versioned transactions). Set explicitly if you need legacy-only behavior. The minimum slot that the request can be evaluated at. Filter by token account association: * `"none"` - Only transactions that directly reference the address. **Default.** * `"balanceChanged"` - Include token account transactions where balance changed (recommended for clean wallet history) * `"all"` - Include all token account transactions Default: `"none"` Filter by Unix timestamp using range operators: `gte`, `gt`, `lte`, `lt`, `eq` Example: `{ "blockTime": { "gte": 1704067200, "lte": 1706745600 } }` Filter by slot number using range operators: `gte`, `gt`, `lte`, `lt`, `eq` Example: `{ "slot": { "gte": 387000000, "lte": 388000000 } }` Filter by transaction signature (base-58, 64 bytes) using range operators: `gte`, `gt`, `lte`, `lt` Example: `{ "signature": { "lt": "5abc..." } }` Filter by transaction status: * `"succeeded"` - Only successful transactions * `"failed"` - Only failed transactions * Omit or use any other value for no status filter ## Response The response `result` is an object containing a `data` array and an optional `paginationToken`: ```json theme={null} { "jsonrpc": "2.0", "id": 1, "result": { "data": [ ... ], "paginationToken": "387936002:541" } } ``` The `paginationToken` is only present when more results are available. Pass it in the next request to fetch the next page. ### With `transactionDetails: "signatures"` or `"none"` Transaction signature (base-58 encoded) Slot in which the transaction was processed Zero-based index of the transaction within the block Unix timestamp of the block Error object if transaction failed, null otherwise Memo text if the transaction includes a Memo program instruction Echoes the requested commitment level (`"finalized"` or `"confirmed"`) ### With `transactionDetails: "accounts"` Same fields as signatures mode, plus: Array of base-58 public keys involved in the transaction, including addresses loaded from lookup tables. ### With `transactionDetails: "full"` Slot in which the transaction was processed Zero-based index of the transaction within the block. This field is unique to `getTransactionsForAddress`. Unix timestamp of the block Full transaction data including message, signatures, and metadata (format depends on `encoding`) Transaction metadata including fee, balances, and logs ## Code Examples ### Basic Request Get the most recent signatures for an address (default mode): ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionsForAddress", "params": [ "ADDRESS_HERE" ] }' ``` ### Get Full Transactions Retrieve up to 100 full transactions: ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionsForAddress", "params": [ "ADDRESS_HERE", { "transactionDetails": "full", "limit": 100 } ] }' ``` ### Get Signatures Only Retrieve up to 1000 signatures without full transaction data: ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionsForAddress", "params": [ "ADDRESS_HERE", { "transactionDetails": "signatures", "limit": 1000 } ] }' ``` ### Oldest-First with Time Filter Get transactions from a specific time range, sorted oldest first: ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionsForAddress", "params": [ "ADDRESS_HERE", { "sortOrder": "asc", "filters": { "blockTime": { "gte": 1704067200, "lte": 1706745600 } } } ] }' ``` ### Successful Transactions Only Filter to only return transactions that succeeded: ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionsForAddress", "params": [ "ADDRESS_HERE", { "filters": { "status": "succeeded" } } ] }' ``` ### Paginated Request Use cursor-based pagination to fetch more results: ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionsForAddress", "params": [ "ADDRESS_HERE", { "limit": 100, "paginationToken": "387936002:541" } ] }' ``` ## TypeScript Example ```typescript theme={null} interface GetTransactionsOptions { transactionDetails?: 'full' | 'signatures' | 'accounts' | 'none'; limit?: number; sortOrder?: 'asc' | 'desc'; paginationToken?: string; commitment?: 'finalized' | 'confirmed'; encoding?: 'json' | 'jsonParsed' | 'base64' | 'base58'; maxSupportedTransactionVersion?: number; minContextSlot?: number; filters?: { tokenAccounts?: 'none' | 'balanceChanged' | 'all'; blockTime?: { gte?: number; gt?: number; lte?: number; lt?: number; eq?: number }; slot?: { gte?: number; gt?: number; lte?: number; lt?: number; eq?: number }; signature?: { gte?: string; gt?: string; lte?: string; lt?: string }; status?: 'succeeded' | 'failed'; }; } interface GtfaResult { data: any[]; paginationToken?: string; } async function getTransactionsForAddress( address: string, options: GetTransactionsOptions = {} ): Promise { const response = await fetch('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'getTransactionsForAddress', params: [address, options], }), }); const data = await response.json(); return data.result; } // Get recent signatures (default mode) const recent = await getTransactionsForAddress('ADDRESS_HERE'); console.log(recent.data); // Get oldest transactions first with time filter const historical = await getTransactionsForAddress('ADDRESS_HERE', { sortOrder: 'asc', filters: { blockTime: { gte: Math.floor(Date.now() / 1000) - 86400 * 30, }, }, }); // Paginate through all signatures async function getAllSignatures(address: string) { const allSignatures: any[] = []; let paginationToken: string | undefined; while (true) { const result = await getTransactionsForAddress(address, { transactionDetails: 'signatures', limit: 1000, paginationToken, }); if (!result.data || result.data.length === 0) break; allSignatures.push(...result.data); paginationToken = result.paginationToken; if (!paginationToken) break; } return allSignatures; } ``` ## Use Cases ### Token Launch Analysis Find the earliest transactions for a token mint to identify first minters and early holders. Use `sortOrder: "asc"` to start from the beginning: ```typescript theme={null} const earlyActivity = await getTransactionsForAddress(tokenMintAddress, { sortOrder: 'asc', limit: 100, transactionDetails: 'full', filters: { tokenAccounts: 'all', }, }); ``` ### Wallet Funding History Trace where a wallet's funds originated by querying its complete transaction history: ```typescript theme={null} const fundingHistory = await getTransactionsForAddress(walletAddress, { sortOrder: 'asc', transactionDetails: 'full', filters: { tokenAccounts: 'all', }, }); const firstTransactions = fundingHistory.data.slice(0, 10); ``` ### Failed Transaction Analysis Debug transaction failures by filtering to only failed transactions: ```typescript theme={null} const result = await getTransactionsForAddress(walletAddress, { filters: { status: 'failed', }, transactionDetails: 'full', }); result.data.forEach(tx => { console.log(tx.meta?.err, tx.meta?.logMessages); }); ``` ### Build a Wallet Transaction Feed Create a paginated transaction history for a wallet UI: ```typescript theme={null} async function getWalletFeed(wallet: string, cursor?: string) { return getTransactionsForAddress(wallet, { limit: 20, paginationToken: cursor, transactionDetails: 'full', filters: { tokenAccounts: 'balanceChanged', }, }); } ``` ### Trading Pattern Analysis Filter successful trades within a specific time window: ```typescript theme={null} const trades = await getTransactionsForAddress(traderWallet, { filters: { status: 'succeeded', blockTime: { gte: startOfMonth, lt: endOfMonth, }, }, transactionDetails: 'full', }); ``` ### Compliance Audit Build a complete transaction history for regulatory compliance: ```typescript theme={null} async function auditAddress(address: string) { const allTxs = await getAllSignatures(address); return { totalTransactions: allTxs.length, successful: allTxs.filter(tx => !tx.err).length, failed: allTxs.filter(tx => tx.err).length, firstTransaction: allTxs[allTxs.length - 1], lastTransaction: allTxs[0], }; } ``` ## Pagination For addresses with many transactions, use the `paginationToken` from the response to fetch the next page. The token is a string in the format `"slot:txIndex"` that tells the server where to continue from. ### Fetching Newer Transactions (Default) With `sortOrder: "desc"` (default), transactions are returned newest-first. The `paginationToken` continues to older transactions: ```typescript theme={null} const page1 = await getTransactionsForAddress(address, { limit: 100 }); if (page1.paginationToken) { const page2 = await getTransactionsForAddress(address, { limit: 100, paginationToken: page1.paginationToken, }); } ``` ### Fetching Older Transactions First With `sortOrder: "asc"`, transactions are returned oldest-first. The `paginationToken` continues to newer transactions: ```typescript theme={null} const page1 = await getTransactionsForAddress(address, { sortOrder: 'asc', limit: 100, }); if (page1.paginationToken) { const page2 = await getTransactionsForAddress(address, { sortOrder: 'asc', limit: 100, paginationToken: page1.paginationToken, }); } ``` ### Complete Pagination Example Fetch all signatures for an address: ```typescript theme={null} async function getAllSignatures(address: string) { const allSignatures: any[] = []; let paginationToken: string | undefined; while (true) { const result = await getTransactionsForAddress(address, { transactionDetails: 'signatures', limit: 1000, paginationToken, }); if (!result.data || result.data.length === 0) break; allSignatures.push(...result.data); paginationToken = result.paginationToken; if (!paginationToken) break; await new Promise(resolve => setTimeout(resolve, 100)); } return allSignatures; } ``` ## Best Practices 1. **Use `signatures` mode for counting**: When you only need to count or list transactions, use `transactionDetails: "signatures"` (the default) for better performance and higher limits (up to 1000). 2. **Filter early**: Apply `blockTime`, `slot`, or `status` filters to reduce the amount of data scanned and improve response times. 3. **Implement pagination**: For addresses with many transactions, always paginate using the `paginationToken` from the response. 4. **Cache results**: Transaction data is immutable once finalized. Cache historical results to avoid redundant requests. 5. **Handle rate limits**: Implement exponential backoff when making many sequential requests. ## Common Errors | Code | Message | Solution | | - | - | - | | -32602 | Invalid params | Verify address format (base-58, 32 bytes) and option values | | -32602 | Invalid paginationToken format | Ensure token is in `"slot:txIndex"` format from a previous response | | -32602 | commitment must be 'finalized' or 'confirmed' | Do not use `"processed"` commitment | | -32603 | Internal error | Database or encoding failure; retry the request | ## Comparison with Standard Methods | Feature | [getSignaturesForAddress](/rpc/methods/getSignaturesForAddress) + [getTransaction](/rpc/methods/getTransaction) | getTransactionsForAddress | | - | - | - | | API calls | Multiple (1 + N) | Single | | Sorting | Newest first only | Bidirectional | | Time filtering | Not supported | Supported via `filters.blockTime` | | Slot filtering | Not supported | Supported via `filters.slot` | | Status filtering | Not supported | Supported via `filters.status` | | Token accounts | Manual ATA queries | Built-in via `filters.tokenAccounts` | | Max signatures | 1000 per call | 1000 per call | | Max full transactions | Limited by calls | 100 per call | | Pagination | Signature-based `before`/`until` | Cursor-based `paginationToken` | # Historical Data Source: https://docs.orbitflare.com/fetching-data/historical-data Access complete Solana blockchain history from genesis ## Overview OrbitFlare runs full archive nodes that retain every block, transaction, and account state since Solana launched. Standard RPC providers typically prune older data to save storage, keeping only recent epochs. With OrbitFlare, you can query any slot in Solana's history. ## Use Cases Historical blockchain data enables several important workflows: * **Event recovery**: Backfill missed transactions after indexer downtime or webhook failures * **Compliance**: Retrieve transaction records for audits and regulatory requirements * **Research**: Analyze on-chain trends, token distributions, and protocol metrics over time * **Explorer apps**: Build block explorers and dashboards that display complete history ## Relevant RPC Methods These methods support historical queries on OrbitFlare's archive nodes: ### Block Methods | Method | Description | | - | - | | [getBlock](/rpc/methods/getBlock) | Retrieve full block data for any slot | | [getBlocks](/rpc/methods/getBlocks) | Get a list of confirmed blocks between two slots | | [getBlocksWithLimit](/rpc/methods/getBlocksWithLimit) | Get confirmed blocks starting from a slot with a limit | | [getBlockTime](/rpc/methods/getBlockTime) | Get the estimated Unix timestamp for a block | | [getBlockHeight](/rpc/methods/getBlockHeight) | Get the current block height | | [getFirstAvailableBlock](/rpc/methods/getFirstAvailableBlock) | Get the lowest slot with block data available | ### Transaction Methods | Method | Description | | - | - | | [getTransaction](/rpc/methods/getTransaction) | Retrieve a confirmed transaction by signature | | [getSignaturesForAddress](/rpc/methods/getSignaturesForAddress) | Get transaction signatures for an address | | [getSignatureStatuses](/rpc/methods/getSignatureStatuses) | Get the status of transaction signatures | ### Account Methods | Method | Description | | - | - | | [getAccountInfo](/rpc/methods/getAccountInfo) | Get account data (current state only) | | [getMultipleAccounts](/rpc/methods/getMultipleAccounts) | Get data for multiple accounts in one call | | [getProgramAccounts](/rpc/methods/getProgramAccounts) | Get all accounts owned by a program | ### Slot Methods | Method | Description | | - | - | | [getSlot](/rpc/methods/getSlot) | Get the current slot | | [minimumLedgerSlot](/rpc/methods/minimumLedgerSlot) | Get the lowest slot the node has data for | ## Examples ### Retrieve a Block from History Fetch block data for slot 100,000,000 using [getBlock](/rpc/methods/getBlock): ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlock", "params": [ 100000000, { "encoding": "json", "transactionDetails": "full", "rewards": false, "maxSupportedTransactionVersion": 0 } ] }' ``` ### Get Block Timestamp Find when a specific block was produced using [getBlockTime](/rpc/methods/getBlockTime): ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockTime", "params": [100000000] }' ``` ### List Blocks in a Range Get all confirmed blocks between two slots using [getBlocks](/rpc/methods/getBlocks): ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlocks", "params": [100000000, 100000100] }' ``` ### Fetch a Transaction Retrieve transaction details by signature using [getTransaction](/rpc/methods/getTransaction): ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ "5wHu1qwD7q5menP8rgbzknAHruqg7rq1BwLNjmKnMwVH2VNhVvPLT6jGfKNLwKUTG4Xn2vRvDz5Q6Z8zCLN5gLp1", { "encoding": "json", "maxSupportedTransactionVersion": 0 } ] }' ``` ### Get Transaction History for an Address Retrieve signatures for an address using [getSignaturesForAddress](/rpc/methods/getSignaturesForAddress): ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSignaturesForAddress", "params": [ "Vote111111111111111111111111111111111111111", {"limit": 100} ] }' ``` ## TypeScript Examples ### Fetch Historical Block Data ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY'); async function getHistoricalBlock(slot: number) { const block = await connection.getBlock(slot, { maxSupportedTransactionVersion: 0, }); if (block) { console.log(`Block ${slot}:`); console.log(` Transactions: ${block.transactions.length}`); console.log(` Block time: ${block.blockTime}`); console.log(` Parent slot: ${block.parentSlot}`); } return block; } // Fetch a block from early 2023 const block = await getHistoricalBlock(175000000); ``` ### Build Complete Transaction History Combine [getSignaturesForAddress](/rpc/methods/getSignaturesForAddress) with [getTransaction](/rpc/methods/getTransaction) to build a full history: ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY'); async function getFullTransactionHistory(address: string) { const pubkey = new PublicKey(address); const allTransactions = []; let before: string | undefined; // Paginate through all signatures while (true) { const signatures = await connection.getSignaturesForAddress(pubkey, { limit: 1000, before, }); if (signatures.length === 0) break; // Fetch full transaction data in batches for (let i = 0; i < signatures.length; i += 10) { const batch = signatures.slice(i, i + 10); const txs = await Promise.all( batch.map(sig => connection.getTransaction(sig.signature, { maxSupportedTransactionVersion: 0, }) ) ); allTransactions.push(...txs.filter(tx => tx !== null)); } before = signatures[signatures.length - 1].signature; } return allTransactions; } ``` ### Query Blocks in a Time Range Find blocks within a specific time period: ```typescript theme={null} async function getBlocksInTimeRange(startTime: number, endTime: number) { // Get a starting slot estimate const currentSlot = await connection.getSlot(); const currentTime = Math.floor(Date.now() / 1000); // Solana produces ~2-3 blocks per second const slotsPerSecond = 2.5; const estimatedStartSlot = currentSlot - Math.floor((currentTime - startTime) * slotsPerSecond); const blocks = []; let slot = estimatedStartSlot; while (true) { const blockTime = await connection.getBlockTime(slot); if (blockTime && blockTime >= startTime && blockTime <= endTime) { const block = await connection.getBlock(slot, { maxSupportedTransactionVersion: 0, }); if (block) blocks.push({ slot, block }); } if (blockTime && blockTime > endTime) break; slot++; } return blocks; } ``` ## Performance Tips 1. **Batch your requests**: Use JSON-RPC batch requests to fetch multiple blocks or transactions in one call 2. **Paginate large queries**: The [getSignaturesForAddress](/rpc/methods/getSignaturesForAddress) method returns up to 1000 signatures per call—use the `before` parameter to paginate 3. **Parallelize carefully**: Fetch transactions in parallel batches of 10-20 to balance speed and rate limits 4. **Cache immutable data**: Historical blocks and transactions never change once confirmed—cache them locally 5. **Use `maxSupportedTransactionVersion`**: Always include this parameter to handle versioned transactions ## Limitations **Account state history**: Solana nodes store current account state, not historical snapshots. You can retrieve transactions that modified an account, but not the account's balance or data at a specific past slot. **Rate limits**: Historical queries may consume more resources. Check your plan's rate limits and implement appropriate throttling. ## Faster Transaction History For efficient address history queries, use [getTransactionsForAddress](/fetching-data/getTransactionsForAddress) which combines signature lookup and transaction retrieval into a single call with filtering and sorting options. ## Managed Backfills For teams that need large-scale historical data delivery without writing custom ETL pipelines, OrbitFlare offers a **Managed Backfills** service. Managed Backfills is an enterprise service. [Contact the team on Discord](https://discord.gg/orbitflare) or email [sales@orbitflare.com](mailto:sales@orbitflare.com) to discuss your requirements. ### What It Does OrbitFlare engineers extract and deliver structured Solana historical data directly to your storage: | Output Format | Supported Destinations | | - | - | | JSON (newline-delimited) | AWS S3, Google Cloud Storage | | Parquet | AWS S3, Google Cloud Storage | | SQL | PostgreSQL, ClickHouse | ### What Can Be Delivered * All transactions for a specific program, wallet, or token mint * Full block data for any slot range * Token transfer history (SPL Token / Token-2022) * Custom queries and data shapes based on your requirements ### SLA | Item | Detail | | - | - | | Delivery time | 3–5 business days for standard backfills | | Data coverage | Genesis (March 2020) to present | | Format | JSON, Parquet, or SQL — your choice | | Dedicated support | Assigned engineer for duration of the job | ### When to Use It Managed Backfills are best for: * Seeding a new indexer or analytics database * One-time compliance or audit data requests * Historical ML/AI training datasets * Backfilling after extended downtime For ongoing streaming of new data, use the [gRPC streaming](/data-streaming/yellowstone) endpoints instead. # Fetching Data Source: https://docs.orbitflare.com/fetching-data/index Guides for historical Solana data, wallet and program activity APIs, and bulk transaction retrieval—patterns for analytics and explorers with fewer RPC round-trips on OrbitFlare. ## Overview OrbitFlare provides powerful tools for retrieving Solana blockchain data. Whether you need to query historical transactions, analyze address activity, or build data pipelines, our infrastructure delivers fast and reliable access to the data you need. Full archive access from genesis. Query any block, transaction, or account state from Solana's entire history. Retrieve complete transaction history for any address in a single call with advanced filtering and pagination. ## Why OrbitFlare for Data Access? * **Full Archive Nodes**: Access complete blockchain history without pruning * **High Performance**: Optimized infrastructure for fast query responses * **Developer-Friendly**: Clean APIs that reduce complexity in your codebase * **Reliable**: Enterprise-grade uptime for production applications ## Coming Soon We're actively building additional data access features: * **Digital Asset Standard (DAS) API**: Comprehensive NFT and token metadata * **Enhanced Transaction Parsing**: Human-readable transaction breakdowns * **Asset and Token APIs**: Streamlined access to ownership and balance data Check back for updates or join our [Discord](https://discord.gg/orbitflare) to stay informed. # Global Infrastructure Source: https://docs.orbitflare.com/infrastructure 11 strategically distributed server locations across 3 continents Strategically distributed server locations worldwide North America, Europe, and Asia Pacific coverage Enterprise-grade reliability with automatic failover Ultra-low latency optimized for geographic proximity ## Overview OrbitFlare operates infrastructure across 11 global regions, optimized with Shredstream from top turbine validators. Endpoints are region-pinned, so you connect directly to the region closest to your deployment for the lowest possible latency. ## Server Locations ### United States (4 locations) Primary US East hub with direct peering to major exchanges and data centers. Low-latency access for financial applications and East Coast infrastructure. West Coast coverage for Pacific-facing applications and services. Central US routing with high redundancy and low-latency cross-region connectivity. ### Europe (5 locations) Central European hub with excellent connectivity across the continent. Major European internet exchange point with broad peering. Western European coverage with strong financial infrastructure ties. Northern European presence with robust transatlantic connectivity. Eastern European coverage for Baltic and Nordic region access. ### Asia Pacific (2 locations) Primary Asia Pacific hub with low-latency access across East Asia. Southeast Asian coverage serving one of the fastest-growing blockchain markets. ## Infrastructure Features Every endpoint is pinned to a region (`fra.rpc.orbitflare.com`, `ams.rpc.orbitflare.com`, ...). Pick the region closest to your infrastructure for the lowest possible latency. Automatic failover across regions ensures continuous availability. If a node or region experiences issues, traffic is seamlessly redirected to the next closest healthy node. All nodes are optimized with Shredstream from top turbine validators, ensuring the fastest possible data propagation and a competitive advantage for latency-sensitive applications. Less than 5ms average response time, optimized for geographic proximity. Our routing layer ensures requests are served by the closest available node to minimize round-trip time. ## Security OrbitFlare infrastructure is built with enterprise-grade security at every layer: * **Network Isolation** — Dedicated nodes run in isolated network environments, ensuring your traffic is never shared with other customers. * **IP Whitelisting** — Restrict access to your endpoints by specifying allowed IP addresses, adding an extra layer of protection. * **SSL Encryption** — All connections are encrypted with SSL/TLS, securing data in transit across every region. All 11 regions are available on all plans. Dedicated nodes can be deployed to specific regions based on your requirements. # OrbitFlare Pass Source: https://docs.orbitflare.com/nft-pass OrbitFlare Pass NFT unlocks dedicated RPC, gRPC streaming, and VIP support in one membership—limited 2024 supply; available on secondary markets only. Only 95 OrbitFlare Passes were ever minted in August 2024. No more will be created. They are now available on secondary markets only. ## Overview The OrbitFlare Pass is a premium NFT that provides all-access to OrbitFlare's Solana infrastructure. A single pass unlocks dedicated RPC endpoints, gRPC streaming, and VIP support — no separate subscriptions or add-ons required. Hold the pass in your wallet and you're in. Trade it when you're done. ## Key Benefits Per-endpoint rate limits designed for high-throughput applications and trading infrastructure, scaling with your access tier. Access every OrbitFlare RPC region worldwide for the lowest possible latency wherever your users are. Full access to Jetstream and Yellowstone gRPC streaming at no additional cost. Priority support with a 4-hour average response time from the infrastructure team. Be the first to test new features, endpoints, and infrastructure upgrades before public release. Join an exclusive channel with other pass holders, the engineering team, and direct feedback loops. The pass is a standard Solana NFT with real utility — buy, sell, or transfer it on any supported marketplace. ## Pricing Pass holders choose a monthly access tier per endpoint. You can switch tiers at each renewal cycle. | Tier | Cost | Rate Limits | | - | - | - | | **Pro** | \$500 USDC/month | 1000 RPS / 200 TPS | | **Elite** | \$575 USDC/month | 1250 RPS / 250 TPS | | **Mega** | \$650 USDC/month | 1500 RPS / 300 TPS | | **Freeze Option** | \$250 USDC/month | No infrastructure access | The freeze option keeps your pass active and your slot reserved without paying the full access fee. You won't have access to infrastructure while frozen, but you can reactivate at any time without losing your position. ## Benefits Comparison | Feature | Standard Plans | NFT Pass | | - | - | - | | **RPC Access** | Shared endpoints | Dedicated endpoints | | **Rate Limits** | Up to 600 RPS / 200 TPS (Pro plan) | Up to 1500 RPS / 300 TPS per endpoint (Mega tier) | | **gRPC Streaming** | Paid add-on (included on Pro) | Included (Jetstream & Yellowstone) on all tiers | | **Support Response** | Community to 24/7 priority, by plan | 4-hour average (VIP) | | **Global Regions** | All 11 regions | All 11 regions | | **Community** | Public Discord | Private Discord channel | ## Where to Buy Browse and purchase OrbitFlare Pass listings on Magic Eden. View floor price and trade OrbitFlare Passes on Tensor. ## Rental Marketplace Pass holders can rent their node access to other users through the OrbitFlare Discord. If you're not using your full allocation, renting lets you offset your monthly access fee while keeping ownership of the pass. Contact the team in the private Discord channel for details on setting up a rental arrangement. # Polygon RPC Nodes Source: https://docs.orbitflare.com/polygon Polygon PoS RPC and WebSocket endpoints on OrbitFlare, with plans from 25 to 1000 RPS, gRPC and archival data on every tier, and dedicated instances. ## Overview OrbitFlare provides Polygon PoS RPC infrastructure for DeFi protocols, gaming, and production applications. Endpoints are region-pinned and authenticated with the same license key as your other OrbitFlare services. Every plan includes RPC, WebSockets, gRPC access, and archival data. Polygon is EVM-compatible, so the JSON-RPC surface is the standard `eth_*` API. ## Endpoint | Region | Region Code | Endpoint | | - | - | - | | Amsterdam | `ams` | `https://ams.poly.orbitflare.com?api_key=YOUR_API_KEY` | The general form matches the rest of the platform: ``` https://{region}.poly.orbitflare.com?api_key=YOUR_API_KEY ``` Polygon endpoints are separate from the Solana endpoints documented in [Authentication & Limits](/authentication). Solana uses `{region}.rpc.orbitflare.com`; Polygon uses `{region}.poly.orbitflare.com`. ## Authentication Append your license key as a query parameter, exactly as with the Solana RPC endpoints: ```bash theme={null} curl -X POST "https://ams.poly.orbitflare.com?api_key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "eth_blockNumber" }' ``` Your license key is in the **Licenses** section of your [OrbitFlare Dashboard](https://orbitflare.com/dashboard). ## Making RPC Calls Polygon uses the same JSON-RPC 2.0 format as Ethereum. Pass `Content-Type: application/json` with a POST body: ```bash cURL theme={null} curl -X POST "https://ams.poly.orbitflare.com?api_key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": 1 }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://ams.poly.orbitflare.com?api_key=YOUR_API_KEY", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1, }), } ); const data = await response.json(); console.log("Latest block:", parseInt(data.result, 16)); ``` ```python Python theme={null} import requests response = requests.post( "https://ams.poly.orbitflare.com?api_key=YOUR_API_KEY", headers={"Content-Type": "application/json"}, json={ "jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": 1, }, ) data = response.json() print("Latest block:", int(data["result"], 16)) ``` ## WebSocket Subscriptions Real-time subscriptions use the `wss://` scheme with the same host and the same `api_key` parameter: ```javascript theme={null} const ws = new WebSocket( "wss://ams.poly.orbitflare.com?api_key=YOUR_API_KEY" ); ws.on("open", () => { ws.send(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_subscribe", params: ["newHeads"], })); }); ws.on("message", (data) => { const msg = JSON.parse(data); console.log("New block header:", msg.params?.result); }); ``` ## Plans Throughput scales by plan. RPC, WebSockets, gRPC access, and archival data are included on every tier, and there are no monthly credit or request caps. | Plan | Price | Requests / Second | | - | - | :-: | | Free | \$0/mo | 25 | | Starter | \$49/mo | 100 | | Growth | \$149/mo | 300 | | Pro | \$299/mo | 600 | | Performance | \$499/mo | 1,000 | Requests over your plan's RPS limit receive an HTTP `429 Too Many Requests` response. Implement exponential backoff in your client to handle these gracefully. No. You can send unlimited total requests as long as you stay within your plan's per-second rate. ## Dedicated Instances For isolated, high-throughput workloads, OrbitFlare also offers dedicated Polygon nodes: | Feature | Details | | - | - | | **Throughput** | Custom, unmetered | | **Hardware** | 100% dedicated resources, isolated environment | | **Configuration** | Custom node configs and parameters | | **SLA** | Custom uptime and response terms | | **Support** | Priority engineering support | | **Pricing** | Custom quote based on throughput, region, and SLA | Dedicated Polygon infrastructure is quoted per deployment. [Contact sales](mailto:sales@orbitflare.com) to discuss throughput, region, and SLA requirements. ## Supported Methods Polygon endpoints expose the standard EVM JSON-RPC API, including: | Category | Methods | | - | - | | **Blocks** | `eth_blockNumber`, `eth_getBlockByHash`, `eth_getBlockByNumber` | | **Transactions** | `eth_sendRawTransaction`, `eth_getTransactionByHash`, `eth_getTransactionReceipt` | | **Accounts** | `eth_getBalance`, `eth_getCode`, `eth_getStorageAt`, `eth_getTransactionCount` | | **Contracts** | `eth_call`, `eth_estimateGas`, `eth_getLogs` | | **Network** | `net_version`, `eth_chainId`, `eth_gasPrice`, `eth_feeHistory` | | **WebSocket** | `eth_subscribe` (newHeads, logs, newPendingTransactions) | Polygon PoS uses `chainId: 137` for mainnet and `chainId: 80002` for the Amoy testnet. ## Rate Limits and Quotas These limits apply to all OrbitFlare EVM chains (Robinhood, Polygon, BSC). They are enforced per license, and HTTP and WebSocket traffic on the same key share a single rate limit and in-flight budget. | Control | Free | Paid | | - | - | - | | Requests / second | plan RPS | plan RPS | | Batch size (HTTP + WS) | 1 item | 100 items | | `eth_getLogs` in flight | 1 | 10 | | `eth_getLogs` block range | 150 blocks | 10,000 blocks | | `debug_*` / `trace_*` methods | blocked | allowed | | WebSocket connections | 1 | from 10 | | Subscriptions per WebSocket connection | 5 | 25 | | `logs` subscription without a filter | refused | allowed | | WebSocket data per connection | 1 GiB | unlimited | Applies to both tiers: WebSocket connection lifetime of 24 hours; idle timeout of 30 minutes with no data frames; frame size of 4 MiB inbound and 32 MiB outbound; each batch item counts as one request. ### Error Codes | HTTP | JSON-RPC | Meaning | | - | - | - | | 429 | `-32429` | Rate limit exceeded | | 429 | `-32005` | Concurrent, batch, connection, or subscription limit exceeded | | 400 | `-32602` | `eth_getLogs` block range too large (the message names the span) | | 403 | `-32601` | Method not available on this plan | | 403 | `-32406` | WebSockets not enabled for this key | `-32005` and `-32429` are transient. Retry with exponential backoff. If `-32005` persists on isolated calls, another client is likely using the same key concurrently, since HTTP and WebSocket share one in-flight budget. ## Get Started How API keys work, endpoint formats, rate limits, and error semantics. Talk to the OrbitFlare team about dedicated Polygon instances and enterprise terms. # Products & Pricing Source: https://docs.orbitflare.com/products Compare OrbitFlare Solana RPC, Shredstream, dedicated nodes, data APIs, staking, and Pass pricing. Shredstream-optimized RPC with transparent limits, SLAs, and global regions. High-performance Solana infrastructure optimized with Shredstream from top turbine validators. Uptime SLA Global Regions Avg Response Time Expert Support *** ## OrbitFlare Pass **Premium all-access pass** — Only 95 ever minted. Available on secondary markets. **\$650**/month **1500 RPS** per endpoint **300 TPS** per endpoint * All 11 global RPC regions * gRPC streaming included (Jetstream & Yellowstone) * VIP support (4-hour avg response) * Early access to new features * Private Discord community **Buy on:** [Magic Eden](https://magiceden.io/marketplace/orbitflare-pass) · [Tensor](https://tensor.trade/collection/orbitflare) [Learn more about the OrbitFlare Pass →](/nft-pass) *** ## RPC Plans No credit limits—unlimited requests within your RPS tier. **\$0**/month * 10 RPS / 1 TPS * Devnet gRPC only * Community support * WebSockets included * 11 global regions **\$49**/month * 50 RPS / 10 TPS * Devnet gRPC only * Chat support * WebSockets included * 11 global regions **\$399**/month * 200 RPS / 75 TPS * gRPC add-on (+\$500) * 24/7 support * WebSockets included * 11 global regions **\$799**/month * 400 RPS / 150 TPS * gRPC add-on (+\$500) * 24/7 priority support * WebSockets included * 11 global regions * Historical data included **\$999**/month * 600 RPS / 200 TPS * gRPC **included** * 24/7 priority support * WebSockets included * 11 global regions * Historical data included **\$0**/quarter * 10 RPS / 1 TPS * Devnet gRPC only * Community support **\$139**/quarter * 50 RPS / 10 TPS * Devnet gRPC only * Chat support **\$1,137**/quarter * 200 RPS / 75 TPS * gRPC add-on available * 24/7 support **\$2,277**/quarter * 400 RPS / 150 TPS * gRPC add-on available * 24/7 priority support **\$2,847**/quarter * 600 RPS / 200 TPS * gRPC **included** * 24/7 priority support **\$0**/year * 10 RPS / 1 TPS * Devnet gRPC only * Community support **\$499**/year * 50 RPS / 10 TPS * Devnet gRPC only * Chat support **\$4,069**/year * 200 RPS / 75 TPS * gRPC add-on available * 24/7 support **\$8,149**/year * 400 RPS / 150 TPS * gRPC add-on available * 24/7 priority support **\$10,189**/year * 600 RPS / 200 TPS * gRPC **included** * 24/7 priority support **All plans include:** Devnet endpoint, WebSockets, 11 global regions, historical data access, Shredstream optimization, and 99.99% uptime SLA. Choose your plan and start building today *** ## gRPC Streaming Real-time Solana data via **Jetstream** (ultra-low latency) or **Yellowstone** (complete data). **\$500**/month * Unlimited real-time streaming * Shared resources (99.99% uptime) * Full gRPC access (1 IP) * Jetstream & Yellowstone **Best for:** Developers, small bots **\$1,800**/month * Dedicated hardware * Unlimited streaming * Optimized for HFT * Priority support **Best for:** HFT, analytics platforms | Feature | Jetstream | Yellowstone | | - | - | - | | **Latency** | Ultra-low | Low | | **Inner instructions** | No | Yes | | **Transaction metadata** | No | Yes | | **Best for** | Real-time trading | Indexing & analytics | | **Connection type** | Shreds-based | Geyser plugin | Learn more in our [Data Streaming documentation](/data-streaming/jetstream). *** ## Dedicated Nodes Enterprise-grade infrastructure with unlimited performance. **Starting at \$2,500**/month * Unlimited RPS & TPS * 100% dedicated hardware * Choice of client (Labs, Jito, Frankendancer) * Custom configuration * Priority support + account manager * 99.99% uptime SLA **Starting at \$1,800**/month * Dedicated streaming resources * Unlimited real-time data * Optimized for HFT & analytics * Priority support Need multiple nodes, custom SLAs, or on-premise deployment? **Contact our sales team.** *** ## Historical Data Complete Solana blockchain history since genesis (March 2020). **Included with all RPC plans at no extra cost.** * \<10ms P50 query latency * Global redundancy across 11 regions * Standard RPC methods supported * Managed backfills available for enterprise [Learn more →](/fetching-data/historical-data) *** ## Getting Started Select the tier that matches your RPS and feature needs. Start free to test. Sign up at [orbitflare.com](https://orbitflare.com) and complete checkout. Access your dashboard to retrieve your API credentials. Use our RPC endpoints in your application. Check our [quickstart guide](/quickstart). *** ## Why OrbitFlare? Powered by top turbine validators for fastest data propagation. US, Europe, and Asia Pacific coverage for low latency worldwide. Unlimited requests within your RPS. No surprise bills. Enterprise reliability with automatic failover. Solana-native engineers via Discord, Telegram, email. Full archive from genesis included with all plans. *** ## FAQs All plans include: Devnet endpoint, WebSocket support, access to all 11 global regions, historical data access, Shredstream optimization, and 99.99% uptime SLA. Yes, you can change your plan at any time from your dashboard. Upgrades take effect immediately, and downgrades apply at the next billing cycle. We accept credit cards and cryptocurrency (USDC on Solana). Enterprise customers can arrange invoicing. Yes! Our Free tier gives you 10 RPS with no time limit. Use it to test our infrastructure before upgrading. Jetstream provides ultra-low latency streaming optimized for trading. Yellowstone provides complete transaction data including inner instructions, better for indexing and analytics. Yes, dedicated RPC nodes start at $2,500/month and dedicated gRPC nodes at $1,800/month. Contact us for enterprise custom solutions. *** ## More Products Stake SOL with our high-performance validator. 5-7% APY with JITO MEV rewards. Dedicated BNB/BSC node infrastructure starting at \$1,800/month. 11 regions across US, Europe, and Asia Pacific. *** Ready to get started? [View pricing](https://orbitflare.com/pricing) or [contact our team](https://discord.gg/orbitflare) to discuss your requirements. # Quickstart Source: https://docs.orbitflare.com/quickstart Create your OrbitFlare account, copy your license key from the dashboard, and call mainnet Solana JSON-RPC with curl or any HTTP client. Includes a free tier; upgrade for higher limits and regional endpoints. Sign up at [orbitflare.com](https://orbitflare.com) and choose a plan that fits your needs. A **free tier** is available so you can start building immediately with no credit card required. The free plan includes 10 requests per second -- enough to prototype and test your integration before upgrading. Once you have signed in, navigate to the **Dashboard** and locate the **Licenses** section. Your license key is provided with your service subscription — copy it from there. You will authenticate RPC requests by appending the license key as the `api_key` query parameter to the endpoint URL. Keep your license key secret. Do not commit it to version control or expose it in client-side code. Use the mainnet endpoint to call `getBlockHeight` and confirm everything is working. ```bash cURL theme={null} curl -X POST "https://fra.rpc.orbitflare.com?api_key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockHeight" }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://fra.rpc.orbitflare.com?api_key=YOUR_API_KEY", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getBlockHeight", }), } ); const data = await response.json(); console.log("Block height:", data.result); ``` ```python Python theme={null} import requests response = requests.post( "https://fra.rpc.orbitflare.com?api_key=YOUR_API_KEY", headers={"Content-Type": "application/json"}, json={ "jsonrpc": "2.0", "id": 1, "method": "getBlockHeight", }, ) data = response.json() print("Block height:", data["result"]) ``` A successful response looks like this: ```json theme={null} { "jsonrpc": "2.0", "result": 283458923, "id": 1 } ``` OrbitFlare supports persistent WebSocket connections for real-time data. Connect to `wss://fra.rpc.orbitflare.com` and subscribe to updates. ```javascript JavaScript theme={null} const WebSocket = require("ws"); const ws = new WebSocket( "wss://fra.rpc.orbitflare.com?api_key=YOUR_API_KEY" ); ws.on("open", () => { ws.send( JSON.stringify({ jsonrpc: "2.0", id: 1, method: "slotSubscribe", }) ); console.log("Subscribed to slot updates"); }); ws.on("message", (data) => { const message = JSON.parse(data); console.log("Received:", message); }); ws.on("error", (err) => { console.error("WebSocket error:", err); }); ``` WebSocket connections use the same `api_key` query parameter for authentication. ## What's Next? Understand API key usage, endpoint formats, rate limits, and streaming connection limits. Dive into the complete HTTP RPC method reference. Stream real-time Solana data with Jetstream. HTTP status codes, JSON-RPC errors, and how to handle them. Production-ready starter templates for building on Solana — Blinks, swaps, and more. # Robinhood Chain Nodes Source: https://docs.orbitflare.com/robinhood-chain Dedicated Robinhood Chain node access from OrbitFlare — low-latency RPC and WebSocket endpoints for the tokenized-stock L2, available for purchase from the OrbitFlare dashboard. ## Overview OrbitFlare provides node infrastructure for **Robinhood Chain**, the EVM-compatible Ethereum Layer 2 that Robinhood launched on mainnet in July 2026. The chain is built on the Arbitrum Orbit (Nitro) stack, uses ETH for gas, and is home to Robinhood's tokenized stocks, which trade around the clock and integrate with DeFi protocols such as Uniswap and Chainlink from day one. If you are building trading systems, indexers, or DeFi integrations around tokenized equities, OrbitFlare gives you direct node access without operating Nitro infrastructure yourself. Because the chain is fully EVM-compatible, standard tooling like Hardhat, Foundry, ethers.js, viem, and Wagmi works against the endpoint without modification, and the chain has first-class support for ERC-4337 account abstraction. Robinhood Chain node access is available for purchase directly from the [OrbitFlare dashboard](https://orbitflare.com/dashboard). Learn more on the [product page](https://orbitflare.com/products/robinhood-chain-rpcs). Robinhood Chain access is also included with the [OrbitFlare Pass](/nft-pass) NFT, capped at 100 RPS for Pass holders and renters. ## Endpoints Robinhood Chain mainnet runs in two regions. Use the endpoint closest to your servers. | Region | JSON-RPC (HTTP) | WebSocket | | - | - | - | | **US (Ohio)** | `https://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY` | `wss://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY` | | **Amsterdam (EU)** | `https://ams.robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY` | `wss://ams.robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY` | Robinhood Chain mainnet uses `chainId: 4663` with ETH as the gas token. ### Access Authenticate with your OrbitFlare license key, passed as the `?api_key=` query parameter on both the HTTP and WebSocket URLs (the same key format, `ORBIT-XXXXXX-NNNNNN-NNNNNN`, used across OrbitFlare products). A license can optionally be placed in IP-whitelist mode, where only whitelisted source IPs may connect and requests still carry the key; manage the mode and whitelist from the [dashboard](https://orbitflare.com/dashboard). Robinhood Chain also runs a public testnet (`chainId: 46630`), an Arbitrum Orbit chain settling to Ethereum Sepolia with ETH as the gas token: | Resource | Location | | - | - | | **Faucet** | [faucet.testnet.chain.robinhood.com](https://faucet.testnet.chain.robinhood.com) | | **Explorer** | [explorer.testnet.chain.robinhood.com](https://explorer.testnet.chain.robinhood.com) | | **Bridge (from Sepolia)** | [Arbitrum Bridge](https://portal.arbitrum.io/bridge?destinationChain=robinhood-chain-testnet\&sourceChain=sepolia) | Generate a throwaway keypair, request testnet ETH from the faucet, and you can exercise the full transaction lifecycle end to end. The OrbitFlare endpoint serves **mainnet only**. ## Making RPC Calls Robinhood Chain is EVM-compatible and speaks standard JSON-RPC 2.0. Pass `Content-Type: application/json` with a POST body: ```bash cURL theme={null} curl -X POST "https://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": 1 }' ``` ```typescript viem theme={null} import { createPublicClient, defineChain, http } from "viem"; const robinhoodChain = defineChain({ id: 4663, name: "Robinhood Chain", nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: ["https://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY"] }, }, }); const client = createPublicClient({ chain: robinhoodChain, transport: http(), }); const block = await client.getBlockNumber(); console.log("Latest block:", block); ``` ```python web3.py theme={null} from web3 import Web3 w3 = Web3(Web3.HTTPProvider("https://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY")) print("Connected:", w3.is_connected()) print("Chain ID:", w3.eth.chain_id) # 4663 print("Latest block:", w3.eth.block_number) ``` ### WebSocket Subscriptions Real-time subscriptions run over the WebSocket endpoint. In Node.js, install the `ws` package (`npm install ws`): ```javascript theme={null} import WebSocket from "ws"; const ws = new WebSocket("wss://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY"); ws.on("open", () => { ws.send(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_subscribe", params: ["newHeads"], })); }); ws.on("message", (data) => { const msg = JSON.parse(data); console.log("New block header:", msg.params?.result); }); ``` Robinhood Chain produces blocks roughly every 100 milliseconds, so expect a much faster `newHeads` cadence than on Ethereum mainnet or most L2s. ### Supported Methods The node exposes the standard EVM JSON-RPC surface, including: | Category | Methods | | - | - | | **Blocks** | `eth_blockNumber`, `eth_getBlockByHash`, `eth_getBlockByNumber` | | **Transactions** | `eth_sendRawTransaction`, `eth_getTransactionByHash`, `eth_getTransactionReceipt` | | **Accounts** | `eth_getBalance`, `eth_getCode`, `eth_getStorageAt`, `eth_getTransactionCount` | | **Contracts** | `eth_call`, `eth_estimateGas`, `eth_getLogs` | | **Network** | `net_version`, `eth_chainId`, `eth_gasPrice`, `eth_feeHistory` | | **WebSocket** | `eth_subscribe` (newHeads, logs, newPendingTransactions) | As an Arbitrum Nitro chain, the node also serves the `arb_*` extension methods alongside the standard `eth_*` set. For the full method reference, see the [Ethereum JSON-RPC specification](https://ethereum.org/en/developers/docs/apis/json-rpc/). ## Rate Limits and Quotas These limits apply to all OrbitFlare EVM chains (Robinhood, Polygon, BSC). They are enforced per license, and HTTP and WebSocket traffic on the same key share a single rate limit and in-flight budget. | Control | Free | Paid | | - | - | - | | Requests / second | plan RPS | plan RPS | | Batch size (HTTP + WS) | 1 item | 100 items | | `eth_getLogs` in flight | 1 | 10 | | `eth_getLogs` block range | 150 blocks | 10,000 blocks | | `debug_*` / `trace_*` methods | blocked | allowed | | WebSocket connections | 1 | from 10 | | Subscriptions per WebSocket connection | 5 | 25 | | `logs` subscription without a filter | refused | allowed | | WebSocket data per connection | 1 GiB | unlimited | Applies to both tiers: WebSocket connection lifetime of 24 hours; idle timeout of 30 minutes with no data frames; frame size of 4 MiB inbound and 32 MiB outbound; each batch item counts as one request. ### Error Codes | HTTP | JSON-RPC | Meaning | | - | - | - | | 429 | `-32429` | Rate limit exceeded | | 429 | `-32005` | Concurrent, batch, connection, or subscription limit exceeded | | 400 | `-32602` | `eth_getLogs` block range too large (the message names the span) | | 403 | `-32601` | Method not available on this plan | | 403 | `-32406` | WebSockets not enabled for this key | `-32005` and `-32429` are transient. Retry with exponential backoff. If `-32005` persists on isolated calls, another client is likely using the same key concurrently, since HTTP and WebSocket share one in-flight budget. ## Example: Reading a Tokenized Stock The chain's flagship assets are Robinhood's tokenized stocks, which are standard ERC-20 contracts. Here is the NVIDIA token (`NVDA`) read directly from chain state: ```typescript viem theme={null} import { createPublicClient, defineChain, erc20Abi, http } from "viem"; const robinhoodChain = defineChain({ id: 4663, name: "Robinhood Chain", nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: ["https://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY"] }, }, }); const client = createPublicClient({ chain: robinhoodChain, transport: http() }); const NVDA = "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC"; const [name, symbol, decimals, totalSupply] = await Promise.all([ client.readContract({ address: NVDA, abi: erc20Abi, functionName: "name" }), client.readContract({ address: NVDA, abi: erc20Abi, functionName: "symbol" }), client.readContract({ address: NVDA, abi: erc20Abi, functionName: "decimals" }), client.readContract({ address: NVDA, abi: erc20Abi, functionName: "totalSupply" }), ]); console.log(`${name} (${symbol}), ${totalSupply / 10n ** BigInt(decimals)} shares tokenized`); // NVIDIA • Robinhood Token (NVDA), 26419 shares tokenized ``` ```bash cURL theme={null} # symbol() on the NVDA token contract curl -X POST "https://robinhood.rpc.orbitflare.com?api_key=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "eth_call", "params": [{ "to": "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC", "data": "0x95d89b41" }, "latest"], "id": 1 }' ``` Search the [explorer](https://robinhoodchain.blockscout.com) carefully before hardcoding token addresses: many copycat tokens use the `NVDA` symbol. The official Robinhood stock tokens carry the `• Robinhood Token` naming, and the address above is the one held by tens of thousands of accounts. ## Use Cases Trade and monitor on-chain equities like NVDA and AAPL 24/7, without market-hours limits. Build against Uniswap pools and Chainlink feeds live on the chain since launch. Follow 100ms blocks in real time over WebSocket and keep your own view of chain state. ## Get Started Buy Robinhood Chain node access directly from the OrbitFlare dashboard and grab your license key. Typed RPC and WebSocket clients for Robinhood Chain, built on alloy with retry and failover out of the box. Learn more about Robinhood Chain RPC access, pricing, and what's included. ## Official Resources * [Robinhood Chain documentation](https://docs.robinhood.com/chain) * [Robinhood Chain explorer (Blockscout)](https://robinhoodchain.blockscout.com) * [Robinhood Chain status page](https://status.robinhoodchain.offchain.io) # Common Error Codes Source: https://docs.orbitflare.com/rpc/error-codes Understanding RPC error messages and their solutions When interacting with OrbitFlare RPC endpoints, you may encounter the following standard HTTP error codes. This guide helps you understand what these errors mean and how to resolve them. ## HTTP Status Codes | Error Code | Error Message | Description | Solution | | - | - | - | - | | 401 | Unauthorized | Invalid API key or authentication token. | - Verify your API key is correct
- Check if your API key has the required permissions
- Ensure your API key is properly set in the request headers | | 403 | Forbidden | Access denied due to insufficient permissions or IP restrictions. | - Verify your IP is whitelisted if IP restrictions are enabled
- Check if your subscription plan allows the requested operation | | 429 | Too Many Requests | You've exceeded the rate limits for your subscription tier. | - Implement request rate limiting in your application
- Consider upgrading to a higher tier with increased limits
- Check your current usage in the dashboard | | 500 | Internal Server Error | An unexpected error occurred on our servers. | - Retry the request after a short delay
- If the issue persists, contact our support team | | 502 | Bad Gateway | Temporary issue with our upstream services. | - Retry the request after a few moments
- If persistent, check our status page for any ongoing incidents | | 503 | Service Unavailable | Service is temporarily unavailable or under maintenance. | - Retry with exponential backoff
- Check our status page for maintenance announcements | | 504 | Gateway Timeout | Request timed out while waiting for a response. | - Check your network connection
- Consider reducing the complexity of your request
- Try breaking large requests into smaller chunks | ## JSON-RPC Errors | Error Code | Message | Description | Solution | | - | - | - | - | | -32700 | Parse error | Invalid JSON received | Check your request format and ensure it's valid JSON | | -32600 | Invalid request | JSON is valid but request format is wrong | Verify your request matches the RPC method specification | | -32601 | Method not found | The requested method doesn't exist | Check the method name and our API documentation | | -32602 | Invalid params | Method parameters are invalid | Review the parameters required for the method | | -32603 | Internal error | Internal JSON-RPC error | Retry the request; if persistent, contact support | | -32000 | Server error | Generic server-side error | Retry with exponential backoff | | -32001 | Transaction simulation failed | Transaction failed during simulation | Check transaction parameters and account states | | -32002 | Account not found | Requested account doesn't exist | Verify the account address is correct | | -32003 | Block not found | Requested block not available | Check if the block is within available history | | -32004 | Node unhealthy | RPC node is not in a healthy state | Try a different RPC endpoint | ## Best Practices 1. **Implement Retry Logic** * Use exponential backoff for retries * Set appropriate timeout values * Handle rate limits gracefully 2. **Error Handling** * Always check error responses * Log error details for debugging * Implement proper fallback mechanisms 3. **Monitoring** * Monitor error rates in your application * Set up alerts for unusual error patterns * Track API usage and rate limit consumption ## Support If you encounter persistent issues or need help understanding specific errors, please: 1. Check our [status page](https://status.orbitflare.com) for system status 2. Join our [Discord community](https://discord.gg/orbitflare) for quick help 3. Contact our support team for detailed assistance For real-time updates on system status and maintenance, follow us on [Twitter](https://twitter.com/orbitflarenodes). # HTTP RPC API Source: https://docs.orbitflare.com/rpc/http Interact with Solana nodes using JSON-RPC 2.0 ## Overview OrbitFlare RPC nodes accept HTTP requests using the JSON-RPC 2.0 specification. This API allows you to interact with the Solana blockchain for querying data and submitting transactions. ## Request Format To make a JSON-RPC request, send an HTTP POST request with the following specifications: * Endpoint: `https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY` * Header: `Content-Type: application/json` * Body: JSON object with these fields: | Field | Type | Description | | - | - | - | | `jsonrpc` | string | Must be "2.0" | | `id` | string/number | Unique request identifier | | `method` | string | The RPC method name | | `params` | array | Array of method parameters | ### Example Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": [ "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri" ] }' ``` ### Response Format Responses will be JSON objects containing: | Field | Type | Description | | - | - | - | | `jsonrpc` | string | Always "2.0" | | `id` | string/number | Matches request identifier | | `result` | varies | The method's result data | | `error` | object | Error details (if applicable) | ## Commitment Many methods accept a `commitment` parameter to specify how confirmed the data should be: * `processed`: Latest block (fastest, not confirmed) * `confirmed`: Confirmed by supermajority (balance between speed and finality) * `finalized`: Finalized by supermajority (slowest, fully confirmed) Example with commitment: ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": [ "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", {"commitment": "finalized"} ] }' ``` ## Batch Requests You can send multiple requests in a single HTTP call by providing an array of request objects: ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY -X POST -H "Content-Type: application/json" -d '[ { "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": ["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"] }, { "jsonrpc": "2.0", "id": 2, "method": "getBlockHeight", "params": [] } ]' ``` ## Common Types | Type | Description | | - | - | | Pubkey | Base-58 encoded public key string | | Hash | Base-58 encoded SHA-256 hash string | | Signature | Base-58 encoded Ed25519 signature | | Slot | Integer block height/slot number | ## Health Check You can check node health with a GET request: ```bash theme={null} curl "https://fra.rpc.orbitflare.com/health?api_key=YOUR_LICENSE_KEY" ``` Possible responses: * `ok`: Node is healthy and up-to-date * `behind { slots: number }`: Node is behind by N slots * `error`: Node is unhealthy ## Available Methods * getAccountInfo * getMultipleAccounts * getProgramAccounts * getMinimumBalanceForRentExemption * getBlock * getBlocks * getBlockHeight * getSlot * getSlotLeader * getTransaction * getSignatureStatuses * getSignaturesForAddress * sendTransaction * simulateTransaction * getTokenAccountBalance * getTokenAccountsByDelegate * getTokenAccountsByOwner * getTokenSupply ## Rate Limits Rate limits vary by subscription tier. See the [Authentication & Limits](/authentication#rate-limits-by-plan) page for the exact RPS and TPS values per plan. ## Best Practices 1. **Use Appropriate Commitment Levels** * Use `processed` for UI updates * Use `confirmed` for most operations * Use `finalized` for critical operations 2. **Optimize Requests** * Use batch requests when possible * Implement proper caching * Choose appropriate polling intervals 3. **Handle Errors** * Implement proper error handling * Use exponential backoff for retries * Monitor request failures ## See Also API key setup, endpoint formats, rate limits, and connection limits. HTTP status codes, JSON-RPC errors, and how to handle them. Real-time subscriptions for accounts, slots, logs, and more. ## Support For technical support or questions about our RPC service: * Join our [Discord community](https://discord.gg/orbitflare) * Check our [status page](https://status.orbitflare.com) * Contact our support team # getAccountInfo Source: https://docs.orbitflare.com/rpc/methods/getAccountInfo Returns all information associated with an account Returns all information associated with the account of provided public key. ## Parameters The public key of the account to query (base-58 encoded string) Configuration object containing the following optional fields: The level of commitment to use for querying the network: * `processed`: Latest block (unconfirmed) * `confirmed`: Confirmed by supermajority * `finalized`: Finalized by supermajority Encoding format for the account data: * `base58` * `base64` * `base64+zstd` * `jsonParsed` (limited to certain account types) Request a slice of the account's data: * `offset`: number - Start position (in bytes) * `length`: number - Number of bytes to return The minimum slot that the request can be evaluated at ## Response If the requested account doesn't exist, returns `null`. Otherwise, returns an object containing: The slot the request was processed at Number of lamports assigned to the account Base-58 encoded public key of the program that owns this account Whether this account contains a program The epoch at which this account will next owe rent The account's data. Format depends on the encoding parameter: * `[string, encoding]` tuple if encoded as base58/base64 * Parsed JSON object if encoding is "jsonParsed" The space used by the account's data ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getAccountInfo", "params": [ "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg", { "encoding": "base58" } ] }' ``` ### Response ```json theme={null} { "jsonrpc": "2.0", "result": { "context": { "slot": 430 }, "value": { "data": ["", "base58"], "executable": false, "lamports": 5000000000, "owner": "11111111111111111111111111111111", "rentEpoch": 18446744073709551615, "space": 0 } }, "id": 1 } ``` ### Request with Data Slice ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getAccountInfo", "params": [ "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg", { "encoding": "base64", "dataSlice": { "offset": 0, "length": 64 } } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const publicKey = new PublicKey('vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg'); const accountInfo = await connection.getAccountInfo( publicKey, 'confirmed' ); ``` ## Notes 1. The `jsonParsed` encoding is only available for certain account types: * Stake account * Token account * Token mint * Token metadata 2. When using `dataSlice`, the data field will be limited to the requested slice only. 3. The account data may be encoded differently based on the program that owns the account. ## Common Errors | Code | Message | Solution | | - | - | - | | -32602 | Invalid param: WrongSize | Verify the public key is valid | | -32602 | Invalid param: not base58 encoded string | Ensure the public key is base58 encoded | | -32007 | Account not found | The requested account does not exist | # getBalance Source: https://docs.orbitflare.com/rpc/methods/getBalance Returns the balance of the account of provided public key ## Parameters Public key of the account to query (base-58 encoded string) Configuration object containing the following optional fields: The level of commitment to use: * `processed`: Latest block (unconfirmed) * `confirmed`: Confirmed by supermajority * `finalized`: Finalized by supermajority The minimum slot that the request can be evaluated at ## Response The slot the request was processed at Number of lamports assigned to this account ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": [ "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri" ] }' ``` ### Request with Commitment ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBalance", "params": [ "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", { "commitment": "finalized" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const publicKey = new PublicKey('83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri'); const balance = await connection.getBalance( publicKey, 'confirmed' ); console.log(`Balance: ${balance / 1e9} SOL`); ``` ## Notes 1. Balance is returned in lamports (1 SOL = 1,000,000,000 lamports) 2. For new accounts, balance will be 0 3. The balance includes all SOL tokens, including those delegated for staking 4. The response is immediate as it reads from the current state ## Best Practices 1. Use appropriate commitment level based on your needs: * `processed` for UI updates * `confirmed` for most operations * `finalized` for critical operations 2. Convert lamports to SOL by dividing by 1e9 when displaying to users 3. Consider using `getAccountInfo` if you need more account details ## Common Errors | Code | Message | Solution | | - | - | - | | -32602 | Invalid param: WrongSize | Verify the public key is valid | | -32602 | Invalid param: not base58 encoded string | Ensure the public key is base58 encoded | | -32007 | Account not found | The account doesn't exist (balance will be 0) | # getBlock Source: https://docs.orbitflare.com/rpc/methods/getBlock Returns identity and transaction information about a confirmed block ## Parameters The slot number of the block to retrieve Configuration object containing the following optional fields: Encoding format for transaction data: * `json` - JSON format * `jsonParsed` - JSON format with parsed transaction data * `base58` - Base-58 encoded binary data * `base64` - Base-64 encoded binary data Level of transaction detail to return: * `full` - Full transaction data * `signatures` - Only transaction signatures * `none` - No transaction data Whether to populate the rewards array Bank state to query: * `processed` - Latest block (unconfirmed) * `confirmed` - Confirmed by supermajority * `finalized` - Finalized by supermajority The maximum transaction version to return in responses ## Response Returns `null` if the block is not found. Otherwise, returns an object containing: The blockhash of this block (base-58) The blockhash of the previous block (base-58) The slot index of the parent block Array of transaction information (if `transactionDetails` != "none"): * For `full`: Complete transaction objects * For `signatures`: Only transaction signatures Array of reward objects (if `rewards` = true): * `pubkey`: string - The public key (base-58) * `lamports`: number - Number of reward lamports * `postBalance`: number - Account balance after reward * `rewardType`: string - Type of reward * `commission`: number - Vote account commission (vote only) Estimated production time (Unix timestamp) The number of blocks beneath this block ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlock", "params": [ 430, { "encoding": "json", "transactionDetails": "full", "rewards": true } ] }' ``` ### Request with Parsed Transaction Data ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlock", "params": [ 430, { "encoding": "jsonParsed", "transactionDetails": "full", "rewards": true, "maxSupportedTransactionVersion": 0 } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const slot = 430; const block = await connection.getBlock( slot, { maxSupportedTransactionVersion: 0 } ); ``` ## Notes 1. Block production time is an estimate based on genesis block time and slots elapsed. 2. Not all blocks include rewards. 3. The `jsonParsed` encoding attempts to parse transaction instruction data based on known program layouts. 4. Some blocks might be skipped (no leader assigned or failed block production). 5. Block data may be pruned from the node based on ledger configuration. ## Best Practices 1. Use `transactionDetails: "signatures"` if you only need transaction signatures. 2. Set `rewards: false` if reward data is not needed. 3. Consider using `getBlockHeight` first if you need the latest block. 4. For real-time updates, consider using websocket subscription instead. ## Common Errors | Code | Message | Solution | | - | - | - | | -32004 | Block not available for slot | The block was pruned or skipped | | -32602 | Invalid param: WrongSize | Verify the slot number is valid | | -32602 | Invalid param: Too large | Request a more recent block | | -32009 | Transaction version unsupported | Specify `maxSupportedTransactionVersion` | # getBlockCommitment Source: https://docs.orbitflare.com/rpc/methods/getBlockCommitment Returns commitment for particular block ## Parameters Block number, identified by Slot ## Response Array of u64 integers logging the amount of cluster stake in lamports that has voted on the block at each depth from 0 to `MAX_LOCKOUT_HISTORY`. Total active stake, in lamports, of the current epoch. ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockCommitment", "params": [5] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const commitment = await connection.getBlockCommitment(5); console.log(commitment); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") response = client.get_block_commitment(5) print(response) ``` ## Notes 1. Returns commitment information for a specific block 2. The commitment array indicates stake weight distribution 3. The totalStake value represents the total active stake in the current epoch 4. The response is immediate as it reads from the current state ## Best Practices 1. Use this method to verify block confirmation status 2. Compare commitment values to understand consensus levels 3. Handle network errors and retry when appropriate # getBlockHeight Source: https://docs.orbitflare.com/rpc/methods/getBlockHeight Returns the current block height of the node ## Parameters Configuration object containing the following optional fields: The level of commitment to use: * `processed`: Latest block (unconfirmed) * `confirmed`: Confirmed by supermajority * `finalized`: Finalized by supermajority The minimum slot that the request can be evaluated at ## Response Current block height ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockHeight" }' ``` ### Request with Commitment ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockHeight", "params": [ { "commitment": "finalized" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const blockHeight = await connection.getBlockHeight('finalized'); console.log(`Current block height: ${blockHeight}`); ``` ## Notes 1. Block height increases monotonically with each new block 2. Block height may be different from slot number due to skipped slots 3. Different nodes may return slightly different heights based on their view of the network 4. The response is immediate as it reads from the current state ## Best Practices 1. Use appropriate commitment level based on your needs: * `processed` for latest possible height * `confirmed` for high probability finality * `finalized` for guaranteed finality 2. Consider using `getSlot` if you need slot numbers instead of block height 3. For historical queries, use `getBlock` with specific slots ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Block height not available | Node may be bootstrapping or syncing | # getBlockProduction Source: https://docs.orbitflare.com/rpc/methods/getBlockProduction Returns recent block production information from the current or previous epoch ## Parameters Configuration object containing the following optional fields: The level of commitment to use: * `processed`: Latest block (unconfirmed) * `confirmed`: Confirmed by supermajority * `finalized`: Finalized by supermajority Slot range to return block production for: * `firstSlot`: number - First slot to return block production information for (inclusive) * `lastSlot`: number - Last slot to return block production information for (inclusive) Only return results for this validator identity (base-58 encoded) ## Response The slot the request was processed at Dictionary of validator identities, where each contains: * `leader_slots`: number - Slots assigned as leader * `blocks_produced`: number - Blocks produced in this epoch First slot of the block production information (inclusive) Last slot of block production information (inclusive) ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockProduction" }' ``` ### Request with Range and Identity ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockProduction", "params": [ { "range": { "firstSlot": 100000000, "lastSlot": 100000100 }, "identity": "GH7ome3EiwEr17v3Fn6XY4RjS1YERxYzwXSW8kZ8kBYq" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const blockProduction = await connection.getBlockProduction(); console.log('Block production:', blockProduction); // Get production for specific range and validator const config = { range: { firstSlot: 100000000, lastSlot: 100000100 }, identity: 'GH7ome3EiwEr17v3Fn6XY4RjS1YERxYzwXSW8kZ8kBYq' }; const specificProduction = await connection.getBlockProduction(config); ``` ## Notes 1. Without parameters, returns production for all validators in the current epoch 2. The `range` parameter can span multiple epochs 3. Block production statistics are used to track validator performance 4. Results include both assigned leader slots and actual blocks produced ## Best Practices 1. Use `range` to limit data when querying historical information 2. Use `identity` to track specific validator performance 3. Compare `leader_slots` to `blocks_produced` to evaluate validator reliability 4. Cache results when appropriate to reduce RPC load ## Common Errors | Code | Message | Solution | | - | - | - | | -32602 | Invalid param: WrongSize | Verify validator identity is valid | | -32602 | Invalid param: not base58 encoded string | Ensure validator identity is base58 encoded | | -32602 | Invalid param: slot range too large | Reduce the slot range size | | -32007 | Block production not available | Node may be bootstrapping or range is too old | # getBlockTime Source: https://docs.orbitflare.com/rpc/methods/getBlockTime Returns the estimated production time of a block ## Parameters The slot to get the block time for ## Response Unix timestamp of when the block was produced, or null if the block is not available ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlockTime", "params": [ 100000000 ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get block time const blockTime = await connection.getBlockTime(100000000); if (blockTime !== null) { console.log('Block time:', new Date(blockTime * 1000)); } else { console.log('Block not available'); } // Get multiple block times async function getBlockTimes(slots: number[]) { const times = await Promise.all( slots.map(slot => connection.getBlockTime(slot)) ); return times.map((time, index) => ({ slot: slots[index], time: time ? new Date(time * 1000) : null })); } ``` ## Notes 1. Returns the Unix timestamp when the block was produced 2. Returns null if the block is not available 3. The timestamp is in seconds since Unix epoch 4. Block times are estimates and may vary between nodes 5. Older blocks may not have timestamps available ## Best Practices 1. Handle null responses appropriately 2. Convert Unix timestamps to Date objects for display 3. Consider timezone differences when displaying times 4. Cache results when appropriate to reduce RPC load 5. Use this method in conjunction with other block methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Block time not available | Block may be too old or not yet produced | ## Use Cases 1. **Block Time Analysis** ```typescript theme={null} interface BlockTimeMetrics { slot: number; time: Date | null; delay: number | null; } async function analyzeBlockTimes(startSlot: number, endSlot: number) { const blocks = await connection.getBlocks(startSlot, endSlot); const times = await Promise.all( blocks.map(slot => connection.getBlockTime(slot)) ); const metrics: BlockTimeMetrics[] = blocks.map((slot, index) => { const time = times[index]; const prevTime = index > 0 ? times[index - 1] : null; return { slot, time: time ? new Date(time * 1000) : null, delay: time && prevTime ? time - prevTime : null }; }); return metrics; } ``` 2. **Block Time Tracking** ```typescript theme={null} interface BlockTimeHistory { slot: number; timestamp: number; date: Date; } async function trackBlockTimes(slots: number[]) { const times = await Promise.all( slots.map(slot => connection.getBlockTime(slot)) ); const history: BlockTimeHistory[] = times .filter((time, index) => time !== null) .map((time, index) => ({ slot: slots[index], timestamp: time!, date: new Date(time! * 1000) })); return history; } ``` 3. **Block Production Rate** ```typescript theme={null} async function calculateBlockProductionRate(startSlot: number, endSlot: number) { const blocks = await connection.getBlocks(startSlot, endSlot); const times = await Promise.all( blocks.map(slot => connection.getBlockTime(slot)) ); const validTimes = times.filter(time => time !== null) as number[]; if (validTimes.length < 2) { return null; } const firstTime = validTimes[0]; const lastTime = validTimes[validTimes.length - 1]; const totalTime = lastTime - firstTime; const blocksPerSecond = validTimes.length / totalTime; return { blocksPerSecond, totalTime, totalBlocks: validTimes.length }; } ``` # getBlocks Source: https://docs.orbitflare.com/rpc/methods/getBlocks Returns a list of confirmed blocks between two slots ## Parameters Start slot (inclusive) End slot (inclusive) Configuration object containing the following optional fields: The level of commitment to use: * `processed`: Latest block (unconfirmed) * `confirmed`: Confirmed by supermajority * `finalized`: Finalized by supermajority ## Response Array of block slots in ascending order ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlocks", "params": [ 100000000, 100000100 ] }' ``` ### Request with Commitment ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlocks", "params": [ 100000000, 100000100, { "commitment": "finalized" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get blocks in range const blocks = await connection.getBlocks(100000000, 100000100); console.log('Blocks:', blocks); // Get finalized blocks const finalizedBlocks = await connection.getBlocks(100000000, 100000100, 'finalized'); console.log('Finalized blocks:', finalizedBlocks); ``` ## Notes 1. Returns confirmed blocks between the specified slots 2. Results are returned in ascending order 3. The range is inclusive of both start and end slots 4. Some slots may be skipped (no block produced) 5. The response is immediate as it reads from the current state ## Best Practices 1. Use appropriate commitment level based on your needs: * `processed` for latest blocks * `confirmed` for high probability finality * `finalized` for guaranteed finality 2. Keep the slot range reasonable to avoid timeouts 3. Consider using `getBlocksWithLimit` for pagination 4. Cache results when appropriate to reduce RPC load 5. Handle skipped slots in your application logic ## Common Errors | Code | Message | Solution | | - | - | - | | -32602 | Invalid param: startSlot must be less than endSlot | Ensure startSlot is less than endSlot | | -32602 | Invalid param: slot range too large | Reduce the slot range size | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Block information unavailable | Node may be bootstrapping or range is too old | ## Use Cases 1. **Block Range Analysis** ```typescript theme={null} async function analyzeBlockRange(startSlot: number, endSlot: number) { const blocks = await connection.getBlocks(startSlot, endSlot); const totalSlots = endSlot - startSlot + 1; const skippedSlots = totalSlots - blocks.length; console.log(`Total slots: ${totalSlots}`); console.log(`Blocks produced: ${blocks.length}`); console.log(`Skipped slots: ${skippedSlots}`); console.log(`Block production rate: ${(blocks.length / totalSlots) * 100}%`); } ``` 2. **Block History Tracking** ```typescript theme={null} interface BlockHistory { startSlot: number; endSlot: number; blocks: number[]; timestamp: Date; } async function trackBlockHistory(startSlot: number, endSlot: number) { const blocks = await connection.getBlocks(startSlot, endSlot); const history: BlockHistory = { startSlot, endSlot, blocks, timestamp: new Date() }; storeBlockHistory(history); return history; } ``` 3. **Block Gap Detection** ```typescript theme={null} async function detectBlockGaps(startSlot: number, endSlot: number) { const blocks = await connection.getBlocks(startSlot, endSlot); const gaps = []; for (let i = 0; i < blocks.length - 1; i++) { const current = blocks[i]; const next = blocks[i + 1]; if (next - current > 1) { gaps.push({ start: current + 1, end: next - 1, length: next - current - 1 }); } } return gaps; } ``` # getBlocksWithLimit Source: https://docs.orbitflare.com/rpc/methods/getBlocksWithLimit Returns a list of confirmed blocks starting at the given slot ## Parameters Start slot (inclusive) Maximum number of blocks to return Configuration object containing the following optional fields: The level of commitment to use: * `processed`: Latest block (unconfirmed) * `confirmed`: Confirmed by supermajority * `finalized`: Finalized by supermajority ## Response Array of block slots in ascending order ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlocksWithLimit", "params": [ 100000000, 10 ] }' ``` ### Request with Commitment ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlocksWithLimit", "params": [ 100000000, 10, { "commitment": "finalized" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get 10 blocks starting from slot const blocks = await connection.getBlocksWithLimit(100000000, 10); console.log('Blocks:', blocks); // Get finalized blocks const finalizedBlocks = await connection.getBlocksWithLimit(100000000, 10, 'finalized'); console.log('Finalized blocks:', finalizedBlocks); ``` ## Notes 1. Returns confirmed blocks starting from the specified slot 2. Results are returned in ascending order 3. The limit controls the maximum number of blocks to return 4. Some slots may be skipped (no block produced) 5. The response is immediate as it reads from the current state ## Best Practices 1. Use appropriate commitment level based on your needs: * `processed` for latest blocks * `confirmed` for high probability finality * `finalized` for guaranteed finality 2. Keep the limit reasonable to avoid timeouts 3. Use this method for pagination instead of `getBlocks` 4. Cache results when appropriate to reduce RPC load 5. Handle skipped slots in your application logic ## Common Errors | Code | Message | Solution | | - | - | - | | -32602 | Invalid param: limit must be positive | Ensure limit is greater than 0 | | -32602 | Invalid param: limit too large | Reduce the limit size | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Block information unavailable | Node may be bootstrapping or slot is too old | ## Use Cases 1. **Block Pagination** ```typescript theme={null} async function getBlocksPaginated(startSlot: number, pageSize: number) { const blocks = await connection.getBlocksWithLimit(startSlot, pageSize); const lastBlock = blocks[blocks.length - 1]; return { blocks, nextStartSlot: lastBlock ? lastBlock + 1 : null }; } ``` 2. **Recent Block History** ```typescript theme={null} async function getRecentBlocks(count: number) { const currentSlot = await connection.getSlot(); const blocks = await connection.getBlocksWithLimit(currentSlot - count, count); return blocks.map(slot => ({ slot, timestamp: new Date() // Add actual timestamp if available })); } ``` 3. **Block Streaming** ```typescript theme={null} async function* streamBlocks(startSlot: number, batchSize: number) { let currentSlot = startSlot; while (true) { const blocks = await connection.getBlocksWithLimit(currentSlot, batchSize); if (blocks.length === 0) { break; } yield blocks; currentSlot = blocks[blocks.length - 1] + 1; } } ``` # getClusterNodes Source: https://docs.orbitflare.com/rpc/methods/getClusterNodes Returns information about all the nodes participating in the cluster ## Parameters This method does not take any parameters. ## Response Array of objects containing node information: Node public key (base-58) Gossip network address for the node TPU network address for the node JSON RPC network address for the node Software version of the node Unique identifier of the node's feature set The shred version the node has been configured to use ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getClusterNodes" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const nodes = await connection.getClusterNodes(); console.log('Cluster nodes:', nodes); // Example: Find all nodes running a specific version const version = '1.16.0'; const nodesWithVersion = nodes.filter(node => node.version === version); console.log(`Nodes running version ${version}:`, nodesWithVersion); // Example: Get all RPC endpoints const rpcEndpoints = nodes .filter(node => node.rpc) .map(node => node.rpc); console.log('Available RPC endpoints:', rpcEndpoints); ``` ## Notes 1. Some fields may be `null` if the information is not available 2. Node information is obtained through the gossip network 3. The list includes all known nodes, whether they are currently active or not 4. Network addresses are in standard socket format (IP:port) ## Best Practices 1. Cache results to reduce RPC load (refresh every few minutes) 2. Use version information to detect network upgrades 3. Filter null values when processing network addresses 4. Consider node versions when selecting RPC endpoints 5. Use TPU addresses for transaction forwarding optimization ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Node information unavailable | Node may be bootstrapping or gossip service may be down | | -32008 | Node list too large | Try again later when network conditions improve | ## Use Cases 1. **Load Balancing** * Discover available RPC endpoints * Distribute client connections 2. **Network Health Monitoring** * Track node versions * Monitor network distribution * Identify network partitions 3. **Transaction Optimization** * Find closest TPU addresses * Implement leader-aware transaction forwarding 4. **Version Management** * Track network upgrades * Ensure client compatibility * Plan maintenance windows # getEpochInfo Source: https://docs.orbitflare.com/rpc/methods/getEpochInfo Returns information about the current epoch ## Parameters The commitment describes how finalized a block is at that point in time. The minimum slot that the request can be evaluated at. ## Response The current slot The current block height The current epoch The current slot relative to the start of the current epoch The number of slots in this epoch Total number of transactions processed without error since genesis ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getEpochInfo" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const epochInfo = await connection.getEpochInfo(); console.log(epochInfo); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") response = client.get_epoch_info() print(response) ``` ## Notes 1. Returns detailed information about the current epoch 2. Includes absolute slot, block height, epoch number, and slots in epoch 3. Transaction count may be null if not available 4. Different commitment levels can be specified 5. The response is immediate as it reads from the current state ## Best Practices 1. Use this method to track epoch transitions 2. Monitor transaction count for network activity 3. Check slot index to determine progress within the current epoch 4. Handle network errors and retry when appropriate 5. Use appropriate commitment level based on your needs # getEpochSchedule Source: https://docs.orbitflare.com/rpc/methods/getEpochSchedule Returns epoch schedule information from this cluster genesis config ## Parameters This method does not require any parameters. ## Response The maximum number of slots in each epoch The number of slots before beginning of an epoch to calculate a leader schedule for that epoch Whether epochs start short and grow First normal-length epoch, log2(slotsPerEpoch) - log2(MINIMUM\_SLOTS\_PER\_EPOCH) MINIMUM\_SLOTS\_PER\_EPOCH \* (2.pow(firstNormalEpoch) - 1) ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getEpochSchedule" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const epochSchedule = await connection.getEpochSchedule(); console.log(epochSchedule); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") response = client.get_epoch_schedule() print(response) ``` ## Notes 1. Returns epoch schedule information from the cluster's genesis configuration 2. The returned data is static and does not change for a given network/cluster 3. Useful for calculating epoch boundaries and leader schedule timing 4. The warmup parameter indicates if epochs start short and grow to their full size ## Best Practices 1. Cache this information locally as it does not change 2. Use the returned values to calculate epoch transitions 3. Use firstNormalEpoch and firstNormalSlot for epoch timing calculations 4. Handle network errors and retry when appropriate # getFeeForMessage Source: https://docs.orbitflare.com/rpc/methods/getFeeForMessage Get the fee the network will charge for a particular message ## Parameters Base-64 encoded Message The commitment level to use for the fee calculation ## Response The slot this request was processed in Fee in lamports that will be charged for processing the message, or null if the message is invalid or the fee cannot be calculated ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getFeeForMessage", "params": [ "AQABAyRn/PA8jzJtN6oAyB3VR0nfOF0xnfkYaKwY4Ir3nrHFJGqhAQICAAEDAgABDAIAAAAEAAAAAAAA", {"commitment": "processed"} ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const message = 'AQABAyRn/PA8jzJtN6oAyB3VR0nfOF0xnfkYaKwY4Ir3nrHFJGqhAQICAAEDAgABDAIAAAAEAAAAAAAA'; const fee = await connection.getFeeForMessage(message); console.log(fee); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") message = "AQABAyRn/PA8jzJtN6oAyB3VR0nfOF0xnfkYaKwY4Ir3nrHFJGqhAQICAAEDAgABDAIAAAAEAAAAAAAA" response = client.get_fee_for_message(message) print(response) ``` ## Notes 1. Returns the fee the network will charge for a particular message 2. Useful for calculating transaction fees more accurately 3. Preferred over older fee calculation methods 4. Different commitment levels can be specified 5. Returns null if the message is invalid or can't be processed ## Best Practices 1. Use this method for accurate fee prediction before sending transactions 2. Provide a valid base64-encoded message to avoid null responses 3. Handle null responses appropriately in your application 4. Handle network errors and retry when appropriate 5. Use this method when precise fee calculation is needed # getFirstAvailableBlock Source: https://docs.orbitflare.com/rpc/methods/getFirstAvailableBlock Returns the slot of the lowest confirmed block that the node has information about ## Parameters This method does not take any parameters. ## Response The slot of the lowest confirmed block available ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getFirstAvailableBlock" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get first available block const firstBlock = await connection.getFirstAvailableBlock(); console.log('First available block:', firstBlock); // Get block range async function getAvailableBlockRange() { const firstBlock = await connection.getFirstAvailableBlock(); const currentSlot = await connection.getSlot(); return { firstBlock, currentSlot, totalBlocks: currentSlot - firstBlock + 1 }; } ``` ## Notes 1. Returns the slot of the lowest confirmed block available 2. This is useful for determining the node's available history 3. The value may change as the node prunes old blocks 4. Different nodes may return different values 5. The response is immediate as it reads from the current state ## Best Practices 1. Use this method to determine available block range 2. Consider node pruning when querying historical blocks 3. Cache results when appropriate to reduce RPC load 4. Handle cases where requested blocks are not available 5. Use in conjunction with other block methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Block information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Block Range Verification** ```typescript theme={null} async function verifyBlockRange(startSlot: number, endSlot: number) { const firstBlock = await connection.getFirstAvailableBlock(); const currentSlot = await connection.getSlot(); if (startSlot < firstBlock) { throw new Error(`Start slot ${startSlot} is before first available block ${firstBlock}`); } if (endSlot > currentSlot) { throw new Error(`End slot ${endSlot} is after current slot ${currentSlot}`); } return { startSlot, endSlot, available: true }; } ``` 2. **Historical Data Availability** ```typescript theme={null} interface BlockAvailability { firstAvailable: number; currentSlot: number; availableRange: number; requestedSlot: number; isAvailable: boolean; } async function checkBlockAvailability(slot: number): Promise { const firstBlock = await connection.getFirstAvailableBlock(); const currentSlot = await connection.getSlot(); return { firstAvailable: firstBlock, currentSlot, availableRange: currentSlot - firstBlock + 1, requestedSlot: slot, isAvailable: slot >= firstBlock && slot <= currentSlot }; } ``` 3. **Block History Analysis** ```typescript theme={null} async function analyzeBlockHistory() { const firstBlock = await connection.getFirstAvailableBlock(); const currentSlot = await connection.getSlot(); const blocks = await connection.getBlocks(firstBlock, currentSlot); const metrics = { firstBlock, currentSlot, totalSlots: currentSlot - firstBlock + 1, availableBlocks: blocks.length, missingBlocks: (currentSlot - firstBlock + 1) - blocks.length, availabilityPercentage: (blocks.length / (currentSlot - firstBlock + 1)) * 100 }; return metrics; } ``` # getGenesisHash Source: https://docs.orbitflare.com/rpc/methods/getGenesisHash Returns the genesis hash of the cluster ## Parameters This method does not take any parameters. ## Response The genesis hash as a base-58 encoded string ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getGenesisHash" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get genesis hash const genesisHash = await connection.getGenesisHash(); console.log('Genesis hash:', genesisHash); // Verify cluster async function verifyCluster(expectedHash: string) { const actualHash = await connection.getGenesisHash(); if (actualHash !== expectedHash) { throw new Error(`Connected to wrong cluster. Expected ${expectedHash}, got ${actualHash}`); } console.log('Connected to correct cluster'); } ``` ## Notes 1. Returns the genesis hash of the cluster 2. The genesis hash is unique to each cluster 3. This can be used to verify which cluster you're connected to 4. The hash is base-58 encoded 5. The response is immediate as it reads from the current state ## Best Practices 1. Use this method to verify cluster connection 2. Cache the result to reduce RPC load 3. Compare against known genesis hashes for different clusters 4. Use in conjunction with other cluster verification methods 5. Handle network errors appropriately ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Genesis hash unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Cluster Verification** ```typescript theme={null} const CLUSTER_HASHES = { mainnet: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d', testnet: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY', devnet: 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG' }; async function verifyClusterConnection(expectedCluster: keyof typeof CLUSTER_HASHES) { const genesisHash = await connection.getGenesisHash(); const expectedHash = CLUSTER_HASHES[expectedCluster]; if (genesisHash !== expectedHash) { throw new Error(`Connected to wrong cluster. Expected ${expectedCluster} (${expectedHash}), got ${genesisHash}`); } return { cluster: expectedCluster, genesisHash, verified: true }; } ``` 2. **Cluster Detection** ```typescript theme={null} async function detectCluster() { const genesisHash = await connection.getGenesisHash(); for (const [cluster, hash] of Object.entries(CLUSTER_HASHES)) { if (hash === genesisHash) { return cluster; } } throw new Error('Unknown cluster'); } ``` 3. **Network Configuration** ```typescript theme={null} interface NetworkConfig { cluster: string; genesisHash: string; rpcEndpoint: string; wsEndpoint: string; } async function configureNetwork(endpoint: string): Promise { const connection = new Connection(endpoint); const genesisHash = await connection.getGenesisHash(); const cluster = await detectCluster(); return { cluster, genesisHash, rpcEndpoint: endpoint, wsEndpoint: endpoint.replace('http', 'ws') }; } ``` # getHealth Source: https://docs.orbitflare.com/rpc/methods/getHealth Returns the current health status of the node ## Parameters This method does not take any parameters. ## Response One of the following status values: * `"ok"`: Node is healthy and up-to-date * `"behind"`: Node is behind by some number of slots * `"unknown"`: Node health cannot be determined ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getHealth" }' ``` ### Alternative HTTP GET Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY/health ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const health = await connection.getHealth(); console.log('Node health:', health); // Example: Check if node is healthy const isHealthy = health === 'ok'; if (!isHealthy) { console.warn('Node is not healthy:', health); } ``` ## Notes 1. This method is commonly used for load balancer health checks 2. The HTTP GET endpoint `/health` provides the same information 3. A node is considered "behind" if it's more than `HEALTH_CHECK_SLOT_DISTANCE` slots from the latest cluster slot 4. The "unknown" status typically indicates the node is bootstrapping or having issues ## Best Practices 1. Use this endpoint for basic health monitoring 2. Implement circuit breakers based on health status 3. Consider using more detailed methods for specific health metrics: * `getVersion` for software version * `getSlot` for slot progress * `getBlockHeight` for block height 4. Set appropriate timeouts for health checks 5. Handle all possible response values ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32603 | Internal error | Node may be experiencing issues | | 503 | Service Unavailable | Node is not ready to handle requests | ## Use Cases 1. **Load Balancer Configuration** ```nginx theme={null} location /health { proxy_pass http://backend; proxy_next_upstream error timeout http_503; } ``` 2. **Health Monitoring** ```typescript theme={null} async function monitorHealth(endpoint: string) { const connection = new Connection(endpoint); try { const health = await connection.getHealth(); if (health !== 'ok') { alertNodeUnhealthy(endpoint, health); } } catch (error) { alertNodeError(endpoint, error); } } ``` 3. **Client-Side Load Balancing** ```typescript theme={null} async function getHealthyEndpoint(endpoints: string[]) { for (const endpoint of endpoints) { const connection = new Connection(endpoint); try { const health = await connection.getHealth(); if (health === 'ok') { return endpoint; } } catch (error) { continue; } } throw new Error('No healthy endpoints available'); } ``` 4. **System Status Dashboard** ```typescript theme={null} interface NodeStatus { endpoint: string; health: string; lastChecked: Date; } async function updateDashboard(nodes: NodeStatus[]) { for (const node of nodes) { const connection = new Connection(node.endpoint); try { node.health = await connection.getHealth(); } catch (error) { node.health = 'error'; } node.lastChecked = new Date(); } renderDashboard(nodes); } ``` # getHighestSnapshotSlot Source: https://docs.orbitflare.com/rpc/methods/getHighestSnapshotSlot Returns the highest slot information from a snapshot ## Parameters This method does not take any parameters. ## Response Highest full snapshot slot Highest incremental snapshot slot, or null if no incremental snapshots are available ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getHighestSnapshotSlot" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get highest snapshot slot const snapshotSlot = await connection.getHighestSnapshotSlot(); console.log('Highest full snapshot slot:', snapshotSlot.full); if (snapshotSlot.incremental !== null) { console.log('Highest incremental snapshot slot:', snapshotSlot.incremental); } // Check snapshot availability async function checkSnapshotAvailability() { const snapshotSlot = await connection.getHighestSnapshotSlot(); const currentSlot = await connection.getSlot(); return { fullSnapshotAvailable: snapshotSlot.full > 0, incrementalSnapshotAvailable: snapshotSlot.incremental !== null, fullSnapshotSlot: snapshotSlot.full, incrementalSnapshotSlot: snapshotSlot.incremental, currentSlot, fullSnapshotAge: currentSlot - snapshotSlot.full, incrementalSnapshotAge: snapshotSlot.incremental ? currentSlot - snapshotSlot.incremental : null }; } ``` ## Notes 1. Returns information about the highest snapshot slots 2. Full snapshots contain the complete ledger state 3. Incremental snapshots contain changes since the last full snapshot 4. Incremental snapshots may not be available on all nodes 5. The response is immediate as it reads from the current state ## Best Practices 1. Use this method to determine snapshot availability 2. Consider snapshot age when planning operations 3. Cache results when appropriate to reduce RPC load 4. Handle cases where incremental snapshots are not available 5. Use in conjunction with other snapshot methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Snapshot information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Snapshot Analysis** ```typescript theme={null} interface SnapshotMetrics { fullSnapshot: { slot: number; age: number; available: boolean; }; incrementalSnapshot: { slot: number | null; age: number | null; available: boolean; }; currentSlot: number; } async function analyzeSnapshots(): Promise { const snapshotSlot = await connection.getHighestSnapshotSlot(); const currentSlot = await connection.getSlot(); return { fullSnapshot: { slot: snapshotSlot.full, age: currentSlot - snapshotSlot.full, available: snapshotSlot.full > 0 }, incrementalSnapshot: { slot: snapshotSlot.incremental, age: snapshotSlot.incremental ? currentSlot - snapshotSlot.incremental : null, available: snapshotSlot.incremental !== null }, currentSlot }; } ``` 2. **Snapshot Monitoring** ```typescript theme={null} interface SnapshotAlert { type: 'full' | 'incremental'; message: string; currentSlot: number; snapshotSlot: number; age: number; } async function monitorSnapshots(maxAge: number): Promise { const snapshotSlot = await connection.getHighestSnapshotSlot(); const currentSlot = await connection.getSlot(); const alerts: SnapshotAlert[] = []; if (snapshotSlot.full > 0) { const fullAge = currentSlot - snapshotSlot.full; if (fullAge > maxAge) { alerts.push({ type: 'full', message: `Full snapshot is ${fullAge} slots old`, currentSlot, snapshotSlot: snapshotSlot.full, age: fullAge }); } } if (snapshotSlot.incremental !== null) { const incrementalAge = currentSlot - snapshotSlot.incremental; if (incrementalAge > maxAge) { alerts.push({ type: 'incremental', message: `Incremental snapshot is ${incrementalAge} slots old`, currentSlot, snapshotSlot: snapshotSlot.incremental, age: incrementalAge }); } } return alerts; } ``` 3. **Snapshot Verification** ```typescript theme={null} async function verifySnapshotAvailability() { const snapshotSlot = await connection.getHighestSnapshotSlot(); const currentSlot = await connection.getSlot(); if (snapshotSlot.full === 0) { throw new Error('No full snapshots available'); } const fullAge = currentSlot - snapshotSlot.full; if (fullAge > 100000) { // Alert if snapshot is more than 100k slots old console.warn(`Full snapshot is ${fullAge} slots old`); } if (snapshotSlot.incremental === null) { console.warn('No incremental snapshots available'); } else { const incrementalAge = currentSlot - snapshotSlot.incremental; if (incrementalAge > 10000) { // Alert if incremental snapshot is more than 10k slots old console.warn(`Incremental snapshot is ${incrementalAge} slots old`); } } return { fullSnapshot: snapshotSlot.full, incrementalSnapshot: snapshotSlot.incremental, currentSlot, fullAge, incrementalAge: snapshotSlot.incremental ? currentSlot - snapshotSlot.incremental : null }; } ``` # getIdentity Source: https://docs.orbitflare.com/rpc/methods/getIdentity Returns the identity pubkey for the current node ## Parameters This method does not require any parameters. ## Response The identity pubkey of the current node (base-58 encoded) ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getIdentity" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const identity = await connection.getIdentity(); console.log(identity); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") response = client.get_identity() print(response) ``` ## Notes 1. Returns the identity public key of the validator node you're connected to 2. Useful for identifying which node you're communicating with 3. The response is immediate as it reads from the node's configuration 4. Can be used to verify you're connected to the expected node ## Best Practices 1. Use this method when troubleshooting node connectivity issues 2. Compare returned identity with expected node identity for validation 3. Handle network errors and retry when appropriate # getInflationGovernor Source: https://docs.orbitflare.com/rpc/methods/getInflationGovernor Returns the current inflation governor parameters ## Parameters This method does not take any parameters. ## Response Initial inflation percentage from time 0 Terminal inflation percentage Rate per year at which inflation is lowered Percentage of total inflation allocated to the foundation Duration of foundation pool inflation in years ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getInflationGovernor" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get inflation governor parameters const inflationGovernor = await connection.getInflationGovernor(); console.log('Initial inflation:', inflationGovernor.initial); console.log('Terminal inflation:', inflationGovernor.terminal); console.log('Taper rate:', inflationGovernor.taper); console.log('Foundation percentage:', inflationGovernor.foundation); console.log('Foundation term:', inflationGovernor.foundationTerm); // Calculate current inflation rate async function calculateCurrentInflation() { const inflationGovernor = await connection.getInflationGovernor(); const epochInfo = await connection.getEpochInfo(); const yearsSinceGenesis = epochInfo.absoluteSlot / (432000 * 365); // Approximate years const currentInflation = Math.max( inflationGovernor.terminal, inflationGovernor.initial - (inflationGovernor.taper * yearsSinceGenesis) ); return { currentInflation, yearsSinceGenesis, initialInflation: inflationGovernor.initial, terminalInflation: inflationGovernor.terminal, taperRate: inflationGovernor.taper }; } ``` ## Notes 1. Returns the parameters that govern inflation on the Solana network 2. Inflation decreases over time according to the taper rate 3. Foundation inflation is separate from validator inflation 4. The response is immediate as it reads from the current state 5. These parameters are set by governance and can change ## Best Practices 1. Use this method to understand the network's inflation schedule 2. Cache results when appropriate to reduce RPC load 3. Consider the impact of inflation on staking rewards 4. Monitor for changes in inflation parameters 5. Use in conjunction with other inflation-related methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Inflation information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Inflation Analysis** ```typescript theme={null} interface InflationMetrics { currentInflation: number; yearsSinceGenesis: number; initialInflation: number; terminalInflation: number; taperRate: number; foundationPercentage: number; foundationTerm: number; validatorInflation: number; foundationInflation: number; } async function analyzeInflation(): Promise { const inflationGovernor = await connection.getInflationGovernor(); const epochInfo = await connection.getEpochInfo(); const yearsSinceGenesis = epochInfo.absoluteSlot / (432000 * 365); const currentInflation = Math.max( inflationGovernor.terminal, inflationGovernor.initial - (inflationGovernor.taper * yearsSinceGenesis) ); const foundationInflation = yearsSinceGenesis < inflationGovernor.foundationTerm ? currentInflation * inflationGovernor.foundation : 0; return { currentInflation, yearsSinceGenesis, initialInflation: inflationGovernor.initial, terminalInflation: inflationGovernor.terminal, taperRate: inflationGovernor.taper, foundationPercentage: inflationGovernor.foundation, foundationTerm: inflationGovernor.foundationTerm, validatorInflation: currentInflation - foundationInflation, foundationInflation }; } ``` 2. **Staking Reward Projection** ```typescript theme={null} interface StakingProjection { currentAPY: number; futureAPY: number[]; years: number[]; } async function projectStakingRewards( totalStaked: number, totalSupply: number, years: number = 10 ): Promise { const inflationGovernor = await connection.getInflationGovernor(); const epochInfo = await connection.getEpochInfo(); const currentYear = epochInfo.absoluteSlot / (432000 * 365); const yearsArray = Array.from({ length: years }, (_, i) => i); const futureAPY = yearsArray.map(year => { const inflation = Math.max( inflationGovernor.terminal, inflationGovernor.initial - (inflationGovernor.taper * (currentYear + year)) ); const foundationInflation = (currentYear + year) < inflationGovernor.foundationTerm ? inflation * inflationGovernor.foundation : 0; const validatorInflation = inflation - foundationInflation; return (validatorInflation * totalSupply) / totalStaked; }); return { currentAPY: futureAPY[0], futureAPY, years: yearsArray }; } ``` 3. **Inflation Monitoring** ```typescript theme={null} interface InflationAlert { type: 'high' | 'low' | 'change'; message: string; currentInflation: number; expectedInflation: number; difference: number; } async function monitorInflation( threshold: number = 0.01 ): Promise { const inflationGovernor = await connection.getInflationGovernor(); const epochInfo = await connection.getEpochInfo(); const yearsSinceGenesis = epochInfo.absoluteSlot / (432000 * 365); const expectedInflation = Math.max( inflationGovernor.terminal, inflationGovernor.initial - (inflationGovernor.taper * yearsSinceGenesis) ); const currentInflation = await connection.getInflationRate(); const difference = Math.abs(currentInflation - expectedInflation); const alerts: InflationAlert[] = []; if (difference > threshold) { alerts.push({ type: 'change', message: `Inflation differs from expected by ${difference.toFixed(4)}`, currentInflation, expectedInflation, difference }); } if (currentInflation > inflationGovernor.initial) { alerts.push({ type: 'high', message: `Inflation is above initial rate`, currentInflation, expectedInflation, difference }); } if (currentInflation < inflationGovernor.terminal) { alerts.push({ type: 'low', message: `Inflation is below terminal rate`, currentInflation, expectedInflation, difference }); } return alerts; } ``` # getInflationRate Source: https://docs.orbitflare.com/rpc/methods/getInflationRate Returns the current inflation rate of the network ## Parameters This method does not take any parameters. ## Response Total inflation rate as a percentage Inflation rate allocated to validators as a percentage Inflation rate allocated to the foundation as a percentage Current epoch number ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getInflationRate" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get current inflation rate const inflationRate = await connection.getInflationRate(); console.log('Total inflation:', inflationRate.total); console.log('Validator inflation:', inflationRate.validator); console.log('Foundation inflation:', inflationRate.foundation); console.log('Current epoch:', inflationRate.epoch); // Calculate staking APY async function calculateStakingAPY(totalStaked: number, totalSupply: number) { const inflationRate = await connection.getInflationRate(); const validatorAPY = (inflationRate.validator * totalSupply) / totalStaked; return { validatorAPY, totalInflation: inflationRate.total, validatorInflation: inflationRate.validator, foundationInflation: inflationRate.foundation, epoch: inflationRate.epoch }; } ``` ## Notes 1. Returns the current inflation rate and its allocation 2. Total inflation is split between validators and foundation 3. Inflation rates are expressed as percentages 4. The response is immediate as it reads from the current state 5. Inflation rates can change between epochs ## Best Practices 1. Use this method to understand current network inflation 2. Cache results when appropriate to reduce RPC load 3. Consider the impact of inflation on staking rewards 4. Monitor for changes in inflation rates 5. Use in conjunction with other inflation-related methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Inflation information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Inflation Tracking** ```typescript theme={null} interface InflationHistory { epoch: number; total: number; validator: number; foundation: number; timestamp: number; } async function trackInflationHistory( epochs: number = 10 ): Promise { const history: InflationHistory[] = []; const currentEpoch = (await connection.getEpochInfo()).epoch; for (let i = 0; i < epochs; i++) { const epoch = currentEpoch - i; const inflationRate = await connection.getInflationRate(); history.push({ epoch: inflationRate.epoch, total: inflationRate.total, validator: inflationRate.validator, foundation: inflationRate.foundation, timestamp: Date.now() }); // Add delay to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 1000)); } return history; } ``` 2. **Staking Analysis** ```typescript theme={null} interface StakingMetrics { currentAPY: number; projectedAPY: number; totalStaked: number; totalSupply: number; inflationRate: number; validatorInflation: number; } async function analyzeStakingMetrics(): Promise { const inflationRate = await connection.getInflationRate(); const supply = await connection.getSupply(); const voteAccounts = await connection.getVoteAccounts(); const totalStaked = voteAccounts.current .concat(voteAccounts.delinquent) .reduce((sum, account) => sum + account.activatedStake, 0); const currentAPY = (inflationRate.validator * supply.value.total) / totalStaked; // Project future APY based on current trends const projectedAPY = currentAPY * 0.95; // Assuming 5% decrease return { currentAPY, projectedAPY, totalStaked, totalSupply: supply.value.total, inflationRate: inflationRate.total, validatorInflation: inflationRate.validator }; } ``` 3. **Inflation Alerts** ```typescript theme={null} interface InflationAlert { type: 'high' | 'low' | 'change'; message: string; currentRate: number; previousRate: number; difference: number; } class InflationMonitor { private previousRate: number | null = null; private threshold: number; constructor(threshold: number = 0.01) { this.threshold = threshold; } async checkInflation(): Promise { const inflationRate = await connection.getInflationRate(); const alerts: InflationAlert[] = []; if (this.previousRate !== null) { const difference = Math.abs(inflationRate.total - this.previousRate); if (difference > this.threshold) { alerts.push({ type: 'change', message: `Inflation rate changed by ${difference.toFixed(4)}`, currentRate: inflationRate.total, previousRate: this.previousRate, difference }); } } // Check for unusually high or low rates if (inflationRate.total > 0.08) { // 8% threshold alerts.push({ type: 'high', message: `Inflation rate is unusually high: ${inflationRate.total}`, currentRate: inflationRate.total, previousRate: this.previousRate || inflationRate.total, difference: 0 }); } if (inflationRate.total < 0.01) { // 1% threshold alerts.push({ type: 'low', message: `Inflation rate is unusually low: ${inflationRate.total}`, currentRate: inflationRate.total, previousRate: this.previousRate || inflationRate.total, difference: 0 }); } this.previousRate = inflationRate.total; return alerts; } } ``` # getInflationReward Source: https://docs.orbitflare.com/rpc/methods/getInflationReward Returns the inflation reward for a list of addresses for an epoch ## Parameters An array of addresses to query, as base-58 encoded strings An epoch for which the reward occurs. If omitted, the previous epoch will be used The minimum slot that the request can be evaluated at ## Response Epoch for which reward occured The slot in which the rewards are effective Reward amount in lamports Post balance of the account in lamports Vote account commission when the reward was credited ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getInflationReward", "params": [ [ "6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu", "BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2" ], {"epoch": 2} ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const addresses = [ '6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu', 'BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2' ]; const rewards = await connection.getInflationReward(addresses, 2); console.log(rewards); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") addresses = [ "6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu", "BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2" ] response = client.get_inflation_reward(addresses, 2) print(response) ``` ## Notes 1. Returns inflation rewards for a list of addresses 2. Results are returned as an array in the same order as the input addresses 3. A null value in the result array means that the address has not received rewards 4. Commission field is only present for validators (null for regular accounts) 5. Rewards are calculated at the end of each epoch ## Best Practices 1. Provide a list of valid addresses to avoid null responses 2. Specify the epoch parameter if you need historical reward data 3. Handle null values in the response array appropriately 4. Use this method to track rewards for staking accounts 5. Handle network errors and retry when appropriate # getLargestAccounts Source: https://docs.orbitflare.com/rpc/methods/getLargestAccounts Returns the 20 largest accounts, by lamport balance ## Parameters Configuration object containing the following optional fields: The level of commitment to use: * `processed`: Latest block (unconfirmed) * `confirmed`: Confirmed by supermajority * `finalized`: Finalized by supermajority Filter results by account type: * `circulating`: Filter to circulating accounts * `nonCirculating`: Filter to non-circulating accounts ## Response The slot the request was processed at Array of objects containing account information: Address of the account (base-58 encoded) Number of lamports in the account ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getLargestAccounts" }' ``` ### Request with Filter ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getLargestAccounts", "params": [ { "filter": "circulating" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get all largest accounts const largestAccounts = await connection.getLargestAccounts(); console.log('Largest accounts:', largestAccounts); // Get circulating accounts only const circulatingAccounts = await connection.getLargestAccounts({ filter: 'circulating' }); console.log('Largest circulating accounts:', circulatingAccounts); ``` ## Notes 1. Returns the top 20 accounts by lamport balance 2. Results are sorted in descending order by balance 3. Circulating accounts are those not owned by the system program 4. Non-circulating accounts are typically owned by the system program 5. The response is immediate as it reads from the current state ## Best Practices 1. Use appropriate commitment level based on your needs: * `processed` for UI updates * `confirmed` for most operations * `finalized` for critical operations 2. Use the `filter` parameter to focus on relevant accounts 3. Convert lamports to SOL by dividing by 1e9 when displaying to users 4. Consider caching results for UI updates to reduce RPC load ## Common Errors | Code | Message | Solution | | - | - | - | | -32602 | Invalid param: Invalid filter | Use either 'circulating' or 'nonCirculating' | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Account information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Network Analysis** ```typescript theme={null} async function analyzeNetworkDistribution() { const accounts = await connection.getLargestAccounts(); const total = accounts.reduce((sum, acc) => sum + acc.lamports, 0); const top20Percentage = accounts.reduce((sum, acc) => sum + acc.lamports, 0) / total; console.log(`Top 20 accounts hold ${top20Percentage * 100}% of total SOL`); } ``` 2. **Token Distribution Monitoring** ```typescript theme={null} async function monitorTokenDistribution() { const circulating = await connection.getLargestAccounts({ filter: 'circulating' }); const nonCirculating = await connection.getLargestAccounts({ filter: 'nonCirculating' }); const circulatingTotal = circulating.reduce((sum, acc) => sum + acc.lamports, 0); const nonCirculatingTotal = nonCirculating.reduce((sum, acc) => sum + acc.lamports, 0); console.log('Circulating supply:', circulatingTotal / 1e9, 'SOL'); console.log('Non-circulating supply:', nonCirculatingTotal / 1e9, 'SOL'); } ``` 3. **Whale Watching** ```typescript theme={null} interface WhaleAlert { address: string; balance: number; change: number; } async function trackWhaleMovements(previousAccounts: any[]) { const currentAccounts = await connection.getLargestAccounts(); const alerts: WhaleAlert[] = []; for (const current of currentAccounts) { const previous = previousAccounts.find(p => p.address === current.address); if (previous) { const change = current.lamports - previous.lamports; if (Math.abs(change) > 1e9) { // Alert on changes > 1 SOL alerts.push({ address: current.address, balance: current.lamports / 1e9, change: change / 1e9 }); } } } return alerts; } ``` # getLatestBlockhash Source: https://docs.orbitflare.com/rpc/methods/getLatestBlockhash Returns the latest blockhash ## Parameters The commitment level to use for the response The minimum slot that the request can be evaluated at ## Response The slot this request was processed in The latest blockhash (base-58 encoded) Last valid block height for the blockhash ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getLatestBlockhash", "params": [ {"commitment": "processed"} ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); console.log(blockhash, lastValidBlockHeight); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") response = client.get_latest_blockhash() print(response) ``` ## Notes 1. This method returns the latest blockhash and how long it will be valid for 2. Replaces the deprecated `getRecentBlockhash` method 3. The blockhash is used for transaction processing and deduplication 4. The blockhash is valid until the specified lastValidBlockHeight is reached 5. Different commitment levels can be specified for the response ## Best Practices 1. Use blockhash for transaction submission and signing 2. Monitor lastValidBlockHeight to determine blockhash validity 3. Refresh blockhash if lastValidBlockHeight is reached or exceeded 4. Handle network errors and retry when appropriate 5. Cache the blockhash for multiple transactions within its validity period # getLeaderSchedule Source: https://docs.orbitflare.com/rpc/methods/getLeaderSchedule Returns the leader schedule for an epoch ## Parameters Fetch the leader schedule for the epoch containing this slot Commitment level (processed, confirmed, finalized) Only return results for this validator identity (base-58 encoded) ## Response Slot at which the request was processed Dictionary of validator identities to their assigned leader slots ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getLeaderSchedule" }' ``` ### Request for Specific Validator ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getLeaderSchedule", "params": [{ "identity": "VALIDATOR_IDENTITY" }] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get leader schedule const leaderSchedule = await connection.getLeaderSchedule(); console.log('Leader schedule:', leaderSchedule.value); // Get validator's leader slots async function getValidatorLeaderSlots(validatorIdentity: string) { const schedule = await connection.getLeaderSchedule({ identity: validatorIdentity }); if (!schedule.value) { return { hasSlots: false, slots: [] }; } const slots = schedule.value[validatorIdentity] || []; return { hasSlots: slots.length > 0, slots, totalSlots: slots.length, context: schedule.context }; } ``` ## Notes 1. Returns the leader schedule for the current or specified epoch 2. Can filter to show only a specific validator's slots 3. Leader slots are assigned to validators based on stake weight 4. The schedule is fixed for the duration of an epoch 5. Response includes the slot at which the request was processed ## Best Practices 1. Use appropriate commitment level based on your needs 2. Filter by validator identity when only interested in specific validators 3. Cache results when appropriate to reduce RPC load 4. Monitor for changes in leader schedule 5. Use in conjunction with other validator-related methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check slot and identity parameters | | -32007 | Schedule information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Validator Analysis** ```typescript theme={null} interface ValidatorMetrics { identity: string; totalSlots: number; slotDistribution: number[]; averageGap: number; maxGap: number; minGap: number; } async function analyzeValidatorSlots( validatorIdentity: string ): Promise { const schedule = await connection.getLeaderSchedule({ identity: validatorIdentity }); if (!schedule.value || !schedule.value[validatorIdentity]) { throw new Error('No leader slots found for validator'); } const slots = schedule.value[validatorIdentity].sort((a, b) => a - b); const gaps = []; for (let i = 1; i < slots.length; i++) { gaps.push(slots[i] - slots[i - 1]); } return { identity: validatorIdentity, totalSlots: slots.length, slotDistribution: slots, averageGap: gaps.reduce((sum, gap) => sum + gap, 0) / gaps.length, maxGap: Math.max(...gaps), minGap: Math.min(...gaps) }; } ``` 2. **Leader Schedule Monitoring** ```typescript theme={null} interface ScheduleAlert { type: 'change' | 'gap' | 'distribution'; message: string; validator: string; details: any; } class ScheduleMonitor { private previousSchedule: Map = new Map(); async monitorSchedule( maxGap: number = 100, minSlots: number = 10 ): Promise { const schedule = await connection.getLeaderSchedule(); const alerts: ScheduleAlert[] = []; for (const [validator, slots] of Object.entries(schedule.value)) { const previousSlots = this.previousSchedule.get(validator) || []; // Check for significant changes if (previousSlots.length > 0) { const change = Math.abs(slots.length - previousSlots.length); if (change > minSlots) { alerts.push({ type: 'change', message: `Validator ${validator} slot count changed by ${change}`, validator, details: { previous: previousSlots.length, current: slots.length } }); } } // Check for large gaps const sortedSlots = [...slots].sort((a, b) => a - b); for (let i = 1; i < sortedSlots.length; i++) { const gap = sortedSlots[i] - sortedSlots[i - 1]; if (gap > maxGap) { alerts.push({ type: 'gap', message: `Large gap of ${gap} slots found for validator ${validator}`, validator, details: { gap, slot1: sortedSlots[i - 1], slot2: sortedSlots[i] } }); } } this.previousSchedule.set(validator, slots); } return alerts; } } ``` 3. **Epoch Planning** ```typescript theme={null} interface EpochPlan { currentEpoch: number; nextEpoch: number; slotsPerEpoch: number; validatorSlots: Map; slotDistribution: Map; } async function planEpoch(): Promise { const schedule = await connection.getLeaderSchedule(); const epochInfo = await connection.getEpochInfo(); const validatorSlots = new Map(); const slotDistribution = new Map(); for (const [validator, slots] of Object.entries(schedule.value)) { validatorSlots.set(validator, slots.length); for (const slot of slots) { if (!slotDistribution.has(slot)) { slotDistribution.set(slot, []); } slotDistribution.get(slot)!.push(validator); } } return { currentEpoch: epochInfo.epoch, nextEpoch: epochInfo.epoch + 1, slotsPerEpoch: epochInfo.slotsInEpoch, validatorSlots, slotDistribution }; } ``` # getMaxRetransmitSlot Source: https://docs.orbitflare.com/rpc/methods/getMaxRetransmitSlot Returns the highest slot that can be retransmitted ## Parameters This method does not take any parameters. ## Response The highest slot that can be retransmitted ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getMaxRetransmitSlot" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get max retransmit slot const maxRetransmitSlot = await connection.getMaxRetransmitSlot(); console.log('Max retransmit slot:', maxRetransmitSlot); // Check if a slot can be retransmitted async function canRetransmitSlot(slot: number) { const maxRetransmitSlot = await connection.getMaxRetransmitSlot(); return slot <= maxRetransmitSlot; } // Get retransmit range async function getRetransmitRange() { const maxRetransmitSlot = await connection.getMaxRetransmitSlot(); const firstAvailableBlock = await connection.getFirstAvailableBlock(); return { startSlot: firstAvailableBlock, endSlot: maxRetransmitSlot, totalSlots: maxRetransmitSlot - firstAvailableBlock + 1 }; } ``` ## Notes 1. Returns the highest slot that can be retransmitted 2. Used to determine the range of slots available for retransmission 3. The response is immediate as it reads from the current state 4. Slots beyond this value cannot be retransmitted 5. This value can change as the node prunes old data ## Best Practices 1. Use this method to determine retransmission availability 2. Cache results when appropriate to reduce RPC load 3. Consider the impact of slot pruning 4. Monitor for changes in max retransmit slot 5. Use in conjunction with other slot-related methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Slot information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Retransmission Planning** ```typescript theme={null} interface RetransmitPlan { startSlot: number; endSlot: number; totalSlots: number; availableSlots: number[]; missingSlots: number[]; } async function planRetransmission( targetSlots: number[] ): Promise { const maxRetransmitSlot = await connection.getMaxRetransmitSlot(); const firstAvailableBlock = await connection.getFirstAvailableBlock(); const availableSlots = targetSlots.filter( slot => slot >= firstAvailableBlock && slot <= maxRetransmitSlot ); const missingSlots = targetSlots.filter( slot => slot < firstAvailableBlock || slot > maxRetransmitSlot ); return { startSlot: firstAvailableBlock, endSlot: maxRetransmitSlot, totalSlots: maxRetransmitSlot - firstAvailableBlock + 1, availableSlots, missingSlots }; } ``` 2. **Slot Availability Monitoring** ```typescript theme={null} interface SlotAlert { type: 'pruned' | 'unavailable'; message: string; slot: number; maxRetransmitSlot: number; } class SlotMonitor { private previousMaxSlot: number | null = null; async monitorSlots( targetSlots: number[] ): Promise { const maxRetransmitSlot = await connection.getMaxRetransmitSlot(); const alerts: SlotAlert[] = []; // Check for pruned slots if (this.previousMaxSlot !== null && maxRetransmitSlot < this.previousMaxSlot) { alerts.push({ type: 'pruned', message: `Slots ${maxRetransmitSlot + 1} to ${this.previousMaxSlot} have been pruned`, slot: this.previousMaxSlot, maxRetransmitSlot }); } // Check target slots availability for (const slot of targetSlots) { if (slot > maxRetransmitSlot) { alerts.push({ type: 'unavailable', message: `Slot ${slot} is beyond max retransmit slot`, slot, maxRetransmitSlot }); } } this.previousMaxSlot = maxRetransmitSlot; return alerts; } } ``` 3. **Historical Data Management** ```typescript theme={null} interface HistoricalData { availableRange: { start: number; end: number; total: number; }; prunedSlots: number[]; availableSlots: number[]; metadata: { timestamp: number; maxRetransmitSlot: number; }; } async function manageHistoricalData( targetRange: { start: number; end: number } ): Promise { const maxRetransmitSlot = await connection.getMaxRetransmitSlot(); const firstAvailableBlock = await connection.getFirstAvailableBlock(); const availableSlots = []; const prunedSlots = []; for (let slot = targetRange.start; slot <= targetRange.end; slot++) { if (slot >= firstAvailableBlock && slot <= maxRetransmitSlot) { availableSlots.push(slot); } else { prunedSlots.push(slot); } } return { availableRange: { start: firstAvailableBlock, end: maxRetransmitSlot, total: maxRetransmitSlot - firstAvailableBlock + 1 }, prunedSlots, availableSlots, metadata: { timestamp: Date.now(), maxRetransmitSlot } }; } ``` # getMaxShredInsertSlot Source: https://docs.orbitflare.com/rpc/methods/getMaxShredInsertSlot Returns the highest slot that can be inserted into the shred database ## Parameters This method does not take any parameters. ## Response The highest slot that can be inserted into the shred database ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getMaxShredInsertSlot" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get max shred insert slot const maxShredInsertSlot = await connection.getMaxShredInsertSlot(); console.log('Max shred insert slot:', maxShredInsertSlot); // Check if a slot can be inserted async function canInsertSlot(slot: number) { const maxShredInsertSlot = await connection.getMaxShredInsertSlot(); return slot <= maxShredInsertSlot; } // Get insert range async function getInsertRange() { const maxShredInsertSlot = await connection.getMaxShredInsertSlot(); const firstAvailableBlock = await connection.getFirstAvailableBlock(); return { startSlot: firstAvailableBlock, endSlot: maxShredInsertSlot, totalSlots: maxShredInsertSlot - firstAvailableBlock + 1 }; } ``` ## Notes 1. Returns the highest slot that can be inserted into the shred database 2. Used to determine the range of slots available for shred insertion 3. The response is immediate as it reads from the current state 4. Slots beyond this value cannot be inserted 5. This value can change as the node processes new slots ## Best Practices 1. Use this method to determine shred insertion availability 2. Cache results when appropriate to reduce RPC load 3. Consider the impact of slot processing 4. Monitor for changes in max shred insert slot 5. Use in conjunction with other slot-related methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32007 | Slot information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Shred Insertion Planning** ```typescript theme={null} interface InsertPlan { startSlot: number; endSlot: number; totalSlots: number; availableSlots: number[]; unavailableSlots: number[]; } async function planShredInsertion( targetSlots: number[] ): Promise { const maxShredInsertSlot = await connection.getMaxShredInsertSlot(); const firstAvailableBlock = await connection.getFirstAvailableBlock(); const availableSlots = targetSlots.filter( slot => slot >= firstAvailableBlock && slot <= maxShredInsertSlot ); const unavailableSlots = targetSlots.filter( slot => slot < firstAvailableBlock || slot > maxShredInsertSlot ); return { startSlot: firstAvailableBlock, endSlot: maxShredInsertSlot, totalSlots: maxShredInsertSlot - firstAvailableBlock + 1, availableSlots, unavailableSlots }; } ``` 2. **Slot Processing Monitoring** ```typescript theme={null} interface ProcessingAlert { type: 'lag' | 'gap' | 'unavailable'; message: string; slot: number; maxShredInsertSlot: number; } class ProcessingMonitor { private previousMaxSlot: number | null = null; async monitorProcessing( targetSlots: number[] ): Promise { const maxShredInsertSlot = await connection.getMaxShredInsertSlot(); const alerts: ProcessingAlert[] = []; // Check for processing lag if (this.previousMaxSlot !== null) { const expectedProgress = this.previousMaxSlot + 1; if (maxShredInsertSlot < expectedProgress) { alerts.push({ type: 'lag', message: `Processing lag detected: expected ${expectedProgress}, got ${maxShredInsertSlot}`, slot: maxShredInsertSlot, maxShredInsertSlot }); } } // Check target slots availability for (const slot of targetSlots) { if (slot > maxShredInsertSlot) { alerts.push({ type: 'unavailable', message: `Slot ${slot} is beyond max shred insert slot`, slot, maxShredInsertSlot }); } } this.previousMaxSlot = maxShredInsertSlot; return alerts; } } ``` 3. **Shred Database Management** ```typescript theme={null} interface ShredDatabaseMetrics { availableRange: { start: number; end: number; total: number; }; processingRate: number; lag: number; metadata: { timestamp: number; maxShredInsertSlot: number; }; } class ShredDatabaseManager { private previousMetrics: { timestamp: number; maxSlot: number; } | null = null; async getMetrics(): Promise { const maxShredInsertSlot = await connection.getMaxShredInsertSlot(); const firstAvailableBlock = await connection.getFirstAvailableBlock(); const currentTime = Date.now(); let processingRate = 0; let lag = 0; if (this.previousMetrics !== null) { const timeDiff = (currentTime - this.previousMetrics.timestamp) / 1000; // seconds const slotDiff = maxShredInsertSlot - this.previousMetrics.maxSlot; processingRate = slotDiff / timeDiff; const currentSlot = await connection.getSlot(); lag = currentSlot - maxShredInsertSlot; } this.previousMetrics = { timestamp: currentTime, maxSlot: maxShredInsertSlot }; return { availableRange: { start: firstAvailableBlock, end: maxShredInsertSlot, total: maxShredInsertSlot - firstAvailableBlock + 1 }, processingRate, lag, metadata: { timestamp: currentTime, maxShredInsertSlot } }; } } ``` # getMinimumBalanceForRentExemption Source: https://docs.orbitflare.com/rpc/methods/getMinimumBalanceForRentExemption Returns the minimum balance required for an account to be rent exempt ## Parameters The length of the account data in bytes Commitment level (processed, confirmed, finalized) ## Response The minimum balance required for rent exemption in lamports ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getMinimumBalanceForRentExemption", "params": [1000] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get minimum balance for rent exemption const dataLength = 1000; // bytes const minBalance = await connection.getMinimumBalanceForRentExemption(dataLength); console.log('Minimum balance:', minBalance); // Calculate account creation cost async function calculateAccountCost( dataLength: number, owner: string ) { const minBalance = await connection.getMinimumBalanceForRentExemption(dataLength); const rentExempt = minBalance; return { dataLength, minBalance, rentExempt, owner }; } // Check if account has sufficient balance async function hasSufficientBalance( account: string, dataLength: number ) { const [balance, minBalance] = await Promise.all([ connection.getBalance(account), connection.getMinimumBalanceForRentExemption(dataLength) ]); return { account, currentBalance: balance, requiredBalance: minBalance, hasSufficientBalance: balance >= minBalance, difference: balance - minBalance }; } ``` ## Notes 1. Returns the minimum balance required for an account to be rent exempt 2. The balance is returned in lamports 3. The required balance depends on the account's data length 4. The response is immediate as it reads from the current state 5. This value can change with network upgrades ## Best Practices 1. Use this method to determine account creation costs 2. Cache results when appropriate to reduce RPC load 3. Consider the impact of data length on costs 4. Monitor for changes in rent exemption requirements 5. Use in conjunction with other account-related methods ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check dataLength parameter | | -32007 | Balance information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Account Cost Analysis** ```typescript theme={null} interface AccountCost { dataLength: number; minBalance: number; rentExempt: number; owner: string; metadata: { timestamp: number; commitment: string; }; } async function analyzeAccountCost( dataLength: number, owner: string, commitment: string = 'confirmed' ): Promise { const minBalance = await connection.getMinimumBalanceForRentExemption( dataLength, { commitment } ); return { dataLength, minBalance, rentExempt: minBalance, owner, metadata: { timestamp: Date.now(), commitment } }; } ``` 2. **Balance Monitoring** ```typescript theme={null} interface BalanceAlert { type: 'insufficient' | 'warning' | 'sufficient'; message: string; account: string; currentBalance: number; requiredBalance: number; difference: number; } class BalanceMonitor { private previousBalances: Map = new Map(); async monitorBalance( account: string, dataLength: number, warningThreshold: number = 0.1 // 10% above minimum ): Promise { const [balance, minBalance] = await Promise.all([ connection.getBalance(account), connection.getMinimumBalanceForRentExemption(dataLength) ]); const alerts: BalanceAlert[] = []; const difference = balance - minBalance; const warningLevel = minBalance * warningThreshold; if (difference < 0) { alerts.push({ type: 'insufficient', message: `Account ${account} has insufficient balance for rent exemption`, account, currentBalance: balance, requiredBalance: minBalance, difference }); } else if (difference < warningLevel) { alerts.push({ type: 'warning', message: `Account ${account} balance is close to minimum required`, account, currentBalance: balance, requiredBalance: minBalance, difference }); } else { alerts.push({ type: 'sufficient', message: `Account ${account} has sufficient balance`, account, currentBalance: balance, requiredBalance: minBalance, difference }); } this.previousBalances.set(account, balance); return alerts; } } ``` 3. **Account Planning** ```typescript theme={null} interface AccountPlan { accounts: Array<{ purpose: string; dataLength: number; minBalance: number; estimatedUsage: string; }>; totalCost: number; recommendations: string[]; } async function planAccounts( accountSpecs: Array<{ purpose: string; dataLength: number; estimatedUsage: string; }> ): Promise { const accounts = await Promise.all( accountSpecs.map(async spec => { const minBalance = await connection.getMinimumBalanceForRentExemption( spec.dataLength ); return { ...spec, minBalance }; }) ); const totalCost = accounts.reduce( (sum, account) => sum + account.minBalance, 0 ); const recommendations = []; // Check for potential optimizations const largeAccounts = accounts.filter( account => account.dataLength > 10000 ); if (largeAccounts.length > 0) { recommendations.push( 'Consider optimizing data storage for large accounts' ); } const expensiveAccounts = accounts.filter( account => account.minBalance > 1e9 // More than 1 SOL ); if (expensiveAccounts.length > 0) { recommendations.push( 'Review high-cost accounts for potential optimizations' ); } return { accounts, totalCost, recommendations }; } ``` # getMultipleAccounts Source: https://docs.orbitflare.com/rpc/methods/getMultipleAccounts Returns the account information for multiple accounts ## Parameters Array of account public keys (base-58 encoded) Commitment level (processed, confirmed, finalized) Encoding format for account data (base58, base64, jsonParsed) Offset into account data to start reading Length of data to read ## Response Slot at which the request was processed Array of account information objects or null for non-existent accounts Account data in the specified encoding Whether the account is executable Account balance in lamports Account owner (base-58 encoded) Epoch at which the account will next owe rent ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getMultipleAccounts", "params": [ ["ACCOUNT1", "ACCOUNT2", "ACCOUNT3"] ] }' ``` ### Request with Encoding ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getMultipleAccounts", "params": [ ["ACCOUNT1", "ACCOUNT2"], { "encoding": "base64", "dataSlice": { "offset": 0, "length": 100 } } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get multiple accounts const accounts = await connection.getMultipleAccounts([ new PublicKey('ACCOUNT1'), new PublicKey('ACCOUNT2') ]); console.log('Accounts:', accounts.value); // Get account data slices async function getAccountDataSlices( pubkeys: PublicKey[], offset: number, length: number ) { const accounts = await connection.getMultipleAccounts(pubkeys, { encoding: 'base64', dataSlice: { offset, length } }); return accounts.value.map((account, index) => ({ pubkey: pubkeys[index].toBase58(), exists: account !== null, data: account?.data[0], lamports: account?.lamports })); } ``` ## Notes 1. Returns information for multiple accounts in a single request 2. Non-existent accounts are returned as null 3. Account data can be sliced to reduce response size 4. The response is immediate as it reads from the current state 5. Different encodings can be specified for account data ## Best Practices 1. Use this method to efficiently fetch multiple accounts 2. Consider using dataSlice to reduce response size 3. Cache results when appropriate to reduce RPC load 4. Handle null values for non-existent accounts 5. Use appropriate encoding based on your needs ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check pubkeys and config parameters | | -32007 | Account information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Account Batch Processing** ```typescript theme={null} interface AccountBatch { accounts: Array<{ pubkey: string; exists: boolean; lamports: number; owner: string; executable: boolean; }>; totalLamports: number; metadata: { timestamp: number; slot: number; }; } async function processAccountBatch( pubkeys: PublicKey[], chunkSize: number = 100 ): Promise { const accounts: AccountBatch['accounts'] = []; let totalLamports = 0; for (let i = 0; i < pubkeys.length; i += chunkSize) { const chunk = pubkeys.slice(i, i + chunkSize); const result = await connection.getMultipleAccounts(chunk); chunk.forEach((pubkey, index) => { const account = result.value[index]; const exists = account !== null; accounts.push({ pubkey: pubkey.toBase58(), exists, lamports: account?.lamports || 0, owner: account?.owner.toBase58() || '', executable: account?.executable || false }); if (exists) { totalLamports += account.lamports; } }); } return { accounts, totalLamports, metadata: { timestamp: Date.now(), slot: accounts[0]?.slot || 0 } }; } ``` 2. **Account Data Analysis** ```typescript theme={null} interface AccountDataAnalysis { pubkey: string; dataSize: number; dataHash: string; lamports: number; owner: string; metadata: { timestamp: number; slot: number; }; } async function analyzeAccountData( pubkeys: PublicKey[], dataSlice?: { offset: number; length: number } ): Promise { const accounts = await connection.getMultipleAccounts(pubkeys, { encoding: 'base64', dataSlice }); return accounts.value.map((account, index) => { const pubkey = pubkeys[index].toBase58(); if (!account) { return { pubkey, dataSize: 0, dataHash: '', lamports: 0, owner: '', metadata: { timestamp: Date.now(), slot: accounts.context.slot } }; } const data = account.data[0]; const dataSize = Buffer.from(data, 'base64').length; return { pubkey, dataSize, dataHash: require('crypto') .createHash('sha256') .update(data) .digest('hex'), lamports: account.lamports, owner: account.owner.toBase58(), metadata: { timestamp: Date.now(), slot: accounts.context.slot } }; }); } ``` 3. **Account Monitoring** ```typescript theme={null} interface AccountChange { pubkey: string; type: 'created' | 'deleted' | 'modified'; previous: any; current: any; difference: number; } class AccountMonitor { private previousAccounts: Map = new Map(); async monitorAccounts( pubkeys: PublicKey[], interval: number = 5000 ): Promise { const accounts = await connection.getMultipleAccounts(pubkeys); const changes: AccountChange[] = []; pubkeys.forEach((pubkey, index) => { const address = pubkey.toBase58(); const current = accounts.value[index]; const previous = this.previousAccounts.get(address); if (!previous && current) { changes.push({ pubkey: address, type: 'created', previous: null, current, difference: current.lamports }); } else if (previous && !current) { changes.push({ pubkey: address, type: 'deleted', previous, current: null, difference: -previous.lamports }); } else if (previous && current) { const difference = current.lamports - previous.lamports; if (difference !== 0 || current.data[0] !== previous.data[0]) { changes.push({ pubkey: address, type: 'modified', previous, current, difference }); } } this.previousAccounts.set(address, current); }); return changes; } } ``` # getProgramAccounts Source: https://docs.orbitflare.com/rpc/methods/getProgramAccounts Returns all accounts owned by a program ## Parameters Program public key (base-58 encoded) Commitment level (processed, confirmed, finalized) Encoding format for account data (base58, base64, jsonParsed) Offset into account data to start reading Length of data to read Array of filter objects to apply to accounts Offset into account data to compare Base-58 encoded bytes to match Account data size to match ## Response Array of account information objects Account public key (base-58 encoded) Account data in the specified encoding Whether the account is executable Account balance in lamports Account owner (base-58 encoded) Epoch at which the account will next owe rent ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getProgramAccounts", "params": [ "PROGRAM_ID" ] }' ``` ### Request with Filters ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getProgramAccounts", "params": [ "PROGRAM_ID", { "filters": [ { "dataSize": 165 }, { "memcmp": { "offset": 0, "bytes": "BASE58_ENCODED_BYTES" } } ] } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get all program accounts const accounts = await connection.getProgramAccounts( new PublicKey('PROGRAM_ID') ); console.log('Accounts:', accounts); // Get filtered program accounts async function getFilteredProgramAccounts( programId: PublicKey, filters: any[] ) { const accounts = await connection.getProgramAccounts(programId, { filters }); return accounts.map(({ pubkey, account }) => ({ pubkey: pubkey.toBase58(), data: account.data, lamports: account.lamports, owner: account.owner.toBase58(), executable: account.executable })); } ``` ## Notes 1. Returns all accounts owned by a specific program 2. Filters can be applied to narrow down results 3. Account data can be sliced to reduce response size 4. The response is immediate as it reads from the current state 5. Different encodings can be specified for account data ## Best Practices 1. Use filters to efficiently query specific accounts 2. Consider using dataSlice to reduce response size 3. Cache results when appropriate to reduce RPC load 4. Handle large result sets with pagination 5. Use appropriate encoding based on your needs ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check programId and config parameters | | -32007 | Account information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Program Account Analysis** ```typescript theme={null} interface ProgramAccountAnalysis { pubkey: string; dataSize: number; dataHash: string; lamports: number; metadata: { timestamp: number; }; } async function analyzeProgramAccounts( programId: PublicKey, filters?: any[] ): Promise { const accounts = await connection.getProgramAccounts(programId, { filters, encoding: 'base64' }); return accounts.map(({ pubkey, account }) => { const data = account.data[0]; const dataSize = Buffer.from(data, 'base64').length; return { pubkey: pubkey.toBase58(), dataSize, dataHash: require('crypto') .createHash('sha256') .update(data) .digest('hex'), lamports: account.lamports, metadata: { timestamp: Date.now() } }; }); } ``` 2. **Program Account Monitoring** ```typescript theme={null} interface ProgramAccountChange { pubkey: string; type: 'created' | 'deleted' | 'modified'; previous: any; current: any; difference: number; } class ProgramAccountMonitor { private previousAccounts: Map = new Map(); async monitorProgramAccounts( programId: PublicKey, filters?: any[], interval: number = 5000 ): Promise { const accounts = await connection.getProgramAccounts(programId, { filters }); const changes: ProgramAccountChange[] = []; accounts.forEach(({ pubkey, account }) => { const address = pubkey.toBase58(); const current = account; const previous = this.previousAccounts.get(address); if (!previous && current) { changes.push({ pubkey: address, type: 'created', previous: null, current, difference: current.lamports }); } else if (previous && !current) { changes.push({ pubkey: address, type: 'deleted', previous, current: null, difference: -previous.lamports }); } else if (previous && current) { const difference = current.lamports - previous.lamports; if (difference !== 0 || current.data[0] !== previous.data[0]) { changes.push({ pubkey: address, type: 'modified', previous, current, difference }); } } this.previousAccounts.set(address, current); }); return changes; } } ``` 3. **Program Account Pagination** ```typescript theme={null} interface ProgramAccountPage { accounts: Array<{ pubkey: string; data: any; lamports: number; }>; total: number; page: number; pageSize: number; metadata: { timestamp: number; }; } async function getProgramAccountPage( programId: PublicKey, page: number = 1, pageSize: number = 100, filters?: any[] ): Promise { const accounts = await connection.getProgramAccounts(programId, { filters, encoding: 'base64' }); const start = (page - 1) * pageSize; const end = start + pageSize; const pageAccounts = accounts.slice(start, end); return { accounts: pageAccounts.map(({ pubkey, account }) => ({ pubkey: pubkey.toBase58(), data: account.data[0], lamports: account.lamports })), total: accounts.length, page, pageSize, metadata: { timestamp: Date.now() } }; } ``` # getRecentBlockhash Source: https://docs.orbitflare.com/rpc/methods/getRecentBlockhash [DEPRECATED] Returns a recent blockhash and its fee schedule This method is deprecated. Please use [`getLatestBlockhash`](/rpc/methods/getLatestBlockhash) instead, which provides more accurate and reliable blockhash information. Returns a recent blockhash and its fee schedule. This method will be removed in a future release. ## Parameters Commitment level (processed, confirmed, finalized) ## Response Slot at which the request was processed Recent blockhash (base-58 encoded) Fee in lamports per signature ## Migration Guide To migrate from `getRecentBlockhash` to `getLatestBlockhash`: 1. Replace method calls: ```diff theme={null} - const { blockhash, feeCalculator } = await connection.getRecentBlockhash(); + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); ``` 2. Update fee calculations: ```typescript theme={null} // Old way const fee = feeCalculator.lamportsPerSignature * numSignatures; // New way const fee = await connection.getFeeForMessage(message); ``` ## Code Examples ### Basic Request (Legacy) ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getRecentBlockhash", "params": [] }' ``` ### Request with Commitment (Legacy) ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getRecentBlockhash", "params": [ { "commitment": "confirmed" } ] }' ``` ### Using web3.js (Legacy) ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get recent blockhash (deprecated) const { value: { blockhash, feeCalculator } } = await connection.getRecentBlockhash(); console.log('Blockhash:', blockhash); console.log('Fee per signature:', feeCalculator.lamportsPerSignature); ``` ## Notes 1. This method is deprecated and will be removed in a future release 2. Use `getLatestBlockhash` for new development 3. The blockhash is valid for a limited time (typically 150 slots) 4. Different commitment levels can be specified 5. Consider updating existing code to use the new methods ## Best Practices 1. Migrate to `getLatestBlockhash` for new development 2. Use `getFeeForMessage` for fee calculations 3. Monitor for blockhash expiration using `lastValidBlockHeight` 4. Handle network errors and retry when appropriate 5. Use appropriate commitment level based on your needs ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check config parameters | | -32007 | Blockhash information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Transaction Fee Calculation** ```typescript theme={null} interface TransactionFee { blockhash: string; feePerSignature: number; totalFee: number; metadata: { timestamp: number; slot: number; }; } async function calculateTransactionFees( numSignatures: number ): Promise { const { context, value } = await connection.getRecentBlockhash(); return { blockhash: value.blockhash, feePerSignature: value.feeCalculator.lamportsPerSignature, totalFee: numSignatures * value.feeCalculator.lamportsPerSignature, metadata: { timestamp: Date.now(), slot: context.slot } }; } ``` 2. **Blockhash Monitoring** ```typescript theme={null} interface BlockhashInfo { blockhash: string; slot: number; feePerSignature: number; metadata: { timestamp: number; age: number; }; } class BlockhashMonitor { private currentBlockhash: string | null = null; private currentSlot: number | null = null; async monitorBlockhash( interval: number = 5000 ): Promise { const { context, value } = await connection.getRecentBlockhash(); if (this.currentBlockhash === value.blockhash) { return null; } this.currentBlockhash = value.blockhash; this.currentSlot = context.slot; return { blockhash: value.blockhash, slot: context.slot, feePerSignature: value.feeCalculator.lamportsPerSignature, metadata: { timestamp: Date.now(), age: 0 } }; } } ``` 3. **Transaction Planning** ```typescript theme={null} interface TransactionPlan { blockhash: string; feePerSignature: number; estimatedFees: { single: number; batch: number; }; metadata: { timestamp: number; slot: number; }; } async function planTransactions( numTransactions: number, signaturesPerTransaction: number ): Promise { const { context, value } = await connection.getRecentBlockhash(); const feePerSignature = value.feeCalculator.lamportsPerSignature; return { blockhash: value.blockhash, feePerSignature, estimatedFees: { single: signaturesPerTransaction * feePerSignature, batch: numTransactions * signaturesPerTransaction * feePerSignature }, metadata: { timestamp: Date.now(), slot: context.slot } }; } ``` # getRecentPerformanceSamples Source: https://docs.orbitflare.com/rpc/methods/getRecentPerformanceSamples Returns recent performance samples ## Parameters Number of samples to return (default: 720) ## Response Array of performance sample objects Slot number Number of slots in the sample Number of transactions in the sample Sample period in seconds Number of non-vote transactions ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getRecentPerformanceSamples", "params": [] }' ``` ### Request with Limit ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getRecentPerformanceSamples", "params": [ 100 ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get recent performance samples const samples = await connection.getRecentPerformanceSamples(); console.log('Performance samples:', samples); // Calculate average TPS function calculateAverageTPS(samples: any[]): number { const totalTransactions = samples.reduce( (sum, sample) => sum + sample.numTransactions, 0 ); const totalSeconds = samples.reduce( (sum, sample) => sum + sample.samplePeriodSecs, 0 ); return totalTransactions / totalSeconds; } ``` ## Notes 1. Returns recent performance samples from the network 2. Each sample covers a period of time (typically 60 seconds) 3. Samples include transaction counts and slot information 4. The response is immediate as it reads from the current state 5. The default limit is 720 samples (approximately 12 hours) ## Best Practices 1. Use appropriate limit based on your analysis needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for significant changes in performance 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check limit parameter | | -32007 | Performance samples unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Performance Analysis** ```typescript theme={null} interface PerformanceMetrics { averageTPS: number; peakTPS: number; totalTransactions: number; totalSlots: number; metadata: { timestamp: number; sampleCount: number; }; } async function analyzePerformance( limit: number = 720 ): Promise { const samples = await connection.getRecentPerformanceSamples(limit); const totalTransactions = samples.reduce( (sum, sample) => sum + sample.numTransactions, 0 ); const totalSeconds = samples.reduce( (sum, sample) => sum + sample.samplePeriodSecs, 0 ); const peakTPS = Math.max( ...samples.map(sample => sample.numTransactions / sample.samplePeriodSecs) ); return { averageTPS: totalTransactions / totalSeconds, peakTPS, totalTransactions, totalSlots: samples.reduce((sum, sample) => sum + sample.numSlots, 0), metadata: { timestamp: Date.now(), sampleCount: samples.length } }; } ``` 2. **Performance Monitoring** ```typescript theme={null} interface PerformanceAlert { type: 'high_tps' | 'low_tps' | 'anomaly'; currentTPS: number; threshold: number; metadata: { timestamp: number; slot: number; }; } class PerformanceMonitor { private previousTPS: number | null = null; private readonly threshold = 0.2; // 20% change threshold async monitorPerformance( interval: number = 60000 ): Promise { const samples = await connection.getRecentPerformanceSamples(1); const currentSample = samples[0]; const currentTPS = currentSample.numTransactions / currentSample.samplePeriodSecs; if (this.previousTPS === null) { this.previousTPS = currentTPS; return null; } const change = Math.abs(currentTPS - this.previousTPS) / this.previousTPS; if (change > this.threshold) { const alert: PerformanceAlert = { type: change > 0 ? 'high_tps' : 'low_tps', currentTPS, threshold: this.threshold, metadata: { timestamp: Date.now(), slot: currentSample.slot } }; this.previousTPS = currentTPS; return alert; } return null; } } ``` 3. **Performance History** ```typescript theme={null} interface PerformanceHistory { samples: Array<{ slot: number; tps: number; nonVoteTPS: number; timestamp: number; }>; summary: { averageTPS: number; peakTPS: number; totalTransactions: number; }; metadata: { startTime: number; endTime: number; sampleCount: number; }; } async function getPerformanceHistory( limit: number = 720 ): Promise { const samples = await connection.getRecentPerformanceSamples(limit); const processedSamples = samples.map(sample => ({ slot: sample.slot, tps: sample.numTransactions / sample.samplePeriodSecs, nonVoteTPS: sample.numNonVoteTransaction / sample.samplePeriodSecs, timestamp: Date.now() - (samples.length - samples.indexOf(sample)) * sample.samplePeriodSecs * 1000 })); const totalTransactions = samples.reduce( (sum, sample) => sum + sample.numTransactions, 0 ); const totalSeconds = samples.reduce( (sum, sample) => sum + sample.samplePeriodSecs, 0 ); return { samples: processedSamples, summary: { averageTPS: totalTransactions / totalSeconds, peakTPS: Math.max(...processedSamples.map(sample => sample.tps)), totalTransactions }, metadata: { startTime: processedSamples[0].timestamp, endTime: processedSamples[processedSamples.length - 1].timestamp, sampleCount: samples.length } }; } ``` # getRecentPrioritizationFees Source: https://docs.orbitflare.com/rpc/methods/getRecentPrioritizationFees Returns a list of recent prioritization fees from recent blocks ## Parameters An array of account addresses (as base-58 encoded strings). If provided, the response will be filtered to only include prioritization fees paid by one of the addresses. ## Response The slot in which the fee was observed The fee amount in micro-lamports (LAMPORTS\_PER\_SOL/1000000) ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getRecentPrioritizationFees", "params": [ ["CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY"] ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const address = 'CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY'; const fees = await connection.getRecentPrioritizationFees([address]); console.log(fees); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") address = "CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY" response = client.get_recent_prioritization_fees([address]) print(response) ``` ## Notes 1. Returns a list of recent prioritization fees from blocks 2. Useful for estimating fees for priority transactions 3. Fees are reported in micro-lamports (LAMPORTS\_PER\_SOL/1000000) 4. If addresses parameter is provided, results are filtered to those accounts 5. The response can be used to calculate appropriate prioritization fees ## Best Practices 1. Use this method to determine appropriate priority fees during network congestion 2. Filter by specific addresses if you only care about fees paid by certain accounts 3. Calculate an appropriate fee based on average or percentile of recent fees 4. Refresh fee data frequently during high network activity 5. Handle network errors and retry when appropriate # getSignatureStatuses Source: https://docs.orbitflare.com/rpc/methods/getSignatureStatuses Returns the statuses of a list of signatures ## Parameters An array of transaction signatures to confirm, as base-58 encoded strings If true, a Solana node will search its ledger cache for any signatures not found in the recent status cache ## Response The slot this request was processed in The slot the transaction was processed Number of blocks since signature confirmation, null if rooted or not found Error if transaction failed, null if transaction succeeded The transaction's cluster confirmation status; either "processed", "confirmed", or "finalized" ## Code Examples ### Basic Request ```bash theme={null} curl http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSignatureStatuses", "params": [ [ "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" ] ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); const signatures = [ '5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW', '5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7' ]; const statuses = await connection.getSignatureStatuses(signatures); console.log(statuses); ``` ### Using Python ```python theme={null} from solana.rpc.api import Client client = Client("http://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY") signatures = [ "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" ] response = client.get_signature_statuses(signatures) print(response) ``` ## Notes 1. Returns the statuses of a list of transaction signatures 2. By default, only searches recent status cache (last \~5 minutes of transactions) 3. Setting searchTransactionHistory to true will search further back in the ledger 4. null values in the response array indicate signatures that could not be found 5. confirmationStatus indicates how finalized a transaction is on the network ## Best Practices 1. Use this method to check the status of recently submitted transactions 2. Pass multiple signatures in a single request to reduce network overhead 3. Set searchTransactionHistory to true only when needed (performance impact) 4. Handle null values in the response array appropriately 5. Check confirmationStatus to determine transaction finality level # getSignaturesForAddress Source: https://docs.orbitflare.com/rpc/methods/getSignaturesForAddress Returns signatures for confirmed transactions for an address ## Parameters Account address (base-58 encoded) Maximum number of signatures to return (default: 1000) Start searching backwards from this signature Search until this signature Commitment level (processed, confirmed, finalized) ## Response Array of signature information objects Transaction signature (base-58 encoded) Slot in which the transaction was processed Error if transaction failed, null if transaction succeeded Memo associated with the transaction Unix timestamp of when the transaction was processed Transaction confirmation status ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSignaturesForAddress", "params": [ "ADDRESS" ] }' ``` ### Request with Config ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSignaturesForAddress", "params": [ "ADDRESS", { "limit": 100, "before": "SIGNATURE", "commitment": "confirmed" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get signatures for address const signatures = await connection.getSignaturesForAddress( new PublicKey('ADDRESS') ); console.log('Signatures:', signatures); // Get paginated signatures async function getPaginatedSignatures( address: PublicKey, limit: number = 100, before?: string ) { const signatures = await connection.getSignaturesForAddress(address, { limit, before }); return signatures.map(signature => ({ signature: signature.signature, slot: signature.slot, success: !signature.err, timestamp: signature.blockTime, status: signature.confirmationStatus })); } ``` ## Notes 1. Returns confirmed transaction signatures for an address 2. Results are returned in reverse chronological order 3. Pagination can be achieved using the `before` parameter 4. The response is immediate as it reads from the current state 5. Different commitment levels can be specified ## Best Practices 1. Use appropriate limit based on your needs 2. Implement pagination for large result sets 3. Cache results when appropriate to reduce RPC load 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check address and config parameters | | -32007 | Signature information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Transaction History** ```typescript theme={null} interface TransactionHistory { transactions: Array<{ signature: string; slot: number; success: boolean; timestamp: number; status: string; }>; metadata: { total: number; startTime: number; endTime: number; }; } async function getTransactionHistory( address: PublicKey, limit: number = 100 ): Promise { const signatures = await connection.getSignaturesForAddress(address, { limit }); return { transactions: signatures.map(signature => ({ signature: signature.signature, slot: signature.slot, success: !signature.err, timestamp: signature.blockTime || 0, status: signature.confirmationStatus || 'unknown' })), metadata: { total: signatures.length, startTime: signatures[0]?.blockTime || 0, endTime: signatures[signatures.length - 1]?.blockTime || 0 } }; } ``` 2. **Transaction Monitoring** ```typescript theme={null} interface TransactionAlert { type: 'new' | 'failed' | 'status_change'; signature: string; previousStatus: string | null; currentStatus: string; metadata: { timestamp: number; slot: number; }; } class TransactionMonitor { private previousSignatures: Map = new Map(); async monitorTransactions( address: PublicKey, interval: number = 5000 ): Promise { const signatures = await connection.getSignaturesForAddress(address, { limit: 10 }); const alerts: TransactionAlert[] = []; signatures.forEach(signature => { const previous = this.previousSignatures.get(signature.signature); if (!previous) { alerts.push({ type: 'new', signature: signature.signature, previousStatus: null, currentStatus: signature.confirmationStatus || 'unknown', metadata: { timestamp: Date.now(), slot: signature.slot } }); } else if (previous.confirmationStatus !== signature.confirmationStatus) { alerts.push({ type: 'status_change', signature: signature.signature, previousStatus: previous.confirmationStatus, currentStatus: signature.confirmationStatus || 'unknown', metadata: { timestamp: Date.now(), slot: signature.slot } }); } this.previousSignatures.set(signature.signature, signature); }); return alerts; } } ``` 3. **Transaction Analysis** ```typescript theme={null} interface TransactionAnalysis { total: number; successful: number; failed: number; statusDistribution: Record; timeDistribution: { hourly: Record; daily: Record; }; metadata: { startTime: number; endTime: number; duration: number; }; } async function analyzeTransactions( address: PublicKey, limit: number = 1000 ): Promise { const signatures = await connection.getSignaturesForAddress(address, { limit }); const statusDistribution: Record = {}; const hourlyDistribution: Record = {}; const dailyDistribution: Record = {}; let successful = 0; let failed = 0; signatures.forEach(signature => { const status = signature.confirmationStatus || 'unknown'; statusDistribution[status] = (statusDistribution[status] || 0) + 1; if (signature.err) { failed++; } else { successful++; } if (signature.blockTime) { const date = new Date(signature.blockTime * 1000); const hour = date.getHours(); const day = date.getDay(); hourlyDistribution[hour] = (hourlyDistribution[hour] || 0) + 1; dailyDistribution[day] = (dailyDistribution[day] || 0) + 1; } }); return { total: signatures.length, successful, failed, statusDistribution, timeDistribution: { hourly: hourlyDistribution, daily: dailyDistribution }, metadata: { startTime: signatures[0]?.blockTime || 0, endTime: signatures[signatures.length - 1]?.blockTime || 0, duration: signatures.length > 1 ? (signatures[0]?.blockTime || 0) - (signatures[signatures.length - 1]?.blockTime || 0) : 0 } }; } ``` # getSlot Source: https://docs.orbitflare.com/rpc/methods/getSlot Returns the current slot of the node ## Parameters Commitment level (processed, confirmed, finalized) ## Response Current slot number ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSlot", "params": [] }' ``` ### Request with Commitment ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSlot", "params": [ { "commitment": "confirmed" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get current slot const slot = await connection.getSlot(); console.log('Current slot:', slot); // Get slot with commitment async function getSlotWithCommitment( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed' ) { const slot = await connection.getSlot(commitment); return { slot, commitment, timestamp: Date.now() }; } ``` ## Notes 1. Returns the current slot of the node 2. The slot number increases as new blocks are produced 3. Different commitment levels can be specified 4. The response is immediate as it reads from the current state 5. The slot number is used for various time-based operations ## Best Practices 1. Cache the slot number when appropriate 2. Consider using websocket subscription for real-time updates 3. Handle network errors and retry when appropriate 4. Use appropriate commitment level based on your needs 5. Monitor slot progression for time-sensitive operations ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check config parameters | | -32007 | Slot information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Slot Monitoring** ```typescript theme={null} interface SlotInfo { slot: number; commitment: string; metadata: { timestamp: number; previousSlot: number | null; slotProgression: number; }; } class SlotMonitor { private previousSlot: number | null = null; async monitorSlot( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed', interval: number = 1000 ): Promise { const slot = await connection.getSlot(commitment); const timestamp = Date.now(); const info: SlotInfo = { slot, commitment, metadata: { timestamp, previousSlot: this.previousSlot, slotProgression: this.previousSlot !== null ? slot - this.previousSlot : 0 } }; this.previousSlot = slot; return info; } } ``` 2. **Slot Analysis** ```typescript theme={null} interface SlotAnalysis { currentSlot: number; slotProgression: { average: number; min: number; max: number; samples: number; }; timeDistribution: { slotsPerSecond: number; slotsPerMinute: number; slotsPerHour: number; }; metadata: { startTime: number; duration: number; }; } async function analyzeSlots( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed', duration: number = 60000 ): Promise { const startTime = Date.now(); const startSlot = await connection.getSlot(commitment); const progressions: number[] = []; let previousSlot = startSlot; while (Date.now() - startTime < duration) { const currentSlot = await connection.getSlot(commitment); progressions.push(currentSlot - previousSlot); previousSlot = currentSlot; await new Promise(resolve => setTimeout(resolve, 1000)); } const totalProgression = progressions.reduce((sum, p) => sum + p, 0); const durationSeconds = (Date.now() - startTime) / 1000; return { currentSlot: previousSlot, slotProgression: { average: totalProgression / progressions.length, min: Math.min(...progressions), max: Math.max(...progressions), samples: progressions.length }, timeDistribution: { slotsPerSecond: totalProgression / durationSeconds, slotsPerMinute: (totalProgression / durationSeconds) * 60, slotsPerHour: (totalProgression / durationSeconds) * 3600 }, metadata: { startTime, duration: Date.now() - startTime } }; } ``` 3. **Slot Synchronization** ```typescript theme={null} interface SlotSync { currentSlot: number; targetSlot: number; difference: number; estimatedTime: number; metadata: { timestamp: number; commitment: string; }; } class SlotSynchronizer { private readonly targetSlotsPerSecond = 2; // Solana's target slot rate async checkSynchronization( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed' ): Promise { const currentSlot = await connection.getSlot(commitment); const targetSlot = Math.floor( (Date.now() / 1000) * this.targetSlotsPerSecond ); return { currentSlot, targetSlot, difference: currentSlot - targetSlot, estimatedTime: Math.abs(currentSlot - targetSlot) / this.targetSlotsPerSecond, metadata: { timestamp: Date.now(), commitment } }; } } ``` # getSlotLeader Source: https://docs.orbitflare.com/rpc/methods/getSlotLeader Returns the current slot leader ## Parameters Commitment level (processed, confirmed, finalized) ## Response Public key of the current slot leader (base-58 encoded) ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSlotLeader", "params": [] }' ``` ### Request with Commitment ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSlotLeader", "params": [ { "commitment": "confirmed" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get current slot leader const slotLeader = await connection.getSlotLeader(); console.log('Current slot leader:', slotLeader); // Get slot leader with commitment async function getSlotLeaderWithCommitment( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed' ) { const slotLeader = await connection.getSlotLeader(commitment); return { slotLeader, commitment, timestamp: Date.now() }; } ``` ## Notes 1. Returns the public key of the current slot leader 2. The slot leader is responsible for producing the next block 3. Different commitment levels can be specified 4. The response is immediate as it reads from the current state 5. The slot leader changes based on the network's leader schedule ## Best Practices 1. Cache the slot leader when appropriate 2. Consider using websocket subscription for real-time updates 3. Handle network errors and retry when appropriate 4. Use appropriate commitment level based on your needs 5. Monitor slot leader changes for network analysis ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check config parameters | | -32007 | Slot leader information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Slot Leader Monitoring** ```typescript theme={null} interface SlotLeaderInfo { slotLeader: string; commitment: string; metadata: { timestamp: number; previousLeader: string | null; leaderChanged: boolean; }; } class SlotLeaderMonitor { private previousLeader: string | null = null; async monitorSlotLeader( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed', interval: number = 1000 ): Promise { const slotLeader = await connection.getSlotLeader(commitment); const timestamp = Date.now(); const info: SlotLeaderInfo = { slotLeader, commitment, metadata: { timestamp, previousLeader: this.previousLeader, leaderChanged: this.previousLeader !== null && this.previousLeader !== slotLeader } }; this.previousLeader = slotLeader; return info; } } ``` 2. **Slot Leader Analysis** ```typescript theme={null} interface SlotLeaderAnalysis { currentLeader: string; leaderChanges: Array<{ from: string; to: string; timestamp: number; }>; leaderDistribution: Record; metadata: { startTime: number; duration: number; }; } async function analyzeSlotLeaders( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed', duration: number = 60000 ): Promise { const startTime = Date.now(); const leaderDistribution: Record = {}; const leaderChanges: Array<{ from: string; to: string; timestamp: number; }> = []; let previousLeader = await connection.getSlotLeader(commitment); leaderDistribution[previousLeader] = 1; while (Date.now() - startTime < duration) { const currentLeader = await connection.getSlotLeader(commitment); if (currentLeader !== previousLeader) { leaderChanges.push({ from: previousLeader, to: currentLeader, timestamp: Date.now() }); previousLeader = currentLeader; } leaderDistribution[currentLeader] = (leaderDistribution[currentLeader] || 0) + 1; await new Promise(resolve => setTimeout(resolve, 1000)); } return { currentLeader: previousLeader, leaderChanges, leaderDistribution, metadata: { startTime, duration: Date.now() - startTime } }; } ``` 3. **Slot Leader Tracking** ```typescript theme={null} interface SlotLeaderTrack { leader: string; slot: number; metadata: { timestamp: number; commitment: string; }; } class SlotLeaderTracker { async trackSlotLeader( commitment: 'processed' | 'confirmed' | 'finalized' = 'confirmed' ): Promise { const [leader, slot] = await Promise.all([ connection.getSlotLeader(commitment), connection.getSlot() ]); return { leader, slot, metadata: { timestamp: Date.now(), commitment } }; } } ``` # getSlotLeaders Source: https://docs.orbitflare.com/rpc/methods/getSlotLeaders Returns the slot leaders for a given range ## Parameters Start slot number Number of slot leaders to return ## Response Array of slot leader public keys (base-58 encoded) ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSlotLeaders", "params": [ 1000000, 10 ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get slot leaders const slotLeaders = await connection.getSlotLeaders(1000000, 10); console.log('Slot leaders:', slotLeaders); // Get slot leaders with analysis async function getSlotLeadersWithAnalysis( startSlot: number, limit: number ) { const slotLeaders = await connection.getSlotLeaders(startSlot, limit); return { slotLeaders, analysis: { uniqueLeaders: new Set(slotLeaders).size, leaderDistribution: slotLeaders.reduce((acc, leader) => { acc[leader] = (acc[leader] || 0) + 1; return acc; }, {} as Record), metadata: { startSlot, endSlot: startSlot + limit - 1, timestamp: Date.now() } } }; } ``` ## Notes 1. Returns the slot leaders for a given range of slots 2. The slot leaders are responsible for producing blocks 3. The response is immediate as it reads from the current state 4. The slot leaders are determined by the network's leader schedule 5. The range must be within the current epoch ## Best Practices 1. Use appropriate start slot and limit based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in the leader schedule 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check startSlot and limit parameters | | -32007 | Slot leader information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Leader Schedule Analysis** ```typescript theme={null} interface LeaderScheduleAnalysis { schedule: Array<{ slot: number; leader: string; }>; distribution: Record; metadata: { startSlot: number; endSlot: number; uniqueLeaders: number; }; } async function analyzeLeaderSchedule( startSlot: number, limit: number ): Promise { const slotLeaders = await connection.getSlotLeaders(startSlot, limit); const schedule = slotLeaders.map((leader, index) => ({ slot: startSlot + index, leader })); const distribution = slotLeaders.reduce((acc, leader) => { acc[leader] = (acc[leader] || 0) + 1; return acc; }, {} as Record); return { schedule, distribution, metadata: { startSlot, endSlot: startSlot + limit - 1, uniqueLeaders: Object.keys(distribution).length } }; } ``` 2. **Leader Schedule Monitoring** ```typescript theme={null} interface LeaderScheduleChange { slot: number; previousLeader: string; currentLeader: string; metadata: { timestamp: number; }; } class LeaderScheduleMonitor { private previousSchedule: Map = new Map(); async monitorLeaderSchedule( startSlot: number, limit: number, interval: number = 60000 ): Promise { const slotLeaders = await connection.getSlotLeaders(startSlot, limit); const changes: LeaderScheduleChange[] = []; slotLeaders.forEach((leader, index) => { const slot = startSlot + index; const previousLeader = this.previousSchedule.get(slot); if (previousLeader && previousLeader !== leader) { changes.push({ slot, previousLeader, currentLeader: leader, metadata: { timestamp: Date.now() } }); } this.previousSchedule.set(slot, leader); }); return changes; } } ``` 3. **Leader Schedule Planning** ```typescript theme={null} interface LeaderSchedulePlan { currentSlot: number; upcomingLeaders: Array<{ slot: number; leader: string; estimatedTime: number; }>; metadata: { timestamp: number; slotsPerSecond: number; }; } class LeaderSchedulePlanner { private readonly slotsPerSecond = 2; // Solana's target slot rate async planLeaderSchedule( startSlot: number, limit: number ): Promise { const [slotLeaders, currentSlot] = await Promise.all([ connection.getSlotLeaders(startSlot, limit), connection.getSlot() ]); const upcomingLeaders = slotLeaders.map((leader, index) => { const slot = startSlot + index; const slotsAhead = slot - currentSlot; const estimatedTime = slotsAhead / this.slotsPerSecond; return { slot, leader, estimatedTime }; }); return { currentSlot, upcomingLeaders, metadata: { timestamp: Date.now(), slotsPerSecond: this.slotsPerSecond } }; } } ``` # getStakeActivation Source: https://docs.orbitflare.com/rpc/methods/getStakeActivation Returns the stake activation information for a given account ## Parameters Public key of the stake account (base-58 encoded) Configuration object containing: Commitment level (processed, confirmed, finalized) Epoch for which to calculate stake activation ## Response Object containing: Activation state (active, inactive, activating, deactivating) Active stake amount in lamports Inactive stake amount in lamports ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getStakeActivation", "params": [ "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj" ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get stake activation const stakeAccount = new PublicKey('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj'); const activation = await connection.getStakeActivation(stakeAccount); console.log('Stake activation:', activation); // Get stake activation with analysis async function getStakeActivationWithAnalysis( stakeAccount: PublicKey, config?: { commitment?: string; epoch?: number } ) { const activation = await connection.getStakeActivation(stakeAccount, config); return { activation, analysis: { totalStake: activation.active + activation.inactive, activePercentage: (activation.active / (activation.active + activation.inactive)) * 100, state: activation.state, metadata: { timestamp: Date.now(), epoch: config?.epoch, commitment: config?.commitment } } }; } ``` ## Notes 1. Returns the stake activation information for a given stake account 2. The activation state indicates whether the stake is active, inactive, activating, or deactivating 3. The response is immediate as it reads from the current state 4. The stake activation can change based on the epoch and commitment level 5. The stake account must be a valid stake account ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in stake activation 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check stake account public key | | -32007 | Stake account not found | Verify the stake account exists | | -32008 | Invalid stake account | Verify the account is a valid stake account | ## Use Cases 1. **Stake Activation Analysis** ```typescript theme={null} interface StakeActivationAnalysis { account: string; activation: { state: string; active: number; inactive: number; }; metrics: { totalStake: number; activePercentage: number; activationProgress?: number; deactivationProgress?: number; }; metadata: { timestamp: number; epoch?: number; commitment?: string; }; } class StakeActivationAnalyzer { async analyzeStakeActivation( stakeAccount: PublicKey, config?: { commitment?: string; epoch?: number } ): Promise { const activation = await connection.getStakeActivation(stakeAccount, config); const metrics = { totalStake: activation.active + activation.inactive, activePercentage: (activation.active / (activation.active + activation.inactive)) * 100 }; if (activation.state === 'activating' || activation.state === 'deactivating') { const progress = (activation.active / (activation.active + activation.inactive)) * 100; if (activation.state === 'activating') { metrics.activationProgress = progress; } else { metrics.deactivationProgress = progress; } } return { account: stakeAccount.toBase58(), activation, metrics, metadata: { timestamp: Date.now(), epoch: config?.epoch, commitment: config?.commitment } }; } } ``` 2. **Stake Activation Monitoring** ```typescript theme={null} interface StakeActivationChange { account: string; previousState: string; currentState: string; activeChange: number; inactiveChange: number; metadata: { timestamp: number; epoch?: number; }; } class StakeActivationMonitor { private previousStates: Map = new Map(); async monitorStakeActivation( stakeAccount: PublicKey, config?: { commitment?: string; epoch?: number } ): Promise { const activation = await connection.getStakeActivation(stakeAccount, config); const accountKey = stakeAccount.toBase58(); const previous = this.previousStates.get(accountKey); if (!previous) { this.previousStates.set(accountKey, { state: activation.state, active: activation.active, inactive: activation.inactive }); return null; } if (previous.state !== activation.state || previous.active !== activation.active || previous.inactive !== activation.inactive) { const change: StakeActivationChange = { account: accountKey, previousState: previous.state, currentState: activation.state, activeChange: activation.active - previous.active, inactiveChange: activation.inactive - previous.inactive, metadata: { timestamp: Date.now(), epoch: config?.epoch } }; this.previousStates.set(accountKey, { state: activation.state, active: activation.active, inactive: activation.inactive }); return change; } return null; } } ``` 3. **Stake Activation Planning** ```typescript theme={null} interface StakeActivationPlan { account: string; currentState: string; targetState: string; steps: Array<{ action: string; estimatedTime: number; requiredStake: number; }>; metadata: { timestamp: number; currentEpoch: number; }; } class StakeActivationPlanner { private readonly slotsPerEpoch = 432000; // Solana's slots per epoch private readonly slotsPerSecond = 2; // Solana's target slot rate async planStakeActivation( stakeAccount: PublicKey, targetState: string ): Promise { const [activation, currentEpoch] = await Promise.all([ connection.getStakeActivation(stakeAccount), connection.getEpochInfo() ]); const steps: Array<{ action: string; estimatedTime: number; requiredStake: number; }> = []; if (activation.state !== targetState) { if (targetState === 'active' && activation.state === 'inactive') { steps.push({ action: 'activate', estimatedTime: (this.slotsPerEpoch / this.slotsPerSecond) / 2, // Half an epoch requiredStake: activation.active + activation.inactive }); } else if (targetState === 'inactive' && activation.state === 'active') { steps.push({ action: 'deactivate', estimatedTime: (this.slotsPerEpoch / this.slotsPerSecond) / 2, // Half an epoch requiredStake: activation.active + activation.inactive }); } } return { account: stakeAccount.toBase58(), currentState: activation.state, targetState, steps, metadata: { timestamp: Date.now(), currentEpoch: currentEpoch.epoch } }; } } ``` # getStakeMinimumDelegation Source: https://docs.orbitflare.com/rpc/methods/getStakeMinimumDelegation Returns the minimum stake delegation amount ## Parameters Configuration object containing: Commitment level (processed, confirmed, finalized) ## Response Minimum stake delegation amount in lamports ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getStakeMinimumDelegation", "params": [] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get minimum stake delegation const minDelegation = await connection.getStakeMinimumDelegation(); console.log('Minimum stake delegation:', minDelegation); // Get minimum stake delegation with analysis async function getStakeMinimumDelegationWithAnalysis( config?: { commitment?: string } ) { const minDelegation = await connection.getStakeMinimumDelegation(config); return { minDelegation, analysis: { inSOL: minDelegation / 1e9, // Convert lamports to SOL metadata: { timestamp: Date.now(), commitment: config?.commitment } } }; } ``` ## Notes 1. Returns the minimum stake delegation amount in lamports 2. The minimum delegation amount is set by the network 3. The response is immediate as it reads from the current state 4. The minimum delegation amount can change with network upgrades 5. This value is important for determining valid stake amounts ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in the minimum delegation amount 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check configuration parameters | | -32007 | Minimum delegation information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Stake Delegation Analysis** ```typescript theme={null} interface StakeDelegationAnalysis { minDelegation: number; metrics: { inSOL: number; recommendedMinimum: number; }; metadata: { timestamp: number; commitment?: string; }; } class StakeDelegationAnalyzer { private readonly recommendedMultiplier = 1.1; // 10% above minimum async analyzeStakeDelegation( config?: { commitment?: string } ): Promise { const minDelegation = await connection.getStakeMinimumDelegation(config); return { minDelegation, metrics: { inSOL: minDelegation / 1e9, recommendedMinimum: minDelegation * this.recommendedMultiplier }, metadata: { timestamp: Date.now(), commitment: config?.commitment } }; } } ``` 2. **Stake Delegation Monitoring** ```typescript theme={null} interface StakeDelegationChange { previousMinDelegation: number; currentMinDelegation: number; change: number; metadata: { timestamp: number; }; } class StakeDelegationMonitor { private previousMinDelegation: number | null = null; async monitorStakeDelegation( config?: { commitment?: string } ): Promise { const currentMinDelegation = await connection.getStakeMinimumDelegation(config); if (this.previousMinDelegation === null) { this.previousMinDelegation = currentMinDelegation; return null; } if (this.previousMinDelegation !== currentMinDelegation) { const change: StakeDelegationChange = { previousMinDelegation: this.previousMinDelegation, currentMinDelegation, change: currentMinDelegation - this.previousMinDelegation, metadata: { timestamp: Date.now() } }; this.previousMinDelegation = currentMinDelegation; return change; } return null; } } ``` 3. **Stake Delegation Planning** ```typescript theme={null} interface StakeDelegationPlan { minDelegation: number; recommendations: Array<{ amount: number; description: string; inSOL: number; }>; metadata: { timestamp: number; }; } class StakeDelegationPlanner { private readonly tiers = [ { multiplier: 1.1, description: 'Minimum recommended' }, { multiplier: 2, description: 'Standard' }, { multiplier: 5, description: 'Premium' } ]; async planStakeDelegation( config?: { commitment?: string } ): Promise { const minDelegation = await connection.getStakeMinimumDelegation(config); const recommendations = this.tiers.map(tier => ({ amount: minDelegation * tier.multiplier, description: tier.description, inSOL: (minDelegation * tier.multiplier) / 1e9 })); return { minDelegation, recommendations, metadata: { timestamp: Date.now() } }; } } ``` # getSupply Source: https://docs.orbitflare.com/rpc/methods/getSupply Returns the current supply of SOL ## Parameters Configuration object containing: Commitment level (processed, confirmed, finalized) Whether to exclude non-circulating accounts from the total supply ## Response Object containing: Total supply in lamports Circulating supply in lamports Non-circulating supply in lamports Array of non-circulating account addresses ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSupply", "params": [] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get supply const supply = await connection.getSupply(); console.log('Supply:', supply); // Get supply with analysis async function getSupplyWithAnalysis( config?: { commitment?: string; excludeNonCirculatingAccountsList?: boolean } ) { const supply = await connection.getSupply(config); return { supply, analysis: { totalInSOL: supply.total / 1e9, circulatingInSOL: supply.circulating / 1e9, nonCirculatingInSOL: supply.nonCirculating / 1e9, circulatingPercentage: (supply.circulating / supply.total) * 100, metadata: { timestamp: Date.now(), commitment: config?.commitment } } }; } ``` ## Notes 1. Returns the current supply of SOL in lamports 2. The supply is broken down into total, circulating, and non-circulating amounts 3. The response is immediate as it reads from the current state 4. The supply can change with network activity and inflation 5. Non-circulating accounts are typically controlled by the Solana Foundation ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in the supply 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check configuration parameters | | -32007 | Supply information unavailable | Node may be bootstrapping or syncing | ## Use Cases 1. **Supply Analysis** ```typescript theme={null} interface SupplyAnalysis { supply: { total: number; circulating: number; nonCirculating: number; }; metrics: { totalInSOL: number; circulatingInSOL: number; nonCirculatingInSOL: number; circulatingPercentage: number; inflationRate?: number; }; metadata: { timestamp: number; commitment?: string; }; } class SupplyAnalyzer { private previousSupply: number | null = null; async analyzeSupply( config?: { commitment?: string; excludeNonCirculatingAccountsList?: boolean } ): Promise { const supply = await connection.getSupply(config); const currentTimestamp = Date.now(); const metrics = { totalInSOL: supply.total / 1e9, circulatingInSOL: supply.circulating / 1e9, nonCirculatingInSOL: supply.nonCirculating / 1e9, circulatingPercentage: (supply.circulating / supply.total) * 100 }; if (this.previousSupply !== null) { const timeElapsed = (currentTimestamp - this.previousSupply) / (1000 * 60 * 60 * 24); // Days const supplyChange = supply.total - this.previousSupply; metrics.inflationRate = (supplyChange / this.previousSupply) * (365 / timeElapsed) * 100; } this.previousSupply = supply.total; return { supply, metrics, metadata: { timestamp: currentTimestamp, commitment: config?.commitment } }; } } ``` 2. **Supply Monitoring** ```typescript theme={null} interface SupplyChange { previousSupply: { total: number; circulating: number; nonCirculating: number; }; currentSupply: { total: number; circulating: number; nonCirculating: number; }; changes: { total: number; circulating: number; nonCirculating: number; }; metadata: { timestamp: number; }; } class SupplyMonitor { private previousSupply: { total: number; circulating: number; nonCirculating: number; } | null = null; async monitorSupply( config?: { commitment?: string; excludeNonCirculatingAccountsList?: boolean } ): Promise { const currentSupply = await connection.getSupply(config); if (this.previousSupply === null) { this.previousSupply = currentSupply; return null; } const changes = { total: currentSupply.total - this.previousSupply.total, circulating: currentSupply.circulating - this.previousSupply.circulating, nonCirculating: currentSupply.nonCirculating - this.previousSupply.nonCirculating }; if (changes.total !== 0 || changes.circulating !== 0 || changes.nonCirculating !== 0) { const change: SupplyChange = { previousSupply: this.previousSupply, currentSupply, changes, metadata: { timestamp: Date.now() } }; this.previousSupply = currentSupply; return change; } return null; } } ``` 3. **Supply Planning** ```typescript theme={null} interface SupplyProjection { currentSupply: { total: number; circulating: number; nonCirculating: number; }; projections: Array<{ days: number; total: number; circulating: number; nonCirculating: number; }>; metadata: { timestamp: number; inflationRate: number; }; } class SupplyPlanner { private readonly defaultInflationRate = 0.08; // 8% annual inflation async projectSupply( config?: { commitment?: string; excludeNonCirculatingAccountsList?: boolean }, days: number[] = [30, 90, 365] ): Promise { const currentSupply = await connection.getSupply(config); const currentTimestamp = Date.now(); const projections = days.map(daysAhead => { const years = daysAhead / 365; const total = currentSupply.total * Math.pow(1 + this.defaultInflationRate, years); const circulating = currentSupply.circulating * Math.pow(1 + this.defaultInflationRate, years); const nonCirculating = currentSupply.nonCirculating * Math.pow(1 + this.defaultInflationRate, years); return { days: daysAhead, total, circulating, nonCirculating }; }); return { currentSupply, projections, metadata: { timestamp: currentTimestamp, inflationRate: this.defaultInflationRate } }; } } ``` # getTokenAccountBalance Source: https://docs.orbitflare.com/rpc/methods/getTokenAccountBalance Returns the token balance of an account ## Parameters Public key of the token account (base-58 encoded) Configuration object containing: Commitment level (processed, confirmed, finalized) ## Response Object containing: The slot the request was processed at Raw amount of tokens as a string Number of decimals configured for token's mint Token amount as a float, accounting for decimals Token amount as a string, accounting for decimals ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTokenAccountBalance", "params": [ "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj" ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get token account balance const tokenAccount = new PublicKey('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj'); const balance = await connection.getTokenAccountBalance(tokenAccount); console.log('Token balance:', balance); // Get token account balance with analysis async function getTokenAccountBalanceWithAnalysis( tokenAccount: PublicKey, config?: { commitment?: string } ) { const balance = await connection.getTokenAccountBalance(tokenAccount, config); return { balance, analysis: { rawAmount: balance.value.amount, uiAmount: balance.value.uiAmount, uiAmountString: balance.value.uiAmountString, decimals: balance.value.decimals, metadata: { timestamp: Date.now(), commitment: config?.commitment } } }; } ``` ## Notes 1. Returns the token balance of a token account 2. The balance is returned in both raw and UI-friendly formats 3. The response is immediate as it reads from the current state 4. The balance can change with token transfers and other operations 5. The account must be a valid token account ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in token balances 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check token account public key | | -32007 | Token account not found | Verify the token account exists | | -32008 | Invalid token account | Verify the account is a valid token account | ## Use Cases 1. **Token Balance Analysis** ```typescript theme={null} interface TokenBalanceAnalysis { account: string; balance: { raw: string; ui: number; decimals: number; }; metrics: { valueInUSD?: number; percentageChange?: number; historicalData?: Array<{ timestamp: number; balance: number; }>; }; metadata: { timestamp: number; commitment?: string; }; } class TokenBalanceAnalyzer { private readonly tokenPrice: number = 1; // Replace with actual token price private historicalBalances: Array<{ timestamp: number; balance: number; }> = []; async analyzeTokenBalance( tokenAccount: PublicKey, config?: { commitment?: string } ): Promise { const balance = await connection.getTokenAccountBalance(tokenAccount, config); const currentTimestamp = Date.now(); this.historicalBalances.push({ timestamp: currentTimestamp, balance: balance.value.uiAmount || 0 }); // Keep only last 24 hours of data const oneDayAgo = currentTimestamp - 24 * 60 * 60 * 1000; this.historicalBalances = this.historicalBalances.filter( data => data.timestamp >= oneDayAgo ); const metrics = { valueInUSD: (balance.value.uiAmount || 0) * this.tokenPrice, historicalData: this.historicalBalances }; if (this.historicalBalances.length > 1) { const oldestBalance = this.historicalBalances[0].balance; const newestBalance = this.historicalBalances[this.historicalBalances.length - 1].balance; metrics.percentageChange = ((newestBalance - oldestBalance) / oldestBalance) * 100; } return { account: tokenAccount.toBase58(), balance: { raw: balance.value.amount, ui: balance.value.uiAmount || 0, decimals: balance.value.decimals }, metrics, metadata: { timestamp: currentTimestamp, commitment: config?.commitment } }; } } ``` 2. **Token Balance Monitoring** ```typescript theme={null} interface TokenBalanceChange { account: string; previousBalance: { raw: string; ui: number; }; currentBalance: { raw: string; ui: number; }; change: { raw: string; ui: number; }; metadata: { timestamp: number; }; } class TokenBalanceMonitor { private previousBalances: Map = new Map(); async monitorTokenBalance( tokenAccount: PublicKey, config?: { commitment?: string } ): Promise { const balance = await connection.getTokenAccountBalance(tokenAccount, config); const accountKey = tokenAccount.toBase58(); const previous = this.previousBalances.get(accountKey); if (!previous) { this.previousBalances.set(accountKey, { raw: balance.value.amount, ui: balance.value.uiAmount || 0 }); return null; } if (previous.raw !== balance.value.amount) { const change: TokenBalanceChange = { account: accountKey, previousBalance: previous, currentBalance: { raw: balance.value.amount, ui: balance.value.uiAmount || 0 }, change: { raw: (BigInt(balance.value.amount) - BigInt(previous.raw)).toString(), ui: (balance.value.uiAmount || 0) - previous.ui }, metadata: { timestamp: Date.now() } }; this.previousBalances.set(accountKey, { raw: balance.value.amount, ui: balance.value.uiAmount || 0 }); return change; } return null; } } ``` 3. **Token Balance Planning** ```typescript theme={null} interface TokenBalancePlan { account: string; currentBalance: { raw: string; ui: number; }; projections: Array<{ days: number; balance: number; growth: number; }>; metadata: { timestamp: number; growthRate: number; }; } class TokenBalancePlanner { private readonly defaultGrowthRate = 0.05; // 5% daily growth async planTokenBalance( tokenAccount: PublicKey, config?: { commitment?: string }, days: number[] = [7, 30, 90] ): Promise { const balance = await connection.getTokenAccountBalance(tokenAccount, config); const currentTimestamp = Date.now(); const projections = days.map(daysAhead => { const currentBalance = balance.value.uiAmount || 0; const projectedBalance = currentBalance * Math.pow(1 + this.defaultGrowthRate, daysAhead); return { days: daysAhead, balance: projectedBalance, growth: projectedBalance - currentBalance }; }); return { account: tokenAccount.toBase58(), currentBalance: { raw: balance.value.amount, ui: balance.value.uiAmount || 0 }, projections, metadata: { timestamp: currentTimestamp, growthRate: this.defaultGrowthRate } }; } } ``` # getTokenAccountsByDelegate Source: https://docs.orbitflare.com/rpc/methods/getTokenAccountsByDelegate Returns all token accounts for a given delegate ## Parameters Public key of the delegate (base-58 encoded) Configuration object containing: Public key of the token program (base-58 encoded) Commitment level (processed, confirmed, finalized) Encoding for account data (base58, base64, jsonParsed) Offset into account data Length of data to return ## Response Object containing: The slot the request was processed at Array of token account objects containing: Public key of the token account (base-58 encoded) Number of lamports in the account Public key of the account owner (base-58 encoded) Whether the account is executable Epoch at which the account will next owe rent Raw amount of tokens as a string Number of decimals configured for token's mint Token amount as a float, accounting for decimals Token amount as a string, accounting for decimals Public key of the token's mint (base-58 encoded) Public key of the token account owner (base-58 encoded) Public key of the delegate (base-58 encoded) Type of account (account) Program that owns the account (spl-token) Number of bytes allocated to the account ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTokenAccountsByDelegate", "params": [ "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj", { "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get token accounts by delegate const delegate = new PublicKey('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj'); const programId = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); const accounts = await connection.getTokenAccountsByDelegate(delegate, { programId }); console.log('Token accounts:', accounts); // Get token accounts by delegate with analysis async function getTokenAccountsByDelegateWithAnalysis( delegate: PublicKey, config: { programId: PublicKey; commitment?: string } ) { const accounts = await connection.getTokenAccountsByDelegate(delegate, config); return { accounts, analysis: { totalAccounts: accounts.value.length, totalBalance: accounts.value.reduce((sum, acc) => { const amount = BigInt(acc.account.data.parsed.info.tokenAmount.amount); return sum + amount; }, BigInt(0)), byMint: accounts.value.reduce((acc, account) => { const mint = account.account.data.parsed.info.mint; const amount = BigInt(account.account.data.parsed.info.tokenAmount.amount); acc[mint] = (acc[mint] || BigInt(0)) + amount; return acc; }, {} as Record), metadata: { timestamp: Date.now(), commitment: config.commitment } } }; } ``` ## Notes 1. Returns all token accounts where the given public key is a delegate 2. The delegate has the authority to transfer tokens from these accounts 3. The response is immediate as it reads from the current state 4. The accounts can change with token transfers and other operations 5. The delegate must be a valid public key ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in delegated accounts 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check delegate public key and configuration | | -32007 | Delegate not found | Verify the delegate exists | | -32008 | Invalid program ID | Verify the program ID is correct | ## Use Cases 1. **Delegate Account Analysis** ```typescript theme={null} interface DelegateAccountAnalysis { delegate: string; accounts: Array<{ pubkey: string; mint: string; balance: string; owner: string; }>; metrics: { totalAccounts: number; totalBalance: string; byMint: Record; byOwner: Record; }; metadata: { timestamp: number; commitment?: string; }; } class DelegateAccountAnalyzer { async analyzeDelegateAccounts( delegate: PublicKey, config: { programId: PublicKey; commitment?: string } ): Promise { const accounts = await connection.getTokenAccountsByDelegate(delegate, config); const byMint: Record = {}; const byOwner: Record = {}; let totalBalance = BigInt(0); accounts.value.forEach(account => { const mint = account.account.data.parsed.info.mint; const owner = account.account.data.parsed.info.owner; const amount = BigInt(account.account.data.parsed.info.tokenAmount.amount); byMint[mint] = (byMint[mint] || BigInt(0)) + amount; byOwner[owner] = (byOwner[owner] || 0) + 1; totalBalance += amount; }); return { delegate: delegate.toBase58(), accounts: accounts.value.map(account => ({ pubkey: account.pubkey, mint: account.account.data.parsed.info.mint, balance: account.account.data.parsed.info.tokenAmount.amount, owner: account.account.data.parsed.info.owner })), metrics: { totalAccounts: accounts.value.length, totalBalance: totalBalance.toString(), byMint: Object.fromEntries( Object.entries(byMint).map(([mint, amount]) => [mint, amount.toString()]) ), byOwner }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } } ``` 2. **Delegate Account Monitoring** ```typescript theme={null} interface DelegateAccountChange { delegate: string; changes: Array<{ type: 'added' | 'removed' | 'modified'; account: string; previousBalance?: string; currentBalance?: string; }>; metadata: { timestamp: number; }; } class DelegateAccountMonitor { private previousAccounts: Map = new Map(); async monitorDelegateAccounts( delegate: PublicKey, config: { programId: PublicKey; commitment?: string } ): Promise { const accounts = await connection.getTokenAccountsByDelegate(delegate, config); const currentAccounts = new Map( accounts.value.map(account => [ account.pubkey, account.account.data.parsed.info.tokenAmount.amount ]) ); const changes: Array<{ type: 'added' | 'removed' | 'modified'; account: string; previousBalance?: string; currentBalance?: string; }> = []; // Check for removed or modified accounts this.previousAccounts.forEach((balance, account) => { if (!currentAccounts.has(account)) { changes.push({ type: 'removed', account, previousBalance: balance }); } else if (currentAccounts.get(account) !== balance) { changes.push({ type: 'modified', account, previousBalance: balance, currentBalance: currentAccounts.get(account) }); } }); // Check for added accounts currentAccounts.forEach((balance, account) => { if (!this.previousAccounts.has(account)) { changes.push({ type: 'added', account, currentBalance: balance }); } }); this.previousAccounts = currentAccounts; if (changes.length > 0) { return { delegate: delegate.toBase58(), changes, metadata: { timestamp: Date.now() } }; } return null; } } ``` 3. **Delegate Account Planning** ```typescript theme={null} interface DelegateAccountPlan { delegate: string; currentAccounts: Array<{ pubkey: string; mint: string; balance: string; }>; recommendations: Array<{ type: 'consolidate' | 'diversify' | 'rebalance'; accounts: string[]; reason: string; }>; metadata: { timestamp: number; }; } class DelegateAccountPlanner { private readonly minBalancePerAccount = BigInt(1000000); // 1 token private readonly maxAccountsPerMint = 5; async planDelegateAccounts( delegate: PublicKey, config: { programId: PublicKey; commitment?: string } ): Promise { const accounts = await connection.getTokenAccountsByDelegate(delegate, config); const byMint = accounts.value.reduce((acc, account) => { const mint = account.account.data.parsed.info.mint; if (!acc[mint]) { acc[mint] = []; } acc[mint].push(account); return acc; }, {} as Record); const recommendations: Array<{ type: 'consolidate' | 'diversify' | 'rebalance'; accounts: string[]; reason: string; }> = []; Object.entries(byMint).forEach(([mint, mintAccounts]) => { // Check for accounts with low balances const lowBalanceAccounts = mintAccounts.filter(account => BigInt(account.account.data.parsed.info.tokenAmount.amount) < this.minBalancePerAccount ); if (lowBalanceAccounts.length > 0) { recommendations.push({ type: 'consolidate', accounts: lowBalanceAccounts.map(account => account.pubkey), reason: `Accounts with balances below minimum threshold for mint ${mint}` }); } // Check for too many accounts per mint if (mintAccounts.length > this.maxAccountsPerMint) { recommendations.push({ type: 'consolidate', accounts: mintAccounts.map(account => account.pubkey), reason: `Too many accounts (${mintAccounts.length}) for mint ${mint}` }); } }); return { delegate: delegate.toBase58(), currentAccounts: accounts.value.map(account => ({ pubkey: account.pubkey, mint: account.account.data.parsed.info.mint, balance: account.account.data.parsed.info.tokenAmount.amount })), recommendations, metadata: { timestamp: Date.now() } }; } } ``` # getTokenAccountsByOwner Source: https://docs.orbitflare.com/rpc/methods/getTokenAccountsByOwner Returns all token accounts for a given owner ## Parameters Public key of the owner (base-58 encoded) Configuration object containing: Public key of the token program (base-58 encoded) Commitment level (processed, confirmed, finalized) Encoding for account data (base58, base64, jsonParsed) Offset into account data Length of data to return ## Response Object containing: The slot the request was processed at Array of token account objects containing: Public key of the token account (base-58 encoded) Number of lamports in the account Public key of the account owner (base-58 encoded) Whether the account is executable Epoch at which the account will next owe rent Raw amount of tokens as a string Number of decimals configured for token's mint Token amount as a float, accounting for decimals Token amount as a string, accounting for decimals Public key of the token's mint (base-58 encoded) Public key of the token account owner (base-58 encoded) Public key of the delegate (base-58 encoded) Type of account (account) Program that owns the account (spl-token) Number of bytes allocated to the account ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTokenAccountsByOwner", "params": [ "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj", { "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get token accounts by owner const owner = new PublicKey('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj'); const programId = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); const accounts = await connection.getTokenAccountsByOwner(owner, { programId }); console.log('Token accounts:', accounts); // Get token accounts by owner with analysis async function getTokenAccountsByOwnerWithAnalysis( owner: PublicKey, config: { programId: PublicKey; commitment?: string } ) { const accounts = await connection.getTokenAccountsByOwner(owner, config); return { accounts, analysis: { totalAccounts: accounts.value.length, totalBalance: accounts.value.reduce((sum, acc) => { const amount = BigInt(acc.account.data.parsed.info.tokenAmount.amount); return sum + amount; }, BigInt(0)), byMint: accounts.value.reduce((acc, account) => { const mint = account.account.data.parsed.info.mint; const amount = BigInt(account.account.data.parsed.info.tokenAmount.amount); acc[mint] = (acc[mint] || BigInt(0)) + amount; return acc; }, {} as Record), metadata: { timestamp: Date.now(), commitment: config.commitment } } }; } ``` ## Notes 1. Returns all token accounts owned by the given public key 2. The owner has full control over these accounts 3. The response is immediate as it reads from the current state 4. The accounts can change with token transfers and other operations 5. The owner must be a valid public key ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in owned accounts 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check owner public key and configuration | | -32007 | Owner not found | Verify the owner exists | | -32008 | Invalid program ID | Verify the program ID is correct | ## Use Cases 1. **Owner Account Analysis** ```typescript theme={null} interface OwnerAccountAnalysis { owner: string; accounts: Array<{ pubkey: string; mint: string; balance: string; delegate?: string; }>; metrics: { totalAccounts: number; totalBalance: string; byMint: Record; delegatedAccounts: number; }; metadata: { timestamp: number; commitment?: string; }; } class OwnerAccountAnalyzer { async analyzeOwnerAccounts( owner: PublicKey, config: { programId: PublicKey; commitment?: string } ): Promise { const accounts = await connection.getTokenAccountsByOwner(owner, config); const byMint: Record = {}; let totalBalance = BigInt(0); let delegatedAccounts = 0; accounts.value.forEach(account => { const mint = account.account.data.parsed.info.mint; const amount = BigInt(account.account.data.parsed.info.tokenAmount.amount); const delegate = account.account.data.parsed.info.delegate; byMint[mint] = (byMint[mint] || BigInt(0)) + amount; totalBalance += amount; if (delegate) { delegatedAccounts++; } }); return { owner: owner.toBase58(), accounts: accounts.value.map(account => ({ pubkey: account.pubkey, mint: account.account.data.parsed.info.mint, balance: account.account.data.parsed.info.tokenAmount.amount, delegate: account.account.data.parsed.info.delegate })), metrics: { totalAccounts: accounts.value.length, totalBalance: totalBalance.toString(), byMint: Object.fromEntries( Object.entries(byMint).map(([mint, amount]) => [mint, amount.toString()]) ), delegatedAccounts }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } } ``` 2. **Owner Account Monitoring** ```typescript theme={null} interface OwnerAccountChange { owner: string; changes: Array<{ type: 'added' | 'removed' | 'modified'; account: string; previousBalance?: string; currentBalance?: string; previousDelegate?: string; currentDelegate?: string; }>; metadata: { timestamp: number; }; } class OwnerAccountMonitor { private previousAccounts: Map = new Map(); async monitorOwnerAccounts( owner: PublicKey, config: { programId: PublicKey; commitment?: string } ): Promise { const accounts = await connection.getTokenAccountsByOwner(owner, config); const currentAccounts = new Map( accounts.value.map(account => [ account.pubkey, { balance: account.account.data.parsed.info.tokenAmount.amount, delegate: account.account.data.parsed.info.delegate } ]) ); const changes: Array<{ type: 'added' | 'removed' | 'modified'; account: string; previousBalance?: string; currentBalance?: string; previousDelegate?: string; currentDelegate?: string; }> = []; // Check for removed or modified accounts this.previousAccounts.forEach((data, account) => { if (!currentAccounts.has(account)) { changes.push({ type: 'removed', account, previousBalance: data.balance, previousDelegate: data.delegate }); } else { const current = currentAccounts.get(account)!; if (current.balance !== data.balance || current.delegate !== data.delegate) { changes.push({ type: 'modified', account, previousBalance: data.balance, currentBalance: current.balance, previousDelegate: data.delegate, currentDelegate: current.delegate }); } } }); // Check for added accounts currentAccounts.forEach((data, account) => { if (!this.previousAccounts.has(account)) { changes.push({ type: 'added', account, currentBalance: data.balance, currentDelegate: data.delegate }); } }); this.previousAccounts = currentAccounts; if (changes.length > 0) { return { owner: owner.toBase58(), changes, metadata: { timestamp: Date.now() } }; } return null; } } ``` 3. **Owner Account Planning** ```typescript theme={null} interface OwnerAccountPlan { owner: string; currentAccounts: Array<{ pubkey: string; mint: string; balance: string; delegate?: string; }>; recommendations: Array<{ type: 'consolidate' | 'diversify' | 'rebalance' | 'delegate'; accounts: string[]; reason: string; }>; metadata: { timestamp: number; }; } class OwnerAccountPlanner { private readonly minBalancePerAccount = BigInt(1000000); // 1 token private readonly maxAccountsPerMint = 5; async planOwnerAccounts( owner: PublicKey, config: { programId: PublicKey; commitment?: string } ): Promise { const accounts = await connection.getTokenAccountsByOwner(owner, config); const byMint = accounts.value.reduce((acc, account) => { const mint = account.account.data.parsed.info.mint; if (!acc[mint]) { acc[mint] = []; } acc[mint].push(account); return acc; }, {} as Record); const recommendations: Array<{ type: 'consolidate' | 'diversify' | 'rebalance' | 'delegate'; accounts: string[]; reason: string; }> = []; Object.entries(byMint).forEach(([mint, mintAccounts]) => { // Check for accounts with low balances const lowBalanceAccounts = mintAccounts.filter(account => BigInt(account.account.data.parsed.info.tokenAmount.amount) < this.minBalancePerAccount ); if (lowBalanceAccounts.length > 0) { recommendations.push({ type: 'consolidate', accounts: lowBalanceAccounts.map(account => account.pubkey), reason: `Accounts with balances below minimum threshold for mint ${mint}` }); } // Check for too many accounts per mint if (mintAccounts.length > this.maxAccountsPerMint) { recommendations.push({ type: 'consolidate', accounts: mintAccounts.map(account => account.pubkey), reason: `Too many accounts (${mintAccounts.length}) for mint ${mint}` }); } // Check for accounts without delegates const accountsWithoutDelegates = mintAccounts.filter( account => !account.account.data.parsed.info.delegate ); if (accountsWithoutDelegates.length > 0) { recommendations.push({ type: 'delegate', accounts: accountsWithoutDelegates.map(account => account.pubkey), reason: `Accounts without delegates for mint ${mint}` }); } }); return { owner: owner.toBase58(), currentAccounts: accounts.value.map(account => ({ pubkey: account.pubkey, mint: account.account.data.parsed.info.mint, balance: account.account.data.parsed.info.tokenAmount.amount, delegate: account.account.data.parsed.info.delegate })), recommendations, metadata: { timestamp: Date.now() } }; } } ``` # getTokenLargestAccounts Source: https://docs.orbitflare.com/rpc/methods/getTokenLargestAccounts Returns the largest token accounts for a given mint ## Parameters Public key of the token mint (base-58 encoded) Configuration object containing: Maximum number of accounts to return (default: 20) Commitment level (processed, confirmed, finalized) ## Response Object containing: The slot the request was processed at Array of token account objects containing: Public key of the token account (base-58 encoded) Raw amount of tokens as a string Number of decimals configured for token's mint Token amount as a float, accounting for decimals Token amount as a string, accounting for decimals ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTokenLargestAccounts", "params": [ "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", { "limit": 10 } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get largest token accounts const mint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); const accounts = await connection.getTokenLargestAccounts(mint, { limit: 10 }); console.log('Largest token accounts:', accounts); // Get largest token accounts with analysis async function getTokenLargestAccountsWithAnalysis( mint: PublicKey, config: { limit?: number; commitment?: string } ) { const accounts = await connection.getTokenLargestAccounts(mint, config); return { accounts, analysis: { totalAccounts: accounts.value.length, totalSupply: accounts.value.reduce((sum, acc) => { const amount = BigInt(acc.amount); return sum + amount; }, BigInt(0)), distribution: accounts.value.reduce((acc, account, index) => { const amount = BigInt(account.amount); const percentage = Number((amount * BigInt(100)) / BigInt(accounts.value[0].amount)); acc[account.address] = { amount: account.amount, percentage, rank: index + 1 }; return acc; }, {} as Record), metadata: { timestamp: Date.now(), commitment: config.commitment } } }; } ``` ## Notes 1. Returns the largest token accounts for a given mint 2. Accounts are sorted by balance in descending order 3. The response is immediate as it reads from the current state 4. The accounts can change with token transfers and other operations 5. The mint must be a valid token mint ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in largest accounts 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check mint public key and configuration | | -32007 | Mint not found | Verify the mint exists | | -32008 | Invalid limit | Ensure limit is a positive number | ## Use Cases 1. **Token Distribution Analysis** ```typescript theme={null} interface TokenDistributionAnalysis { mint: string; accounts: Array<{ address: string; amount: string; percentage: number; rank: number; }>; metrics: { totalAccounts: number; totalSupply: string; concentration: number; // Gini coefficient top10Percentage: number; }; metadata: { timestamp: number; commitment?: string; }; } class TokenDistributionAnalyzer { private calculateGiniCoefficient(amounts: bigint[]): number { const n = amounts.length; const mean = amounts.reduce((sum, x) => sum + x, BigInt(0)) / BigInt(n); let numerator = BigInt(0); let denominator = BigInt(0); for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { numerator += (amounts[i] - amounts[j]).abs(); } denominator += amounts[i]; } return Number(numerator) / (2 * n * Number(denominator)); } async analyzeTokenDistribution( mint: PublicKey, config: { limit?: number; commitment?: string } ): Promise { const accounts = await connection.getTokenLargestAccounts(mint, config); const amounts = accounts.value.map(acc => BigInt(acc.amount)); const totalSupply = amounts.reduce((sum, x) => sum + x, BigInt(0)); const top10Supply = amounts.slice(0, 10).reduce((sum, x) => sum + x, BigInt(0)); return { mint: mint.toBase58(), accounts: accounts.value.map((account, index) => ({ address: account.address, amount: account.amount, percentage: Number((BigInt(account.amount) * BigInt(100)) / totalSupply), rank: index + 1 })), metrics: { totalAccounts: accounts.value.length, totalSupply: totalSupply.toString(), concentration: this.calculateGiniCoefficient(amounts), top10Percentage: Number((top10Supply * BigInt(100)) / totalSupply) }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } } ``` 2. **Token Holder Monitoring** ```typescript theme={null} interface TokenHolderChange { mint: string; changes: Array<{ type: 'added' | 'removed' | 'modified'; address: string; previousAmount?: string; currentAmount?: string; previousRank?: number; currentRank?: number; }>; metadata: { timestamp: number; }; } class TokenHolderMonitor { private previousHolders: Map = new Map(); async monitorTokenHolders( mint: PublicKey, config: { limit?: number; commitment?: string } ): Promise { const accounts = await connection.getTokenLargestAccounts(mint, config); const currentHolders = new Map( accounts.value.map((account, index) => [ account.address, { amount: account.amount, rank: index + 1 } ]) ); const changes: Array<{ type: 'added' | 'removed' | 'modified'; address: string; previousAmount?: string; currentAmount?: string; previousRank?: number; currentRank?: number; }> = []; // Check for removed or modified holders this.previousHolders.forEach((data, address) => { if (!currentHolders.has(address)) { changes.push({ type: 'removed', address, previousAmount: data.amount, previousRank: data.rank }); } else { const current = currentHolders.get(address)!; if (current.amount !== data.amount || current.rank !== data.rank) { changes.push({ type: 'modified', address, previousAmount: data.amount, currentAmount: current.amount, previousRank: data.rank, currentRank: current.rank }); } } }); // Check for added holders currentHolders.forEach((data, address) => { if (!this.previousHolders.has(address)) { changes.push({ type: 'added', address, currentAmount: data.amount, currentRank: data.rank }); } }); this.previousHolders = currentHolders; if (changes.length > 0) { return { mint: mint.toBase58(), changes, metadata: { timestamp: Date.now() } }; } return null; } } ``` 3. **Token Supply Planning** ```typescript theme={null} interface TokenSupplyPlan { mint: string; currentHolders: Array<{ address: string; amount: string; rank: number; }>; recommendations: Array<{ type: 'diversify' | 'consolidate' | 'rebalance'; addresses: string[]; reason: string; }>; metadata: { timestamp: number; }; } class TokenSupplyPlanner { private readonly maxConcentration = 0.5; // Maximum allowed concentration private readonly minHolders = 100; // Minimum number of holders async planTokenSupply( mint: PublicKey, config: { limit?: number; commitment?: string } ): Promise { const accounts = await connection.getTokenLargestAccounts(mint, config); const amounts = accounts.value.map(acc => BigInt(acc.amount)); const totalSupply = amounts.reduce((sum, x) => sum + x, BigInt(0)); const recommendations: Array<{ type: 'diversify' | 'consolidate' | 'rebalance'; addresses: string[]; reason: string; }> = []; // Check for concentration const top10Supply = amounts.slice(0, 10).reduce((sum, x) => sum + x, BigInt(0)); const concentration = Number((top10Supply * BigInt(100)) / totalSupply) / 100; if (concentration > this.maxConcentration) { recommendations.push({ type: 'diversify', addresses: accounts.value.slice(0, 10).map(acc => acc.address), reason: `Top 10 holders control ${(concentration * 100).toFixed(2)}% of supply` }); } // Check for minimum holders if (accounts.value.length < this.minHolders) { recommendations.push({ type: 'diversify', addresses: accounts.value.map(acc => acc.address), reason: `Only ${accounts.value.length} holders, below minimum of ${this.minHolders}` }); } return { mint: mint.toBase58(), currentHolders: accounts.value.map((account, index) => ({ address: account.address, amount: account.amount, rank: index + 1 })), recommendations, metadata: { timestamp: Date.now() } }; } } ``` # getTokenSupply Source: https://docs.orbitflare.com/rpc/methods/getTokenSupply Returns the total supply of a token mint ## Parameters Public key of the token mint (base-58 encoded) Configuration object containing: Commitment level (processed, confirmed, finalized) ## Response Object containing: The slot the request was processed at Raw amount of tokens as a string Number of decimals configured for token's mint Token amount as a float, accounting for decimals Token amount as a string, accounting for decimals ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTokenSupply", "params": [ "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get token supply const mint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); const supply = await connection.getTokenSupply(mint); console.log('Token supply:', supply); // Get token supply with analysis async function getTokenSupplyWithAnalysis( mint: PublicKey, config: { commitment?: string } ) { const supply = await connection.getTokenSupply(mint, config); return { supply, analysis: { rawAmount: BigInt(supply.value.amount), formattedAmount: supply.value.uiAmountString, decimals: supply.value.decimals, metadata: { timestamp: Date.now(), commitment: config.commitment } } }; } ``` ## Notes 1. Returns the total supply of a token mint 2. The supply includes all tokens in circulation 3. The response is immediate as it reads from the current state 4. The supply can change with token minting and burning 5. The mint must be a valid token mint ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in token supply 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check mint public key and configuration | | -32007 | Mint not found | Verify the mint exists | ## Use Cases 1. **Token Supply Analysis** ```typescript theme={null} interface TokenSupplyAnalysis { mint: string; supply: { rawAmount: string; formattedAmount: string; decimals: number; }; metrics: { maxSupply?: string; burnedAmount?: string; circulatingSupply?: string; inflationRate?: number; }; metadata: { timestamp: number; commitment?: string; }; } class TokenSupplyAnalyzer { private readonly maxSupply: Record = { 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': BigInt(1000000000000000) // USDC }; async analyzeTokenSupply( mint: PublicKey, config: { commitment?: string } ): Promise { const supply = await connection.getTokenSupply(mint, config); const rawAmount = BigInt(supply.value.amount); const maxSupply = this.maxSupply[mint.toBase58()]; return { mint: mint.toBase58(), supply: { rawAmount: supply.value.amount, formattedAmount: supply.value.uiAmountString!, decimals: supply.value.decimals }, metrics: { maxSupply: maxSupply?.toString(), burnedAmount: maxSupply ? (maxSupply - rawAmount).toString() : undefined, circulatingSupply: supply.value.uiAmountString, inflationRate: maxSupply ? Number((rawAmount * BigInt(100)) / maxSupply) : undefined }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } } ``` 2. **Token Supply Monitoring** ```typescript theme={null} interface TokenSupplyChange { mint: string; changes: { previousAmount: string; currentAmount: string; difference: string; percentageChange: number; }; metadata: { timestamp: number; }; } class TokenSupplyMonitor { private previousSupply: Map = new Map(); async monitorTokenSupply( mint: PublicKey, config: { commitment?: string } ): Promise { const supply = await connection.getTokenSupply(mint, config); const currentAmount = supply.value.amount; const previousAmount = this.previousSupply.get(mint.toBase58()); if (previousAmount && previousAmount !== currentAmount) { const difference = BigInt(currentAmount) - BigInt(previousAmount); const percentageChange = Number((difference * BigInt(100)) / BigInt(previousAmount)); this.previousSupply.set(mint.toBase58(), currentAmount); return { mint: mint.toBase58(), changes: { previousAmount, currentAmount, difference: difference.toString(), percentageChange }, metadata: { timestamp: Date.now() } }; } this.previousSupply.set(mint.toBase58(), currentAmount); return null; } } ``` 3. **Token Supply Planning** ```typescript theme={null} interface TokenSupplyPlan { mint: string; currentSupply: { rawAmount: string; formattedAmount: string; decimals: number; }; recommendations: Array<{ type: 'mint' | 'burn' | 'freeze'; amount?: string; reason: string; }>; metadata: { timestamp: number; }; } class TokenSupplyPlanner { private readonly targetInflationRate = 0.02; // 2% annual inflation private readonly maxSupply: Record = { 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': BigInt(1000000000000000) // USDC }; async planTokenSupply( mint: PublicKey, config: { commitment?: string } ): Promise { const supply = await connection.getTokenSupply(mint, config); const rawAmount = BigInt(supply.value.amount); const maxSupply = this.maxSupply[mint.toBase58()]; const recommendations: Array<{ type: 'mint' | 'burn' | 'freeze'; amount?: string; reason: string; }> = []; if (maxSupply) { const currentInflationRate = Number((rawAmount * BigInt(100)) / maxSupply) / 100; const targetAmount = maxSupply * BigInt(Math.floor(this.targetInflationRate * 100)) / BigInt(100); if (currentInflationRate > this.targetInflationRate) { const burnAmount = rawAmount - targetAmount; recommendations.push({ type: 'burn', amount: burnAmount.toString(), reason: `Current inflation rate (${(currentInflationRate * 100).toFixed(2)}%) exceeds target (${(this.targetInflationRate * 100).toFixed(2)}%)` }); } else if (currentInflationRate < this.targetInflationRate) { const mintAmount = targetAmount - rawAmount; recommendations.push({ type: 'mint', amount: mintAmount.toString(), reason: `Current inflation rate (${(currentInflationRate * 100).toFixed(2)}%) below target (${(this.targetInflationRate * 100).toFixed(2)}%)` }); } } return { mint: mint.toBase58(), currentSupply: { rawAmount: supply.value.amount, formattedAmount: supply.value.uiAmountString!, decimals: supply.value.decimals }, recommendations, metadata: { timestamp: Date.now() } }; } } ``` # getTransaction Source: https://docs.orbitflare.com/rpc/methods/getTransaction Returns transaction details for a given signature ## Parameters Transaction signature (base-58 encoded) Configuration object containing: Commitment level (processed, confirmed, finalized) Encoding for transaction data (base58, base64, jsonParsed) Maximum transaction version to support ## Response Object containing: The slot the transaction was processed in Number of required signatures Number of read-only signed accounts Number of read-only unsigned accounts Array of account public keys (base-58 encoded) Recent blockhash used for transaction Array of instruction objects containing: Program ID (base-58 encoded) Array of account indices Instruction data (base-58 encoded) Array of transaction signatures (base-58 encoded) Error object if transaction failed Fee paid for the transaction Array of account balances before the transaction Array of account balances after the transaction Array of inner instruction objects Array of log messages Array of token balances before the transaction Array of token balances after the transaction Array of reward objects ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ "5VERv8NMvzbJMEkV8xnrLkUaQwJJ9C9zV5uZ6cKf7Kq9", { "encoding": "jsonParsed" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, Transaction } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get transaction const signature = '5VERv8NMvzbJMEkV8xnrLkUaQwJJ9C9zV5uZ6cKf7Kq9'; const transaction = await connection.getTransaction(signature, { encoding: 'jsonParsed' }); console.log('Transaction:', transaction); // Get transaction with analysis async function getTransactionWithAnalysis( signature: string, config: { commitment?: string; encoding?: string } ) { const transaction = await connection.getTransaction(signature, config); return { transaction, analysis: { success: !transaction.meta.err, fee: transaction.meta.fee, balanceChanges: transaction.meta.postBalances.map((post, index) => ({ account: transaction.transaction.message.accountKeys[index], change: post - transaction.meta.preBalances[index] })), tokenBalanceChanges: transaction.meta.postTokenBalances?.map((post, index) => ({ account: post.account, mint: post.mint, change: BigInt(post.uiTokenAmount.amount) - BigInt(transaction.meta.preTokenBalances![index].uiTokenAmount.amount) })), metadata: { timestamp: Date.now(), commitment: config.commitment } } }; } ``` ## Notes 1. Returns transaction details for a given signature 2. The transaction must be confirmed to be returned 3. The response includes detailed information about the transaction 4. The transaction can be parsed in different encodings 5. The transaction must be a valid signature ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for transaction status changes 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check signature and configuration | | -32007 | Transaction not found | Verify the transaction exists | | -32008 | Invalid encoding | Ensure encoding is valid | ## Use Cases 1. **Transaction Analysis** ```typescript theme={null} interface TransactionAnalysis { signature: string; transaction: { success: boolean; fee: number; balanceChanges: Array<{ account: string; change: number; }>; tokenBalanceChanges?: Array<{ account: string; mint: string; change: bigint; }>; instructions: Array<{ programId: string; accounts: string[]; data: string; }>; }; metrics: { totalBalanceChange: number; totalTokenBalanceChange?: bigint; instructionCount: number; accountCount: number; }; metadata: { timestamp: number; commitment?: string; }; } class TransactionAnalyzer { async analyzeTransaction( signature: string, config: { commitment?: string; encoding?: string } ): Promise { const transaction = await connection.getTransaction(signature, config); return { signature, transaction: { success: !transaction.meta.err, fee: transaction.meta.fee, balanceChanges: transaction.meta.postBalances.map((post, index) => ({ account: transaction.transaction.message.accountKeys[index], change: post - transaction.meta.preBalances[index] })), tokenBalanceChanges: transaction.meta.postTokenBalances?.map((post, index) => ({ account: post.account, mint: post.mint, change: BigInt(post.uiTokenAmount.amount) - BigInt(transaction.meta.preTokenBalances![index].uiTokenAmount.amount) })), instructions: transaction.transaction.message.instructions.map(ix => ({ programId: transaction.transaction.message.accountKeys[ix.programIdIndex], accounts: ix.accounts.map(index => transaction.transaction.message.accountKeys[index]), data: ix.data })) }, metrics: { totalBalanceChange: transaction.meta.postBalances.reduce((sum, post, index) => sum + (post - transaction.meta.preBalances[index]), 0), totalTokenBalanceChange: transaction.meta.postTokenBalances?.reduce((sum, post, index) => sum + (BigInt(post.uiTokenAmount.amount) - BigInt(transaction.meta.preTokenBalances![index].uiTokenAmount.amount)), BigInt(0)), instructionCount: transaction.transaction.message.instructions.length, accountCount: transaction.transaction.message.accountKeys.length }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } } ``` 2. **Transaction Monitoring** ```typescript theme={null} interface TransactionChange { signature: string; changes: { status: 'confirmed' | 'failed' | 'dropped'; previousStatus?: 'confirmed' | 'failed' | 'dropped'; error?: any; }; metadata: { timestamp: number; }; } class TransactionMonitor { private previousTransactions: Map = new Map(); async monitorTransaction( signature: string, config: { commitment?: string; encoding?: string } ): Promise { const transaction = await connection.getTransaction(signature, config); const currentStatus = transaction ? (transaction.meta.err ? 'failed' : 'confirmed') : 'dropped'; const previous = this.previousTransactions.get(signature); if (previous && (previous.status !== currentStatus || previous.error !== transaction?.meta.err)) { this.previousTransactions.set(signature, { status: currentStatus, error: transaction?.meta.err }); return { signature, changes: { status: currentStatus, previousStatus: previous.status, error: transaction?.meta.err }, metadata: { timestamp: Date.now() } }; } this.previousTransactions.set(signature, { status: currentStatus, error: transaction?.meta.err }); return null; } } ``` 3. **Transaction Planning** ```typescript theme={null} interface TransactionPlan { signature: string; transaction: { success: boolean; fee: number; balanceChanges: Array<{ account: string; change: number; }>; tokenBalanceChanges?: Array<{ account: string; mint: string; change: bigint; }>; }; recommendations: Array<{ type: 'optimize' | 'retry' | 'cancel'; reason: string; }>; metadata: { timestamp: number; }; } class TransactionPlanner { private readonly maxFee = 1000000; // 0.001 SOL private readonly minBalance = 1000000; // 0.001 SOL async planTransaction( signature: string, config: { commitment?: string; encoding?: string } ): Promise { const transaction = await connection.getTransaction(signature, config); const recommendations: Array<{ type: 'optimize' | 'retry' | 'cancel'; reason: string; }> = []; // Check for high fees if (transaction.meta.fee > this.maxFee) { recommendations.push({ type: 'optimize', reason: `Transaction fee (${transaction.meta.fee}) exceeds maximum (${this.maxFee})` }); } // Check for low balances const lowBalanceAccounts = transaction.meta.postBalances .map((post, index) => ({ account: transaction.transaction.message.accountKeys[index], balance: post })) .filter(acc => acc.balance < this.minBalance); if (lowBalanceAccounts.length > 0) { recommendations.push({ type: 'optimize', reason: `Accounts with low balances: ${lowBalanceAccounts.map(acc => acc.account).join(', ')}` }); } // Check for failed transaction if (transaction.meta.err) { recommendations.push({ type: 'retry', reason: `Transaction failed with error: ${JSON.stringify(transaction.meta.err)}` }); } return { signature, transaction: { success: !transaction.meta.err, fee: transaction.meta.fee, balanceChanges: transaction.meta.postBalances.map((post, index) => ({ account: transaction.transaction.message.accountKeys[index], change: post - transaction.meta.preBalances[index] })), tokenBalanceChanges: transaction.meta.postTokenBalances?.map((post, index) => ({ account: post.account, mint: post.mint, change: BigInt(post.uiTokenAmount.amount) - BigInt(transaction.meta.preTokenBalances![index].uiTokenAmount.amount) })) }, recommendations, metadata: { timestamp: Date.now() } }; } } ``` # getTransactionCount Source: https://docs.orbitflare.com/rpc/methods/getTransactionCount Returns the current transaction count from the ledger ## Parameters Configuration object containing: Commitment level (processed, confirmed, finalized) ## Response The current transaction count ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getTransactionCount" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get transaction count const count = await connection.getTransactionCount(); console.log('Transaction count:', count); // Get transaction count with analysis async function getTransactionCountWithAnalysis( config: { commitment?: string } ) { const count = await connection.getTransactionCount(config); return { count, analysis: { timestamp: Date.now(), commitment: config.commitment } }; } ``` ## Notes 1. Returns the current transaction count from the ledger 2. The count includes all transactions processed by the node 3. The response is immediate as it reads from the current state 4. The count can change with new transactions 5. The count may vary between nodes due to network propagation ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in transaction count 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check configuration parameters | ## Use Cases 1. **Transaction Count Analysis** ```typescript theme={null} interface TransactionCountAnalysis { count: number; metrics: { timestamp: number; commitment?: string; }; } class TransactionCountAnalyzer { async analyzeTransactionCount( config: { commitment?: string } ): Promise { const count = await connection.getTransactionCount(config); return { count, metrics: { timestamp: Date.now(), commitment: config.commitment } }; } } ``` 2. **Transaction Count Monitoring** ```typescript theme={null} interface TransactionCountChange { changes: { previousCount: number; currentCount: number; difference: number; percentageChange: number; }; metadata: { timestamp: number; }; } class TransactionCountMonitor { private previousCount: number | null = null; async monitorTransactionCount( config: { commitment?: string } ): Promise { const currentCount = await connection.getTransactionCount(config); if (this.previousCount !== null && this.previousCount !== currentCount) { const difference = currentCount - this.previousCount; const percentageChange = (difference / this.previousCount) * 100; this.previousCount = currentCount; return { changes: { previousCount: this.previousCount, currentCount, difference, percentageChange }, metadata: { timestamp: Date.now() } }; } this.previousCount = currentCount; return null; } } ``` 3. **Transaction Count Planning** ```typescript theme={null} interface TransactionCountPlan { currentCount: number; recommendations: Array<{ type: 'scale' | 'optimize' | 'monitor'; reason: string; }>; metadata: { timestamp: number; }; } class TransactionCountPlanner { private readonly maxTransactionsPerSecond = 1000; private readonly minTransactionsPerSecond = 100; async planTransactionCount( config: { commitment?: string } ): Promise { const currentCount = await connection.getTransactionCount(config); const recommendations: Array<{ type: 'scale' | 'optimize' | 'monitor'; reason: string; }> = []; // Check for high transaction rate if (currentCount > this.maxTransactionsPerSecond) { recommendations.push({ type: 'scale', reason: `Transaction count (${currentCount}) exceeds maximum (${this.maxTransactionsPerSecond})` }); } // Check for low transaction rate if (currentCount < this.minTransactionsPerSecond) { recommendations.push({ type: 'optimize', reason: `Transaction count (${currentCount}) below minimum (${this.minTransactionsPerSecond})` }); } return { currentCount, recommendations, metadata: { timestamp: Date.now() } }; } } ``` # getVersion Source: https://docs.orbitflare.com/rpc/methods/getVersion Returns the current version of the node ## Parameters None ## Response Object containing: Version of the Solana core software Feature set version ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getVersion" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get version const version = await connection.getVersion(); console.log('Node version:', version); // Get version with analysis async function getVersionWithAnalysis() { const version = await connection.getVersion(); return { version, analysis: { timestamp: Date.now(), isLatest: await checkLatestVersion(version['solana-core']) } }; } async function checkLatestVersion(currentVersion: string): Promise { // Implementation to check if current version is latest // This would typically involve calling an external API return true; } ``` ## Notes 1. Returns the current version of the node 2. The version includes the Solana core software version 3. The response is immediate as it reads from the current state 4. The version can change with node updates 5. Different nodes may run different versions ## Best Practices 1. Cache results when appropriate to reduce RPC load 2. Monitor for version changes 3. Consider using websocket subscription for real-time updates 4. Handle network errors and retry when appropriate 5. Keep track of version compatibility ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | ## Use Cases 1. **Version Analysis** ```typescript theme={null} interface VersionAnalysis { version: { core: string; featureSet?: number; }; metrics: { timestamp: number; isLatest: boolean; isCompatible: boolean; }; } class VersionAnalyzer { private readonly minCompatibleVersion = '1.8.0'; async analyzeVersion(): Promise { const version = await connection.getVersion(); return { version: { core: version['solana-core'], featureSet: version['feature-set'] }, metrics: { timestamp: Date.now(), isLatest: await this.checkLatestVersion(version['solana-core']), isCompatible: this.checkCompatibility(version['solana-core']) } }; } private async checkLatestVersion(currentVersion: string): Promise { // Implementation to check if current version is latest return true; } private checkCompatibility(currentVersion: string): boolean { const [currentMajor, currentMinor] = currentVersion.split('.').map(Number); const [minMajor, minMinor] = this.minCompatibleVersion.split('.').map(Number); return currentMajor > minMajor || (currentMajor === minMajor && currentMinor >= minMinor); } } ``` 2. **Version Monitoring** ```typescript theme={null} interface VersionChange { changes: { previousVersion: string; currentVersion: string; type: 'upgrade' | 'downgrade'; }; metadata: { timestamp: number; }; } class VersionMonitor { private previousVersion: string | null = null; async monitorVersion(): Promise { const version = await connection.getVersion(); const currentVersion = version['solana-core']; if (this.previousVersion !== null && this.previousVersion !== currentVersion) { const type = this.compareVersions(currentVersion, this.previousVersion) > 0 ? 'upgrade' : 'downgrade'; this.previousVersion = currentVersion; return { changes: { previousVersion: this.previousVersion, currentVersion, type }, metadata: { timestamp: Date.now() } }; } this.previousVersion = currentVersion; return null; } private compareVersions(v1: string, v2: string): number { const [major1, minor1, patch1] = v1.split('.').map(Number); const [major2, minor2, patch2] = v2.split('.').map(Number); if (major1 !== major2) return major1 - major2; if (minor1 !== minor2) return minor1 - minor2; return patch1 - patch2; } } ``` 3. **Version Planning** ```typescript theme={null} interface VersionPlan { currentVersion: string; recommendations: Array<{ type: 'upgrade' | 'downgrade' | 'maintain'; reason: string; }>; metadata: { timestamp: number; }; } class VersionPlanner { private readonly targetVersion = '1.9.0'; private readonly minCompatibleVersion = '1.8.0'; async planVersion(): Promise { const version = await connection.getVersion(); const currentVersion = version['solana-core']; const recommendations: Array<{ type: 'upgrade' | 'downgrade' | 'maintain'; reason: string; }> = []; // Check for version compatibility if (!this.checkCompatibility(currentVersion)) { recommendations.push({ type: 'upgrade', reason: `Current version (${currentVersion}) is not compatible with minimum version (${this.minCompatibleVersion})` }); } // Check for target version if (this.compareVersions(currentVersion, this.targetVersion) < 0) { recommendations.push({ type: 'upgrade', reason: `Current version (${currentVersion}) is below target version (${this.targetVersion})` }); } return { currentVersion, recommendations, metadata: { timestamp: Date.now() } }; } private checkCompatibility(currentVersion: string): boolean { const [currentMajor, currentMinor] = currentVersion.split('.').map(Number); const [minMajor, minMinor] = this.minCompatibleVersion.split('.').map(Number); return currentMajor > minMajor || (currentMajor === minMajor && currentMinor >= minMinor); } private compareVersions(v1: string, v2: string): number { const [major1, minor1, patch1] = v1.split('.').map(Number); const [major2, minor2, patch2] = v2.split('.').map(Number); if (major1 !== major2) return major1 - major2; if (minor1 !== minor2) return minor1 - minor2; return patch1 - patch2; } } ``` # getVoteAccounts Source: https://docs.orbitflare.com/rpc/methods/getVoteAccounts Returns the current vote accounts ## Parameters Configuration object containing: Commitment level (processed, confirmed, finalized) Public key of a specific vote account to query (base-58 encoded) Whether to include unstaked delinquent vote accounts Maximum slot distance to consider a vote account delinquent ## Response Object containing: Array of current vote account objects containing: Public key of the vote account (base-58 encoded) Public key of the validator (base-58 encoded) Amount of activated stake in lamports Whether the account voted in the current epoch Array of epoch credit objects containing: Epoch number Number of credits earned Number of credits earned in previous epoch Slot of the last vote Slot of the root Array of delinquent vote account objects with the same structure as current ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getVoteAccounts" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get vote accounts const voteAccounts = await connection.getVoteAccounts(); console.log('Vote accounts:', voteAccounts); // Get vote accounts with analysis async function getVoteAccountsWithAnalysis( config: { commitment?: string; votePubkey?: string; keepUnstakedDelinquents?: boolean; delinquentSlotDistance?: number; } ) { const voteAccounts = await connection.getVoteAccounts(config); return { voteAccounts, analysis: { current: { count: voteAccounts.current.length, totalStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0), averageStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.current.length, activeVoters: voteAccounts.current.filter(acc => acc.epochVoteAccount).length }, delinquent: { count: voteAccounts.delinquent.length, totalStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0), averageStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.delinquent.length, activeVoters: voteAccounts.delinquent.filter(acc => acc.epochVoteAccount).length }, metadata: { timestamp: Date.now(), commitment: config.commitment } } }; } ``` ## Notes 1. Returns the current vote accounts 2. The response includes both current and delinquent vote accounts 3. The response is immediate as it reads from the current state 4. The accounts can change with stake changes and voting 5. The vote accounts must be valid ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in vote accounts 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check configuration parameters | | -32007 | Vote account not found | Verify the vote account exists | ## Use Cases 1. **Vote Account Analysis** ```typescript theme={null} interface VoteAccountAnalysis { accounts: { current: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; lastVote: number; rootSlot?: number; }>; delinquent: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; lastVote: number; rootSlot?: number; }>; }; metrics: { current: { count: number; totalStake: number; averageStake: number; activeVoters: number; participationRate: number; }; delinquent: { count: number; totalStake: number; averageStake: number; activeVoters: number; participationRate: number; }; }; metadata: { timestamp: number; commitment?: string; }; } class VoteAccountAnalyzer { async analyzeVoteAccounts( config: { commitment?: string; votePubkey?: string; keepUnstakedDelinquents?: boolean; delinquentSlotDistance?: number; } ): Promise { const voteAccounts = await connection.getVoteAccounts(config); return { accounts: { current: voteAccounts.current.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount, lastVote: acc.lastVote, rootSlot: acc.rootSlot })), delinquent: voteAccounts.delinquent.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount, lastVote: acc.lastVote, rootSlot: acc.rootSlot })) }, metrics: { current: { count: voteAccounts.current.length, totalStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0), averageStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.current.length, activeVoters: voteAccounts.current.filter(acc => acc.epochVoteAccount).length, participationRate: voteAccounts.current.filter(acc => acc.epochVoteAccount).length / voteAccounts.current.length }, delinquent: { count: voteAccounts.delinquent.length, totalStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0), averageStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.delinquent.length, activeVoters: voteAccounts.delinquent.filter(acc => acc.epochVoteAccount).length, participationRate: voteAccounts.delinquent.filter(acc => acc.epochVoteAccount).length / voteAccounts.delinquent.length } }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } } ``` 2. **Vote Account Monitoring** ```typescript theme={null} interface VoteAccountChange { changes: { current: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; delinquent: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; }; metadata: { timestamp: number; }; } class VoteAccountMonitor { private previousAccounts: { current: Map; delinquent: Map; } = { current: new Map(), delinquent: new Map() }; async monitorVoteAccounts( config: { commitment?: string; votePubkey?: string; keepUnstakedDelinquents?: boolean; delinquentSlotDistance?: number; } ): Promise { const voteAccounts = await connection.getVoteAccounts(config); const changes: { current: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; delinquent: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; } = { current: [], delinquent: [] }; // Check current accounts this.checkAccountChanges( voteAccounts.current, this.previousAccounts.current, changes.current ); // Check delinquent accounts this.checkAccountChanges( voteAccounts.delinquent, this.previousAccounts.delinquent, changes.delinquent ); // Update previous accounts this.previousAccounts.current = new Map( voteAccounts.current.map(acc => [ acc.votePubkey, { stake: acc.activatedStake, vote: acc.epochVoteAccount } ]) ); this.previousAccounts.delinquent = new Map( voteAccounts.delinquent.map(acc => [ acc.votePubkey, { stake: acc.activatedStake, vote: acc.epochVoteAccount } ]) ); if (changes.current.length > 0 || changes.delinquent.length > 0) { return { changes, metadata: { timestamp: Date.now() } }; } return null; } private checkAccountChanges( currentAccounts: Array<{ votePubkey: string; activatedStake: number; epochVoteAccount: boolean; }>, previousAccounts: Map, changes: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }> ) { // Check for removed or modified accounts previousAccounts.forEach((data, votePubkey) => { const current = currentAccounts.find(acc => acc.votePubkey === votePubkey); if (!current) { changes.push({ type: 'removed', votePubkey, previousStake: data.stake, previousVote: data.vote }); } else if ( current.activatedStake !== data.stake || current.epochVoteAccount !== data.vote ) { changes.push({ type: 'modified', votePubkey, previousStake: data.stake, currentStake: current.activatedStake, previousVote: data.vote, currentVote: current.epochVoteAccount }); } }); // Check for added accounts currentAccounts.forEach(account => { if (!previousAccounts.has(account.votePubkey)) { changes.push({ type: 'added', votePubkey: account.votePubkey, currentStake: account.activatedStake, currentVote: account.epochVoteAccount }); } }); } } ``` 3. **Vote Account Planning** ```typescript theme={null} interface VoteAccountPlan { accounts: { current: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; }>; delinquent: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; }>; }; recommendations: Array<{ type: 'stake' | 'unstake' | 'monitor'; votePubkey: string; reason: string; }>; metadata: { timestamp: number; }; } class VoteAccountPlanner { private readonly minStake = 1000000000; // 1 SOL private readonly maxDelinquentVotes = 3; async planVoteAccounts( config: { commitment?: string; votePubkey?: string; keepUnstakedDelinquents?: boolean; delinquentSlotDistance?: number; } ): Promise { const voteAccounts = await connection.getVoteAccounts(config); const recommendations: Array<{ type: 'stake' | 'unstake' | 'monitor'; votePubkey: string; reason: string; }> = []; // Check current accounts voteAccounts.current.forEach(account => { if (account.activatedStake < this.minStake) { recommendations.push({ type: 'stake', votePubkey: account.votePubkey, reason: `Account has low stake (${account.activatedStake})` }); } if (!account.epochVoteAccount) { recommendations.push({ type: 'monitor', votePubkey: account.votePubkey, reason: 'Account not voting in current epoch' }); } }); // Check delinquent accounts voteAccounts.delinquent.forEach(account => { if (account.activatedStake > 0) { recommendations.push({ type: 'unstake', votePubkey: account.votePubkey, reason: 'Account is delinquent with stake' }); } }); return { accounts: { current: voteAccounts.current.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount })), delinquent: voteAccounts.delinquent.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount })) }, recommendations, metadata: { timestamp: Date.now() } }; } } ``` # isBlockhashValid Source: https://docs.orbitflare.com/rpc/methods/isBlockhashValid Returns whether a blockhash is still valid or not ## Parameters The blockhash to validate (base-58 encoded) Configuration object containing: Commitment level (processed, confirmed, finalized) The minimum slot that the request can be evaluated at ## Response Whether the blockhash is still valid (true) or not (false) ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "isBlockhashValid", "params": [ "J7rBdM6AecPDEZp8aPq5tPmsPzPhQG4HD6YtAcQBDfJj" ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Check if blockhash is valid const isValid = await connection.isBlockhashValid('J7rBdM6AecPDEZp8aPq5tPmsPzPhQG4HD6YtAcQBDfJj'); console.log('Is blockhash valid:', isValid); // Check blockhash validity with analysis async function checkBlockhashValidityWithAnalysis( blockhash: string, config: { commitment?: string; minContextSlot?: number; } ) { const isValid = await connection.isBlockhashValid(blockhash, config); return { blockhash, validity: { isValid, checkedAt: Date.now() }, config: { commitment: config.commitment, minContextSlot: config.minContextSlot } }; } ``` ## Notes 1. Returns whether a blockhash is still valid 2. Blockhashes expire after a certain number of slots 3. The response is immediate as it reads from the current state 4. Invalid blockhashes cannot be used in transactions 5. The blockhash must be base-58 encoded ## Best Practices 1. Use appropriate commitment level based on your needs 2. Cache results when appropriate to reduce RPC load 3. Check blockhash validity before sending transactions 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check the blockhash format | | -32007 | Blockhash not found | The blockhash has expired or is invalid | ## Use Cases 1. **Blockhash Validation** ```typescript theme={null} interface BlockhashValidation { blockhash: string; validity: { isValid: boolean; checkedAt: number; }; config: { commitment?: string; minContextSlot?: number; }; } class BlockhashValidator { async validateBlockhash( blockhash: string, config: { commitment?: string; minContextSlot?: number; } ): Promise { const isValid = await connection.isBlockhashValid(blockhash, config); return { blockhash, validity: { isValid, checkedAt: Date.now() }, config: { commitment: config.commitment, minContextSlot: config.minContextSlot } }; } } ``` 2. **Blockhash Monitoring** ```typescript theme={null} interface BlockhashStatus { blockhash: string; status: { isValid: boolean; lastChecked: number; checkCount: number; }; history: Array<{ timestamp: number; isValid: boolean; }>; } class BlockhashMonitor { private statuses: Map = new Map(); async monitorBlockhash( blockhash: string, config: { commitment?: string; minContextSlot?: number; } ): Promise { const isValid = await connection.isBlockhashValid(blockhash, config); const now = Date.now(); let status = this.statuses.get(blockhash); if (!status) { status = { blockhash, status: { isValid, lastChecked: now, checkCount: 1 }, history: [{ timestamp: now, isValid }] }; } else { status.status.isValid = isValid; status.status.lastChecked = now; status.status.checkCount++; status.history.push({ timestamp: now, isValid }); } this.statuses.set(blockhash, status); return status; } } ``` 3. **Transaction Planning** ```typescript theme={null} interface TransactionPlan { blockhash: string; validity: { isValid: boolean; checkedAt: number; }; recommendations: Array<{ type: 'proceed' | 'retry' | 'abort'; reason: string; }>; metadata: { timestamp: number; }; } class TransactionPlanner { private readonly maxRetries = 3; private readonly validityCheckInterval = 1000; // 1 second async planTransaction( blockhash: string, config: { commitment?: string; minContextSlot?: number; } ): Promise { const isValid = await connection.isBlockhashValid(blockhash, config); const now = Date.now(); const recommendations: Array<{ type: 'proceed' | 'retry' | 'abort'; reason: string; }> = []; if (isValid) { recommendations.push({ type: 'proceed', reason: 'Blockhash is valid' }); } else { recommendations.push({ type: 'abort', reason: 'Blockhash is invalid' }); recommendations.push({ type: 'retry', reason: 'Get a new blockhash and retry' }); } return { blockhash, validity: { isValid, checkedAt: now }, recommendations, metadata: { timestamp: now } }; } async waitForValidBlockhash( blockhash: string, config: { commitment?: string; minContextSlot?: number; } ): Promise { let retries = 0; while (retries < this.maxRetries) { const isValid = await connection.isBlockhashValid(blockhash, config); if (isValid) { return true; } retries++; await new Promise(resolve => setTimeout(resolve, this.validityCheckInterval)); } return false; } } ``` # minimumLedgerSlot Source: https://docs.orbitflare.com/rpc/methods/minimumLedgerSlot Returns the lowest slot that the node has information about in its ledger ## Parameters This method does not accept any parameters. ## Response The lowest slot that the node has information about in its ledger ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "minimumLedgerSlot" }' ``` ### Using web3.js ```typescript theme={null} import { Connection } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Get minimum ledger slot const minSlot = await connection.minimumLedgerSlot(); console.log('Minimum ledger slot:', minSlot); // Get minimum ledger slot with analysis async function getMinimumLedgerSlotWithAnalysis() { const minSlot = await connection.minimumLedgerSlot(); const currentSlot = await connection.getSlot(); return { slots: { minimum: minSlot, current: currentSlot, difference: currentSlot - minSlot }, metadata: { timestamp: Date.now() } }; } ``` ## Notes 1. Returns the lowest slot in the node's ledger 2. The response is immediate as it reads from the current state 3. The minimum slot can change as the ledger is pruned 4. Slots below this value are not accessible 5. The value may vary between nodes based on their configuration ## Best Practices 1. Use this method to determine ledger boundaries 2. Cache results when appropriate to reduce RPC load 3. Monitor for changes in minimum slot 4. Consider using websocket subscription for real-time updates 5. Handle network errors and retry when appropriate ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32603 | Internal error | Node may be bootstrapping or syncing | ## Use Cases 1. **Ledger Analysis** ```typescript theme={null} interface LedgerAnalysis { slots: { minimum: number; current: number; difference: number; }; metrics: { pruningRate: number; retentionPeriod: number; }; metadata: { timestamp: number; }; } class LedgerAnalyzer { private readonly slotsPerSecond = 2; // Solana's target slots per second async analyzeLedger(): Promise { const minSlot = await connection.minimumLedgerSlot(); const currentSlot = await connection.getSlot(); const difference = currentSlot - minSlot; return { slots: { minimum: minSlot, current: currentSlot, difference }, metrics: { pruningRate: difference / this.slotsPerSecond, // seconds retentionPeriod: difference / (this.slotsPerSecond * 3600) // hours }, metadata: { timestamp: Date.now() } }; } } ``` 2. **Ledger Monitoring** ```typescript theme={null} interface LedgerStatus { slots: { minimum: number; previous: number; difference: number; }; history: Array<{ timestamp: number; minimum: number; }>; } class LedgerMonitor { private previousMinSlot: number | null = null; private history: Array<{ timestamp: number; minimum: number; }> = []; async monitorLedger(): Promise { const minSlot = await connection.minimumLedgerSlot(); const now = Date.now(); this.history.push({ timestamp: now, minimum: minSlot }); if (this.previousMinSlot === null) { this.previousMinSlot = minSlot; return null; } const status: LedgerStatus = { slots: { minimum: minSlot, previous: this.previousMinSlot, difference: minSlot - this.previousMinSlot }, history: this.history }; this.previousMinSlot = minSlot; return status; } } ``` 3. **Ledger Planning** ```typescript theme={null} interface LedgerPlan { slots: { minimum: number; current: number; difference: number; }; recommendations: Array<{ type: 'fetch' | 'wait' | 'error'; reason: string; }>; metadata: { timestamp: number; }; } class LedgerPlanner { private readonly maxSlotRange = 1000000; // Maximum slots to search async planLedgerOperation( targetSlot: number ): Promise { const minSlot = await connection.minimumLedgerSlot(); const currentSlot = await connection.getSlot(); const now = Date.now(); const recommendations: Array<{ type: 'fetch' | 'wait' | 'error'; reason: string; }> = []; if (targetSlot < minSlot) { recommendations.push({ type: 'error', reason: `Target slot ${targetSlot} is below minimum ledger slot ${minSlot}` }); } else if (targetSlot > currentSlot) { recommendations.push({ type: 'wait', reason: `Target slot ${targetSlot} has not been reached yet` }); } else if (currentSlot - targetSlot > this.maxSlotRange) { recommendations.push({ type: 'error', reason: `Target slot ${targetSlot} is too far in the past` }); } else { recommendations.push({ type: 'fetch', reason: 'Target slot is within valid range' }); } return { slots: { minimum: minSlot, current: currentSlot, difference: currentSlot - minSlot }, recommendations, metadata: { timestamp: now } }; } } ``` # requestAirdrop Source: https://docs.orbitflare.com/rpc/methods/requestAirdrop Requests an airdrop of lamports to a Solana account ## Parameters Public key of the account to receive the airdrop (base-58 encoded) Amount of lamports to request (1 SOL = 1,000,000,000 lamports) Configuration object containing: Commitment level (processed, confirmed, finalized) ## Response Transaction signature of the airdrop (base-58 encoded) ## Code Examples ### Basic Request ```bash theme={null} curl https://devnet.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "requestAirdrop", "params": [ "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 1000000000 ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, PublicKey } from '@solana/web3.js'; const connection = new Connection('https://devnet.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Request airdrop const signature = await connection.requestAirdrop( new PublicKey('83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri'), 1000000000 // 1 SOL ); console.log('Airdrop signature:', signature); // Request airdrop with confirmation async function requestAirdropWithConfirmation( pubkey: string, lamports: number, config: { commitment?: string } ) { const signature = await connection.requestAirdrop( new PublicKey(pubkey), lamports, config ); // Wait for confirmation const confirmation = await connection.confirmTransaction(signature); return { signature, confirmation: { status: confirmation.value.err ? 'failed' : 'success', slot: confirmation.context.slot }, request: { pubkey, lamports, formatted: lamports / 1e9 + ' SOL' }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } ``` ## Notes 1. Requests an airdrop of SOL to a Solana account 2. Only available on test networks (devnet, testnet) 3. The response is the transaction signature 4. The transaction must be confirmed before funds are available 5. The amount is limited based on network configuration ## Best Practices 1. Use appropriate commitment level based on your needs 2. Wait for transaction confirmation before proceeding 3. Handle rate limits and network errors 4. Consider using faucet services for larger amounts 5. Monitor transaction status for failures ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a test network | | -32602 | Invalid params | Check public key format and lamports amount | | -32003 | Transaction simulation failed | Amount may exceed limit or account may be invalid | ## Use Cases 1. **Airdrop Management** ```typescript theme={null} interface AirdropRequest { request: { pubkey: string; lamports: number; formatted: string; }; transaction: { signature: string; status: 'pending' | 'success' | 'failed'; confirmations: number; }; metadata: { timestamp: number; network: string; commitment?: string; }; } class AirdropManager { private readonly maxRetries = 3; private readonly confirmationTimeout = 30000; // 30 seconds async requestAirdrop( pubkey: string, lamports: number, config: { commitment?: string } ): Promise { let retries = 0; let signature: string; while (retries < this.maxRetries) { try { signature = await connection.requestAirdrop( new PublicKey(pubkey), lamports, config ); // Wait for confirmation const confirmation = await connection.confirmTransaction(signature, config.commitment); if (!confirmation.value.err) { return { request: { pubkey, lamports, formatted: lamports / 1e9 + ' SOL' }, transaction: { signature, status: 'success', confirmations: 1 }, metadata: { timestamp: Date.now(), network: connection.rpcEndpoint, commitment: config.commitment } }; } } catch (error) { retries++; if (retries === this.maxRetries) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000)); } } } } ``` 2. **Airdrop Monitoring** ```typescript theme={null} interface AirdropStatus { request: { pubkey: string; lamports: number; }; status: { signature: string; confirmations: number; slot: number; }; history: Array<{ timestamp: number; status: string; confirmations: number; }>; } class AirdropMonitor { private requests: Map = new Map(); async monitorAirdrop( signature: string, request: { pubkey: string; lamports: number; } ): Promise { let status = this.requests.get(signature); const now = Date.now(); const confirmation = await connection.getSignatureStatus(signature); if (!status) { status = { request, status: { signature, confirmations: confirmation?.confirmations || 0, slot: confirmation?.slot || 0 }, history: [{ timestamp: now, status: confirmation ? 'confirmed' : 'pending', confirmations: confirmation?.confirmations || 0 }] }; } else { status.status.confirmations = confirmation?.confirmations || 0; status.status.slot = confirmation?.slot || 0; status.history.push({ timestamp: now, status: confirmation ? 'confirmed' : 'pending', confirmations: confirmation?.confirmations || 0 }); } this.requests.set(signature, status); return status; } } ``` 3. **Airdrop Planning** ```typescript theme={null} interface AirdropPlan { request: { pubkey: string; lamports: number; formatted: string; }; recommendations: Array<{ type: 'proceed' | 'split' | 'abort'; reason: string; }>; metadata: { timestamp: number; }; } class AirdropPlanner { private readonly maxAirdrop = 2000000000; // 2 SOL private readonly minAirdrop = 100000; // 0.0001 SOL async planAirdrop( pubkey: string, lamports: number ): Promise { const recommendations: Array<{ type: 'proceed' | 'split' | 'abort'; reason: string; }> = []; if (lamports > this.maxAirdrop) { recommendations.push({ type: 'split', reason: `Amount ${lamports} exceeds maximum airdrop of ${this.maxAirdrop}` }); } else if (lamports < this.minAirdrop) { recommendations.push({ type: 'abort', reason: `Amount ${lamports} is below minimum airdrop of ${this.minAirdrop}` }); } else { recommendations.push({ type: 'proceed', reason: 'Amount is within valid range' }); } return { request: { pubkey, lamports, formatted: lamports / 1e9 + ' SOL' }, recommendations, metadata: { timestamp: Date.now() } }; } async splitAirdrop( pubkey: string, totalLamports: number ): Promise> { const chunks: Array<{ lamports: number; delay: number; }> = []; let remaining = totalLamports; let delay = 0; while (remaining > 0) { const chunk = Math.min(remaining, this.maxAirdrop); chunks.push({ lamports: chunk, delay }); remaining -= chunk; delay += 2000; // 2 seconds between requests } return chunks; } } ``` # sendTransaction Source: https://docs.orbitflare.com/rpc/methods/sendTransaction Submits a signed transaction to the cluster for processing ## Parameters Fully-signed transaction as a base-64 encoded string Configuration object containing: Encoding format for the transaction (base58, base64, json) Commitment level (processed, confirmed, finalized) Commitment level for preflight (processed, confirmed, finalized) If true, skip the preflight transaction checks Maximum number of times for the RPC node to retry sending the transaction to the leader The minimum slot that the request can be evaluated at ## Response First transaction signature embedded in the transaction (base-58 encoded) ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "sendTransaction", "params": [ "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTFNhyKNMr3WNFCrXgDS7uV7u", { "encoding": "base64", "commitment": "confirmed" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, Transaction, PublicKey, SystemProgram } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Create and send transaction const transaction = new Transaction().add( SystemProgram.transfer({ fromPubkey: new PublicKey('83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri'), toPubkey: new PublicKey('J7rBdM6AecPDEZp8aPq5tPmsPzPhQG4HD6YtAcQBDfJj'), lamports: 1000000000 }) ); const signature = await connection.sendTransaction(transaction, [signer]); console.log('Transaction signature:', signature); // Send transaction with confirmation async function sendTransactionWithConfirmation( transaction: Transaction, signers: Array, config: { commitment?: string; preflightCommitment?: string; skipPreflight?: boolean; maxRetries?: number; } ) { const signature = await connection.sendTransaction( transaction, signers, config ); // Wait for confirmation const confirmation = await connection.confirmTransaction(signature, config.commitment); return { signature, confirmation: { status: confirmation.value.err ? 'failed' : 'success', slot: confirmation.context.slot }, transaction: { recentBlockhash: transaction.recentBlockhash, feePayer: transaction.feePayer?.toBase58(), instructions: transaction.instructions.length }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } ``` ## Notes 1. Submits a signed transaction to the cluster 2. The transaction must be fully signed 3. The response is the transaction signature 4. The transaction must be confirmed before changes are applied 5. The transaction must include a recent blockhash ## Best Practices 1. Use appropriate commitment level based on your needs 2. Wait for transaction confirmation before proceeding 3. Handle rate limits and network errors 4. Monitor transaction status for failures 5. Consider using preflight checks for validation ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check transaction format and encoding | | -32003 | Transaction simulation failed | Verify transaction validity and account balances | | -32004 | Blockhash not found | Use a more recent blockhash | | -32005 | Node is behind | Try a different RPC node | | -32006 | Transaction already processed | Transaction was already submitted | | -32007 | Transaction signature verification failed | Check transaction signatures | ## Use Cases 1. **Transaction Management** ```typescript theme={null} interface TransactionRequest { transaction: { signature: string; blockhash: string; feePayer: string; instructions: number; }; status: { state: 'pending' | 'success' | 'failed'; confirmations: number; slot?: number; error?: string; }; metadata: { timestamp: number; commitment?: string; retries: number; }; } class TransactionManager { private readonly maxRetries = 3; private readonly confirmationTimeout = 30000; // 30 seconds async sendTransaction( transaction: Transaction, signers: Array, config: { commitment?: string; preflightCommitment?: string; skipPreflight?: boolean; } ): Promise { let retries = 0; let signature: string; while (retries < this.maxRetries) { try { // Get a new blockhash if retrying if (retries > 0) { const { blockhash } = await connection.getRecentBlockhash(); transaction.recentBlockhash = blockhash; } signature = await connection.sendTransaction( transaction, signers, config ); // Wait for confirmation const confirmation = await connection.confirmTransaction(signature, config.commitment); if (!confirmation.value.err) { return { transaction: { signature, blockhash: transaction.recentBlockhash, feePayer: transaction.feePayer?.toBase58(), instructions: transaction.instructions.length }, status: { state: 'success', confirmations: 1, slot: confirmation.context.slot }, metadata: { timestamp: Date.now(), commitment: config.commitment, retries } }; } throw new Error(confirmation.value.err.toString()); } catch (error) { retries++; if (retries === this.maxRetries) { return { transaction: { signature, blockhash: transaction.recentBlockhash, feePayer: transaction.feePayer?.toBase58(), instructions: transaction.instructions.length }, status: { state: 'failed', confirmations: 0, error: error.message }, metadata: { timestamp: Date.now(), commitment: config.commitment, retries } }; } await new Promise(resolve => setTimeout(resolve, 1000)); } } } } ``` 2. **Transaction Monitoring** ```typescript theme={null} interface TransactionStatus { transaction: { signature: string; slot?: number; }; status: { confirmations: number; err: any; }; history: Array<{ timestamp: number; confirmations: number; error?: string; }>; } class TransactionMonitor { private transactions: Map = new Map(); async monitorTransaction( signature: string ): Promise { let status = this.transactions.get(signature); const now = Date.now(); const confirmation = await connection.getSignatureStatus(signature); if (!status) { status = { transaction: { signature, slot: confirmation?.slot }, status: { confirmations: confirmation?.confirmations || 0, err: confirmation?.err }, history: [{ timestamp: now, confirmations: confirmation?.confirmations || 0, error: confirmation?.err?.toString() }] }; } else { status.status.confirmations = confirmation?.confirmations || 0; status.status.err = confirmation?.err; status.history.push({ timestamp: now, confirmations: confirmation?.confirmations || 0, error: confirmation?.err?.toString() }); } this.transactions.set(signature, status); return status; } } ``` 3. **Transaction Planning** ```typescript theme={null} interface TransactionPlan { transaction: { instructions: number; signers: number; estimatedFee: number; }; recommendations: Array<{ type: 'proceed' | 'split' | 'abort'; reason: string; }>; metadata: { timestamp: number; }; } class TransactionPlanner { private readonly maxInstructions = 10; private readonly maxSigners = 8; async planTransaction( transaction: Transaction ): Promise { const { feeCalculator } = await connection.getRecentBlockhash(); const estimatedFee = feeCalculator.lamportsPerSignature * transaction.signatures.length; const recommendations: Array<{ type: 'proceed' | 'split' | 'abort'; reason: string; }> = []; if (transaction.instructions.length > this.maxInstructions) { recommendations.push({ type: 'split', reason: `Transaction has ${transaction.instructions.length} instructions, exceeding maximum of ${this.maxInstructions}` }); } if (transaction.signatures.length > this.maxSigners) { recommendations.push({ type: 'split', reason: `Transaction has ${transaction.signatures.length} signers, exceeding maximum of ${this.maxSigners}` }); } if (recommendations.length === 0) { recommendations.push({ type: 'proceed', reason: 'Transaction is within limits' }); } return { transaction: { instructions: transaction.instructions.length, signers: transaction.signatures.length, estimatedFee }, recommendations, metadata: { timestamp: Date.now() } }; } async splitTransaction( transaction: Transaction ): Promise; }>> { const chunks: Array<{ transaction: Transaction; signers: Array; }> = []; let currentChunk = new Transaction(); let currentSigners: Array = []; for (let i = 0; i < transaction.instructions.length; i++) { const instruction = transaction.instructions[i]; if (currentChunk.instructions.length >= this.maxInstructions) { chunks.push({ transaction: currentChunk, signers: currentSigners }); currentChunk = new Transaction(); currentSigners = []; } currentChunk.add(instruction); // Add unique signers instruction.keys .filter(key => key.isSigner) .forEach(key => { if (!currentSigners.find(signer => signer.equals(key.pubkey))) { currentSigners.push(key.pubkey); } }); } if (currentChunk.instructions.length > 0) { chunks.push({ transaction: currentChunk, signers: currentSigners }); } return chunks; } } ``` # simulateTransaction Source: https://docs.orbitflare.com/rpc/methods/simulateTransaction Simulates sending a transaction ## Parameters Transaction to simulate, as a base-64 encoded string Configuration object containing: If true, verify transaction signatures Commitment level (processed, confirmed, finalized) Encoding format for the transaction (base58, base64, json) If true, replace the transaction's recent blockhash with the most recent blockhash Accounts configuration object containing: Encoding format for account data (base58, base64, json) Array of account addresses to return data for The minimum slot that the request can be evaluated at ## Response Object containing: Error if transaction simulation failed, null if simulation succeeded Array of log messages the transaction instructions output during execution Array of accounts with their data (if requested) Number of compute units consumed by the transaction Program return data from the simulation ## Code Examples ### Basic Request ```bash theme={null} curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "simulateTransaction", "params": [ "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTFNhyKNMr3WNFCrXgDS7uV7u", { "sigVerify": false, "commitment": "confirmed", "encoding": "base64" } ] }' ``` ### Using web3.js ```typescript theme={null} import { Connection, Transaction, PublicKey, SystemProgram } from '@solana/web3.js'; const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY'); // Create and simulate transaction const transaction = new Transaction().add( SystemProgram.transfer({ fromPubkey: new PublicKey('83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri'), toPubkey: new PublicKey('J7rBdM6AecPDEZp8aPq5tPmsPzPhQG4HD6YtAcQBDfJj'), lamports: 1000000000 }) ); const simulation = await connection.simulateTransaction(transaction); console.log('Simulation result:', simulation); // Simulate transaction with analysis async function simulateTransactionWithAnalysis( transaction: Transaction, config: { sigVerify?: boolean; commitment?: string; replaceRecentBlockhash?: boolean; } ) { const simulation = await connection.simulateTransaction(transaction, config); return { simulation: { success: !simulation.value.err, error: simulation.value.err, logs: simulation.value.logs, unitsConsumed: simulation.value.unitsConsumed }, transaction: { recentBlockhash: transaction.recentBlockhash, feePayer: transaction.feePayer?.toBase58(), instructions: transaction.instructions.length }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } ``` ## Notes 1. Simulates sending a transaction without actually sending it 2. The response includes execution logs and errors 3. The simulation is performed on the current state 4. No state changes are persisted 5. Useful for testing and debugging transactions ## Best Practices 1. Use simulation before sending real transactions 2. Check compute unit consumption 3. Analyze program logs for errors 4. Verify account states after simulation 5. Handle simulation errors appropriately ## Common Errors | Code | Message | Solution | | - | - | - | | -32601 | Method not found | Verify you're connected to a Solana RPC node | | -32602 | Invalid params | Check transaction format and encoding | | -32003 | Transaction simulation failed | Verify transaction validity and account balances | | -32004 | Blockhash not found | Use a more recent blockhash | | -32005 | Node is behind | Try a different RPC node | ## Use Cases 1. **Transaction Analysis** ```typescript theme={null} interface TransactionAnalysis { transaction: { instructions: number; signers: number; estimatedUnits: number; }; simulation: { success: boolean; error: string | null; logs: string[]; unitsConsumed: number; }; analysis: { programInvocations: number; accountAccesses: number; errorAnalysis?: string; }; metadata: { timestamp: number; commitment?: string; }; } class TransactionAnalyzer { async analyzeTransaction( transaction: Transaction, config: { sigVerify?: boolean; commitment?: string; } ): Promise { const simulation = await connection.simulateTransaction(transaction, config); const logs = simulation.value.logs || []; const programInvocations = logs.filter(log => log.includes('Program') && log.includes('invoke') ).length; const accountAccesses = logs.filter(log => log.includes('Account') || log.includes('Token') ).length; let errorAnalysis; if (simulation.value.err) { errorAnalysis = this.analyzeError(simulation.value.err); } return { transaction: { instructions: transaction.instructions.length, signers: transaction.signatures.length, estimatedUnits: simulation.value.unitsConsumed || 0 }, simulation: { success: !simulation.value.err, error: simulation.value.err, logs, unitsConsumed: simulation.value.unitsConsumed || 0 }, analysis: { programInvocations, accountAccesses, errorAnalysis }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } private analyzeError(error: any): string { if (typeof error === 'string') { if (error.includes('insufficient funds')) { return 'Transaction failed due to insufficient funds'; } if (error.includes('invalid account owner')) { return 'Transaction failed due to invalid account ownership'; } if (error.includes('account not found')) { return 'Transaction failed due to missing account'; } } return 'Unknown error occurred during simulation'; } } ``` 2. **Transaction Validation** ```typescript theme={null} interface TransactionValidation { transaction: { signature: string; blockhash: string; feePayer: string; }; checks: Array<{ name: string; passed: boolean; details?: string; }>; recommendations: Array<{ type: 'fix' | 'warning' | 'info'; message: string; }>; metadata: { timestamp: number; }; } class TransactionValidator { private readonly maxUnits = 200000; private readonly minAccountRent = 890880; async validateTransaction( transaction: Transaction, config: { sigVerify?: boolean; commitment?: string; } ): Promise { const simulation = await connection.simulateTransaction(transaction, config); const checks: Array<{ name: string; passed: boolean; details?: string; }> = []; const recommendations: Array<{ type: 'fix' | 'warning' | 'info'; message: string; }> = []; // Check compute units const unitsConsumed = simulation.value.unitsConsumed || 0; checks.push({ name: 'Compute Units', passed: unitsConsumed <= this.maxUnits, details: `${unitsConsumed} units consumed` }); if (unitsConsumed > this.maxUnits) { recommendations.push({ type: 'fix', message: 'Transaction exceeds compute unit limit' }); } // Check for errors checks.push({ name: 'Simulation', passed: !simulation.value.err, details: simulation.value.err?.toString() }); if (simulation.value.err) { recommendations.push({ type: 'fix', message: `Fix simulation error: ${simulation.value.err}` }); } // Analyze logs const logs = simulation.value.logs || []; checks.push({ name: 'Program Execution', passed: logs.some(log => log.includes('Program log:')), details: 'Program execution logs present' }); // Check account rent const rentWarnings = logs.filter(log => log.includes('insufficient lamports for rent') ); if (rentWarnings.length > 0) { checks.push({ name: 'Account Rent', passed: false, details: 'Insufficient lamports for rent' }); recommendations.push({ type: 'fix', message: `Ensure accounts have minimum rent: ${this.minAccountRent} lamports` }); } return { transaction: { signature: transaction.signatures[0]?.signature?.toString() || '', blockhash: transaction.recentBlockhash || '', feePayer: transaction.feePayer?.toBase58() || '' }, checks, recommendations, metadata: { timestamp: Date.now() } }; } } ``` 3. **Transaction Optimization** ```typescript theme={null} interface TransactionOptimization { original: { instructions: number; unitsConsumed: number; }; optimized: { instructions: number; unitsConsumed: number; savings: number; }; changes: Array<{ type: 'merge' | 'remove' | 'reorder'; description: string; impact: number; }>; metadata: { timestamp: number; }; } class TransactionOptimizer { async optimizeTransaction( transaction: Transaction ): Promise { // Simulate original transaction const originalSimulation = await connection.simulateTransaction(transaction); const originalUnits = originalSimulation.value.unitsConsumed || 0; const changes: Array<{ type: 'merge' | 'remove' | 'reorder'; description: string; impact: number; }> = []; // Create optimized transaction const optimizedTransaction = new Transaction(); const instructions = [...transaction.instructions]; // Analyze and merge similar instructions const mergedInstructions = this.mergeInstructions(instructions); if (mergedInstructions.length < instructions.length) { changes.push({ type: 'merge', description: `Merged ${instructions.length - mergedInstructions.length} similar instructions`, impact: 0 // Will be updated after simulation }); } // Remove redundant instructions const cleanedInstructions = this.removeRedundantInstructions(mergedInstructions); if (cleanedInstructions.length < mergedInstructions.length) { changes.push({ type: 'remove', description: `Removed ${mergedInstructions.length - cleanedInstructions.length} redundant instructions`, impact: 0 }); } // Reorder instructions for optimal execution const orderedInstructions = this.reorderInstructions(cleanedInstructions); changes.push({ type: 'reorder', description: 'Reordered instructions for optimal execution', impact: 0 }); // Add optimized instructions orderedInstructions.forEach(instruction => { optimizedTransaction.add(instruction); }); // Simulate optimized transaction const optimizedSimulation = await connection.simulateTransaction(optimizedTransaction); const optimizedUnits = optimizedSimulation.value.unitsConsumed || 0; // Calculate impacts const totalSavings = originalUnits - optimizedUnits; changes.forEach(change => { change.impact = Math.floor(totalSavings / changes.length); }); return { original: { instructions: instructions.length, unitsConsumed: originalUnits }, optimized: { instructions: orderedInstructions.length, unitsConsumed: optimizedUnits, savings: totalSavings }, changes, metadata: { timestamp: Date.now() } }; } private mergeInstructions( instructions: Array ): Array { // Implementation would analyze and merge similar instructions // This is a placeholder that returns the original instructions return instructions; } private removeRedundantInstructions( instructions: Array ): Array { // Implementation would identify and remove redundant instructions // This is a placeholder that returns the original instructions return instructions; } private reorderInstructions( instructions: Array ): Array { // Implementation would reorder instructions for optimal execution // This is a placeholder that returns the original instructions return instructions; } } ``` # WebSocket API Source: https://docs.orbitflare.com/rpc/websocket Real-time Solana subscriptions for accounts, slots, logs, and more ## Overview OrbitFlare supports persistent WebSocket connections for real-time Solana data. WebSocket subscriptions push updates to your client as they occur on-chain — no polling required. WebSocket endpoints use the `wss://` scheme with the same region codes as the HTTP API: ``` wss://{region}.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY ``` For a region endpoint (Frankfurt shown): ``` wss://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY ``` Your license key is found in the **Licenses** section of your [OrbitFlare Dashboard](https://orbitflare.com/dashboard). Pass it as the `api_key` query parameter — the same key used for HTTP RPC. ## Connecting ```javascript theme={null} const WebSocket = require("ws"); const ws = new WebSocket( "wss://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY" ); ws.on("open", () => { console.log("Connected"); }); ws.on("message", (data) => { const msg = JSON.parse(data); console.log("Received:", msg); }); ws.on("error", (err) => { console.error("WebSocket error:", err); }); ws.on("close", (code, reason) => { console.log(`Connection closed: ${code} ${reason}`); }); ``` WebSocket connections idle for more than **60 seconds** without any message exchange are closed by the server. Send a ping or keep your subscriptions active to maintain the connection. ## Subscription Methods ### accountSubscribe Receive updates whenever a specific account's lamport balance or data changes. ```javascript theme={null} ws.on("open", () => { ws.send(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "accountSubscribe", params: [ "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", { encoding: "base64", commitment: "confirmed" } ], })); }); ``` **Update fields:** | Field | Description | | - | - | | `lamports` | Current lamport balance | | `data` | Account data (encoding determined by the `encoding` param) | | `owner` | Owning program public key | | `executable` | Whether the account is an executable program | | `rentEpoch` | Epoch at which rent is next owed | *** ### logsSubscribe Receive transaction log messages in real time. Optionally filter to a specific program. ```javascript theme={null} // All transactions ws.send(JSON.stringify({ jsonrpc: "2.0", id: 2, method: "logsSubscribe", params: ["all"], })); // Filter to a specific program ws.send(JSON.stringify({ jsonrpc: "2.0", id: 3, method: "logsSubscribe", params: [ { mentions: ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"] }, // Raydium AMM { commitment: "confirmed" } ], })); ``` **Update fields:** | Field | Description | | - | - | | `signature` | Transaction signature (base-58) | | `err` | `null` if successful, error object if failed | | `logs` | Array of log message strings | *** ### slotSubscribe Receive a notification each time a slot is processed. ```javascript theme={null} ws.send(JSON.stringify({ jsonrpc: "2.0", id: 4, method: "slotSubscribe", params: [], })); ``` **Update fields:** | Field | Description | | - | - | | `slot` | Current slot number | | `parent` | Parent slot number | | `root` | Highest confirmed root slot | *** ### signatureSubscribe Receive a one-time notification when a specific transaction signature reaches a given commitment level. ```javascript theme={null} ws.send(JSON.stringify({ jsonrpc: "2.0", id: 5, method: "signatureSubscribe", params: [ "TRANSACTION_SIGNATURE_HERE", { commitment: "finalized" } ], })); ``` The subscription automatically closes after the first notification is sent. *** ### programSubscribe Receive updates for all accounts owned by a specific program. ```javascript theme={null} ws.send(JSON.stringify({ jsonrpc: "2.0", id: 6, method: "programSubscribe", params: [ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // SPL Token { encoding: "jsonParsed", commitment: "confirmed", filters: [{ dataSize: 165 }], // Standard token account size } ], })); ``` *** ### blockSubscribe Receive full block data as each block is confirmed. **Requires an add-on — contact support to enable.** ```javascript theme={null} ws.send(JSON.stringify({ jsonrpc: "2.0", id: 7, method: "blockSubscribe", params: ["all", { commitment: "confirmed", encoding: "base64" }], })); ``` *** ### rootSubscribe Receive the highest confirmed root slot each time it advances. ```javascript theme={null} ws.send(JSON.stringify({ jsonrpc: "2.0", id: 8, method: "rootSubscribe", params: [], })); ``` *** ### voteSubscribe Receive raw vote transactions as they are observed in the gossip network. **High volume — filter carefully.** ```javascript theme={null} ws.send(JSON.stringify({ jsonrpc: "2.0", id: 9, method: "voteSubscribe", params: [], })); ``` ## Unsubscribing Each subscription method has a corresponding `Unsubscribe` method. Pass the subscription ID returned in the initial subscription response. ```javascript theme={null} // Subscribe and capture the subscription ID ws.on("message", (data) => { const msg = JSON.parse(data); // Subscription confirmation contains the subscription ID if (msg.id === 1 && msg.result !== undefined) { const subscriptionId = msg.result; console.log("Subscribed, ID:", subscriptionId); // Unsubscribe later ws.send(JSON.stringify({ jsonrpc: "2.0", id: 10, method: "accountUnsubscribe", params: [subscriptionId], })); } }); ``` Available unsubscribe methods: `accountUnsubscribe`, `logsUnsubscribe`, `slotUnsubscribe`, `signatureUnsubscribe`, `programUnsubscribe`, `blockUnsubscribe`, `rootUnsubscribe`, `voteUnsubscribe`. ## Keepalive WebSocket connections are closed after 60 seconds of inactivity. Send a JSON-RPC ping periodically to keep the connection alive: ```javascript theme={null} const pingInterval = setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ jsonrpc: "2.0", id: 0, method: "ping" })); } }, 30_000); ws.on("close", () => clearInterval(pingInterval)); ``` ## Reconnection Implement automatic reconnection with exponential backoff for production applications: ```javascript theme={null} function connect(url, onMessage) { let ws; let delay = 1000; let attempt = 0; function createConnection() { ws = new WebSocket(url); ws.on("open", () => { console.log("Connected (attempt", attempt + 1, ")"); delay = 1000; // reset backoff on successful connection attempt = 0; // re-subscribe to all channels here }); ws.on("message", onMessage); ws.on("close", () => { attempt++; const nextDelay = Math.min(delay * 2, 30_000); console.log(`Reconnecting in ${delay}ms...`); setTimeout(createConnection, delay); delay = nextDelay; }); ws.on("error", (err) => console.error("WebSocket error:", err)); } createConnection(); return () => ws?.close(); } const disconnect = connect( "wss://fra.rpc.orbitflare.com?api_key=YOUR_LICENSE_KEY", (data) => { const msg = JSON.parse(data); console.log("Update:", msg); } ); ``` ## Connection Limits WebSocket connections share the same per-IP limit as gRPC connections. See [Authentication & Limits](/authentication#websocket) for details. ## See Also JSON-RPC 2.0 request format, batch requests, and all available methods. HTTP status codes, JSON-RPC errors, and how to handle them. # EVM SDK Source: https://docs.orbitflare.com/sdk/evm Rust SDK for OrbitFlare's EVM chains, the orbitflare-evm-sdk crate. One Rust client for OrbitFlare's EVM chains - Polygon, BNB Smart Chain, and Robinhood Chain - plus any EVM chain you define yourself. Built on [alloy](https://github.com/alloy-rs/alloy) types throughout (`Address`, `U256`, `B256`, `Filter`, `TransactionRequest`, typed blocks, transactions, receipts, and logs), with OrbitFlare's own transport: endpoint failover, retry with backoff, and self-healing WebSocket subscriptions. The clients are generic over the chain (`RpcClient`, `WsClient`); the aliases below are convenience shorthands. gRPC is currently Polygon only (Bor). ## Supported chains | Chain | Chain ID | Alias | | - | - | - | | Polygon | 137 | `PolygonRpcClient`, `PolygonWsClient` | | BNB Smart Chain | 56 | `BnbRpcClient`, `BnbWsClient` | | Robinhood Chain | 4663 | `RobinhoodRpcClient`, `RobinhoodWsClient` | | Your own | any | `RpcClient` where `impl Chain for C` | Migrating from `orbitflare-robinhood-sdk`? Robinhood Chain is now covered here via `RobinhoodRpcClient` / `RobinhoodWsClient`. The RPC and WebSocket surface is the same; the standalone crate is deprecated. ## Install ```bash theme={null} cargo add orbitflare-evm-sdk ``` Only the RPC client is enabled by default. Enable what you need: ```bash theme={null} cargo add orbitflare-evm-sdk --features ws cargo add orbitflare-evm-sdk --features grpc cargo add orbitflare-evm-sdk --features all ``` ## RPC ```rust Example theme={null} let client = PolygonRpcClient::builder() .url("https://ams.poly.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build()?; let block = client.get_block_number().await?; let gas_price = client.get_gas_price().await?; ``` ```rust Full Example theme={null} use orbitflare_evm_sdk::{primitives::address, PolygonRpcClient, Result}; #[tokio::main] async fn main() -> Result<()> { let client = PolygonRpcClient::builder() .url("https://ams.poly.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build()?; let block = client.get_block_number().await?; let gas_price = client.get_gas_price().await?; let wallet = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); let balance = client.get_balance(wallet).await?; println!("block {block}, gas {gas_price} wei, balance {balance} wei"); Ok(()) } ``` There are no baked-in default endpoints: set the URL with `.url()` or via an environment variable (see [Endpoints](#endpoints)), and the API key with `.api_key()` or `ORBITFLARE_LICENSE_KEY`. ### Builder methods **`.url(url)`** - The primary endpoint. Resolution order: `.url()` on the builder, then the `ORBITFLARE_RPC_URL` environment variable. **`.urls(&[...])`** - Set the primary and all fallbacks in one call. The first element is the primary, the rest are fallbacks. **`.fallback_url(url)` / `.fallback_urls(&[...])`** - Add failover endpoints. Failing endpoints are quarantined with exponential cooldown (10s, 20s, 40s, max 60s) and automatically retried once the cooldown expires; healthy endpoints are always preferred. **`.api_key(key)`** - Your OrbitFlare license key. If not set, the SDK checks `ORBITFLARE_LICENSE_KEY`. The key is appended to the endpoint URL as `?api_key=` at request time. **`.block_tag(tag)`** - Default block tag used by state queries (`get_balance`, `call`, `get_code`, ...). Defaults to `Latest`. **`.retry(policy)`** - Controls retry on transient errors (5xx, 429, connection resets, JSON-RPC error code -32005) with exponential backoff before failing over to the next endpoint. **`.timeout(duration)`** - HTTP timeout for each individual request. ### Available methods | Method | Returns | | - | - | | `get_block_number()` | `u64` | | `get_chain_id()` | `u64` | | `get_balance(Address)` | `U256` wei | | `get_transaction_count(Address)` | `u64` nonce | | `get_gas_price()` | `u128` wei | | `max_priority_fee_per_gas()` | `u128` wei | | `get_block_by_number(impl Into, full_txs)` | `Option` | | `get_block_by_hash(B256, full_txs)` | `Option` | | `get_transaction_by_hash(B256)` | `Option` | | `get_transaction_receipt(B256)` | `Option` | | `get_logs(&Filter)` | `Vec` | | `get_code(Address)` | `Bytes` | | `get_storage_at(Address, B256)` | `B256` | | `call(&TransactionRequest)` | `Bytes` | | `estimate_gas(&TransactionRequest)` | `u64` | | `send_raw_transaction(&[u8])` | `B256` tx hash | | `fee_history(blocks, newest, percentiles)` | `FeeHistory` | | `request(method, params)` | Any RPC method by name (`serde_json::Value`) | | `request_raw(body)` | Raw JSON-RPC body string | ### Log filters `get_logs` takes alloy's `Filter` directly: ```rust Example theme={null} let filter = Filter::new() .from_block(60_000_000u64) .to_block(BlockNumberOrTag::Latest) .address(address!("0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270")) .event_signature(transfer_topic); let logs = client.get_logs(&filter).await?; ``` ```rust Full Example theme={null} use orbitflare_evm_sdk::{primitives::{address, b256}, BlockNumberOrTag, Filter, PolygonRpcClient, Result}; #[tokio::main] async fn main() -> Result<()> { let client = PolygonRpcClient::builder() .url("https://ams.poly.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build()?; let transfer_topic = b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); let filter = Filter::new() .from_block(60_000_000u64) .to_block(BlockNumberOrTag::Latest) .address(address!("0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270")) .event_signature(transfer_topic); let logs = client.get_logs(&filter).await?; println!("{} logs", logs.len()); Ok(()) } ``` ### Contract calls and transactions `call` and `estimate_gas` take alloy's `TransactionRequest`. To send a transaction, build and sign it with alloy (`alloy-signer`, `alloy-network`), then broadcast through the SDK: ```rust theme={null} let hash = client.send_raw_transaction(&signed_tx_rlp).await?; let receipt = client.get_transaction_receipt(hash).await?; ``` ### Arbitrary methods `request` calls any RPC method by name - the SDK builds the JSON-RPC envelope, handles retry and failover, and returns the `result` field. `request_raw` sends a raw JSON-RPC body string. ```rust theme={null} use serde_json::json; let syncing = client.request("eth_syncing", json!([])).await?; ``` ## WebSocket Enable the `ws` feature. `.build()` is async - it establishes the connection before returning. ```rust Example theme={null} let client = PolygonWsClient::builder() .url("wss://ams.poly.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build() .await?; let mut heads = client.new_heads_subscribe().await?; ``` ```rust Full Example theme={null} use orbitflare_evm_sdk::{PolygonWsClient, Result}; #[tokio::main] async fn main() -> Result<()> { let client = PolygonWsClient::builder() .url("wss://ams.poly.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build() .await?; let mut heads = client.new_heads_subscribe().await?; while let Some(head) = heads.next().await { println!("block {} (gas used {})", head.number, head.gas_used); } Ok(()) } ``` The builder shares `.urls()`, `.fallback_url(s)`, `.api_key()`, and `.retry()` with the RPC builder; the WebSocket-specific options are `.ping_interval_secs(n)` (default 10) and `.max_missed_pongs(n)` (default 3). ### Subscriptions Subscriptions are typed - each yields the corresponding alloy type instead of raw JSON: | Method | Yields | | - | - | | `new_heads_subscribe()` | `Header` per new block | | `logs_subscribe(&Filter)` | `Log` matching the filter | | `new_pending_transactions_subscribe()` | `B256` transaction hash | All subscriptions return a `WsSubscription`. Call `.next()` for the next typed event (`None` means the subscription was closed), or `.next_raw()` for the untyped `serde_json::Value` payload. `sub.unsubscribe().await` removes one explicitly; dropping a subscription also works. ### Reconnection If the connection drops, the background task reconnects with exponential backoff and re-subscribes every active subscription automatically. Your `.next()` calls just keep working. Dead connections are detected via active ping/pong. ## Polygon gRPC Polygon exposes a Bor gRPC interface for low-overhead block, header, and receipt access. Enable the `grpc` feature. gRPC runs over plaintext HTTP/2; authenticate with a token via `.api_key()` (sent as `x-token`) or use IP whitelisting. ```rust Example theme={null} let client = PolygonGrpcClient::builder() .url("http://your-bor-grpc-endpoint:3131") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build()?; let header = client.header_by_number(BlockNumber::Latest).await?; ``` ```rust Full Example theme={null} use orbitflare_evm_sdk::grpc::{BlockNumber, PolygonGrpcClient}; use orbitflare_evm_sdk::Result; #[tokio::main] async fn main() -> Result<()> { let client = PolygonGrpcClient::builder() .url("http://your-bor-grpc-endpoint:3131") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build()?; let header = client.header_by_number(BlockNumber::Latest).await?; let author = client.author(header.number).await?; println!("block {} authored by {author}", header.number); Ok(()) } ``` | Method | Returns | | - | - | | `header_by_number(impl Into)` | `Header` | | `block_by_number(impl Into)` | `Block` | | `transaction_receipt(B256)` | `Receipt` | | `bor_block_receipt(B256)` | `Receipt` | | `author(impl Into)` | `Address` | | `td_by_hash(B256)` / `td_by_number(...)` | `u64` total difficulty | | `root_hash(start, end)` | `String` | | `block_info_in_batch(start, end)` | `Vec` | H160/H256 values convert to alloy `Address`/`B256` with the `ToAlloy` trait. ## Custom chains Any EVM chain works: implement `Chain` for a marker type and point a generic `RpcClient` at its URL. `CHAIN_ID` is metadata (available as `RpcClient::::chain_id_const()`); it does not gate requests. ```rust Example theme={null} struct Base; impl Chain for Base { const CHAIN_ID: u64 = 8453; } let client = RpcClient::::builder() .url("https://mainnet.base.org") .build()?; ``` ```rust Full Example theme={null} use orbitflare_evm_sdk::{Chain, RpcClient, Result}; struct Base; impl Chain for Base { const CHAIN_ID: u64 = 8453; } #[tokio::main] async fn main() -> Result<()> { let client = RpcClient::::builder() .url("https://mainnet.base.org") .build()?; println!("chain id: {}", client.get_chain_id().await?); println!("block: {}", client.get_block_number().await?); Ok(()) } ``` ## Arbitrum and Nitro block fields Robinhood Chain is Arbitrum Nitro, so its blocks carry extra fields (`l1BlockNumber`, `sendRoot`, `sendCount`) that standard EVM block types drop. The SDK preserves them and exposes typed accessors via `NitroBlockExt`. Any other non-standard field is still available on `block.other`. ```rust Example theme={null} let block = client .get_block_by_number(client.block_tag(), false) .await? .expect("block"); let l1 = block.l1_block_number(); let send_root = block.send_root(); ``` ```rust Full Example theme={null} use orbitflare_evm_sdk::{NitroBlockExt, RobinhoodRpcClient, Result}; #[tokio::main] async fn main() -> Result<()> { let client = RobinhoodRpcClient::builder() .url("https://robinhood.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .build()?; let block = client .get_block_by_number(client.block_tag(), false) .await? .expect("block"); println!("l1 block: {:?}", block.l1_block_number()); println!("send root: {:?}", block.send_root()); println!("send count: {:?}", block.send_count()); Ok(()) } ``` Arbitrum precompiles (ArbSys, ArbGasInfo, ...) are reachable through `call()` like any contract. ## Endpoints There are no default endpoints. Set the URL per client with `.url()`, or via an environment variable. Resolution order: `.url()` on the builder, then the environment variable. | Chain | RPC | WebSocket | | - | - | - | | Polygon | `https://ams.poly.rpc.orbitflare.com` | `wss://ams.poly.rpc.orbitflare.com` | | BNB Smart Chain | `https://bsc.rpc.orbitflare.com` | `wss://bsc.rpc.orbitflare.com` | | Robinhood Chain | `https://robinhood.rpc.orbitflare.com` | `wss://robinhood.rpc.orbitflare.com` | Use the exact endpoints from your OrbitFlare dashboard. ## Environment variables | Variable | Used by | Purpose | | - | - | - | | `ORBITFLARE_LICENSE_KEY` | RPC, WebSocket, gRPC | API key appended to endpoint URLs (`x-token` for gRPC) | | `ORBITFLARE_RPC_URL` | RPC | Endpoint used if `.url()` is not called | | `ORBITFLARE_WS_URL` | WebSocket | Endpoint used if `.url()` is not called | | `ORBITFLARE_GRPC_URL` | gRPC | Endpoint used if `.url()` is not called | ## Source The SDK is open source: [github.com/orbitflare/orbitflare-evm-sdk-rs](https://github.com/orbitflare/orbitflare-evm-sdk-rs) # Go gRPC Client (Yellowstone) Source: https://docs.orbitflare.com/sdk/go-grpc Yellowstone Geyser streaming in Go, with automatic reconnection, ping/pong liveness detection, and filters built programmatically via SubscribeRequestBuilder. ## Install ```bash theme={null} go get github.com/orbitflare/orbitflare-sdk-go ``` ```go theme={null} import "github.com/orbitflare/orbitflare-sdk-go/geyser" ``` ## Building the client Here's a client with every option set: ```go theme={null} import ( "time" "github.com/orbitflare/orbitflare-sdk-go/geyser" "github.com/orbitflare/orbitflare-sdk-go/orbitflare" ) client, err := geyser.NewBuilder(). URL("http://ny.rpc.orbitflare.com:10000"). FallbackURL("http://fra.rpc.orbitflare.com:10000"). Retry(orbitflare.RetryPolicy{ InitialDelay: 100 * time.Millisecond, MaxDelay: 30 * time.Second, Multiplier: 2.0, MaxAttempts: 0, }). Timeout(30 * time.Second). Keepalive(60 * time.Second). PingInterval(10 * time.Second). MaxMissedPongs(3). ChannelCapacity(4096). Build() ``` Minimal setup: ```go theme={null} client, err := geyser.NewBuilder(). URL("http://ny.rpc.orbitflare.com:10000"). Build() ``` gRPC needs no API key. The URL falls back to the `ORBITFLARE_GRPC_URL` environment variable. ### Builder methods **`.URL(url)`** - The primary gRPC endpoint. Falls back to `ORBITFLARE_GRPC_URL`. Use `http://` for plaintext HTTP/2 and `https://` for TLS. ```go theme={null} .URL("http://ny.rpc.orbitflare.com:10000") ``` **`.URLs(urls...)`** - Primary + fallbacks in one call. First element is primary. ```go theme={null} .URLs("http://ny.rpc.orbitflare.com:10000", "http://fra.rpc.orbitflare.com:10000") ``` **`.FallbackURL(url)` / `.FallbackURLs(urls...)`** - Add fallback endpoints. On connection failure, the SDK rotates through them. ```go theme={null} .FallbackURL("http://fra.rpc.orbitflare.com:10000") ``` **`.Retry(policy)`** - Controls reconnection backoff. When a connection drops, the SDK waits `InitialDelay`, then multiplies it each attempt (capped at `MaxDelay`). Set `MaxAttempts` to 0 for infinite retries. Default: 100ms initial, 30s max, 2x multiplier, infinite. ```go theme={null} .Retry(orbitflare.RetryPolicy{ InitialDelay: 200 * time.Millisecond, MaxDelay: 15 * time.Second, Multiplier: 2.0, MaxAttempts: 0, }) ``` **`.Timeout(duration)`** - Connection/dial timeout. Default: 30s. ```go theme={null} .Timeout(15 * time.Second) ``` **`.Keepalive(duration)`** - TCP/HTTP2 keepalive interval, used to detect dead connections at the transport level. Default: 60s. ```go theme={null} .Keepalive(30 * time.Second) ``` **`.PingInterval(duration)`** - How often the SDK sends proto-level `Ping` messages to the server. The server should respond with a `Pong`. Default: 10s. ```go theme={null} .PingInterval(15 * time.Second) ``` **`.MaxMissedPongs(n)`** - How many consecutive pings can go unanswered before the SDK considers the connection dead and reconnects. Default: 3. With the defaults, a dead connection is detected within 30 seconds. ```go theme={null} .MaxMissedPongs(5) ``` **`.ChannelCapacity(n)`** - The bounded channel between the background goroutine and your code. If your code is slow to consume events, the background goroutine blocks when this fills up instead of eating unlimited memory. Default: 4096. ```go theme={null} .ChannelCapacity(8192) ``` **`.Logger(logger)`** - A `*slog.Logger` for connection lifecycle events. When unset, logging is controlled by `ORBITFLARE_LOG`. ## Building a subscription Filters are built programmatically with `SubscribeRequestBuilder` and the typed filter types. A transaction matches if it involves any `AccountInclude` address; `AccountExclude` removes matches; `AccountRequired` means every listed address must be present. ```go theme={null} request := geyser.NewSubscribeRequestBuilder(). Transactions("pumpfun", geyser.NewTransactionFilter(). Vote(false). Failed(false). AccountInclude("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")). Accounts("usdc-holders", geyser.NewAccountFilter(). Owner("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"). Datasize(165). Memcmp(geyser.MemcmpBase58(0, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")). Lamports(geyser.LamportsGt(0))). Slots("slots", geyser.NewSlotFilter().FilterByCommitment(true)). Commitment(geyser.Confirmed). Build() ``` ### Filter types * **`NewTransactionFilter()`** - `Vote(bool)`, `Failed(bool)`, `Signature(string)`, `AccountInclude(...)`, `AccountExclude(...)`, `AccountRequired(...)`. * **`NewAccountFilter()`** - `Account(...)`, `Owner(...)`, `Datasize(uint64)`, `TokenAccountState(bool)`, `Memcmp(...)`, `Lamports(...)`, `NonemptyTxnSignature(bool)`. * **`NewSlotFilter()`** - `FilterByCommitment(bool)`, `InterslotUpdates(bool)`. * **`NewBlockFilter()`** - `AccountInclude(...)`, `IncludeTransactions(bool)`, `IncludeAccounts(bool)`, `IncludeEntries(bool)`. * **`NewDeshredTransactionFilter()`** - `Vote(bool)`, `AccountInclude(...)`, `AccountExclude(...)`, `AccountRequired(...)`. `Memcmp` is built with `geyser.MemcmpBytes(offset, []byte)`, `geyser.MemcmpBase58(offset, string)`, or `geyser.MemcmpBase64(offset, string)`. `Lamports` with `geyser.LamportsEq/Ne/Lt/Gt(uint64)`. `Commitment` is one of `geyser.Processed`, `geyser.Confirmed`, `geyser.Finalized`. `Subscribe` also accepts a raw `*geyser.SubscribeRequest` (a re-export of the proto type) if you'd rather build it by hand. ## Reading the stream `Subscribe` returns a `*geyser.Stream`. Call `Next(ctx)` to get the next update: ```go theme={null} stream := client.Subscribe(request) defer stream.Close() for { update, err := stream.Next(ctx) if errors.Is(err, io.EOF) { break // stream closed } if err != nil { break // fatal: all retries exhausted } switch { case update.GetTransaction() != nil: tx := update.GetTransaction() // tx.GetSlot(), tx.GetTransaction() (signature, accounts, instructions, meta) case update.GetAccount() != nil: acct := update.GetAccount() // acct.GetSlot(), acct.GetAccount() (pubkey, lamports, owner, data), acct.GetIsStartup() case update.GetSlot() != nil: slot := update.GetSlot() // slot.GetSlot(), slot.GetStatus() case update.GetBlockMeta() != nil: meta := update.GetBlockMeta() // meta.GetSlot(), meta.GetBlockhash(), meta.GetParentSlot() } } ``` `Next(ctx)` blocks until an event arrives. It returns `io.EOF` once the stream has ended (including after `Close`); any other non-nil error is fatal for that stream. It also returns `ctx.Err()` if the context is cancelled. Pong messages are consumed internally and never appear in your stream. ### Closing ```go theme={null} stream.Close() ``` Stops the background goroutine immediately. `client.Close()` stops every stream the client started. ## Multiple streams One client can run many streams at once. Each gets its own background connection: ```go theme={null} pumpfun := client.Subscribe(pumpfunReq) raydium := client.Subscribe(raydiumReq) slots := client.Subscribe(slotsReq) ``` They share endpoint health state - if one stream quarantines a failing endpoint, the others skip it on their next reconnect. But their connections and lifecycles are fully independent. You can spin up new streams at any time. ## Full example A stream that watches pump.fun transactions and prints a summary for each one. ```go theme={null} package main import ( "context" "errors" "fmt" "io" "log" "github.com/orbitflare/orbitflare-sdk-go/geyser" ) func main() { client, err := geyser.NewBuilder(). URL("http://ny.rpc.orbitflare.com:10000"). FallbackURL("http://fra.rpc.orbitflare.com:10000"). Build() if err != nil { log.Fatal(err) } defer client.Close() request := geyser.NewSubscribeRequestBuilder(). Transactions("pumpfun", geyser.NewTransactionFilter(). Vote(false). Failed(false). AccountInclude("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")). Slots("slots", geyser.NewSlotFilter().FilterByCommitment(true)). Commitment(geyser.Confirmed). Build() stream := client.Subscribe(request) defer stream.Close() ctx := context.Background() var count int fmt.Println("streaming...") for { update, err := stream.Next(ctx) if errors.Is(err, io.EOF) { return } if err != nil { log.Fatal(err) } switch { case update.GetTransaction() != nil: count++ tx := update.GetTransaction() fmt.Printf("#%d slot=%d\n", count, tx.GetSlot()) case update.GetSlot() != nil: s := update.GetSlot() fmt.Printf("slot %d (%s)\n", s.GetSlot(), s.GetStatus()) } } } ``` # Go JetStream Client Source: https://docs.orbitflare.com/sdk/go-jetstream OrbitFlare's decoded shreds as gRPC streams in Go, with typed transaction filters that can be added and removed on a live stream. ## Install ```bash theme={null} go get github.com/orbitflare/orbitflare-sdk-go ``` ```go theme={null} import "github.com/orbitflare/orbitflare-sdk-go/jetstream" ``` ## Building the client ```go theme={null} client, err := jetstream.NewBuilder(). URL("http://ny.jetstream.orbitflare.com"). FallbackURL("http://fra.jetstream.orbitflare.com"). Build() ``` The URL falls back to the `ORBITFLARE_JETSTREAM_URL` environment variable. All builder methods are identical to the [gRPC client](/sdk/go-grpc) - same defaults, same behavior (`Retry`, `Timeout`, `Keepalive`, `PingInterval`, `MaxMissedPongs`, `ChannelCapacity`, `Logger`). ## Building a subscription JetStream supports transaction filters (no slots, blocks, or commitment - those are Yellowstone-specific). Each `TransactionFilter` exposes `AccountInclude`, `AccountExclude`, and `AccountRequired`. ```go theme={null} request := jetstream.NewSubscribeRequestBuilder(). Transactions("raydium", jetstream.NewTransactionFilter(). AccountInclude("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")). Transactions("pumpfun", jetstream.NewTransactionFilter(). AccountInclude("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")). Build() ``` ## Reading the stream `Subscribe` returns a `*jetstream.Stream`. Call `Next(ctx)`: ```go theme={null} stream := client.Subscribe(request) defer stream.Close() for { update, err := stream.Next(ctx) if errors.Is(err, io.EOF) { break } if err != nil { break } if tx := update.GetTransaction(); tx != nil { // tx.GetSlot() - the slot number // tx.GetTransaction() - transaction info with signature, account_keys, instructions } } ``` Closing, multiple streams, reconnection, and ping/pong all work identically to the [gRPC client](/sdk/go-grpc). ## Full example A stream that watches Raydium AMM swaps and prints each transaction's slot. ```go theme={null} package main import ( "context" "errors" "fmt" "io" "log" "github.com/orbitflare/orbitflare-sdk-go/jetstream" ) func main() { client, err := jetstream.NewBuilder(). URL("http://ny.jetstream.orbitflare.com"). Build() if err != nil { log.Fatal(err) } defer client.Close() request := jetstream.NewSubscribeRequestBuilder(). Transactions("raydium", jetstream.NewTransactionFilter(). AccountInclude("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")). Build() stream := client.Subscribe(request) defer stream.Close() ctx := context.Background() var count int fmt.Println("streaming raydium txs...") for { update, err := stream.Next(ctx) if errors.Is(err, io.EOF) { return } if err != nil { log.Fatal(err) } if tx := update.GetTransaction(); tx != nil { count++ fmt.Printf("#%d slot=%d\n", count, tx.GetSlot()) } } } ``` ## JetStream v2 JetStream v2 (`github.com/orbitflare/orbitflare-sdk-go/jetstream/v2`) runs on the **same endpoints and authentication** as v1 and is fully additive: v1 keeps working unchanged. What v2 adds: * **Runtime-managed filters** - add and remove filters on a live stream without reconnecting. * **Per-message sequence numbers** - every response carries a monotonic `Sequence`, so you can detect dropped messages. * **Opt-in enrichment** - request fee payer, program ids, compute-unit price, compute limit, resolved address-table addresses, and more, per transaction. * **Slot lifecycle events** - a separate server stream of slot alive/complete/dead events. The v2 client lives under `jetstream/v2`. The builder is identical to v1 (same methods, defaults, `ORBITFLARE_JETSTREAM_URL` env var, and failover): ```go theme={null} import jetstreamv2 "github.com/orbitflare/orbitflare-sdk-go/jetstream/v2" client, err := jetstreamv2.NewBuilder(). URL("http://ny.jetstream.orbitflare.com"). FallbackURL("http://fra.jetstream.orbitflare.com"). Build() ``` ### Building typed filters Each filter is a `TransactionFilter` with a client-chosen id (via `.WithID()`). The id is echoed back on every matching transaction and in the filter-validation acknowledgement, so you can correlate matches and remove the filter later. ```go theme={null} filter := jetstreamv2.NewTransactionFilter(). AccountInclude("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"). IncludeEnrichment(true). WithID("pumpfun") ``` A filter must set at least one of `AccountInclude`, `AccountExclude`, or `AccountRequired`; empty (match-everything) filters are rejected. `IncludeEnrichment` is optional (default off) and applies to the whole subscription: if any active filter enables it, every transaction you receive is enriched. ### Subscribing to transactions ```go theme={null} stream := client.SubscribeTransactions(filter) defer stream.Close() for { resp, err := stream.Next(ctx) if errors.Is(err, io.EOF) { break } if err != nil { break } switch { case resp.GetTransaction() != nil: ft := resp.GetTransaction() // resp.GetSequence() - monotonic per-stream sequence number // ft.GetFilterIds() - which of your filters this tx matched if tx := ft.GetTransaction(); tx != nil { // tx.GetSlot(), tx.GetSignature(), tx.GetAccountKeys(), tx.GetInstructions() // enrichment (only when IncludeEnrichment is set): tx.GetFeePayer(), // tx.GetProgramIds(), tx.GetComputeUnitPrice(), tx.GetComputeLimit(), // tx.GetLoadedWritableAddresses(), tx.GetLoadedReadonlyAddresses() fmt.Printf("seq=%d slot=%d\n", resp.GetSequence(), tx.GetSlot()) } case resp.GetFilterValidation() != nil: r := resp.GetFilterValidation() // one per filter after each add/remove fmt.Printf("filter %s accepted=%v %s\n", r.GetFilterId(), r.GetAccepted(), r.GetRejectionReason()) case resp.GetHeartbeat() != nil: // resp.GetHeartbeat().GetServerTsMs() - server wall-clock, ms since epoch case resp.GetPong() != nil: // resp.GetPong().GetPingId() } } ``` ### Managing filters on a live stream Take a handle from the stream and add or remove filters without reconnecting. Removals reference the filter id you assigned with `.WithID()`. ```go theme={null} handle := stream.Handle() handle.AddFilters([]*jetstreamv2.TxFilter{ jetstreamv2.NewTransactionFilter(). AccountInclude("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"). WithID("raydium"), }) handle.RemoveFilters([]string{"pumpfun"}) ``` `stream.AddFilters(...)` and `stream.RemoveFilters(...)` are shortcuts for the same thing. Each returns an error if the stream has been closed. ### Slot lifecycle events `SubscribeSlots()` is a separate server stream and takes no filters. ```go theme={null} slots := client.SubscribeSlots() defer slots.Close() for { event, err := slots.Next(ctx) if errors.Is(err, io.EOF) { break } if err != nil { break } // event.GetSlot(), event.GetStatus() (a SlotStatus enum: alive / complete / dead), // event.GetParentSlot(), event.GetCurrentLeader(), event.GetSequence() fmt.Printf("slot=%d status=%s\n", event.GetSlot(), event.GetStatus()) } ``` ### Health probes `GetVersion(ctx)` and `Ping(ctx, count)` are unary calls with the same failover as the streams. ```go theme={null} version, err := client.GetVersion(ctx) count, err := client.Ping(ctx, 7) ``` For the full protobuf specification, message shapes, enrichment fields, and slot statuses, see the [JetStream Protocol Reference](/data-streaming/jetstream-reference). # Go RPC Client Source: https://docs.orbitflare.com/sdk/go-rpc Solana JSON-RPC client for Go from the orbitflare-sdk-go module, with retry, failover, and typed helpers for common methods. ## Install ```bash theme={null} go get github.com/orbitflare/orbitflare-sdk-go ``` ```go theme={null} import "github.com/orbitflare/orbitflare-sdk-go/rpc" ``` ## Building the client Here's a client with every option set: ```go theme={null} import ( "time" "github.com/orbitflare/orbitflare-sdk-go/orbitflare" "github.com/orbitflare/orbitflare-sdk-go/rpc" ) client, err := rpc.NewBuilder(). URL("http://ny.rpc.orbitflare.com"). FallbackURL("http://fra.rpc.orbitflare.com"). FallbackURL("http://ams.rpc.orbitflare.com"). APIKey("ORBIT-XXXXXX-NNNNNN-NNNNNN"). Commitment("confirmed"). Retry(orbitflare.RetryPolicy{ InitialDelay: 100 * time.Millisecond, MaxDelay: 30 * time.Second, Multiplier: 2.0, MaxAttempts: 5, }). Timeout(30 * time.Second). Build() ``` Most of these have sensible defaults. A minimal setup: ```go theme={null} client, err := rpc.NewBuilder(). URL("http://ny.rpc.orbitflare.com"). Build() ``` Every method takes a `context.Context`, so pass a context with a deadline to bound a call: ```go theme={null} ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() ``` ### Builder methods **`.URL(url)`** - The primary endpoint to send requests to. If not set, the SDK checks the `ORBITFLARE_RPC_URL` environment variable. ```go theme={null} .URL("http://ny.rpc.orbitflare.com") ``` **`.URLs(urls...)`** - Set the primary and all fallbacks in one call. The first element is the primary, the rest are fallbacks. ```go theme={null} .URLs("http://ny.rpc.orbitflare.com", "http://fra.rpc.orbitflare.com", "http://ams.rpc.orbitflare.com") ``` **`.FallbackURL(url)`** - Add a single fallback endpoint. Call multiple times to add several. When the primary fails, the SDK tries fallbacks in order. ```go theme={null} .FallbackURL("http://fra.rpc.orbitflare.com"). FallbackURL("http://ams.rpc.orbitflare.com") ``` **`.FallbackURLs(urls...)`** - Same as `FallbackURL` but takes several at once. ```go theme={null} .FallbackURLs("http://fra.rpc.orbitflare.com", "http://ams.rpc.orbitflare.com") ``` **`.APIKey(key)`** - Your OrbitFlare license key. If not set, the SDK checks `ORBITFLARE_LICENSE_KEY` from the environment. The key is injected into the URL at request time, never stored in the endpoint, and never printed in logs or errors. ```go theme={null} .APIKey("ORBIT-XXXXXX-NNNNNN-NNNNNN") ``` **`.Commitment(level)`** - Default commitment level used for all typed helpers. Options: `"processed"`, `"confirmed"`, `"finalized"`. Defaults to `"confirmed"`. ```go theme={null} .Commitment("finalized") ``` **`.Retry(policy)`** - Controls how the SDK retries failed requests. `InitialDelay` is the wait before the first retry. `Multiplier` scales the delay on each attempt. `MaxDelay` caps the backoff. `MaxAttempts` limits total retries (0 means infinite). Defaults: 100ms initial, 30s max, 2x multiplier, infinite attempts. ```go theme={null} .Retry(orbitflare.RetryPolicy{ InitialDelay: 200 * time.Millisecond, MaxDelay: 15 * time.Second, Multiplier: 2.0, MaxAttempts: 5, }) ``` **`.Timeout(duration)`** - HTTP timeout for each individual request. Defaults to 30 seconds. ```go theme={null} .Timeout(10 * time.Second) ``` **`.Logger(logger)`** - A `*slog.Logger` for request retries and failovers. When unset, logging is controlled by the `ORBITFLARE_LOG` environment variable (`trace|debug|info|warn|error|silent`, default `silent`). ```go theme={null} .Logger(slog.New(slog.NewTextHandler(os.Stderr, nil))) ``` ## Available RPC methods Typed helpers return Go values; everything else returns `json.RawMessage` you can unmarshal into your own types. ### `GetSlot(ctx)` Returns the current slot number. ```go theme={null} slot, err := client.GetSlot(ctx) // uint64 ``` ### `GetBalance(ctx, address)` Returns the balance in lamports for an account. ```go theme={null} lamports, err := client.GetBalance(ctx, "So11111111111111111111111111111111111111112") // uint64 sol := float64(lamports) / 1_000_000_000 ``` ### `GetAccountInfo(ctx, address)` Returns the full account data as `json.RawMessage`, or nil if the account doesn't exist. Data is base64-encoded. ```go theme={null} raw, err := client.GetAccountInfo(ctx, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") if raw != nil { var info struct { Owner string `json:"owner"` Lamports uint64 `json:"lamports"` } json.Unmarshal(raw, &info) } ``` ### `GetMultipleAccounts(ctx, addresses)` Fetches multiple accounts in one call. Automatically chunks into batches of 100 (Solana's per-request limit), so you can pass any number of addresses. ```go theme={null} accounts, err := client.GetMultipleAccounts(ctx, []string{"addr1", "addr2", "addr3"}) // []json.RawMessage ``` ### `GetLatestBlockhash(ctx)` Returns the most recent blockhash and the last block height it's valid for. ```go theme={null} blockhash, lastValid, err := client.GetLatestBlockhash(ctx) // (string, uint64) ``` ### `GetTransaction(ctx, signature)` Fetches a confirmed transaction by its signature. Returns the full transaction with metadata as `json.RawMessage`. ```go theme={null} raw, err := client.GetTransaction(ctx, "5K8F2j...") ``` ### `GetSignaturesForAddress(ctx, address, limit)` Returns recent transaction signatures for an address, newest first. ```go theme={null} sigs, err := client.GetSignaturesForAddress(ctx, "So111...", 10) // []json.RawMessage ``` ### `GetProgramAccounts(ctx, programID)` Returns all accounts owned by a program. Can return a lot of data. ```go theme={null} accounts, err := client.GetProgramAccounts(ctx, "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") ``` ### `GetRecentPrioritizationFees(ctx, addresses)` Returns recent priority fees for a set of accounts. Useful for estimating compute unit pricing. ```go theme={null} fees, err := client.GetRecentPrioritizationFees(ctx, []string{"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"}) ``` ### `SendTransaction(ctx, txBase64)` Sends a signed transaction to the network. Takes a base64-encoded serialized transaction. Returns the transaction signature. ```go theme={null} signature, err := client.SendTransaction(ctx, txBase64) // string ``` ### `SimulateTransaction(ctx, txBase64)` Simulates a transaction without submitting it. Returns logs, compute units consumed, and any errors as `json.RawMessage`. ```go theme={null} raw, err := client.SimulateTransaction(ctx, txBase64) ``` ### `GetTokenAccountsByOwner(ctx, owner, mint, programID)` Returns token accounts for a wallet. Pass either a specific mint or a token program ID. If both are empty strings, defaults to the SPL Token program. ```go theme={null} tokens, err := client.GetTokenAccountsByOwner(ctx, "wallet_address", "", "") ``` ### `GetTransactionsForAddress(ctx, address, options)` OrbitFlare-specific method that combines `getSignaturesForAddress` and `getTransaction` into one call. Returns a `GetTransactionsResult` with the `Data` slice and an optional `PaginationToken` for fetching the next page. Supports four detail levels (`signatures`, `none`, `accounts`, `full`), bidirectional sorting, time/slot filtering, status filtering, and token account inclusion. `options` may be nil for defaults. ```go theme={null} details, order, status := "full", "asc", "succeeded" var limit uint32 = 100 gte, lte := int64(1704067200), int64(1706745600) result, err := client.GetTransactionsForAddress(ctx, "address", &rpc.GetTransactionsOptions{ TransactionDetails: &details, Limit: &limit, SortOrder: &order, Filters: &rpc.GetTransactionsFilters{ Status: &status, BlockTime: &rpc.RangeFilter[int64]{Gte: >e, Lte: <e}, }, }) for _, tx := range result.Data { _ = tx // json.RawMessage } if result.PaginationToken != nil { // fetch next page by setting PaginationToken on the next call } ``` ### `Request(ctx, method, params)` Call any RPC method by name. The SDK builds the JSON-RPC envelope, handles retry and failover, and returns the `result` field as `json.RawMessage`. ```go theme={null} epoch, err := client.Request(ctx, "getEpochInfo", []any{}) inflation, err := client.Request(ctx, "getInflationRate", []any{}) ``` ### `RequestRaw(ctx, body)` Send a raw JSON-RPC body string. Same retry and failover as everything else. ```go theme={null} result, err := client.RequestRaw(ctx, `{"jsonrpc":"2.0","id":1,"method":"getHealth","params":[]}`) ``` ## Full example A program that checks the state of the network, fetches a wallet's SOL balance, and looks up recent transactions. ```go theme={null} package main import ( "context" "encoding/json" "fmt" "log" "time" "github.com/orbitflare/orbitflare-sdk-go/rpc" ) func main() { client, err := rpc.NewBuilder(). URL("http://ny.rpc.orbitflare.com"). FallbackURL("http://fra.rpc.orbitflare.com"). Commitment("confirmed"). Build() if err != nil { log.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() wallet := "CKs1E69a2e9TmH4mKKLrXFF8kD3ZnwKjoEuXa6sz9WqX" slot, _ := client.GetSlot(ctx) blockhash, _, _ := client.GetLatestBlockhash(ctx) fmt.Printf("network slot: %d\nblockhash: %s\n", slot, blockhash) balance, _ := client.GetBalance(ctx, wallet) fmt.Printf("\n%s\n SOL: %.4f\n", wallet, float64(balance)/1_000_000_000) sigs, _ := client.GetSignaturesForAddress(ctx, wallet, 5) fmt.Println("\nrecent transactions:") for _, raw := range sigs { var sig struct { Signature string `json:"signature"` Slot uint64 `json:"slot"` Err any `json:"err"` } json.Unmarshal(raw, &sig) status := "ok" if sig.Err != nil { status = "failed" } fmt.Printf(" %d %s %s\n", sig.Slot, status, sig.Signature[:20]) } } ``` # Go WebSocket Client Source: https://docs.orbitflare.com/sdk/go-websocket Real-time Solana subscriptions in Go from the orbitflare-sdk-go module: account changes, logs, slots, and signatures with auto-reconnect. ## Install ```bash theme={null} go get github.com/orbitflare/orbitflare-sdk-go ``` ```go theme={null} import "github.com/orbitflare/orbitflare-sdk-go/ws" ``` ## Building the client ```go theme={null} import ( "time" "github.com/orbitflare/orbitflare-sdk-go/orbitflare" "github.com/orbitflare/orbitflare-sdk-go/ws" ) client, err := ws.NewBuilder(). URL("ws://ny.rpc.orbitflare.com"). FallbackURL("ws://fra.rpc.orbitflare.com"). APIKey("ORBIT-XXXXXX-NNNNNN-NNNNNN"). Retry(orbitflare.RetryPolicy{ InitialDelay: 100 * time.Millisecond, MaxDelay: 30 * time.Second, Multiplier: 2.0, MaxAttempts: 0, }). PingInterval(10 * time.Second). MaxMissedPongs(3). Build(ctx) ``` Minimal: ```go theme={null} client, err := ws.NewBuilder(). URL("ws://ny.rpc.orbitflare.com"). Build(ctx) ``` Note that `.Build(ctx)` establishes the WebSocket connection before returning - the passed context governs the initial dial. If the connection fails, you get the error immediately. The client then runs until `Close()`. ### Builder methods **`.URL(url)`** - Primary WebSocket endpoint. Falls back to `ORBITFLARE_WS_URL` env var. ```go theme={null} .URL("ws://ny.rpc.orbitflare.com") ``` **`.URLs(urls...)`** - Primary + fallbacks in one call. ```go theme={null} .URLs("ws://ny.rpc.orbitflare.com", "ws://fra.rpc.orbitflare.com") ``` **`.FallbackURL(url)` / `.FallbackURLs(urls...)`** - Add failover endpoints. On reconnect, the SDK rotates through them. ```go theme={null} .FallbackURL("ws://fra.rpc.orbitflare.com") ``` **`.APIKey(key)`** - License key. Falls back to `ORBITFLARE_LICENSE_KEY` env var. Injected into the URL at connect time, never printed in logs or errors. ```go theme={null} .APIKey("ORBIT-XXXXXX-NNNNNN-NNNNNN") ``` **`.Retry(policy)`** - Reconnection backoff. Default: 100ms initial, 30s max, 2x multiplier, infinite attempts. ```go theme={null} .Retry(orbitflare.RetryPolicy{ InitialDelay: 200 * time.Millisecond, MaxDelay: 15 * time.Second, Multiplier: 2.0, MaxAttempts: 0, }) ``` **`.PingInterval(duration)`** - How often the SDK sends WebSocket `Ping` frames. Default: 10s. ```go theme={null} .PingInterval(15 * time.Second) ``` **`.MaxMissedPongs(n)`** - Pings without a response before killing the connection. Default: 3. ```go theme={null} .MaxMissedPongs(5) ``` **`.Logger(logger)`** - A `*slog.Logger` for connection lifecycle events. When unset, controlled by `ORBITFLARE_LOG`. ## Available subscriptions ### `SlotSubscribe(ctx)` Fires every time a slot is processed, confirmed, or finalized. ```go theme={null} sub, err := client.SlotSubscribe(ctx) ``` Each event is JSON with `slot`, `parent`, and `root` fields: ```json theme={null} {"slot": 413014740, "parent": 413014739, "root": 413014708} ``` ### `AccountSubscribe(ctx, address, commitment)` Fires when the specified account's data changes. ```go theme={null} sub, err := client.AccountSubscribe(ctx, "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "confirmed") ``` Returns base64-encoded account data with lamports, owner, and executable flag. ### `LogsSubscribe(ctx, mentions, commitment)` Fires for transactions that mention the given addresses. Pass an empty slice for all transactions. ```go theme={null} sub, err := client.LogsSubscribe(ctx, []string{"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"}, "confirmed") ``` All logs: ```go theme={null} sub, err := client.LogsSubscribe(ctx, nil, "confirmed") ``` ### `SignatureSubscribe(ctx, signature, commitment)` Fires once when a transaction reaches the given commitment level. Useful for confirming a transaction you just sent. ```go theme={null} sub, err := client.SignatureSubscribe(ctx, "5K8F2j...", "confirmed") ``` ## Reading events All subscriptions return a `*ws.Subscription`. Call `Next(ctx)` to get the next event: ```go theme={null} for { event, ok := sub.Next(ctx) if !ok { break // context cancelled or client shut down } fmt.Println(string(event)) } ``` `Next(ctx)` returns `(json.RawMessage, bool)`. When `ok` is false the subscription is finished (the context was cancelled or the client closed). ## Unsubscribing ```go theme={null} sub.Unsubscribe() ``` This sends an unsubscribe message to the server. If you stop reading a subscription without calling this, the SDK detects the orphan and sends the unsubscribe automatically. ## Multiple subscriptions All subscriptions run on a single WebSocket connection. The SDK routes notifications to the right subscription internally. ```go theme={null} slots, _ := client.SlotSubscribe(ctx) usdc, _ := client.AccountSubscribe(ctx, "EPjFWdd5...", "confirmed") logs, _ := client.LogsSubscribe(ctx, []string{"6EF8r..."}, "confirmed") ``` You can add new subscriptions at any time while existing ones are running. ## Reconnection If the connection drops, the background goroutine reconnects with exponential backoff and re-subscribes everything automatically. Your `Next(ctx)` calls just keep working - events resume once the connection is back. ## Full example A monitoring program that subscribes to slot updates and USDC account changes, printing a live feed. ```go theme={null} package main import ( "context" "encoding/json" "fmt" "log" "github.com/orbitflare/orbitflare-sdk-go/ws" ) func main() { ctx := context.Background() client, err := ws.NewBuilder(). URL("ws://ny.rpc.orbitflare.com"). Build(ctx) if err != nil { log.Fatal(err) } defer client.Close() slots, err := client.SlotSubscribe(ctx) if err != nil { log.Fatal(err) } defer slots.Unsubscribe() fmt.Println("watching slots...") for { event, ok := slots.Next(ctx) if !ok { return } var slot struct { Slot uint64 `json:"slot"` Parent uint64 `json:"parent"` } json.Unmarshal(event, &slot) fmt.Printf("slot %d (parent %d)\n", slot.Slot, slot.Parent) } } ``` # OrbitFlare SDKs Source: https://docs.orbitflare.com/sdk/overview Official client libraries for building on OrbitFlare. RPC, gRPC (Yellowstone), JetStream, and WebSocket - with built-in retry, failover, and reconnection. The OrbitFlare SDK gives you typed clients for every OrbitFlare service. Instead of managing raw HTTP requests, gRPC channels, and WebSocket connections yourself, the SDK handles connection lifecycle, authentication, retries, failover, and reconnection behind a clean API. ## What's included | Service | What it does | | - | - | | **RPC** | JSON-RPC client with typed helpers for common Solana methods, plus raw escape hatches for anything custom | | **WebSocket** | Subscriptions for account changes, logs, slots, and signatures with auto-resubscribe on reconnect | | **gRPC (Yellowstone)** | Yellowstone Geyser streaming - transactions, accounts, slots, blocks. YAML config or programmatic filters | | **JetStream** | OrbitFlare's decoded shreds delivered as gRPC streams. Same client pattern as Yellowstone, different proto. v1 (fixed filters) and v2 (runtime-managed filters, sequence numbers, opt-in enrichment, slot events) | ## Environment variables | Variable | Used by | Purpose | | - | - | - | | `ORBITFLARE_LICENSE_KEY` | RPC, WebSocket | API key appended to endpoint URLs | | `ORBITFLARE_RPC_URL` | RPC | Default endpoint if `.url()` is not called | | `ORBITFLARE_WS_URL` | WebSocket | Default endpoint if `.url()` is not called | | `ORBITFLARE_GRPC_URL` | gRPC | Default endpoint if `.url()` is not called | | `ORBITFLARE_JETSTREAM_URL` | JetStream | Default endpoint if `.url()` is not called | ## Robinhood Chain SDK For [Robinhood Chain](/robinhood-chain), OrbitFlare ships a separate Rust crate, `orbitflare-robinhood-sdk`, built on alloy types with the same transport features - retry, endpoint failover, and self-healing WebSocket subscriptions. See the [Robinhood Chain SDK](/sdk/robinhood) page. ## Source The SDKs are open source: * Rust: [github.com/orbitflare/orbitflare-sdk-rs](https://github.com/orbitflare/orbitflare-sdk-rs) * TypeScript: [github.com/orbitflare/orbitflare-sdk-ts](https://github.com/orbitflare/orbitflare-sdk-ts) * Go: [github.com/orbitflare/orbitflare-sdk-go](https://github.com/orbitflare/orbitflare-sdk-go) * Robinhood Chain (Rust): [github.com/orbitflare/orbitflare-robinhood-sdk-rs](https://github.com/orbitflare/orbitflare-robinhood-sdk-rs) # Rust SDK Source: https://docs.orbitflare.com/sdk/robinhood Rust SDK for Robinhood Chain, the orbitflare-robinhood-sdk crate. `orbitflare-robinhood-sdk` is deprecated. Robinhood Chain is now supported by the [EVM SDK](/sdk/evm) (`orbitflare-evm-sdk`) via `RobinhoodRpcClient` / `RobinhoodWsClient`, which also covers Polygon, BNB, and custom chains. The RPC and WebSocket surface is the same. This crate will receive no further updates. Built on [alloy](https://github.com/alloy-rs/alloy) types throughout - `Address`, `U256`, `B256`, `Filter`, `TransactionRequest`, typed blocks, transactions, receipts, and logs - with OrbitFlare's own transport: endpoint failover, retry with backoff, and self-healing WebSocket subscriptions. It targets [Robinhood Chain](/robinhood-chain), the EVM-compatible L2, and defaults to OrbitFlare's production endpoints. ## Install ```bash theme={null} cargo add orbitflare-robinhood-sdk ``` Only the RPC client is enabled by default. Enable what you need: ```bash theme={null} cargo add orbitflare-robinhood-sdk --features ws cargo add orbitflare-robinhood-sdk --features all ``` ## Alloy types All addresses, hashes, and quantities are alloy types. The common ones are re-exported at the crate root (`Address`, `U256`, `B256`, `Bytes`, `Filter`, `TransactionRequest`, `BlockNumberOrTag`, ...), and the full crates are available as `orbitflare_robinhood_sdk::primitives` (alloy-primitives) and `orbitflare_robinhood_sdk::rpc_types` (alloy-rpc-types-eth). ```rust theme={null} use orbitflare_robinhood_sdk::primitives::{address, b256, utils::format_ether}; ``` ## RPC client Here's a client with every option set: ```rust theme={null} use orbitflare_robinhood_sdk::{BlockNumberOrTag, Result, RetryPolicy, RpcClientBuilder}; use std::time::Duration; let client = RpcClientBuilder::new() .url("https://robinhood.rpc.orbitflare.com") .fallback_urls(&["https://robinhood-backup.rpc.orbitflare.com"]) .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .block_tag(BlockNumberOrTag::Finalized) .retry(RetryPolicy { initial_delay: Duration::from_millis(100), max_delay: Duration::from_secs(30), multiplier: 2.0, max_attempts: 5, }) .timeout(Duration::from_secs(30)) .build()?; ``` Everything has a sensible default - the builder defaults to the production OrbitFlare endpoint, so the minimal setup is: ```rust theme={null} let client = RpcClientBuilder::new().build()?; ``` ### Builder methods **`.url(url)`** - The primary endpoint. Resolution order: `.url()` on the builder, then the `ORBITFLARE_ROBINHOOD_RPC_URL` environment variable, then the default `https://robinhood.rpc.orbitflare.com` (`rpc::DEFAULT_RPC_URL`). **`.urls(&[...])`** - Set the primary and all fallbacks in one call. The first element is the primary, the rest are fallbacks. **`.fallback_url(url)` / `.fallback_urls(&[...])`** - Add failover endpoints. When the primary fails, the SDK tries fallbacks in order. Failing endpoints are quarantined with exponential cooldown (10s, 20s, 40s, max 60s) and automatically retried once the cooldown expires; healthy endpoints are always preferred. **`.api_key(key)`** - Your OrbitFlare license key. If not set, the SDK checks `ORBITFLARE_LICENSE_KEY` from the environment. The key is appended to the endpoint URL at request time. **`.block_tag(tag)`** - Default block tag used by state queries (`get_balance`, `call`, `get_code`, ...). Takes anything that converts into `BlockNumberOrTag`. Defaults to `Latest`. **`.retry(policy)`** - Controls retry on transient errors (5xx, 429, connection resets, JSON-RPC error code -32005) with exponential backoff before failing over to the next endpoint. 429 responses with a `Retry-After` header are respected. **`.timeout(duration)`** - HTTP timeout for each individual request. ### Available RPC methods | Method | Returns | | - | - | | `get_block_number()` | `u64` | | `get_chain_id()` | `u64` | | `get_balance(Address)` | `U256` wei | | `get_transaction_count(Address)` | `u64` nonce | | `get_gas_price()` | `u128` wei | | `max_priority_fee_per_gas()` | `u128` wei | | `get_block_by_number(impl Into, full_txs)` | `Option` | | `get_transaction_by_hash(B256)` | `Option` | | `get_transaction_receipt(B256)` | `Option` | | `get_logs(&Filter)` | `Vec` | | `get_code(Address)` | `Bytes` | | `call(&TransactionRequest)` | `Bytes` | | `estimate_gas(&TransactionRequest)` | `u64` | | `send_raw_transaction(&[u8])` | `B256` tx hash | | `fee_history(blocks, newest, percentiles)` | `FeeHistory` | | `request(method, params)` | Any RPC method by name (`serde_json::Value`) | | `request_raw(body)` | Raw JSON-RPC body string | ### Reading chain state ```rust theme={null} use orbitflare_robinhood_sdk::primitives::{address, utils::format_ether}; let block = client.get_block_number().await?; let gas_price = client.get_gas_price().await?; let wallet = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); let balance = client.get_balance(wallet).await?; let nonce = client.get_transaction_count(wallet).await?; println!("ETH: {}", format_ether(balance)); ``` ### Log filters `get_logs` takes alloy's `Filter` directly: ```rust theme={null} use orbitflare_robinhood_sdk::primitives::{address, b256}; use orbitflare_robinhood_sdk::{BlockNumberOrTag, Filter}; let transfer_topic = b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); let filter = Filter::new() .from_block(1_000_000u64) .to_block(BlockNumberOrTag::Latest) .address(address!("d0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC")) .event_signature(transfer_topic); let logs = client.get_logs(&filter).await?; ``` ### Contract calls and transactions `call` and `estimate_gas` take alloy's `TransactionRequest`. To send a transaction, build and sign it with alloy (`alloy-signer`, `alloy-network`), then broadcast through the SDK: ```rust theme={null} let hash = client.send_raw_transaction(&signed_tx_rlp).await?; let receipt = client.get_transaction_receipt(hash).await?; ``` ### Arbitrary methods `request` calls any RPC method by name - the SDK builds the JSON-RPC envelope, handles retry and failover, and returns the `result` field. This also covers the `arb_*` extension methods Robinhood Chain serves as an Arbitrum Nitro chain. `request_raw` sends a raw JSON-RPC body string. ```rust theme={null} use serde_json::json; let syncing = client.request("eth_syncing", json!([])).await?; let version = client .request_raw(r#"{"jsonrpc":"2.0","id":1,"method":"web3_clientVersion","params":[]}"#) .await?; ``` ## WebSocket client Enable the `ws` feature. Robinhood Chain produces blocks roughly every 100 milliseconds, so `newHeads` fires far faster than on Ethereum mainnet or most L2s. ```rust theme={null} use orbitflare_robinhood_sdk::{Result, RetryPolicy, WsClientBuilder}; use std::time::Duration; let client = WsClientBuilder::new() .url("wss://robinhood.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .retry(RetryPolicy { initial_delay: Duration::from_millis(100), max_delay: Duration::from_secs(30), multiplier: 2.0, max_attempts: 0, }) .ping_interval_secs(10) .max_missed_pongs(3) .build() .await?; ``` Minimal: ```rust theme={null} let client = WsClientBuilder::new().build().await?; ``` Note that `.build()` is async - it establishes the WebSocket connection before returning. The builder shares `.urls()`, `.fallback_url(s)()`, `.api_key()`, and `.retry()` with the RPC builder; the WebSocket-specific options are: **`.url(url)`** - Primary WebSocket endpoint. Resolution order: `.url()`, then `ORBITFLARE_ROBINHOOD_WS_URL`, then the default `wss://robinhood.rpc.orbitflare.com` (`ws::DEFAULT_WS_URL`). **`.ping_interval_secs(n)`** - How often the SDK sends WebSocket `Ping` frames to detect dead connections. Default: 10. **`.max_missed_pongs(n)`** - Pings without a response before the connection is considered dead and reconnected. Default: 3. ### Subscriptions Subscriptions are typed - each yields the corresponding alloy type instead of raw JSON: | Method | Yields | | - | - | | `new_heads_subscribe()` | `Header` per new block | | `logs_subscribe(&Filter)` | `Log` matching the filter | | `new_pending_transactions_subscribe()` | `B256` transaction hash | ```rust theme={null} use orbitflare_robinhood_sdk::primitives::b256; use orbitflare_robinhood_sdk::Filter; let mut heads = client.new_heads_subscribe().await?; let transfer_topic = b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); let mut transfers = client .logs_subscribe(&Filter::new().event_signature(transfer_topic)) .await?; ``` All subscriptions return a `WsSubscription`. Call `.next()` for the next typed event (`None` means the subscription was closed), or `.next_raw()` for the untyped `serde_json::Value` payload. ```rust theme={null} while let Some(head) = heads.next().await { println!("block {} (gas used {})", head.number, head.gas_used); } ``` All subscriptions run on a single WebSocket connection, and you can add new ones at any time. `sub.unsubscribe().await` removes one explicitly; dropping a subscription without calling it also works - the SDK detects the orphan and sends the unsubscribe automatically. ### Reconnection If the connection drops, the background task reconnects with exponential backoff and re-subscribes every active subscription automatically. Your `.next()` calls just keep working - events resume once the connection is back. Dead connections are detected via active ping/pong, configurable with `.ping_interval_secs()` and `.max_missed_pongs()`. ## Environment variables | Variable | Used by | Purpose | | - | - | - | | `ORBITFLARE_LICENSE_KEY` | RPC, WebSocket | API key appended to endpoint URLs | | `ORBITFLARE_ROBINHOOD_RPC_URL` | RPC | Overrides the default endpoint if `.url()` is not called | | `ORBITFLARE_ROBINHOOD_WS_URL` | WebSocket | Overrides the default endpoint if `.url()` is not called | ## Full example A monitoring script that reads a wallet, then follows new blocks and ERC-20 transfers in real time: ```rust theme={null} use orbitflare_robinhood_sdk::primitives::{address, b256, utils::format_ether}; use orbitflare_robinhood_sdk::{Filter, Result, RpcClientBuilder, WsClientBuilder}; #[tokio::main] async fn main() -> Result<()> { let rpc = RpcClientBuilder::new().build()?; let wallet = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); let balance = rpc.get_balance(wallet).await?; let block = rpc.get_block_number().await?; println!("block {block}, wallet holds {} ETH", format_ether(balance)); let ws = WsClientBuilder::new().build().await?; let mut heads = ws.new_heads_subscribe().await?; let transfer_topic = b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); let mut transfers = ws .logs_subscribe(&Filter::new().event_signature(transfer_topic)) .await?; println!("watching new heads and ERC-20 transfers..."); loop { tokio::select! { Some(head) = heads.next() => { println!("block {} (gas used {})", head.number, head.gas_used); } Some(log) = transfers.next() => { let tx = log.transaction_hash.unwrap_or_default(); println!("transfer on {} in {tx}", log.address()); } } } } ``` ## Source The SDK is open source: [github.com/orbitflare/orbitflare-robinhood-sdk-rs](https://github.com/orbitflare/orbitflare-robinhood-sdk-rs) # Rust gRPC Client (Yellowstone) Source: https://docs.orbitflare.com/sdk/rust-grpc Yellowstone Geyser streaming in Rust via the orbitflare-sdk grpc feature, with automatic reconnection, ping/pong liveness detection, and YAML-configured filters. ## Install ```bash theme={null} cargo add orbitflare-sdk --features grpc ``` ## Building the client Here's a client with every option set: ```rust theme={null} use orbitflare_sdk::{GeyserClientBuilder, RetryPolicy, Result}; use std::time::Duration; let client = GeyserClientBuilder::new() .url("http://ny.rpc.orbitflare.com:10000") .fallback_url("http://fra.rpc.orbitflare.com:10000") .retry(RetryPolicy { initial_delay: Duration::from_millis(100), max_delay: Duration::from_secs(30), multiplier: 2.0, max_attempts: 0, }) .timeout_secs(30) .keepalive_secs(60) .ping_interval_secs(10) .max_missed_pongs(3) .channel_capacity(4096) .build()?; ``` Minimal setup: ```rust theme={null} let client = GeyserClientBuilder::new() .url("http://ny.rpc.orbitflare.com:10000") .build()?; ``` ### Builder methods **`.url(url)`** - The primary gRPC endpoint. Falls back to `ORBITFLARE_GRPC_URL` env var. ```rust theme={null} .url("http://ny.rpc.orbitflare.com:10000") ``` **`.urls(&[...])`** - Primary + fallbacks in one call. First element is primary. ```rust theme={null} .urls(&["http://ny.rpc.orbitflare.com:10000", "http://fra.rpc.orbitflare.com:10000"]) ``` **`.fallback_url(url)` / `.fallback_urls(&[...])`** - Add fallback endpoints. On connection failure, the SDK rotates through them. ```rust theme={null} .fallback_url("http://fra.rpc.orbitflare.com:10000") ``` **`.retry(policy)`** - Controls reconnection backoff. When a connection drops, the SDK waits `initial_delay`, then doubles it each attempt (capped at `max_delay`). Set `max_attempts` to 0 for infinite retries. Default: 100ms initial, 30s max, 2x multiplier, infinite. ```rust theme={null} .retry(RetryPolicy { initial_delay: Duration::from_millis(200), max_delay: Duration::from_secs(15), multiplier: 2.0, max_attempts: 0, }) ``` **`.timeout_secs(n)`** - Per-request gRPC timeout. Default: 30. ```rust theme={null} .timeout_secs(15) ``` **`.keepalive_secs(n)`** - TCP keepalive interval. The OS sends probes at this interval to detect dead connections at the TCP level. Default: 60. ```rust theme={null} .keepalive_secs(30) ``` **`.ping_interval_secs(n)`** - How often the SDK sends proto-level `Ping` messages to the server. The server should respond with a `Pong`. Default: 10. ```rust theme={null} .ping_interval_secs(15) ``` **`.max_missed_pongs(n)`** - How many consecutive pings can go unanswered before the SDK considers the connection dead and reconnects. Default: 3. With the defaults, a dead connection is detected within 30 seconds. ```rust theme={null} .max_missed_pongs(5) ``` **`.channel_capacity(n)`** - The bounded channel between the background task and your code. If your code is slow to consume events, the background task blocks when this fills up instead of eating unlimited memory. Default: 4096. ```rust theme={null} .channel_capacity(8192) ``` ## Writing a YAML config Create a YAML file with the filters you want: ```yaml theme={null} # grpc.yml transactions: pumpfun: vote: false failed: false account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" jupiter: vote: false failed: false account_include: - "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" accounts: usdc_mint: account: - "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" token_program: owner: - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" slots: all: filter_by_commitment: true commitment: confirmed ``` ### YAML filter reference **`transactions`** - each entry is a named filter. A transaction matches if it involves any of the `account_include` addresses. `account_exclude` removes matches. `account_required` means all listed addresses must be present. `vote` and `failed` filter by transaction type. **`accounts`** - watch specific accounts by address, or watch all accounts owned by a program via `owner`. **`slots`** - subscribe to slot updates. `filter_by_commitment` only sends updates at your commitment level. **`commitment`** - `"processed"`, `"confirmed"`, or `"finalized"`. Defaults to `"confirmed"`. The YAML supports `${ENV_VAR}` expansion: ```yaml theme={null} transactions: target: account_include: - "${TARGET_PROGRAM}" ``` ## Subscribing and reading events ### From YAML ```rust theme={null} let mut stream = client.subscribe_yaml("grpc.yml")?; ``` ### Programmatically For dynamic filters built at runtime: ```rust theme={null} use std::collections::HashMap; use orbitflare_sdk::proto::geyser::*; let mut filters = HashMap::new(); filters.insert("target".into(), SubscribeRequestFilterTransactions { vote: Some(false), failed: Some(false), signature: None, account_include: vec![some_address.to_string()], account_exclude: vec![], account_required: vec![], }); let request = SubscribeRequest { transactions: filters, commitment: Some(1), ping: Some(SubscribeRequestPing { id: 1 }), ..Default::default() }; let mut stream = client.subscribe(request); ``` ### Reading the stream Both `subscribe_yaml` and `subscribe` return a `GeyserStream`. Call `.next()` to get the next event: ```rust theme={null} use orbitflare_sdk::proto::geyser::subscribe_update::UpdateOneof; while let Some(update) = stream.next().await { let update = update?; match update.update_oneof { Some(UpdateOneof::Transaction(tx)) => { // tx.slot - the slot this transaction was in // tx.transaction - the transaction info (signature, accounts, instructions, meta) } Some(UpdateOneof::Account(acct)) => { // acct.slot - the slot // acct.account - account info (pubkey, lamports, owner, data) // acct.is_startup - true during initial account snapshot } Some(UpdateOneof::Slot(slot)) => { // slot.slot - the slot number // slot.status - processed, confirmed, finalized, etc. } Some(UpdateOneof::BlockMeta(meta)) => { // meta.slot, meta.blockhash, meta.parent_slot // meta.executed_transaction_count } _ => {} } } ``` `stream.next()` blocks until an event arrives. Returns `None` when the stream is closed. Each event is a `Result` - an `Err` means all retries were exhausted and the connection is permanently gone. Pong messages are consumed internally and never appear in your stream. ### Closing ```rust theme={null} stream.close(); ``` Stops the background task immediately. Or just drop the stream. ## Multiple streams One client can run many streams at once. Each gets its own background connection: ```rust theme={null} let mut pumpfun = client.subscribe_yaml("config/pumpfun.yml")?; let mut raydium = client.subscribe_yaml("config/raydium.yml")?; let mut slots = client.subscribe_yaml("config/slots.yml")?; ``` They share endpoint health state - if one stream quarantines a failing endpoint, the others skip it on their next reconnect. But their connections and lifecycles are fully independent. You can spin up new streams at any time, including dynamically based on data from an existing stream. ## Full example A stream that watches pump.fun transactions, decodes the signature, and prints a summary for each one. ```rust theme={null} use orbitflare_sdk::{GeyserClientBuilder, Result}; use orbitflare_sdk::proto::geyser::subscribe_update::UpdateOneof; #[tokio::main] async fn main() -> Result<()> { let client = GeyserClientBuilder::new() .url("http://ny.rpc.orbitflare.com:10000") .fallback_url("http://fra.rpc.orbitflare.com:10000") .build()?; let mut stream = client.subscribe_yaml("grpc.yml")?; let mut tx_count: u64 = 0; println!("streaming..."); while let Some(update) = stream.next().await { let update = update?; match update.update_oneof { Some(UpdateOneof::Transaction(tx)) => { tx_count += 1; if let Some(info) = &tx.transaction { let sig = bs58::encode(&info.signature).into_string(); let fee = info.meta.as_ref().map(|m| m.fee).unwrap_or(0); let ix_count = info.transaction .as_ref() .and_then(|t| t.message.as_ref()) .map(|m| m.instructions.len()) .unwrap_or(0); println!( "#{tx_count} slot={} sig={}... fee={fee} instructions={ix_count}", tx.slot, &sig[..16], ); } } Some(UpdateOneof::Slot(slot)) => { println!("slot {} ({:?})", slot.slot, slot.status); } _ => {} } } Ok(()) } ``` With this `grpc.yml`: ```yaml theme={null} transactions: pumpfun: vote: false failed: false account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" slots: all: filter_by_commitment: true commitment: confirmed ``` # Rust JetStream Client Source: https://docs.orbitflare.com/sdk/rust-jetstream OrbitFlare's decoded shreds as gRPC streams in Rust, with transaction and account filters configured in YAML. ## Install ```bash theme={null} cargo add orbitflare-sdk --features jetstream ``` ## Building the client ```rust theme={null} use orbitflare_sdk::{JetstreamClientBuilder, RetryPolicy, Result}; use std::time::Duration; let client = JetstreamClientBuilder::new() .url("http://ny.jetstream.orbitflare.com") .fallback_url("http://fra.jetstream.orbitflare.com") .retry(RetryPolicy { initial_delay: Duration::from_millis(100), max_delay: Duration::from_secs(30), multiplier: 2.0, max_attempts: 0, }) .timeout_secs(30) .keepalive_secs(60) .ping_interval_secs(10) .max_missed_pongs(3) .channel_capacity(4096) .build()?; ``` Minimal: ```rust theme={null} let client = JetstreamClientBuilder::new() .url("http://ny.jetstream.orbitflare.com") .build()?; ``` URL falls back to `ORBITFLARE_JETSTREAM_URL` env var. All builder methods are identical to the [gRPC client](/sdk/rust-grpc) - same defaults, same behavior. ## Writing a YAML config JetStream supports transaction and account filters. No slots, blocks, or commitment - those are Yellowstone-specific. ```yaml theme={null} # jetstream.yml transactions: raydium: account_include: - "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" pumpfun: account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" accounts: my_wallet: account: - "YOUR_WALLET_ADDRESS" owner: - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ``` ### YAML filter reference **`transactions`** - named filters. `account_include` matches transactions involving those addresses. `account_exclude` removes matches. `account_required` means all listed addresses must appear. **`accounts`** - watch specific addresses with `account`, or all accounts owned by a program with `owner`. Supports `${ENV_VAR}` expansion. ## Subscribing and reading events ### From YAML ```rust theme={null} let mut stream = client.subscribe_yaml("jetstream.yml")?; ``` ### Programmatically ```rust theme={null} use std::collections::HashMap; use orbitflare_sdk::proto::jetstream::*; let mut filters = HashMap::new(); filters.insert("target".into(), SubscribeRequestFilterTransactions { account_include: vec![some_address.to_string()], account_exclude: vec![], account_required: vec![], }); let request = SubscribeRequest { transactions: filters, accounts: HashMap::new(), ping: Some(SubscribeRequestPing { id: 1 }), }; let mut stream = client.subscribe(request); ``` ### With the typed builder `SubscribeRequestBuilder` and `TransactionFilter` build the same request without hand-writing proto. Each `TransactionFilter` exposes `account_include`, `account_exclude`, and `account_required`, and accepts any iterator of string-like values. ```rust theme={null} use orbitflare_sdk::jetstream::{SubscribeRequestBuilder, TransactionFilter}; let request = SubscribeRequestBuilder::new() .transactions( "pumpfun", TransactionFilter::new() .account_include(["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"]) .account_required(["So11111111111111111111111111111111111111112"]), ) .build(); let mut stream = client.subscribe(request); ``` ### Reading the stream ```rust theme={null} use orbitflare_sdk::proto::jetstream::subscribe_update::UpdateOneof; while let Some(update) = stream.next().await { let update = update?; match update.update_oneof { Some(UpdateOneof::Transaction(tx)) => { // tx.slot - the slot number // tx.transaction - transaction info with signature, account_keys, // instructions, address_table_lookups } Some(UpdateOneof::Account(acct)) => { // acct.slot - the slot // acct.account - account info (pubkey, lamports, owner, data) // acct.is_startup - true during initial snapshot } _ => {} } } ``` Closing, multiple streams, reconnection, and ping/pong all work identically to the [gRPC client](/sdk/rust-grpc). ## Full example A stream that watches Raydium AMM swaps and prints each transaction's signature and instruction count. ```rust theme={null} use orbitflare_sdk::{JetstreamClientBuilder, Result}; use orbitflare_sdk::proto::jetstream::subscribe_update::UpdateOneof; #[tokio::main] async fn main() -> Result<()> { let client = JetstreamClientBuilder::new() .url("http://ny.jetstream.orbitflare.com") .build()?; let mut stream = client.subscribe_yaml("jetstream.yml")?; let mut count: u64 = 0; println!("streaming raydium txs..."); while let Some(update) = stream.next().await { let update = update?; if let Some(UpdateOneof::Transaction(tx)) = update.update_oneof { count += 1; if let Some(info) = &tx.transaction { let sig = bs58::encode(&info.signature).into_string(); let num_ix = info.instructions.len(); let num_accounts = info.account_keys.len(); println!( "#{count} slot={} sig={}... ix={num_ix} accounts={num_accounts}", tx.slot, &sig[..16], ); } } } Ok(()) } ``` With this `jetstream.yml`: ```yaml theme={null} transactions: raydium: account_include: - "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" ``` ## JetStream v2 JetStream v2 (`orbitflare_sdk::jetstream::v2`) runs on the **same endpoints and authentication** as v1 and is fully additive: v1 keeps working unchanged. What v2 adds: * **Runtime-managed filters** - add and remove filters on a live stream without reconnecting. * **Per-message sequence numbers** - every response carries a monotonic `sequence`, so you can detect dropped messages. * **Opt-in enrichment** - request fee payer, program ids, compute-unit price, compute limit, resolved address-table addresses, and more, per transaction. * **Slot lifecycle events** - a separate server stream of slot alive/complete/dead events. The v2 client lives under `jetstream::v2`. The builder is identical to v1 (same methods, defaults, `ORBITFLARE_JETSTREAM_URL` env var, and failover): ```rust theme={null} use orbitflare_sdk::jetstream::v2::{JetstreamClientBuilder, TransactionFilter}; let client = JetstreamClientBuilder::new() .url("http://ny.jetstream.orbitflare.com") .fallback_url("http://fra.jetstream.orbitflare.com") .build()?; ``` ### Building typed filters Each filter is a `TransactionFilter` with a client-chosen id (via `.with_id()`). The id is echoed back on every matching transaction and in the filter-validation acknowledgement, so you can correlate matches and remove the filter later. ```rust theme={null} let filter = TransactionFilter::new() .account_include(["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"]) .include_enrichment(true) .with_id("pumpfun"); ``` A filter must set at least one of `account_include`, `account_exclude`, or `account_required`; empty (match-everything) filters are rejected. `include_enrichment` is optional (default off) and applies to the whole subscription: if any active filter enables it, every transaction you receive is enriched. ### Subscribing to transactions ```rust theme={null} use orbitflare_sdk::proto::jetstream::v2::subscribe_transactions_response::Payload; let mut stream = client.subscribe_transactions(vec![filter]); while let Some(resp) = stream.next().await { let resp = resp?; match resp.payload { Some(Payload::Transaction(ft)) => { // resp.sequence - monotonic per-stream sequence number // ft.filter_ids - which of your filters this tx matched if let Some(tx) = &ft.transaction { // tx.slot, tx.signature, tx.account_keys, tx.instructions // enrichment (only when include_enrichment is set): tx.fee_payer, // tx.program_ids, tx.compute_unit_price, tx.compute_limit, // tx.loaded_writable_addresses, tx.loaded_readonly_addresses println!("seq={} slot={} cu_price={}", resp.sequence, tx.slot, tx.compute_unit_price); } } Some(Payload::FilterValidation(r)) => { // one per filter after each add/remove println!("filter {} accepted={} {}", r.filter_id, r.accepted, r.rejection_reason); } Some(Payload::Heartbeat(hb)) => { // hb.server_ts_ms - server wall-clock, ms since epoch let _ = hb; } Some(Payload::Pong(p)) => { // p.ping_id let _ = p; } None => {} } } ``` ### Managing filters on a live stream Take a handle from the stream and add or remove filters without reconnecting. Removals reference the filter id you assigned with `.with_id()`. ```rust theme={null} let handle = stream.handle(); handle.add_filters(vec![ TransactionFilter::new() .account_include(["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"]) .with_id("raydium"), ])?; handle.remove_filters(vec!["pumpfun".to_string()])?; ``` ### Slot lifecycle events `subscribe_slots()` is a separate server stream and takes no filters. ```rust theme={null} let mut slots = client.subscribe_slots(); while let Some(event) = slots.next().await { let event = event?; // event.slot, event.status() (a SlotStatus enum: alive / complete / dead), // event.parent_slot, event.current_leader, event.sequence println!("slot={} status={:?}", event.slot, event.status()); } ``` ### Health probes `get_version()` and `ping()` are unary calls with the same failover as the streams. ```rust theme={null} println!("version={}", client.get_version().await?); println!("ping={}", client.ping(7).await?); ``` For the full protobuf specification, message shapes, enrichment fields, and slot statuses, see the [JetStream Protocol Reference](/data-streaming/jetstream-reference). # Rust RPC Client Source: https://docs.orbitflare.com/sdk/rust-rpc Solana JSON-RPC client for Rust from the orbitflare-sdk crate, with retry, failover, and typed helpers for common methods. ## Install ```bash theme={null} cargo add orbitflare-sdk ``` The `rpc` feature is enabled by default. ## Building the client Here's a client with every option set: ```rust theme={null} use orbitflare_sdk::{RpcClientBuilder, RetryPolicy, Result}; use std::time::Duration; let client = RpcClientBuilder::new() .url("http://ny.rpc.orbitflare.com") .fallback_url("http://fra.rpc.orbitflare.com") .fallback_url("http://ams.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .commitment("confirmed") .retry(RetryPolicy { initial_delay: Duration::from_millis(100), max_delay: Duration::from_secs(30), multiplier: 2.0, max_attempts: 5, }) .timeout(Duration::from_secs(30)) .build()?; ``` Most of these have sensible defaults. A minimal setup: ```rust theme={null} let client = RpcClientBuilder::new() .url("http://ny.rpc.orbitflare.com") .build()?; ``` ### Builder methods **`.url(url)`** - The primary endpoint to send requests to. If not set, the SDK checks the `ORBITFLARE_RPC_URL` environment variable. ```rust theme={null} .url("http://ny.rpc.orbitflare.com") ``` **`.urls(&[...])`** - Set the primary and all fallbacks in one call. The first element is the primary, the rest are fallbacks. ```rust theme={null} .urls(&["http://ny.rpc.orbitflare.com", "http://fra.rpc.orbitflare.com", "http://ams.rpc.orbitflare.com"]) ``` **`.fallback_url(url)`** - Add a single fallback endpoint. Call multiple times to add several. When the primary fails, the SDK tries fallbacks in order. ```rust theme={null} .fallback_url("http://fra.rpc.orbitflare.com") .fallback_url("http://ams.rpc.orbitflare.com") ``` **`.fallback_urls(&[...])`** - Same as `fallback_url` but takes a slice. ```rust theme={null} .fallback_urls(&["http://fra.rpc.orbitflare.com", "http://ams.rpc.orbitflare.com"]) ``` **`.api_key(key)`** - Your OrbitFlare license key. If not set, the SDK checks `ORBITFLARE_LICENSE_KEY` from the environment. The key is injected into the URL at request time, never stored in the endpoint. ```rust theme={null} .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") ``` **`.commitment(level)`** - Default commitment level used for all typed helpers. Options: `"processed"`, `"confirmed"`, `"finalized"`. Defaults to `"confirmed"`. ```rust theme={null} .commitment("finalized") ``` **`.retry(policy)`** - Controls how the SDK retries failed requests. `initial_delay` is the wait before the first retry. `multiplier` scales the delay on each attempt. `max_delay` caps the backoff. `max_attempts` limits total retries per endpoint (0 means infinite). Defaults: 100ms initial, 30s max, 2x multiplier, infinite attempts. ```rust theme={null} use orbitflare_sdk::RetryPolicy; use std::time::Duration; .retry(RetryPolicy { initial_delay: Duration::from_millis(200), max_delay: Duration::from_secs(15), multiplier: 2.0, max_attempts: 5, }) ``` **`.timeout(duration)`** - HTTP timeout for each individual request. Defaults to 30 seconds. ```rust theme={null} .timeout(Duration::from_secs(10)) ``` ## Available RPC methods ### `get_slot()` Returns the current slot number. ```rust theme={null} let slot: u64 = client.get_slot().await?; ``` ### `get_balance(address)` Returns the balance in lamports for an account. ```rust theme={null} let lamports: u64 = client.get_balance("So11111111111111111111111111111111111111112").await?; let sol = lamports as f64 / 1_000_000_000.0; ``` ### `get_account_info(address)` Returns the full account data or `None` if the account doesn't exist. Data is base64-encoded. ```rust theme={null} let account: Option = client.get_account_info("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").await?; if let Some(info) = account { println!("owner: {}", info["owner"]); println!("lamports: {}", info["lamports"]); println!("data length: {}", info["data"][0].as_str().unwrap_or("").len()); } ``` ### `get_multiple_accounts(addresses)` Fetches multiple accounts in one call. Automatically chunks into batches of 100 (Solana's per-request limit), so you can pass any number of addresses. ```rust theme={null} let accounts: Vec> = client .get_multiple_accounts(&["addr1", "addr2", "addr3"]) .await?; for (i, acct) in accounts.iter().enumerate() { match acct { Some(info) => println!("account {i}: {} lamports", info["lamports"]), None => println!("account {i}: not found"), } } ``` ### `get_latest_blockhash()` Returns the most recent blockhash and the last block height it's valid for. ```rust theme={null} let (blockhash, last_valid_block_height): (String, u64) = client.get_latest_blockhash().await?; ``` ### `get_transaction(signature)` Fetches a confirmed transaction by its signature. Returns the full transaction with metadata. ```rust theme={null} let tx: serde_json::Value = client.get_transaction("5K8F2j...").await?; println!("slot: {}", tx["slot"]); println!("fee: {}", tx["meta"]["fee"]); ``` ### `get_signatures_for_address(address, limit)` Returns recent transaction signatures for an address, newest first. ```rust theme={null} let sigs: Vec = client .get_signatures_for_address("So111...", 10) .await?; for sig in &sigs { println!("{} at slot {}", sig["signature"], sig["slot"]); } ``` ### `get_program_accounts(program_id)` Returns all accounts owned by a program. Can return a lot of data. ```rust theme={null} let accounts: Vec = client .get_program_accounts("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") .await?; ``` ### `get_recent_prioritization_fees(addresses)` Returns recent priority fees for a set of accounts. Useful for estimating compute unit pricing. ```rust theme={null} let fees: Vec = client .get_recent_prioritization_fees(&["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"]) .await?; for fee in &fees { println!("slot {}: {} micro-lamports", fee["slot"], fee["prioritizationFee"]); } ``` ### `send_transaction(tx_base64)` Sends a signed transaction to the network. Takes a base64-encoded serialized transaction. Returns the transaction signature. ```rust theme={null} let signature: String = client.send_transaction(tx_base64).await?; println!("sent: {signature}"); ``` ### `simulate_transaction(tx_base64)` Simulates a transaction without submitting it. Returns logs, compute units consumed, and any errors. ```rust theme={null} let result: serde_json::Value = client.simulate_transaction(tx_base64).await?; if let Some(err) = result["err"].as_str() { println!("simulation failed: {err}"); } else { println!("compute units: {}", result["unitsConsumed"]); } ``` ### `get_token_accounts_by_owner(owner, mint, program_id)` Returns token accounts for a wallet. Pass either a specific mint or a token program ID. If both are `None`, defaults to the SPL Token program. ```rust theme={null} let tokens: Vec = client .get_token_accounts_by_owner("wallet_address", None, None) .await?; for token in &tokens { let info = &token["account"]["data"]["parsed"]["info"]; println!("mint: {} balance: {}", info["mint"], info["tokenAmount"]["uiAmountString"], ); } ``` ### `get_transactions_for_address(address, options)` OrbitFlare-specific method that combines `getSignaturesForAddress` and `getTransaction` into one call. Returns a `GetTransactionsResult` with the data array and an optional `pagination_token` for fetching the next page. Supports four detail levels (`signatures`, `none`, `accounts`, `full`), bidirectional sorting, time/slot filtering, status filtering, and token account inclusion. ```rust theme={null} use orbitflare_sdk::{GetTransactionsOptions, GetTransactionsFilters, RangeFilter}; let result = client.get_transactions_for_address( "address", GetTransactionsOptions::new() .transaction_details("full") .limit(100) .sort_order("asc") .filters( GetTransactionsFilters::new() .token_accounts("all") .status("succeeded") .block_time(RangeFilter { gte: Some(1704067200), lte: Some(1706745600), ..Default::default() }) ), ).await?; for tx in &result.data { println!("slot {}", tx["slot"]); } if let Some(token) = result.pagination_token { // fetch next page using .pagination_token(&token) } ``` ### `request(method, params)` Call any RPC method by name. The SDK builds the JSON-RPC envelope, handles retry and failover, and returns the `result` field. ```rust theme={null} use serde_json::json; let epoch = client.request("getEpochInfo", json!([])).await?; let inflation = client.request("getInflationRate", json!([])).await?; let supply = client.request("getSupply", json!([])).await?; ``` ### `request_raw(body)` Send a raw JSON-RPC body string. Same retry and failover as everything else. ```rust theme={null} let result = client .request_raw(r#"{"jsonrpc":"2.0","id":1,"method":"getHealth","params":[]}"#) .await?; ``` ## Full example A script that checks the state of the network, fetches a wallet's SOL balance and token holdings, and looks up recent transactions. ```rust theme={null} use orbitflare_sdk::{RpcClientBuilder, Result}; #[tokio::main] async fn main() -> Result<()> { let client = RpcClientBuilder::new() .url("http://ny.rpc.orbitflare.com") .fallback_url("http://fra.rpc.orbitflare.com") .commitment("confirmed") .build()?; let wallet = "CKs1E69a2e9TmH4mKKLrXFF8kD3ZnwKjoEuXa6sz9WqX"; let slot = client.get_slot().await?; let (blockhash, _) = client.get_latest_blockhash().await?; println!("network slot: {slot}"); println!("blockhash: {blockhash}"); let balance = client.get_balance(wallet).await?; println!("\n{wallet}"); println!(" SOL: {:.4}", balance as f64 / 1_000_000_000.0); let tokens = client.get_token_accounts_by_owner(wallet, None, None).await?; for token in &tokens { let info = &token["account"]["data"]["parsed"]["info"]; let mint = info["mint"].as_str().unwrap_or("unknown"); let amount = info["tokenAmount"]["uiAmountString"].as_str().unwrap_or("0"); if amount != "0" { println!(" {mint}: {amount}"); } } let sigs = client.get_signatures_for_address(wallet, 5).await?; println!("\nrecent transactions:"); for sig in &sigs { let signature = sig["signature"].as_str().unwrap_or("?"); let slot = sig["slot"].as_u64().unwrap_or(0); let err = if sig["err"].is_null() { "ok" } else { "failed" }; println!(" {slot} {err} {}", &signature[..20]); } let fees = client .get_recent_prioritization_fees(&[wallet]) .await?; let avg: f64 = fees.iter() .filter_map(|f| f["prioritizationFee"].as_f64()) .sum::() / fees.len().max(1) as f64; println!("\navg priority fee: {avg:.0} micro-lamports"); Ok(()) } ``` # Rust WebSocket Client Source: https://docs.orbitflare.com/sdk/rust-websocket Real-time Solana subscriptions in Rust via the orbitflare-sdk ws feature: account changes, logs, slots, and signatures with auto-reconnect. ## Install ```bash theme={null} cargo add orbitflare-sdk --features ws ``` ## Building the client ```rust theme={null} use orbitflare_sdk::{WsClientBuilder, RetryPolicy, Result}; use std::time::Duration; let client = WsClientBuilder::new() .url("ws://ny.rpc.orbitflare.com") .fallback_url("ws://fra.rpc.orbitflare.com") .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") .retry(RetryPolicy { initial_delay: Duration::from_millis(100), max_delay: Duration::from_secs(30), multiplier: 2.0, max_attempts: 0, }) .ping_interval_secs(10) .max_missed_pongs(3) .build() .await?; ``` Minimal: ```rust theme={null} let client = WsClientBuilder::new() .url("ws://ny.rpc.orbitflare.com") .build() .await?; ``` Note that `.build()` is async - it establishes the WebSocket connection before returning. If the connection fails, you get the error immediately. ### Builder methods **`.url(url)`** - Primary WebSocket endpoint. Falls back to `ORBITFLARE_WS_URL` env var. ```rust theme={null} .url("ws://ny.rpc.orbitflare.com") ``` **`.urls(&[...])`** - Primary + fallbacks in one call. ```rust theme={null} .urls(&["ws://ny.rpc.orbitflare.com", "ws://fra.rpc.orbitflare.com"]) ``` **`.fallback_url(url)` / `.fallback_urls(&[...])`** - Add failover endpoints. On reconnect, the SDK rotates through them. ```rust theme={null} .fallback_url("ws://fra.rpc.orbitflare.com") ``` **`.api_key(key)`** - License key. Falls back to `ORBITFLARE_LICENSE_KEY` env var. ```rust theme={null} .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN") ``` **`.retry(policy)`** - Reconnection backoff. Default: 100ms initial, 30s max, 2x multiplier, infinite attempts. ```rust theme={null} .retry(RetryPolicy { initial_delay: Duration::from_millis(200), max_delay: Duration::from_secs(15), multiplier: 2.0, max_attempts: 0, }) ``` **`.ping_interval_secs(n)`** - How often the SDK sends WebSocket `Ping` frames. Default: 10. ```rust theme={null} .ping_interval_secs(15) ``` **`.max_missed_pongs(n)`** - Pings without a response before killing the connection. Default: 3. ```rust theme={null} .max_missed_pongs(5) ``` ## Available subscriptions ### `slot_subscribe()` Fires every time a slot is processed, confirmed, or finalized. ```rust theme={null} let mut sub = client.slot_subscribe().await?; ``` Each event is a JSON value with `slot`, `parent`, and `root` fields: ```json theme={null} {"slot": 413014740, "parent": 413014739, "root": 413014708} ``` ### `account_subscribe(address, commitment)` Fires when the specified account's data changes. ```rust theme={null} let mut sub = client.account_subscribe( "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "confirmed", ).await?; ``` Returns base64-encoded account data with lamports, owner, and executable flag. ### `logs_subscribe(mentions, commitment)` Fires for transactions that mention the given addresses. Pass an empty slice for all transactions. ```rust theme={null} let mut sub = client.logs_subscribe( &["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"], "confirmed", ).await?; ``` All logs: ```rust theme={null} let mut sub = client.logs_subscribe(&[], "confirmed").await?; ``` ### `signature_subscribe(signature, commitment)` Fires once when a transaction reaches the given commitment level. Useful for confirming a transaction you just sent. ```rust theme={null} let mut sub = client.signature_subscribe("5K8F2j...", "confirmed").await?; ``` ## Reading events All subscriptions return a `WsSubscription`. Call `.next()` to get the next event: ```rust theme={null} while let Some(event) = sub.next().await { println!("{event}"); } ``` `.next()` returns `Option`. `None` means the subscription was closed. ## Unsubscribing ```rust theme={null} sub.unsubscribe().await; ``` This sends an unsubscribe message to the server. If you drop a subscription without calling this, the SDK detects the orphan and sends the unsubscribe automatically. ## Multiple subscriptions All subscriptions run on a single WebSocket connection. The SDK routes notifications to the right subscription internally. ```rust theme={null} let mut slots = client.slot_subscribe().await?; let mut usdc = client.account_subscribe("EPjFWdd5...", "confirmed").await?; let mut logs = client.logs_subscribe(&["6EF8r..."], "confirmed").await?; ``` You can add new subscriptions at any time while existing ones are running. ## Reconnection If the connection drops, the background task reconnects with exponential backoff and re-subscribes everything automatically. Your `.next()` calls just keep working - events resume once the connection is back. ## Full example A monitoring script that subscribes to slot updates and account changes for USDC, printing a live feed. ```rust theme={null} use orbitflare_sdk::{WsClientBuilder, Result}; #[tokio::main] async fn main() -> Result<()> { let client = WsClientBuilder::new() .url("ws://ny.rpc.orbitflare.com") .build() .await?; let mut slots = client.slot_subscribe().await?; let mut usdc = client.account_subscribe( "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "confirmed", ).await?; println!("watching slots and USDC mint account..."); loop { tokio::select! { Some(slot) = slots.next() => { let s = slot["slot"].as_u64().unwrap_or(0); let parent = slot["parent"].as_u64().unwrap_or(0); println!("slot {s} (parent {parent})"); } Some(acct) = usdc.next() => { let lamports = acct["lamports"].as_u64().unwrap_or(0); let data_len = acct["data"][0] .as_str() .map(|s| s.len()) .unwrap_or(0); println!("USDC mint updated: {lamports} lamports, {data_len} bytes"); } } } } ``` # TypeScript gRPC Client (Yellowstone) Source: https://docs.orbitflare.com/sdk/typescript-grpc Yellowstone Geyser streaming in TypeScript on @grpc/grpc-js, with automatic reconnection, ping/pong liveness detection, and YAML config. ## Install ```bash theme={null} npm install @orbitflare/sdk @grpc/grpc-js yaml ``` `@grpc/grpc-js` and `yaml` are optional peer dependencies. Install them only if you use the gRPC client (or YAML config). ## Building the client Here's a client with every option set: ```ts theme={null} import { GeyserClientBuilder } from '@orbitflare/sdk/grpc'; import type { RetryPolicy } from '@orbitflare/sdk'; const client = new GeyserClientBuilder() .url('http://ny.rpc.orbitflare.com:10000') .fallbackUrl('http://fra.rpc.orbitflare.com:10000') .retry({ initialDelayMs: 100, maxDelayMs: 30_000, multiplier: 2.0, maxAttempts: 0, }) .timeoutSecs(30) .keepaliveSecs(60) .pingIntervalSecs(10) .maxMissedPongs(3) .channelCapacity(4096) .build(); ``` Minimal setup: ```ts theme={null} const client = new GeyserClientBuilder() .url('http://ny.rpc.orbitflare.com:10000') .build(); ``` ### Builder methods **`.url(url)`** - The primary gRPC endpoint. Falls back to `ORBITFLARE_GRPC_URL` env var. ```ts theme={null} .url('http://ny.rpc.orbitflare.com:10000') ``` **`.urls([...])`** - Primary + fallbacks in one call. First element is primary. ```ts theme={null} .urls(['http://ny.rpc.orbitflare.com:10000', 'http://fra.rpc.orbitflare.com:10000']) ``` **`.fallbackUrl(url)` / `.fallbackUrls([...])`** - Add fallback endpoints. On connection failure, the SDK rotates through them. ```ts theme={null} .fallbackUrl('http://fra.rpc.orbitflare.com:10000') ``` **`.retry(policy)`** - Controls reconnection backoff. When a connection drops, the SDK waits `initialDelayMs`, then doubles it each attempt (capped at `maxDelayMs`). Set `maxAttempts` to 0 for infinite retries. Default: 100ms initial, 30s max, 2x multiplier, infinite. ```ts theme={null} .retry({ initialDelayMs: 200, maxDelayMs: 15_000, multiplier: 2.0, maxAttempts: 0, }) ``` **`.timeoutSecs(n)`** - Per-request gRPC timeout. Default: 30. ```ts theme={null} .timeoutSecs(15) ``` **`.keepaliveSecs(n)`** - TCP keepalive interval. The OS sends probes at this interval to detect dead connections at the TCP level. Default: 60. ```ts theme={null} .keepaliveSecs(30) ``` **`.pingIntervalSecs(n)`** - How often the SDK sends proto-level `Ping` messages to the server. The server should respond with a `Pong`. Default: 10. ```ts theme={null} .pingIntervalSecs(15) ``` **`.maxMissedPongs(n)`** - How many consecutive pings can go unanswered before the SDK considers the connection dead and reconnects. Default: 3. With the defaults, a dead connection is detected within 30 seconds. ```ts theme={null} .maxMissedPongs(5) ``` **`.channelCapacity(n)`** - The bounded buffer between the background task and your code. If your code is slow to consume events, the background task pauses when this fills up instead of eating unlimited memory. Default: 4096. ```ts theme={null} .channelCapacity(8192) ``` ## Writing a YAML config Create a YAML file with the filters you want: ```yaml theme={null} # grpc.yml transactions: pumpfun: vote: false failed: false account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" jupiter: vote: false failed: false account_include: - "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" accounts: usdc_mint: account: - "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" token_program: owner: - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" slots: all: filter_by_commitment: true commitment: confirmed ``` ### YAML filter reference **`transactions`** - each entry is a named filter. A transaction matches if it involves any of the `account_include` addresses. `account_exclude` removes matches. `account_required` means all listed addresses must be present. `vote` and `failed` filter by transaction type. **`accounts`** - watch specific accounts by address, or watch all accounts owned by a program via `owner`. **`slots`** - subscribe to slot updates. `filter_by_commitment` only sends updates at your commitment level. **`commitment`** - `'processed'`, `'confirmed'`, or `'finalized'`. Defaults to `'confirmed'`. The YAML supports `${ENV_VAR}` expansion: ```yaml theme={null} transactions: target: account_include: - "${TARGET_PROGRAM}" ``` ## Subscribing and reading events ### From YAML ```ts theme={null} const stream = client.subscribeYaml('grpc.yml'); ``` ### Programmatically For dynamic filters built at runtime: ```ts theme={null} import { proto } from '@orbitflare/sdk/proto'; const request: proto.geyser.SubscribeRequest = { transactions: { target: { vote: false, failed: false, signature: undefined, accountInclude: [someAddress], accountExclude: [], accountRequired: [], }, }, accounts: {}, slots: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {}, entry: {}, accountsDataSlice: [], commitment: 1, ping: { id: 1 }, }; const stream = client.subscribe(request); ``` ### Reading the stream Both `subscribeYaml` and `subscribe` return a `GeyserStream`. It's an async iterable - use `for await`: ```ts theme={null} for await (const update of stream) { if (update.transaction) { // update.transaction.slot - the slot this transaction was in // update.transaction.transaction - the transaction info (signature, accounts, instructions, meta) } else if (update.account) { // update.account.slot - the slot // update.account.account - account info (pubkey, lamports, owner, data) // update.account.isStartup - true during initial account snapshot } else if (update.slot) { // update.slot.slot - the slot number // update.slot.status - processed, confirmed, finalized, etc. } else if (update.blockMeta) { // update.blockMeta.slot, update.blockMeta.blockhash, update.blockMeta.parentSlot // update.blockMeta.executedTransactionCount } } ``` Or call `await stream.next()` directly to pull one event at a time. Returns `undefined` when the stream is closed. If retries are exhausted, the next iteration throws. Pong messages are consumed internally and never appear in your stream. ### Closing ```ts theme={null} stream.close(); ``` Stops the background task immediately. ## Multiple streams One client can run many streams at once. Each gets its own background connection: ```ts theme={null} const pumpfun = client.subscribeYaml('config/pumpfun.yml'); const raydium = client.subscribeYaml('config/raydium.yml'); const slots = client.subscribeYaml('config/slots.yml'); ``` They share endpoint health state - if one stream quarantines a failing endpoint, the others skip it on their next reconnect. But their connections and lifecycles are fully independent. You can spin up new streams at any time, including dynamically based on data from an existing stream. ## Full example A stream that watches pump.fun transactions, decodes the signature, and prints a summary for each one. ```ts theme={null} import bs58 from 'bs58'; import { GeyserClientBuilder } from '@orbitflare/sdk/grpc'; async function main() { const client = new GeyserClientBuilder() .url('http://ny.rpc.orbitflare.com:10000') .fallbackUrl('http://fra.rpc.orbitflare.com:10000') .build(); const stream = client.subscribeYaml('grpc.yml'); let txCount = 0; console.log('streaming...'); for await (const update of stream) { if (update.transaction) { txCount += 1; const info = update.transaction.transaction; if (info?.signature) { const sig = bs58.encode(info.signature); const fee = info.meta?.fee ?? 0n; const ixCount = info.transaction?.message?.instructions.length ?? 0; console.log( `#${txCount} slot=${update.transaction.slot} sig=${sig.slice(0, 16)}... fee=${fee} instructions=${ixCount}`, ); } } else if (update.slot) { console.log(`slot ${update.slot.slot} (${update.slot.status})`); } } } void main(); ``` With this `grpc.yml`: ```yaml theme={null} transactions: pumpfun: vote: false failed: false account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" slots: all: filter_by_commitment: true commitment: confirmed ``` # TypeScript JetStream Client Source: https://docs.orbitflare.com/sdk/typescript-jetstream OrbitFlare's decoded shreds as gRPC streams in TypeScript on @grpc/grpc-js, with transaction and account filters. ## Install ```bash theme={null} npm install @orbitflare/sdk @grpc/grpc-js yaml ``` `@grpc/grpc-js` and `yaml` are optional peer dependencies. Install them only if you use JetStream (or YAML config). ## Building the client ```ts theme={null} import { JetstreamClientBuilder } from '@orbitflare/sdk/jetstream'; import type { RetryPolicy } from '@orbitflare/sdk'; const client = new JetstreamClientBuilder() .url('http://ny.jetstream.orbitflare.com') .fallbackUrl('http://fra.jetstream.orbitflare.com') .retry({ initialDelayMs: 100, maxDelayMs: 30_000, multiplier: 2.0, maxAttempts: 0, }) .timeoutSecs(30) .keepaliveSecs(60) .pingIntervalSecs(10) .maxMissedPongs(3) .channelCapacity(4096) .build(); ``` Minimal: ```ts theme={null} const client = new JetstreamClientBuilder() .url('http://ny.jetstream.orbitflare.com') .build(); ``` URL falls back to `ORBITFLARE_JETSTREAM_URL` env var. All builder methods are identical to the [gRPC client](/sdk/typescript-grpc) - same defaults, same behavior. ## Writing a YAML config JetStream supports transaction and account filters. No slots, blocks, or commitment - those are Yellowstone-specific. ```yaml theme={null} # jetstream.yml transactions: raydium: account_include: - "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" pumpfun: account_include: - "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" accounts: my_wallet: account: - "YOUR_WALLET_ADDRESS" owner: - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ``` ### YAML filter reference **`transactions`** - named filters. `account_include` matches transactions involving those addresses. `account_exclude` removes matches. `account_required` means all listed addresses must appear. **`accounts`** - watch specific addresses with `account`, or all accounts owned by a program with `owner`. Supports `${ENV_VAR}` expansion. ## Subscribing and reading events ### From YAML ```ts theme={null} const stream = client.subscribeYaml('jetstream.yml'); ``` ### Programmatically ```ts theme={null} import { proto } from '@orbitflare/sdk/proto'; const request: proto.jetstream.SubscribeRequest = { transactions: { target: { accountInclude: [someAddress], accountExclude: [], accountRequired: [], }, }, accounts: {}, ping: { id: 1 }, }; const stream = client.subscribe(request); ``` ### With the typed builder `SubscribeRequestBuilder` and `TransactionFilter` build the same request without hand-writing proto. Each `TransactionFilter` exposes `accountInclude`, `accountExclude`, and `accountRequired`, so you get autocomplete and compile-time checking on the filter shape. ```ts theme={null} import { SubscribeRequestBuilder, TransactionFilter, } from '@orbitflare/sdk/jetstream'; const request = new SubscribeRequestBuilder() .transactions( 'pumpfun', new TransactionFilter() .accountInclude(['6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P']) .accountRequired(['So11111111111111111111111111111111111111112']), ) .build(); const stream = client.subscribe(request); ``` ### Reading the stream ```ts theme={null} for await (const update of stream) { if (update.transaction) { // update.transaction.slot - the slot number // update.transaction.transaction - transaction info with signature, accountKeys, // instructions, addressTableLookups } else if (update.account) { // update.account.slot - the slot // update.account.account - account info (pubkey, lamports, owner, data) // update.account.isStartup - true during initial snapshot } } ``` Closing, multiple streams, reconnection, and ping/pong all work identically to the [gRPC client](/sdk/typescript-grpc). ## Full example A stream that watches Raydium AMM swaps and prints each transaction's signature and instruction count. ```ts theme={null} import bs58 from 'bs58'; import { JetstreamClientBuilder } from '@orbitflare/sdk/jetstream'; async function main() { const client = new JetstreamClientBuilder() .url('http://ny.jetstream.orbitflare.com') .build(); const stream = client.subscribeYaml('jetstream.yml'); let count = 0; console.log('streaming raydium txs...'); for await (const update of stream) { if (update.transaction) { count += 1; const info = update.transaction.transaction; if (info?.signature) { const sig = bs58.encode(info.signature); const numIx = info.instructions.length; const numAccounts = info.accountKeys.length; console.log( `#${count} slot=${update.transaction.slot} sig=${sig.slice(0, 16)}... ix=${numIx} accounts=${numAccounts}`, ); } } } } void main(); ``` With this `jetstream.yml`: ```yaml theme={null} transactions: raydium: account_include: - "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" ``` ## JetStream v2 JetStream v2 runs on the **same endpoints and authentication** as v1 and is fully additive: v1 keeps working unchanged. What v2 adds: * **Runtime-managed filters** - add and remove filters on a live stream without reconnecting. * **Per-message sequence numbers** - every response carries a monotonic `sequence`, so you can detect dropped messages. * **Opt-in enrichment** - request fee payer, program ids, compute-unit price, compute limit, resolved address-table addresses, and more, per transaction. * **Slot lifecycle events** - a separate server stream of slot alive/complete/dead events. Import it from the `jetstream/v2` entry point. The builder is identical to v1 (same methods, defaults, `ORBITFLARE_JETSTREAM_URL` env var, and failover): ```ts theme={null} import { JetstreamClientBuilder, TransactionFilter } from '@orbitflare/sdk/jetstream/v2'; const client = new JetstreamClientBuilder() .url('http://ny.jetstream.orbitflare.com') .fallbackUrl('http://fra.jetstream.orbitflare.com') .build(); ``` ### Building typed filters Each filter is a `TransactionFilter` with a client-chosen id (via `.withId()`). The id is echoed back on every matching transaction and in the filter-validation acknowledgement, so you can correlate matches and remove the filter later. ```ts theme={null} const filter = new TransactionFilter() .accountInclude(['6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P']) .includeEnrichment(true) .withId('pumpfun'); ``` A filter must set at least one of `accountInclude`, `accountExclude`, or `accountRequired`; empty (match-everything) filters are rejected. `includeEnrichment` is optional (default off) and applies to the whole subscription: if any active filter enables it, every transaction you receive is enriched. ### Subscribing to transactions ```ts theme={null} const stream = client.subscribeTransactions([filter]); for await (const resp of stream) { if (resp.transaction) { // resp.sequence - monotonic per-stream sequence number // resp.transaction.filterIds - which of your filters this tx matched const tx = resp.transaction.transaction; if (tx) { // tx.slot, tx.signature, tx.accountKeys, tx.instructions // enrichment (only when includeEnrichment is set): tx.feePayer, // tx.programIds, tx.computeUnitPrice, tx.computeLimit, // tx.loadedWritableAddresses, tx.loadedReadonlyAddresses console.log(`seq=${resp.sequence} slot=${tx.slot} cuPrice=${tx.computeUnitPrice}`); } } else if (resp.filterValidation) { // one per filter after each add/remove const r = resp.filterValidation; console.log(`filter ${r.filterId} accepted=${r.accepted} ${r.rejectionReason}`); } else if (resp.heartbeat) { // resp.heartbeat.serverTsMs - server wall-clock, ms since epoch } else if (resp.pong) { // resp.pong.pingId } } ``` ### Managing filters on a live stream Take a handle from the stream and add or remove filters without reconnecting. Removals reference the filter id you assigned with `.withId()`. ```ts theme={null} const handle = stream.handle(); handle.addFilters([ new TransactionFilter() .accountInclude(['675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8']) .withId('raydium'), ]); handle.removeFilters(['pumpfun']); ``` ### Slot lifecycle events `subscribeSlots()` is a separate server stream and takes no filters. ```ts theme={null} const slots = client.subscribeSlots(); for await (const event of slots) { // event.slot, event.status (a SlotStatus enum: alive / complete / dead), // event.parentSlot, event.currentLeader, event.sequence console.log(`slot=${event.slot} status=${event.status}`); } ``` ### Health probes `getVersion()` and `ping()` are unary calls with the same failover as the streams. ```ts theme={null} console.log(`version=${await client.getVersion()}`); console.log(`ping=${await client.ping(7)}`); ``` For the full protobuf specification, message shapes, enrichment fields, and slot statuses, see the [JetStream Protocol Reference](/data-streaming/jetstream-reference). # TypeScript RPC Client Source: https://docs.orbitflare.com/sdk/typescript-rpc Solana JSON-RPC client for TypeScript from the @orbitflare/sdk package, with retry, failover, and typed helpers for common methods. ## Install ```bash theme={null} npm install @orbitflare/sdk ``` The RPC client has no extra peer dependencies. ## Building the client Here's a client with every option set: ```ts theme={null} import { RpcClientBuilder, type RetryPolicy } from '@orbitflare/sdk'; const client = new RpcClientBuilder() .url('http://ny.rpc.orbitflare.com') .fallbackUrl('http://fra.rpc.orbitflare.com') .fallbackUrl('http://ams.rpc.orbitflare.com') .apiKey('ORBIT-XXXXXX-NNNNNN-NNNNNN') .commitment('confirmed') .retry({ initialDelayMs: 100, maxDelayMs: 30_000, multiplier: 2.0, maxAttempts: 5, }) .timeoutMs(30_000) .build(); ``` Most of these have sensible defaults. A minimal setup: ```ts theme={null} const client = new RpcClientBuilder() .url('http://ny.rpc.orbitflare.com') .build(); ``` ### Builder methods **`.url(url)`** - The primary endpoint to send requests to. If not set, the SDK checks the `ORBITFLARE_RPC_URL` environment variable. ```ts theme={null} .url('http://ny.rpc.orbitflare.com') ``` **`.urls([...])`** - Set the primary and all fallbacks in one call. The first element is the primary, the rest are fallbacks. ```ts theme={null} .urls(['http://ny.rpc.orbitflare.com', 'http://fra.rpc.orbitflare.com', 'http://ams.rpc.orbitflare.com']) ``` **`.fallbackUrl(url)`** - Add a single fallback endpoint. Call multiple times to add several. When the primary fails, the SDK tries fallbacks in order. ```ts theme={null} .fallbackUrl('http://fra.rpc.orbitflare.com') .fallbackUrl('http://ams.rpc.orbitflare.com') ``` **`.fallbackUrls([...])`** - Same as `fallbackUrl` but takes an array. ```ts theme={null} .fallbackUrls(['http://fra.rpc.orbitflare.com', 'http://ams.rpc.orbitflare.com']) ``` **`.apiKey(key)`** - Your OrbitFlare license key. If not set, the SDK checks `ORBITFLARE_LICENSE_KEY` from the environment. The key is injected into the URL at request time, never stored in the endpoint. ```ts theme={null} .apiKey('ORBIT-XXXXXX-NNNNNN-NNNNNN') ``` **`.commitment(level)`** - Default commitment level used for all typed helpers. Options: `'processed'`, `'confirmed'`, `'finalized'`. Defaults to `'confirmed'`. ```ts theme={null} .commitment('finalized') ``` **`.retry(policy)`** - Controls how the SDK retries failed requests. `initialDelayMs` is the wait before the first retry. `multiplier` scales the delay on each attempt. `maxDelayMs` caps the backoff. `maxAttempts` limits total retries per endpoint (0 means infinite). Defaults: 100ms initial, 30s max, 2x multiplier, infinite attempts. ```ts theme={null} .retry({ initialDelayMs: 200, maxDelayMs: 15_000, multiplier: 2.0, maxAttempts: 5, }) ``` **`.timeoutMs(ms)`** - HTTP timeout for each individual request. Defaults to 30 seconds. ```ts theme={null} .timeoutMs(10_000) ``` ## Available RPC methods ### `getSlot()` Returns the current slot number. ```ts theme={null} const slot: number = await client.getSlot(); ``` ### `getBalance(address)` Returns the balance in lamports for an account. ```ts theme={null} const lamports: number = await client.getBalance('So11111111111111111111111111111111111111112'); const sol = lamports / 1_000_000_000; ``` ### `getAccountInfo(address)` Returns the full account data or `null` if the account doesn't exist. Data is base64-encoded. ```ts theme={null} const account = await client.getAccountInfo('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); if (account) { console.log('owner:', account.owner); console.log('lamports:', account.lamports); console.log('data length:', (account.data?.[0] ?? '').length); } ``` ### `getMultipleAccounts(addresses)` Fetches multiple accounts in one call. Automatically chunks into batches of 100 (Solana's per-request limit), so you can pass any number of addresses. ```ts theme={null} const accounts = await client.getMultipleAccounts(['addr1', 'addr2', 'addr3']); accounts.forEach((acct, i) => { if (acct) console.log(`account ${i}: ${acct.lamports} lamports`); else console.log(`account ${i}: not found`); }); ``` ### `getLatestBlockhash()` Returns the most recent blockhash and the last block height it's valid for. ```ts theme={null} const { blockhash, lastValidBlockHeight } = await client.getLatestBlockhash(); ``` ### `getTransaction(signature)` Fetches a confirmed transaction by its signature. Returns the full transaction with metadata. ```ts theme={null} const tx = await client.getTransaction('5K8F2j...'); console.log('slot:', tx.slot); console.log('fee:', tx.meta?.fee); ``` ### `getSignaturesForAddress(address, limit)` Returns recent transaction signatures for an address, newest first. ```ts theme={null} const sigs = await client.getSignaturesForAddress('So111...', 10); for (const sig of sigs) { console.log(`${sig.signature} at slot ${sig.slot}`); } ``` ### `getProgramAccounts(programId)` Returns all accounts owned by a program. Can return a lot of data. ```ts theme={null} const accounts = await client.getProgramAccounts('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); ``` ### `getRecentPrioritizationFees(addresses)` Returns recent priority fees for a set of accounts. Useful for estimating compute unit pricing. ```ts theme={null} const fees = await client.getRecentPrioritizationFees(['6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P']); for (const fee of fees) { console.log(`slot ${fee.slot}: ${fee.prioritizationFee} micro-lamports`); } ``` ### `sendTransaction(txBase64)` Sends a signed transaction to the network. Takes a base64-encoded serialized transaction. Returns the transaction signature. ```ts theme={null} const signature: string = await client.sendTransaction(txBase64); console.log('sent:', signature); ``` ### `simulateTransaction(txBase64)` Simulates a transaction without submitting it. Returns logs, compute units consumed, and any errors. ```ts theme={null} const result = await client.simulateTransaction(txBase64); if (result.err) { console.log('simulation failed:', result.err); } else { console.log('compute units:', result.unitsConsumed); } ``` ### `getTokenAccountsByOwner(owner, mint?, programId?)` Returns token accounts for a wallet. Pass either a specific mint or a token program ID. If both are omitted, defaults to the SPL Token program. ```ts theme={null} const tokens = await client.getTokenAccountsByOwner('wallet_address'); for (const token of tokens) { const info = token.account?.data?.parsed?.info; console.log(`mint: ${info?.mint} balance: ${info?.tokenAmount?.uiAmountString}`); } ``` ### `getTransactionsForAddress(address, options)` OrbitFlare-specific method that combines `getSignaturesForAddress` and `getTransaction` into one call. Returns a `GetTransactionsResult` with the data array and an optional `paginationToken` for fetching the next page. Supports four detail levels (`signatures`, `none`, `accounts`, `full`), bidirectional sorting, time/slot filtering, status filtering, and token account inclusion. ```ts theme={null} const result = await client.getTransactionsForAddress('address', { transactionDetails: 'full', limit: 100, sortOrder: 'asc', filters: { tokenAccounts: 'all', status: 'succeeded', blockTime: { gte: 1704067200, lte: 1706745600 }, }, }); for (const tx of result.data) { console.log(`slot ${tx.slot}`); } if (result.paginationToken) { // fetch next page using { paginationToken: result.paginationToken } } ``` ### `request(method, params)` Call any RPC method by name. The SDK builds the JSON-RPC envelope, handles retry and failover, and returns the `result` field. ```ts theme={null} const epoch = await client.request('getEpochInfo', []); const inflation = await client.request('getInflationRate', []); const supply = await client.request('getSupply', []); ``` ### `requestRaw(body)` Send a raw JSON-RPC body string. Same retry and failover as everything else. ```ts theme={null} const result = await client.requestRaw( '{"jsonrpc":"2.0","id":1,"method":"getHealth","params":[]}', ); ``` ## Full example A script that checks the state of the network, fetches a wallet's SOL balance and token holdings, and looks up recent transactions. ```ts theme={null} import { RpcClientBuilder } from '@orbitflare/sdk'; async function main() { const client = new RpcClientBuilder() .url('http://ny.rpc.orbitflare.com') .fallbackUrl('http://fra.rpc.orbitflare.com') .commitment('confirmed') .build(); const wallet = 'CKs1E69a2e9TmH4mKKLrXFF8kD3ZnwKjoEuXa6sz9WqX'; const slot = await client.getSlot(); const { blockhash } = await client.getLatestBlockhash(); console.log(`network slot: ${slot}`); console.log(`blockhash: ${blockhash}`); const balance = await client.getBalance(wallet); console.log(`\n${wallet}`); console.log(` SOL: ${(balance / 1_000_000_000).toFixed(4)}`); const tokens = await client.getTokenAccountsByOwner(wallet); for (const token of tokens) { const info = token?.account?.data?.parsed?.info; const mint = info?.mint ?? 'unknown'; const amount = info?.tokenAmount?.uiAmountString ?? '0'; if (amount !== '0') console.log(` ${mint}: ${amount}`); } const sigs = await client.getSignaturesForAddress(wallet, 5); console.log('\nrecent transactions:'); for (const sig of sigs) { const signature = sig?.signature ?? '?'; const status = sig?.err == null ? 'ok' : 'failed'; console.log(` ${sig?.slot ?? 0} ${status} ${signature.slice(0, 20)}`); } const fees = await client.getRecentPrioritizationFees([wallet]); const total = fees.reduce( (acc: number, f: any) => acc + (typeof f?.prioritizationFee === 'number' ? f.prioritizationFee : 0), 0, ); const avg = total / Math.max(fees.length, 1); console.log(`\navg priority fee: ${avg.toFixed(0)} micro-lamports`); } void main(); ``` # TypeScript WebSocket Client Source: https://docs.orbitflare.com/sdk/typescript-websocket Real-time Solana subscriptions in TypeScript, with ws as an optional peer dependency: account changes, logs, slots, and signatures with auto-reconnect. ## Install ```bash theme={null} npm install @orbitflare/sdk ws ``` `ws` is an optional peer dependency. Install it only if you use the WebSocket client. ## Building the client ```ts theme={null} import { WsClientBuilder } from '@orbitflare/sdk/ws'; import type { RetryPolicy } from '@orbitflare/sdk'; const client = await new WsClientBuilder() .url('ws://ny.rpc.orbitflare.com') .fallbackUrl('ws://fra.rpc.orbitflare.com') .apiKey('ORBIT-XXXXXX-NNNNNN-NNNNNN') .retry({ initialDelayMs: 100, maxDelayMs: 30_000, multiplier: 2.0, maxAttempts: 0, }) .pingIntervalSecs(10) .maxMissedPongs(3) .build(); ``` Minimal: ```ts theme={null} const client = await new WsClientBuilder() .url('ws://ny.rpc.orbitflare.com') .build(); ``` Note that `.build()` is async - it establishes the WebSocket connection before resolving. If the connection fails, the promise rejects immediately. ### Builder methods **`.url(url)`** - Primary WebSocket endpoint. Falls back to `ORBITFLARE_WS_URL` env var. ```ts theme={null} .url('ws://ny.rpc.orbitflare.com') ``` **`.urls([...])`** - Primary + fallbacks in one call. ```ts theme={null} .urls(['ws://ny.rpc.orbitflare.com', 'ws://fra.rpc.orbitflare.com']) ``` **`.fallbackUrl(url)` / `.fallbackUrls([...])`** - Add failover endpoints. On reconnect, the SDK rotates through them. ```ts theme={null} .fallbackUrl('ws://fra.rpc.orbitflare.com') ``` **`.apiKey(key)`** - License key. Falls back to `ORBITFLARE_LICENSE_KEY` env var. ```ts theme={null} .apiKey('ORBIT-XXXXXX-NNNNNN-NNNNNN') ``` **`.retry(policy)`** - Reconnection backoff. Default: 100ms initial, 30s max, 2x multiplier, infinite attempts. ```ts theme={null} .retry({ initialDelayMs: 200, maxDelayMs: 15_000, multiplier: 2.0, maxAttempts: 0, }) ``` **`.pingIntervalSecs(n)`** - How often the SDK sends WebSocket `Ping` frames. Default: 10. ```ts theme={null} .pingIntervalSecs(15) ``` **`.maxMissedPongs(n)`** - Pings without a response before killing the connection. Default: 3. ```ts theme={null} .maxMissedPongs(5) ``` ## Available subscriptions ### `slotSubscribe()` Fires every time a slot is processed, confirmed, or finalized. ```ts theme={null} const sub = await client.slotSubscribe(); ``` Each event is an object with `slot`, `parent`, and `root` fields: ```json theme={null} {"slot": 413014740, "parent": 413014739, "root": 413014708} ``` ### `accountSubscribe(address, commitment)` Fires when the specified account's data changes. ```ts theme={null} const sub = await client.accountSubscribe( 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'confirmed', ); ``` Returns base64-encoded account data with lamports, owner, and executable flag. ### `logsSubscribe(mentions, commitment)` Fires for transactions that mention the given addresses. Pass an empty array for all transactions. ```ts theme={null} const sub = await client.logsSubscribe( ['6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'], 'confirmed', ); ``` All logs: ```ts theme={null} const sub = await client.logsSubscribe([], 'confirmed'); ``` ### `signatureSubscribe(signature, commitment)` Fires once when a transaction reaches the given commitment level. Useful for confirming a transaction you just sent. ```ts theme={null} const sub = await client.signatureSubscribe('5K8F2j...', 'confirmed'); ``` ## Reading events All subscriptions return a `WsSubscription`. Call `.next()` to get the next event: ```ts theme={null} while (true) { const event = await sub.next(); if (event === undefined) break; console.log(event); } ``` `.next()` resolves with `undefined` when the subscription is closed. You can also attach an event listener style callback: ```ts theme={null} const off = sub.on((event) => { console.log(event); }); // later: off(); ``` ## Unsubscribing ```ts theme={null} await sub.unsubscribe(); ``` This sends an unsubscribe message to the server. If you drop a subscription without calling this, the SDK detects the orphan and sends the unsubscribe automatically. ## Multiple subscriptions All subscriptions run on a single WebSocket connection. The SDK routes notifications to the right subscription internally. ```ts theme={null} const slots = await client.slotSubscribe(); const usdc = await client.accountSubscribe('EPjFWdd5...', 'confirmed'); const logs = await client.logsSubscribe(['6EF8r...'], 'confirmed'); ``` You can add new subscriptions at any time while existing ones are running. ## Reconnection If the connection drops, the background task reconnects with exponential backoff and re-subscribes everything automatically. Your `.next()` calls just keep working - events resume once the connection is back. ## Full example A monitoring script that subscribes to slot updates and account changes for USDC, printing a live feed. ```ts theme={null} import { WsClientBuilder } from '@orbitflare/sdk/ws'; async function main() { const client = await new WsClientBuilder() .url('ws://ny.rpc.orbitflare.com') .build(); const slots = await client.slotSubscribe(); const usdc = await client.accountSubscribe( 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'confirmed', ); console.log('watching slots and USDC mint account...'); slots.on((slot) => { const s = slot?.slot ?? 0; const parent = slot?.parent ?? 0; console.log(`slot ${s} (parent ${parent})`); }); usdc.on((acct) => { const lamports = acct?.lamports ?? 0; const data = acct?.data?.[0]; const dataLen = typeof data === 'string' ? data.length : 0; console.log(`USDC mint updated: ${lamports} lamports, ${dataLen} bytes`); }); await new Promise(() => {}); } void main(); ``` # Shredstream Source: https://docs.orbitflare.com/shredstream Stream raw Solana turbine shreds over UDP to your IP and port for lowest-latency trading and MEV infrastructure, sourced from top-of-turbine validators with multi-region coverage on OrbitFlare. Turbine fragments forwarded directly to your IP:port Premium and Standard locations worldwide Forwarding adds less than a millisecond of latency Enterprise SLA with redundant infrastructure ## Before the Block, There's the Shred Raw Solana shreds forwarded straight to your servers via UDP. Sourced from top-of-turbine validators across 8 global regions. See transactions before they hit any RPC or gRPC endpoint. Shredstream is for teams that need the absolute earliest on-chain signal. If you need processed, developer-friendly data, see [gRPC Streaming](/data-streaming/jetstream) instead. ## Solana Data Path Where Shredstream sits in the stack: Produces the block and splits it into shreds. Shreds fan out through the validator tree. Raw UDP shreds forwarded to your IP:port. **You are here** -- upstream of every other feed. Receives shreds before the block is finalized. Meanwhile, gRPC and RPC endpoints are still assembling. ## Core Features Shreds are forwarded directly to your IP:port via UDP -- the fastest possible transport. No HTTP overhead, no WebSocket connections, no serialization. Just raw packets. Strategically placed across major financial and data center hubs. Frankfurt, Amsterdam, London, New York, Dublin, Siauliai, Salt Lake City, and Tokyo. Our servers are fed by high-stake validators sitting near the root of Solana's Turbine tree, minimizing propagation hops from the leader node. 99.99% uptime SLA with redundant infrastructure, automatic failover, and 24/7 monitoring across all regions. ## Use Cases Shave critical microseconds off your signal pipeline. Raw shreds let your strategies act on block data while others wait for RPC confirmation. Observe pending state transitions at the Turbine layer and construct bundles with an information edge that processed feeds cannot match. Spot new token deployments and liquidity events at the shred level, before they surface through conventional WebSocket or gRPC streams. Refresh routing tables and price quotes from raw transaction fragments, reducing staleness and improving fill quality for end users. Monitor collateral ratio changes as they propagate through Turbine and trigger liquidation calls ahead of the broader network. Tighten bid-ask spreads by adjusting quotes based on shred-level order flow, well before aggregated block data is available. ## How It Works Select your region and billing frequency. Pay with account balance, credit card (Stripe), or Solana crypto. Set your public IP address and UDP port in the dashboard. The system validates and registers your endpoint instantly. Shreds start flowing to your server within seconds. Build your deshredding and parsing logic to extract transactions. **NFT Pass Holders** -- Get 1 free Shredstream slot per active NFT Pass license for a limited time. Restricted to OrbitServers IPv4 prefixes. Claim it from the Shredstream dashboard. **Free Trial** -- Try Shredstream free for 1 hour per region. Trials are provisioned by the team: open a ticket in our [Discord](https://discord.gg/orbitflare) to start one. It runs for 1 hour from activation, so request it only when you are ready to consume the stream. ## 8 Global Shredstream Regions Premium regions offer maximum validator density in the highest-demand locations. Standard regions provide geographic redundancy and broader coverage. ### Premium Regions -- \$1,000/month | City | Country | | - | - | | 🇩🇪 Frankfurt | Germany | | 🇳🇱 Amsterdam | Netherlands | | 🇺🇸 New York | United States | | 🇬🇧 London | United Kingdom | ### Standard Regions -- \$500/month | City | Country | | - | - | | 🇮🇪 Dublin | Ireland | | 🇱🇹 Siauliai | Lithuania | | 🇺🇸 Salt Lake City | United States | | 🇯🇵 Tokyo | Japan | ## Pricing Flat-rate pricing per region with discounts for longer commitments. No hidden fees, no credit systems. **\$1,000**/month 🇩🇪 Frankfurt, 🇳🇱 Amsterdam, 🇺🇸 New York, 🇬🇧 London * Raw UDP shred forwarding * Top-of-turbine validator access * Configure IP:port destination * Auto-renewal with Stripe or crypto Ideal for HFT, MEV, and latency-critical applications. **\$500**/month 🇮🇪 Dublin, 🇱🇹 Siauliai, 🇺🇸 Salt Lake City, 🇯🇵 Tokyo * Raw UDP shred forwarding * Top-of-turbine validator access * Configure IP:port destination * Auto-renewal with Stripe or crypto Ideal for redundancy, multi-region setups, and broader coverage. | Billing Period | Discount | | - | - | | Monthly | -- | | Quarterly | 5% off | | Semi-Annual | 10% off | | Annual | 15% off | Payment methods: account balance, credit card (Stripe), or Solana cryptocurrency. Discount codes accepted at checkout. ## OrbitFlare vs Other Providers | Feature | OrbitFlare | Other Providers | | - | - | - | | **Starting Price** | \$500/mo | \$1,000-6,000/mo | | **Protocol** | Raw UDP | Raw UDP | | **Regions Available** | 8 | 1-5 | | **Free Trial** | 1 hour (via Discord) | Limited / Contact sales | | **NFT Pass Free Slot** | Yes | No | | **Payment Methods** | Balance, Card, Crypto | Varies | | **Billing Discounts** | Up to 15% annual | Varies | | **Dashboard Management** | Full self-service | Varies | | **Uptime SLA** | 99.99% | Varies | ## Technical Specifications | Spec | Value | | - | - | | **Transport** | UDP (raw packets) | | **Max Shred Size** | 1,228 bytes | | **Packet Frequency** | Continuous per slot | | **Authentication** | IP whitelist | | **Destination Config** | IP:port via dashboard | | **Sync Frequency** | Every 5 seconds | | **Bandwidth Required** | \~50-100 Mbps for a full shred stream | ## Code Example A minimal Rust listener that receives and parses raw shreds using the `solana_ledger` crate: ```rust shred_listener.rs theme={null} use std::net::UdpSocket; use solana_ledger::shred::{ merkle::Shred, ShredType, }; fn main() -> std::io::Result<()> { let socket = UdpSocket::bind("0.0.0.0:9000")?; let mut buf = [0u8; 1232]; loop { let (len, _src) = socket.recv_from(&mut buf)?; let raw = buf[..len].to_vec(); let shred = match Shred::from_payload(raw) { Ok(s) => s, Err(_) => continue, }; let hdr = shred.common_header(); let kind = match shred.shred_type() { ShredType::Data => "DATA", ShredType::Code => "CODE", }; println!( "[{kind}] slot={} idx={} fec={} {len}B", hdr.slot, shred.index(), shred.fec_set_index(), ); } } ``` ## Solana Shred Header Format | Offset | Size | Field | | - | - | - | | `0x00` | 64 bytes | Signature (Ed25519) | | `0x40` | 1 byte | Variant (type + auth) | | `0x41` | 8 bytes | Slot (u64 little-endian) | | `0x49` | 4 bytes | Index (u32 little-endian) | | `0x4D` | 2 bytes | Version (u16 little-endian) | | `0x4F` | 4 bytes | FEC Set Index (u32 little-endian) | | `0x53+` | varies | Type-specific header + payload | ## FAQ Shreds are the smallest unit of data in Solana's block propagation protocol (Turbine). Validators break blocks into shreds and distribute them across the network. By receiving raw shreds, you get access to transaction data before it's assembled into full blocks or processed by any RPC or gRPC endpoint. When you purchase a Shredstream slot, you specify a region and a destination IP:port. Our servers in that region receive shreds from top-of-turbine validators and forward them directly to your server via UDP. Shreds start arriving within seconds of configuring your destination. 8 regions total. Premium regions at $1,000/month: Frankfurt, Amsterdam, New York, and London. Standard regions at $500/month: Dublin, Siauliai, Salt Lake City, and Tokyo. All regions receive shreds from top-of-turbine validators. You need a server with a public IP address and an open UDP port (no NAT/CGNAT). Your server should have sufficient bandwidth (\~50-100 Mbps for a full shred stream) and you'll need to implement deshredding logic to reconstruct transactions from the raw shred fragments. Yes, for a limited time. Each active OrbitFlare NFT Pass license includes 1 free Shredstream slot, restricted to OrbitServers IPv4 prefixes. You can claim it directly from the Shredstream dashboard. Yes. Shredstream trials run for 1 hour per region and are provisioned by the team -- open a ticket in our [Discord](https://discord.gg/orbitflare) to request one. Have your public IPv4 and open UDP port ready before you start, since the hour begins at activation. Account balance (instant), credit/debit card via Stripe (supports recurring auto-renewal), and Solana cryptocurrency (on-chain verification). Discount codes accepted at checkout. Subscriptions auto-renew by default at the end of each billing period. Choose monthly, quarterly (5% discount), semi-annual (10% discount), or annual (15% discount) billing. Auto-renewal can be toggled on/off, and cancellation requests can be submitted from the dashboard at any time. ## Related Processed transaction and account streaming with developer-friendly tooling. Full Yellowstone gRPC interface for comprehensive on-chain data access. Private RPC and validator infrastructure with guaranteed resources. Premium all-access NFT pass with a free Shredstream slot. # For Analytics Source: https://docs.orbitflare.com/solutions/analytics Complete historical data and streaming for analytics, AI/ML, and compliance Historical Data Query Latency Full Archive Global Redundancy ## Who This Is For OrbitFlare provides the data infrastructure that analytics teams, researchers, and compliance organizations need to extract insights from Solana at scale. * **On-Chain Analytics** -- Platforms building dashboards, leaderboards, and market intelligence products from Solana data * **AI/ML Training** -- Teams that need large-scale historical blockchain data for model training and feature engineering * **Historical Research** -- Analysts studying protocol behavior, token flows, and network dynamics over time * **Compliance Teams** -- Organizations that require complete transaction audit trails and monitoring capabilities *** ## Recommended Products **Recommended** **From \$500/mo** * Complete Solana history since genesis (March 2020) * Sub-10ms P50 query latency * Standard RPC methods for data retrieval * Managed backfills for bulk historical ingestion * Global redundancy across 11 regions **Best for:** Analytics platforms, research teams, and compliance tools that need reliable access to the full Solana archive. **Live Data** **From \$500/mo** * Real-time transaction and account streaming * Live dashboard and alerting pipelines * Jetstream (low latency) and Yellowstone (complete data) * Continuous data feeds for indexing **Best for:** Live dashboards, real-time alerting systems, and streaming ETL pipelines that process data as it arrives. **Bulk Processing** **From \$2,500/mo** * Unlimited RPS and TPS * 100% dedicated bare-metal hardware * Optimized for high-throughput data extraction * Custom configuration for analytics workloads * Priority engineering support **Best for:** Large-scale data extraction, backfill operations, and AI/ML pipelines that need sustained high throughput. **Historical + gRPC = complete data coverage.** Use historical data for backfills and research, and gRPC streaming for real-time ingestion -- together they give you a complete, continuously updated view of Solana. *** ## Key Benefits Our archive infrastructure stores the complete Solana ledger history -- every block, transaction, and account state change since March 2020. Historical queries return in under 10ms at P50. Fast enough for interactive dashboards and real-time analytics applications. Data is replicated across 11 regions with automatic failover. Your analytics pipeline stays online regardless of regional disruptions. Access data via standard RPC methods, gRPC streaming, or bulk extraction on dedicated nodes -- whichever fits your pipeline architecture. Need to ingest years of historical data? Our team can provision managed backfill operations to seed your data warehouse efficiently. Run heavy queries and extraction jobs without worrying about per-request costs. Predictable pricing that scales with your tier, not your query volume. *** ## Performance Metrics | Metric | Value | | - | - | | **Historical Query Latency** | \< 10ms P50 | | **Archive Coverage** | Genesis to present (March 2020+) | | **Global Regions** | 11 | | **Uptime SLA** | 99.99% | | **Max RPS (shared)** | 600 | | **Max RPS (dedicated)** | Unlimited | **For large-scale ingestion projects**, we recommend pairing a dedicated node with managed backfills. Our engineering team can help design an extraction pipeline that matches your data warehouse architecture and throughput requirements. *** ## Get Started Choose a plan with historical data access and add gRPC streaming or dedicated nodes as your pipeline grows. Building an analytics platform or data pipeline? Our team can help architect the right data infrastructure for your scale. # For DeFi Source: https://docs.orbitflare.com/solutions/defi Real-time infrastructure for DEXs, AMMs, lending, and yield protocols Uptime SLA gRPC Streaming Global Regions DeFi Support ## Who This Is For OrbitFlare provides the real-time, always-on infrastructure that DeFi protocols require to operate safely and efficiently on Solana. * **DEXs and AMMs** -- Protocols that need instant price feeds, trade execution, and liquidity pool monitoring * **Lending Protocols** -- Platforms that depend on real-time collateral valuations and timely liquidation triggers * **Yield Aggregators** -- Systems that continuously scan for optimal yield opportunities across protocols * **Staking Platforms** -- Services managing delegations, rewards distribution, and validator monitoring *** ## Recommended Products **Recommended** **From \$500/mo** * Real-time price feeds and account state changes * Instant liquidation event detection * Streaming account updates for collateral monitoring * Jetstream (ultra-low latency) and Yellowstone (complete data) * Shared or dedicated streaming options **Best for:** Protocols that need to react instantly to on-chain state changes -- liquidations, price movements, and pool rebalancing. **Core Infrastructure** **Free to \$999/mo** * Up to 600 RPS / 200 TPS * Transaction processing and state queries * No credit limits -- unlimited requests within your tier * 11 global regions * WebSockets included **Best for:** Transaction submission, balance checks, and general state queries that power your protocol's core operations. **Mission-Critical** **From \$2,500/mo** * Unlimited RPS and TPS * 100% isolated bare-metal hardware * Custom client and configuration * Priority engineering support * 99.99% SLA-backed uptime **Best for:** Protocols where downtime or latency directly translates to financial loss. **Combine RPC with gRPC for full coverage.** Use RPC for transaction submission and state reads, and gRPC streaming for real-time event monitoring -- the standard DeFi infrastructure stack. *** ## Key Benefits Enterprise-grade uptime backed by SLA guarantees. DeFi protocols cannot afford downtime -- neither can your infrastructure. Geyser gRPC delivers account updates, transaction confirmations, and slot notifications the moment they happen on-chain. 11 regions with automatic failover ensure your protocol stays online regardless of regional outages or network issues. Our engineering team understands DeFi-specific requirements -- from liquidation mechanics to AMM integrations and MEV protection. Receive block data ahead of standard providers through direct shred-level streaming from top turbine validators. Predictable pricing with unlimited requests within your tier. No per-request billing that scales unpredictably with protocol usage. *** ## Performance Metrics | Metric | Value | | - | - | | **Uptime SLA** | 99.99% | | **Average Response Time** | \< 5ms | | **Global Regions** | 11 | | **Streaming Latency** | Sub-millisecond (Jetstream) | | **Max RPS (shared)** | 600 | | **Max RPS (dedicated)** | Unlimited | **DeFi protocols using OrbitFlare** benefit from the same infrastructure that powers some of the highest-volume trading operations on Solana. Our Shredstream optimization and global distribution are designed for exactly these workloads. *** ## Get Started Choose the plan and add-ons that match your protocol's throughput and latency requirements. Building a DeFi protocol? Our team can help architect the right infrastructure setup for your specific needs. # For Enterprise Source: https://docs.orbitflare.com/solutions/enterprise Custom infrastructure solutions for institutional and large-scale applications Isolated Infrastructure Guaranteed Uptime 24/7 Support Tailored Solutions ## Who This Is For OrbitFlare enterprise solutions are designed for organizations that need guaranteed performance, security, and dedicated support for mission-critical Solana infrastructure. * **Fintech Companies** -- Payment processors, neobanks, and financial platforms integrating Solana into their product stack * **Institutional Clients** -- Funds, trading desks, and custodians that require isolated infrastructure with strict SLAs * **Large-Scale Applications** -- Consumer-facing products with millions of users that need sustained, high-throughput Solana access * **Compliance-Focused Organizations** -- Regulated entities that require audit trails, access controls, and dedicated environments *** ## Recommended Products **Most Popular** **From \$2,500/mo** * Unlimited RPS and TPS * 100% isolated bare-metal hardware * Choice of Solana client (Labs, Jito, Frankendancer) * Custom configuration and tuning * Dedicated account manager * 99.99% SLA-backed uptime **Best for:** Organizations that need guaranteed capacity, full isolation, and the ability to configure infrastructure to their exact specifications. **Custom Pricing** * Dedicated streaming resources * Custom subscription filters * SLA-guaranteed delivery * Jetstream and Yellowstone support * Isolated from shared traffic * Priority engineering support **Best for:** Institutions running real-time monitoring, alerting, and data ingestion pipelines that cannot tolerate shared-resource contention. **Contact Sales** * Bespoke architecture design * Multi-node clusters * On-premise deployment options * White-label infrastructure * Custom networking and peering * Volume discounts available **Best for:** Organizations with unique requirements that go beyond standard product offerings -- multi-region clusters, custom SLAs, or white-label deployments. *** ## Enterprise Features Every enterprise deployment runs on isolated bare-metal hardware with dedicated network interfaces, storage, and compute. No shared resources, no noisy neighbors. Contractual uptime guarantees backed by service credits. Standard enterprise SLA is 99.99% -- custom SLAs available for mission-critical deployments. Dedicated account manager and direct access to engineering. 24/7 availability with guaranteed response times for critical issues. Infrastructure designed for regulated environments. Audit logging, access controls, and deployment configurations that meet institutional compliance requirements. IP whitelisting, API key management, isolated network environments, and encrypted connections. Enterprise-grade security as standard. Full control over your Solana client selection, RPC method availability, cache sizing, and performance tuning. Your infrastructure, your rules. *** ## Enterprise vs Standard Comparison | Feature | Enterprise | Standard Plans | | - | - | - | | **Infrastructure** | Dedicated bare-metal | Shared multi-tenant | | **RPS / TPS** | Unlimited | Rate-limited per tier | | **SLA** | 99.99% contractual | 99.9% best-effort | | **Support** | Dedicated account manager, 24/7 | Standard support channels | | **Client Selection** | Labs, Jito, Frankendancer | Solana Labs | | **Custom Config** | Full control | Not available | | **Compliance** | Audit logs, access controls | Standard logging | | **Networking** | Custom peering, private connectivity | Shared endpoints | **Enterprise onboarding** typically takes 3-5 business days for standard dedicated deployments. Custom multi-node architectures and on-premise solutions are scoped individually during the consultation process. *** ## How It Works Our team works with you to understand your requirements -- throughput, latency targets, compliance needs, regional preferences, and integration architecture. We design a custom infrastructure plan tailored to your workload, including node selection, region placement, failover strategy, and monitoring. Your dedicated infrastructure is provisioned, configured, and validated. Standard deployments are live within 3-5 business days. Your dedicated account manager and our engineering team provide continuous support, proactive monitoring, and capacity planning as your needs evolve. *** ## Contact Sales Ready to discuss enterprise Solana infrastructure? Reach out to our team to schedule a technical consultation, get a custom quote, or request a proof-of-concept deployment. Join our Discord and open a ticket in the enterprise channel for a direct conversation with our sales engineering team. Reach us on Telegram for quick questions or to schedule a call with our enterprise team. # For Traders Source: https://docs.orbitflare.com/solutions/traders Infrastructure built for trading bots, terminals, and market makers Response Time Uptime SLA Global Regions MEV Enabled ## Who This Is For OrbitFlare trading infrastructure is purpose-built for teams and individuals who need the fastest, most reliable Solana connectivity available. * **Trenchers** -- High-frequency snipers and meme coin traders who need sub-5ms execution * **Trading Bots** -- Automated strategies that depend on low latency and consistent uptime * **Trading Terminals** -- Real-time UIs that require streaming data and instant transaction submission * **Market Makers** -- Quoting engines and arbitrage systems that demand dedicated throughput *** ## Recommended Products **Most Popular** **Free to \$999/mo** * Up to 600 RPS / 200 TPS * No credit limits -- unlimited requests within your tier * 11 global regions * Sub-5ms average latency * Shredstream optimized * WebSockets included **Best for:** Trading bots, terminals, and automated strategies that need reliable, low-latency RPC. **Real-Time Data** **From \$500/mo** * Jetstream (ultra-low latency) and Yellowstone (full data) * Real-time account, transaction, and slot updates * Streaming price feeds and order book changes * Shared or dedicated options **Best for:** Bots that react to on-chain events, market makers tracking liquidity pools. **Maximum Performance** **From \$2,500/mo** * Unlimited RPS and TPS * 100% dedicated bare-metal hardware * Choice of client: Solana Labs, Jito, Frankendancer * Priority engineering support * Custom configuration **Best for:** HFT firms, professional market makers, and teams that need zero contention. **Start free, scale when ready.** Our Free tier includes 10 RPS with no time limit -- test latency and reliability before committing to a paid plan. *** ## Key Benefits Shredstream-optimized nodes powered by top turbine validators deliver the fastest data propagation on Solana. Native support for Jito bundles, tip distribution, and MEV strategies on dedicated nodes with the Jito client. Deploy close to your infrastructure with coverage across North America, Europe, and Asia-Pacific. Receive block data faster than standard RPC providers through direct shred-level streaming from validators. Pay for your RPS tier and make unlimited requests. No per-request billing, no surprise invoices. Solana-native engineers available around the clock via Discord, Telegram, and email. *** ## Performance Metrics | Metric | Value | | - | - | | **Average Response Time** | \< 5ms | | **Uptime SLA** | 99.99% | | **Global Regions** | 11 | | **JITO MEV** | Enabled (dedicated nodes) | | **Shredstream** | All nodes | | **Max RPS (shared)** | 600 | | **Max RPS (dedicated)** | Unlimited | *** ## Get Started Compare plans and choose the tier that matches your trading volume and latency requirements. Need help choosing the right setup? Our team can recommend the optimal configuration for your trading strategy. # For Wallets Source: https://docs.orbitflare.com/solutions/wallets Reliable infrastructure for wallet providers and mobile apps Uptime SLA Balance Updates Full History Global Coverage ## Who This Is For OrbitFlare delivers the reliable, fast infrastructure that wallet providers need to give their users a seamless experience on Solana. * **Wallet Providers** -- Teams building custodial or non-custodial wallet products that serve thousands to millions of users * **Mobile Apps** -- Applications that need consistent, low-latency RPC access across global mobile networks * **Browser Extensions** -- Wallet extensions that require fast balance queries and responsive transaction submission * **Hardware Wallet Integrations** -- Firmware and companion apps that connect to Solana for signing and broadcasting *** ## Recommended Products **Most Popular** **Free to \$999/mo** * Balance queries and account data retrieval * Transaction sending and confirmation * Token account lookups (SPL, Token-2022) * Up to 600 RPS / 200 TPS * 11 global regions for low-latency access * WebSockets for real-time confirmations **Best for:** Core wallet operations -- balance checks, transaction submission, and account data for all your users. **Complete History** **From \$500/mo** * Full transaction history since genesis (March 2020) * Sub-10ms query latency * Standard RPC methods supported * Managed backfills available * Global redundancy across all regions **Best for:** Displaying complete transaction history, portfolio tracking, and tax reporting for wallet users. **Real-Time Updates** **From \$500/mo** * Instant balance change notifications * Real-time transaction confirmations * Account state streaming * Jetstream and Yellowstone support **Best for:** Push notifications, live balance updates, and real-time confirmation tracking in wallet UIs. **Drop-in replacement for any Solana RPC.** OrbitFlare endpoints are fully compatible with the standard Solana JSON-RPC spec. Switch your RPC URL and everything works -- no code changes required. *** ## Key Benefits Wallet users expect their app to work every time they open it. Our SLA-backed infrastructure ensures your wallet never goes offline. Stream balance changes and transaction confirmations instantly via gRPC or WebSockets -- no polling required. Access every transaction since Solana genesis. Give users full portfolio history, activity feeds, and tax-ready exports. 11 regions across North America, Europe, and Asia-Pacific ensure fast response times for users anywhere in the world. Standard Solana JSON-RPC compatibility means you can switch to OrbitFlare by changing a single URL. No SDK changes, no migration. Serve all your users without worrying about per-request costs. Pay for your RPS tier and make unlimited requests. *** ## Performance Metrics | Metric | Value | | - | - | | **Uptime SLA** | 99.99% | | **Average Response Time** | \< 5ms | | **Global Regions** | 11 | | **Historical Data** | Since genesis (March 2020) | | **History Query Latency** | \< 10ms P50 | | **Max RPS (shared)** | 600 | **Scaling for wallet providers:** If your user base is growing rapidly and you need guaranteed capacity, consider dedicated nodes for unlimited RPS with zero contention. Our team can help you plan for scale. *** ## Get Started Start with our Free tier to test integration, then scale to the plan that matches your user base. Building a wallet? Our team can advise on the best architecture for your user volume and feature set. # Validator Staking Source: https://docs.orbitflare.com/staking Stake SOL with OrbitFlare validators: competitive APY, Jito MEV tips, transparent commission, and infrastructure from the team that runs production Solana RPC and streaming. Competitive staking rewards with MEV tips Low commission to maximize your returns High-availability infrastructure for consistent rewards Stake any amount of SOL with no minimum requirement ## Overview OrbitFlare operates a high-performance Solana validator powered by Shredstream for faster block propagation and reduced skip rates. By staking with OrbitFlare, you earn standard staking rewards plus a share of Jito MEV tips, maximizing your yield compared to validators without MEV integration. Your staked SOL remains in your custody at all times -- you delegate voting authority to OrbitFlare's validator but never transfer ownership of your tokens. ## Validator Details **Validator Identity Address** ``` orbit1bWKxnECKLqjhm5rybTiEC2GEYbecyebgEfM5q ``` This is the public key that identifies OrbitFlare's validator on the Solana network. **Vote Account Address** ``` 68q1YeY3QJoL3DF3umVKkCFARYh931sQTbZbRtYthGu9 ``` This is the vote account you delegate your stake to when staking with OrbitFlare. ## How to Stake Open your preferred Solana wallet (Phantom, Solflare, Backpack, or any wallet that supports staking). Navigate to the staking section within your wallet interface. Enter the amount of SOL you want to stake. There is no minimum requirement -- you can stake any amount. Keep a small SOL balance (at least 0.05 SOL) for future transaction fees. Search for OrbitFlare in the validator list, or manually enter the vote account address `68q1YeY3QJoL3DF3umVKkCFARYh931sQTbZbRtYthGu9`. Confirm the delegation transaction in your wallet. Rewards are distributed automatically every epoch (approximately 2-3 days). Your stake becomes active at the start of the next epoch after delegation. No further action is required -- rewards compound automatically. ## Estimated Rewards The table below shows estimated rewards for a 100 SOL stake based on current network conditions and an effective APY of approximately 6.18% after the 5% commission. | Period | Estimated Reward | | - | - | | **Daily** | \~0.0169 SOL | | **Monthly** | \~0.515 SOL | | **Yearly** | \~6.18 SOL | The effective APY is approximately **6.18%** after OrbitFlare's 5% commission. Actual returns may vary based on network conditions, validator performance, total stake on the network, and Jito MEV tip volume. The figures above are estimates and not guaranteed. Staking rewards include a share of Jito MEV tips in addition to standard Solana inflation rewards. This MEV component can increase your effective yield above the base staking APY. ## Security Validator servers have SSH access disabled in production. All management operations are performed through secure, audited automation pipelines with strict access controls. Validator keys are stored in secure, off-server key management systems. Private keys never reside on the validator machine itself, protecting against server compromise. Comprehensive monitoring tracks validator health, performance, skip rates, and voting accuracy around the clock. Automated alerts trigger immediate response to any anomalies. OrbitFlare applies Solana software updates promptly and during low-activity windows to minimize disruption. All updates are tested in staging environments before production deployment. While staking on Solana does not carry slashing risk under current protocol rules, staked SOL is illiquid during the cool-down period (approximately 2-3 days) when you choose to unstake. Plan accordingly if you need immediate liquidity. ## Validator Dashboards View OrbitFlare's validator performance, uptime history, and stake distribution on validators.app. Track rewards, commission history, and compare validator performance on staking.kiwi. # Welcome to OrbitFlare Source: https://docs.orbitflare.com/welcome Sub-5ms Solana RPC, Yellowstone and Jetstream gRPC, Shredstream UDP shreds, and dedicated nodes across 11 regions—built for traders, wallets, and DeFi teams who need speed and reliability. OrbitFlare Banner OrbitFlare Banner OrbitFlare provides Shredstream-optimized Solana RPC nodes, real-time gRPC streaming, and enterprise-grade dedicated infrastructure across 11 global regions. Beyond Solana, OrbitFlare also runs RPC infrastructure for BNB Chain, Polygon, and Robinhood Chain. ## Start Building Make your first API call in minutes Find the right plan for your workload Customer API endpoints and authentication ## Core Infrastructure High-performance JSON-RPC endpoints with less than 5ms latency Real-time gRPC via Jetstream and Yellowstone Raw UDP shreds direct from top-of-turbine validators Unlimited RPS/TPS with 100% isolated hardware ## More Products Full archival state since genesis 5-7% APY with JITO MEV rewards Premium all-access NFT pass ## More Chains Dedicated BNB/BSC node infrastructure Polygon PoS RPC, WebSockets, and Bor gRPC Robinhood Chain (Arbitrum Nitro) RPC and WebSockets ## Developer Tools Solana RPC, gRPC, Jetstream, and WebSocket in Rust Solana RPC, gRPC, Jetstream, and WebSocket in TypeScript Solana RPC, gRPC, Jetstream, and WebSocket in Go RPC, WebSocket, and gRPC for Polygon, BNB, and Robinhood ## Common Use Cases | Use Case | Recommended Products | | - | - | | Trading bots & snipers | RPC Nodes + gRPC Streaming (Jetstream) | | DeFi protocols | gRPC Streaming + Dedicated Nodes | | Wallet applications | RPC Nodes + Historical Data | | Analytics & indexing | Historical Data + gRPC (Yellowstone) | | Enterprise / HFT | Dedicated Nodes + Dedicated gRPC | ## Resources 11 regions across 3 continents Common questions answered ## Blog Read developer guides, product deep-dives, and Solana fundamentals on the [OrbitFlare Blog](https://orbitflare.com/blog). Recent posts: * [Building a Real-Time Solana Wallet Tracker](https://orbitflare.com/blog/developers/jetstream-wallet-tracker) — build a wallet tracker with Jetstream, PumpFun decoding, and whale alerts * [The Two BPFs: How a Packet Filter Runs Code on Your Favourite Blockchain](https://orbitflare.com/blog/fundamentals/the-two-bpfs) — deep dive into sBPF and eBPF ## Support * Join our [Discord community](https://discord.gg/orbitflare) for real-time support * Check out our [GitHub](https://github.com/orbitflare) for examples and tools * Follow us on [Twitter](https://x.com/orbitflarenodes) for updates and announcements # Apex Authentication Source: https://docs.orbitflare.com/apex/authentication Authenticate to OrbitFlare Apex with an x-api-key header or api-key query parameter over HTTP, or a client certificate derived from your key over QUIC. ## Get an API Key Open the [OrbitFlare dashboard](https://orbitflare.com/dashboard) and go to **Dashboard > Apex** to create a key. Each key belongs to a tier, and the tier sets your tip floor and rate limit. Treat the key like a password. Anyone who holds it can send transactions against your rate limit. It cannot spend your funds: transactions are still signed by your own keypair. ## HTTP: Header or Query Parameter Over HTTP the key travels with every request. There are two ways to pass it: | Method | Example | Notes | | - | - | - | | **`x-api-key` header** (recommended) | `x-api-key: YOUR_API_KEY` | Keeps the key out of URLs, logs, and browser history | | **`api-key` query parameter** | `http://fra.apex.orbitflare.com?api-key=YOUR_API_KEY` | For tools that only accept a URL, such as an RPC URL field | ```bash Header theme={null} curl -s http://fra.apex.orbitflare.com \ -H "Content-Type: application/json" \ -H "x-api-key: $APEX_API_KEY" \ -d '{"jsonrpc":"2.0","id":1,"method":"getTipAccounts","params":[]}' ``` ```bash Query parameter theme={null} curl -s "http://fra.apex.orbitflare.com?api-key=$APEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"getTipAccounts","params":[]}' ``` The same two options work on the plain HTTP routes (`/send`, `/send-bin`, `/send-batch`, `/send-bundle`). The HTTP routes are unencrypted, like other Solana senders: your API key and transactions are readable by anyone on the network path. Prefer QUIC, where the key never leaves your machine, or send HTTP only from a network you trust. Rotate the key from the dashboard if you think it was exposed. `getTipAccounts`, `getVersion`, `health`, and `GET /ping` need no key. Everything that submits a transaction does. A missing or invalid key returns JSON-RPC error `-32001`, or HTTP `401` on the plain routes. See [Errors and rate limits](/apex/errors-and-rate-limits). ## QUIC: Client Certificate On QUIC nothing is authenticated per request, and **your API key never crosses the wire**. Instead: 1. Your client derives an ed25519 keypair from the API key. 2. It presents that public key in its TLS client certificate during the QUIC handshake. 3. The Apex endpoint maps the public key to your account. Every stream on that connection is yours from then on. The Rust crate [`orbitflare-apex`](/apex/rust-client) and the Go module [`orbitflare-apex-go`](/apex/go-client) do all of this for you. For other languages the derivation is: ```text theme={null} seed = HKDF-SHA256(ikm = api_key bytes, salt = "orbitflare-apex", info = "orbitflare-apex/quic-client-cert/v1", L = 32) keypair = ed25519 keypair from seed ``` Wrap the public key in Solana's dummy X.509 certificate, the same format validators use for TPU QUIC (`new_dummy_x509_certificate` in the `solana-tls-utils` crate). The endpoint only checks the key inside the certificate. The other certificate fields are ignored. **Test vector:** the API key `test-api-key` derives the public key `2ovF9aU8fszHXpZwxaC1S9NF2m5VVDRBUvDUVQvXs4Tv`. If the handshake fails, the endpoint closes the connection with an application error code: | Close code | Meaning | | - | - | | `1` | No client certificate was presented | | `2` | The certificate's key does not match any API key | | `3` | Too many connections: at most 128 per API key and 64 per address. One is enough | ### Check Your Derived Key The dashboard shows the client public key next to each API key. If your QUIC connection is refused, compare the two. With the Rust crate: ```rust theme={null} use orbitflare_apex::client_pubkey; fn main() { let api_key = std::env::var("APEX_API_KEY").expect("APEX_API_KEY is required"); // Prints the certificate key only, never the API key itself. println!("{}", client_pubkey(&api_key)); } ``` With the Go module: ```go theme={null} package main import ( "fmt" "os" apex "github.com/orbitflare/orbitflare-apex-go" ) func main() { fmt.Println(apex.ClientPubkey(os.Getenv("APEX_API_KEY"))) } ``` A mismatch means the key was copied incorrectly. ## Browsers Every HTTP reply carries `Access-Control-Allow-Origin: *`, and `OPTIONS` preflight requests are answered, so you can call an Apex endpoint from browser code. An API key embedded in a public web page is visible to every visitor. For public frontends, send through your own backend, or use a key you are prepared to rotate. Apex endpoints are served over plain HTTP on port 80. A page that is itself loaded over HTTPS is subject to the browser's mixed content rules, so test from the environment you plan to ship. # Apex Best Practices Source: https://docs.orbitflare.com/apex/best-practices Land more Solana transactions with OrbitFlare Apex: keep connections warm, size the tip and compute budget, confirm every send, and retry the right way. ## Keep Connections Warm The slowest part of a cold send is the connection setup, not Apex. Open the connection before you need it and keep it open. The [Rust client](/apex/rust-client) and the [Go client](/apex/go-client) handle this for you. They ping every second against the Apex endpoint's 30 second idle timeout, resumes with 0-RTT, and re-handshakes in the background as soon as a drop is noticed. * Create **one client per process and Apex endpoint** at startup, not one per send. Streams multiplex on the single connection. * The client is cheap to `Clone` and clones share the connection. Hand a clone to each task. * Export `health()`, `reconnects_total()`, and `zero_rtt_resumptions_total()` to your metrics so you can see drops. If you implement QUIC yourself, send a QUIC PING well inside the 30 second idle timeout and keep a session ticket for 0-RTT. * **Reuse one connection.** Use a client that keeps connections alive: a `requests.Session` in Python, a shared `reqwest::Client` in Rust, or a keep-alive agent in Node.js. A new TCP connection per send adds a full round trip. * **Call `GET /ping` when idle.** It needs no key, returns `pong`, and keeps the connection from being closed between bursts. Every few seconds is enough. * **Warm up at startup.** Call `/ping` once before your first real send so the handshake is already done. ```python theme={null} import threading import requests session = requests.Session() # reuse this session for every send def keep_warm(stop: threading.Event, url="http://fra.apex.orbitflare.com/ping"): while not stop.wait(5): session.get(url, timeout=2) stop = threading.Event() threading.Thread(target=keep_warm, args=(stop,), daemon=True).start() ``` ## Send from Close to the Apex Endpoint Pick the [Apex endpoint](/apex/endpoints) with the lowest round trip from your sender, and measure it with `/ping` rather than guessing from a map. Every Apex endpoint routes to the validator clients nearest the upcoming leaders, so your job is only to reach the endpoint quickly. If you run in several regions, send from each region to its own nearest endpoint. Sending the **same signed transaction** to more than one Apex endpoint is safe, since the network executes a signature once, but each copy counts against your rate limit. ## Size the Tip to the Trade The floor (0.001 SOL on the standard tier) is the minimum to be accepted, not a recommendation for every transaction. * The tip is what funds your **Jito bid**: the tip minus the 5,000 lamport base fee. In a contested block, a floor-level bid may lose the auction. The stake-weighted and TPU paths still run, but you give up one of your three paths. * You pay the tip **only when the transaction lands**, so a larger tip on a transaction that matters costs nothing when it misses. * Scale the tip with the value of landing. A routine transfer can sit at the floor. A competitive trade should tip in proportion to what landing first is worth to you. Read the floor from the rejection message if you are unsure of your tier: a below-floor error states it. ## Set a Compute Budget on Every Transaction Apex gets you to the leader. The leader's scheduler then orders by priority fee. Without a compute budget you compete at the bottom of the queue. * **Compute unit limit:** simulate the transaction on your regular RPC, take the units consumed, and add a margin of around 10 to 20 percent. The default limit when you set none is far higher than most transactions need, which wastes your priority. * **Compute unit price:** follow the market for the accounts you write to. `getRecentPrioritizationFees` with your writable accounts is a reasonable starting signal. * Put the ComputeBudget instructions **first** in the transaction. * This applies to legacy and v0 transactions. A [transaction v1](/apex/transaction-v1) sets its budget in the message's `TransactionConfig` instead, and ComputeBudget instructions do not take effect there. The memo transactions in these docs use a 100,000 unit limit because the memo program is unusually expensive. It is a poor guide for your own limit. Size it to your own instructions. ## Confirm Every Send A signature in the reply means **accepted**, not landed. * Poll `getSignatureStatuses` on a Solana RPC, or subscribe with `signatureSubscribe`, until the status is `confirmed` or `finalized`. * Check the `err` field. A transaction can land and still fail on chain. The tip is part of the transaction, so a transaction that fails during execution rolls back the tip transfer too, and you pay only the network fee. * Stop waiting when the blockhash expires. Track `lastValidBlockHeight` from `getLatestBlockhash`. Once the chain passes it, the transaction can never land. In Rust, `rpc::SolanaRpc::confirm(signature, timeout)` does the polling and returns the slot, `None` on timeout, or an error if the transaction failed on chain. ## Let Apex Retry, Then Rebuild Apex resends an accepted transaction until it lands or its blockhash expires. Work with that instead of around it: * **Do not resend an accepted transaction in a loop.** It adds nothing and spends your rate limit. * **Use a fresh blockhash.** Fetch it at `confirmed` commitment right before you sign. A blockhash that is already a minute old leaves Apex very little time to work with. * **When the blockhash expires, rebuild.** Fetch a new blockhash, re-sign, and send the new transaction. Make sure the first one can no longer land before you do, or use logic that is safe if both execute. * **Retry transport failures.** If a send fails before you get a reply, send the same bytes again. The endpoint deduplicates by signature. * **With a durable nonce, send one signed transaction everywhere.** Copies signed separately for each provider race each other, and only one can land. The same signed transaction sent to every provider lands through whichever is fastest. * **Back off on `rate limited` and `busy`.** See [which errors to retry](/apex/errors-and-rate-limits#which-errors-to-retry). `maxRetries` caps how many times Apex re-sends a transaction. Leave it at the default unless you want a transaction to give up early, for example a quote that is stale after a second or two. ## Integrate with Feedback, Run without It While you integrate, use a transport that tells you why a transaction was rejected: JSON-RPC, the HTTP routes, or a bidirectional QUIC stream. Once your transactions are accepted reliably, move hot paths to unidirectional QUIC streams, and keep a bidirectional or HTTP send available for debugging. ## Make Every Transaction Unique Apex deduplicates by signature. Two transactions with identical instructions, signers, and blockhash have the same signature and count as one. If you intend to send the same action twice, change something, such as a memo or the compute unit price by one micro-lamport. ## Checklist * One tip per transaction, top level, funded by a signer, tip account in the static keys * Tip account chosen at random per transaction from a cached `getTipAccounts` list * Compute unit limit and price on every transaction * Blockhash fetched right before signing * Connection opened and warmed at startup * Every send confirmed, with the `err` field checked * Backoff on rate limited and busy, rebuild on invalid * API key in an environment variable or secret store, not in code # Apex Bundles Source: https://docs.orbitflare.com/apex/bundles Send atomic Solana bundles of 1 to 4 transactions through OrbitFlare Apex with sendBundle or POST /send-bundle, and track them with getInflightBundleStatuses. ## What a Bundle Is A bundle is a group of **1 to 4 transactions** that execute **in order** and **all or nothing**. Either every transaction in the bundle lands in the same block, in the order you gave, or none of them do. Use a bundle when a later transaction only makes sense if an earlier one succeeded, for example a setup transaction followed by a swap. **Bundles only land on Jito-enabled leaders.** A group of transactions cannot be kept atomic on the stake-weighted or direct TPU paths, so bundles go to the block engine path only. When the current leader does not run Jito, the bundle waits for the next one that does. For a single transaction, a regular [send](/apex/sending-transactions) uses all three paths and lands sooner. ## Rules | Rule | Detail | | - | - | | **Size** | 1 to 4 transactions | | **Order** | Executed in the order given | | **Atomicity** | All or nothing | | **Tip** | **Exactly one** transaction in the bundle carries the Apex tip, at or above your tier's floor | | **Member size** | The same as a single transaction: up to 4096 bytes as [transaction v1](/apex/transaction-v1), 1232 bytes as legacy or v0 | | **Rate** | 2 bundles per second per API key, with a short burst of 4, on top of your transaction limit. Over it you get HTTP `429` or `-32029` | | **Status** | `getInflightBundleStatuses` needs the API key that sent the bundle and takes up to 100 ids. A bundle sent with another key reads as `Invalid` | | **Retries** | Apex resubmits the bundle, under the same bundle id, until it lands, the **first** transaction's blockhash expires, or the resubmission budget runs out | The tip follows the same [tip rule](/apex/tips) as a single send: one top-level SystemProgram transfer to a published tip account, funded by a signer, with the tip account in the static keys. That tip minus the 5,000 lamport base fee becomes the Jito bid for the whole bundle. Because the bundle is atomic, the tip is only paid if the entire bundle lands. ## JSON-RPC sendBundle | Param | Type | Description | | - | - | - | | `params[0]` | string\[] | 1 to 4 signed transactions in execution order, all in one encoding | | `params[1].encoding` | string, optional | `"base64"` or `"base58"`. Left out, each transaction's is detected | The result is a **bundle id**. Keep it to query the bundle's status. The examples below use base64. ```bash cURL theme={null} # Two signed transactions from the helper in Sending transactions. # Exactly one of them may carry the tip, so build the first one without it. TX1=$(node -e 'import("./apex-tx.mjs").then(async (m) => console.log(Buffer.from((await m.buildTx({ tip: false, label: "apex bundle 1" })).serialize()).toString("base64")))') TX2=$(node -e 'import("./apex-tx.mjs").then(async (m) => console.log(Buffer.from((await m.buildTx({ tip: true, label: "apex bundle 2" })).serialize()).toString("base64")))') BUNDLE_ID=$(curl -s "$APEX_RPC" \ -H "Content-Type: application/json" \ -H "x-api-key: $APEX_API_KEY" \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"sendBundle\",\"params\":[[\"$TX1\",\"$TX2\"],{\"encoding\":\"base64\"}]}" \ | jq -r '.result // .error') echo "bundle id: $BUNDLE_ID" curl -s "$APEX_RPC" \ -H "Content-Type: application/json" \ -H "x-api-key: $APEX_API_KEY" \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getInflightBundleStatuses\",\"params\":[[\"$BUNDLE_ID\"]]}" ``` ```javascript JavaScript theme={null} import { apexRpc, buildTx } from "./apex-tx.mjs"; // Exactly one transaction carries the tip. const txs = [ await buildTx({ tip: false, label: "apex bundle 1" }), await buildTx({ tip: true, label: "apex bundle 2" }), ]; const bundleId = await apexRpc("sendBundle", [ txs.map((tx) => Buffer.from(tx.serialize()).toString("base64")), { encoding: "base64" }, ]); console.log("bundle id:", bundleId); // Poll until the bundle settles. for (;;) { const { value } = await apexRpc("getInflightBundleStatuses", [[bundleId]]); const { status, landed_slot } = value[0]; if (status === "Landed") { console.log("landed in slot", landed_slot); break; } if (status === "Failed" || status === "Invalid") { console.log("bundle", status); break; } await new Promise((resolve) => setTimeout(resolve, 1000)); } ``` ```python Python theme={null} import base64 import time from apex_tx import apex_rpc, build_tx # Exactly one transaction carries the tip. txs = [ build_tx(tip=False, label="apex bundle 1"), build_tx(tip=True, label="apex bundle 2"), ] bundle_id = apex_rpc("sendBundle", [ [base64.b64encode(bytes(tx)).decode() for tx in txs], {"encoding": "base64"}, ]) print("bundle id:", bundle_id) # Poll until the bundle settles. while True: entry = apex_rpc("getInflightBundleStatuses", [[bundle_id]])["value"][0] if entry["status"] == "Landed": print("landed in slot", entry["landed_slot"]) break if entry["status"] in ("Failed", "Invalid"): print("bundle", entry["status"]) break time.sleep(1) ``` The JavaScript and Python samples import the helper from [Sending transactions](/apex/sending-transactions#build-a-signed-tipped-transaction). Response: ```json theme={null} { "jsonrpc": "2.0", "id": 1, "result": "" } ``` ## POST /send-bundle The binary route takes the same framing as [`/send-batch`](/apex/sending-transactions#post-/send-batch): for each transaction, a big-endian u16 length followed by the raw transaction bytes, 1 to 4 frames in execution order. ```javascript JavaScript theme={null} import { APEX_RPC, API_KEY, buildTx } from "./apex-tx.mjs"; const txs = [ await buildTx({ tip: false, label: "apex bundle 1" }), await buildTx({ tip: true, label: "apex bundle 2" }), ]; // Each frame: u16 big-endian length, then the transaction bytes. const frames = txs.map((tx) => { const wire = Buffer.from(tx.serialize()); const length = Buffer.alloc(2); length.writeUInt16BE(wire.length); return Buffer.concat([length, wire]); }); const res = await fetch(`${APEX_RPC}/send-bundle`, { method: "POST", headers: { "Content-Type": "application/octet-stream", "x-api-key": API_KEY }, body: Buffer.concat(frames), }); const body = await res.json(); if (!body.bundle_id) throw new Error(`rejected ${res.status} ${body.error}: ${body.message}`); console.log("bundle id:", body.bundle_id); console.log("signatures:", body.signatures); ``` ```python Python theme={null} import struct from apex_tx import APEX_RPC, build_tx, session txs = [ bytes(build_tx(tip=False, label="apex bundle 1")), bytes(build_tx(tip=True, label="apex bundle 2")), ] # Each frame: u16 big-endian length, then the transaction bytes. payload = b"".join(struct.pack(">H", len(wire)) + wire for wire in txs) r = session.post(f"{APEX_RPC}/send-bundle", headers={"Content-Type": "application/octet-stream"}, data=payload, timeout=5) body = r.json() if "bundle_id" not in body: raise RuntimeError(f"rejected {r.status_code} {body['error']}: {body['message']}") print("bundle id:", body["bundle_id"]) print("signatures:", body["signatures"]) ``` Response: ```json theme={null} { "bundle_id": "", "signatures": ["", ""] } ``` Errors use the same format and status codes as the other [plain HTTP routes](/apex/errors-and-rate-limits#plain-http-errors). A bundle refused as a whole comes back with the `bundle` label. ## Track a Bundle Call `getInflightBundleStatuses` with an array of bundle ids: ```json theme={null} { "jsonrpc": "2.0", "id": 1, "method": "getInflightBundleStatuses", "params": [[""]] } ``` The result carries one entry per bundle id, in the order you asked: ```json theme={null} { "jsonrpc": "2.0", "id": 1, "result": { "context": { "slot": 367112345 }, "value": [ { "bundle_id": "", "status": "Landed", "landed_slot": 367112344 } ] } } ``` `landed_slot` is the slot the bundle landed in, or `null` when it has not landed. `status` is one of four states: | Status | Meaning | | - | - | | `Pending` | Apex holds the bundle and is still submitting it | | `Landed` | The bundle landed. `landed_slot` holds the slot | | `Failed` | The bundle did not land before the first transaction's blockhash expired, or the resubmission budget ran out | | `Invalid` | The bundle id is unknown to this Apex endpoint, or is older than five minutes | The bundle id stays the same while Apex resubmits, so keep polling the id you got when you sent the bundle. Ask the same Apex endpoint you sent the bundle to. Bundle ids are not shared between endpoints. Since a bundle is atomic, you can also confirm it like any transaction: once any of its signatures shows up as confirmed in `getSignatureStatuses` on a Solana RPC, the whole bundle landed. ## Bundle or Batch? | | Bundle | [Batch](/apex/sending-transactions#post-/send-batch) | | - | - | - | | **Transactions** | 1 to 4 | Up to 16 | | **Atomic** | Yes | No, each is independent | | **Ordered** | Yes | No | | **Tip** | One for the whole bundle | One per transaction | | **Paths** | Jito block engine only | All three | | **Member size limit** | 1232 bytes, or 4096 for v1 | 1232 bytes, or 4096 for v1 | # Apex Endpoints and Regions Source: https://docs.orbitflare.com/apex/endpoints Hostnames, ports, and region codes for OrbitFlare Apex endpoints: HTTP on port 80 and QUIC on UDP 7001 in Frankfurt, Amsterdam, London, New York, and more. ## Hostname Pattern Every Apex endpoint has one hostname that serves both transports: | Transport | Address | Protocol | | - | - | - | | **HTTP** (JSON-RPC and the plain HTTP routes) | `http://.apex.orbitflare.com` | TCP port 80 | | **QUIC** | `.apex.orbitflare.com:7001` | UDP port 7001 | Replace `` with a region code from the table below. ## Regions | Region | Code | HTTP | QUIC | | - | - | - | - | | 🇩🇪 Frankfurt, DE | `fra` | `http://fra.apex.orbitflare.com` | `fra.apex.orbitflare.com:7001` | | 🇳🇱 Amsterdam, NL | `ams` | `http://ams.apex.orbitflare.com` | `ams.apex.orbitflare.com:7001` | | 🇮🇪 Dublin, IE | `dub` | `http://dub.apex.orbitflare.com` | `dub.apex.orbitflare.com:7001` | | 🇬🇧 London, GB | `lon` | `http://lon.apex.orbitflare.com` | `lon.apex.orbitflare.com:7001` | | 🇺🇸 New York, US | `nyc` | `http://nyc.apex.orbitflare.com` | `nyc.apex.orbitflare.com:7001` | | 🇺🇸 Salt Lake City, US | `slc` | `http://slc.apex.orbitflare.com` | `slc.apex.orbitflare.com:7001` | | 🇸🇬 Singapore, SG | `sgp` | `http://sgp.apex.orbitflare.com` | `sgp.apex.orbitflare.com:7001` | | 🇯🇵 Tokyo, JP | `tyo` | `http://tyo.apex.orbitflare.com` | `tyo.apex.orbitflare.com:7001` | | 🇱🇹 Siauliai, LT | `sqq` | `http://sqq.apex.orbitflare.com` | `sqq.apex.orbitflare.com:7001` | | 🌐 Global (nearest) | `global` | `http://global.apex.orbitflare.com` | `global.apex.orbitflare.com:7001` | **`global.apex.orbitflare.com` resolves to the healthy Apex endpoint nearest to you.** The choice is made at DNS lookup, from your resolver's view of your location: your own network when the resolver passes it on (Google and Quad9 do), otherwise the Cloudflare location your resolver reached. An endpoint that stops answering health checks leaves the answers within about a minute. A QUIC connection stays on the endpoint it resolved when it connected. ## Choosing an Apex Endpoint Pick the Apex endpoint nearest to the machine that sends the transaction. Every Apex endpoint knows the full leader schedule and routes each transaction to the validator clients nearest the upcoming leaders. Your choice affects your own round trip to the endpoint. It does not change the landing path. Use a named region rather than `global` when you need a fixed host, for example for a firewall rule or a pinned round trip. Measure before you decide. `GET /ping` needs no API key and returns `pong`: ```bash theme={null} for code in fra ams dub lon nyc slc sgp tyo sqq; do printf "%s " "$code" curl -s -o /dev/null -w "%{time_total}s\n" "http://$code.apex.orbitflare.com/ping" done ``` The first request to each host includes the TCP handshake. Run the loop twice and compare the second pass, or see [warm connections](/apex/best-practices#keep-connections-warm). ## Firewall Rules | Direction | Protocol | Port | Used by | | - | - | - | - | | Outbound | TCP | 80 | JSON-RPC and plain HTTP routes | | Outbound | UDP | 7001 | QUIC | The QUIC client binds an ephemeral local UDP port by default. If your firewall needs a fixed source port, the Rust client accepts a bind address through `ClientOptions::bind_addr` and the Go client through `Options.BindAddr`. See [Rust client](/apex/rust-client#options) and [Go client](/apex/go-client#options). ## Utility Methods These JSON-RPC methods work on every Apex endpoint without an API key: | Method | Returns | | - | - | | `getTipAccounts` | The tip accounts this Apex endpoint accepts | | `getVersion` | The endpoint's version | | `health` | Whether the endpoint is up | ```bash theme={null} curl -s http://fra.apex.orbitflare.com \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"getTipAccounts","params":[]}' ``` # Apex Errors and Rate Limits Source: https://docs.orbitflare.com/apex/errors-and-rate-limits Every OrbitFlare Apex error: JSON-RPC codes -32001, -32029, -32602, and -32603, plain HTTP statuses, QUIC admission codes, rate limits, and what to do about each. ## Accepted Does Not Mean Landed A successful reply means the Apex endpoint holds your transaction and is racing it to the leaders until it lands or its blockhash expires. It does **not** mean the transaction landed. Confirm every send with `getSignatureStatuses` on a Solana RPC. See [Best practices](/apex/best-practices#confirm-every-send). ### Why an Accepted Transaction Did Not Land Your dashboard records how each accepted transaction ended. When it did not land, one of these codes says why: | Code | What happened | What to do | | - | - | - | | `blockhash` | The blockhash expired before the transaction landed | Rebuild with a fresh blockhash. A higher priority fee helps it land sooner | | `max_attempts` | Apex used up its retries, or your `maxRetries` | Raise or leave out `maxRetries`, or rebuild | | `nonce_used` | The transaction's durable nonce was used by another transaction, usually a copy of the same action sent through another provider | Nothing, if the other copy landed. See the [FAQ](/apex/faq) | | `blockhash_unknown` | The blockhash was never seen on mainnet: built against another cluster, or long expired | Build against a recent mainnet blockhash | | `nonce_timeout` | A durable nonce transaction was sent for 90 seconds and its nonce was still unused | Rebuild it, with a higher priority fee | | `insufficient_funds_for_fee`, `fee_payer_not_found`, `invalid_account_for_fee` | Checked after Apex gave up: the fee payer does not exist or cannot pay the fee, so no leader would include it | Fund the fee payer | | `lookup_table_not_found`, `invalid_lookup_table` | Checked after Apex gave up: an address lookup table it uses does not exist or does not hold that index | Build against a lookup table that exists on mainnet | | `invalid_account_locks`, `signature_failure`, `invalid_transaction`, `invalid_compute_budget` | Checked after Apex gave up: the runtime refuses the transaction as built | Fix the transaction; simulate it before sending | A transaction that landed but failed on chain shows as failed: its instructions returned an error, so the tip was never paid. Apex checks a transaction in this order: API key, rate limit, transaction validity and size, then the tip. The first failed check decides the error. Nothing is sent anywhere until every check passes, so a rejected transaction costs nothing. ## JSON-RPC Errors | Code | Meaning | What to do | | - | - | - | | `-32001` | Unauthorized. The API key is missing or invalid | Check the `x-api-key` header or `?api-key=` parameter | | `-32029` | Rate limited. Your key is over its per-second limit | Back off briefly and retry. Smooth out bursts | | `-32602` | Invalid transaction or tip. The message says what failed, and states your floor when the tip is short | Fix the transaction. Retrying the same bytes will fail again | | `-32603` | Busy. The endpoint is shedding load | Retry after a short delay, or fail over to another Apex endpoint | ```json theme={null} { "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "..." } } ``` ## Plain HTTP Errors On `/send` and `/send-bin`, success is HTTP `200` with `{"signature": "..."}`. `/send-batch` replies `200` with `{"attempted": n, "accepted": n, "rejected": n, "results": [...]}`, where each result, in frame order, is either `{"signature": "..."}` or `{"error": "