# Introduction

Welcome! This is the official documentation for Triton One's high-performance blockchain RPC services. Whether you're building your first dApp or managing a high-frequency trading operation, our goal is to provide you with reliable, fast, and feature-rich infrastructure.

#### Who is this for?

* **dApp Developers:** Get fast and reliable RPC access for your application's frontend and backend needs.
* **Data Analysts:** Leverage our advanced streaming and archival services to get deep insights into on-chain activity.
* **Traders & Arbitrage Bots:** Use our low-latency transaction delivery network to gain a competitive edge.
* **Validators:** Integrate with our network to provide and monetize transaction bandwidth.

#### Getting Started

If you're new here, the best place to start is our [**Getting Started Guide**](/getting-started). It will walk you through the process of creating an account, understanding your endpoints, and making your first API call.

#### Our Services

* **Core RPC Features:** Learn about our global infrastructure, including GeoDNS routing, rate limiting, and security features.
* **Chain-Specific Guides:** Find detailed information for the chains we support, including Solana, SUI, and more.
* **Advanced Data & Streaming:** Dive deep with our powerful data streaming solutions like gRPC and improved WebSockets.
* **Trading Solutions:** Explore our Cascade network for optimized, low-latency transaction delivery.


# Getting started

Welcome to Triton One! This guide will walk you through the essential first steps to get your application connected to our RPC services.

#### Step 1: Create an account

To get started with using Triton One you will need access to a customer account with an active subscription.

* **Self sign up:** You can sign up following this [link](https://customers.triton.one/onboarding) and make a deposit to activate your account.
* **Contact us directly:** For custom inquiries, you can email us at <support@triton.one> or reach out via [Telegram](https://t.me/tritonone).

Our team will work with you to understand your needs and recommend the best plan, whether it's our shared service or a dedicated node deployment.

#### Step 2: Understand your endpoint vs. your token

Once your account is created, you will receive access to **Endpoints** and **Tokens** through the [Customer Portal](https://customers.triton.one). It is critical to understand the difference:

* **Endpoint URL (for frontend / dApps):**
  * This is the URL you integrate directly into your public-facing dApp or website (e.g., `https://your-app.mainnet.rpcpool.com`).
  * Traffic from these endpoints is typically rate-limited by IP and origin domain to protect against abuse.
  * **Never embed a secret token in your frontend code.**
* **Secret token (for backend services):**
  * A token is a secret key used to authenticate requests from your backend servers, scripts, or trading bots.
  * Backend traffic with a token usually has higher rate limits.
  * **This token must be kept secret.** If you suspect it has been leaked, contact us immediately to have it rotated.

#### 3. Authenticate your request

Triton supports two auth modes:

* **Allowed origins**: tighter rate limit per IP, no secret in the page. Use for browser apps
* **Token auth**: higher rate limit, identifies your account. Recommended for backend services

How you attach the token depends on the protocol.

**x-token header (works for all protocols)**

The `x-token` metadata header authenticates all request types (JSON-RPC, WebSocket, and gRPC).

* Example: `x-token: <your-token>`
* When using the header, the endpoint URL stays clean: `https://<your-endpoint>.mainnet.rpcpool.com`

**Token in the URL path (HTTPS and WSS only)**

JSON-RPC and WebSocket also accept the token in the URL path. Token-in-URL is not supported on gRPC and returns `403`.

Examples:

* `https://<your-endpoint>.mainnet.rpcpool.com/<your-token>`
* `wss://<your-endpoint>.mainnet.rpcpool.com/<your-token>`

#### Step 4: Configure your application

With your endpoint URL, you can now configure your application.

* For standard RPC calls, use the HTTPS URL (e.g., `https://...`).
* For WebSocket subscriptions, replace `https` with `wss` (e.g., `wss://...`).

#### Step 5: Explore the documentation

You're all set! Now you can explore the rest of our documentation to make the most of our service:

* Learn about our [**Core features**](/core-features/introduction) like [GeoDNS](/core-features/geodns) and [Rate limits](/core-features/ratelimits).
* Read our guide on [**Best Practices for Sending Solana Transactions**](/chains/solana/cascade/sending-txs).
* Discover our [**Advanced data & streaming**](/project-yellowstone/introduction) services for real-time insights.


# Introduction

Our platform provides a rich set of features designed to enhance your experience, from core infrastructure guarantees to advanced, chain-specific APIs. This page serves as a guide to some of the key capabilities you can find throughout our documentation.

#### Core Platform Features

These foundational features apply to all our endpoints, providing a baseline of performance, reliability, and security.

* **Global Request Routing** Learn how our multi-layered system using GeoDNS and BGP Anycast ensures low-latency connections for your users worldwide. Read the [Request Routing Guide →](/core-features/geodns)
* **Security & Abuse Prevention** A look into our proactive, multi-layered defense system, including intelligent rate limiting, protocol-level traffic filtering, and advanced fingerprinting. Learn about [Abuse Prevention →](/core-features/abuse-prevention)
* **High Availability Architecture** Our endpoints are backed by clusters of nodes and load balancers, enabling instantaneous failover to ensure maximum uptime for your application.

#### Support for a Multi-Chain Ecosystem

While Solana is a core part of our services, our infrastructure is built to support a variety of high-performance blockchains. We provide dedicated, reliable RPC access for multiple ecosystems, including:

* [Solana](/chains/solana)
* [Sui](/chains/sui)
* [Monad](https://docs.triton.one/chains/monad)

#### Advanced Solana Features

As our core offering, Solana benefits from a number of specialized, high-performance features.

* **Cascade Transaction Network** An optimized network for high-performance transaction delivery on Solana, using Stake-Weighted Quality of Service (SWQoS). Explore [Cascade →](/chains/solana/cascade)
* **Cloudbreak  Adaptive Indexes** Serve heavy `getProgramAccounts` queries with exceptional performance using our custom-built, on-chain data indexes. Discover [Cloudbreak →](/project-yellowstone/cloudbreak-custom-indexes)
* **Digital Assets API** A powerful, indexed API for querying NFTs and other digital assets on Solana, including support for compressed NFTs. Read the [Digital Assets API Docs →](/digital-assets-api/introduction)

#### Data, Streaming & Archives

We offer multiple ways to stream real-time data or query historical information.

* **Dragon's Mouth (gRPC)** High-performance, low-latency gRPC streams for real-time Solana data, powered by Geyser. Get started with gRPC →
* **Whirligig (WebSockets)** An improved, more reliable WebSocket implementation for applications that require a stable connection for subscriptions. Learn about [Whirligig WebSockets →](/project-yellowstone/whirligig-websockets)
* **Full Chain Archives** Query the entire history of the Solana blockchain, back to the genesis block, using Superbank archival solutions. Learn about [Archival Access →](/project-yellowstone/old-faithful-historical-archive)

#### Trading APIs & Tools

Gain a competitive edge with our suite of integrated APIs for on-chain trading.

* **Titan Swap API:** Meta-aggregator that sources from multiple DEX aggregators and RFQ venues, with a WebSocket streaming path (Titan Prime) for real-time quotes and DART routing that re-optimises at execution time, not just quote time.
* **Jito simulateBundle:** RPC method for simulating atomic multi-transaction Jito bundles before submission, letting advanced traders validate complex arbitrage and MEV strategies end-to-end.


# Routing

We use a multi-layered architecture to route your API requests quickly and reliably across our global network. This page explains our standard routing method and our next-generation BGP Anycast system.

#### Standard Routing: GeoDNS + Load Balancers

By default, we use GeoDNS to resolve your endpoint's domain name to the geographically closest datacenter. This ensures that your application connects to a server in your region, minimizing network latency.

Your endpoint connects not to a single server, but to a high-availability cluster of load balancers within that datacenter. These load balancers then distribute your requests across our fleet of healthy RPC nodes.

This architecture provides **instantaneous failover**. If a backend server fails, our load balancers immediately and automatically redirect traffic to healthy nodes. This protects your application from disruptions without you having to wait for DNS changes to propagate.

#### Next-Generation Routing: BGP Anycast

For the highest level of performance and reliability, we offer routing via BGP Anycast. This advanced method skips GeoDNS entirely and uses the internet's core routing protocols to direct your traffic to our nearest network entry point.

Once your request enters our network, it travels over our private, dedicated backbone to the appropriate backend servers, bypassing the variable performance and congestion of the public internet.

This provides several key advantages:

* **Lower Latency:** BGP routing is often faster and more efficient than DNS-based routing.
* **Improved Reliability:** Your connection is more resilient as it no longer depends on DNS resolution for routing.
* **Enhanced Performance:** Our dedicated network provides a faster, more secure, and more consistent path for your data.

#### How to Use Anycast Routing

BGP Anycast routing is the future of our network and will become the default over time. This feature is currently available for clients who require the highest performance. If you are interested in using an Anycasted endpoint, please contact our support team.

#### Troubleshooting Connectivity

If you notice you are not being routed to the expected region under our **standard GeoDNS system**, please check the following:

* **VPN Usage:** Are you using a VPN? This will make your requests appear to originate from the VPN's location, not your own.
* **DNS Service:** Some custom DNS services can interfere with location data by masking or removing EDNS information.
* **Proxying:** Routing your traffic through a proxy can interfere with GeoDNS effectiveness. Please see our guide on [Proxying](/core-features/proxying) for more details.


# Abuse prevention

#### Our Philosophy

We take a proactive approach to abuse prevention with two primary goals:

1. **Protect Your Application:** Ensure that malicious traffic doesn't disrupt the service for your legitimate users.
2. **Protect Your Bill:** Prevent runaway bots or abusive traffic from causing unexpected charges.

Our abuse prevention systems are a core feature built over several years of experience running high-demand public and private endpoints.

{% hint style="info" %}
**A Note on Proxies**

We manage abuse prevention natively, so you do not need to place third-party proxies (like Cloudflare) in front of our service. In fact, doing so often introduces disadvantages like centralization and man-in-the-middle security risks. For a detailed explanation, please see our [guide on Proxying](/core-features/proxying).
{% endhint %}

#### How We Protect You

Our strategy is a multi-layered defense designed to filter out malicious traffic while allowing legitimate requests to pass through smoothly. Key components include:

* **Access Control (Endpoints vs. Tokens):** We provide a clear distinction between two methods of access. **Public Endpoints** are for your frontend dApp and are secured by an allowlist of web origins you provide. **Secret Tokens** are for your backend services and must be kept private.
* **Intelligent** [**Rate Limiting**](/core-features/ratelimits)**:** Our platform enforces carefully tuned Rate Limits based on IP address and other factors to prevent any single actor from overwhelming the service.
* **Traffic Filtering:** Our load balancers inspect incoming traffic to ensure it conforms to valid JSON-RPC specifications. Malformed requests or traffic that doesn't resemble a useful RPC call is denied at the edge before it can impact backend nodes.
* **Advanced Fingerprinting:** We employ sophisticated fingerprinting techniques to identify and block malicious actors attempting to circumvent our security measures, such as by spoofing authentication credentials or web origins. This protects against more advanced and persistent abuse patterns.

#### Your Role in Security

Properly using endpoints and tokens is the most important step you can take to secure your service.

* **NEVER** expose a secret Token in public source code, like a frontend JavaScript application. Use your public Endpoint URL instead.
* **ALWAYS** keep your Tokens secure on your backend, treating them like any other API key or password.

For applications like mobile or desktop apps where embedding a token may seem necessary, please contact our support team first. We will help you find a secure setup for your use case.


# Rate limits

#### Why we have rate limits

We apply rate limits to all our nodes to ensure service stability and protect our infrastructure from abusive traffic. Our goal is to allow legitimate application traffic to flow smoothly while mitigating the impact of aggressive bots.

#### How it works

Rate limits are primarily based on your originating IP address. If you exceed the request limit in a given time window, our server will reply with an **`HTTP 429 Too Many Requests`** status code.

{% hint style="info" %}
**Handling a `429` Error**

Your application's logic for handling rate limits is critical. When you receive a `429` error, you **must pause all requests from that IP for 10 seconds**.

Attempting to retry immediately will only result in more `429` errors and will not succeed. A 10-second backoff is required to get out of the rate-limited state.
{% endhint %}

#### Rate limits on the shared service

Our shared RPC pools have standard rate limits designed for well-written frontend dApp traffic.

* **Default limit:** The standard limit for most methods is **1200 requests per 10 seconds** per IP.
* **Stricter limits:** Computationally expensive methods, such as `getProgramAccounts`, have significantly lower limits.
* **View your limits:** To see the specific limits for your endpoint, visit `https://<your-endpoint>.rpcpool.com/ratelimits`.
* **Monitor programmatically:** Shared pool responses include `X-Ratelimit-*` HTTP headers, allowing you to monitor your current usage in real-time.

#### Custom limits for dedicated nodes

If you have a dedicated node, we can customise your rate limits to perfectly suit your workload.


# Proxying

## Proxying Traffic

{% hint style="warning" %}
**Warning: We Strongly Advise Against Proxying** Our platform is engineered for optimal performance, reliability, and security through direct connections. Placing a third-party proxy (like Cloudflare) in front of your RPC endpoint is not recommended, as it typically undermines these core features, introduces significant complexity, and degrades performance.
{% endhint %}

Before attempting to implement a proxy, we urge you to contact our support team. We can almost always provide a more direct, performant, and robust solution to achieve your goals without the drawbacks of an additional network layer.

#### Why We Advise Against Proxying

While proxying can be useful in traditional web hosting, it is poorly suited for high-performance RPC infrastructure for several key reasons.

**1. Performance Degradation and Routing Interference**

Proxying actively interferes with the sophisticated routing mechanisms we use to give you a fast and reliable connection.

* **It Breaks Geo-Routing:** Our standard routing uses GeoDNS to connect your users to the closest geographical datacenter. If your proxy isn't geographically distributed or does not provide accurate EDNS information, it will break this functionality, nullifying the latency benefits.
* **It Is Incompatible with BGP Anycast:** Our next-generation Anycast routing provides a significant performance boost by using the internet's core protocols to find the optimal path from your user to our network. This system relies on the user's real IP address. A proxy completely masks this information, making it impossible for the Anycast system to work as intended and routing your traffic sub-optimally.
* **It Delays Failover:** Proxies often cache DNS records. This can interfere with our ability to reroute your traffic during an emergency, leading to reduced redundancy and longer potential downtimes for your application.

**2. Security and Responsibility Concerns**

When you place a proxy in front of our service, you inherit significant security responsibilities.

* **You Add Redundant DDoS Protection:** Many users enable proxies to gain access to DDoS (Distributed Denial-of-Service) mitigation. However, our infrastructure is already well-protected against DDoS attacks, meaning a commercial provider like Cloudflare adds a layer of protection that your endpoint does not need.
* **You Create a Man-in-the-Middle:** By design, your proxy decrypts all traffic between your users and our servers. You assume full responsibility for any security incidents or data leaks that may result from this.
* **SSL/TLS Misconfiguration is Common:** A common mistake is to use insecure proxy SSL modes (like Cloudflare's "Flexible" or "Full" settings without proper validation), which results in unencrypted traffic over parts of the network and breaks end-to-end security. We do not recommend disabling SSL validation.
* **You Must Handle Abuse Prevention:** When you use a proxy, you take over primary responsibility for rate limiting and abuse prevention. Our systems will see all traffic as coming from your proxy's IP, requiring us to partially disable our standard protections for your endpoint.

**3. Technical and Configuration Complexity**

A proxy setup requires careful and complex configuration to avoid breaking functionality.

* **HTTP Header Forwarding:** You must ensure your proxy is configured to correctly forward critical headers, including `Host` and `Origin` (to prevent CORS errors and routing failures) and `X-Forwarded-For` / `X-Real-IP` (to pass the original user IP).
* **Requires a Specialized Token:** You must contact us to be issued a specialized token for your proxy to use. You need to ensure this token is not leaked in proxy error messages or otherwise.
* **DNS-Layer vs. HTTP-Layer Proxies:** Simple DNS-layer proxies (such as Cloudflare's default "Proxy" DNS records) are generally not supported for our shared endpoints. Only full HTTP-layer proxies can be made to work, and they require the complex configuration described above.

If a misconfigured proxy causes abusive traffic, we will be forced to severely limit or block your endpoint.


# FAQs

With our user-friendly platform and thorough documentation, our customer

#### Account & Billing

**Q: How do I get an account?** A: The best way to start is by self onboarding following [this link](https://customers.triton.one/onboarding?utm_source=steve). For custom inquiries, you can also email us at <support@triton.one> or contact us on [Telegram](https://t.me/tritonone).

**Q: Do you require long-term contracts?** A: No, our standard billing is month-to-month.

**Q: What payment methods do you accept?** A: We accept:

* USDC (SPL, ERC20 on Ethereum, Tron, Polygon)
* Credit cards (via Coinflow)
* Wire transfers in GBP and USD

**Q: Can I change my plan later?** A: Yes, you can upgrade or downgrade your plan as your needs change.

**Q: How do I cancel my subscription?** A: We require one calendar month's notice for cancellation. This aligns with the notice periods from our own infrastructure vendors.

#### General Technical Questions

**Q: What happens if I hit my rate limit?** A: Your application will receive `HTTP 429` errors. When this happens, you must pause requests for 10 seconds to clear the limit. We strongly recommend implementing a backoff-and-retry mechanism. See our Rate Limits page for more details.

**Q: Can I run scripts on the shared service?** A: No, the shared service is designed for frontend dApp traffic only. Backend scripts and bots require a dedicated node to ensure fair use and stability for everyone.

**Q: What should I do if my secret token is leaked?** A: Contact us immediately. We will disable the compromised token and issue a new one to secure your account.

**Q: How do I connect to WebSockets?** A: Simply replace `https` with `wss` in your RPC endpoint URL. For example: `wss://your-endpoint.rpcpool.com`. We do recommend however to use the gRPC interface for streaming purposes if available, see [Dragon's Mouth gRPC Subscription](/project-yellowstone/dragons-mouth-grpc-subscriptions) page for details on how to do that on Solana.&#x20;

#### Solana-Specific Questions

**Q: How can I use Geyser plugins?** A: Geyser plugins can be enabled on our dedicated Solana nodes. They provide powerful, real-time streams of on-chain data. Contact our support team to discuss your specific needs and we can configure a node for you.

**Q: What is the advantage of sending transactions through a staked validator?** A: Staked validators have access to a much larger, reserved connection pool for sending transactions. During periods of high network congestion on Solana, this provides a significant advantage over the limited, highly contested public connection pools. See [Cascade](/chains/solana/cascade).

**Q: Can a "processed" transaction on Solana still be dropped?** A: Yes. A transaction is only final once it reaches `confirmed` or `finalized` commitment. Slots containing `processed` transactions can still be dropped during minority forks or network congestion.


# Security practices

At Triton One, we are committed to a defense-in-depth security model to protect our global infrastructure and our customers' services. This document provides an overview of the technical and operational measures we take to ensure the security and integrity of our platform.

#### Access Control

Secure access to our infrastructure is controlled through a centralized Identity and Access Management (IAM) system governed by the principle of least privilege.

* **Authentication:** We enforce strict password policies and mandate the use of Multi-Factor Authentication (MFA) for all services, requiring hardware security tokens (like Yubikeys) for an added layer of protection.
* **Single Sign-On (SSO):** All access to external and internal services is gated through our SSO provider to ensure centralized control and auditing.
* **SSH Access:** All SSH access to our servers is strictly controlled. We use bastion hosts (jump servers), short-lived SSH certificates issued by a central authority, and require hardware key-based authentication instead of passwords.

#### Network Security

Our network is designed to be secure by default, minimizing exposure and encrypting all traffic.

* **Private Network Backbone:** All internal traffic between our servers travels over a private, encrypted network backbone, completely isolated from the public internet.
* **Host Firewalls:** Every host is protected by a firewall configured with a minimal exposure policy. Only the specific ports required for a host's services are opened, and access is restricted to trusted internal subnets.
* **Data in Transit:** All data is encrypted in transit by default, whether it is moving across our internal network or over the public internet via HTTPS.

#### System and Endpoint Security

The security of our individual systems and the devices our team uses is paramount.

* **Secure Employee Workstations:** All employee devices used for work are required to have full disk encryption, up-to-date antivirus software, and must use a VPN when connected to any untrusted network.
* **Automated Patching:** Our servers receive automated security patches on a daily basis to protect against the latest threats.
* **Vulnerability Monitoring:** We continuously monitor critical software packages for new Common Vulnerabilities and Exposures (CVEs) to ensure they are patched proactively.

#### Personnel and Procedural Security

Our security posture is strengthened by our rigorous internal processes and the training of our team.

* **Onboarding and Offboarding:** We follow a formal, secure process for onboarding new employees, granting access gradually based on need. When an employee departs, a strict offboarding procedure is initiated to immediately and completely revoke all access to all systems.
* **Security Training:** Our team undergoes regular security awareness training to stay informed about the latest security best practices, tools, and procedures.

#### Auditing, Alerting, and Incident Response

We believe in proactive monitoring and having a clear plan for any potential issues.

* **Continuous Auditing:** We use automated tools to perform daily security audits on all hosts, checking for compliance with our security policies and identifying potential risks.
* **Real-Time Alerting:** Our systems are monitored 24/7 with a sophisticated alerting stack that notifies our on-call team of any suspicious activity or critical security events.
* **Incident Response & Disaster Recovery:** We have formal, well-documented Security Incident Response and Disaster Recovery plans based on NIST standards. These procedures ensure we can respond to and resolve any potential incidents swiftly and effectively.

#### Contact information

* For general concerns regarding privacy or security, please email <support@triton.one>.
* For urgent security reports, please contact <noc@triton.one>.


# Data Privacy

We are committed to the responsible handling of data and full compliance with privacy regulations such as GDPR. This page outlines our policies regarding the customer data we process to provide our services.

#### What Customer Data We Handle

To operate our services, we process three main categories of customer-related data:

* **Validator Node Identity Keys:** For customers running validators on our infrastructure.
* **RPC Service Log Data:** Logs generated by user interaction with our RPC endpoints.
* **Customer Account & Billing Information:** Information required for account management and invoicing.

#### How We Protect Your Data

We employ strict measures to protect the data you entrust to us.

* **Validator Keys:** Private keys are a critical asset. They are never stored or transmitted unencrypted and only ever exist in a decrypted state inside the relevant processes memory while in active use.
* **Billing Information:** Sensitive payment details, such as credit card numbers, are never stored on our systems. All billing is handled by separate, dedicated, and compliant third-party payment processors.

#### RPC Log Data and Privacy Levels

We provide different levels of data privacy to suit our customers' needs.

**Shared Services**

On our **shared services**, all data collection is for the sole purpose of abuse prevention and operational stability. We do not identify or de-anonymize individual end-users, and our standard log retention period is four weeks.

**Dedicated Services**

For our **dedicated services**, we offer customers direct control over their logging policies to meet specific legal or privacy requirements. You can choose from the following levels:

* **Max Privacy:** No request parameters or payloads are logged. This offers the highest level of privacy but may limit our ability to detect certain types of abuse. This is the required policy for any shared infrastructure components.
* **Enhanced Privacy (Default):** We may log method parameters but cannot link the signer of a transaction to the originating IP address. This provides a balance of operational insight and user privacy.
* **GDPR Privacy:** We may log all parameters for technical support, legal, or compliance reasons. All data is handled in a fully GDPR-compliant manner.

For any questions regarding our data privacy policies, please contact us at **<support@triton.one>**.


# Solana

Welcome to our documentation for the Solana ecosystem. Our platform provides best-in-class infrastructure for developers, traders, and enterprises, offering everything from standard RPC access to a suite of high-performance, proprietary tools that give you a competitive edge.

#### Core RPC Services

* **Full API Support:** We offer complete compatibility with the standard Solana JSON-RPC and WebSocket APIs across **Mainnet**, **Testnet**, and **Devnet**, ensuring seamless integration with any existing Solana SDK or tool.
* **Complete Historical Archive:** All our endpoints provide access to the full history of the Solana blockchain. You can query any transaction or block, all the way back to genesis, using our powerful archival solutions. Learn more about our [Archival solutions →](/project-yellowstone/old-faithful-historical-archive)

#### Enhanced Features ([Project Yellowstone →](/project-yellowstone/introduction))

Project Yellowstone is our suite of Solana infrastructure improvements designed for superior performance and capability.

* **Steamboat Custom Indexes:** Serve heavy `getProgramAccounts` queries with exceptional performance using our custom-built, on-chain data indexes that can reduce query times from seconds to milliseconds. Discover [Steamboat →](/project-yellowstone/cloudbreak-custom-indexes)
* **Advanced Data Streaming:** Access real-time on-chain data through a variety of powerful tools, including low-latency gRPC streams and improved, highly reliable WebSockets. Compare our [Streaming options →](/chains/solana/streaming)
* **Parsed Program Streams (Vixen):** Go beyond raw data with our service that provides real-time, pre-parsed data streams for popular on-chain programs, saving you immense development and data processing overhead. Explore [Vixen Program Data Streams →](/project-yellowstone/vixen-parsing-framework)

#### High-Performance Transaction Sending

* **Cascade Network:** Our optimized network for transaction delivery, using Solana's Stake-Weighted Quality of Service (SWQoS) to provide a more reliable path to network leaders, especially during periods of high congestion. Learn about the [Cascade Network →](/chains/solana/cascade)
* **Transaction Sending Advice:** Follow our best practices for client-side retries, CU budgets, and priority fees to maximize your transaction inclusion rate. Read our [Transaction Sending Advice →](/chains/solana/cascade/sending-txs)
* **Yellowstone Shield:** Protect your transactions from MEV and other validator-level risks by creating on-chain policies that control which validators are allowed to process your transactions. Learn how to use [Yellowstone Shield →](/project-yellowstone/shield-transaction-policies)

#### Specialized APIs

* **Digital Assets API (DAS):** A powerful, indexed API for querying NFTs, fungible tokens (SPL and Token22), and other digital assets on Solana, including first-class support for compressed NFTs. Explore the [Digital Assets API →](/digital-assets-api/introduction)
* **Jito Bundle Simulation:** For advanced traders, we offer support for Jito RPC on all endpoints, giving you access to `simulateBundle` for complex atomic arbitrage and MEV strategies. Learn about [Jito Bundle Simulation →](/trading-apis/bundle-simulation-with-jito)

#### Developer Guides

* **Web3.js Connection Issues:** A guide to diagnosing and fixing common socket and connection errors when using the `@solana/web3.js` library in NodeJS environments. Troubleshoot [Web3.js issues →](/chains/solana/web3js-socket-connection-issues)
* **Solana v2.0 Deprecated Calls:** Stay ahead of network upgrades by reviewing the list of RPC calls that will be deprecated in Solana v2.0 and their modern replacements. See the list of [deprecated calls →](/chains/solana/deprecated-calls-solana-2.0)


# Streaming

Real-time on-chain data is the lifeblood of modern Web3 applications. We offer a suite of powerful streaming solutions, each tailored to a specific use case—from simple frontend notifications to the high-throughput, low-latency demands of MEV searchers and trading bots.

Streaming data from Solana requires a high-bandwidth Internet connection and a fast client environment. Frequent disconnections are a sign of a weak client setup. See our [Streaming Troubleshooting Checklist](/chains/solana/streaming/streaming-troubleshooting-checklist) for helpful tips.

***

#### Dragon's Mouth (gRPC)

**The fastest way to get real-time on-chain data.** Dragon's Mouth is a gRPC interface powered by Geyser, streaming account and transaction updates directly from Solana validators.

* **Best for:** Backend applications, high-frequency trading bots, MEV searchers, and any system where milliseconds matter.
* **Key Feature:** Provides **intra-slot updates**, delivering data up to 400ms faster than standard WebSockets by showing you state changes as they happen, not just at the end of a slot.
* **Note:** gRPC is not supported in web browsers and is intended for server-to-server communication.

**Learn more about** [**Dragon's Mouth gRPC →**](/project-yellowstone/dragons-mouth-grpc-subscriptions)

***

#### Whirligig (WebSockets)

**A better, faster, stronger WebSocket.** Whirligig is a high-performance, drop-in replacement for Solana's standard WebSocket API, built on top of our Dragon's Mouth gRPC streams.

* **Best for:** Web3 frontends, real-time user interfaces, and easily upgrading existing applications that already use WebSockets.
* **Key Feature:** Offers the same **intra-slot update** advantages as gRPC but over a browser-compatible WebSocket connection, making your dApp feel significantly more responsive.

**Learn more about** [**Whirligig WebSockets →**](/project-yellowstone/whirligig-websockets)

***

#### Fumarole (Reliable Streams)

**A persistent, "never miss an event" streaming solution.** Fumarole is designed for applications where guaranteed data delivery is absolutely critical.

* **Best for:** Accounting systems, compliance tools, analytics platforms, and any application that needs a complete and verifiable history of events.
* **Key Feature:** Allows your client to disconnect and reconnect to the stream at a later time, automatically backfilling any data that was missed. It also supports horizontal scaling via persistent subscribers.

**Learn more about** [**Fumarole Reliable Streams →**](/project-yellowstone/fumarole)


# Streaming Troubleshooting Checklist

If you’ve started experiencing Dragon’s Mouth gRPC disconnects recently, this page is for you. Solana’s average daily TPS has almost doubled since December, and the full-chain feed can now spike to \~1.3-1.8 Gbps.

Use this checklist to confirm that your environment can keep up with the current Solana throughput.\
If some answers are “no”, that’s ok, we just want you to get the full picture before contacting Triton support.

***

* #### My subscriber host has at least 5 Gbps of network capacity to the internet

  5 Gbps is strongly recommended for large or full-chain subscriptions. In cloud environments, instance types are often capped at 1 Gbps by default
* #### My round-trip latency from the subscriber to the Triton endpoint is ≤ 50 ms

  Lower latency gives you more headroom for variability in network conditions. Measure it from the same machine that runs your subscriber client, using `ping`
* #### Zstd compression is enabled

  Without compression, bandwidth requirements increase significantly, and it becomes much harder to stay on the tip of the blockchain during spikes
* #### Adaptive window size is set to true

  This setting allows the client to adjust flow-control window sizes automatically based on throughput
* #### No cloud Lambdas!

  Cloud Lambdas, like AWS, do not play well with streaming connections. They can leave many open connections on the servers, which can degrade service.
* #### I'm not using vanilla NodeJS

  Vanilla NodeJS is too slow for Solana and requires a special client library. Use Rust, Golang, or the special NodeJS/TypeScript client discussed on our [gRPC Subscriptions](/chains/solana/streaming#dragons-mouth-grpc) page.
* #### I’ve tested my stream capacity with the test client

  Use the client-ubuntu tool described on the [gRPC Subscriptions](/chains/solana/streaming#dragons-mouth-grpc) page to benchmark your streaming setup. If it outputs a ping message every 10 seconds and you get a total between 60-80 Mbps, it means your setup is keeping up great, and you can stay on the chain’s tip without any disconnections.\
  \
  Can be run using:\
  `./client-ubuntu-22.04 --http2-adaptive-window true ---compression zstd --endpoint http://aaa.mainnet.rpcpool.com --x-token <token> subscribe --transactions --accounts --stats`
* #### I’ve considered other Triton streaming options for my workload

  If you’re using Dragon’s Mouth, we assume your workload prioritises lowest latency over absolute data completeness, persistence, and redundancy.<br>

  If you’d prefer the guarantees over speed, for example, if you run accounting, analytics, indexing, or compliance workloads, consider using Fumarole (beta) instead of raw gRPC.


# Preconfirmations gRPC

Triton Preconfs streams preconfirmed Solana transactions over gRPC, from the two block production systems that emit them: Harmonic and BAM.

### What a preconfirmation is

On Solana one validator is the [leader](https://docs.anza.xyz/consensus/leader-rotation) for each slot and builds the block for it. A transaction you send travels to that leader; the leader executes it, packs it into entries, splits the entries into shreds and broadcasts them; every other node receives the shreds, replays the block and only then reports the transaction, first at `processed` [commitment](https://solana.com/docs/rpc#configuring-state-commitment), later at `confirmed` and `finalized`. Everything you can observe through an RPC node, a WebSocket or a Geyser stream sits at the end of that path.

A preconfirmation is a message emitted at the start of it. The party assembling the block, the builder or the leader itself, tells you the moment a transaction has been executed or committed into the slot under construction, before any shred exists. You learn what the block will contain while it is still being built. The message carries the raw transaction bytes exactly as the block will, plus the slot and, depending on the feed, the execution outcome and the position in the block.

A preconfirmation is a statement by the block producer, not a confirmation by the cluster. The slot can still be skipped, the block can land on a fork that is abandoned, and a builder can restart a slot. A preconfirmed transaction almost always lands, but "almost" is the operative word: act on it as the earliest possible signal, and treat `confirmed` and `finalized` as the settlement they are.

### How each feed produces one

[**Harmonic**](https://docs.harmonic.gg/) runs block builders next to leaders that run the Harmonic validator client. Transactions reach the builder, which executes them and fixes their order for the block. As it does so it streams the executed transactions to subscribers in batches, numbered from zero within each slot and delivered in order, with a start and an end marker per slot. Each transaction carries the outcome the builder observed: success, execution failure (committed, fees charged, state reverted) or fees only (failed to load, only the fee is charged). The leader then executes the same transactions in the same order; Harmonic guarantees the resulting state is equivalent to executing them serially in batch order, so the outcome you receive is the outcome that lands. A Harmonic preconfirmation therefore means: this transaction was executed with this result and sits in the block being built.

[**BAM**](https://bam.dev/docs/bam/bam-overview/), Jito's Block Assembly Marketplace, sits between transaction senders and leaders that run the BAM validator client. A BAM node schedules the transactions it receives, first in first out with respect to the accounts they lock, and hands them to the leader; the leader executes them and acknowledges each one back to the node, which streams it to subscribers. Each transaction carries the node it came through, its position in the scheduler's sequence (transactions sharing a sequence were bundled together), its position inside that bundle, and whether the bundle reverts on error. BAM does not report an execution outcome: a BAM preconfirmation means this transaction was committed by the leader into the slot; whether it succeeded is only visible once the block lands.

Triton subscribes to both systems in every region they publish from and delivers the same bytes to you over one endpoint, filtered to the accounts and signatures you ask for.

#### Harmonic and BAM side by side

|                          | Harmonic                                             | BAM                                                      |
| ------------------------ | ---------------------------------------------------- | -------------------------------------------------------- |
| who produces the preconf | the block builder, before the leader executes        | the BAM node, after the leader executes and acknowledges |
| what it asserts          | executed with this outcome, in the block being built | committed by the leader into the slot                    |
| execution outcome        | success, execution failure, fees only                | not reported                                             |
| ordering information     | batch number within the slot                         | scheduler sequence, bundle position, revert on error     |
| slot framing             | `SlotStart` and `SlotEnd` per slot                   | none, each transaction names its slot                    |
| regions                  | 7                                                    | 15                                                       |

### What the feeds cover, and what they do not

The questions we get most often come from expecting the feeds to be a copy of the chain. They are not. Three things decide what a stream carries.

**Only slots led by a validator running that client.** Harmonic preconfirms the slots of leaders on the Harmonic client; BAM preconfirms the slots of leaders on the BAM client. A slot led by any other validator has no preconfirmation on either feed, from any region. The share of slots each feed covers follows the stake running its client and changes as validators switch. Neither feed sees the other's slots, so subscribing to both is how you cover the most slots.

**Static account keys only.** Filters match the accounts written in the transaction message. Accounts a v0 transaction reaches through an address lookup table are not matched; legacy and v1 transactions carry every account inline and are matched in full. This is the single most common reason a filter sees fewer transactions than expected; see Address lookup tables below.

**One region, one stream, and only the regions near you.** Each regional stream carries the slots of the leaders that region serves, not a copy of the whole feed. A far region's preconfirmations arrive after the people located there have acted on them. See Choosing regions.

### Endpoint and access

`https://preconfs.rpcpool.com`

The address is anycast: the connection lands on the closest point of presence, one of the servers behind that address; every point of presence serves every region of both feeds.

#### Token

Every request, `GetVersion` included, carries an `x-token`: the token of a subscription that has Harmonic preconfs, BAM preconfs or both enabled. It is the same token you use for other Triton gRPC services on that subscription. Send the token's value, not its name.

Enabling a feed on a subscription reaches the servers within about a minute. Until then, and for a token whose subscription does not have that feed, subscribing is refused with `UNAUTHENTICATED` and the message `x-token not entitled to this feed`. Turning a feed off closes the open streams of that token with `PERMISSION_DENIED`.

#### Which point of presence answered

`GetVersion` returns the server version and the point of presence that took your connection, and needs the token like every other call:

```
grpcurl -import-path . -proto preconfs.proto -H "x-token: $TOKEN" \
  preconfs.rpcpool.com:443 preconfs.Harmonic/GetVersion
{
  "version": "0.1.13",
  "region": "fra1"
}
```

The same call is `Client::version()` in the Rust client.

#### Client

The Rust client and the protobuf definitions are on [GitHub](https://github.com/rpcpool/preconfs-client) as the `triton-preconfs-client` and `triton-preconfs-proto` crates. Other languages use the proto directly, see Other languages.

### Quick start

Add the client:

```toml
[dependencies]
triton-preconfs-client = "0.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

Subscribe to Harmonic in Amsterdam for transactions touching the SPL token program and print what arrives:

```rust
use solana_pubkey::Pubkey;
use triton_preconfs_client::{Connector, Event, Feed, Filter, Filters, Region, parse};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Connector::new("https://preconfs.rpcpool.com")
        .x_token(Some(std::env::var("PRECONFS_TOKEN")?))
        .connect()
        .await?;

    let token_program: Pubkey = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".parse()?;
    let region = Region::parse(Feed::Harmonic, "ams")?;
    let filters = Filters::single(Filter::new().accounts([token_program]));

    let mut stream = client.subscribe_harmonic(region, filters).await?;
    while let Some(event) = stream.next().await {
        match event? {
            Event::Transaction(matched) => {
                let signature = parse::parse_signature(&matched.transaction.transaction)?;
                println!("slot {} {signature}", matched.transaction.slot);
            }
            Event::SlotEnd { slot } => println!("slot {slot} complete"),
            Event::Reconnected { attempts } => println!("reconnected after {attempts} attempts"),
            _ => {}
        }
    }
    Ok(())
}
```

The same shape works for BAM with `Feed::Bam` and `subscribe_bam`.

#### The example CLI

The repository ships `preconfs-subscribe`, which subscribes and logs every event:

```
cargo run -p preconfs-example -- --x-token $TOKEN --region harmonic:ams \
    --account TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
```

`--region bam:fra`, `--require`, `--signature`, `--result` and `--no-reconnect` cover the other options; `--help` lists them.

#### Trying it with grpcurl

With `preconfs.proto` from the repository in the current directory:

```
grpcurl -import-path . -proto preconfs.proto \
  -H "x-token: $TOKEN" \
  -d '{"harmonic_region":"HARMONIC_REGION_FRA","transactions":{"t":{"account_include":["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"]}}}' \
  preconfs.rpcpool.com:443 preconfs.Harmonic/Subscribe
```

For BAM call `preconfs.BAM/Subscribe` with `"bam_region":"BAM_REGION_FRA"`.

### Feeds and regions

Three things carry the word region; they are not the same thing.

```
Solana leaders                        Solana leaders
      │                                     │
      ▼                                     ▼
Harmonic builders                       BAM nodes
ams ewr fra lon tyo sgp slc             ams dfw dub ewr fra hkg iad lax
                                        lon pit sea sin slc sqq tyo
      │                                     │
      └─────────────────┬───────────────────┘
                        ▼
          Triton points of presence
          ams1 fra1 lon1 nyc1 ...   each relays every region
                        │
                        ▼
          https://preconfs.rpcpool.com   anycast picks the closest
                        │
                        ▼
          your stream: one feed, one region
```

**Feed regions** are the ones in the proto (`HarmonicRegion`, `BamRegion`) and in the subscribe request. Harmonic runs a block builder in each of its regions and BAM runs a node in each of its; a leader is served by one of them, and that location's stream carries the slots of the leaders it serves. The regional streams are not copies of each other: `harmonic:ams` and `harmonic:fra` deliver different slots. Subscribe to the ones near your servers; see Choosing regions.

**Origin on each transaction**: `region` on a Harmonic transaction and `node` on a BAM transaction name the builder or node it came through. On a single region stream it matches what you subscribed; it is there so transactions stay self describing when you merge streams.

**Points of presence** are Triton's servers, named after their site (`ams1`, `fra1`, `lon1`, `nyc1` and so on). The anycast address lands you on the closest one and every point of presence relays every feed region, so where you connect never limits what you can subscribe to. `Client::version` returns the one that answered in its `region` field.

A stream serves one feed in one region. A region of the other feed is refused.

#### Harmonic

The builder executes the transaction and reports the outcome. Streams are framed per slot (see The stream) and carry an execution result on every transaction, so `execution_results` filters are accepted.

| region | location          |
| ------ | ----------------- |
| `ams`  | Amsterdam         |
| `ewr`  | New York (Newark) |
| `fra`  | Frankfurt         |
| `lon`  | London            |
| `tyo`  | Tokyo             |
| `sgp`  | Singapore         |
| `slc`  | Salt Lake City    |

#### BAM

The leader commits the transaction; no outcome is reported and there is no slot framing. Each transaction names its slot.

| region | location             |
| ------ | -------------------- |
| `ams`  | Amsterdam            |
| `dfw`  | Dallas               |
| `dub`  | Dublin               |
| `ewr`  | New York (Newark)    |
| `fra`  | Frankfurt            |
| `hkg`  | Hong Kong            |
| `iad`  | Washington (Ashburn) |
| `lax`  | Los Angeles          |
| `lon`  | London               |
| `pit`  | Pittsburgh           |
| `sea`  | Seattle              |
| `sin`  | Singapore            |
| `slc`  | Salt Lake City       |
| `sqq`  | Siauliai             |
| `tyo`  | Tokyo                |

#### Choosing regions

Pick the regions near you. Do not subscribe to all of them.

A preconfirmation is worth what its lead over the landed block is worth, and that lead is spent on the wire. Cables do not run in straight lines: a packet from Tokyo to Frankfurt takes around 250 ms on a normal route. A slot preconfirmed in Tokyo therefore reaches a server in Frankfurt a quarter of a second after it reached a server in Tokyo. By then a trader sitting in Tokyo has acted on it, and the block itself has long been broadcast. For a far region you pay for the stream and still come second every time. This is not a limit we impose; it is geography, and it is the reason the feeds publish per region in the first place.

So: subscribe to the regions whose builders and nodes sit in your own data centre or metro, one stream per region. Someone in Frankfurt subscribes to `harmonic:fra` and `bam:fra`, maybe `ams` and `lon` next to them; someone in New York to `ewr`, maybe `iad` and `pit`. Regions elsewhere only make sense for a server placed there. The streams of the regions you pick share one connection.

If you do open several, merge them. Each stream carries different slots, so there is nothing to deduplicate between regions of the same feed, and framing is per stream: a `SlotEnd` on `ams` says nothing about `fra`. In Rust, `select_all` polls whichever region has an event ready:

```rust
use futures::stream::{StreamExt, select_all};
use solana_pubkey::Pubkey;
use triton_preconfs_client::{Connector, Event, Feed, Filter, Filters, Region, parse};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Connector::new("https://preconfs.rpcpool.com")
        .x_token(Some(std::env::var("PRECONFS_TOKEN")?))
        .connect()
        .await?;

    let token_program: Pubkey = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".parse()?;
    let filter = Filter::new().accounts([token_program]);

    // The regions next to this server, one stream each, on one connection,
    // tagged with the region name so the merged output stays self describing.
    let mut streams = Vec::new();
    for name in ["fra", "ams", "lon"] {
        let region = Region::parse(Feed::Harmonic, name)?;
        let stream = client
            .subscribe_harmonic(region, Filters::single(filter.clone()))
            .await?;
        streams.push(stream.map(move |event| (name, event)).boxed());
    }

    let mut merged = select_all(streams);
    while let Some((region, event)) = merged.next().await {
        match event? {
            Event::Transaction(matched) => {
                let signature = parse::parse_signature(&matched.transaction.transaction)?;
                println!("{region} slot {} {signature}", matched.transaction.slot);
            }
            Event::SlotEnd { slot } => println!("{region} slot {slot} complete"),
            Event::Clip { transactions } => println!("{region} clipped {transactions}"),
            Event::Reconnected { attempts } => println!("{region} reconnected after {attempts}"),
            Event::SlotStart { .. } => {}
        }
    }
    Ok(())
}
```

`futures = "0.3"` provides `select_all`. The same shape with `Feed::Bam` and `subscribe_bam` covers the BAM regions near you; the two feeds have different event types, so merge them separately or map both into your own type first. Each stream reconnects on its own, so a point of presence restart shows up as one `Reconnected` per region.

Two more things to know when you open several streams:

* They all draw on your account's coverage share (see Coverage). A wide filter across many regions reaches the share quickly; the share is meant for filters that select what you actually use.
* The concurrent stream limit is 1000 per account, far above what any placement needs.

#### In the client

```rust
let region = Region::parse(Feed::Harmonic, "ams")?;
let region: Region = "bam:fra".parse()?;
Feed::Bam.regions(); // the names above
```

To pin one point of presence instead of letting anycast choose, `Connector::dial("host:port")` opens the TCP connection to that address while TLS keeps the endpoint's host name. `Client::version` returns the server version and the region of the point of presence answering.

### Filters

A subscribe request carries one or more named filters. A transaction is delivered when it matches at least one, and every delivered transaction names the filters it matched.

A transaction matches a filter when it satisfies every condition the filter sets:

| condition           | matches when the transaction                     |
| ------------------- | ------------------------------------------------ |
| `account_include`   | references any of these accounts                 |
| `account_required`  | references all of these accounts                 |
| `signatures`        | has one of these first signatures                |
| `execution_results` | landed with one of these outcomes, Harmonic only |

Every filter must set at least one of the first three; the full feed cannot be subscribed.

"References" means the account is a static account key of the transaction message. Accounts loaded through address lookup tables do not count, see What the feeds cover.

#### account\_exclude

There is no `account_exclude` today. Filters are positive selections: every filter names the accounts or signatures it wants, so a subscription cannot ask for the whole feed minus a few accounts. To narrow a selection, combine `account_include` with `account_required` (the transaction must reference all of these), or on Harmonic with `execution_results`, and drop what you do not want on your side.

The proto reserves the field for it, and `account_exclude` as a further condition on a positive filter (include A, minus B) is planned. It will not allow subscribing to everything except some accounts.

#### Limits

| limit                                                     | value    |
| --------------------------------------------------------- | -------- |
| filters per stream                                        | 64       |
| accounts per `account_include` or `account_required` list | 10000    |
| signatures per filter                                     | 1000     |
| filter name                                               | 64 bytes |
| concurrent streams per account                            | 1000     |

The client checks the first four before sending, so a request over a limit fails locally with a `FilterError`.

#### In the client

```rust
let filters = Filters::new()
    .with("token", Filter::new().accounts([token_program]))
    .with("mine", Filter::new().accounts([token_program]).require([my_account]))
    .with("landed", Filter::new().accounts([my_account]).execution_results([ExecutionResult::Success]));
```

`Filters::single(filter)` names a lone filter `default`.

### Address lookup tables

Solana has three transaction message formats, and only one of them hides accounts:

| format                                                                                                                                                 | accounts                                                                                                                           | lookup tables |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| legacy                                                                                                                                                 | every account written in the message                                                                                               | no            |
| v0                                                                                                                                                     | static accounts in the message, plus indexes into on-chain [address lookup tables](https://solana.com/docs/advanced/lookup-tables) | yes           |
| v1 ([SIMD-0385](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0385-v1-transaction-format.md), the 4 KB format) | every account written in the message, up to 64                                                                                     | no            |

For legacy and v1 transactions the bytes name every account, so a filter sees all of them. The gap is v0: its bytes carry only the table address and an index for each looked-up account, and turning those into addresses means reading the table's current state on chain.

Preconfirmations are the raw transaction bytes, emitted before the block exists. Neither Harmonic nor BAM resolves v0 lookup tables before streaming, and Triton does not resolve them on the way through: it would mean an account lookup per transaction on the hot path, and the whole point of the feed is the milliseconds it is ahead. So for v0 transactions, filters match the static keys only. The client parses all three formats; `parse::parse_static_parts` returns exactly the keys a filter sees.

What this means in practice:

* Program ids and well known accounts are almost always static keys, in every format. A filter on the SPL token program, a DEX program or pump.fun matches every transaction that invokes it.
* Pools, positions, vaults and user token accounts in v0 transactions are often loaded through tables, especially from aggregators and bots. A filter on such an account misses the v0 transactions that reach it through a table. Legacy and v1 transactions touching the same account match normally.
* The same v0 transaction, seen through Geyser or Deshred after the block lands, has its tables resolved. That is why a side by side comparison of "transactions touching account X" shows more on the landed stream than on the preconf stream.

If your accounts are mostly behind tables, filter on the program and on the static accounts that always accompany your transactions, and select the rest on your side. Both feed operators have said they intend to add resolution upstream, where it costs no latency; when they do, filters will match resolved accounts without any change on your side. As v1 adoption grows the gap closes on its own, since v1 has no tables.

### The stream

`subscribe_harmonic` and `subscribe_bam` return a stream of `Event`s.

| event                      | meaning                                                                                                                     |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `SlotStart { slot }`       | a leader began streaming preconfs for this slot (Harmonic)                                                                  |
| `Transaction(matched)`     | a matching transaction; `matched.filters` names the filters, `matched.transaction` is the feed's message with the raw bytes |
| `SlotEnd { slot }`         | no further transactions for this slot will arrive (Harmonic)                                                                |
| `Clip { transactions }`    | matching transactions were withheld, see Coverage                                                                           |
| `Reconnected { attempts }` | the stream dropped and was resubscribed; the data produced in between is gone                                               |

Pings are consumed by the stream.

#### Slot framing

On Harmonic every transaction sits between its slot's `SlotStart` and `SlotEnd`. After `SlotEnd` for a slot you hold everything your filters matched for it. A stream that subscribes while a slot is open joins at the next `SlotStart`, so the first slot you see is always complete. Slot boundaries arrive whether or not your filters matched anything in that slot.

Rarely a leader restarts a slot: a second `SlotStart` for a slot that already ended, followed by its definitive transactions and a new `SlotEnd`. Rebuild your view of that slot from the new frame; the last `SlotEnd` wins.

BAM has no framing. Each transaction names its slot.

#### Transaction bytes

`matched.transaction.transaction` holds the raw transaction bytes. The client parses what filtering needs without a full decode:

```rust
let signature = parse::parse_signature(&bytes)?;
let (signature, account_keys) = parse::parse_static_parts(&bytes)?;
```

Legacy, v0 and v1 message formats are supported.

#### Transaction fields

Harmonic (`HarmonicTransaction`):

| field         | meaning                                                               |
| ------------- | --------------------------------------------------------------------- |
| `transaction` | raw transaction bytes                                                 |
| `slot`        | slot the transaction was preconfirmed in                              |
| `result`      | the builder's outcome: success, execution failure or fees only        |
| `region`      | Harmonic region it was received from                                  |
| `seq`         | preconf batch within the slot, numbered from 0 and delivered in order |

BAM (`BamTransaction`):

| field                | meaning                                                                             |
| -------------------- | ----------------------------------------------------------------------------------- |
| `transaction`        | raw transaction bytes                                                               |
| `slot`               | slot the transaction was preconfirmed in                                            |
| `node`               | BAM node it was received from                                                       |
| `sequence`           | scheduler order on that node; transactions sharing a sequence were bundled together |
| `bundle_position`    | execution order within that bundle                                                  |
| `is_revert_on_error` | the bundle reverts on error (bundle intent, not an outcome)                         |

#### Slow consumers

A stream that cannot keep up ends with an explicit `RESOURCE_EXHAUSTED` status. Nothing is ever thinned silently: if you hold the stream open, it is complete for your filters, minus what `Clip` announces.

### Coverage

Each account may receive up to a share of a feed's traffic. This is a condition of the feeds: Harmonic and BAM license preconfirmations for your own use, and the share is what makes the full firehose impossible to obtain or resell through Triton. It is not a rate limit on the server; a narrow filter never hits it.

How it is measured:

* The share is **25%** of what a feed publishes in the region you subscribe, counted over a sliding **60 second** window. Harmonic and BAM are counted separately. The percentage is agreed with the feed operators and can change; this page states the current value.
* It belongs to the **account**, meaning your subscription: every token, connection and stream of the subscription draws on the same share. Opening more streams or rotating filters does not widen it.
* A **burst cap** holds instantaneous delivery to half the feed's current rate, so a spike cannot take the whole window's share in a second.
* Each point of presence measures on its own.

What happens over the share:

1. Matching transactions are withheld. The count arrives as a `Clip` event at most once a second, and on Harmonic always before the affected slot's `SlotEnd`, so a completed slot is never silently short.
2. If the account stays over the share for **30 seconds** without recovering, the stream ends with `RESOURCE_EXHAUSTED` and the message `account exceeded its coverage limit, cooling off`.
3. Subscribing again is refused for **60 seconds**, with the remaining time in the error message. The client's reconnect waits and retries on its own.

Staying under it: subscribe to the accounts you act on rather than to whole programs. A filter on the SPL token program or pump.fun across every region asks for most of the chain and reaches the share within a minute; the same accounts narrowed with `account_required` to your pools or wallets do not. If your use needs a larger share, ask through support; the share can be set per account.

### Billing

Two quantities are counted per subscription and per feed:

| quantity   | counts                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------ |
| `messages` | transactions delivered on your streams                                                           |
| `slots`    | slots your streams were open for, on Harmonic, whether or not a transaction matched your filters |

Harmonic is priced at the greater of a per slot and a per message amount, so the slot count is a floor: a stream open on a Harmonic region pays for every slot that region produces while it is open, matched or empty. BAM is priced per message only.

Not counted as messages: `SlotStart`, `SlotEnd`, pings, `Clip` notices, transactions your filters did not match, and transactions withheld by coverage.

Streams add up. Two streams whose filters overlap receive, and are counted for, the same transactions twice, and two streams on the same Harmonic region are two streams open on those slots. A transaction matching several filters on one stream is delivered and counted once; reconnecting within a slot does not count the slot again on the same point of presence.

Prices per message and per slot for each feed are on your subscription in the customer panel, together with the daily usage.

### Errors and reconnect

#### Error types

| type             | from                                  | when                                                                                                                                               |
| ---------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ConnectError`   | `Connector::connect`, `connect_lazy`  | bad endpoint URI, token not ascii, TLS setup, connection refused or timed out                                                                      |
| `SubscribeError` | `subscribe_harmonic`, `subscribe_bam` | region of the wrong feed, filters over a limit, or the server refused the subscribe (bad token, region not served, feed not entitled, cooling off) |
| `StreamError`    | a stream item                         | the server ended the stream with a status, or closed it                                                                                            |

`RegionError`, `FilterError` and `ParseError` are the smaller types behind them. For one top level type wrap them with `anyhow` or `Box<dyn Error>`.

#### Status codes

| status                | meaning                                                                                     | what to do                                               |
| --------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `UNAUTHENTICATED`     | no `x-token`, unknown token, or the subscription does not have this feed enabled            | check the token value and that the feed is enabled       |
| `PERMISSION_DENIED`   | the feed was disabled on the subscription while the stream was open                         | the stream is closed; re-enable the feed                 |
| `INVALID_ARGUMENT`    | region missing, unspecified or of the other feed; a filter over a limit or with no selector | fix the request                                          |
| `FAILED_PRECONDITION` | the region is not served by this deployment                                                 | use one of the regions listed above                      |
| `RESOURCE_EXHAUSTED`  | over the coverage share for 30 seconds, still cooling off, or the client cannot keep up     | narrow the filters, read faster, retry after the cooloff |
| `DATA_LOSS`           | the client fell so far behind that the server dropped its position                          | reconnect; the gap is gone                               |
| `UNAVAILABLE`         | the point of presence is restarting                                                         | reconnect; anycast lands on another                      |

#### Reconnect

Points of presence restart on every deploy, so a long lived stream will drop. By default the stream resubscribes with the same request after a backoff and yields `Event::Reconnected`. Preconfs produced in between are gone; on Harmonic, framing restarts at the next `SlotStart`.

Errors that retrying cannot fix end the stream instead: `Unauthenticated`, `PermissionDenied`, `InvalidArgument`, `FailedPrecondition`, `NotFound`, `Unimplemented`. `Unavailable`, `DataLoss`, `ResourceExhausted`, `Internal`, `Aborted`, `DeadlineExceeded` and a closed stream are retried.

The default schedule waits 100ms, then doubles up to 10s between attempts, and never gives up. Tune or disable it on the connector:

```rust
use std::time::Duration;
use triton_preconfs_client::{Connector, Reconnect};

Connector::new(endpoint).reconnect(Reconnect {
    initial_interval: Duration::from_millis(250),
    multiplier: 2.0,
    max_interval: Duration::from_secs(30),
    max_retries: Some(20),
});

Connector::new(endpoint).no_reconnect();
```

With reconnect off the stream yields the error and ends.

### Other languages

The wire contract is `preconfs.proto` in the repository (`preconfs-proto/proto/preconfs.proto`); the same file is exported by the Rust proto crate as `PROTO_SOURCE`. Generate a client for your language from it.

#### Calls

| service                 | rpc                                                           |                                                              |
| ----------------------- | ------------------------------------------------------------- | ------------------------------------------------------------ |
| `preconfs.Harmonic`     | `Subscribe(SubscribeRequest) returns (stream HarmonicUpdate)` | the Harmonic stream                                          |
| `preconfs.Harmonic`     | `GetVersion(VersionRequest) returns (VersionResponse)`        | server version and point of presence region; needs the token |
| `preconfs.BAM`          | `Subscribe(SubscribeRequest) returns (stream BamUpdate)`      | the BAM stream                                               |
| `preconfs.BAM`          | `GetVersion(VersionRequest) returns (VersionResponse)`        |                                                              |
| `grpc.health.v1.Health` | `Check`                                                       | standard health service                                      |

#### Authentication

Send the token as gRPC metadata on every call, `GetVersion` included: key `x-token`, value the token. A token that is not entitled to at least one feed is refused with `UNAUTHENTICATED`.

#### Request

`SubscribeRequest` has two fields:

* `transactions`: a map from filter name to `TransactionFilter` (`account_include`, `account_required`, `signature`, `execution_results`; accounts and signatures as base58 strings).
* `region`: exactly one of `harmonic_region` or `bam_region`, matching the service called. Unspecified or the other feed's region is `INVALID_ARGUMENT`; a region the server does not serve is `FAILED_PRECONDITION`.

The limits in Filters apply; a request over them is `INVALID_ARGUMENT`.

#### Updates

`HarmonicUpdate` and `BamUpdate` carry `filters` (the names that matched, empty for slot boundaries and pings) and one payload: `transaction`, `ping`, `clip`, and on Harmonic `slot_start` and `slot_end`. The contract in The stream holds as described; pings arrive on quiet streams and can be ignored.

Transactions carry the raw bytes in `transaction`. The first signature is the 64 bytes after the compact-u16 signature count.

### Questions we get asked

**I filter on my accounts and see far fewer transactions than Geyser shows for the same accounts.** Two causes, in this order. Accounts a v0 transaction reaches through address lookup tables are not matched, only static keys are; see Address lookup tables. And only slots led by validators on the Harmonic or BAM client exist on the feeds; other leaders' slots have no preconfirmation anywhere. See What the feeds cover.

**Can I get the whole feed?** No. Every filter must select accounts or signatures, and the coverage share caps what one account receives. This is the condition under which the feeds are offered.

**Why was my stream closed with "cooling off" within a minute?** Wide filters, usually a whole program across many regions, took more than the share of the feed. Narrow them with `account_required`, drop the regions you cannot act on in time, and resubscribe after the cooloff. See Coverage.

**Is there an account\_exclude like other providers have?** Not yet. It is planned as an extra condition on a positive filter, never as a way to get everything except some accounts. Until then narrow with `account_required`. See account\_exclude.

**Should I subscribe to all regions?** No. A preconfirmation from a far region reaches you after the people located there have acted on it, so it is paid for and always late. Subscribe to the regions next to your servers, one stream each, and place servers where you need other regions. See Choosing regions.

**Which point of presence am I on?** `GetVersion` returns it. Anycast picks the closest; `Connector::dial` pins one.

**Am I billed for slots with no matching transactions?** On Harmonic yes: the slot fee is a floor for every slot your stream is open on. Pings and slot markers are not messages. See Billing.

**Does the `execution_results` filter cost anything?** No. Transactions it filters out are never delivered and never counted.

**Does a BAM subscription elsewhere give me BAM preconfs here?** No. Access is per Triton subscription; the feed is enabled on your subscription and used with your Triton token.

**Where exactly are the builders and nodes?** In the cities listed under Feeds and regions. The data centre is the feed operator's; Triton's points of presence sit close to them, and the region you subscribe is what decides the slots you get, not where you connect.


# Geyser

We are happy to configure dedicated Geyser nodes.&#x20;

You can see the current set of Geyser plugins that we support here:\
<https://github.com/rpcpool/solana-geyser-park>.

If you want to run a Geyser setup, our recommendation is to have a dedicated node just for Geyser. It is possible to run Geyser and RPC on a single node, but you will face a risk that your RPC calls could put the node behind which would also cause Geyser to fall behind. This could eventually lead to missed Geyser updates.&#x20;

For complete reliability you would need two dedicated nodes. This would enable both failover in case one node goes down but also in cases where we need to upgrade the Solana version or any other regular maintenance of the node.&#x20;

Contact us via your customer support channel if you would like to enable Geyser on your node. We are happy to get on a call to discuss your requirements and experiences of working with Geyser.


# Token Program

There are multiple methods to choose from when querying data from the Token Program.

### Query by mint

You can query all the token accounts by mint using getProgramAccounts.

#### Original token program

When querying the original token program  it is important to specify dataSize as 165 to ensure that you are querying only valid token accounts.

{% tabs %}
{% tab title="getProgramAccounts" %}

```json
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      {
        "filters": [
          {
              "dataSize": 165
          },
          {
            "memcmp": {
              "offset": 0,
              "bytes": "j14XLJZSVMcUYpAfajdZRpnfHUpJieZHS4aPektLWvh"
            }
          }
        ],
        "encoding": "base64"
      }
    ] 
}
```

{% endtab %}
{% endtabs %}

#### Token22

When querying Token 22 you do not add the dataSize parameter but instead filter by the byte on offset 165.

{% tabs %}
{% tab title="getProgramAccounts" %}

```json
{
    "jsonrpc":"2.0",
    "id":1,
    "method":"getProgramAccounts",
    "params":[
        "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
        {
            "filters": [
                {"memcmp": {"offset":0, "bytes": "FRAGJ157KSDfGvBJtCSrsTWUqFnZhrw4aC8N8LqHuoos"}},
                {"memcmp": {"offset":165,"bytes": [2] }
                }
            ],
            "encoding": "base64",
            "commitment":"confirmed"
        }
    ]
}
```

{% endtab %}
{% endtabs %}

### Query by owner

The standard method for querying by owner is [`getTokenAccountsByOwner`](https://solana.com/docs/rpc/http/gettokenaccountsbyowner).  You can also use getProgramAccounts to query the same data, with the added possibiltiy of more advanced filtering.

#### Original token program

{% tabs %}
{% tab title="getTokenAccountsByOwner" %}

```json
{
   "jsonrpc": "2.0",
   "id": 1,
   "method": "getTokenAccountsByOwner",
   "params": [
     "B4aJyVCibxP1J95RccZ57DPJWEfkkzUDbm9DuFD9bonk",
     {
       "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
     },
     {
       "commitment": "finalized",
       "encoding": "jsonParsed"
     }
   ]
}
```

{% endtab %}

{% tab title="getProgramAccounts" %}

```json
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      {
        "filters": [
          {
              "dataSize": 165
          },
          {
            "memcmp": {
              "offset": 32,
              "bytes": "B4aJyVCibxP1J95RccZ57DPJWEfkkzUDbm9DuFD9bonk"
            }
          }
        ],
        "encoding": "base64",
        "commitment": "confirmed"
      }
    ] 
}
```

{% endtab %}
{% endtabs %}

#### Token22

{% tabs %}
{% tab title="getTokenAccountsByOwner" %}

```json
{
   "jsonrpc": "2.0",
   "id": 1,
   "method": "getTokenAccountsByOwner",
   "params": [
     "pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn",
     {
       "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
     },
     {
       "commitment": "finalized",
       "encoding": "jsonParsed"
     }
   ]
}
```

{% endtab %}

{% tab title="getProgramAccounts" %}

```json
{
    "jsonrpc":"2.0",
    "id":1,
    "method":"getProgramAccounts",
    "params":[
        "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
        {
            "filters": [
                {"memcmp": {"offset":32, "bytes": "pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn"}},
                {"memcmp": {"offset":165,"bytes": [2] }
                }
            ],
            "encoding": "base64",
            "commitment":"confirmed"
        }
    ]
}
```

{% endtab %}
{% endtabs %}

### Troubleshooting

A common error you may see when querying Token programs via getProgramAccounts is the following:

```
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA excluded from account secondary indexes; this RPC method unavailable for key
```

If you see this error, please double check that you have included the parameters correctly as per above - especially that you have provided the correct dataSize parameter. If you are still facing errors, please contact us via our customer support channels.

### Digital Assets API

For a higher level API with better filtering functions, you can also use the Digital Assets API. This API supports both traditional tokens as well as compressed NFTs within the same interface, allowing you to query the full set of tokens owned by a user.

[Read more about the Digital Assets API](/digital-assets-api/introduction).


# Archival Data Access

All our Solana endpoints support querying the full history of the blockchain, all the way back to the genesis block. This allows you to look up historical transactions, blocks, and signatures without needing to manage your own archival infrastructure.

#### How It Works

When you make a request for a transaction, our system will route you to our Alpamayo and block-cache ledger backends. \
\
If the data is within the last several epochs, we will serve it directly from these low-latency endpoints. If the data is older than what these store, the request is automatically forwarded to our long-term archival ledger systems, Hydrant and Old Faithful.

#### Hydrant Archive & Pricing

Our Hydrant archive serves as the final step in our automated lookup process for very old data.&#x20;

* **Pricing:** $10.00 per million queries (or a fraction thereof).
* **Minimum Charge:** $10.00 per month if the service is used.
* **Example Queries:** `getTransaction`, `getBlock`, `getSignaturesForAddress` , `getTransactionsForAddress`.

#### Old Faithful (Next-Generation Archive)

Old Faithful is our modern, open-source solution for accessing Solana's historical ledger that provides gRPC streaming of bulk transaction and block data.

&#x20;It is currently available for use via a separate, dedicated path. Please refer to the full [Old Faithful documentation](/project-yellowstone/old-faithful-historical-archive) for details on how to access it today.


# Improved Priority Fees API

#### The Problem with Standard Priority Fees

Solana's default `getRecentPrioritizationFees` RPC method can be difficult to use effectively for setting a competitive priority fee. It only returns the **minimum** priority fee paid in recent blocks, which is often zero and not a useful indicator of the market rate required for a transaction to land.

#### Our Solution: Percentile-Based Fees

To solve this, we have enhanced the `getRecentPrioritizationFees` method with a `percentile` parameter. You can now request a specific percentile (e.g., 50th for the median fee, 90th for a high-end fee) to get a much more realistic and actionable fee estimate based on recent on-chain activity.

> For other RPC providers or users running their own nodes, we provide the patches to enable this functionality and encourage its adoption for a better developer experience. [Learn more →](/chains/solana/improved-priority-fees-api/for-rpc-providers)

#### How to Use the API

The request and response formats are straightforward. The method name remains `getRecentPrioritizationFees`.

**Sample Request**

The `percentile` parameter is an integer between 1 and 10,000, representing a range from 0.01% to 100.00%. For example, `5000` represents the 50th percentile (median).

```json
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "getRecentPrioritizationFees",
  "params": [
    [
      "RNXnAJV1DeBt6Lytjz4wYzvS3d6bhsfidS5Np4ovwZz"
    ],
    {
      "percentile": 5000
    }
  ]
}
```

**Sample Response**

The response format is identical to the standard Solana RPC method, but the prioritizationFee value will reflect the requested percentile instead of the minimum. If no percentile is provided, the method defaults to the original behavior of returning the minimum fee.

```json
{
  "jsonrpc": "2.0",
  "result": [
    {
      "slot": 348126,
      "prioritizationFee": 1000
    },
    {
      "slot": 348127,
      "prioritizationFee": 500
    }
  ],
  "id": 1
}
```

**Client Integration**

Since this is an enhanced method, it may not be available in standard SDKs. We have prepared an example repository with utility functions you can use in your JavaScript/TypeScript codebase to get started quickly.

{% embed url="<https://github.com/rpcpool/solana-prioritization-fees-api/>" %}


# For RPC Providers

If you're an RPC provider or run your own RPC nodes and would like to integrate this feature, here's the patch you'd need to apply to your Solana

{% embed url="<https://github.com/rpcpool/solana-public/tree/v1.17.23-getrpf>" %}

{% embed url="<https://github.com/anza-xyz/agave/issues/3332>" %}

{% embed url="<https://github.com/anza-xyz/agave/pull/217>" %}


# Transaction handling

Solana transaction handling advice.

At Triton One we put a lot of effort into making sure our customers transactions get ingested into the Solana Blockchain. Cascade is our high-performance transaction delivery network for Solana, designed to provide a more reliable and predictable path for your transactions to land on-chain, especially during periods of high network congestion.

#### How It Works

Cascade utilizes Solana's **Stake-Weighted Quality of Service (SWQoS)**, allowing transactions to be sent through the reserved, private connection pools of staked validators. This bypasses the often-congested public TPU ports that standard RPC services use, resulting in a higher delivery success rate.

#### Customizability

* **Best Practices for Sending** To get the most out of Cascade, it's crucial to structure your transactions correctly. Following our [Transaction Sending Advice](/chains/solana/cascade/sending-txs) for client-side retries, compute budgets, and priority fees will significantly improve performance.
* **Yellowstone Shield Integration** Cascade is fully integrated with [Yellowstone Shield](/project-yellowstone/shield-transaction-policies), allowing you to apply on-chain policies to control exactly which validators are allowed to process your transactions, protecting you from MEV and other risks.
* **Send Transactions over HTTP** For faster transaction submission, you can now send your transactions over HTTP without JSON RPC overhead by using `sendTX` instead of `sendTransaction`. Learn more in the [Transaction Submission API](https://docs.triton.one/chains/solana/cascade/transaction-submission) docs.


# Transaction Submission API

Direct HTTP endpoint for submitting Solana transactions through Cascade without JSON-RPC overhead.

Triton's Cascade-enabled Solana endpoints support a direct HTTP transaction submission path that bypasses the JSON-RPC layer entirely:

```
POST /sendtx
```

This endpoint is designed for latency-sensitive workloads where every millisecond of overhead matters. It accepts a plain transaction payload over HTTP and eliminates several sources of latency present in a standard `sendTransaction` JSON-RPC call.

#### Why use `/sendtx` instead of `sendTransaction`?

The standard Solana `sendTransaction` RPC method wraps your transaction in a JSON-RPC envelope, which adds overhead at every stage of the request. The `/sendtx` endpoint removes that overhead.

* **No JSON parsing.** The server receives your transaction bytes directly, skipping JSON deserialization.
* **No CORS preflight.** When using `Content-Type: application/octet-stream` or `text/plain`, browsers skip the preflight `OPTIONS` request. That saves a full round-trip.
* **Smaller payloads.** Without the JSON-RPC wrapper (`jsonrpc`, `id`, `method`, `params`), the request body is smaller on the wire.
* **Simpler client code.** You don't need a Solana JSON-RPC client library. A single HTTP POST is all it takes.

This makes `/sendtx` a good fit for **browser-based applications** that are sensitive to preflight latency and **high-frequency backends** that send large volumes of transactions.

If you need a transaction signature returned in the response, use the `response=signature` query parameter. Otherwise, track signatures client-side before submitting. The signature is deterministic and can be derived from the signed transaction before it is sent.

{% hint style="info" %}
If you prefer the standard Solana RPC interface or need full `sendTransaction` options like `skipPreflight`, you can continue using `sendTransaction` as normal. See our [Transaction sending advice](/chains/solana/cascade/sending-txs) for best practices.
{% endhint %}

#### Request format

**Method:** `POST`\
**Path:** `/sendtx`

The request body should contain your serialized transaction. You can submit it in one of two ways:

* **Raw bytes.** Set `Content-Type: application/octet-stream` and send the transaction as a binary payload.
* **Encoded string.** Set `Content-Type: text/plain` and send the transaction as a text body (base58 or base64). Use the `encoding` query parameter to indicate the format.

#### Query parameters

| Parameter     | Values             | Required    | Description                                                                 |
| ------------- | ------------------ | ----------- | --------------------------------------------------------------------------- |
| `encoding`    | `base58`, `base64` | No          | Encoding format when sending the transaction as text. Defaults to `base58`. |
| `response`    | `signature`        | Recommended | When set, the response body contains the transaction signature on success.  |
| `max_retries` | integer            | No          | Override the default retry count for this transaction.                      |

#### Optional headers

| Header                      | Description                                                                                                                       |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `solana-forwardingpolicies` | Comma-separated [Yellowstone Shield](/project-yellowstone/shield-transaction-policies) policy addresses to apply when forwarding. |

#### Examples

**Raw bytes**

```bash
curl -X POST 'https://<your-endpoint>/sendtx?response=signature&max_retries=3' \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @transaction.bin
```

**Base64-encoded transaction**

```bash
curl -X POST 'https://<your-endpoint>/sendtx?encoding=base64&response=signature' \
  -H 'Content-Type: text/plain' \
  -d '<base64-encoded-transaction>'
```

**Base64 with a Yellowstone Shield forwarding policy**

```bash
curl -X POST 'https://<your-endpoint>/sendtx?encoding=base64&response=signature' \
  -H 'solana-forwardingpolicies: <policy-address>' \
  -d '<base64-encoded-transaction>'
```

#### Response

**On success:**

* HTTP `200 OK`
* If `response=signature` was set, the body contains the transaction signature as plain text.
* If `response=signature` was not set, the body is empty. Derive the signature client-side from your signed transaction before submitting.

**On error:**

* HTTP `4xx` or `5xx`
* The response body contains error details describing what went wrong.

#### Notes

* `/sendtx` is for transaction submission only. It does not support simulation or other RPC methods.
* For best results, follow our [Transaction sending advice](/chains/solana/cascade/sending-txs) for client-side retries, compute budgets, priority fees, and preflight handling.
* If you are using Yellowstone Shield policies, see the [Shield documentation](/project-yellowstone/shield-transaction-policies) for configuration details.


# Transaction sending advice

Recommendations to optimize transaction delivery through our Cascade network.

Sending transactions effectively on Solana requires a client-side strategy that accounts for network congestion and leader schedules. Following these best practices will significantly increase your transaction inclusion rate and create a better experience for your users.

{% hint style="success" %}
**TL;DR: Quick Summary**

* **Handle retries in your own code.** Do not rely on the RPC node to retry for you.
* When sending, set **`maxRetries: 0`**.
* If you need simulations, **do it separately** via `simulateTransaction()` before sending.
* When sending, set **`skipPreflight: true`**.
* Use a recent blockhash from a **`finalized`** or **`confirmed`** commitment level. Never use processed for blockhashes.
* Set a [**tight Compute Unit (CU) budget** and a **competitive priority fee**](#id-3.-set-precise-compute-budgets-and-priority-fees).&#x20;
  {% endhint %}

***

#### 1. Handle Retries in Your Client

Previously, RPC nodes would queue and retry transactions on behalf of the user. During periods of high traffic, this system creates backpressure, saturating the queues and causing widespread transaction failures.

The modern best practice is to manage the retry logic entirely within your own application. This gives you full control over the user experience and leads to more predictable transaction delivery.

* **Your Action:** Build your own asynchronous retry logic (e.g., re-fetching a recent blockhash and re-signing every few seconds).
* **RPC Parameter:** Always include **`maxRetries: 0`** in your `sendTransaction` options. This tells our RPC node not to use its legacy retry queue.

***

#### 2. Simulate First, Then Send

Our Cascade delivery network has more pathways available for transactions that do not require a pre-flight simulation. For the fastest and most reliable delivery, you should separate simulation from execution.

* **Your Action:** If you need to verify a transaction's outcome, call the `simulateTransaction()` method first.
* **RPC Parameter:** When you are ready to send the transaction for real, use `sendTransaction()` with the **`skipPreflight: true`** option.
* **Billing**: `simulateTransaction()` and `sendTransaction()` are two distinct RPC calls, billed independently. That means if you leave `skipPreflight` as `false`, your sends will be billed twice.

***

#### 3. Set Precise Compute Budgets & Priority Fees

A well-defined compute budget and priority fee are critical for getting your transaction included in a block.

* **Compute Budget:** The default CU budget is often too high for simple transactions. Setting an accurate, tight budget helps validators fit your transaction into blocks more efficiently. For example, a simple SOL transfer uses only \~500 CUs. Here is a good page on [How to Optimize Compute Usage on Solana](https://solana.com/developers/guides/advanced/how-to-optimize-compute).
* **Priority Fee:** Including a priority fee is essential. You should check the prevailing market rates for the accounts your transaction needs to write-lock. Our [Improved Priority Fees API](/chains/solana/improved-priority-fees-api) can help you determine a competitive fee without overpaying.

***

#### Resources

* **Example Code:** See our [Optimized Transactions Examples](https://github.com/rpcpool/optimized-txs-examples) repository on GitHub for sample client-side retry logic.
* **Further Reading:** This [thread on X by Jordan from Anza Labs](https://x.com/jordaaash/status/1774892862049800524?s=20) provides excellent context on this topic.


# Web3JS Socket/Connection Issues

Dealing with Socket/Connection issues in Web3JS

If you are running high-throughput applications using [`@solana/web3.js`](https://www.npmjs.com/package/@solana/web3.js) in a NodeJS environment, you may encounter persistent socket or connection errors, especially under heavy asynchronous load. This guide explains the root cause of these issues and provides several effective solutions.

***

#### Common Errors

These errors often manifest with messages like `fetch failed`, `Connect Timeout Error`, `ECONNREFUSED`, `ECONNRESET`, or `other side closed`. They are a strong indicator that your application is hitting local system limits for network connections.

<details>

<summary>Click to see full error examples</summary>

```
cause: ConnectTimeoutError: Connect Timeout Error
code: 'UND_ERR_CONNECT_TIMEOUT'
```

```
cause: Error: connect ECONNREFUSED 1.2.3.4:443
code: 'ECONNREFUSED'
```

```
cause: Error: Client network socket disconnected before secure TLS connection was established
code: 'ECONNRESET'
```

```
cause: SocketError: other side closed
code: 'UND_ERR_SOCKET'
```

</details>

***

#### The Root Cause: Port Exhaustion in NodeJS

These errors are not caused by the RPC node or the [`@solana/web3.js`](https://www.npmjs.com/package/@solana/web3.js) library itself. They stem from **NodeJS's implementation of `fetch`**, which uses the `undici` HTTP client. By default, [`undici`](https://www.npmjs.com/package/undici) aggressively opens a new TCP socket for each outgoing request.

Under heavy, concurrent load, this behavior can quickly **exhaust the available ephemeral ports** on your operating system, leading to the connection failures you see.

***

**Diagnosing the Issue**

You can check how many ports your Node process is using on a UNIX-based system with the following command. A high number (in the thousands) is a clear sign of this issue.

```bash
lsof -i -n -P | grep node | awk '{print $9}' | awk -F '->' '{print $1}' | \
awk -F ':' '{print $2}' | sort -u | wc -l
```

***

**External References**

This is a known issue related to connection management in `undici`. You can find more context in these community discussions and articles:

* [GitHub Issue: `undici` hangs/times out when under heavy load](https://github.com/nodejs/undici/issues/583)
* [GitHub Issue: `UND_ERR_SOCKET` on `fetch`](https://github.com/nodejs/undici/issues/2412)
* [Blog Post: Fixing `UND_ERR_SOCKET` in NodeJS](https://sg.wantedly.com/companies/rightcode/post_articles/871110)
* [StackExchange Post: `UND_ERR_SOCKET`](https://ethereum.stackexchange.com/questions/130028/hardhat-deploy-script-on-rsk-throws-und-err-socket)

***

### Recommended Solutions

The fundamental solution is to stop creating a new connection for every request and instead **reuse a pool of connections**.

***

#### Why Limit Connections?

Establishing a new TCP connection requires a TLS handshake, which is a time-consuming process. More details [here](https://www.cloudflare.com/en-gb/learning/ssl/what-happens-in-a-tls-handshake/).  By reusing a smaller pool of persistent connections, your application will be more performant, and it will place significantly less load on both your client machine and our RPC servers.

***

#### What Is an Ideal Connection Count?

We recommend starting with a limit of **50 connections**. You can monitor your port usage with the `lsof` command above. If you see that all 50 connections are consistently in use and your application could benefit, you can gradually increase this limit.

***

#### 1. Limit Connections with a Global Agent (Recommended)

This is the best solution for most applications. You configure NodeJS to use a global agent with a connection pool, forcing the HTTP client to reuse connections.

**For `undici` (Node's default `fetch`):**

Install [`undici`](https://www.npmjs.com/package/undici) and set the global dispatcher at the very beginning of your application's entry point.

```javascript
// At the top of your main application file
import { setGlobalDispatcher, Agent } from "undici";

setGlobalDispatcher(
  new Agent({
    connections: 50, // Recommended starting default
  })
);
```

**For `axios`:**

If you prefer [`axios`](https://www.npmjs.com/package/axios), you can create a client with a dedicated `https.Agent`. The [`@solana/web3.js`](https://www.npmjs.com/package/@solana/web3.js) `Connection` object allows you to pass a custom fetch implementation. A full example can be found at [this Gist](https://gist.github.com/WilfredAlmeida/9adea27abb5958178c4370c5656e89b7).

```javascript
import https from 'https';

const agent = new https.Agent({
  maxSockets: 50, // Recommended starting default
  keepAlive: true,
});

const axiosInstance = axios.create({
  httpsAgent: agent,
});
```

***

#### 2. Use an Alternative Runtime (Bun)

The [Bun](https://bun.sh/) runtime is a modern alternative to NodeJS. Based on our testing, its internal HTTP client handles connection pooling more efficiently and does not exhibit this socket exhaustion issue. This is a great option for new projects but may be a larger change for existing codebases.

#### 3. Using the new version of [\`@solana/web3.js\`](https://github.com/solana-labs/solana-web3.js/tree/master/packages/library).

&#x20;Anza Labs is developing a new and improved version of the library which, as of this writing, is in technical preview. It uses the [`undici`](https://www.npmjs.com/package/undici) library and the errors are less frequent in it. If encountered, with the configuration mentioned in [\[1\]](#id-1.-limit-connections-with-a-global-agent-recommended) they should be resolved. Do note that the library is not compatible with the current version, meaning that it’ll be a ground up rewrite of your codebase.

***

#### 4. A Note on Other Libraries

If you're using SDKs like [jito-ts](https://www.npmjs.com/package/jito-ts) which uses [node-fetch](https://www.npmjs.com/package/node-fetch) underneath, or some other libraries, you may encounter errors like `ERR_STREAM_PREMATURE_CLOSE` which have this same underlying issue and limiting the number of connections for NodeJS has resolved the issue for our customers. The number of connections for libraries like `node-fetch` and others can be limited by providing an `http(s).Agent` configuration. For example

Copy

```
new https.Agent({
  maxSockets: 50,
});
```


# Solana v2.0: Deprecated RPC Methods

> **NOTICE:** The Solana v2.0 network upgrade has already been deployed to Mainnet. As part of this upgrade, several legacy JSON-RPC methods have been permanently removed. If your application still relies on any of the deprecated methods listed below, it will no longer function as expected. We strongly recommend auditing your codebase and updating to the supported successor methods immediately. These modern alternatives are fully live and supported across all nodes.

***

#### Deprecated Methods and Their Successors

The following table lists all RPC methods that will be removed in v2.0 and the new methods that should be used instead.

| Deprecated Method (Old)             | Successor Method (New)                                                                                                 |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `confirmTransaction`                | `getSignatureStatuses`                                                                                                 |
| `getSignatureStatus`                | `getSignatureStatuses`                                                                                                 |
| `getSignatureConfirmation`          | `getSignatureStatuses`                                                                                                 |
| `getConfirmedSignaturesForAddress`  | `getSignaturesForAddress`                                                                                              |
| `getConfirmedSignaturesForAddress2` | `getSignaturesForAddress`                                                                                              |
| `getConfirmedBlock`                 | `getBlock`                                                                                                             |
| `getConfirmedBlocks`                | `getBlocks`                                                                                                            |
| `getConfirmedBlocksWithLimit`       | `getBlocksWithLimit`                                                                                                   |
| `getConfirmedTransaction`           | `getTransaction`                                                                                                       |
| `getRecentBlockhash`                | `getLatestBlockhash`                                                                                                   |
| `getFees`                           | `getFeeForMessage`                                                                                                     |
| `getFeeCalculatorForBlockhash`      | `isBlockhashValid` or `getFeeForMessage`                                                                               |
| `getSnapshotSlot`                   | `getHighestSnapshotSlot`                                                                                               |
| `getStakeActivation`                | Use `getAccountInfo`. [See this guide for the alternative approach.](https://solana.stackexchange.com/questions/15710) |
| `getTotalSupply`                    | **None.** This method will be removed completely.                                                                      |
| `getFeeRateGovernor`                | **None.** This method will be removed completely.                                                                      |

***

#### Resources

* **Official Transition Guide:** For more technical details, please refer to the [Anza Agave v2.0 Transition Guide on GitHub](https://github.com/anza-xyz/agave/wiki/Agave-v2.0-Transition-Guide).

If you have any questions or need assistance updating your application, please reach out to our support team.


# Unsupported Account Queries

Account-related JSON-RPC methods that are disabled, restricted, or require dedicated infrastructure on Triton One shared mainnet RPC.

Some account-related Solana JSON-RPC methods are too expensive or require specialized indexing that the **shared mainnet** RPC pool cannot serve reliably. This page lists the exact methods that fall outside the shared pool, what error you should expect, and which dedicated product unlocks them.

If your workload depends on any of the methods below, please reach out to our support team to discuss the appropriate setup.

***

#### Hard-disabled on shared mainnet

These methods return HTTP `410 Gone` with the JSON-RPC error `"The RPC call or parameters have been disabled"`:

| Method               | Reason                                                                                | Where to use it                                                                                     |
| -------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `getLargestAccounts` | Prohibitively expensive at mainnet scale; scans the entire account set on every call. | Available on **dedicated** nodes by request.                                                        |
| `requestAirdrop`     | Disabled on mainnet (no faucet).                                                      | Use the official Solana faucet at [faucet.solana.com](https://faucet.solana.com) on devnet/testnet. |

***

#### `getProgramAccounts` — restricted on shared mainnet

The default Agave validator software that powers our shared RPC nodes only maintains built-in secondary indexes for a small set of well-known programs (notably SPL Token and Token-2022). For any other program, an unfiltered `getProgramAccounts` request — or one with filters the validator cannot satisfy from a secondary index — must scan the entire account set, which is not feasible to serve on a shared pool.

If you see this error on shared mainnet:

```
<ProgramId> excluded from account secondary indexes; this RPC method unavailable for key
```

…it means the program you are querying is not on the validator’s secondary-index allowlist, and your query cannot be served from the shared pool. Common cases:

* Querying a custom program with `memcmp` filters (any DeFi/perps/lending/governance program).
* Querying SPL Token without the correct `dataSize` filter — see [Token Program](/chains/solana/token-program) for the precise filter shape that does work.

**Solution: Steamboat Custom Indexes.** Steamboat is our Postgres-backed `getProgramAccounts` implementation, populated by a Yellowstone gRPC stream and tuned with custom indexes per program and `memcmp` offset. Query times typically drop from seconds to milliseconds. Steamboat runs on dedicated nodes only.

For details, configuration options, and the SPL Token caveat, see [Steamboat Custom Indexes](/project-yellowstone/cloudbreak-custom-indexes).

***

#### Methods served by Steamboat

When Steamboat is enabled on your dedicated node, the following JSON-RPC methods are answered by Steamboat rather than the validator. All others continue to be served by the underlying Agave RPC.

| Method                       | Notes                                                                                                                                                            |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `getProgramAccounts`         | Supports `memcmp`, `dataSize`, and `dataSlice` filters. SPL Token is intentionally **not** routed through Steamboat — Agave’s built-in index already handles it. |
| `getTokenAccountsByOwner`    | Served from the same indexed store.                                                                                                                              |
| `getTokenAccountsByDelegate` | Served from the same indexed store.                                                                                                                              |
| `getSlot`                    | Returns the current slot at the requested commitment level.                                                                                                      |
| `getHealth`                  | Returns the Steamboat service health.                                                                                                                            |

Steamboat **does not** currently implement (these calls are still handled by the validator on the same node, but are listed here for completeness):

* `getMultipleAccounts`
* `getAccountInfo`
* `getBalance`
* `getTokenAccountBalance`
* `getTokenAccountsByMint`
* `getVoteAccounts`

These methods are on the Steamboat roadmap.

**Commitment levels.** Steamboat fully supports `confirmed` and `finalized`. The `processed` commitment level is not supported by default — requests with `processed` are either rejected, or rewritten to `confirmed` if the operator has enabled `processed-commitment = "use-confirmed"`. Full `processed` support is planned as an optional plugin.

**Pagination.** Steamboat currently returns all matching accounts in a single response. Paginated responses are on the roadmap; until then, take care with queries that match very large result sets.

***

#### Digital Assets API — disabled methods

The Digital Assets API (DAS) is enabled by default on all our shared and dedicated endpoints. One method is currently turned off:

| Method             | Status               | Notes                                                                                                         |
| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------- |
| `getAssetsByGroup` | Temporarily disabled | Disabled for maintenance and performance optimisation. Please avoid calling this method until further notice. |

The full list of DAS methods and their parameters lives in the [API Methods](/digital-assets-api/metaplex-digital-assets-api) reference.

***

#### Batch behaviour

The following account-related methods are always **unbatched** — submit them as individual JSON-RPC requests rather than as elements of a batch array:

* `getProgramAccounts`
* `getTokenAccountsByOwner`
* `getTokenAccountsByDelegate`

If you submit them inside a batch, the request is split and each call is processed independently on the back end. This is not an error condition — your application will receive a valid batched response — but it does mean batch ordering and atomicity guarantees do not apply to these calls.

***

#### Summary — which product unlocks which method

| You need...                                                         | Use...                                                                                          |
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `getLargestAccounts`                                                | Dedicated node, by request.                                                                     |
| `getProgramAccounts` for a custom program at production performance | [Steamboat Custom Indexes](/project-yellowstone/cloudbreak-custom-indexes) on a dedicated node. |
| `getTokenAccountsByOwner` / `getTokenAccountsByDelegate` at scale   | Available on shared RPC; Steamboat-backed on dedicated nodes for higher throughput.             |
| NFT and fungible-asset queries (including compressed NFTs)          | [Digital Assets API](/digital-assets-api/introduction) — enabled on all endpoints.              |
| `getAssetsByGroup`                                                  | Currently unavailable. Contact support for the recommended workaround.                          |

If you are not sure which product fits your workload, contact our support team — we can review your query patterns and recommend either an index configuration on Steamboat or a different routing setup.


# Agave - AccountsDB: Storage Files shrinking process

Basically shrinking is a process that is periodically executed by Agave and the goal is to remove from files accounts that are not longer valid because a new version exists.

* It happens once per second (hardcoded):

  ```bash
  const SHRINK_INTERVAL: Duration = Duration::from_secs(1);
  ```
* it is controlled by 2 config flags:

  ```bash
  --accounts-shrink-ratio  # defaults to 0.8
  --accounts-shrink-optimize-total-space  # defaults to true

  ```
* So basically the process goes like this: each second the bank (inside the AccountsBackgroundService) will execute `shrink_candidate_slots()` (<https://github.com/anza-xyz/agave/blob/master/runtime/src/accounts_background_service.rs#L566>) which will check a list of files marked as canditates for shrinking, a file becomes a candidate if its rate of valid (not old) accounts goes bellow `accounts-shrink-ratio`. The second key point in the logic is that it will calculate the total valid accounts byes over the total accounts byte(if the 2nd config flag is true), and then it will order the list of candidates starting by the ones with more dead accounts, and it will iterate shrinking files until the rate of live accounts becomes higher than `accounts-shrink-ratio`. All files that were not shrinked remains in the queue for the next shrinking iteration
  * it reads storage files inside `shrink_collect()` which returns accounts marked as dead
  * it does not modify the original file, instead if generates a new one with only the valid accounts and then point to the updated one
  * if `accounts-shrink-optimize-total-space` is false, it will shrink all files individually without any global context in each iteration
  * to check if an account inside a file is dead or not there is 2 ways:
    * checks `AccountStorageEntry::obsolete_accounts` (accounts will be written here inside `_store_accounts_frozen` in <https://github.com/anza-xyz/agave/blob/master/accounts-db/src/accounts_db.rs#L5738>) if the offset for the account is there filter it out
    * For remaining accounts not filered out in the 1st step above it will look for them in the `AccountsDb::accounts_index` (on `load_accounts_index_for_shrink()`). if the index for the account is pointing to the slot of the current Storage file being analyzed, means the account is still valid, if not marks it as dead and filter it out

The consequence of this process is that the storage and thus the snapshot, doesn’t have any guarantee over old data, it might or might not be there. So we need to only keep the last version of each account before being able to trust at all any data we read from the snapshot


# getTransactionsForAddress

Transaction history API for retrieving address activity with server-side filtering, configurable sort order, pagination, and optional full transaction details.

`getTransactionsForAddress` is designed for workloads that would otherwise require calling `getSignaturesForAddress` followed by `getTransaction` for every returned signature.

That standard two-step pattern works, but it creates an N+1 request flow: one request to discover signatures, then one additional request per transaction to fetch details. It also pushes filtering, pagination management, token-account expansion, and result assembly onto the client.

This method combines those steps into a single address-history query. It can return either signature-level results or full transaction payloads, apply filters server-side, preserve a single pagination cursor, and optionally include token-account activity owned by the requested address.

`getTransactionsForAddress` is a custom RPC method. It is not part of the standard Solana JSON-RPC API, but it follows the same JSON-RPC request and response envelope style as standard Solana RPC methods.

## Request

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTransactionsForAddress",
  "params": [
    "AddressBase58",
    {
      "transactionDetails": "signatures",
      "sortOrder": "desc",
      "limit": 100,
      "paginationToken": null,
      "filters": {
        "slot": { "gte": 250000000 },
        "status": "any",
        "tokenAccounts": "none"
      }
    }
  ]
}
```

## Parameters

| Parameter | Type   | Required | Description                              |
| --------- | ------ | -------- | ---------------------------------------- |
| address   | string | Yes      | Base58 Solana account address to search. |
| options   | object | No       | Query options and filters.               |

## Options

| Option                         | Type                                   | Default                           | Description                                  |
| ------------------------------ | -------------------------------------- | --------------------------------- | -------------------------------------------- |
| transactionDetails             | signatures \| full                     | signatures                        | Controls response detail.                    |
| sortOrder                      | asc \| desc                            | desc                              | Sort by slot and transaction position.       |
| limit                          | number                                 | 1000 for signatures, 100 for full | Maximum number of results.                   |
| paginationToken                | string                                 | null                              | Token returned from a previous response.     |
| commitment                     | confirmed \| finalized                 | finalized                         | Commitment level.                            |
| minContextSlot                 | number                                 | none                              | Fails if the node has not reached this slot. |
| encoding                       | json \| jsonParsed \| base58 \| base64 | json                              | Used when transactionDetails is full.        |
| maxSupportedTransactionVersion | number                                 | none                              | Used when transactionDetails is full.        |
| filters                        | object                                 | none                              | Optional filters.                            |

## Filters

| Filter        | Type                          | Description                                                          |
| ------------- | ----------------------------- | -------------------------------------------------------------------- |
| slot          | comparison object             | Filter by slot. Supports gte, gt, lte, lt.                           |
| blockTime     | comparison object             | Filter by block time. Supports gte, gt, lte, lt, eq.                 |
| signature     | comparison object             | Filter by transaction signature position. Supports gte, gt, lte, lt. |
| status        | any \| succeeded \| failed    | Filter by transaction status.                                        |
| tokenAccounts | none \| balanceChanged \| all | Include token-owner activity for the address.                        |

Comparison object example:

```json
{
  "gte": 250000000,
  "lt": 260000000
}
```

## Token Account Filtering

`tokenAccounts` controls whether token-account activity owned by the requested address is included.

| Value          | Behavior                                                                  |
| -------------- | ------------------------------------------------------------------------- |
| none           | Only transactions where the address appears directly.                     |
| all            | Also include transactions involving token accounts owned by the address.  |
| balanceChanged | Include owned token-account transactions only when token balance changed. |

Token-owner activity is derived from transaction pre/post token balance metadata.

## Response

{% tabs %}
{% tab title="Signatures" %}
When `transactionDetails` is `signatures`, each result contains transaction metadata.

```json
{
  "jsonrpc": "2.0",
  "result": {
    "data": [
      {
        "signature": "TransactionSignatureBase58",
        "slot": 250000001,
        "transactionIndex": 12,
        "err": null,
        "memo": null,
        "blockTime": 1700000000,
        "confirmationStatus": "finalized"
      }
    ],
    "paginationToken": "250000001:12"
  },
  "id": 1
}
```

{% endtab %}

{% tab title="Full Transactions" %}
When `transactionDetails` is `full`, each result contains the encoded transaction and metadata.

```json
{
  "jsonrpc": "2.0",
  "result": {
    "data": [
      {
        "slot": 250000001,
        "transactionIndex": 12,
        "blockTime": 1700000000,
        "transaction": {...},
        "meta": {...}
      }
    ],
    "paginationToken": "250000001:12"
  },
  "id": 1
}
```

{% endtab %}
{% endtabs %}

## Pagination

Use the returned `paginationToken` in the next request to continue scanning.

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "getTransactionsForAddress",
  "params": [
    "AddressBase58",
    {
      "limit": 100,
      "paginationToken": "250000001:12"
    }
  ]
}
```

## Example: Successful Transactions Only

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTransactionsForAddress",
  "params": [
    "AddressBase58",
    {
      "filters": {
        "status": "succeeded"
      }
    }
  ]
}
```

## Example: Token Balance Changes

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTransactionsForAddress",
  "params": [
    "OwnerAddressBase58",
    {
      "filters": {
        "tokenAccounts": "balanceChanged"
      }
    }
  ]
}
```

## Example: Slot Range

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTransactionsForAddress",
  "params": [
    "AddressBase58",
    {
      "filters": {
        "slot": {
          "gte": 250000000,
          "lt": 251000000
        }
      }
    }
  ]
}
```


# Shred Streaming

{% hint style="info" %}
Triton Shred Streaming is available on request. Contact us through the [Customer Portal](https://customers.triton.one) to have it enabled on your account.
{% endhint %}

Shred Streaming delivers Solana shreds to you at the lowest latency we can achieve — before a block is assembled, before it is replayed, and before it appears in any RPC or gRPC stream.

Shreds are the fragments a leader breaks its block into and fans out across the network. Receiving them directly is the earliest possible view of what a leader is producing, which is why Shred Streams are built for workloads where every millisecond of head start matters.

* **Best for:** trading systems, MEV searchers, market makers, and any latency-sensitive backend that reconstructs blocks itself.
* **Key feature:** a **full shred feed, deduplicated** — you receive every shred we see, and you (typically) receive each one only once.

### Is Triton Shred Streaming for me?

Processing raw shreds is an advanced feature and requires custom software development on your end. It offers no filtering, and relies on forward error correction to handle any potential dropped or missed packages.

An alternative to raw shreds is the [gRPC SubscribeDeshred](/project-yellowstone/dragons-mouth-grpc-subscriptions) subscription that we offer via our Riptide and other regular gRPC endpoints.&#x20;

SubscribeDeshred uses the same shred sources as our raw shred streams but does the work for you of processing the shreds back to transactions and entries, offers filtering as well as guaranteed delivery of data.

### How to use Triton Shred Streaming

To use shred streams you need to run a software on your end that is capable of decoding and processing raw shreds. To get you started we provide a full working code sample of how reassemble incoming shreds to transactions in this repository:&#x20;

{% embed url="<https://github.com/rpcpool/deshred-example>" %}

You can use this to verify that your shred streaming is working and as a starting point for your own receiver code.&#x20;

### **Where you can receive shreds**

Shredstream is available in five regions:

| Region | Location  |
| ------ | --------- |
| US     | New York  |
| EU     | London    |
| EU     | Amsterdam |
| EU     | Frankfurt |
| AP     | Tokyo     |

You can enable any combination of regions on your account and receive shreds in each of them independently.

### **Where the shreds come from**

Shred Streams aggregates shreds from several sources so that you are not dependent on a single vantage point into the network:

* **Triton validators:** shreds observed directly by the validators we operate.
* **DoubleZero Edge:** through a special partnership with DoubleZero, we receive shreds from their Edge network.
* **Partner validators:** additional validators we work with, spread across the network.
* **Turbine:** for any validator we do not partner with and which is not sending their shreds through DoubleZero Edge, we have the lowest latency pathway possible for receiving their shreds via Turbine.

Because the same shred usually arrives from more than one of these sources, the feed is deduplicated before it reaches you: whichever copy arrives first is the one you get, and later duplicates are discarded.

### **How it works**

Shred Streaming runs on a custom multicast network built specifically for shred distribution.

* **Shortest-path ingestion.** Our receivers are positioned so that shreds reach our network over the shortest available path from the leader, rather than over whatever route the public Internet happens to pick.
* **Custom low-latency links.** Once a shred is inside our network, we distribute it between regions over our own low-latency links instead of the public Internet.
* **Multicast fan-out.** Within each region, shreds are fanned out over multicast, so adding subscribers does not add latency for the ones already connected.
* **Dedicated network ports.** We use dedicated shred distribution hosts with multiple dedicated network ports  to ensure that there is minimal distribution latency.

The result is that a shred picked up in one region is available to subscribers in every other region very shortly after we first see it.

### **Getting started**

1. **Request access.** Contact our customer support through the [Customer Portal](https://customers.triton.one) and ask to have Shred Streams enabled on your account.
2. **Configure your destinations.** Once it is enabled, a Shred Streams section appears in the Customer Portal. There you add the **IP addresses and ports** you want shreds delivered to, for each of the regions you want to receive from. Shortly after adding them you will start receiving shreds from our source IP (67.209.53.15).
3. **Start receiving.** Shreds begin flowing to the destinations you configured. Make sure the hosts you list can accept the traffic — the destination IPs must be reachable and the ports open. If you have an ingress firewall allow traffic originating from 67.209.53.15.

{% hint style="warning" %}
A full shred feed is high-bandwidth and bursty. Provision the receiving hosts and their network capacity accordingly, and place them as close as possible to the region you subscribe from.
{% endhint %}

### **Questions**

For help with sizing, region selection, or anything else about Shredstream, reach out through your Triton CS channel or the [Customer Portal](https://customers.triton.one).


# SUI

### Overview

> **Deprecation notice:** Sui JSON-RPC is being sunset. Triton will continue to support it until the **end of October 2026**, after which it is removed from the Sui source code and node binaries and our endpoints will no longer serve it. Migrate to [gRPC](https://docs.triton.one/chains/sui/grpc) or [GraphQL](https://docs.triton.one/chains/sui/graphql) before then. See [JSON-RPC deprecation](https://claude.ai/chat/a683b0bb-ca5b-424e-9847-872ab66e0791#json-rpc-deprecation) below.

As one of the original validators on Sui, we provide full-service Sui data access. Our stack now spans full nodes (JSON-RPC and gRPC), GraphQL RPC, a long-term Archival Service, and Walrus and Seal support.

***

#### Service summary

<table><thead><tr><th>Stack</th><th width="376.76171875">Protocol &#x26; endpoint format</th><th>Data range</th><th>Reference</th></tr></thead><tbody><tr><td><strong>Mainnet — Full node</strong></td><td>JSON-RPC: <code>https://XXX.sui.rpcpool.com</code> gRPC: <code>XXX.sui.rpcpool.com:443</code></td><td>Recent (pruned by full node)</td><td><a href="https://docs.triton.one/chains/sui/grpc">gRPC</a></td></tr><tr><td><strong>Mainnet — GraphQL</strong></td><td>GraphQL: <code>https://XXX.sui.rpcpool.com/graphql</code></td><td>Latest 30 days</td><td><a href="https://docs.triton.one/chains/sui/graphql">GraphQL</a></td></tr><tr><td><strong>Mainnet — Archival Service</strong></td><td>gRPC: <code>archive.mainnet.sui.rpcpool.com:443</code></td><td>Full history (from genesis)</td><td><a href="https://docs.triton.one/chains/sui/archival-storage-and-services">Archival Storage and Services</a></td></tr><tr><td><strong>Testnet — Full node</strong></td><td>JSON-RPC: <code>https://XXX.sui.rpcpool.com</code> gRPC: <code>XXX.sui.rpcpool.com:443</code></td><td>Recent (pruned by full node)</td><td><a href="https://docs.triton.one/chains/sui/grpc">gRPC</a></td></tr></tbody></table>

> `XXX` is your unique endpoint slug, visible in the [client panel](https://customers.triton.one/), where you also find your token. GraphQL and the Archival Service are available on **Mainnet only**.

Authentication uses the `X-Token` header on all non-free endpoints (GraphQL also accepts the token in the URL). We also provide **free, rate-limited test endpoints** for gRPC and GraphQL that require no token — see the [gRPC](https://docs.triton.one/chains/sui/grpc) and [GraphQL](https://docs.triton.one/chains/sui/graphql) pages for details.

***

#### Choosing an interface

* **gRPC** — high-performance, low-latency point lookups and real-time checkpoint streaming. Best for indexers, exchanges, bots, and backend systems. Now generally available.
* **GraphQL** — flexible, composable queries that return exactly the fields you need across transactions, objects, events, and balances. Best for frontends, explorers, and dashboards. Serves the latest 30 days.
* **Archival Service** — long-term, consistent access to the full history of Sui mainnet from genesis, over the same gRPC `LedgerService` interface. Use it for any lookup older than the full node or GraphQL retention windows.
* **JSON-RPC** — deprecated. Supported through the end of October 2026 for backward compatibility only. See below.

***

#### Additional services

**Mainnet**

* Walrus — storage, publisher, and aggregator
* Seal — permissioned server

**Testnet**

* Walrus — storage, publisher, and aggregator
* Seal — open and permissioned server

***

#### JSON-RPC deprecation

Sui JSON-RPC is deprecated across the network and is currently in its **sunsetting period**. JSON-RPC has already been disabled on Sui Foundation mainnet full nodes, and upstream removal from the Sui source code and node binaries is scheduled for October 2026.

**Triton will continue to support JSON-RPC until the end of October 2026.** After that date, JSON-RPC is removed from the Sui source code and binaries, and our endpoints will no longer serve it.

To avoid disruption, migrate to one of the supported interfaces before then:

* Move backend systems, indexers, exchanges, and streaming workloads to [gRPC](https://docs.triton.one/chains/sui/grpc).
* Move frontends, explorers, dashboards, and flexible or filtered read workloads to [GraphQL](https://docs.triton.one/chains/sui/graphql).

For a method-by-method mapping from JSON-RPC and guidance on choosing between gRPC and GraphQL, see the official [JSON-RPC Migration Guide](https://docs.sui.io/develop/accessing-data/json-rpc-migration).

> **Note:** The Sui WebSocket API is officially deprecated and has been retired by Mysten Labs. Use gRPC checkpoint streaming for real-time updates.

***

#### Resources

* [gRPC](https://docs.triton.one/chains/sui/grpc)
* [GraphQL](https://docs.triton.one/chains/sui/graphql)
* [Archival Storage and Services](https://docs.triton.one/chains/sui/archival-storage-and-services)
* [Official: Access Sui Data](https://docs.sui.io/develop/accessing-data/data-serving)
* [Official: JSON-RPC Migration Guide](https://docs.sui.io/develop/accessing-data/json-rpc-migration)


# Walrus

[Walrus](https://walrus.xyz/) is a decentralized, scalable, distributed, and fault-tolerant storage layer optimised for storing large binary blobs. Walrus uses Sui for coordination among other things.

Walrus Publisher enables writing data on-chain. Aggregator enables reading data, and Storage nodes provide storage.

Triton hosts Walrus Publishers,  Aggregators. and Storage nodes. Read more about Walrus components from the official [documentation](https://docs.wal.app/dev-guide/components.html).


# gRPC

### Overview

The Sui gRPC API is a fast, type-safe, and efficient interface for interacting with the Sui blockchain. It replaces the deprecated JSON-RPC protocol and is now generally available. gRPC uses [Protocol Buffers](https://protobuf.dev/overview/) for compact, high-performance data serialization, and supports both standard request-response calls and real-time server-side streaming — no polling required.

> **Note:** JSON-RPC is deprecated and will be deactivated in October 2026. Migrating to gRPC is strongly recommended for all production integrations.

### When to use gRPC

gRPC is the best choice when you need:

* **High-performance, low-latency** point lookups for transactions, objects, and checkpoints
* **Real-time streaming** subscriptions (replaces deprecated WebSocket support)
* **Strongly typed clients** generated from `.proto` definitions in TypeScript, Go, Rust, and other languages
* **Scalable backend systems** — indexers, exchanges, bots, and dashboards

For frontends, developer tooling, or flexible filtered historical queries, consider GraphQL RPC as an alternative.

> **Important:** gRPC full nodes do not automatically fall back to Archival Storage for historical data. If your integration requires deep historical lookups, use the Archival Storage and Services endpoint directly.

***

### Endpoints

#### Free Test Endpoint (Mainnet — rate-limited)

We provide a free shared gRPC endpoint for testing and evaluation purposes. It is strictly rate-limited and intended for development use only, **not for production traffic**.

```
mainnet.sui.rpcpool.com:443
```

No authentication token is required for this endpoint.

***

#### Shared Clients

Shared plan clients access gRPC via the standard shared endpoint. Your unique endpoint hostname is available in the [client panel](https://customers.triton.one/).

**Endpoint format:**

```
XXX.sui.rpcpool.com:443
```

Replace `XXX` with your specific endpoint slug shown in the panel.

**Authentication** is required via the `X-Token` header:

```
X-Token: <your-token>
```

Both your endpoint and token can be found in your [client panel](https://customers.triton.one/).

***

#### Dedicated Clients

Dedicated plan clients have a private endpoint provisioned exclusively for their use.

**Endpoint format:**

```
XXX.sui.rpcpool.com:443
```

Replace `XXX` with your dedicated endpoint slug shown in the panel.

**Authentication** is required via the `X-Token` header:

```
X-Token: <your-token>
```

Both your endpoint and token can be found in your [client panel](https://customers.triton.one/).

***

### Authentication

All non-free endpoints require the `X-Token` header to be included with every request. You can find your token in the client panel.

When using `grpcurl`:

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  XXX.sui.rpcpool.com:443 \
  list
```

When using a generated gRPC client (e.g. in Go or TypeScript), add the token as a metadata field on every call:

```
key:   "x-token"
value: "<your-token>"
```

***

### Available Services

The Sui gRPC API is defined via `.proto` files available in the [`sui-apis` repository on GitHub](https://github.com/MystenLabs/sui-apis/tree/main/proto). The following services are available:

| Service                        | Purpose                                                                               |
| ------------------------------ | ------------------------------------------------------------------------------------- |
| `TransactionExecutionService`  | Submit and execute signed transactions on the Sui network                             |
| `LedgerService`                | Look up checkpoints, transactions, and objects from current state and recent history  |
| `StateService`                 | Query live onchain data: balances, owned objects, dynamic fields; dry-run simulations |
| `SubscriptionService`          | Stream live checkpoint updates in real time                                           |
| `MovePackageService`           | Access deployed Move package metadata and content                                     |
| `SignatureVerificationService` | Validate signatures (including zkLogin) outside of transaction execution              |
| `NameService`                  | Resolve SuiNS names to addresses and perform reverse lookups                          |

Use these `.proto` definitions to generate type-safe client libraries in your language of choice.

***

### Quickstart: grpcurl Examples

[`grpcurl`](https://github.com/fullstorydev/grpcurl) is the easiest way to explore the API from the command line.

#### List available services

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  XXX.sui.rpcpool.com:443 \
  list
```

#### List methods in LedgerService

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  XXX.sui.rpcpool.com:443 \
  list sui.rpc.v2.LedgerService
```

#### Get the latest checkpoint

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  -d '{}' \
  XXX.sui.rpcpool.com:443 \
  sui.rpc.v2.LedgerService/GetLatestCheckpoint
```

#### Get a transaction (events and effects)

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  -d '{
    "digest": "<transaction-digest>",
    "read_mask": {
      "paths": ["effects", "events"]
    }
  }' \
  XXX.sui.rpcpool.com:443 \
  sui.rpc.v2.LedgerService/GetTransaction
```

#### Subscribe to live checkpoints (streaming)

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  -d '{}' \
  XXX.sui.rpcpool.com:443 \
  sui.rpc.v2.SubscriptionService/SubscribeCheckpoints
```

***

### Key Concepts

#### Field Masks

Use `read_mask` to request only the fields you need, reducing response size and latency:

```json
{
  "read_mask": {
    "paths": ["digest", "effects", "events"]
  }
}
```

Pass `"*"` to request all fields. If `read_mask` is omitted, it defaults to `*`. In batch requests, only the top-level `read_mask` is respected.

#### Pagination

List APIs (owned objects, balances, etc.) return paginated results. Use `page_size` to control batch size and pass the returned `next_page_token` into the next request to continue. An empty `next_page_token` means you have reached the end. Keep all other request parameters constant between pages.

#### Encodings

* `Address` and `ObjectId`: 64-character hex with a `0x` prefix
* `Digest`: Base58 encoded
* `TypeTag` / `StructTag`: Canonical string format

#### Errors

Errors follow the [gRPC richer error model](https://grpc.io/docs/guides/error/#richer-error-model). Detailed error information is provided in the `grpc-status-details-bin` header as a Base64-encoded `google.rpc.Status` message.

***

### Resources

* [Sui gRPC Reference (official)](https://docs.sui.io/references/fullnode-protocol)
* [Proto definitions — sui-apis on GitHub](https://github.com/MystenLabs/sui-apis/tree/main/proto)
* [Official: What is gRPC?](https://docs.sui.io/develop/accessing-data/grpc/what-is-grpc)
* [Official: Querying Data with gRPC](https://docs.sui.io/develop/accessing-data/grpc/using-grpc)
* [grpcurl tool](https://github.com/fullstorydev/grpcurl)


# Archival Storage and Services

### Overview

Full nodes on Sui enforce limited data retention for performance and scalability reasons. Once a full node prunes older data, it is no longer available through the standard gRPC or JSON-RPC endpoints.

The **Archival Storage and Service** solves this by providing long-term, consistent access to the full historical record of the Sui blockchain — including old transactions, checkpoints, and object states from genesis. It is the historical backbone for indexers, analytics platforms, exchanges, and any application that needs to query data older than what a full node retains.

The Archival Service exposes the same `LedgerService` gRPC interface, so if you are already using the standard gRPC API, switching to the archival endpoint for historical lookups requires no client changes beyond the endpoint URL.

> **Note:** The standard gRPC full node endpoint does **not** automatically fall back to Archival when data has been pruned. Your application must query the Archival endpoint explicitly for historical data.

***

### Endpoints

#### Shared Clients

Shared plan clients access the Archival Service via a fixed shared endpoint:

```
archive.mainnet.sui.rpcpool.com:443
```

**Authentication** is required via the `X-Token` header:

```
X-Token: <your-token>
```

Your token can be found in your [client panel](https://customers.triton.one/).

***

#### Dedicated Clients

Dedicated plan clients have a private archival endpoint provisioned exclusively for their use:

```
archive-XXX.sui.rpcpool.com:443
```

Replace `XXX` with your dedicated endpoint slug shown in the panel.

**Authentication** is required via the `X-Token` header:

```
X-Token: <your-token>
```

Both your endpoint and token can be found in your [client panel](https://customers.triton.one/).

***

### Authentication

All archival endpoints require the `X-Token` header on every request — the same token used for your standard gRPC endpoint.

Example with `grpcurl`:

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  archive.mainnet.sui.rpcpool.com:443 \
  list
```

***

### What Data is Available

The Archival Service stores and serves the complete history of the Sui mainnet, including:

* **Transactions** — full transaction data from genesis
* **Checkpoints** — complete checkpoint records
* **Object states** — historical object snapshots at past checkpoints
* **Effects and events** — full transaction effects and emitted events

***

### Using the Archival Service

The Archival Service uses the standard Sui gRPC `LedgerService` interface. You interact with it the same way as a regular gRPC full node — the only difference is the endpoint URL.

#### List available services

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  archive.mainnet.sui.rpcpool.com:443 \
  list
```

#### Get a historical transaction

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  -d '{
    "digest": "<transaction-digest>"
  }' \
  archive.mainnet.sui.rpcpool.com:443 \
  sui.rpc.v2.LedgerService/GetTransaction
```

#### Get a historical checkpoint

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  -d '{
    "sequence_number": 12345678
  }' \
  archive.mainnet.sui.rpcpool.com:443 \
  sui.rpc.v2.LedgerService/GetCheckpoint
```

#### Get an object at a historical checkpoint

```bash
grpcurl \
  -H "X-Token: <your-token>" \
  -d '{
    "object_id": "0x<object-id>",
    "version": "<object-version>"
  }' \
  archive.mainnet.sui.rpcpool.com:443 \
  sui.rpc.v2.LedgerService/GetObject
```

> For dedicated clients, replace `archive.mainnet.sui.rpcpool.com` with your dedicated archival endpoint `archive-XXX.sui.rpcpool.com`.

***

### Routing Strategy: Full Node vs. Archival

A common production pattern is to query your standard gRPC endpoint first for recent data, and fall back to the Archival endpoint when data is not found (i.e. when the node returns `NotFound`).

```
Request
  │
  ▼
Standard gRPC endpoint (XXX.sui.rpcpool.com)
  │
  ├─ Found → Return result
  │
  └─ NotFound → Retry on Archival endpoint
                  (archive.mainnet.sui.rpcpool.com
                   or archive-XXX.sui.rpcpool.com)
```

This keeps latency low for recent data while ensuring full coverage for historical lookups.

***

### Endpoint Summary

| Plan          | Standard gRPC Endpoint    | Archival Endpoint                     |
| ------------- | ------------------------- | ------------------------------------- |
| **Shared**    | `XXX.sui.rpcpool.com:443` | `archive.mainnet.sui.rpcpool.com:443` |
| **Dedicated** | `XXX.sui.rpcpool.com:443` | `archive-XXX.sui.rpcpool.com:443`     |

> `XXX` is your unique endpoint slug, visible in the [client panel](https://customers.triton.one/).

Authentication is the same `X-Token` header for both endpoint types.

***

### Resources

* [Official: Archival Store and Service](https://docs.sui.io/develop/accessing-data/archival-store)
* [Official: Querying Historical Data with Archival Service](https://docs.sui.io/develop/accessing-data/archival-store/using-archival-store)
* [grpcurl tool](https://github.com/fullstorydev/grpcurl)


# GraphQL

#### Overview

GraphQL RPC is a flexible, ergonomic interface for reading data from the Sui blockchain, submitting transactions, and simulating transactions. Instead of a fixed set of methods, GraphQL lets you request exactly the fields you need — across transactions, objects, events, balances, and checkpoints — in a single composable query, reducing overfetching and round trips.

Under the hood, our GraphQL service is backed by the Sui **General-purpose Indexer**, which ingests checkpoint data into a Postgres-compatible database and serves it through a strongly typed schema. It is the best fit for frontends, explorers, dashboards, and any data-driven application that benefits from filtered, nested, or point-in-time queries.

> **Note:** JSON-RPC is deprecated and will be deactivated in October 2026. GraphQL RPC and [gRPC](https://docs.triton.one/chains/sui/grpc) are the two supported replacements. Migrating to either is strongly recommended for all production integrations.

***

#### When to use GraphQL

GraphQL is the best choice when your application:

* Needs to render **structured results in a frontend**, such as wallets, explorers, and dashboards.
* Benefits from **flexible, composable queries** that fetch only the fields you need and combine multiple entities (transactions, objects, events, balances) in one request.
* Requires **filtered historical access**, such as all transactions sent by an address, all transactions that called a given function, or all live objects of a given type.
* Wants **consistent, point-in-time reads** across one or more requests, as if the responses came from a snapshot at a specific checkpoint.

For **high-performance, low-latency point lookups** and **real-time checkpoint streaming**, use [gRPC](https://docs.triton.one/chains/sui/grpc) instead. Many production stacks use both: gRPC for hot-path lookups and streaming, GraphQL for flexible reads and frontends.

***

#### How it works

The GraphQL service answers each request from one of two backing data sources, each with its own retention window. Understanding which source serves which query helps you set the right expectations for data availability:

| Data source                     | Example queries                                                          | Retention on our service      |
| ------------------------------- | ------------------------------------------------------------------------ | ----------------------------- |
| **Consistent store**            | Live objects owned by an address, live objects by type, address balances | \~1 hour (recent checkpoints) |
| **Indexed database (Postgres)** | `transactions`, `events`, `checkpoints`, and filtered historical queries | **30 days**                   |

> **Important:** GraphQL queries are served exclusively from the Consistent Store and the Postgres-indexed database — they never reach beyond these two sources. Our service retains **30 days** of indexed checkpoint data, so filtered and historical GraphQL queries (transactions, events, checkpoints) resolve only within this 30-day window. There is **no automatic fallback** to Archival. For records older than 30 days, query our [Archival Storage and Services](https://docs.triton.one/chains/sui/archival-storage-and-services) endpoint directly — it serves the complete history of Sui mainnet from genesis via the gRPC `LedgerService` interface.

***

#### Endpoints

The GraphQL service is served over HTTPS at the `/graphql` path on your Sui endpoint. All queries are sent as `POST` requests with a JSON body containing your GraphQL `query` and optional `variables`.

**Free Test Endpoint (Mainnet — rate-limited)**

We provide a free shared GraphQL endpoint for testing and evaluation. It is strictly rate-limited and intended for development use only, **not for production traffic**.

```
https://mainnet.sui.rpcpool.com/graphql
```

No authentication token is required for this endpoint.

***

**Shared Clients**

Shared plan clients access GraphQL via the standard shared endpoint. Your unique endpoint hostname is available in the [client panel](https://customers.triton.one/).

**Endpoint format:**

```
https://XXX.sui.rpcpool.com/graphql
```

Replace `XXX` with your specific endpoint slug shown in the panel.

**Authentication** is required via the `X-Token` header:

```
X-Token: <your-token>
```

Both your endpoint and token can be found in your [client panel](https://customers.triton.one/).

***

**Dedicated Clients**

Dedicated plan clients have a private endpoint provisioned exclusively for their use.

**Endpoint format:**

```
https://XXX.sui.rpcpool.com/graphql
```

Replace `XXX` with your dedicated endpoint slug shown in the panel.

**Authentication** is required via the `X-Token` header:

```
X-Token: <your-token>
```

Both your endpoint and token can be found in your [client panel](https://customers.triton.one/).

***

#### Authentication

All non-free endpoints require authentication with your token — the same token used for your standard gRPC endpoint. You can find it in the [client panel](https://customers.triton.one/). There are two equivalent ways to supply it.

**Option 1 — `X-Token` header:**

```bash
curl -X POST https://XXX.sui.rpcpool.com/graphql \
  -H "X-Token: <your-token>" \
  -H "Content-Type: application/json" \
  -d '{ "query": "{ epoch { referenceGasPrice } }" }'
```

**Option 2 — token in the URL:**

```bash
curl -X POST https://XXX.sui.rpcpool.com/<your-token>/graphql \
  -H "Content-Type: application/json" \
  -d '{ "query": "{ epoch { referenceGasPrice } }" }'
```

The URL form is convenient for tools that can only set a URL and not custom headers. Both methods are equivalent — use whichever fits your client.

The free test endpoint accepts the same requests with no token at all:

```bash
curl -X POST https://mainnet.sui.rpcpool.com/graphql \
  -H "Content-Type: application/json" \
  -d '{ "query": "{ epoch { referenceGasPrice } }" }'
```

***

#### Quickstart

A GraphQL request is a JSON object with a `query` string and, optionally, a `variables` object. Below are the two most common request shapes.

**A simple query**

This query reads only current chain state — the chain identifier and the reference gas price for the current epoch — so it returns immediately without touching historical or archival data.

```bash
curl -X POST https://XXX.sui.rpcpool.com/graphql \
  -H "X-Token: <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ chainIdentifier epoch { referenceGasPrice } }"
  }'
```

**Response:**

```json
{
  "data": {
    "chainIdentifier": "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S",
    "epoch": {
      "referenceGasPrice": "100"
    }
  }
}
```

***

#### Example Queries

The following examples cover common tasks. Each is a GraphQL document you send as the `query` field of a request; where a query declares variables, supply them in the request's `variables` object.

**Reference gas price for the latest epoch**

```graphql
query {
  epoch {
    referenceGasPrice
  }
}
```

**A transaction by its digest**

Fetch a transaction and its effects — gas sponsor, gas price and budget, execution status, and the checkpoint it landed in.

```graphql
query ($digest: String!) {
  transaction(digest: $digest) {
    gasInput {
      gasSponsor { address }
      gasPrice
      gasBudget
    }
    effects {
      status
      timestamp
      checkpoint { sequenceNumber }
      epoch { epochId referenceGasPrice }
    }
  }
}
```

**Variables:**

```json
{ "digest": "<transaction-digest>" }
```

**Coins and balances owned by an address**

```graphql
query ($address: SuiAddress!) {
  address(address: $address) {
    balance {
      totalBalance
      coinType { repr }
    }
  }
}
```

**All transactions that touched an object**

Trace every transaction that modified, transferred, or deleted a given object — useful for following a `Coin`, `StakedSui`, or NFT.

```graphql
query ($objectID: SuiAddress!) {
  transactions(filter: { affectedObject: $objectID }) {
    pageInfo { hasNextPage endCursor }
    nodes {
      digest
      sender { address }
      effects {
        objectChanges {
          nodes { address }
        }
      }
    }
  }
}
```

**Filter transactions by function**

Find the last 10 transactions that called a specific Move function.

```graphql
query {
  transactions(
    last: 10
    filter: { function: "0x2::transfer::public_transfer" }
  ) {
    nodes { digest }
  }
}
```

**Execute a transaction**

GraphQL is not read-only. `executeTransaction` submits a signed transaction, and you can select fields from `effects` in the same request to read the result immediately, without waiting for a separate indexed query.

```graphql
mutation ($tx: String!, $sigs: [String!]!) {
  executeTransaction(transactionDataBcs: $tx, signatures: $sigs) {
    errors
    effects {
      status
      gasEffects {
        gasSummary { computationCost }
      }
    }
  }
}
```

`transactionDataBcs` is the serialized unsigned transaction data (for example, from `sui client call --serialize-unsigned-transaction`), and `signatures` come from signing that data (for example, with `sui keytool sign`).

**Simulate a transaction**

Use `simulateTransaction` to preview effects, estimate gas, or test logic without committing onchain.

```graphql
query ($tx: JSON!) {
  simulateTransaction(transaction: $tx, checksEnabled: true, doGasSelection: true) {
    effects {
      status
      gasEffects {
        gasSummary { computationCost storageCost }
      }
    }
    outputs {
      returnValues { value { json } }
    }
  }
}
```

***

#### Key Concepts

**Pagination and connections**

Fields that return multiple results are **connections** following the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). They accept `first`/`after` (forward) or `last`/`before` (backward) arguments and return `pageInfo` (with `hasNextPage` and `endCursor`) alongside `nodes`. Read the first page, then pass the returned `endCursor` as `after` to fetch the next page. Cursors are opaque — only use a cursor value returned by a previous response.

```graphql
query ($after: String) {
  checkpoints(first: 5, after: $after) {
    pageInfo { hasNextPage endCursor }
    nodes { digest timestamp }
  }
}
```

**Scope and consistency**

Every request is evaluated as of a single checkpoint. By default this is the latest checkpoint the service has fully indexed. You can pin queries to an earlier checkpoint for historical reads using `checkpoint(sequenceNumber: ...) { query { ... } }`, and cursors for live-object queries encode the checkpoint at which pagination began, so later pages remain consistent even as new checkpoints arrive.

**Limits**

Requests are validated against complexity and payload rules (query depth, input/output node counts, page sizes, and request timeouts) in addition to rate limits. You can query the active limits at runtime:

```graphql
{
  serviceConfig {
    maxQueryDepth
    maxQueryNodes
    maxOutputNodes
    maxPageSize(type: "Query", field: "objects")
    queryTimeoutMs
  }
}
```

**Checking the available range**

Because retention varies by data source, check the available checkpoint range before starting a long pagination run or a historical query:

```graphql
{
  serviceConfig {
    availableRange(type: "Query", field: "transactions", filters: ["affectedAddress"]) {
      first { sequenceNumber }
      last { sequenceNumber }
    }
  }
}
```

If a query requests data outside the available range, it returns an "outside available range" error — use fresh cursors and keep your checkpoint bounds within the 30-day window.

***

#### Data Retention and Historical Data

Our GraphQL service retains **30 days** of indexed checkpoint data. This covers the overwhelming majority of frontend, dashboard, and analytics workloads without the cost and latency of full historical indexing.

For data older than 30 days, use the [Archival Storage and Services](https://docs.triton.one/chains/sui/archival-storage-and-services) endpoint. Archival serves the complete history of Sui mainnet from genesis and exposes the gRPC `LedgerService` interface for point lookups of transactions, checkpoints, and historical object states.

> **Note:** GraphQL does not automatically fall back to Archival for pruned data. If your application needs records older than 30 days, query the Archival endpoint directly. A common pattern is to serve recent data from GraphQL and route deep-history point lookups to Archival.

***

#### Exploring the schema

The Sui GraphQL schema is introspectable, so any GraphQL client library can discover the full set of available types and fields programmatically via a standard introspection query. For the complete, human-readable schema — every query, mutation, type, and field — see the [official Sui GraphQL reference](https://docs.sui.io/references/sui-graphql).

***

#### Endpoint Summary

| Plan          | GraphQL Endpoint                          | Auth                             |
| ------------- | ----------------------------------------- | -------------------------------- |
| **Free test** | `https://mainnet.sui.rpcpool.com/graphql` | None (rate-limited)              |
| **Shared**    | `https://XXX.sui.rpcpool.com/graphql`     | `X-Token` header or token in URL |
| **Dedicated** | `https://XXX.sui.rpcpool.com/graphql`     | `X-Token` header or token in URL |

> `XXX` is your unique endpoint slug, visible in the [client panel](https://customers.triton.one/). The token is the same one used for your gRPC and Archival endpoints.

***

#### Resources

* [gRPC](https://docs.triton.one/chains/sui/grpc) — high-performance point lookups and streaming
* [Archival Storage and Services](https://docs.triton.one/chains/sui/archival-storage-and-services) — full historical data beyond 30 days
* [Official: GraphQL for Sui RPC](https://docs.sui.io/develop/accessing-data/graphql/graphql-rpc)
* [Official: Querying Data with GraphQL RPC](https://docs.sui.io/develop/accessing-data/graphql/query-with-graphql)
* [Sui GraphQL schema reference](https://docs.sui.io/references/sui-graphql)


# Monad

Monad is a Layer-1 blockchain delivering high performance, decentralization, and EVM compatibility with 10.000 TPS, 0.8s finality, and 0.4s block times

## Overview

### Block explorers

* Mainnet: <https://monadvision.com>
* Testnet: <https://testnet.monadvision.com/>

### Developer docs

* [https://docs.monad.xyz](https://docs.monad.xyz/)

## APIs

{% hint style="warning" %}
The differences between Monad and the standard Geth client are described here: <https://docs.monad.xyz/reference/rpc-differences>
{% endhint %}

### JSON-RPC

Monad supports most of the JSON-RPC methods available in Geth. You can find the official documentation [here](https://docs.monad.xyz/reference/json-rpc/)

The linked documentation also includes cURL examples where you just need to replace the RPC URL, e.g:

```bash
curl --request POST \
     --url <TRITON_URL> \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "id": 0,
  "jsonrpc": "2.0",
  "method": "eth_blockNumber",
  "params": []
}
'
```

Expected result:

```bash
{
    "jsonrpc": "2.0",
    "result": "0x23feb61",
    "id": 0
}
```

### Websocket

The websocket API is also available in Monad. You can find the official documentation [here](https://docs.monad.xyz/reference/websockets)

See an example below how to test websocket using the `wscat` utility:

**Install node-ws in Linux**:

```bash
sudo apt update
sudo apt install node-ws
```

**Subscribe to websockets**:

```bash
wscat -c wss://<TRITON_URL>
Connected (press CTRL+C to quit)
> { "id": 1, "jsonrpc": "2.0", "method": "eth_subscribe", "params": ["newHeads"] }
```

Expected result:

{% code overflow="wrap" %}

```bash
< {"jsonrpc":"2.0","result":"0x42cef98808e973c28e55c2ecd59c85c5","id":1}
< {"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x42cef98808e973c28e55c2ecd59c85c5","result":{"hash":"0x3b4e76a0bcfcc7b6f6eb0c44ca8d95f59ae3feb9fb378922b5e9697beee6178e","parentHash":"0x68f6ed9d35ad80209efd9c6a8c23557da367a424233ef15b6c9a562544fa059e","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x63ce5e9d70b77c60ccc68158eafe6398c9ce61f8","stateRoot":"0x5d232d542a1beee75e0803af58d34f5d6305ea8c6c6da01535d793b54e300590","transactionsRoot":"0x8bcc011db89df7ec8f35b55fdc6f4d0fe3823e6b08df32f088b318348225b71d","receiptsRoot":"0xfcde62dec790860f76d45e98f50df4878b6880860305c8b3990d5432535d6b09","logsBloom":"0x242c804d010488e04260419090320020c1820200100c00008024002430004002040e2402402001000014150106020821200400504410200020802a002120008c46401600204020880000000842000af12008060108c480a14064000080414800040200200a8a08400100a4a4190808054c0044010130270419012390000c08000040400419001000074080301700000000400c510201080848000041400000021600048090048a210040000009504001000000000006080392400202a05808000410008a400460000801802038481110020041000a00601000000816006228008018c000030041040c4081089400182004001000040c02410048002412000080","difficulty":"0x0","number":"0x23fee0f","gasLimit":"0xbebc200","gasUsed":"0x203788b","timestamp":"0x6924aa80","extraData":"0x0000000000000000000000000000000000000000000000000000000000000000","mixHash":"0x753243a7ce528c3925b31dc1b6c33fc784e6a68ebc2dedbbef8b4c53fe25de66","nonce":"0x0000000000000000","baseFeePerGas":"0x174876e800","withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","requestsHash":"0x0000000000000000000000000000000000000000000000000000000000000000","totalDifficulty":"0x0","size":"0x30f"}}}
```

{% endcode %}

*Note: JS examples are also available in the official documentation.*


# Pythnet

{% hint style="danger" %}
Pyth DAO has announced the deprecation of Pyth Core on July 31st, 2026. As of July 30th, 2026, Triton One Pythnet and Hermes endpoints will stop serving data — see [Pyth price feeds](https://www.pyth.network/price-feeds) for direct integration.
{% endhint %}

We provide dedicated access points for Pythnet RPC as well as [Hermes](broken://pages/FxExyeoZAajosSFwtmV3) API endpoints with all Triton subscriptions.&#x20;


# Others

Should you require other chains than our standard offering? Get in touch at <help@triton.one>. We run validators and nodes across a wide range of blockchain ecosystems and are always happy to lend our expertise to run dedicated setups for you.&#x20;


# Introduction

We actively contribute to the development of the Metaplex Digital Assets Standard API (DAS) that is used to interact with Solana NFTs, SPL (Solana Program Library) Tokens and Token-2022 (Token Extension Program), ensuring also production-ready indexing and caching layer.

DAS API provides a fast and scalable way to query digital assets on the Solana blockchain, including Fungible Tokens, Regular and Compressed NFTs. The decompression of NFTs and data caching is done server-side, providing a convenient way to access Digital Assets data and metadata. It is available via all our shared and dedicated endpoints, fully integrated with our RPC Pool infrastructure.

DAS API offers a comprehensive set of features for querying digital assets, including:

* The transaction history of any compressed asset
* Current token holders and balances
* Search and filtering by owner, creator, authority, or collection
* Data across regular and programmable NFTs, compressed NFTs, MPL Core, fungible tokens, and metaplex agent tokens
* Merkle proofs for compressed assets

## How to use

The DAS API is enabled by default on all our shared and dedicated endpoints. You can use any of our Solana mainnet RPC endpoints with the DAS API methods, and your queries will be routed automatically.

Available methods are listed and documented in the [API Methods](https://github.com/rpcpool/customer-docs/blob/gitbook/digital-assets-api/digital-assets-api/metaplex-digital-assets-api/README.md) section.

The DAS API is billed on a per-request basis ($50 per million requests). To see detailed billing information, check the Billable Items Chart in your account panel at the customers.triton.one.

[Ratelimits](https://github.com/rpcpool/customer-docs/blob/gitbook/rpc-pool/ratelimits.md) as per your standard RPC Pool will apply.

## Further reading

To learn more about account state compression and Digital Assets, see [developers.metaplex.com/das-api](https://developers.metaplex.com/das-api).

Client and examples: [https://github.com/metaplex-foundation/digital-asset-standard-api](https://github.com/metaplex-foundation/digital-asset-standard-api/tree/main/clients/js#examples)


# Fungible Assets

### **Overview**

The DAS API indexes all mints and token accounts for the Solana Token Program and Token Extensions Program. It allows querying token balances, metadata, and associated program details.

#### **Supported Queries**

* **GetAsset**: Fetch details of a specific token mint.
* **GetAssetsByOwner**: Retrieve all tokens (fungible and non-fungible) held by an account.
* **SearchAssets**: Search for assets using filters, including token type.

#### **Querying Token Balances**

DAS API supports querying token balances across:

* SPL Tokens
* Token22 Tokens
* NFTs (regular and compressed NFTs)

**Example Request**

```json
{
    "jsonrpc": "2.0",
    "id": "string",
    "method": "searchAssets",
    "params": {
        "ownerAddress": "5aZZ4duJUKiMsJN9vRsoAn4SDX7agvKu7Q3QdFWRfWze",
        "tokenType": "All"
    }
}
```

**Example Response**

```json
{
    "id": "TokenMintAddress",
    "content": {
        "metadata": {
            "name": "Jito Staked SOL",
            "symbol": "JitoSOL",
            "token_standard": "Fungible"
        },
        "token_info": {
            "symbol": "EXT",
            "balance": 1000000000,
            "supply": 500000000000,
            "decimals": 9,
            "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "associated_token_address": "H7iLu4DPFpzEx1AGN8BCN7Qg966YFndt781p6ukhgki9"
        }
    }
}
```

The DAS API supports Token22 tokens and their extensions. The response includes the `mint_extensions` field when applicable.

**Example Token22 Response**

```json
{
    "mint_extensions": {
        "transfer_fee_config": {
            "withheld_amount": 0,
            "newer_transfer_fee": {
                "epoch": 500,
                "maximum_fee": 1000000000,
                "transfer_fee_basis_points": 500
            },
            "withdraw_withheld_authority": "AuthorityPublicKey",
            "transfer_fee_config_authority": "AuthorityPublicKey"
        }
    }
}
```

### **Conclusion**

The DAS API allows querying of fungible tokens, SPL and Token22, including metadata, balances, and associated program details.


# API Methods

The API follows the JSON-RPC 2.0 specification, requiring requests to include a method name, parameters, and a unique ID.

| Method                                                                                                | Description                                                                                                                                                                          |
| ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [getAsset](/digital-assets-api/metaplex-digital-assets-api/get-asset)                                 | Retrieve detailed information about an asset using its unique ID.                                                                                                                    |
| [getAssets](/digital-assets-api/metaplex-digital-assets-api/get-assets)                               | Retrieve detailed information about multiple assets using theirs unique IDs.                                                                                                         |
| [getAssetProof](/digital-assets-api/metaplex-digital-assets-api/get-asset-proof)                      | Obtain a Merkle proof for verifying the integrity of a compressed asset.                                                                                                             |
| [getAssetProofs](/digital-assets-api/metaplex-digital-assets-api/get-asset-proofs)                    | Fetch Merkle proofs for multiple compressed assets in a single request.                                                                                                              |
| [getAssetSignatures](/digital-assets-api/metaplex-digital-assets-api/get-asset-signatures)            | Fetch transaction signatures associated with a compressed asset.                                                                                                                     |
| [getAssetsByAuthority](/digital-assets-api/metaplex-digital-assets-api/get-assets-by-authority)       | Fetch a list of assets controlled by a specific authority.                                                                                                                           |
| [getAssetsByCreator](/digital-assets-api/metaplex-digital-assets-api/get-assets-by-creator)           | List all assets that were created by a specified address.                                                                                                                            |
| [getAssetsByGroup](/digital-assets-api/metaplex-digital-assets-api/get-assets-by-group)               | \[TEMPORARILY DISABLED] Get assets that belong to a specified group, identified by a key and value. This method is currently unavailable due to scheduled performance optimizations. |
| [getAssetsByOwner](/digital-assets-api/metaplex-digital-assets-api/get-assets-by-owner)               | Retrieve all assets associated with a given owner’s public key.                                                                                                                      |
| [searchAssets](/digital-assets-api/metaplex-digital-assets-api/search-assets)                         | Perform a search and apply filters to find assets based on various criteria.                                                                                                         |
| [getNftEditions](/digital-assets-api/metaplex-digital-assets-api/get-nft-editions)                    | Retrieve all printable editions for a master edition NFT mint.                                                                                                                       |
| [getTokenAccounts](/digital-assets-api/metaplex-digital-assets-api/get-token-accounts)                | Retrieve details of all token accounts associated with a given mint or owner.                                                                                                        |
| [getTokenLargestAccounts](/digital-assets-api/metaplex-digital-assets-api/get-token-largest-accounts) | Retrieve 20 largest token accounts for a specific SPL Token.                                                                                                                         |

Each API method is explained in detail on its own page, including required parameters and example requests. Navigate through the documentation to explore each method’s capabilities.


# Get Asset

This method retrieves detailed information about a specific digital asset/NFT. The metadata returned includes information about the asset URL, metadata, collection, creators, authorities, compression status and ownership.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset)

| Name | Description                                     | Required |
| ---- | ----------------------------------------------- | -------- |
| id   | The unique identifier of the asset to retrieve. | Yes      |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getAsset",
    "params": {
      "id": "7k7YqWc85BtyTx5UyvNnogv2jPHVe4BZ9HQsFLM8VnoJ"
    }
}
```

</details>


# Get Assets

This method retrieves detailed information about a multiple digital assets/NFTs. The metadata returned includes information about the asset URL, metadata, collection, creators, authorities, compression status and ownership. Alias of `getAsset` method is `getAssetBatch`.

<details>

<summary>Request (POST)</summary>

#### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-assets)

| Name | Description                     |
| ---- | ------------------------------- |
| ids  | A set of asset IDs to retrieve. |

```json
{
  "jsonrpc": "2.0",
  "id": "123",
  "method": "getAssets",
  "params": {
    "ids": [
      "7k7YqWc85BtyTx5UyvNnogv2jPHVe4BZ9HQsFLM8VnoJ",
      "HP2QQgwfTgHNRcX7g3HHMNKxXjAEvQsr1wComqbPfWAR"
    ]
  }
} 
```

</details>


# Get Asset Proof

Fetches the proof path for a given compressed asset/NFT. This method is required for submitting changes to a compressed merkle tree which requires the proof path for the tree it belongs to.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset-proof)

| Name | Description                                    | Required |
| ---- | ---------------------------------------------- | -------- |
| id   | The unique identifier of the compressed asset. | Yes      |

<details>

<summary>Request (POST)</summary>

```json
{
  "jsonrpc": "2.0",
  "id": "123",
  "method": "getAssetProof",
  "params": {
    "id": "EZsmgudhX6EFVfrdxmShDiNj365E85x7kpLJCVvPLVSH"
  }
} 
```

</details>


# Get Asset Proofs

Get merkle proofs for compressed assets by their IDs.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset-proofs)

| Name | Description                                 | Required |
| ---- | ------------------------------------------- | -------- |
| ids  | A set of asset IDs used to retrieve proofs. | Yes      |

<details>

<summary>Request (POST)</summary>

```json
{
  "jsonrpc": "2.0",
  "id": "123",
  "method": "getAssetProofs",
  "params": {
    "ids": [
      "EZsmgudhX6EFVfrdxmShDiNj365E85x7kpLJCVvPLVSH",
      "Hhxb7pNvwi76WTSqFjMX5yXjSso1S3agEJ9kQZP9NQeJ"
    ]
  }
} 
```

</details>


# Get Asset Signatures

This method retrieves the transaction signatures associated with a compressed digital asset/NFT. It can be identified by its ID or by tree and leaf index.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset-signatures)

| Name      | Description                                    | Required       |
| --------- | ---------------------------------------------- | -------------- |
| id        | The unique identifier of the compressed asset. | or tree + leaf |
| tree      | The tree corresponding to the leaf.            | or id          |
| leafIndex | The leaf index of digital compressed asset.    | or id          |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getAssetSignatures",
    "params": {
      "id": "EZsmgudhX6EFVfrdxmShDiNj365E85x7kpLJCVvPLVSH"
    }
}
```

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getAssetSignatures",
    "params": {
      "tree": "J2aFAeninyTqDjucueEHBTN6JDo1i9cZ7LzfNcvnnuo4",
      "leafIndex": 161957
    }
}
```

</details>


# Get Assets By Authority

Fetch all the assets belonging to a specific authority. This allows paged responses in order to easily fetch a large number of records.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset-by-authority)

| Name             | Description                                                                                                                                                                                       | Required |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| authorityAddress | Address of the asset authority                                                                                                                                                                    | Yes      |
| sortBy           | The Sorting Criteria: Defined as an object `{ sortBy: , sortDirection: }`, where `sortBy` can be `"created", "updated", "recentAction", or "none"`, and `sortDirection` can be `"asc" or "desc"`. | No       |
| limit            | The maximum number of assets to retrieve.                                                                                                                                                         | No       |
| page             | The index of the "page" to retrieve.                                                                                                                                                              | No       |
| before           | Fetch assets before the given ID.                                                                                                                                                                 | No       |
| after            | Fetch assets after the given ID.                                                                                                                                                                  | No       |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getAssetsByAuthority",
    "params": [
        "3pMvTLUA9NzZQd4gi725p89mvND1wRNQM3C8XEv1hTdA",
        {
            "sortBy": "created",
            "sortDirection": "desc"
        },
        50,
        1,
        null,
        null
    ]
}
```

</details>


# Get Assets By Creator

This method provides the list of assets created by a particular creator. By using this method, developers and users can programmatically access and retrieve the assets created or owned by a specific creator, enabling functionalities like showcasing an artist's portfolio or facilitating transactions involving the creator's assets.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset-by-creator)

| Name           | Description                                                                                                                                                                                                               | Required |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| creatorAddress | The address of the creator of the assets.                                                                                                                                                                                 | Yes      |
| onlyVerified   | Indicates whether to retrieve only verified assets or not. Recommended to set to true to be sure that the asset belongs to the creator.                                                                                   | No       |
| sortBy         | Sorting criteria. This is specified as an object `{ sortBy: <value>, sortDirection: <value> }`, where `sortBy` is one of `["created", "updated", "recentAction", "none"]` and `sortDirection` is one of `["asc", "desc"]` | No       |
| page           | The index of the "page" to retrieve.                                                                                                                                                                                      | No       |
| before         | Fetch assets before the given ID.                                                                                                                                                                                         | No       |
| after          | Fetch assets after the given ID.                                                                                                                                                                                          | No       |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getAssetsByCreator",
    "params": [
        "D3XrkNZz6wx6cofot7Zohsf2KSsu2ArngNk8VqU9cTY3",
        true,
        {
            "sortBy": "created",
            "sortDirection": "desc"
        },
        50,
        1,
        null,
        null
    ]
}
```

</details>


# Get Assets By Group

{% hint style="warning" %}
Temporary Suspension

This method is currently temporarily disabled for maintenance and performance optimization. Please avoid calling this method until further notice.
{% endhint %}

The method then returns a response containing information about all the assets belonging to a specific group, like a Collection.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset-by-group)

| Name       | Description                                                                                                                                                                                                               | Required |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| groupKey   | The key of the group (e.g., `"collection"`).                                                                                                                                                                              | Yes      |
| groupValue | The value of the group.                                                                                                                                                                                                   | Yes      |
| sortBy     | Sorting criteria. This is specified as an object `{ sortBy: <value>, sortDirection: <value> }`, where `sortBy` is one of `["created", "updated", "recentAction", "none"]` and `sortDirection` is one of `["asc", "desc"]` | No       |
| page       | The index of the "page" to retrieve.                                                                                                                                                                                      | No       |
| before     | Fetch assets before the given ID.                                                                                                                                                                                         | No       |
| after      | Fetch assets after the given ID.                                                                                                                                                                                          | No       |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getAssetsByGroup",
    "params": {
        "groupKey": "collection",
        "groupValue": "BUjZjAS2vbbb65g7Z1Ca9ZRVYoJscURG5L3AkVvHP9ac",
        "page": 1,
        "limit": 50
    }
}
```

</details>


# Get Assets By Owner

The method returns a list of assets owned by a certain address or account. You can query the API for all assets associated with that owner by supplying the owner's pubkey as a parameter.

This is the more efficient version of \`getTokenAccountsByOwner\` in the standard Solana RPC API and allows you to access and retrieve the assets owned by a specific address, enabling functionalities like displaying a user's NFT collection, managing ownership, or facilitating transactions involving the owner's assets.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/get-asset-by-owner)

| Name         | Description                                                                                                                                                                                       | Required |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| ownerAddress | Address of the asset authority                                                                                                                                                                    | Yes      |
| sortBy       | The Sorting Criteria: Defined as an object `{ sortBy: , sortDirection: }`, where `sortBy` can be `"created", "updated", "recentAction", or "none"`, and `sortDirection` can be `"asc" or "desc"`. | Yes      |
| limit        | The maximum number of assets to retrieve.                                                                                                                                                         | No       |
| page         | The index of the "page" to retrieve.                                                                                                                                                              | No       |
| before       | Fetch assets before the given ID.                                                                                                                                                                 | No       |
| after        | Fetch assets after the given ID.                                                                                                                                                                  | No       |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getAssetsByOwner",
    "params": [
        "9kPPbeBAvCtJCZ98EFKabxp7wTeFQRseCYDRdovyfUfz",
        {
            "sortBy": "created",
            "sortDirection": "desc"
        },
        50,
        1,
        null,
        null
    ]
}
```

</details>


# Search Assets

The method allows users to query and retrieve information about specific NFT assets based on certain criteria (custom-query). This query provides filters such as asset name, owner address, collection ID, or other relevant attributes to narrow down your search. Using this method, users can programmatically explore the Metaplex ecosystem, retrieve information about specific NFT assets, and perform various operations related to those assets, such as transferring ownership or displaying asset metadata.

### Parameters [(Source)](https://developers.metaplex.com/das-api/methods/search-assets)

| Name                | Description                                                                                                                                                                                                                | Required |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `negate`            | Indicates whether the search criteria should be inverted or not.                                                                                                                                                           | No       |
| `conditionType`     | Indicates whether to retrieve all (`"all"`) or any (`"any"`) asset that matches the search criteria.                                                                                                                       | No       |
| `interface`         | The interface value (one of `["V1_NFT", "V1_PRINT" "LEGACY_NFT", "V2_NFT", "FungibleAsset", "Custom", "Identity", "Executable"]`).                                                                                         | No       |
| `ownerAddress`      | The address of the owner.                                                                                                                                                                                                  | No       |
| `ownerType`         | Type of ownership `["single", "token"]`.                                                                                                                                                                                   | No       |
| `creatorAddress`    | The address of the creator.                                                                                                                                                                                                | No       |
| `creatorVerified`   | Indicates whether the creator must be verified or not.                                                                                                                                                                     | No       |
| `authorityAddress`  | The address of the authority.                                                                                                                                                                                              | No       |
| `grouping`          | The grouping `["key", "value"]` pair.                                                                                                                                                                                      | No       |
| `delegateAddress`   | The address of the delegate.                                                                                                                                                                                               | No       |
| `frozen`            | Indicates whether the asset is frozen or not.                                                                                                                                                                              | No       |
| `supply`            | The supply of the asset.                                                                                                                                                                                                   | No       |
| `supplyMint`        | The address of the supply mint.                                                                                                                                                                                            | No       |
| `compressed`        | Indicates whether the asset is compressed or not.                                                                                                                                                                          | No       |
| `compressible`      | Indicates whether the asset is compressible or not.                                                                                                                                                                        | No       |
| `royaltyTargetType` | Type of royalty `["creators", "fanout", "single"]`.                                                                                                                                                                        | No       |
| `royaltyTarget`     | The target address for royalties.                                                                                                                                                                                          | No       |
| `royaltyAmount`     | The royalties amount.                                                                                                                                                                                                      | No       |
| `burnt`             | Indicates whether the asset is burnt or not.                                                                                                                                                                               | No       |
| `sortBy`            | Sorting criteria. This is specified as an object `{ sortBy: <value>, sortDirection: <value> }`, where `sortBy` is one of `["created", "updated", "recentAction", "none"]` and `sortDirection` is one of `["asc", "desc"]`. | No       |
| `limit`             | The maximum number of assets to retrieve.                                                                                                                                                                                  | No       |
| `page`              | The index of the "page" to retrieve.                                                                                                                                                                                       | No       |
| `before`            | Retrieve assets before the specified ID.                                                                                                                                                                                   | No       |
| `after`             | Retrieve assets after the specified ID.                                                                                                                                                                                    | No       |
| `jsonUri`           | The value for the JSON URI.                                                                                                                                                                                                | No       |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "searchAssets",
    "params": {
        "ownerAddress": "Ewb8XmZ4RbSAxnFY4P4XMtjz5EccYn12bnUF6T4SSvVq",       
        "page": 1,
        "limit": 50
    }
}
```

</details>


# Get NFT Editions

Get all printable editions for a master edition NFT mint.

### Parameters

| Name          | Description                             | Required |
| ------------- | --------------------------------------- | -------- |
| `mintAddress` | The mint address of the master edition. | Yes      |
| `page`        | The current pagination page.            | No       |
| `limit`       | Number of results per page.             | No       |
| `cursor`      | Optional pagination cursor.             | No       |
| `before`      | Return results before the cursor.       | No       |
| `after`       | Return results after the cursor.        | No       |

<details>

<summary>Request (POST)</summary>

```json
{
    "id": "123",
    "jsonrpc": "2.0",
    "method": "getNftEditions",
    "params": {
      "mintAddress": "BUaiggSfm81ZRAeW572dNf1BhXCzZxaWrGNRm5PjSQzY",
      "page": 1,
      "limit": 100
    }
}
```

</details>

<details>

<summary>Response 200</summary>

```json
{
    "jsonrpc": "2.0",
    "result": {
        "total": 0,
        "limit": 100,
        "master_edition_address": "4faJkL1F5yLKTf9DSgqBmp2DJeUKk9fyfZzMKM1nMbzD",
        "supply": 0,
        "max_supply": 0,
        "page": 1
    },
    "id": "123"
}
```

</details>


# Get Token Accounts

Retrieve all token accounts linked to a specific mint or owner. Use this to efficiently list all holders of an SPL token or view all tokens owned by a given address.

### Parameters

| Name           | Description                       | Required            |
| -------------- | --------------------------------- | ------------------- |
| `mintAddress`  | The address of associated mint.   | Yes or ownerAddress |
| `ownerAddress` | The owner address of the tokens.  | Yes or mintAddress  |
| `page`         | The current pagination page.      | No                  |
| `limit`        | Number of results per page.       | No                  |
| `cursor`       | Optional pagination cursor.       | No                  |
| `before`       | Return results before the cursor. | No                  |
| `after`        | Return results after the cursor.  | No                  |

<details>

<summary>Request (POST)</summary>

```json
{
    "id": "123",
    "jsonrpc": "2.0",
    "method": "getTokenAccounts",
    "params": {
      "mintAddress": "BUaiggSfm81ZRAeW572dNf1BhXCzZxaWrGNRm5PjSQzY",
      "ownerAddress": "BiKcVb6t6YDZczYjWLjXKwdgAUecdQrp1EJXAfRmu3VR",
      "page": 1,
      "limit": 100
    }
}
```

</details>

<details>

<summary>Response 200</summary>

```json
{
    "jsonrpc": "2.0",
    "result": {
        "total": 1,
        "limit": 100,
        "page": 1,
        "token_accounts": [
            {
                "address": "EFaMF6bbRnTwDFUZt9FiTjF8hj6w65kLW43YyhyM85HJ",
                "mint": "BUaiggSfm81ZRAeW572dNf1BhXCzZxaWrGNRm5PjSQzY",
                "amount": 1,
                "owner": "BiKcVb6t6YDZczYjWLjXKwdgAUecdQrp1EJXAfRmu3VR",
                "frozen": true,
                "delegate": null,
                "delegated_amount": 0,
                "close_authority": null,
                "extensions": null
            }
        ]
    },
    "id": "123"
}
```

</details>


# Get Token Largest Accounts

Retrieve the 20 largest token accounts for a specific SPL Token.

### Parameters [(Source)](https://solana.com/docs/rpc/http/gettokenlargestaccounts)

| Name   | Description                                     |
| ------ | ----------------------------------------------- |
| pubkey | The unique identifier of the token to retrieve. |

<details>

<summary>Request (POST)</summary>

```json
{
    "jsonrpc": "2.0",
    "id": "123",
    "method": "getTokenLargestAccounts",
     "params": [
       "jovWMTogqDpoEWUSFSns2Y9rkJkfJHsTgPWJgzCjwCF",
       {
         "commitment": "confirmed"
       }
     ]
}
```

</details>


# Introduction

Solana RPC 2.0

Yellowstone National Park in the Western US is known for its many geysers. Collectively, our new blockchain infrastructure is called Project Yellowstone, and we named each sub-project after one of the geysers at Yellowstone National Park.

The Yellowstone projects form a service explicitly built for high-performance blockchain data delivery. Backend services can use gRPC subscriptions through Dragon's Mouth, and front-end GUIs will feel faster due to Steamboat custom indexes and Whirligig WebSockets.&#x20;

Together with the Solana Foundation, we have also built a new historical archive called Old Faithful, through which all of Solana's transaction and block data is available at low cost and through decentralised means.

We are excited to build a better Solana!


# Dragon's Mouth gRPC Subscriptions

Streaming Account Updates for Backend Applications.

Dragon's Mouth is our Geyser-fed gRPC interface that supports streaming:

* Account Writes
* Transactions
* Deshred transactions (pre-execution, beta)
* Entries
* Block notifications
* Slot notifications

It also supports unary operations:

* getLatestBlockhash
* getBlockHeight
* getSlot
* isValidBlockhash
* subscribeReplayInfo

The gRPC streams and RPC calls are supported through Solana's [Geyser](/chains/solana/geyser) interface. This is the fastest way to receive updates on on-chain events. This interface is more stable and faster than the traditional WebSocket interface. We recommend using gRPC for all future development of backend clients.

Dragon's Mouth also streams transactions as they are processed in real-time. You will receive multiple account updates within the current slot. This contrasts with regular RPC, where you receive only one update at the end of the slot. For DeFi traders, Dragon's Mouth can give you up to a 400ms advantage over other traders!

Use Dragon's Mouth to stream data directly to your application middle-layer hosted on a cloud service provider. Update your backend database with the lowest possible latency.

gRPC is unsupported by web browsers, so Dragon's Mouth is entirely targeted at backend software. Another Yellowstone project, [Whirligig](/project-yellowstone/whirligig-websockets), provides a WebSocket interface to replace the current Solana WebSocket implementation.

### Protocol files

You can find the latest version of protobuf files in the repository <https://github.com/rpcpool/yellowstone-grpc/tree/master/yellowstone-grpc-proto/proto> or use Rust crate <https://crates.io/crates/yellowstone-grpc-proto>.

## Clients/SDKs

We offer sample clients in multiple languages, and you can also use the generic grpcurl client to test the interface. As the underlying gRPC proto can change, it is essential to test with clients matching the current version of the Solana/gRPC interface.

### grpcurl

`grpcurl` is a good client for testing. You will also need the following two Protobuf proto files to describe the protocol:

Example subscription:

```shell
./grpcurl \
  -proto geyser.proto \
  -d '{"slots": { "slots": { } }, "accounts": { "usdc": { "account": ["9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT"] } }, "transactions": {}, "blocks": {}, "blocks_meta": {}}' \
  -H "x-token: <token>" \
  api.rpcpool.com:443 \
  geyser.Geyser/Subscribe
```

Customers should specify their endpoint + token in the example above, developers looking to run their own RPC nodes can test it against their own Solana instances, just remove the x-token header as it's probably not relevant to you.

### client-ubuntu

The yellowstone-grpc project provides a `client-ubuntu` binary for testing gRPC endpoints. Prebuilt binaries are available for Ubuntu 22.04 and 24.04. Download

You can download the latest release from the [Releases](https://github.com/rpcpool/yellowstone-grpc/releases) page.

**Usage Examples**

**Subscribe to all accounts:**

```shell
./client-ubuntu-22.04 --endpoint https://<endpoint> --x-token <token> subscribe --accounts --slots
```

**Subscribe to a specific program** (e.g Raydium)

```shell
./client-ubuntu-22.04 --endpoint https://<endpoint> --x-token <token> subscribe --accounts --slots --accounts-owner 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8
```

Note: You may also add the `--stats` flag to see useful stats about the subscription, like total amount of accounts / slots streamed + bandwidth

### Rust

A sample Rust client is available at <https://github.com/rpcpool/yellowstone-grpc/tree/master/examples/rust>.

### Golang

A sample Golang client is available at <https://github.com/rpcpool/yellowstone-grpc/tree/master/examples/golang>.

### NodeJS/TypeScript

From version `5.1.x` we supercharged the TypeScript SDK performance with Rust using the NAPI framework. You can read more about it [here](https://blog.triton.one/grpc-js-alternative-napi-rust/).

You can include NodeJS Yellowstone gRPC client as a dependency by running the following command:

```
npm install --save @triton-one/yellowstone-grpc

# or, for yarn:

yarn add @triton-one/yellowstone-grpc
```

A sample Typescript/Nodejs client is available at <https://github.com/rpcpool/yellowstone-grpc/tree/master/examples/typescript>. You can also switch the language of code samples to TypeScript in the following documentation.

#### Initializing the client

Once you have installed the client dependency, you can initialize it as follows:

```javascript
import Client from "@triton-one/yellowstone-grpc";

const client = new Client("https://api.rpcpool.com:443", "<insert your token here>");

// connect to the client
await client.connect();

// now you can call the client methods, e.g.:

const version = await client.getVersion(); // gets the version information
console.log(version);
```

Please note that the client is asynchronous, so it is expected that all calls are executed inside an async block or async function.

#### Subscription streams

You can get updates and send requests through the *subscription stream*. You can create it by calling the `client.subscribe()` method:

```typescript
import { SubscribeRequest } from "@triton-one/yellowstone-grpc";

// Create a subscription stream.
const stream = client.subscribe();

// Collecting all incoming events.
stream.on("data", (data) => {
  console.log("data", data);
});

// Create a subscription request.
const request: SubscribeRequest = {
  // you can use the standard JSON request format here.
  // the following documentation describes available requests and filters.
  ...
};

// Sending a subscription request.
await new Promise<void>((resolve, reject) => {
  stream.write(request, (err) => {
    if (err === null || err === undefined) {
      resolve();
    } else {
      reject(err);
    }
  });
}).catch((reason) => {
  console.error(reason);
  throw reason;
});
```

## Excluded Programs

Certain programs are excluded from all Dragon's Mouth gRPC streams and are also unavailable via `getProgramAccounts`.

| Program                         | Address                                       |
| ------------------------------- | --------------------------------------------- |
| Light Protocol / ZK Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` |

**Why is Light Protocol /** **ZK Compression excluded?**

The ZK Compression program generates extremely high-volume account updates. Each update produces a \~10MB blob, resulting in several gigabits per second of data. This volume makes it impractical to include in standard gRPC streams.

From a practical standpoint, most users consuming this data are interested in the **end result** processed by the [Photon indexer](https://github.com/helius-labs/photon), not the raw account blobs. If you need ZK Compression data, that is the recommended way to consume it.

## Example Subscribe Requests

Here are examples of subscribe requests you can make to the gRPC interface.

### Subscribe to an account

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": { "wsol/usdc": { "account": ["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6"] } }, "transactions": {}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": [], "commitment": 1}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {
    "wsol/usdc": {
      "account": ["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6"]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": [],
  "commitment": CommitmentLevel.CONFIRMED
};
```

{% endtab %}
{% endtabs %}

This sample subscribes to the SOL-USDC OpenBook account on `confirmed` commitment level. In the example above, "wsol/usdc" is a client-assigned label. You can specify different JSON files to subscribe to different items. You can combine any of these variables below into a JSON to receive a combination of program, account, block, and slot updates.

### Subscribe to an account with \`account\_data\_slice\`

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{
    "accounts": {
        "usdc": {
            "owner": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
            "filters": [{
                "token_account_state": true
            }, {
                "memcmp": {
                    "offset": 0,
                    "data": {
                        "base58": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
                    }
                }
            }]
        }
    },
    "accounts_data_slice": [{ "offset": 32, "length": 40 }]
}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  "slots": {},
  "accounts": {
    "usdc": {
      "owner": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
      "filters": [{
          "tokenAccountState": true
      }, {
          "memcmp": {
              "offset": 0,
              "data": {
                  "base58": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
              }
          }
      }]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocksMeta": {},
  "entry": {},
  "commitment": CommitmentLevel.CONFIRMED
  "accountsDataSlice": [{ "offset": 32, "length": 40 }],
};
```

{% endtab %}
{% endtabs %}

This sample subscribes to the USDC Tokenkeg accounts. With `account_data_slice` instead of receiving all 165 bytes we receive only 40 bytes from account data (`offset` field with 32 gives us `owner` and `lamports`).

### Subscribe to a program

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": { "solend": {  "owner": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"] } }, "transactions": {}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": [], "commitment": 0}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {
    "solend": {
      "owner": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": [],
  "commitment": CommitmentLevel.PROCESSED
}
```

{% endtab %}
{% endtabs %}

### Subscribe to multiple programs

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": { "programs": {  "owner": [ "So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo", "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"] } }, "transactions": {}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {
    "programs": {
      "owner": [
        "So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo",
        "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
      ]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

OR, if you want different tags for different program updates:

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": { "solend": {  "owner":  ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"] }, "serum": { "owner": ["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"] } }, "transactions": {}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {
    "solend": {
      "owner": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"]
    },
    "serum": {
      "owner": ["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"]
    }
  },
  "transactions": {},
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
}
```

{% endtab %}
{% endtabs %}

### Subscribe to all finalized non-vote and non-failed transactions

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": {}, "transactions": { "alltxs": { "vote": false, "failed": false }}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": [], "commitment": 2}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
import { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {},
  "transactions": {
    "alltxs": {
      "vote": false,
      "failed": false
    }
  },
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": [],
  "commitment": CommitmentLevel.FINALIZED
};
```

{% endtab %}
{% endtabs %}

For transactions, if all fields are empty, then all transactions are broadcasted. Otherwise, fields work as logical `AND`, and values in arrays as logical `OR`. You can include/exclude vote transactions and include/exclude failed transactions.

### Subscribe to non-vote transactions mentioning an account

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": {}, "transactions": { "serum": { "vote": false, "account_include": [ "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" ]}}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {},
  "transactions": {
    "serum": {
      "vote": false,
      "accountInclude": [
        "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
      ]
    }
  },
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

### Subscribe to transactions excluding accounts

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": {}, "transactions": { "serum": { "account_exclude": [ "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin", "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ]}}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {},
  "transactions": {
    "serum": {
      "accountExclude": [
        "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
      ]
    }
  },
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

### Subscribe to transactions mentioning accounts & excluding certain accounts

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "slots": {} }, "accounts": {}, "transactions": { "serum": { "account_include": [ "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" ], "account_exclude": [ "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT" ] }}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {
    "slots": {}
  },
  "accounts": {},
  "transactions": {
    "serum": {
      "accountInclude": [
        "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
      ],
      "accountExclude": [
        "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT"
      ]
    }
  },
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

### Subscribe to a transaction signature

You can subscribe to an individual transaction signature, which will provide updates as the signature is confirmed and finalized.

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": {}, "accounts": {}, "transactions": { "sign": { "signature": "5rp2hL9b6kexex11Mugfs3vfU9GhieKruj4CkFFSnu52WLxiGn4VcLLwsB62XURhMmT1j4CZiXT6FFtYbXsLq2Zs"}}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {},
  "accounts": {},
  "transactions": {
    "sign": {
      "signature": "5rp2hL9b6kexex11Mugfs3vfU9GhieKruj4CkFFSnu52WLxiGn4VcLLwsB62XURhMmT1j4CZiXT6FFtYbXsLq2Zs"
    }
  },
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

### Subscribe to slots

You do not need to provide further details to subscribe to slot notifications. All you'll need to provide is a name for the slot updates that they will be tagged as.

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": { "incoming_slots": {} }, "accounts": {}, "transactions": {}, "blocks": {}, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {
    "incoming_slots": {}
  },
  "accounts": {},
  "transactions": {},
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

### Subscribe to blocks

This will return all the blocks as they are produced. It will send blocks along with the transactions:

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": {}, "accounts": { }, "transactions": {}, "blocks": { "blocks": {} }, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {},
  "accounts": {},
  "transactions": {},
  "blocks": {
    "blocks": {}
  },
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

By default `Block` message includes all transactions, but you can exclude them or include updated accounts:

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": {}, "accounts": { }, "transactions": {}, "blocks": { "blocks": {"include_transactions": false, "include_accounts": true} }, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {},
  "accounts": {},
  "transactions": {},
  "blocks": {
    "blocks": {
      "includeTransactions": false,
      "includeAccounts": true
    }
  },
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

If you interested only in transactions/accounts where any of specified accounts are mentioned you can use special filter:

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": {}, "accounts": { }, "transactions": {}, "blocks": { "blocks": {"account_include": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"]} }, "blocks_meta": {}, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {},
  "accounts": {},
  "transactions": {},
  "blocks": {
    "blocks": {
      "accountInclude": ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"]
    }
  },
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

### Subscribe to block metadata

If you want to subscribe just to notifications as blocks are processed without receiving all the transactions, then you can use the block meta subscription:

{% tabs %}
{% tab title="gRPC" %}
{% code overflow="wrap" %}

```json
{"slots": {}, "accounts": {}, "transactions": {}, "blocks": {}, "blocks_meta": { "blockmetadata": {} }, "accounts_data_slice": []}
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {},
  "accounts": {},
  "transactions": {},
  "blocks": {},
  "blocksMeta": {
    "blockmetadata": {}
  },
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

### Compressed account filters

When you need to track thousands or millions of accounts on a single connection, listing every public key explicitly is impractical: the subscribe request grows large, and each public key adds server-side filter matching work. Compressed account filters solve this by replacing the explicit public key list with a compact probabilistic filter that the server evaluates in constant time per account update.\
\
The filter is a cuckoo filter: a space-efficient data structure that answers "is this public key in my tracked set?" with a small false-positive rate (below 1%) and zero false negatives. The server uses this filter for matching. Your client confirms membership on the return path using the exact set.\
\
**When to use compressed filters**

Use compressed filters when you are tracking more accounts than you can reasonably list as explicit public key filters. A good threshold is roughly 100+ accounts. Below that, explicit filters in a single subscription work fine.

Compressed filters are especially useful when your tracked set changes over time. Inserts and removes are O(1), so you can update the filter and resend the subscription without rebuilding from scratch.

**Rust**

```rust
use yellowstone_grpc_proto::cuckoo::CompressedAccountFilterSet;

// Build the filter
let mut accounts = CompressedAccountFilterSet::with_capacity(2_000_000)?;
for pk in my_tracked_pubkeys() {
    accounts.insert(*pk)?;
}

// Attach to subscribe request
let mut req = SubscribeRequest::default();
accounts.insert_into_subscribe_request(&mut req, "tracked");

// Send req over your subscribe stream as usual
```

TypeScript/NodeJS

```rust
import Client, { CompressedAccountFilterSet } from "@triton-one/yellowstone-grpc";

// Build the filter
const accounts = CompressedAccountFilterSet.withCapacity(2_000_000);
for (const pk of myTrackedPubkeys) {
  accounts.insert(pk);
}

// Attach to subscribe request and send
const request = accounts.toSubscribeRequest("tracked");
stream.write(request);
```

**Filtering false positives on the return path**

Because the server-side filter is probabilistic, there a 1% chance that updates (false positive) will be for accounts not in your tracked set. Confirm every incoming update against your exact set before processing:

```rust
if accounts.contains(&incoming_pubkey) {
    // This update is for a tracked account
}
```

This check is not optional cleanup. It is necessary.\
\
**Sizing**

Size the filter for your expected set at construction time using `with_capacity`. A 2M-account filter builds in roughly 390ms on a release build. The wire payload for a 2M-account filter is roughly 3MB, compared to roughly 64MB for 2M explicit base58-encoded public keys. Subsequent insert/remove operations are O(1) and do not trigger a rebuild.

### Sending pings to keep the stream alive

Some cloud providers (eg. Cloudflare) close idle streams. To avoid this, you need to keep sending pings to the server. The server responds with a `pong` message every 15 seconds.

Here is a rust example for this <https://gist.github.com/lvboudre/7bbcd895ab3b7df3cd6b0ad1450fac88>

Here is an example of how you can do periodic pings and handle the pong responses

```
const PING_INTERVAL_MILLISECONDS = 30000;

// Ping request
const pingRequest: SubscribeRequest = {
    ping: { id: 1 },
    accounts: {},
    accountsDataSlice: [],
    transactions: {},
    transactionsStatus: {},
    blocks: {},
    blocksMeta: {},
    entry: {},
    slots: {},
};

// Sending pings periodically
setInterval(async () => {
  await new Promise<void>((resolve, reject) => {
    stream.write(pingRequest, (err) => {
      if (err === null || err === undefined) {
        resolve();
      } else {
        reject(err);
      }
    });
  }).catch((reason) => {
    console.error(reason);
    throw reason;
  });
}, PING_INTERVAL_MILLISECONDS);

// Handling pong responses
// This goes in your `data` handler
stream.on("data", (data) => {
if (data.pong) {
    console.log("Received Pong response");
  }
});

```

### Modifying subscription

The Subscribe method offers a bi-directional stream, so you can modify the subscription by simply submitting your newly updated subscription string, and you will start receiving updates on your modified filters.

This will entirely overwrite the previous subscription, so ensure your client maintains a local register of the entire subscription config you are interested in.

### Replaying from a slot

Dragon's Mouth supports replaying recently buffered updates by setting `from_slot` on `SubscribeRequest`. This is mainly used to recover from short disconnections.

When a client subscribes with `from_slot`, the server first replays buffered updates starting from that slot and then continues streaming live updates on the same connection.

Important details:

* Replay only covers the server's retained replay window.
* To discover the earliest replayable slot, call the unary `SubscribeReplayInfo` RPC and read `first_available`.
* If `from_slot` is older than the earliest available slot, the request will fail and the client should retry with a newer slot.
* Replay uses the same filters and commitment level as the live subscription.
* Replay starts at a slot boundary. If you reconnect from the last slot you processed, you may receive duplicate updates from that slot, so clients should deduplicate.

This is useful for reconnect logic:

1. Track the latest slot your application has processed.
2. On disconnect, reconnect and resubscribe with `from_slot` set to that slot.
3. If the requested slot is no longer available, fall back to a fresh live subscription or your own backfill path.

**Example replay subscribe request**

gRPC

```json
{
  "slots": {
    "incoming_slots": {}
  },
  "accounts": {},
  "transactions": {
    "alltxs": {
      "vote": false,
      "failed": false
    }
  },
  "blocks": {},
  "blocks_meta": {},
  "accounts_data_slice": [],
  "from_slot": 382001234
}
```

NodeJS

```typescript
const request = {
  slots: {
    incoming_slots: {}
  },
  accounts: {},
  transactions: {
    alltxs: {
      vote: false,
      failed: false
    }
  },
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  fromSlot: 382001234
};

```

**Example: checking replay availability**

```typescript
const info = await client.subscribeReplayInfo();
console.log(info.firstAvailable);
```

{% hint style="info" %}
For teams running their own Yellowstone gRPC server, replay must be enabled server-side by setting `replay_stored_slots` to a value greater than `0`.
{% endhint %}

### Auto-reconnect (Rust client)

Starting from v13.1.0 the Rust client library handles reconnect automatically. You can enable it when building the client:

```rust
let client = GeyserGrpcClient::build_from_shared(endpoint)?
    .x_token(x_token)?
    .tls_config(ClientTlsConfig::new().with_native_roots())?
    .set_reconnect_config(ReconnectConfig::default())
    .connect()
    .await?;

let mut stream = client.subscribe_once(request).await?;

while let Some(msg) = stream.next().await {
    // stream transparently reconnects on disconnect,
    // replays from last processed slot, and deduplicates replayed messages
}
```

What it does automatically, under the default `RecoverMissedData` policy::

* Tracks the last fully processed slot
* On disconnect, reconnects with `from_slot` set to that slot
* Deduplicates messages replayed during the reconnect window
* Falls back to a live subscription if the slot is outside the server's replay window

### Choosing what happens to missed data

Starting from v13.3.0 you can choose how auto-reconnect handles the outage. `ReconnectionPolicy` on `ReconnectConfig` decides whether you get the data produced while you were disconnected.

`RecoverMissedData` is the default, so by default the stream has no gap and recovers what was missed during a disconnect. It also handles duplicates, making sure you get one steady stream.

```rust
.set_reconnect_config(ReconnectConfig {
    backoff: Backoff::default(),
    policy: ReconnectionPolicy::RecoverMissedData {
        slot_retention: DEFAULT_SLOT_RETENTION,
    },
})
```

`SkipMissedData` reconnects and continues from the newest data. Note that with this config anything produced during the outage is lost and cannot be recovered later.

```rust
.set_reconnect_config(ReconnectConfig {
    backoff: Backoff::default(),
    policy: ReconnectionPolicy::SkipMissedData,
})
```

Auto-reconnect is **disabled by default**. Omit `set_reconnect_config` to get the original single-stream behavior.

### Unsubscribing

If you want to unsubscribe from all streams, send the following request:

{% tabs %}
{% tab title="gRPC" %}

<pre class="language-json" data-overflow="wrap"><code class="lang-json"><strong>{"slots": {}, "accounts": {}, "transactions": {}, "blocks": {}, "blocks_meta": {}}
</strong></code></pre>

{% endtab %}

{% tab title="NodeJS" %}

```javascript
const request = {
  "slots": {},
  "accounts": {},
  "transactions": {},
  "blocks": {},
  "blocksMeta": {},
  "accountsDataSlice": []
};
```

{% endtab %}
{% endtabs %}

This will clear all current subscriptions but keep the connection open for future subscriptions.

## Managing commitment levels

The gRPC streams happen by default on the processed commitment level.

We also support specifying confirmed and finalized commitment levels. In these cases, Dragon's Mouth will buffer the incoming updates for you and release them once the updates have become confirmed or finalized.

For maximum performance, however, we recommend handling commitment levels client side.

To specify commitment level in your Dragon's Mouth gRPC calls provide the following values:

```
enum CommitmentLevel {
  PROCESSED = 0;
  CONFIRMED = 1;
  FINALIZED = 2;
}
```

### Benefits of working at processed

The benefit of working on processed is that you can process transactions as soon as they arrive, but only commit to them once you know whether they are confirmed or finalized. This means that you can get faster response times in your UI by doing a lot of the processing work at a lower commitment level and then be able to surface the changes as soon as you see that the event is committed.

### How to manage \`confirmed\` and \`finalized\`

To manage confirmed and finalized you need to buffer events by slot. Each event (transaction or account write) will have a slot attached to it. You store these events in a buffer ordered by slot.

You then also make sure you subscribe to [slot notifications](#subscribe-to-slots). This will give you information about when a slot is confirmed or finalized. Depending on the commitment level you are interested in, you should release your buffer when you receive the slot notification for a particular slot at a particular commitment level.

You will receive all the transaction notifications or account write notifications for the slot **before** you receive the "confirmed" and "finalized" notification for that slot.

### The special thing about finalized

Unfortunately, due to a quirk (fixed in `master` of solana) in the way that Geyser works on Solana not every slot finalized notification is issued. This means that you need some special processing if you want to handle finalized correctly.

The special handling is as follows: whenever you see a `finalized` slot notification, you need to retroactively mark its ancestors as `finalized` too, even if you didn't receive a notification for them.

## Intra-slot update

The Dragon's Mouth gRPC stream allows subscribers to listen for 'intra-slot' updates, which represent different lifecycle stages a slot goes through inside the RPC node, from the first shred received to a fully replayed slot.

Here's a list of supported intra-slot update events :

```
enum SlotStatus {
  ...
  SLOT_FIRST_SHRED_RECEIVED = 3;
  SLOT_COMPLETED = 4;
  SLOT_CREATED_BANK = 5;
  SLOT_DEAD = 6;
}
```

**SLOT\_FIRST\_SHRED\_RECEIVED**: The remote RPC node you're connected to has received the first shred of a given slot. This does not indicate it has been replayed yet. This event occurs during the [retransmit stage](https://docs.anza.xyz/validator/tvu#retransmit-stage) in the [TVU](https://docs.anza.xyz/validator/tvu).

**SLOT\_CREATED\_BANK**: A bank for the given slot has been created on the remote RPC node you're connected to. Within a validator, a Bank acts as an isolated execution environment during the replay stage (which follows the retransmit stage). Due to the decentralized nature of blockchains, forks are inevitable, meaning a slot can have multiple descendants.

To handle this, validators must be capable of replaying multiple slots that share the same ancestor without their execution interfering with one another. Each slot is assigned its own Bank instance, and these Banks form a fork graph, where each edge represents a parent-child relationship between two banks.

Banks serve as self-contained execution contexts, maintaining replay results and essential metadata about the slot and its lineage. Importantly, a Bank is instantiated once per slot.

**SLOT\_COMPLETED**: All the shreds for the given slot have been received by the RPC node you're connected to. However, this does not necessarily mean that the slot has been fully replayed yet.

**SLOT\_DEAD:** Dead slots are slots that have been rejected by the validator for various reasons, such as invalid transaction signatures in the leader's shreds, incorrect entry hashes during Proof of History (PoH) verification, or an unexpected number of entries in the slot. When a slot is marked as dead, it is discarded by the network as a whole and effectively skipped. This can occur at any point during the replay process, even after the slot has been marked as 'completed'.

Here's a "simplfied" overview of the expected lifecycle of a slot:\\

{% code title="Slot lifecycle through time" %}

```
                                                                                                                                            
                                                                                                                                            
                                                                                                                                            
                                          TIME ->                                                                                           
      ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────►   
      ┌───────────────────────────────────────────────────────┐                                                                             
      │ Slot download                                         │                                                                             
      │ ┌───────────┐┌──────┐         ┌───────┐┌───────────┐  │                                                                             
      │ │FIRST_SHRED││SHRED2│  ...    │SHRED N││ COMPLETED │  │                                                                             
      │ │ RECEIVED  │└──────┘         └───────┘└───────────┘  │                                                                             
      │ └───────────                                          │                                                                             
      └──────────────┌───────────────────────────────────────────────────────────────────────────────┐                                      
                     │ REPLAY STAGE                                                                  │                                      
                     │┌─────────────┐ ┌──────────────┐ ┌───┌───┐┌──────┐    ┌──────────┐ ┌─────────┐ │                                      
                     ││BANK_CREATED │ │ACCOUNT UPDATE│ │TX1│TX2││ENTRY1│... │BLOCK_META│ │PROCESSED│ │                                      
                     │└─────────────┘ └──────────────┘ └───└───┘└──────┘    └──────────┘ └─────────┘ │                                      
                     │                                                                               │                                      
                     └───────────────────────────────────────────────────────────────────────────────┘                                      
                                                                                                    ┌──────────────────────────────────┐    
                                                                                                    │ CONSENSUS                        │    
                                                                                                    │ ┌──────────┐      ┌───────────┐  │    
                                                                                                    │ │CONFIRMED │      │FINALIZED  │  │    
                                                                                                    │ └──────────┘      └───────────┘  │    
                                                                                                    │                                  │    
                                                                                                    └──────────────────────────────────┘    
                                                                                                                                            
```

{% endcode %}

## Deshred transactions (beta)

Dragon's Mouth also supports [`SubscribeDeshred`](https://github.com/rpcpool/yellowstone-grpc/blob/236ebd7b0616dd88407a7a2b61f903a56b92b186/yellowstone-grpc-proto/proto/geyser.proto?ref=blog.triton.one#L12), a separate gRPC stream that delivers transactions reconstructed from shreds before the validator executes them.

This is the earliest usable on-chain signal exposed by Dragon's Mouth. It is designed for latency-sensitive systems that care about transaction intent as early as possible, such as arbitrage, market making, copy trading, liquidations, and HFT pipelines.

Unlike the standard `Subscribe` transaction stream, deshred updates are emitted before Replay. That means you receive the decoded transaction earlier, but without execution context.

A deshred update includes:

* `slot`
* `signature`
* vote flag
* raw transaction
* `loaded_writable_addresses`
* `loaded_readonly_addresses`

The loaded address fields contain addresses resolved from Address Lookup Tables (ALTs), so deshred filters match both static account keys and dynamically loaded addresses.

#### Deshred filters

`SubscribeDeshred` supports:

* `vote`
* `account_include`
* `account_exclude`
* `account_required`

#### Important limitations

* `SubscribeDeshred` is a separate RPC, not a field on `SubscribeRequest`
* deshred data has no execution metadata: no status, logs, inner instructions, balance changes, compute units, or `TransactionStatusMeta`
* deshred data has no confirmation or finality guarantee: a transaction may fail, land on a dead fork, or never confirm
* if you need confirmation and execution results, use Deshred together with the normal `transactions` stream

#### Availability

`SubscribeDeshred` is currently available only on Triton One gRPC servers and is in paid beta. It depends on Triton's validator-side extension and does not work out of the box on a stock Agave node.

For a deeper overview of the architecture and tradeoffs, see [Deshred transactions: the fastest path to Solana data](https://blog.triton.one/deshred-transactions-the-fastest-path-to-solana-data/). For the wire format and RPC definitions, see [`geyser.proto`](https://github.com/rpcpool/yellowstone-grpc/blob/236ebd7b0616dd88407a7a2b61f903a56b92b186/yellowstone-grpc-proto/proto/geyser.proto?ref=blog.triton.one#L12).

#### Rust example

```rust
use {
    futures::{sink::SinkExt, stream::StreamExt},
    solana_signature::Signature,
    std::collections::HashMap,
    tonic::transport::channel::ClientTlsConfig,
    yellowstone_grpc_client::GeyserGrpcClient,
    yellowstone_grpc_proto::prelude::{
        subscribe_update_deshred::UpdateOneof, SubscribeDeshredRequest,
        SubscribeRequestFilterDeshredTransactions, SubscribeRequestPing,
    },
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let endpoint = std::env::var("ENDPOINT")
        .unwrap_or("https://<endpoint>".into());
    let x_token = std::env::var("X_TOKEN").ok();

    let mut client = GeyserGrpcClient::build_from_shared(endpoint)?
        .x_token(x_token)?
        .tls_config(ClientTlsConfig::new().with_native_roots())?
        .http2_adaptive_window(true)
        .initial_connection_window_size(8 * 1024 * 1024) // 8 MiB
        .initial_stream_window_size(4 * 1024 * 1024) // 4 MiB
        .connect()
        .await?;

    let request = SubscribeDeshredRequest {
        deshred_transactions: HashMap::from([(
            "deshred".into(),
            SubscribeRequestFilterDeshredTransactions {
                vote: Some(false),
                account_include: vec![],
                account_exclude: vec![],
                account_required: vec![],
            },
        )]),
        ping: None,
    };

    let (mut tx, mut stream) =
        client.subscribe_deshred_with_request(Some(request)).await?;

    while let Some(msg) = stream.next().await {
        match msg?.update_oneof {
            Some(UpdateOneof::DeshredTransaction(update)) => {
                let info = update.transaction.as_ref().unwrap();
                let sig = Signature::try_from(info.signature.as_slice())?;
                println!("slot={} sig={sig} vote={}", update.slot, info.is_vote);
            }
            Some(UpdateOneof::Ping(_)) => {
                tx.send(SubscribeDeshredRequest {
                    ping: Some(SubscribeRequestPing { id: 1 }),
                    ..Default::default()
                }).await?;
            }
            Some(UpdateOneof::Pong(_)) => {}
            None => break,
        }
    }

    Ok(())
}
```


# Riptide

A lower-latency connection path for Dragon's Mouth gRPC.

Riptide is a dedicated endpoint for the existing [Dragon's Mouth gRPC service](/project-yellowstone/dragons-mouth-grpc-subscriptions). It provides the same streams, unary methods, protocol, and SDK support through a lower-latency network path.

If you use Dragon's Mouth through a shared subscription, point your client at `https://riptide.rpcpool.com:443`. Your shared-subscription token, SDK, Protobuf version, subscription requests, and filters continue to work.

| Setting        | Value                                                           |
| -------------- | --------------------------------------------------------------- |
| Endpoint       | `https://riptide.rpcpool.com:443`                               |
| Availability   | Shared subscriptions — see [Availability](#availability)        |
| Authentication | Your shared-subscription token in the `x-token` metadata header |
| Protocol       | Dragon's Mouth / Yellowstone gRPC                               |
| Network        | Mainnet only                                                    |
| Routing        | Automatic to the nearest location; no region-specific endpoints |

Riptide serves Dragon's Mouth gRPC only. Continue to use your standard RPC endpoint for JSON-RPC and WebSocket requests. For client setup, subscription filters, and request examples, see the [Dragon's Mouth documentation](/project-yellowstone/dragons-mouth-grpc-subscriptions).

## Availability

Riptide is available on shared Dragon's Mouth subscriptions, and usage is billed as part of the shared service. Dedicated-node tokens do not work on `riptide.rpcpool.com`.

If your account only has dedicated nodes, you have two options:

* Add a shared gRPC subscription to your account for immediate access. Shared-service billing applies.
* Contact our support team — over your dedicated support channel, or at <support@triton.one> — to arrange a dedicated Riptide setup for your own nodes.

{% hint style="info" %}
A `403 Access forbidden` response with empty `x-ratelimit-*` headers means the token you are sending is not enabled for this endpoint — it is not an invalid token. Use your shared-subscription token, or contact our support team to enable access.
{% endhint %}

## Connection resets and reconnection

Riptide's failover operates at the connection level: it directs new connections to healthy backends but cannot move an existing gRPC stream between them. Because the network path can change during normal operation, a dropped connection may arrive as a TCP reset (`RST`) rather than a graceful close. Your client should:

1. Detect stream errors, end-of-stream events, and TCP resets.
2. Reconnect automatically, using exponential backoff with jitter for repeated failures.
3. Create a new stream and send the complete subscription request again after reconnecting.
4. Track the last fully processed slot and use [`from_slot`](/project-yellowstone/dragons-mouth-grpc-subscriptions#replaying-from-a-slot) to recover buffered updates after a short interruption. Deduplicate updates from the replayed slot.

The Dragon's Mouth Rust client supports [automatic reconnection](/project-yellowstone/dragons-mouth-grpc-subscriptions#auto-reconnect-rust-client), replay, and deduplication. Auto-reconnect is disabled by default and must be enabled in the client configuration.

Riptide does not replace or deprecate your existing Dragon's Mouth endpoint. It provides the same features over a faster path, and the reconnection handling above applies to both endpoints, so we recommend moving your gRPC traffic to Riptide.


# Old Faithful Historical Archive

Old Faithful is Triton's open-source solution to the Solana historical ledger problem, making the entire history of Solana accessible to everyone. Sponsored by the Solana Foundation, this project empowers you to explore Solana's blockchain from genesis to the latest block.

## Key Features

* **Full Historical Access:** Retrieve any Solana block from previous epochs!
* **Flexible APIs:** Access data via standard JSON-RPC, via  or high-performance gRPC streams.
* **Self-Hosted or Managed:** Run your own archive node (requires significant storage) or use Triton's managed endpoints.
* **Open Source:** Built in the open for transparency and community collaboration.

## Getting Started

Old Faithful is automatically integrated into your Triton One subscription. By making a `getBlock` or `getTransaction` call for historical data, our systems will intelligently route you to Old Faithful as required.

### Jetstreamer

[Jetstreamer](https://github.com/anza-xyz/jetstreamer) is our recommended approach to backfilling large amounts of data from the Old Faithful archives. It supports filtering and customizable backends for storing the data. You can also use already existing geyser plugins for processing the data.&#x20;

### Deprecated: gRPC

⚠️ **Service Retired** The current hosted version of the gRPC interface is retired. We are working on an updated version. You can still self-host this. Please contact <support@triton.one> for assistance.

Most JSON-RPC methods can be made with gRPC and for greater efficiency we've also implemented **StreamTransactions** and **StreamBlocks** which allow you to filter large troves of data server side and receive only what's relevant for you. You can see examples in the gRPC [docs](https://docs.old-faithful.net/references/grpc-methods/examples#streaming-methods) page.&#x20;

## Additional Resources

* [Yellowstone Faithful on GitHub](https://github.com/rpcpool/yellowstone-faithful)
* [JSON-RPC Examples](https://docs.old-faithful.net/usage-and-installation/rpc-methods)
* [gRPC Examples](https://docs.old-faithful.net/usage-and-installation/grpc-methods)

## Support

For questions, feedback, or to request access, please contact [customer support](mailto:support@triton.one) or your account representative.


# Old Faithful Public Report

July 13, 2024

### Introduction

Triton One Limited (“Triton”) has completed the initial phases of the Old Faithful project to make Solana's ledger history widely available to the community through decentralized means. We have validated the archive's integrity from Solana’s genesis and uploaded copies to S3-compatible storage. With this completion of the initial phase of the Old Faithful project, Solana now has widely available copies of ledger history and new tooling required to verify the archive’s integrity.

While conducting our work, we learned of missing data files (“snapshots”) for less than five hundred blocks, representing four minutes and eighteen seconds. Engineers successfully verified the correctness of these slots by developing new verification tooling.

This document discusses the Old Faithful project for a general audience and a proof to demonstrate the complete validity of the Solana archive using this new method. See the footnotes below for more detailed information, including an open-sourcing of all tooling and work related to Old Faithful and ledger verification.

### Solana Archive & Google BigTable

In the early days of Solana development, RPC nodes ran on Google Cloud. Cloud hosting provided convenience and simplicity for developers focused on iterating a new codebase. Solana generates more ledger data than can be easily stored on a single computing instance, so the developers used Google BigTable for ledger storage. Trent Nelson from Anza says, “Every bad decision was the right decision at the time.” Using BigTable for ledger storage made sense because the RPCs were in Google Cloud, and network traffic between RPC & BigTable was free. Over time, RPCs moved to bare metal servers outside Google Cloud, and network egress from BigTable to external servers became very expensive. Access to the archives became a pain point for the developer community.

### Old Faithful

Blockchain research projects require access to historical data. Independent researchers struggled to gain affordable access to Solana ledger history since the Archives were only available in BigTable through a handful of entities. To solve this problem, Linus Kendall, Triton Co-Founder, and Richard Patel, currently with the Jump Crypto Firedancer team, brainstormed possible solutions. The two of them thought that Content Addressable aRchive (“CAR”) files would provide a scalable file format that can be saved long-term on various storage platforms, such as any S3-compatible storage platform.

The objective of The Old Faithful project, financially supported by The Solana Foundation, is to copy the entire Solana archive into CAR files accessible by the community. As part of the project, we also independently verified the archive's integrity from its genesis.

### Snapshot Warehouse

In the Solana Labs (now “Anza”) client, the term “account snapshots” or just "snapshot" refers to the capture of the account state at a given slot. Since storing snapshots is computationally expensive, they are written at fixed, somewhat infrequent intervals. In addition to account snapshots, these nodes keep backup copies of the Solana ledger data produced by the node. The collection of snapshot & ledger files is called “the warehouse.”

The warehouse files are saved in cloud storage (Anza uses Google Cloud Storage/GCS, and Triton One uses Backblaze B2 for this storage) for later retrieval and processing.

Asynchronously, an RPC node uploads new blocks to BigTable. The RPC also regularly creates a snapshot of the then-current account state and backfills the BigTable as needed for each epoch.

Solana Labs managed the original snapshot warehouse and shared it with external parties when requested. Over time, multiple ecosystem players, including Triton, copied the files to create & maintain independent warehouses. Triton and several other ecosystem participants also received a copy of the BigTable archive to maintain independent database instances. At this time, there are a handful of independent BigTable instances.

### Missing Proof-of-History Entries

Our research discovered that warehouse ledger backup files were missing for less than five hundred slots, representing four minutes and eighteen seconds, in Epoch 208 (August 3, 2021). The BigTable database included all transactions for the slots, but the warehouse files were missing. The source warehouse files are needed because BigTable does not include the Proof-of-History tick data or the entry batches (“entries”) required to verify the block hashes using the existing tooling. An alternative proof would be required to show that the BigTable ledger history was valid.

### Alternative Proof

One of Solana's unique features is that it includes redundant hashes to verify account state and history. Every block has a “block hash,” which includes the hash from the previous block as input. Validators reference the “bank hash” for a slot when they vote to confirm blocks and resolve forks. Each validator's vote contains the bank hash for the slot they are voting for. A vote for a block N is implicitly a vote for any block that is a parent of N. The bank hashes are included in the votes, and the votes are included in the blocks. Therefore, PoH is generally not needed to verify state transitions – it is possible to prove the validity of ledger history using the votes.

Engineers from Anza led the effort to create the tooling required for an alternative, cryptographically valid proof. At a high level, the proof: 1) starts with a known and verified block hash & bank hash immediately preceding the gap. Then, within the gap, 2) replay transactions from BigTable for each slot to determine the bank hash, 3) confirm that a supermajority of validators voted for the bank hash, and finally, 4) compare the final bank hash at the end of the gap to the votes from known blocks immediately after the gap.

Anza engineers created the alternative proof by replaying transactions and analyzing votes. They also created tooling to show that the transaction history is valid. Triton has independently verified the alternative proof, and we can confirm the Solana transaction history is complete and valid.

We encourage others in the community to verify the alternative proof. Links to documentation and tooling appear in the Footnotes section below.

### Summary

Triton has completed the initial phases of the Old Faithful project to make Solana ledger history widely available. We have uploaded copies of the CAR files to S3-compatible storage. We will also continue the project with the support of The Solana Foundation. With this completion of the initial phase of the Old Faithful project, Solana now has widely available copies of ledger history and the tooling required to verify the archive’s integrity.

### Footnotes

Links for Further Research:

* Old Faithful website: <https://old-faithful.net/>
* Old Faithful Git repo: <https://github.com/rpcpool/yellowstone-faithful/issues>
* Old Faithful Technical Documentation: <https://docs.old-faithful.net/>
* Anza Ledger Tool: <https://github.com/anza-xyz/agave/tree/master/ledger-tool>
* Anza BigTable Git Repo: <https://github.com/solana-labs/solana-bigtable>

Major Contributors:

* Linus Kendall, Co-Founder, Triton One Limited
* Slavomir Balsan, Software Engineer, Triton One Limited
* Steve Czabaniuk, Software Engineer, Anza
* Tyera Eulberg, Technical Project Leader, Anza
* Richard Patel, Firedancer Core Contributor, Jump Crypto


# Cloudbreak Custom Indexes

Cloudbreak serves `getProgramAccounts`, account lookups, and SPL token queries from a purpose-built index, so they stay fast even for programs with millions of accounts. On a Triton endpoint where Cloudbreak is enabled, these methods are answered by the index automatically. You call them like any other Solana RPC method; there is nothing to enable or route on your side.

{% hint style="info" %}
Cloudbreak serves current account state. It does not stream updates or serve transaction history. Pair it with Dragon's Mouth when you need a live feed.
{% endhint %}

### What Cloudbreak does

Cloudbreak handles these workloads:

* `getProgramAccounts` against a program with a large account set, especially with `memcmp` or `dataSize` filters you run repeatedly.
* Account and balance lookups: `getAccountInfo`, `getMultipleAccounts`, `getBalance`.
* SPL token queries by owner, delegate, or mint, and token balances.

What to expect:

* These methods read indexed state, so they return current account data quickly and handle large result sets without timing out.
* Methods Cloudbreak does not cover (`getVoteAccounts`, transaction methods, block methods) are not accelerated; your endpoint continues to serve them as usual.
* For a live feed of account or transaction changes, use Dragon's Mouth instead of polling these methods.

### How it works

Cloudbreak builds its index from a validator snapshot for the starting state and a Yellowstone gRPC stream of account updates to stay current. It tracks both `confirmed` and `finalized` commitment as the chain advances, so a query reads the same state the chain has reached at the commitment you ask for.

It verifies its data against the validator's own account hash: a lattice hash computed over every account's address, lamports, owner, executable flag, and data. When that hash matches the validator's, the indexed state is identical to on-chain state at that slot.

### Making requests

The Cloudbreak-accelerated methods are standard Solana JSON-RPC, so you call them the same way you call any other method on your RPC endpoint. There is nothing Cloudbreak-specific to install or configure on the client side, and existing Solana clients (`@solana/web3.js`, `solana-py`, and others) work without changes.

Every call is an HTTP POST with `Content-Type: application/json`:

```shell
curl https://<your-rpc-endpoint> \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSlot","params":[]}'
```

A few things to know before your first query:

* **Coverage is automatic.** The most widely used Solana programs are already indexed and served by Cloudbreak, and programs that receive enough query traffic are added over time, so common queries are fast with no setup on your side. See [How indexes get created](#how-indexes-get-created).
* **Commitment levels** are `confirmed` and `finalized`. The default is `finalized`. `processed` is rejected by default with error `-32003`. See Commitment and consistency.
* **Encodings** are `base64`, `base58`, `base64+zstd`, and `jsonParsed`. If you do not set `encoding`, the response uses the legacy `binary` format, which is a single base58 string rather than a `[data, encoding]` array. Set `encoding` to `base64` for most uses.

{% hint style="warning" %}
The default `binary` encoding and `base58` only work for account data under 128 bytes. Larger accounts return `-32602` with a message telling you to switch to `base64`. Because the default is `binary`, set `encoding` to `base64` whenever an account can exceed 128 bytes.
{% endhint %}

### Supported methods

| Method                       | Returns                                                        |
| ---------------------------- | -------------------------------------------------------------- |
| `getAccountInfo`             | A single account, or `null`.                                   |
| `getMultipleAccounts`        | An array of accounts (each `null` if missing), in input order. |
| `getBalance`                 | The lamport balance of an account.                             |
| `getProgramAccounts`         | All accounts owned by a program, with filters.                 |
| `getTokenAccountsByOwner`    | SPL Original and Token-2022 token accounts owned by a wallet.  |
| `getTokenAccountsByDelegate` | SPL Original and Token-2022 accounts a delegate controls.      |
| `getTokenAccountsByMint`     | SPL Original and Token-2022 accounts of a given mint.          |
| `getTokenAccountBalance`     | The token amount held by a token account.                      |
| `getSlot`                    | The current slot at a commitment level.                        |
| `getVersion`                 | The upstream Solana version and Cloudbreak build.              |
| `getGenesisHash`             | The cluster genesis hash.                                      |
| `getHealth`                  | Service health.                                                |

#### getAccountInfo

Returns the account at a single address, or `null` if it is not in the indexed set. Compatible with the standard [Solana `getAccountInfo` API](https://solana.com/docs/rpc/http/getaccountinfo).

Position 0 is the account pubkey (base58), required. Position 1 is an optional config object:

| Field            | Required | Default     | Description                                                    |
| ---------------- | -------- | ----------- | -------------------------------------------------------------- |
| `encoding`       | optional | `binary`    | `base64`, `base58`, `base64+zstd`, or `jsonParsed`.            |
| `dataSlice`      | optional | full data   | `{ "offset": <number>, "length": <number> }`.                  |
| `commitment`     | optional | `finalized` | `confirmed` or `finalized`.                                    |
| `minContextSlot` | optional | none        | Fail with `-32000` if the index has not reached this slot yet. |

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getAccountInfo",
  "params": [
    "4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA",
    { "encoding": "base64", "commitment": "confirmed" }
  ]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": {
      "lamports": 1234567,
      "owner": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
      "executable": false,
      "rentEpoch": 18446744073709551615,
      "space": 752,
      "data": ["<base64-string>", "base64"]
    }
  }
}
```

{% endtab %}
{% endtabs %}

When the account is not found, `value` is `null`.

#### getMultipleAccounts

Returns one entry per requested address, in the same order, with `null` for any address not in the indexed set. Compatible with the standard [Solana `getMultipleAccounts` API](https://solana.com/docs/rpc/http/getmultipleaccounts).

Position 0 is an array of pubkeys (base58), required. Position 1 is the same optional config object as `getAccountInfo`.

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getMultipleAccounts",
  "params": [
    [
      "4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA",
      "11111111111111111111111111111111"
    ],
    { "encoding": "base64" }
  ]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": [
      {
        "lamports": 1234567,
        "owner": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
        "executable": false,
        "rentEpoch": 18446744073709551615,
        "space": 752,
        "data": ["<base64-string>", "base64"]
      },
      null
    ]
  }
}
```

{% endtab %}
{% endtabs %}

#### getBalance

Returns the lamport balance of an account, or `0` if it is not in the indexed set. Compatible with the standard [Solana `getBalance` API](https://solana.com/docs/rpc/http/getbalance).

Position 0 is the account pubkey (base58), required. Position 1 is an optional config object accepting `commitment` and `minContextSlot`.

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getBalance",
  "params": [
    "4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA",
    { "commitment": "confirmed" }
  ]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": 1234567
  }
}
```

{% endtab %}
{% endtabs %}

#### getProgramAccounts

Returns every account owned by a program, with optional filtering. This is the method Cloudbreak is built to accelerate. Compatible with the standard [Solana `getProgramAccounts` API](https://solana.com/docs/rpc/http/getprogramaccounts).

Position 0 is the program pubkey (base58 string), required. Position 1 is an optional config object:

| Field            | Required | Default     | Description                                                                                 |
| ---------------- | -------- | ----------- | ------------------------------------------------------------------------------------------- |
| `encoding`       | optional | `binary`    | `base64`, `base58`, `base64+zstd`, or `jsonParsed`.                                         |
| `filters`        | optional | none        | Array of `memcmp` and/or `dataSize` filters. All filters must match.                        |
| `dataSlice`      | optional | full data   | `{ "offset": <number>, "length": <number> }`. Returns only that byte range of account data. |
| `commitment`     | optional | `finalized` | `confirmed` or `finalized`.                                                                 |
| `minContextSlot` | optional | none        | Fail with `-32000` if the index has not reached this slot yet.                              |
| `withContext`    | optional | `false`     | When `true`, wrap the result in `{ "context": { "slot": N }, "value": [...] }`.             |

A `memcmp` filter matches a base58-encoded byte sequence at a fixed offset: `{ "memcmp": { "offset": 32, "bytes": "<base58-bytes>" } }`. A `dataSize` filter matches accounts of an exact data length: `{ "dataSize": 165 }`.

The example below fetches Raydium AMM accounts of a fixed size at `confirmed`, asking for the slot context alongside the result.

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getProgramAccounts",
  "params": [
    "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
    {
      "encoding": "base64",
      "commitment": "confirmed",
      "withContext": true,
      "filters": [{ "dataSize": 752 }]
    }
  ]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": [
      {
        "pubkey": "58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2",
        "account": {
          "lamports": 1234567,
          "owner": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
          "executable": false,
          "rentEpoch": 18446744073709551615,
          "space": 752,
          "data": ["<base64-string>", "base64"]
        }
      }
    ]
  }
}
```

{% endtab %}
{% endtabs %}

Without `withContext`, `result` is the array directly rather than the `{ context, value }` envelope, matching standard Solana RPC behavior.

{% hint style="info" %}
`getProgramAccounts` responses are streamed back as a chunked HTTP body, so a client can begin parsing accounts before the query finishes and peak memory stays bounded on both ends. Standard HTTP and JSON-RPC clients consume the chunked body transparently. If a request fails before the first batch is sent, you get a normal JSON-RPC error. If it fails partway through streaming, the body is truncated and your JSON parser reports an incomplete document. Treat a parse error on a large response as a failed request and retry.
{% endhint %}

#### getTokenAccountsByOwner

Returns the Original and Token-2022 token accounts owned by a wallet, both from the. Cloudbreak indexes the token owner and mint as dedicated columns, so this method stays fast even though the SPL Token program holds hundreds of millions of accounts. Compatible with the standard [Solana `getTokenAccountsByOwner` API](https://solana.com/docs/rpc/http/gettokenaccountsbyowner).

{% hint style="info" %}
For SPL token data, use the token methods rather than a raw `getProgramAccounts` against the Token program. The token methods hit purpose-built indexes; an unfiltered `getProgramAccounts` over the entire Token program is not indexed and is not a supported access pattern.
{% endhint %}

Position 0 is the owner pubkey (base58), required. Position 1 is a filter object, required, with exactly one of:

| Filter                               | Description                                                                                                                          |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `{ "mint": "<mint-pubkey>" }`        | Restrict to a single mint.                                                                                                           |
| `{ "programId": "<token-program>" }` | Restrict to SPL Token (`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) or Token-2022 (`TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`). |

Position 2 is an optional config object accepting `encoding`, `dataSlice`, `commitment`, and `minContextSlot`, with the same meanings as in `getProgramAccounts`.

The example below fetches a wallet's USDC accounts and decodes them with `jsonParsed`.

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTokenAccountsByOwner",
  "params": [
    "<owner-wallet-pubkey>",
    { "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" },
    { "encoding": "jsonParsed", "commitment": "confirmed" }
  ]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": [
      {
        "pubkey": "<token-account-pubkey>",
        "account": {
          "lamports": 2039280,
          "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "executable": false,
          "rentEpoch": 18446744073709551615,
          "space": 165,
          "data": {
            "program": "spl-token",
            "parsed": {
              "type": "account",
              "info": {
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "owner": "<owner-wallet-pubkey>",
                "tokenAmount": {
                  "amount": "1000000",
                  "decimals": 6,
                  "uiAmount": 1.0,
                  "uiAmountString": "1"
                },
                "state": "initialized",
                "isNative": false
              }
            },
            "space": 165
          }
        }
      }
    ]
  }
}
```

{% endtab %}
{% endtabs %}

The token methods always wrap their result in the `{ context, value }` envelope, matching standard Solana RPC.

#### getTokenAccountsByDelegate

Identical parameters and response shape to `getTokenAccountsByOwner`, but position 0 is the delegate pubkey and results are the Original and Token-2022 token accounts that delegate has authority over. Compatible with the standard [Solana `getTokenAccountsByDelegate` API](https://solana.com/docs/rpc/http/gettokenaccountsbydelegate).

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTokenAccountsByDelegate",
  "params": [
    "<delegate-pubkey>",
    { "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" },
    { "encoding": "jsonParsed" }
  ]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": [
      {
        "pubkey": "<token-account-pubkey>",
        "account": {
          "lamports": 2039280,
          "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "executable": false,
          "rentEpoch": 18446744073709551615,
          "space": 165,
          "data": {
            "program": "spl-token",
            "parsed": {
              "type": "account",
              "info": {
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "owner": "<owner-wallet-pubkey>",
                "delegate": "<delegate-pubkey>",
                "delegatedAmount": {
                  "amount": "500000",
                  "decimals": 6,
                  "uiAmount": 0.5,
                  "uiAmountString": "0.5"
                },
                "state": "initialized",
                "isNative": false
              }
            },
            "space": 165
          }
        }
      }
    ]
  }
}
```

{% endtab %}
{% endtabs %}

#### getTokenAccountsByMint

Returns the Original and Token-2022 token accounts of a given mint. This is a Cloudbreak extension; standard Solana RPC has no `getTokenAccountsByMint`. Position 0 is the mint pubkey (base58), required. Position 1 is an optional config object:

| Field            | Required | Default     | Description                                                    |
| ---------------- | -------- | ----------- | -------------------------------------------------------------- |
| `programId`      | optional | SPL Token   | Token program to query: SPL Token or Token-2022.               |
| `encoding`       | optional | `binary`    | `base64`, `base58`, `base64+zstd`, or `jsonParsed`.            |
| `dataSlice`      | optional | full data   | `{ "offset": <number>, "length": <number> }`.                  |
| `commitment`     | optional | `finalized` | `confirmed` or `finalized`.                                    |
| `minContextSlot` | optional | none        | Fail with `-32000` if the index has not reached this slot yet. |

The result is always wrapped in the `{ context, value }` envelope and is streamed like `getProgramAccounts`.

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTokenAccountsByMint",
  "params": [
    "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    { "encoding": "jsonParsed", "commitment": "confirmed" }
  ]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": [
      {
        "pubkey": "<token-account-pubkey>",
        "account": {
          "lamports": 2039280,
          "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "executable": false,
          "rentEpoch": 18446744073709551615,
          "space": 165,
          "data": {
            "program": "spl-token",
            "parsed": {
              "type": "account",
              "info": {
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "owner": "<owner-wallet-pubkey>",
                "tokenAmount": {
                  "amount": "1000000",
                  "decimals": 6,
                  "uiAmount": 1.0,
                  "uiAmountString": "1"
                },
                "state": "initialized",
                "isNative": false
              }
            },
            "space": 165
          }
        }
      }
    ]
  }
}
```

{% endtab %}
{% endtabs %}

#### getTokenAccountBalance

Returns the token amount held by a token account. Compatible with the standard [Solana `getTokenAccountBalance` API](https://solana.com/docs/rpc/http/gettokenaccountbalance).

Position 0 is the token account pubkey (base58), required. Position 1 is an optional commitment config `{ "commitment": "confirmed" }`. Unlike the other methods, this one does not accept `minContextSlot`.

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTokenAccountBalance",
  "params": ["<token-account-pubkey>", { "commitment": "confirmed" }]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 250000042 },
    "value": {
      "amount": "1000000",
      "decimals": 6,
      "uiAmount": 1.0,
      "uiAmountString": "1"
    }
  }
}
```

{% endtab %}
{% endtabs %}

#### Network and service methods

These return service and chain metadata. None take account-specific parameters.

| Method           | Returns                                                                                                                         |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `getSlot`        | The current slot at the requested commitment. Accepts an optional `{ "commitment": ..., "minContextSlot": ... }` config.        |
| `getVersion`     | A `{ "solana-core": "<version>-cloudbreak<version>" }` object identifying the upstream Solana version and the Cloudbreak build. |
| `getGenesisHash` | The genesis hash of the cluster this index tracks.                                                                              |
| `getHealth`      | `"ok"` when the index is healthy, or an error while it is bootstrapping a snapshot or repairing a slot gap.                     |

{% tabs %}
{% tab title="Request" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getSlot",
  "params": [{ "commitment": "confirmed" }]
}
```

{% endtab %}

{% tab title="Response" %}

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": 250000042
}
```

{% endtab %}
{% endtabs %}

### Batch requests

Send an array of request objects to run several in one round trip. The response is an array of results in the same order. Within a batch, `getProgramAccounts` and `getTokenAccountsByMint` are buffered into a complete response rather than streamed, since the array has to be assembled before it is sent.

{% tabs %}
{% tab title="Request" %}

```json
[
  { "jsonrpc": "2.0", "id": 1, "method": "getSlot", "params": [] },
  { "jsonrpc": "2.0", "id": 2, "method": "getVersion", "params": [] }
]
```

{% endtab %}

{% tab title="Response" %}

```json
[
  { "jsonrpc": "2.0", "id": 1, "result": 250000042 },
  {
    "jsonrpc": "2.0",
    "id": 2,
    "result": { "solana-core": "2.1.0-cloudbreak0.1.0" }
  }
]
```

{% endtab %}
{% endtabs %}

### Commitment and consistency

Cloudbreak tracks two commitment levels and serves point-in-time reads against them.

* **`confirmed`** reflects account state as soon as a block is confirmed. The indexer writes the slot's accounts first, then advances the confirmed marker, so a confirmed read never sees a half-written slot.
* **`finalized`** (the default) reflects state once the slot is finalized and superseded account versions have been deleted.

`processed` commitment support is coming soon.

Use `minContextSlot` to guarantee freshness. If you read a slot from one call and want a later query to be at least as fresh, pass that slot as `minContextSlot`. If the index has not reached it, the call fails with `-32000` rather than returning stale data.

Use `withContext: true` on `getProgramAccounts` when you need to know which slot a result corresponds to. The account, token, and balance methods always include the context.

### How indexes get created

A `getProgramAccounts` query is fast when an index matches its filter shape. Cloudbreak creates these indexes from real traffic: it counts the query shapes it receives, and once a shape is requested often enough it builds the matching index automatically, backing off while the index catches up so this never competes with ingestion. SPL Token program queries are handled by the dedicated token methods rather than this path.

A query with no matching index yet still returns correct results, it just runs slower until the index is built.

### Performance characteristics

* **Account and balance lookups** (`getAccountInfo`, `getMultipleAccounts`, `getBalance`, `getTokenAccountBalance`) resolve directly by address, with no filter index needed.
* **`getProgramAccounts` and token queries** return in milliseconds once an index matches the query shape. A shape with no matching index still returns correct data, it just runs slower until the index exists. See How indexes get created.
* **Repeated queries are cached.** Triton runs Cloudbreak with response caching on. For each query shape (program, filters, encoding, data slice, and commitment), the serialized response is kept, and the next time you run that query only the accounts that changed since the last response are read and re-encoded. Frequent polling of the same query stays cheap.
* **Large results stream back in full.** A response is sent as the index reads it, so a large `getProgramAccounts` result starts arriving right away and comes back complete rather than timing out or hitting a response-size limit.

### Error reference

| Code     | Name                                     | Meaning                                                                                                                             |
| -------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `-32601` | Method not found                         | The method is not implemented. See Supported methods.                                                                               |
| `-32602` | Invalid params                           | Bad pubkey, malformed filter, or `base58` requested for data over 128 bytes.                                                        |
| `-32000` | Slot behind min context slot             | The index has not yet reached your `minContextSlot`.                                                                                |
| `-32003` | Processed commitment not (yet) supported | You requested `processed`; use `confirmed` or `finalized`. Processed commitment will be supported in a future version of Cloudbreak |
| `-32603` | Internal error                           | A database error or a query that exceeded the server-side timeout.                                                                  |

### FAQ

**Can I send the exact same requests I send to a normal Solana RPC node?** Yes, for the supported methods. The request and response formats are JSON-RPC 2.0 and match standard Solana RPC, so existing clients and tooling work without changes.

**Why does my `getProgramAccounts` for a program return `-32010`?** That program's accounts are not indexed, so Cloudbreak cannot serve the query. Widely used programs are indexed automatically; if you hit this for a program you expect to be covered, let the Triton team know.

**Why does `getAccountInfo` return `null` for an account I know exists on-chain?** The account's owner program is not among the programs Cloudbreak indexes, so the account is not stored. Account lookups return `null` (or `0` for `getBalance`) rather than an error in that case.

**Why is one `getProgramAccounts` query fast and a similar one slow?** The fast one has a matching index and the slow one does not. A new query shape becomes fast once it is requested often enough for automatic index creation to pick it up. Both return correct data regardless.

**My large `getProgramAccounts` response failed to parse. What happened?** A streamed response that errors partway through is truncated on purpose, so your JSON parser reports an incomplete document. Treat that as a failed request and retry; do not treat a partial body as a partial result.

**How fresh is the data?** A `confirmed` read reflects state at the latest confirmed slot; a `finalized` read reflects the latest finalized slot. Use `withContext: true` to see the exact slot, and `minContextSlot` to require a minimum.

**Can I get a live stream of changes instead of polling?** Use Dragon's Mouth. Cloudbreak answers point-in-time queries; Dragon's Mouth streams updates as they occur.


# Whirligig WebSockets

Better, Faster, Stronger Solana WebSockets

The Whirligig WebSocket Proxy is a Rust-based proxy between a Dragon's Mouth gRPC server and a WebSocket client. It allows the client to send requests to a WebSocket connection rather than through the gRPC protocol. This can be used when WebSocket connections are preferred, such as in a web3 application.

At the 'processed' commitment level most traders use, Whirligig will also provide intra-slot updates. Account updates will arrive up to 400ms faster than previous WebSocket subscriptions.

## WebSocket API

Whirligig aims for full compatibility with the Solana WebSocket API (<https://docs.solana.com/api/websocket>). Please be aware of one extended subscription we introduced: `transactionSubscribe`. This subscription may not be compatible with other providers.

To use Whirligig, append `/whirligig` to your RPC endpoint. Using web3.js, you can pass in a custom WS endpoint like this: `new web3.Connection('rpc_endpoint', { wsEndpoint: 'ws_endpoint' })`.&#x20;

NOTE: Some client software might require another trailing slash like `/whirligig/`.

NOTE: Connections inactive for over 60 seconds get closed. To keep a connection alive, you need to ping the server `{"jsonrpc":"2.0","method":"ping"}` &#x20;

### Whirligig CLI Client

You can check Whirligig CLI Client on GitHub: <https://github.com/rpcpool/yellowstone-whirligig-client>

### Commitment Levels

The Whirligig WebSocket API supports all three commitment levels (processed, confirmed, finalized).

You can, however, also [manage commitment levels](/project-yellowstone/dragons-mouth-grpc-subscriptions#managing-commitment-levels) in your app in the same way as you would in [Dragon's Mouth gRPC](/project-yellowstone/dragons-mouth-grpc-subscriptions#managing-commitment-levels). Each subscribe method implements a corresponding unsubscribe method which takes the subscription id provided by the subscribe method.

<https://docs.solana.com/cluster/commitments>

"**Processed**": The highest slot of the heaviest fork processed by the node. Ledger state at this slot is not derived from a confirmed or finalized block, but if multiple forks are present, is from the fork the validator believes is most likely to finalize.

"**Confirmed**": The highest slot that has been voted on by supermajority of the cluster, ie. is confirmed. Confirmation incorporates votes from gossip and replay. It does not count votes on descendants of a block, only direct votes on that block, and upholds "optimistic confirmation" guarantees in release 1.3 and onwards.

"**Finalized**": The highest slot having reached max vote lockout, as recognized by a supermajority of the cluster.

### Endpoint

The WebSocket endpoint allows you to stream data from the server to the client in real-time. The default endpoint for the WebSocket is `wss://<your endpoint>.rpcpool.com/<token>/whirligig`.

### Unsubscribe

Every subscription request on success return `id` of subscription, like:

```json
{
  "jsonrpc": "2.0",
  "result": 42,
  "id": 1
}
```

This subscription `id` can be used to unsubscribe from the stream:

```json
{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "slotUnsubscribe",
  "params": [42]
}
```

Result would be `true` in case of such subscription was active and removed. In case if subscription doesn't exists, result would be `false`.

```json
{
  "jsonrpc": "2.0",
  "result": true,
  "id": 1
}
```

### accountSubscribe

This method is fully compatiable with Solana WebSocket API. API details: <https://docs.rs/solana-client/latest/solana\\_client/nonblocking/pubsub\\_client/struct.PubsubClient.html#method.account\\_subscribe>

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "accountSubscribe",
  "params": [
    "SysvarC1ock11111111111111111111111111111111",
    {
      "encoding": "base58",
      "commitment": "finalized"
    }
  ]
}

```

`base58` stream data example:

```json
{
  "jsonrpc": "2.0",
  "method": "accountNotification",
  "params": {
    "result": {
      "context": {
        "apiVersion": "1.16.1",
        "slot": 206122916
      },
      "value": {
        "data": [
          "9AAG2zomYcUVKQfondw13yz193crQ5R5zmzdEojhMPXiyYEjD8viLrb",
          "base58"
        ],
        "executable": false,
        "lamports": 1169280,
        "owner": "Sysvar1111111111111111111111111111111111111",
        "rentEpoch": 361,
        "space": 40
      }
    },
    "subscription": 0
  }
}
```

### accountUnsubscribe

See [#Unsubscribe](#unsubscribe).

### blockSubscribe

Fully compatiable with Solana WebSocket API. API details: <https://docs.rs/solana-client/latest/solana\\_client/nonblocking/pubsub\\_client/struct.PubsubClient.html#method.block\\_subscribe>

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "blockSubscribe",
  "params": [
    "all",
    {
      "commitment": "finalized",
      "maxSupportedTransactionVersion": 1,
      "showRewards": false,
      "transactionDetails": "none"
    }
  ]
}
```

stream data example:

```json
{
  "jsonrpc": "2.0",
  "method": "blockNotification",
  "params": {
    "result": {
      "context": {
        "apiVersion": "1.16.1",
        "slot": 206125999
      },
      "value": {
        "block": {
          "blockHeight": 188652385,
          "blockTime": 1689709529,
          "blockhash": "74iqy9xdFvenLkFizkoBkkLRWTyygEeCiiN3B8Z8ma2D",
          "parentSlot": 206125998,
          "previousBlockhash": "Cq5nfgu9Y5KF47J9qj5CTYmt8D5CxBpw8HCRqQZ7o7i9"
        },
        "err": null,
        "slot": 206125999
      }
    },
    "subscription": 0
  }
}
```

### blockUnsubscribe

See [#Unsubscribe](#unsubscribe).

### logsSubscribe

Fully compatiable with Solana WebSocket API. API details: <https://docs.rs/solana-client/latest/solana\\_client/nonblocking/pubsub\\_client/struct.PubsubClient.html#method.logs\\_subscribe>

```json
{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "logsSubscribe",
  "params": [
    {
      "mentions": ["8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6"]
    },
    {
      "commitment": "finalized"
    }
  ]
}
```

stream data example:

```json
{
  "jsonrpc": "2.0",
  "method": "logsNotification",
  "params": {
    "result": {
      "context": {
        "apiVersion": "1.16.1",
        "slot": 206126709
      },
      "value": {
        "err": null,
        "logs": [
          "Program FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj invoke [1]",
          "Program log: process_instruction: FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj: 3 accounts, data=[12]",
          "Program log: Instruction: UpdateLendingPool",
          "Program log: current_borrow_rate: 73081853393945351",
          "Program FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj consumed 23800 of 1000000 compute units",
          "Program FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj success",
          "Program FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj invoke [1]",
          "Program log: process_instruction: FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj: 3 accounts, data=[12]",
          "Program log: Instruction: UpdateLendingPool",
          "Program log: current_borrow_rate: 82164236984041212",
          "Program FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj consumed 28159 of 976200 compute units",
          "Program FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj success",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N invoke [1]",
          "Program log: Instruction: LiquidateUnstakeLp",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
          "Program log: Instruction: Transfer",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4740 of 879873 compute units",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N consumed 79765 of 948041 compute units",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N success",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N invoke [1]",
          "Program log: Instruction: RemoveLiquidity",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N consumed 24767 of 868276 compute units",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N success",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N invoke [1]",
          "Program log: Instruction: SwapAndWithdraw",
          "Program log: withdraw_type: 2,\\n            user_info.pending_repay_0: 0,\\n            user_info.pending_repay_1: 0,\\n            user_info.tkn_0: 0,\\n            user_info.tkn_1: 0,\\n            ",
          "Program log: swap_direction: 0,\\n            amount_in: 0\\n            ",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
          "Program log: Instruction: Transfer",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4785 of 815778 compute units",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
          "Program log: Instruction: Transfer",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4831 of 807689 compute units",
          "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N consumed 47497 of 843509 compute units",
          "Program 2nAAsYdXF3eTQzaeUQS3fr4o782dDg8L28mX39Wr5j8N success"
        ],
        "signature": "5UPB2AEWgUafVDEw2xTkup8CUedxCMNL4k7EMuxtcyi1WnYLmpXeYUfBANpnPnpSz6LRwwLnBQf7tViKSFT7ojiB"
      }
    },
    "subscription": 0
  }
}
```

### logsUnsubscribe

See [#Unsubscribe](#unsubscribe).

### programSubscribe

Fully compatiable with Solana WebSocket API. API details: <https://docs.rs/solana-client/latest/solana\\_client/nonblocking/pubsub\\_client/struct.PubsubClient.html#method.program\\_subscribe>

```json
{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "programSubscribe",
  "params": [
    "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX",
    {
      "encoding": "base64+zstd",
      "commitment": "finalized"
    }
  ]
}
```

stream data example:

```json
{
  "jsonrpc": "2.0",
  "method": "programNotification",
  "params": {
    "result": {
      "context": {
        "apiVersion": "1.16.1",
        "slot": 206127070
      },
      "value": {
        "account": {
          "data": [
            "KLUv/QBYDQEAkHNlcnVtQQABAAQAcGFkZGluZwUA03+gI0CxQgAYeABG",
            "base64+zstd"
          ],
          "executable": false,
          "lamports": 457104960,
          "owner": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX",
          "rentEpoch": 0,
          "space": 65548
        },
        "pubkey": "5qRKrGPYaQmazdtMnia1iC3gFbpgW4CT7KPrHq9nJGm5"
      }
    },
    "subscription": 0
  }
}
```

### programUnsubscribe

See [#Unsubscribe](#unsubscribe).

### signatureSubscribe

Fully compatiable with Solana WebSocket API. API details: <https://docs.rs/solana-client/latest/solana\\_client/nonblocking/pubsub\\_client/struct.PubsubClient.html#method.signature\\_subscribe>

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "signatureSubscribe",
  "params": [
    "2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b",
    {
      "commitment": "finalized"
    }
  ]
}
```

stream data example:

```json
{
  "jsonrpc": "2.0",
  "method": "signatureNotification",
  "params": {
    "result": {
      "context": {
        "apiVersion": "1.16.1",
        "slot": 206127070
      },
      "value": {
        "err": null
      }
    },
    "subscription": 24006
  }
}
```

### signatureUnsubscribe

See [#Unsubscribe](#unsubscribe).

### slotSubscribe

Fully compatiable with Solana WebSocket API. API details: <https://docs.rs/solana-client/latest/solana\\_client/nonblocking/pubsub\\_client/struct.PubsubClient.html#method.slot\\_subscribe>

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "slotSubscribe"
}
```

stream data example:

```json
{
  "jsonrpc": "2.0",
  "method": "slotNotification",
  "params": {
    "result": {
      "parent": 206128153,
      "root": 206128115,
      "slot": 206128154
    },
    "subscription": 0
  }
}
```

### slotUnsubscribe

See [#Unsubscribe](#unsubscribe).

### transactionSubscribe

**Solana WebSocket API doesn't support this kind of subscription.**

Method can accept two arguments, first is `filter` and second `config`. In `filter` all fields are optional. `config` argument equal to [RpcBlockSubscribeConfig](https://docs.rs/solana-client/latest/solana_client/rpc_config/struct.RpcBlockSubscribeConfig.html).

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "transactionSubscribe",
  "params": [
    {
      "vote": false,
      "failed": false,
      "signature": "2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b",
      "accounts": {
        "include": ["5qRKrGPYaQmazdtMnia1iC3gFbpgW4CT7KPrHq9nJGm5"],
        "exclude": ["srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"],
        "required": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"]
      }
    },
    {
      "commitment": "processed",
      "encoding": "base58",
      "transactionDetails": "full",
      "showRewards": false,
      "maxSupportedTransactionVersion": 1
    }
  ]
}
```

stream data example:

```json
{
  "jsonrpc": "2.0",
  "method": "transactionNotification",
  "params": {
    "result": {
      "context": {
        "apiVersion": "1.16.1",
        "slot": 206132915
      },
      "value": {
        "slot": 241068727,
        "signature": "2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b",
        "transaction": {
          "meta": {
            "computeUnitsConsumed": 27426,
            "err": null,
            "fee": 5000,
            "innerInstructions": [
              {
                "index": 3,
                "instructions": [
                  {
                    "accounts": [
                      7,
                      3,
                      11
                    ],
                    "data": "3DTZbgwsozUF",
                    "programIdIndex": 15,
                    "stackHeight": null
                  },
                  {
                    "accounts": [
                      9,
                      8,
                      11
                    ],
                    "data": "3DTZbgwsozUF",
                    "programIdIndex": 15,
                    "stackHeight": null
                  }
                ]
              }
            ],
            "loadedAddresses": {
              "readonly": [],
              "writable": []
            },
            "logMessages": [
              "Program ComputeBudget111111111111111111111111111111 invoke [1]",
              "Program ComputeBudget111111111111111111111111111111 success",
              "Program GDDMwNyyx8uB6zrqwBFHjLLG3TBYk2F8Az4yrQC5RzMp invoke [1]",
              "Program log: Sequence in order | sequence_num=755609 | last_known=755608",
              "Program GDDMwNyyx8uB6zrqwBFHjLLG3TBYk2F8Az4yrQC5RzMp consumed 3398 of 300000 compute units",
              "Program GDDMwNyyx8uB6zrqwBFHjLLG3TBYk2F8Az4yrQC5RzMp success",
              "Program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX invoke [1]",
              "Program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX consumed 3820 of 296602 compute units",
              "Program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX success",
              "Program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX invoke [1]",
              "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
              "Program log: Instruction: Transfer",
              "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4740 of 285091 compute units",
              "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
              "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
              "Program log: Instruction: Transfer",
              "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4740 of 277786 compute units",
              "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
              "Program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX consumed 20208 of 292782 compute units",
              "Program srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX success"
            ],
            "postBalances": [
              10194778999,
              1224960,
              457104960,
              2039280,
              23357760,
              1825496640,
              3591360,
              2039280,
              2039280,
              2039280,
              457104960,
              0,
              1,
              1141440,
              1141440,
              934087680
            ],
            "postTokenBalances": [
              {
                "accountIndex": 3,
                "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                "owner": "ASx1wk74GLZsxVrYiBkNKiViPLjnJQVGxKrudRgPir4A",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "4973704995974757",
                  "decimals": 5,
                  "uiAmount": 49737049959.74757,
                  "uiAmountString": "49737049959.74757"
                }
              },
              {
                "accountIndex": 7,
                "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                "owner": "3oQLKk1TyyXMT14p2i8p95v5jKqsQ5qzZGwxZCTnFC7p",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "3167826900000000",
                  "decimals": 5,
                  "uiAmount": 31678269000,
                  "uiAmountString": "31678269000"
                }
              },
              {
                "accountIndex": 8,
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "owner": "ASx1wk74GLZsxVrYiBkNKiViPLjnJQVGxKrudRgPir4A",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "148629334218",
                  "decimals": 6,
                  "uiAmount": 148629.334218,
                  "uiAmountString": "148629.334218"
                }
              },
              {
                "accountIndex": 9,
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "owner": "3oQLKk1TyyXMT14p2i8p95v5jKqsQ5qzZGwxZCTnFC7p",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "5797710875",
                  "decimals": 6,
                  "uiAmount": 5797.710875,
                  "uiAmountString": "5797.710875"
                }
              }
            ],
            "preBalances": [
              10194783999,
              1224960,
              457104960,
              2039280,
              23357760,
              1825496640,
              3591360,
              2039280,
              2039280,
              2039280,
              457104960,
              0,
              1,
              1141440,
              1141440,
              934087680
            ],
            "preTokenBalances": [
              {
                "accountIndex": 3,
                "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                "owner": "ASx1wk74GLZsxVrYiBkNKiViPLjnJQVGxKrudRgPir4A",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "4973704995974757",
                  "decimals": 5,
                  "uiAmount": 49737049959.74757,
                  "uiAmountString": "49737049959.74757"
                }
              },
              {
                "accountIndex": 7,
                "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                "owner": "3oQLKk1TyyXMT14p2i8p95v5jKqsQ5qzZGwxZCTnFC7p",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "3167826900000000",
                  "decimals": 5,
                  "uiAmount": 31678269000,
                  "uiAmountString": "31678269000"
                }
              },
              {
                "accountIndex": 8,
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "owner": "ASx1wk74GLZsxVrYiBkNKiViPLjnJQVGxKrudRgPir4A",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "148629334218",
                  "decimals": 6,
                  "uiAmount": 148629.334218,
                  "uiAmountString": "148629.334218"
                }
              },
              {
                "accountIndex": 9,
                "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                "owner": "3oQLKk1TyyXMT14p2i8p95v5jKqsQ5qzZGwxZCTnFC7p",
                "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                "uiTokenAmount": {
                  "amount": "5797710875",
                  "decimals": 6,
                  "uiAmount": 5797.710875,
                  "uiAmountString": "5797.710875"
                }
              }
            ],
            "rewards": null,
            "status": {
              "Ok": null
            }
          },
          "transaction": [
            "nMXJYDy61Acdo2FY39i2ENUwNCMDYBWgRi6dNBgJdMHv7NgY4jUS6uwa9YMzyen1nzANm35qQSr189ykkvNzEFRhrULEwNxXA39HRS8JjYwEdUQ7ziXT2bqnFCeJc5rtk2uS7rbrrvEANjeDJ8PVkdJW9ZrxTcpg3nY3S5uVi6t7N2aB2eDmyju1cyRRU5T1aiT9vH1MHornT53XSe9UzuXFEP4sXfJjHovtucUkjqpJqU1PnKcrfK98wNT1Li6zjS8XGreRYNz5N8nNuxhVxFUM9CxQVurQQ3mhAiSYUmsptNgM5RqwSsy1CwebfZRpc35qThUtyu55TGyXh7MAgA8sJCfBrTDCgtWChaDkyqpggKPQ8VnCgLQdPBMwaady3cF3GgMvCpue5ryFW2p3EYgZxME7j6FTgBRjYRd6LuyeyhWp68fsNscYbMY8eZnV6WVVpSoBUpxg9FnyEpCatgS9ZNiTX3Wb8K3Dg4ncxmVutegbxn9uKbEvBNUWn5KV5m2jbmJmSafmMAJrFW3m4pv2HAKHezcEPfNNMCrK5J4Jjjg2JqQBKK9Z1PddhAX4CConAnbF7ZY4xkFnpWVpVi8cJrR6Wsa6VPDMpgzraukYmnwHgrBNx2bQokeZxmpyb7j2f6mWkreQcLZFFDx5tP33XbwSQqNFvtQZzUDJ2zYwMhZmCuN3E4vFgWQgU3Sb9jepy8ev9LniReyPdJRRRGbkJegdxYkbE2XDb3R2p9PF3um3LrKP35MeJgKoDrNBG6UAe6M5Q3cP7JfZGgZVud9GQcVGdnBa9ZbQJyojHnMbV4TDNmi2526dXoi5XqfUhNkrc1PgnAzrKzwZmNyiCX12SSUZLKzgzJTnk7C4gavYNSvhpNcupQyrLEnJf9FkQwhv1uN4WAsYdym6rR5oLuZet9bnebNvJCPWRCT4H59Yrm8y1QTF64hiB9zWazhLawAKAtYdBKteW9gr8BstGDUuo8QRMTyb3jBJYhdoeiKMqZWo",
            "base58"
          ]
        },
        "error": null
      }
    },
    "subscription": 0
  }
}
```

### transactionUnsubscribe

See [#Unsubscribe](#unsubscribe).


# Fumarole Reliable Streams

Scalable and reliable streaming of account and transaction data.

{% hint style="info" %}
Fumarole is currently **available by default on all mainnet subscriptions**. For questions or feedback, please reach out through your Triton CS channel.
{% endhint %}

Fumarole is our new streaming system to allow you to be able to reliably stream accounts and transactions.

#### Fumarole provides

* **High availability**: by collecting data from multiple downstream Solana nodes and merging them into a single stream, your stream does not get interrupted if a node restarts or is upgraded.
* Persistence: Fumarole stores up to 4 days of historical state and lets you reconnect your stream if your clients go down. Disconnect and resume from your last position any time within that window.
* **Parallel replay download :** Fumarole can replay in parallel geyser data wherever you left off.

#### How to get started

1. Use your existing mainnet subscription token — no separate access request needed.
2. Read our launch post and get started with the Fume CLI: <https://blog.triton.one/introducing-yellowstone-fumarole>
3. Build your integration with Fumarole via the Rust or Typescript SDKs: <https://github.com/rpcpool/yellowstone-fumarole>

#### Regional Endpoints

Fumarole runs as **independent regional clusters**. We currently operate:

| Region | Endpoint          | Location  |
| ------ | ----------------- | --------- |
| EU     | `ams.rpcpool.com` | Amsterdam |
| US     | `nyc.rpcpool.com` | New York  |

**Choosing an endpoint**

For Fumarole, connect directly to a **regional endpoint** rather than shared `*.mainnet.rpcpool.com` endpoints. Pick the region closest to your backend infrastructure to minimize latency.

{% hint style="warning" %}
Shared `*.mainnet.rpcpool.com` endpoints are **not recommended for Fumarole**. See the next section for why.
{% endhint %}

**Why direct regional endpoints matter for Fumarole**

The shared endpoints (`*.mainnet.rpcpool.com`) routes traffic to the closest regional load balancer based on GeoDNS. For stateless RPC calls this is ideal, any region can serve any request.

**Persistent subscribers in Fumarole are stateful per cluster.** A persistent subscriber and the slot offsets it tracks live locally on the cluster where it was created and **do not replicate across regions**.

If you connect through a shared endpoint, your traffic can be routed to a region where your persistent subscriber does not exist - for example, after a routing change, a network event, or a shift in the perceived geography of your backend. When that happens, your subscriber will not be found on the new cluster and you will need to recreate it.

Pointing your Fumarole client directly at a regional endpoint avoids this entirely.

**Switching regions**

To move a persistent subscriber from one regional cluster to another (e.g., EU → US):

1. Recreate your persistent subscriber on the new cluster - subscribers do not carry over between regional clusters.
2. Point your client to the new regional endpoint.
3. Reconnect. Your existing token continues to work; no token changes are required.

**Cross-region redundancy (customer-managed)**

With multiple regional clusters available, you can implement cross-region redundancy on the client side. If one cluster experiences a major issue, you can fail over to another region by recreating the persistent subscriber **from the last slot you observed on the failing cluster**.

Fumarole makes this type of redundancy easy to handle in the client since it tracks the last **full** slot you consumed.

This pattern is fully customer-managed:

* Your client tracks the last-seen slot per cluster.
* Your client detects the failure and triggers the cutover.
* Triton does not synchronize subscriber state, track last-seen slots, or orchestrate failover between clusters.

If you need this level of redundancy, plan your slot bookkeeping and failover logic accordingly.

#### Migrating from Dragon's Mouth

If you already have code built for our gRPC streams in Dragon's Mouth, integrating with Fumarole for additional reliability is easy. The code changes should be minimal as Fumarole uses the same types as Dragon's Mouth.

The main difference is that you need to manage a **persistent subscriber**, and alter your subscribe request slightly.

For more details see the Github repo:

[Yellowstone-fumarole-client crates.io](https://docs.rs/yellowstone-fumarole-client/0.5.0+solana.3/yellowstone_fumarole_client/)


# Fumarole Cluster Failover Guide

{% hint style="info" %}
This guide shows how to implement cross-region failover for Fumarole from the subscriber side. For an overview of regional clusters and the redundancy model, see [Fumarole Reliable Streams.](/project-yellowstone/fumarole)
{% endhint %}

Fumarole runs as **independent regional clusters**. Persistent subscriber state does not replicate across regions. This means that if your primary cluster experiences a major outage, you can fail over to another region, but the cutover is fully customer-managed and requires you to recreate your persistent subscriber on the secondary cluster from the last slot you consumed.

This guide walks through the full failover lifecycle: preparation, detection, cutover, and failback.

#### Scope and assumptions

This guide assumes:

* You have an active Fumarole integration on one regional cluster (your **primary**).
* You have access to a second regional cluster (your **secondary**), the same token works on both.
* You can implement client-side logic that tracks per-cluster state and reacts to outage signals.

The example throughout this guide uses **EU (`ams.rpcpool.com`)** as primary and **US (`nyc.rpcpool.com`)** as secondary. The procedure is symmetric, it applies in either direction.

{% hint style="warning" %}
Triton does **not** orchestrate failover for you, failover is **customer-managed**. We do not synchronize subscriber state, track your last-consumed slot, or trigger the cutover. Everything described below runs on your side. This page tells you exactly what to do.
{% endhint %}

#### Before you start

Failover is only possible if you do the prep work ahead of time. Put these in place during normal operation, not during the outage.

**1. Track the last fully-consumed slot per cluster.**

Your client must keep a record of the highest slot you have successfully processed end-to-end from each cluster. Persist this value durably (e.g. in your database, in Redis, in a file) anywhere that survives a process restart. You will need it to recreate the subscriber on the secondary cluster at the correct position.

Fumarole exposes the last **full** slot you have consumed, so you can rely on that value rather than computing it yourself.

**2. Build your outage-detection logic.**

Decide what counts as a cluster-level failure for your workload (see Detecting an outage below) and instrument the client to surface it.

**3. Make sure your processing pipeline is idempotent.**

There will be a small overlap window during failover where the same slot may be delivered from both clusters. Your downstream processing must tolerate seeing the same slot twice without producing duplicate side effects.

#### Detecting an outage

A failover is only worth the effort when the primary is genuinely down. Transient hiccups should be handled by normal reconnect logic, not failover.

Before triggering a failover, make sure the problem is on the cluster and not on your side. Some signals worth tracking:

* **Stream interruption that does not recover on reconnect.** Fumarole is built to survive normal node restarts and rolling upgrades. If you have been disconnected for more than a few minutes and reconnect attempts keep failing, that is beyond normal recovery.
* **`fume test-config` against the failing endpoint fails or hangs.** This is the most direct check that the cluster is reachable.
* **No new slots advancing.** The `fumarole_offset_lag_from_tip` metric on your client should normally trend toward zero. If it is growing unbounded and your network is healthy, the cluster is not advancing the log.
* **"Not found" or stale errors** for a persistent subscriber you know was healthy minutes ago.

{% hint style="warning" %}
**Check whether the problem is differential.** If only some of your clients are affected but others are fine, the problem is likely on your side: network, host, or deployment. Do not fail over.
{% endhint %}

If `fume test-config` succeeds against the failing endpoint and the subscriber exists, but you are not receiving data, that is worth a CS-channel ping before declaring a full failover.

#### Failover procedure

When your detection logic concludes the primary cluster is down, execute the following sequence.

**Step 1 — Stop the primary client**

Halt consumption from `ams.rpcpool.com`. Stop reconnect attempts.

**Step 2 — Read the last fully-consumed slot for the primary**

How you read `last_primary_slot` depends on whether the failover is planned or reactive.\
\
**Planned failover**, primary still healthy: gracefully stop your consumer (stop accepting new events and let in-flight processing drain) then take the slot of the last event your pipeline finished processing. The running client already has this value; no separate lookup is needed. If you also keep a durable record (recommended, see [Before you start](#before-you-start)), the two should agree.<br>

**Reactive failover**, primary unreachable: read `last_primary_slot` from the durable per-cluster value you have been tracking. This is the only source available when the primary is down. If you do not have such a record, your options are to start the secondary at LATEST (accepts a gap) or at a conservatively older slot (accepts duplicates). Fumarole guarantees at-least-once delivery, so duplicates are always safe to choose over data loss.

**Step 3 — Create the subscriber on the secondary cluster**

Connect to the secondary cluster and create the persistent subscriber there, starting from `last_primary_slot + 1`.

```bash
fume --config ~/.fumarole/config-us.yaml create \
  --name my-app-failover \
  --from_slot 312500001
```

If a subscriber with that name already exists on the secondary cluster from a previous failover, delete it first:

```bash
fume --config ~/.fumarole/config-us.yaml delete --name my-app-failover
fume --config ~/.fumarole/config-us.yaml create \
  --name my-app-failover \
  --from_slot 312500001
```

The same pattern applies whether you use the Fume CLI or the Rust / TypeScript SDK, create a fresh subscriber pointing at your recovery slot.

**Step 4 — Begin consuming from the secondary**

Start your client against `nyc.rpcpool.com`. Resume normal processing.

Until the secondary catches up to live tip, you will be receiving historical data. Expect some replay delay proportional to the gap between `last_primary_slot` and the current chain tip.

**Step 5 — Confirm forward progress**

Once the secondary is at live tip and slots are advancing normally, your failover is complete. Log the cutover and update your monitoring to track the new active cluster.

#### Failing back to the primary

When the primary cluster recovers, you have a choice:

* **Stay on the secondary.** Acceptable if the secondary is geographically reasonable for your workload.
* **Fail back to the primary.** Necessary if the secondary is far from your backend and you accepted the failover only for availability.

If you decide to fail back, run the failover procedure in reverse:

1. Track the last fully-consumed slot on the secondary.
2. Stop the secondary client.
3. Create the primary subscriber starting from `last_secondary_slot + 1` (deleting the existing primary subscriber first if needed).
4. Start consuming from the primary.
5. Confirm forward progress.

{% hint style="warning" %}
Failback is a planned operation, not an emergency one. Schedule it during a low-traffic window and verify the primary has been stable for some time before cutting back.
{% endhint %}

#### Common pitfalls

* **Failing over on a transient blip.** A single dropped connection is not a cluster outage. Tune your detection thresholds so normal reconnects do not trip failover.
* **Not persisting the last-consumed slot.** If you only hold it in memory and your process restarts mid-outage, you have lost your resume point. Persist it durably.
* **Forgetting the subscriber starts at `last_slot + 1`.** Starting at `last_slot` itself replays the slot you already processed, which widens the duplicate window.
* **Assuming token-or-permission errors mean an outage.** Authentication failures are not outage signals, they indicate a client configuration problem, not a cluster problem.

#### Quick reference

| Phase     | What you do                                                                                                                                                                                                                                                            |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Prepare   | Track last-consumed slot per cluster, build detection logic, ensure idempotent processing                                                                                                                                                                              |
| Detect    | Run `fume test-config`, watch the `fumarole_offset_lag_from_tip` metric, and check for "not found" / stale errors on your subscriber. See the [Fume CLI reference](https://github.com/rpcpool/yellowstone-fumarole/blob/main/apps/yellowstone-fumarole-cli/README.md). |
| Cut over  | Stop primary client → read `last_primary_slot` → create subscriber on secondary from `last_primary_slot + 1` → start consuming → confirm progress                                                                                                                      |
| Fail back | Same procedure in reverse, scheduled rather than reactive                                                                                                                                                                                                              |


# Program Data Streams

Real-time Solana program data streams

## Introduction

Program Data Streams are a high-performance gRPC endpoints that provides real-time, parsed Solana blockchain data streams by program. It solves the critical challenge of efficiently monitoring and reacting to on-chain activities by delivering pre-parsed account states and transaction instructions for specific Solana programs.

Powered by the [Yellowstone Vixen](https://github.com/rpcpool/yellowstone-vixen) open-source framework which provides the building blocks to create custom indexes for specific programs, accounts, and transactions. Vixen Streams enables developers to build responsive applications that can react to on-chain events without the complexity of building and maintaining custom parsing infrastructure.

### The Problem

Traditionally, monitoring blockchain activity requires:

* Polling RPC endpoints, which is inefficient and prone to missing fast-moving events.
* Custom parsing and filtering logic, which is complex to develop and difficult to scale.
* High infrastructure costs, as developers must manage large volumes of raw blockchain data.

### The Solution

Vixen Streams addresses these pain points by providing:

* gRPC Streaming – Real-time, high-performance delivery of parsed Solana blockchain data.
* Multi-Program Support – Detailed parsing for multiple Solana programs, with more coming soon.
* Scalability – Designed to handle high transaction volumes efficiently.
* TypeScript SDK – A simple and secure SDK for TypeScript applications with token-based authentication, abstracting away gRPC and protobuf complexity.

## Getting Started

Install the [Typescript SDK](https://www.npmjs.com/package/@triton-one/vixen-stream)

```bash
npm install @triton-one/vixen-stream
```

### Usage

```typescript
import {
  ProgramStreamsServiceClient,
  ProgramUpdateType,
  credentials,
  createCallCredentials,
  ProgramAddress,
} from "@triton-one/vixen-stream";

const creds = credentials.createSsl();

// <token> is your Triton One authentication token
const callCredentials = createCallCredentials("<token>");

const combinedCredentials = credentials.combineChannelCredentials(
  creds,
  callCredentials
);

// <endpoint> is your Triton One RPC endpoint
const client = new ProgramStreamsServiceClient(
  "<endpoint>",
  combinedCredentials
);

// Subscribe to account and instruction updates for the token keg program
let stream = client.Subscribe({
  program: ProgramAddress.Token,
});

// Handle the update events as you see fit. Write them to a database, call an API, or submit a Solana transaction.
stream.on("data", function (update: ProgramUpdateType<ProgramAddress.Token>) {
  console.log(update);
});

stream.on("end", () => console.log("end"));
stream.on("error", (e: Error) => console.log("error: ", e));
```

## Supported Programs

The following table lists the supported programs and their corresponding addresses. If you need support for additional programs, please reach out to our support.

<table><thead><tr><th width="252.1171875">Program</th><th>Address</th></tr></thead><tbody><tr><td>Token</td><td>TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA</td></tr><tr><td>Token22</td><td>TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb</td></tr><tr><td>OrcaWhirlpool</td><td>whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc</td></tr><tr><td>RaydiumClmm</td><td>CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK</td></tr><tr><td>Meteora</td><td>LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo</td></tr><tr><td>Pumpfun</td><td>6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P</td></tr><tr><td>JupiterSwap</td><td>JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4</td></tr><tr><td>MeteoraAmm</td><td>cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG</td></tr><tr><td>Moonshot</td><td>MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG</td></tr><tr><td>PumpSwaps</td><td>pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA</td></tr><tr><td>RaydiumCpmm</td><td>CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C</td></tr><tr><td>RaydiumAmmv4</td><td>675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8</td></tr><tr><td>KaminoLimitOrders</td><td>LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF</td></tr></tbody></table>

### FAQ

#### ❓ What commitment level are streams set to?

*Answer:* Streams deliver change events for all commitment levels (Processed, Confirmed, and Finalized).

❓ I’m getting a parser error. What should I do?

*Answer:* Make sure you have the latest SDK version installed. Check [npm](https://www.npmjs.com/package/@triton-one/vixen-stream) for the most recent release.

❓ Are streams globally distributed?

*Answer:* Yes. Our infrastructure runs in the USA, EU, and AP regions, minimizing latency for developers worldwide.\
❓ Can I stream multiple programs simultaneously?

*Answer:* Yes. You can open multiple subscriptions to stream data for different programs in parallel.

#### ❓ How do I authenticate?

*Answer:* Authentication is handled via token-based credentials. You must provide a valid Triton One token when initializing the client. Since we are currently in Beta, you’ll need to contact Triton Support to receive a token.

❓ What’s the difference between this and RPC polling?

*Answer:* RPC polling requires repeatedly fetching data and parsing it manually, which is slower and resource-intensive. Vixen Streams pushes pre-parsed, program-specific updates in real time, reducing overhead and improving responsiveness.

❓ Is there rate limiting?

*Answer:* No, there is no rate limiting. However, usage is billed based on bandwidth consumption.


# Shield Transaction Policies

Learn more about Shield in our blog post: [Introducing Yellowstone Shield 🚫🛡️](https://blog.triton.one/introducing-yellowstone-shield)

### What is Shield?

Yellowstone Shield lets you control which validators can process your Solana transactions. It's a simple but powerful idea: create a list of validators you trust (allowlist) or don't trust (blocklist), and your transactions will only go to the validators that match your criteria.

**Why use Shield?** Some validators engage in practices that can harm users, such as:

* **Sandwich attacks** - Inserting transactions before and after yours to extract value
* **Frontrunning** - Copying and executing your transaction idea before you
* **Other harmful MEV strategies** - Various techniques to extract value at users' expense

Shield helps you avoid these validators by simply not sending your transactions to them.

### How It Works

1. **Create a Policy** - You create an on-chain list of validators (either "allow these validators" or "block these validators")
2. **Own Your Policy** - When you create a policy, you receive a special token that gives you control over that policy
3. **Use the Policy** - Add your policy's address to your transactions
4. **Automatic Filtering** - The RPC checks each validator against your policy and only sends transactions to approved validators

**What happens to blocked transactions?** If the current validator doesn't meet your criteria, the transaction is dropped - it won't be sent to that validator. The system doesn't hold or queue transactions.

**Note about time-critical transactions**: Since Shield can drop transactions when no eligible validators are available, be careful using strict policies with time-sensitive operations like arbitrage or liquidations.

**Important**: This only works with Shield-enabled RPCs (like those using Yellowstone Jet). Standard Solana RPCs will ignore the policy parameter.

### Quick Start

If you're already using an RPC that supports Shield (like Triton RPC with Cascade), just add the policy to your transaction:

#### RPC Method Parameter

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sendTransaction",
  "params": [
    "<base64_encoded_transaction>",
    {
      "encoding": "base64",
      "skipPreflight": true,
      "forwardingPolicies": ["<your_policy_pda>"]
    }
  ]
}
```

#### HTTP Header (Alternative)

```
Solana-ForwardingPolicies: "<your_policy_pda>,<your_policy_pda2>"
```

### Understanding Policies and Tokens

When you create a Shield policy:

* An SPL token (using Token Extensions) is created
* You receive 1 token in your wallet
* This token gives you control over the policy
* As the creator, you keep the mint authority and can mint more tokens if needed
* Anyone holding the token can manage the policy (add/remove validators)
* The policy lives on-chain and can be used by anyone who knows its address

**Note**: Since the token is fungible and more can be minted, you could potentially share policy management with others by minting and sending them tokens.

### Important: Protection Limitations

Shield policies are **not automatic protection against harmful validator practices**. Here's why:

* Policies only block validators that are on your blocklist (or not on your allowlist)
* New validators join Solana every epoch
* Validators engaging in harmful practices (like sandwich attacks or frontrunning) can appear at any time
* **Policy maintainers must actively update their lists** to catch validators that harm users

Think of it as a filter, not a guarantee. The quality of protection depends entirely on how well the policy is maintained.

### Finding and Using Existing Policies

#### Policy Explorer

Visit [validators.app/yellowstone-shield](https://www.validators.app/yellowstone-shield?locale=en\&network=mainnet) to:

* Browse all existing policies
* See which validators are included/excluded
* Copy policy addresses for your transactions
* Check when policies were last updated

#### Common Policy Types

**Allow Lists**

* Top validators by stake
* Geographically distributed validators
* Performance-based selection

**Block Lists (Deny Lists)**

* Validators known for sandwich attacks or frontrunning
* Poor performing validators
* Community-flagged validators

### Creating Your Own Policy

#### Prerequisites

* Solana CLI installed and configured
* SOL for transaction fees
* A list of validator addresses

#### Step 1: Install Shield CLI

```bash
git clone https://github.com/rpcpool/yellowstone-shield
cd yellowstone-shield
cargo build --release --bin yellowstone-shield-cli
```

#### Step 2: Prepare Metadata

Create a JSON file describing your policy:

```json
{
  "name": "My Validator Policy",
  "symbol": "MVP",
  "description": "Blocks validators known for sandwich attacks",
  "image": "https://your-image-url.com/image.png",
  "external_url": "https://your-website.com",
  "attributes": []
}
```

Upload this file to IPFS or Arweave and save the URL.

#### Step 3: Create the Policy

```bash
# For a blocklist (deny these validators)
yellowstone-shield-cli policy create \
  --strategy deny \
  --name "My Validator Policy" \
  --symbol "MVP" \
  --uri "https://your-metadata-url.json"

# For an allowlist (only allow these validators)
yellowstone-shield-cli policy create \
  --strategy allow \
  --name "My Validator Policy" \
  --symbol "MVP" \
  --uri "https://your-metadata-url.json"
```

The CLI will output your policy's mint address - save this! You'll receive 1 token and retain mint authority, allowing you to mint more tokens later if you want to share policy management with others.

#### Step 4: Add Validators

Create a text file with validator addresses (one per line):

```
ValidatorAddress1...
ValidatorAddress2...
ValidatorAddress3...
```

Then add them to your policy:

```bash
yellowstone-shield-cli identities add \
  --mint <your_mint_address> \
  --identities-path validators.txt
```

#### Managing Your Policy

To manage a policy (add/remove validators), you must hold at least 1 token in your wallet. If you've transferred all your tokens to others, you'll lose the ability to manage the policy.

**Update validators (replace entire list):**

```bash
yellowstone-shield-cli identities update \
  --mint <mint_address> \
  --identities-path new_validators.txt
```

**Remove specific validators:**

```bash
yellowstone-shield-cli identities remove \
  --mint <mint_address> \
  --identities-path validators_to_remove.txt
```

**View policy details:**

```bash
yellowstone-shield-cli policy show --mint <mint_address>
```

### For RPC Providers

If you're running your own RPC and want to support Shield:

1. **Using Yellowstone Jet** - Shield support is built-in
2. **Custom Integration** - Use the [yellowstone-shield-store](https://crates.io/crates/yellowstone-shield-store) crate to:
   * Cache policies locally
   * Check validators against policies
   * Integrate with your transaction forwarding logic

### For Developers

Shield policies only work with RPCs that support them. You can't use the standard Solana web3.js methods - you need to make direct RPC calls:

```typescript
// Prepare and sign your transaction
import { Transaction } from '@solana/web3.js';
import bs58 from 'bs58';

const transaction = new Transaction().add(/* your instructions */);
const signedTx = await wallet.signTransaction(transaction);
const serializedTx = bs58.encode(signedTx.serialize());

// Send with Shield policy
const response = await fetch('https://your-shield-enabled-rpc.com', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'sendTransaction',
    params: [
      serializedTx,
      {
        encoding: 'base58',
        skipPreflight: true,
        forwardingPolicies: ['<policy_pda>']
      }
    ]
  })
});

const result = await response.json();
const signature = result.result;
```

### Key Points to Remember

1. **Policies need maintenance** - New validators appear every epoch. Lists must be updated regularly to remain effective.
2. **It's just a filter** - Shield simply prevents transactions from going to certain validators. It doesn't guarantee protection from sandwich attacks or transaction failures.
3. **Token = Control** - When you create a policy, you get an SPL token. Anyone with this token can manage the policy. As the creator, you can mint more tokens to share control.
4. **Anyone can use your policy** - Once created, anyone who knows the policy address can use it in their transactions.
5. **Not all RPCs support Shield** - Only use Shield-enabled RPCs like those using Yellowstone Jet.

### Resources

* **GitHub Repository**: [yellowstone-shield](https://github.com/rpcpool/yellowstone-shield)
* **Policy Explorer**: [validators.app/yellowstone-shield](https://www.validators.app/yellowstone-shield?locale=en\&network=mainnet)
* **Blog Post**: [Introducing Yellowstone Shield](https://blog.triton.one/introducing-yellowstone-shield)
* **Rust Crate**: [yellowstone-shield-store](https://crates.io/crates/yellowstone-shield-store)


# Vixen Parsing Framework

Real-Time Solana Data Pipeline Framework

## Overview

Yellowstone Vixen is a modular framework for building program-aware, real-time Solana data pipelines. Built to consume Solana events and transform them into structured, actionable data, Vixen powers indexing, analytics, and real-time application backends with ease.

At its core, Vixen listens to Dragon’s Mouth gRPC streams and routes program-specific changes through composable parsers and handlers, letting developers observe, react to, and enrich blockchain activity at scale.

> Use it to build your own indexer, analytics pipeline, or event-driven application—without writing low-level Solana RPC or deserialization logic.

## Why Use Yellowstone Vixen?

Traditional Solana infrastructure is expensive to scale, difficult to extend, and requires deep protocol expertise to interpret data. Vixen changes that by offering:

* ✅ Cost Efficiency: Share a single Dragon’s Mouth stream among multiple pipelines, and only process what you need.
* ⚙️ Operational Simplicity: Vixen is lightweight and dependency-free—easy to run in your own infra, Docker, or Kubernetes.
* 📊 Observability Built In: Get real-time metrics via Prometheus: lag, throughput, error rates.
* 🧱 Composability: Reusable parser crates handle complex cross-program interactions (like CPIs).
* 🔌 Stream-Ready Outputs: Serve parsed data directly to clients over your own gRPC interfaces.

## Key Features

| Feature                       | Description                                                                                   |
| ----------------------------- | --------------------------------------------------------------------------------------------- |
| 🛠 Parser + Handler System    | Easily transform raw Solana account/instruction data into structured models with logic hooks. |
| 🔥 Dragon’s Mouth Integration | Ingest real-time Geyser data with a few lines of config.                                      |
| 📈 Prometheus Metrics         | Out-of-the-box support for /metrics scraping.                                                 |
| 🧪 Offline Testing            | Use devnet fixtures to test locally without Solana node access.                               |
| 🔄 gRPC Server                | Serve custom, program-specific streams to your own clients.                                   |

## Quick Start

Here’s a minimal example that listens to Token Program updates and logs parsed data.

```rust
use yellowstone_vixen::Pipeline;
use yellowstone_vixen_parser::token_program::{AccountParser, InstructionParser};
use yellowstone_vixen_yellowstone_grpc_source::YellowstoneGrpcSource;

#[derive(Debug)]
pub struct Logger;

impl<V: std::fmt::Debug + Sync> vixen::Handler<V> for Logger {
    async fn handle(&self, value: &V) -> vixen::HandlerResult<()> {
        tracing::info!(?value);
        Ok(())
    }
}

fn main() {
    // Set up logging and config parsing
    ...

    yellowstone_vixen::Runtime::builder()
        .source(YellowstoneGrpcSource::new())
        .account(Pipeline::new(AccountParser, [Logger]))
        .instruction(Pipeline::new(InstructionParser, [Logger]))
        .metrics(yellowstone_vixen::metrics::Prometheus)
        .commitment_level(yellowstone_vixen::CommitmentLevel::Confirmed)
        .build(config)
        .run();
}
```

```
RUST_LOG=info cargo run -- --config ./Vixen.toml
```

## Prometheus Setup

To run Prometheus locally and collect Vixen metrics:

```
sudo docker-compose up
```

Then visit <http://localhost:9090> to explore.

## Supported Programs

\
Vixen ships with built-in support for a wide and growing set of Solana programs:

| Program                               | Parser                                                                                                                 |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Jupiter Aggregator v6                 | [jupiter-swap-parser](https://github.com/rpcpool/yellowstone-vixen/tree/main/crates/jupiter-swap-parser)               |
| Pump.fun / AMM                        | [pumpfun-parser](https://github.com/rpcpool/yellowstone-vixen/blob/main/crates/pumpfun-parser)                         |
| Boop.fun                              | [boop-parser](https://github.com/rpcpool/yellowstone-vixen/tree/main/crates/boop-parser)                               |
| Kamino Limit Orders                   | [kamino-limit-orders-parser](https://github.com/rpcpool/yellowstone-vixen/blob/main/crates/kamino-limit-orders-parser) |
| Raydium AMM / CLMM / CPMM / Launchpad | [View all Raydium parsers](https://github.com/rpcpool/yellowstone-vixen/tree/main/crates)                              |
| Meteora Vault / DLMM / Pools / DBC    | [View Meteora parsers](https://github.com/rpcpool/yellowstone-vixen/tree/main/crates)                                  |
| Whirlpools                            | [orca-whirlpool-parser](https://github.com/rpcpool/yellowstone-vixen/blob/main/crates/orca-whirlpool-parser)           |
| Virtuals                              | [virtuals-parser](https://github.com/rpcpool/yellowstone-vixen/blob/main/crates/virtuals-parser)                       |
| …                                     | Custom parser support available                                                                                        |

Need support for a new program? [Contact us](mailto:support@triton.one).

You can easily create a parser crate with the [Vixen parser generator](/project-yellowstone/vixen-parsing-framework/generate-parsers-with-codama) for Codama, using any IDL specification that Codama supports.

## Dragon’s Mouth Integration

Yellowstone Vixen is powered by [Dragon’s Mouth](/project-yellowstone/dragons-mouth-grpc-subscriptions), a high-performance gRPC source for Solana Geyser events.

## Developer Resources

* [🧪 Parser Testing with Fixtures](https://github.com/rpcpool/yellowstone-vixen/tree/main/crates/mock)

  Simulate devnet transactions and accounts offline.
* [📂 Example Pipelines](https://github.com/rpcpool/yellowstone-vixen/tree/main/examples)

  Learn by example—real pipelines for multiple programs.
* [📝 Config File Template](https://github.com/rpcpool/yellowstone-vixen/blob/main/Vixen.example.toml)

  Use this as your base to start streaming.


# Generate Parsers with Codama

How-to generate Vixen parser with Codama

This guide walks you through generating a [Vixen](https://github.com/rpcpool/yellowstone-vixen) Parser using [Codama](https://github.com/abklabs/codama), a tool for rendering Rust SDKs and parser implementations from IDLs.

Vixen is a framework for building real-time program data pipelines in Rust. This guide helps you scaffold a parser that can be used in the Vixen runtime to decode and process Solana program data.

#### Prerequisites

1. **An `idl.json` file:** Either Anchor-generated or custom.
2. **Install** [**pnpm**](https://pnpm.io/)**:** Or use npm/yarn if preferred.
3. **Initialize a JavaScript Project:**

   ```bash
   pnpm init
   ```

#### Installation

Install the required Codama packages:

```bash
pnpm install @codama/renderers-vixen-parser
```

Also, install dependencies for the parser generation script:

```bash
pnpm install \
  @codama/nodes \
  @codama/nodes-from-anchor \
  @codama/renderers-core \
  @codama/visitors-core
```

#### Setup

**Create a Parser Generation Script**

Create a new file, `codama.cjs`:

```javascript
const path = require("node:path");
const { rootNode } = require("@codama/nodes");
const { rootNodeFromAnchor } = require("@codama/nodes-from-anchor");
const { readJson } = require("@codama/renderers-core");
const { visit } = require("@codama/visitors-core");
const { renderVisitor } = require("@codama/renderers-vixen-parser");

const projectName = "example-parser";
const idl = readJson(path.join(__dirname, "idl.json"));
const node = rootNodeFromAnchor(idl);

visit(
    node,
    renderVisitor({
        projectFolder: __dirname,
        projectName,
    }),
);
```

> **Tip:** The `projectName` is used for the Cargo crate name of the generated parser.

**Run the Code Generation Script**

```bash
node codama.cjs
```

Your folder structure should look like:

```
example-parser/
├── proto/
│   └── example_parser.proto
├── src/
│   ├── generated_parser/
│   ├── generated_sdk/
│   └── lib.rs
├── build.rs
├── Cargo.toml
├── codama.cjs
└── idl.json
```

**Build and Verify**

```bash
cargo build
```

If successful, you now have a working parser for Solana account data using Yellowstone Vixen.

#### Completion

Congratulations! You now have a custom Vixen parser ready for integration into a Vixen pipeline.


# Yellowstone Account Sync (Beta)

Faster account info queries with lower costs

## Yellowstone Account Sync & the Triton SDK

This guide explains how to use `@triton-one/triton-sdk` to read Solana accounts. Later sections explain each option and method.

You do not need to understand gRPC, WebSocket, or the service internals before you start.

#### Note: The service is currently in a paid beta and billed by standard streaming rates of $0.08/GB.

### TL;DR

If your Node.js application frequently calls Solana RPC methods such as `getAccountInfo` or `getMultipleAccountsInfo`, those reads can add RPC latency, request volume, and per-request costs as your application scales.

Account Sync streams account updates in the background and keeps the latest account state in memory. It exposes the same account-reading methods as `@solana/web3.js`, so existing applications can use the streamed data without being rewritten around a streaming API.

Instead of each read making another RPC request, methods such as `getAccountInfo` can read the latest account state directly from memory.

| Action                 | Standard RPC reads       | Account Sync                      |
| ---------------------- | ------------------------ | --------------------------------- |
| Account updates        | Fetched on request       | Streamed continuously             |
| `getAccountInfo`       | RPC request              | In-memory read                    |
| RPC processing latency | On every request         | Avoided for cached account reads  |
| Request rate limits    | Apply                    | Not applicable to in-memory reads |
| Usage cost             | RPC requests + bandwidth | Primarily streamed bandwidth      |
| Application changes    | -                        | Existing Web3.js-style read APIs  |

### Performance

In our `getMultipleAccountsInfo` benchmark, Account Sync reduced p90 read latency from \~**15 ms to \~1.65 ms,** an **86.3% reduction**.

| Method                | Median latency | p90 latency |
| --------------------- | :------------: | :---------: |
| `@solana/web3.js` RPC |    12.01 ms    |   14.94 ms  |
| Account Sync          |     1.65 ms    |   1.98 ms   |
| **Reduction**         |    **86.3%**   |  **86.8%**  |

You can checkout the benchmark script [here](https://github.com/rpcpool/triton-js-sdk/blob/main/examples/account-sync/src/compare_get_multiple_accounts_info_latency.ts)

### What this SDK does

Solana stores programs and user state in **accounts**. Each account has an address, a balance, an owner, and some data bytes.

A normal `@solana/web3.js` connection asks a Solana JSON-RPC server for account data each time your code makes a read. **JSON-RPC** is the normal request and response API used by Solana clients.

This SDK adds a live account stream:

1. Your application tells the SDK which accounts it needs.
2. The account-sync service sends updates for those accounts.
3. The SDK **streams** the newest value for each account in memory.
4. Supported account read methods use that local value when possible.
5. The SDK also uses JSON-RPC to load initial state, resolve a missing local value, and keep reads working while the stream is down.
6. The SDK also exports the public values and types from `@solana/web3.js`. You can import `PublicKey`, `AccountInfo`, and the SDK `Connection` from one package.
7. The SDK is thus a drop in replacement for the `@solana/web3.js` SDK.

The in-memory copy is called the **buffer** in this guide.

The main benefit is that repeated reads can use current data already held by your application instead of making a new JSON-RPC request each time.It does not provide stored or replayable events.

### When to use Account Sync

Account Sync is designed for applications that frequently poll the same accounts using methods such as `getAccountInfo` or `getMultipleAccountsInfo` and benefit from having the latest account state available with low read latency.

#### A good fit

Account Sync is a good fit if your application:

* Frequently calls `getAccountInfo` or `getMultipleAccountsInfo` for the same accounts repeatedly.
* Polls accounts to keep application state up to date.
* Needs fresh account data with low read latency.
* Makes enough account RPC requests that request volume or RPC costs are significant.

In these workloads, Account Sync replaces repeated RPC reads with streamed account updates and serves subsequent reads from memory. This can reduce both read latency and RPC request costs.

#### When standard RPC may be a better fit

Standard RPC calls may be more appropriate if your application:

* Reads account data infrequently.
* Does not require account state to stay continuously up to date.
* Only needs fresh account data in response to occasional user actions or events.

Account Sync continuously receives updates for subscribed accounts, even when your application is not reading them. If reads are infrequent, the bandwidth cost of maintaining the stream may outweigh the savings from avoiding individual RPC requests.

**Rule of thumb:** The more frequently you poll the same accounts, the more Account Sync can reduce repeated RPC work. For occasional reads, fetching the data on demand is typically a better fit.

## **Node.js and browser support**

| Environment | WebSocket | gRPC |
| ----------- | --------- | ---- |
| Node.js     | Yes       | Yes  |
| Browser     | Yes       | No   |

## Install

With npm:

```bash
npm install @triton-one/triton-sdk
```

With pnpm:

```bash
pnpm add @triton-one/triton-sdk
```

With Yarn:

```bash
yarn add @triton-one/triton-sdk
```

### Examples

You can checkout all the examples [here](https://github.com/rpcpool/triton-js-sdk/tree/main/examples/account-sync)

## Node.js with gRPC

Use gRPC with backend applications wherever its supported

```ts
import {
  AccountSyncTransports,
  Connection,
  PublicKey,
} from "@triton-one/triton-sdk";

const endpoint = process.env.RPC_ENDPOINT;

if (!endpoint) {
  throw new Error("Set RPC_ENDPOINT before running this program");
}

const address = new PublicKey(
  "So11111111111111111111111111111111111111112",
);

const connection = new Connection(endpoint, {
  accountSync: {
    transport: AccountSyncTransports.GRPC,
    initialAccounts: [address],
  },
});

try {
  const response = await connection.getAccountInfoAndContext(address);

  console.log("Observed at slot:", response.context.slot);
  console.log("Account:", response.value);
} finally {
  await connection.close();
}
```

gRPC is not available in browsers. Browser code that requests it throws an error during `Connection` construction.

## Browser with Websocket

The package automatically selects its browser entry when used by a browser build tool. The browser entry does not load the Node.js gRPC code.

```ts
import {
  AccountSyncTransports,
  Connection,
  PublicKey,
} from "@triton-one/triton-sdk";

const address = new PublicKey(
  "So11111111111111111111111111111111111111112",
);

const connection = new Connection(
  "https://api.example.com/<BROWSER_TOKEN>",
  {
    accountSync: {
      transport: AccountSyncTransports.WS,
      initialAccounts: [address],
    },
  },
);

async function showAccount(): Promise<void> {
  const response = await connection.getAccountInfoAndContext(address);
  const output = document.querySelector("#account-output");

  if (output) {
    output.textContent = response.value
      ? `Balance: ${response.value.lamports} lamports at slot ${response.context.slot}`
      : `Account missing at slot ${response.context.slot}`;
  }
}

void showAccount().catch(console.error);

window.addEventListener("pagehide", () => {
  void connection.close();
});
```

## Native web3.js connections

Omit `accountSync` to get an actual `@solana/web3.js` Connection. Its prototype and methods come from web3.js. The SDK does not create an account-sync core, transport, hydration plugin, or parse cache.

```ts
import { Connection, PublicKey } from "@triton-one/triton-sdk";

const connection = new Connection(
  "https://example.com/token",
  "confirmed",
);

const account = await connection.getAccountInfo(PublicKey.default);
```

An ordinary web3.js config object also selects native mode. Its `fetch`, `fetchMiddleware`, `httpHeaders`, `wsEndpoint`, retry settings, and other options go directly to web3.js. An omitted commitment keeps the web3.js default behavior.

## Account-sync connections

Pass `accountSync: {}` to enable buffered account reads with the default settings. Supply options inside that object to configure the transport and buffer. Node supports WebSocket and gRPC; the browser build supports WebSocket.

```ts
import { Connection, PublicKey } from "@triton-one/triton-sdk";

const connection = new Connection("https://example.com/token", {
  accountSync: {},
});

try {
  const account = await connection.getAccountInfo(PublicKey.default);
} finally {
  await connection.close();
}
```

Account-sync reads default to `confirmed` when neither the constructor nor `accountSync` supplies a commitment. The enabled implementation retains its own hydration and parsing behavior. Its hydration requests do not inherit web3.js fetch or header settings.

### Choosing a mode

| Input                                                       | Connection returned |
| ----------------------------------------------------------- | ------------------- |
| No config, a commitment string, or ordinary web3.js options | Native web3.js      |
| `accountSync: undefined`                                    | Native web3.js      |
| `accountSync: {}` or an options object                      | Account-sync        |

Only account-sync instances provide `addAccounts`, `removeAccounts`, `setAccounts`, `close`, and `getLastTransportError`.

## How a read works

Understanding these steps helps explain response time and errors.

**1. An account that is already in the buffer**

If the SDK has the value buffered, it returns that value from memory. It does not start a new JSON-RPC request for that read.

**2. An account that is not in the buffer**

The SDK starts a one-time `getMultipleAccounts` JSON-RPC request. By default, it also adds a stream subscription for the account. The SDK returns the first suitable answer from the buffer or JSON-RPC.

If JSON-RPC confirms that the account is absent, the result is `null`. The SDK stores the slot where that absence was observed.

If neither source can produce a suitable answer before `missTimeoutMs`, the read fails.

**3. Startup**

The SDK opens the stream and asks JSON-RPC for the current value of every tracked account specified in the `initialAccounts` field. This prevents the SDK from waiting for the next account change before it knows the starting state.

Stream updates may arrive while the JSON-RPC starting values are loading. The SDK compares their slots and write order and gives you the newest value.

**4. When the stream disconnects**

The SDK starts JSON-RPC polling for tracked accounts and tries to reconnect in the background. After reconnecting, it sends the complete account set again and gets a fresh JSON-RPC view before it stops polling.

Reads can still succeed during this time. They may be served from a buffered value or a JSON-RPC request.

## Read one account

**Value only**

```ts
const account = await connection.getAccountInfo(address);

if (account) {
  console.log(account.lamports);
}
```

The result is `AccountInfo<Buffer> | null`.

**Account fields**

For an account that exists, the result is same as web3.js response which contains:

| Field        | Meaning                                             |
| ------------ | --------------------------------------------------- |
| `lamports`   | Account balance. One SOL is 1,000,000,000 lamports. |
| `owner`      | `PublicKey` of the program that owns the account.   |
| `executable` | Whether the account contains an executable program. |
| `rentEpoch`  | Epoch when the account will next owe rent.          |
| `data`       | Raw account bytes as a Node.js `Buffer`.            |
| `space`      | Full account data size in bytes at runtime.         |

The current web3.js `AccountInfo` TypeScript type does not declare `space`, but the Triton SDK includes it at runtime to match current RPC responses.

## Read several accounts

Use the `getMultipleAccountsInfo` call when you need several accounts together:

```ts
const addresses = [addressA, addressB, addressA];

const accounts = await connection.getMultipleAccountsInfo(addresses);

for (const [index, account] of accounts.entries()) {
  console.log(addresses[index].toBase58(), account);
}
```

Results stay in input order. Duplicate addresses produce duplicate result positions. A missing account has `null` in its position.

To receive a shared context:

```ts
const response = await connection.getMultipleAccountsInfoAndContext([
  addressA,
  addressB,
]);

console.log("Shared slot:", response.context.slot);
console.log("Accounts:", response.value);
```

The SDK sends unresolved accounts in one JSON-RPC request when possible. Large background hydration sets are split into groups of 100 accounts.

## Read parsed accounts

Raw account data is bytes. Some common Solana programs have known data layouts. A parsed account replaces those bytes with a JavaScript object that is easier to read.

Read one parsed account:

```ts
const response = await connection.getParsedAccountInfo(address, {
  commitment: "confirmed",
});

console.log("Parsed account:", data.parsed);
```

Read several parsed accounts:

```ts
const response = await connection.getMultipleParsedAccounts([
  addressA,
  addressB,
]);

console.log(response.context.slot, response.value);
```

**Why parsed data can fall back to raw data**

The parser may not support the account's owner program. Some parsers also need another account. For example, parsing an SPL token account needs its mint account to calculate display amounts.

The connection automatically fetches a required mint through its own `getAccountInfo` method. It keeps these supporting values in a small cache. The default cache holds up to 256 entries for five minutes. Concurrent parses that need the same uncached mint share one fetch.

`dataSlice` is meant for raw bytes. Asking for parsed data with a slice may cause raw fallback data because a partial account often cannot be parsed.

## Read part of account data

Use `dataSlice` when you need only a range of raw account bytes:

```ts
const account = await connection.getAccountInfo(address, {
  commitment: "confirmed",
  dataSlice: {
    offset: 8,
    length: 32,
  },
});
```

This means “start at byte 8 and return at most 32 bytes.”

Both `offset` and `length` must be non-negative safe integers. A zero length returns an empty `Buffer`. An offset past the end also returns an empty `Buffer`. A range that reaches past the end stops at the end.

The slice changes only the returned `data`. The runtime `space` field still contains the full size of the account data.

The same option works with single-account and multiple-account methods.

## Require a minimum slot

Use `minContextSlot` when you must not accept an observation older than a known slot:

```ts
const recentSlot = await connection.getSlot("finalized");

const response = await connection.getAccountInfoAndContext(address, {
  commitment: "confirmed",
  minContextSlot: recentSlot,
});

if (response.context.slot < recentSlot) {
  throw new Error("The SDK returned an older observation than requested");
}
```

`minContextSlot` must be a non-negative safe integer.

On a cache miss, the SDK sends this value to Solana JSON-RPC. If the RPC node has not reached the requested slot, it commonly returns error code `-32016`. The SDK also waits for a suitable buffered observation until `missTimeoutMs`.

**Current slot limit**

For a value loaded from JSON-RPC, the context is the bank context slot returned by JSON-RPC. For a value received through the stream, the SDK currently uses the slot of that account update. It does not yet track every bank slot.

This matters when an account has not changed recently. A bank may be newer even though the last account update slot is older. Do not describe the context of every cached stream value as a full bank-progress marker.

For a multiple-account result, the returned shared context is the lowest known observation slot in that result.

## Choose a commitment

The SDK supports three values `processed, confirmed, finalized` for account-sync reads. Other web3.js commitment names, such as `max`, are rejected.

Set the default for the connection:

```ts
const connection = new Connection(endpoint, {
  accountSync: {
    commitment: "confirmed",
  },
});
```

You can also use the normal web3.js constructor commitment:

```ts
const connection = new Connection(endpoint, "finalized");
```

`accountSync.commitment` wins when both forms are present. With neither form, the default is `confirmed`.

Override it for one read:

```ts
await connection.getAccountInfo(address, "processed");

await connection.getAccountInfo(address, {
  commitment: "finalized",
});
```

A different commitment has separate account state. The SDK therefore opens a separate stream and buffer for it. An unused non-default commitment stream is closed after its pinned and temporary accounts are gone.

## Choose which accounts stay subscribed

There are two kinds of tracked accounts.

### **Pinned accounts**

Pinned accounts stay subscribed until you remove them or close the connection.

Pin accounts at startup:

```ts
const connection = new Connection(endpoint, {
  accountSync: {
    initialAccounts: [addressA, addressB],
  },
});
```

Pin more accounts later:

```ts
await connection.addAccounts([addressC, addressD]);
```

Duplicate addresses are ignored.

### **Temporary accounts**

By default, reading an untracked account creates a temporary subscription. It expires after `dynamicSubscriptionTtlMs` without another read. The default is 60 seconds.

Disable this for default-commitment cache misses:

```ts
const connection = new Connection(endpoint, {
  accountSync: {
    autoSubscribeOnMiss: false,
  },
});
```

The read still makes a one-time JSON-RPC request. A read at a commitment other than the connection default still needs its own commitment stream and may track the account there.

### **Remove accounts**

```ts
await connection.removeAccounts([addressA]);
```

This removes pinned or temporary ownership and clears the cached account state immediately. A read already in progress may finish through its one-time JSON-RPC request.

A later read can create a new temporary subscription. If the account should remain subscribed, call `addAccounts` before reading it again.

Removing an address that is not tracked does not fail. It also clears any supporting parsed-account cache entry for that address.

**Replace the full account set**

```ts
await connection.setAccounts([addressB, addressC]);
```

This makes the given list the complete pinned set for that commitment. It also clears all temporary accounts for that commitment.

## **Use a specific commitment**

All three account control methods accept an optional commitment:

```ts
await connection.addAccounts([addressA], "finalized");

await connection.removeAccounts([addressA], "finalized");

await connection.setAccounts([addressB], "finalized");
```

Without this argument, they use the configured account-sync commitment you set during `Connection` creation.

## **Complete lifecycle example**

```ts
import {
  AccountSyncTransports,
  Connection,
  PublicKey,
} from "@triton-one/triton-sdk";

const endpoint = process.env.RPC_ENDPOINT;

if (!endpoint) throw new Error("Set RPC_ENDPOINT");

const address = new PublicKey(
  "So11111111111111111111111111111111111111112",
);

const newAddress = new PublicKey(
  "ping6gwBZx1ccMMFyLgkVSupUmujYrFidEXuNRPq989",
);

const connection = new Connection(endpoint, {
  accountSync: {
    transport: AccountSyncTransports.GRPC,
    initialAccounts: [],
    dynamicSubscriptionTtlMs: 60_000,
  },
});

try {
  await connection.addAccounts([address]);

  const first = await connection.getAccountInfoAndContext(address);
  console.log("First read:", first);

  await connection.removeAccounts([address]);

  // Re-add it before the next read so it remains pinned.
  await connection.addAccounts([address]);

  const second = await connection.getAccountInfoAndContext(address);
  console.log("Read after re-add:", second);

  // Clear all buffered accounts and pin new accounts
  await connection.setAccounts([newAddress]);

  // Read the new accounts
  const third = await connection.getAccountInfoAndContext(newAddress);
  console.log("New account:", third);
} finally {
  // close the streams and clear the buffered accounts
  await connection.close();
}
```

The SDK does not set a fixed account-count limit. Your endpoint, memory, CPU, network, and the account-sync service can still have practical limits. Test the size you plan to use.

## Configuration reference

All account-sync settings belong under `accountSync`:

```ts
const connection = new Connection(endpoint, {
  accountSync: {
    // Settings go here.
  },
});
```

**Common settings**

| Setting                    | Type                                        | Default                 | Meaning                                                                                    |
| -------------------------- | ------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------ |
| `transport`                | `"ws" \| "grpc"`                            | `"ws"`                  | Stream type. Browsers allow only `"ws"`.                                                   |
| `subscriptionEndpoint`     | `string`                                    | Derived from `endpoint` | Optional, overrides only the account-sync stream endpoint. JSON-RPC still uses `endpoint`. |
| `commitment`               | `"processed" \| "confirmed" \| "finalized"` | `"confirmed"`           | Default commitment for buffered reads.                                                     |
| `initialAccounts`          | Array of strings or `PublicKey` values      | `[]`                    | Accounts pinned from startup.                                                              |
| `autoSubscribeOnMiss`      | `boolean`                                   | `true`                  | Temporarily subscribe when a default-commitment read misses the buffer.                    |
| `missTimeoutMs`            | positive safe integer                       | `5000`                  | Longest wait for a suitable buffered or RPC observation.                                   |
| `rpcPollIntervalMs`        | positive safe integer                       | `1000`                  | Delay between RPC refreshes while the stream is unavailable.                               |
| `reconnectInitialDelayMs`  | positive safe integer                       | `100`                   | First wait before reconnecting a failed stream.                                            |
| `reconnectMaxDelayMs`      | positive safe integer                       | `5000`                  | Largest reconnect wait. Must be at least the initial delay.                                |
| `connectTimeoutMs`         | positive safe integer                       | `10000`                 | Longest wait for one WebSocket connection attempt. Not used by gRPC.                       |
| `closeTimeoutMs`           | positive safe integer                       | `5000`                  | Longest wait for account-sync shutdown.                                                    |
| `dynamicSubscriptionTtlMs` | positive safe integer                       | `60000`                 | Idle lifetime of an account subscription created by a read for an unpinned account.        |

A **safe integer** is a whole JavaScript number that can be represented exactly. These time values are milliseconds. Zero, negative values, fractions, `NaN`, and numbers outside the safe integer range are rejected.

### **gRPC settings**

These settings are valid only with the gRPC transport:

```ts
const connection = new Connection(endpoint, {
  accountSync: {
    transport: AccountSyncTransports.GRPC,
    grpc: {
      flowControlWindowBytes: 16 * 1024 * 1024,
      maxReceiveMessageLengthBytes: 16 * 1024 * 1024,
      keepAliveIntervalMs: 30_000,
      keepAliveTimeoutMs: 10_000,
      keepAlivePermitWithoutCalls: true,
    },
  },
});
```

| Setting                        | Default    | Meaning                                        |
| ------------------------------ | ---------- | ---------------------------------------------- |
| `flowControlWindowBytes`       | `16777216` | Fixed HTTP/2 receive window used by `grpc-js`. |
| `maxReceiveMessageLengthBytes` | `16777216` | Largest gRPC message the client accepts.       |
| `keepAliveIntervalMs`          | `30000`    | Time between gRPC keepalive pings.             |
| `keepAliveTimeoutMs`           | `10000`    | Time allowed for a keepalive response.         |
| `keepAlivePermitWithoutCalls`  | `true`     | Allow keepalive pings while no call is active. |

Byte and time values must be positive safe integers. The keepalive flag must be a boolean.

Do not add `grpc` settings while `transport` is WebSocket. The Node.js constructor rejects that combination. The browser build rejects all gRPC settings.

**Separate RPC and stream hosts**

Use `subscriptionEndpoint` if JSON-RPC and account sync have different hosts:

```ts
const connection = new Connection("https://rpc.example.com/<RPC_TOKEN>", {
  accountSync: {
    transport: AccountSyncTransports.GRPC,
    subscriptionEndpoint: "https://stream.example.com/<STREAM_TOKEN>",
  },
});
```

Initial account loading and fallback polling still use the rpc endpoint.

## Errors and recovery

**Missing account versus failed read**

These cases are different:

* `null`: JSON-RPC successfully observed that the account did not exist.
* rejected promise: the SDK could not complete the read, the input was invalid, or JSON-RPC returned an error.

### **Read timeout**

Context and multiple-account methods can reject with `AccountSyncReadTimeoutError`:

```ts
import {
  AccountSyncReadTimeoutError,
  Connection,
  PublicKey,
} from "@triton-one/triton-sdk";

try {
  const response = await connection.getAccountInfoAndContext(address);
  console.log(response);
} catch (error) {
  if (error instanceof AccountSyncReadTimeoutError) {
    console.error("Account:", error.accountId);
    console.error("Commitment:", error.commitment);
    console.error("Waited milliseconds:", error.timeoutMs);
    console.error("Minimum slot:", error.minContextSlot ?? "not set");
  } else {
    throw error;
  }
}
```

`getAccountInfo` follows web3.js error wording and wraps failures in a new `Error` that includes the account address. Because of that wrapping, catch `AccountSyncReadTimeoutError` directly only when using a context or multiple-account method.

### **JSON-RPC errors**

Context and multiple-account methods keep web3.js `SolanaJSONRPCError` instances. For example, a node that has not reached `minContextSlot` commonly returns code `-32016`.

```ts
import { SolanaJSONRPCError } from "@triton-one/triton-sdk";

try {
  await connection.getAccountInfoAndContext(address, {
    minContextSlot: requiredSlot,
  });
} catch (error) {
  if (error instanceof SolanaJSONRPCError && error.code === -32016) {
    console.error("Account not found on RPC");
  } else {
    throw error;
  }
}
```

Again, `getAccountInfo` wraps the original failure, so it does not return the original `SolanaJSONRPCError` instance to your catch block.

### **Background errors**

Stream, polling, hydration, and reconnect work happens in the background. Read the newest saved background error with:

```ts
const error = connection.getLastTransportError();

if (error) {
  console.warn("Account sync saw a background error:", error);
}
```

Despite its method name, this can contain more than a transport error. It can also contain an RPC polling, starting-state, or recovery error.

The SDK keeps the last error even after recovery. A non-null result does not by itself prove that the connection is currently broken. Use it for logs and health details, not as the only reason to stop serving reads.

### **Connection and shutdown timeouts**

A WebSocket attempt that exceeds `connectTimeoutMs` is reported as an error named `AccountSyncConnectTimeoutError`. Polling and reconnect work can continue.

If shutdown exceeds `closeTimeoutMs`, `close()` rejects with an error named `AccountSyncCloseTimeoutError`. The connection still remains closed.

After `close()`, reads and most account-set changes reject with `account-sync connection is closed`. Calling `close()` again is safe and shares the same shutdown result.

## Production guidance

**Always close unused connections**

Each connection can own streams, timers, retries, and pending reads. Use `try`/`finally` in scripts and services. In long-running applications, reuse a connection instead of making one per read.

**Plan for both the stream and JSON-RPC**

JSON-RPC is not only an emergency fallback. It supplies starting state and resolves new account reads. Make sure the endpoint allows the expected RPC load, especially when adding many accounts at once.

**Watch memory and network use**

The full latest data for tracked accounts is kept in application memory. Large accounts and large account sets increase memory, RPC, and stream traffic. There is no client-side fixed count limit.

Measure with realistic account sizes and update rates. Do not raise gRPC window or message limits without checking memory use.

**Large gRPC streams**

For large streams, current project guidance is:

* use a host with at least 5 Gbps of internet capacity;
* keep round-trip time to the Triton endpoint at or below 50 ms;
* use the default fixed 16 MiB window settings as a starting point;
* avoid short-lived function workers for a continuous stream; and
* test the host and connection under expected load before production use.

`grpc-js` uses a fixed HTTP/2 flow-control window. It does not expose the adaptive-window option used by Triton's native test client. It also does not support Zstd stream compression.

**Delivery limits**

The backend Triton service closes a client session if that client cannot consume updates and its server-side queue fills. The SDK then uses its normal polling and reconnect path.

**Observe errors without causing false alarms**

Record read errors and `getLastTransportError()` in logs. Combine it with successful read rates, response times, and application health signals.

## Troubleshooting

**My result is `null`**

JSON-RPC confirmed that the account was missing at the returned context slot. Check the public key and selected commitment. A timeout or network failure is not returned as `null`.

**My read timed out**

The stream and JSON-RPC did not provide a suitable observation within `missTimeoutMs`. Check the endpoint, token, account-sync access, RPC health, and `minContextSlot`. Increase the timeout only when the expected operation can reasonably need more time.

**The browser says gRPC is unsupported**

Use `AccountSyncTransports.WS`. gRPC works only in Node.js.

**The WebSocket does not connect**

Check that:

* the endpoint starts with `http`, `https`, `ws`, or `wss` as appropriate;
* the token is present when required;
* the provider exposes the account-sync WebSocket service;
* a proxy allows WebSocket upgrades; and
* browser origin rules allow the request.

Call `getLastTransportError()` for the latest saved background error. The SDK may still be serving reads through JSON-RPC polling.

**`getLastTransportError()` still returns an error after recovery**

This is expected. The SDK keeps the latest background error and does not clear it after recovery.

**My Node.js process does not exit**

Call and await `connection.close()`. A connection owns background resources that can keep work active.

**My minimum slot has not been reached**

The RPC node may return code `-32016`, or the SDK may time out while waiting. Use a reachable slot, a suitable commitment, and enough `missTimeoutMs` for the cluster to progress. Remember that cached stream state uses the account update slot.

**Removing an account did not keep it removed**

A later read can create a new temporary subscription. If you want the account to remain absent from the tracked set, do not read it through this connection, or set `autoSubscribeOnMiss: false` for default-commitment reads.

## Advanced account encoding

Most users can use `getAccountInfo` and `getParsedAccountInfo` and skip this section. These helpers are useful when you already have raw account values and need Solana RPC-style encoding.

Encoding runs in a bundled WebAssembly (WASM) module that works in Node.js and browsers. The module loads when an encoding helper first needs it. All callers share the same loading operation.

The encoding helpers do not open network connections or fetch supporting accounts.

### Supported encodings

| Encoding      | Output                                                                       |
| ------------- | ---------------------------------------------------------------------------- |
| `binary`      | A base58 string. This is the legacy encoding name.                           |
| `base58`      | A `[data, "base58"]` tuple.                                                  |
| `base64`      | A `[data, "base64"]` tuple.                                                  |
| `base64+zstd` | A tuple containing base64-encoded, zstd-compressed data and `"base64+zstd"`. |
| `jsonParsed`  | A `ParsedAccountData` object, or an error if parsing fails.                  |

### Convert raw account data

```ts
import { convertAccountData } from "@triton-one/triton-sdk";

const base58Data = await convertAccountData(
  base64Data,
  "base64",
  "base58",
);
```

`convertAccountData` converts between raw encodings. It does not accept `jsonParsed` as either the source or destination encoding.

### Encode a web3.js account

```ts
import {
  accountInfoToEncodingInput,
  encodeAccount,
} from "@triton-one/triton-sdk";

const accountInfo = await connection.getAccountInfo(address);

if (accountInfo) {
  const encoded = await encodeAccount(
    accountInfoToEncodingInput(address, accountInfo),
    "base64",
  );

  console.log(encoded.data);
}
```

`encodeAccount` returns a JSON-compatible `UiAccount`. Its fields are `lamports`, `data`, `owner`, `executable`, `rentEpoch`, and optional `space`.

An `AccountEncodingInput` accepts:

* `pubkey`: a string or `PublicKey`.
* `owner`: a string or `PublicKey`.
* `lamports`: an unsigned 64-bit value as a number, bigint, or decimal string.
* `executable`: a boolean.
* `rentEpoch`: an unsigned 64-bit value as a number, bigint, or decimal string.
* `data`: a `Uint8Array`, `Buffer`, or base64 string.

Unsafe numeric inputs, negative values, values outside the unsigned 64-bit range, invalid addresses, and invalid base64 strings are rejected.

For raw encodings, the optional `dataSlice` selects a byte range before encoding. The `space` field still reports the full account data size. Slicing does not apply to `jsonParsed`.

### Supply parse context

Some account parsers need supporting data. For example, parsing an SPL token account requires its mint account data.

Load that data before calling the helper and supply it through `parseContext`:

```ts
import { parseJsonParsed } from "@triton-one/triton-sdk";

const mintAccount = await connection.getAccountInfo(mintAddress);

if (!mintAccount) {
  throw new Error("Mint account does not exist");
}

const parsed = await parseJsonParsed(tokenAccountInput, {
  parseContext: {
    splTokenMint: {
      pubkey: mintAddress,
      data: mintAccount.data,
    },
    unixTimestamp: Math.floor(Date.now() / 1000),
  },
});
```

`parseJsonParsed` parses once with the supplied context. It returns `ParsedAccountData` or rejects with an error. It does not fetch missing data, retry parsing, or return raw data after a failure.

`encodeAccount(input, "jsonParsed", options)` accepts the same parse context and follows the same rules. It returns a full `UiAccount` containing the parsed data.

`unixTimestamp` supplies the time for parsers whose output depends on it. A top-level `options.unixTimestamp` overrides `parseContext.unixTimestamp`.

Account-sync connection methods handle supporting reads separately. They can load a missing mint and retry parsing once. A parse or context fetch failure rejects the read. For `getMultipleParsedAccounts`, one failure rejects the entire call; missing accounts return `null`.

Without `accountSync`, `Connection` uses the upstream web3.js behavior.

### Encoding error classes

| Error                      | Meaning                                                                                                                 |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `UnsupportedEncodingError` | The requested encoding or conversion is unsupported.                                                                    |
| `InvalidAccountDataError`  | An account field, encoded value, or option is invalid.                                                                  |
| `MissingParseContextError` | Parsing requires supporting data that was not supplied.                                                                 |
| `WasmParserError`          | The account parser failed, including for unsupported programs or malformed account data, or WASM returned invalid JSON. |
| `ContextFetchError`        | An account-sync connection could not load supporting parse data.                                                        |

These errors can include the account address, owner, encoding, missing account, context kind, or original cause when that information is available.

### Lower-level helpers

* `loadAccountEncodingWasm()` loads and returns the bundled encoder.
* `bufferedAccountToEncodingInput()` converts an internal buffered state. Most application code will not have this internal type.
* `accountInfoToEncodingInput()` converts a raw web3.js account result.
* `toWeb3JsParsedAccountInfo()` converts a JSON-compatible parsed `UiAccount` into the web3.js account shape. It rejects raw encoded data.

## web3.js compatibility

The SDK `Connection` extends the web3.js `Connection` class.

These methods are replaced with account-sync-backed versions:

* `getAccountInfo`
* `getAccountInfoAndContext`
* `getParsedAccountInfo`
* `getMultipleAccountsInfo`
* `getMultipleAccountsInfoAndContext`
* `getMultipleParsedAccounts`

Other inherited methods, such as `getSlot`, continue to use JSON-RPC normally.

This often makes migration small:

```ts
// Before

import { Connection, PublicKey } from "@solana/web3.js";

// After

import { Connection, PublicKey } from "@triton-one/triton-sdk";
```

Then add `accountSync` configuration and call `close()` when you are finished.

The six method signatures and normal account result shapes match web3.js. The behaviour differs in important ways described in this guide: local buffering, temporary subscriptions, fallback polling, and commitment-specific streams.

## Public API summary

**Main values and classes**

* `Connection`: web3.js-compatible connection with buffered account reads.
* `AccountSyncTransports`: contains `WS` and `GRPC` transport choices.
* `PublicKey` and other web3.js exports: re-exported from `@solana/web3.js`.
* `AccountSyncReadTimeoutError`: typed timeout from context and multiple-account reads.

**Connection methods added or changed**

```ts
new Connection(endpoint, commitmentOrConfig?)

connection.getAccountInfo(publicKey, commitmentOrConfig?)

connection.getAccountInfoAndContext(publicKey, commitmentOrConfig?)

connection.getParsedAccountInfo(publicKey, commitmentOrConfig?)

connection.getMultipleAccountsInfo(publicKeys, commitmentOrConfig?)

connection.getMultipleAccountsInfoAndContext(publicKeys, commitmentOrConfig?)

connection.getMultipleParsedAccounts(publicKeys, config?)

connection.addAccounts(accountIds, commitment?)

connection.removeAccounts(accountIds, commitment?)

connection.setAccounts(accountIds, commitment?)

connection.getLastTransportError()

connection.close()
```

**Encoding values and classes**

* `AccountParseContextCache`
* `ContextFetchError`
* `InvalidAccountDataError`
* `MissingParseContextError`
* `UnsupportedEncodingError`
* `WasmParserError`
* `accountInfoToEncodingInput`
* `bufferedAccountToEncodingInput`
* `convertAccountData`
* `encodeAccount`
* `isBase64ZstdEncodingSupported`
* `loadAccountEncodingWasm`
* `parseJsonParsed`
* `toWeb3JsParsedAccountInfo`

**Exported account-sync types**

Both builds export:

* `AccountSyncCommitment`
* `AccountSyncOptions`
* `AccountSyncReadTimeoutErrorOptions`

The Node.js build also exports:

* `GrpcTransportOptions`
* `NodeAccountSyncConnectionConfig`
* `NodeSubscriptionTransport`

The browser build instead exports:

* `BrowserAccountSyncConnectionConfig`
* `BrowserSubscriptionTransport`

**Exported encoding types**

* `AccountDataEncoding`
* `AccountEncodingInput`
* `AccountEncodingOptions`
* `AccountParseContext`
* `AccountParseContextAccount`
* `AccountParseContextCacheOptions`
* `AccountParseContextFetcher`
* `AccountParseContextMint`
* `ConvertAccountDataOptions`
* `EncodedAccountData`
* `UiAccount`


# Payments

Here is a step-by-step guide on how to sign up and process payments on Hel.io via the Triton One customer site:

1. Open your preferred web browser and go to <https://customers.triton.one> and login to your Triton One account.

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-1c62abe9087717619e9a419fd574b7e2fc4edd27%2FScreenshot%202023-03-21%20at%2010.28.47%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

2. You will be prompted that your RPC account is still inactive. To activate the account, make a payment via Hel.io web3 payment. You can opt to click on the account name (follow step #3) or click on "here" or the banner on the link (directly proceeds to step #4).

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-da66edd101a3bfb5aaeb9b9b65ea1ce9071d4371%2FQuel%20est%20le%20mail%20du%20service%20client%20.png?alt=media" alt=""><figcaption></figcaption></figure>

3. You will be routed to the account dashboard, which shows the account is still inactive. Click on the "Inactive Pay Stream" banner.

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-b663a5a5c5a3bfcf67210053e16adb48fd352e2e%2FScreenshot%202023-03-21%20at%2010.30.14%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

4. You see the Hel.io Pay Stream page. Click on "Connect Wallet”.

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-849951eea66cb7224a0373ec4c632c5e95e38d69%2FScreenshot%202023-03-21%20at%2010.31.16%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

5. Select "Web" or "Extension" if prompted.

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-cc836b3c1b01ee839f0f590529f76549a9dfdabd%2FScreenshot%202023-03-21%20at%2010.30.35%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

6. Tap on "Connect".

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-df5ef023e091065705f152c8635c7f27ceca4324%2FScreenshot%202023-03-21%20at%2010.30.57%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

7. Click "Approve".

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-e4bfe1f8f5c0b68d866a43623b6c13813a0e1175%2FScreenshot%202023-03-21%20at%2010.32.04%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

8. Check the settings for duration and price. If all entries are correct click "Pay".

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-e798d75f9e65f8b46275e9ae69a4129b3e5298ce%2FScreenshot%202023-03-23%20at%208.51.41%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

9. The page will show "Approving transaction".

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-b6a832b615add767a0729a6141ab7e5f36d38a38%2FScreenshot%202023-03-21%20at%2010.33.29%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

10. &#x20;Payment is done! To stop the pay stream, click on "Stop Pay Stream".

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-e5399e931976c065e83b7ed59fc5a2f74e64b8c7%2FScreenshot%202023-03-21%20at%2010.37.19%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

11. Click "Approve".

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-3961c8018759d379eebdbae098b5ae01fbf51ca7%2FScreenshot%202023-03-21%20at%2010.37.56%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>

12. Payment confirmation is sent via email.

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-c677d937905a9954342b8d9a85d47447a05da2b4%2FImage%203-24-23%20at%207.05%20PM.jpeg?alt=media" alt=""><figcaption></figcaption></figure>

13. Navigate back to <https://customers.triton.one>. You may need to refresh the page to display the activated RPC account.

<figure><img src="https://842859422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNcCPhJxywRiDuyEqAAsL%2Fuploads%2Fgit-blob-33ed863a9dad089f372f8402dcde602fbe80e7fb%2FScreenshot%202023-03-24%20at%2012.40.38%20PM.png?alt=media" alt=""><figcaption></figcaption></figure>


# Account management API

We provide an account management REST API which can allow you to automatically manage your Triton subscriptions and services.&#x20;

To get access to the account management API, send an email to <help@triton.one> or contact us over any of your dedicated support channels.&#x20;


# Introduction

First, make sure you have an account and request account management API access from us before making account management API requests. You can contact us to create an account for you, or if you already have one, you can sign into the UI [here](https://customers.triton.one). Once logged in, if you requested Operator or Reseller access, you'll be able to create API tokens that you can use to make scoped, authenticated requests for your users and accounts.


# Auth & Headers

### **Auth**

Pass your token in through the Authorization header to make scoped, authenticated requests against the account management API. You can use the `GET /api/v1/accounts` endpoint to test your token. If you get a 200 status code response, your token is being passed correctly. This token is only used for the account management API (`customers.rpcpool.com/api`). You'll create separate tokens for consuming the RPC API.

Examples in this account management API guide are demonstrated using curl, but you should use an HTTP library of your choice.

```
$ curl -H "Authorization: secret" -H "Content-Type: application/json" -H "Accept:application/json" https://customers.triton.one/api/v1/accounts         
```

### **Headers**

All endpoints accept and return JSON, and you should pass both an Accept and Content-Type header of `application/json`


# Accounts

### **Overview**

Accounts are only used for billing. You cannot create new accounts yourself. Instead, you'll need to ask an administrator to do this for you, if you'd like to have separate billing for your subscriptions.

### List Accounts

<mark style="color:blue;">`GET /api/v1/accounts`</mark>

Returns all accounts that the current authentication token has access to.

**Parameters**

All parameters are optional.

<table><thead><tr><th width="220">Name</th><th width="216.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>name</td><td><code>string</code></td><td>Will filter the results by account name, there is nothing by default</td></tr><tr><td>per</td><td><code>integer</code></td><td>Will limit the results to NN entries per page, defaults to 50</td></tr><tr><td>page</td><td><code>integer</code></td><td>Will return the results from passed page, defaults to 1</td></tr></tbody></table>

**Request**

Example requests with page filtering, number of accounts per page and filtering by name.

```url
curl 'https://customers.triton.one/api/v1/accounts' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json"
```

```url
curl 'https://customers.triton.one/api/v1/accounts?name=Account-123' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

```
curl 'https://customers.triton.one/api/v1/accounts?per=10&page=2' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

#### Response

Returns a hash with the `accounts` key containing an array of account objects and the `meta`  containing pagination data.

```json
{
   "accounts": [
      { account_object_1 },
      { account_object_2 },
      { account_object_3 },
      ...
   ],
   "meta": {
      "current_page": 1,
      "next_page": null,
      "per_page": 50,
      "prev_page": null,
      "total_pages": 1,
      "total_count": 3
   }
}
```

### Get Account

<mark style="color:blue;">`GET /api/v1/accounts/:account-uuid`</mark>

Get Account that the current authentication token has access to.

**Parameters**

`account-uuid` is the only parameter that needs to be provided.

**Request**

Example request containing `account-uuid`

```url
curl 'https://customers.triton.one/api/v1/accounts/c92a9cea-47cc-494b-b1b0-4230a2316ee5' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

#### Response

Returns a hash with the `account` key containing an account object. The response data for the account object is the same as for <mark style="color:blue;">`GET /api/v1/accounts`</mark>. Below is the sample response.

```json
{
   "account": {
      "uuid": "c92a9cea-47cc-494b-b1b0-4230a2316ee5",
      "name": "John Doe",
      "billing_address1": "1645 S Telegraph Rd",
      "billing_address2": null,
      "billing_address3": null,
      "billing_city": "Bloomfield Hills",
      "billing_state_or_province": "Michigan",
      "billing_postal_code": "48302",
      "billing_country_code": "US",
      "telephone_country_code": "US",
      "telephone": "(248) 858-2300",
      "last_time_activated":"28/06/2023",
      "is_active": true,
      "created_at": "2023-06-22T09:11:11Z",
      "updated_at": "2023-06-28T11:26:52Z",
      "users_count": 1,
      "tokens_count": 6,
      "endpoints_count": 4,
      "subscriptions_count": 2,
      "is_helio_pay_stream_active": true,
      "pay_stream_id": "639cbc90a989eacb2574a055",
      "metrics_enabled": false,
      "payment_method": "manual",
      "maximum_tokens": -1,
      "address_watch_lists": false,
      "allow_ip_ranges": false,
      "subscriptions": [
         {
            "uuid": "74ea9d9a-4b2a-4f01-af47-1c175f8a2af6",
            "name": "Mainnet Shared Subscription",
            "account_uuid": "c92a9cea-47cc-494b-b1b0-4230a2316ee5",
            "subscription_type": "mainnet-shared",
            "starts_at": null,
            "ends_at": null,
            "is_active": true,
            "endpoints_count": 2,
            "tokens_count": 4
            "supported_rate_tiers": [
               {
                  "uuid": "ca141949-a46f-446a-abee-a031e501fea0",
                  "name": "developer",
                  "active_tokens_count": 5,
                  "active_endpoints_count": 4,
                  "supported_subscription_type_uuids": [
                     "413defdd-b011-4dda-8145-fa782524284c"
                  ]
               }
            ]
         }
      ],
      "deactivation_reasons": [
         {
            "description": "Deactivation reason description",
            "created_at": "2023-06-26T20:16:05Z"
         }
      ]
   }
}
```

### **Create Account**

<mark style="color:red;">This API is only limited to account management API tokens created with the admin role.</mark>

### **Update Account**

<mark style="color:blue;">`PUT /api/v1/accounts/:account-uuid`</mark>

Update account that the current authentication token has access to.

**Parameters**

All parameters are optional unless marked as required.

<table><thead><tr><th width="311">Name</th><th width="155.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>name <mark style="color:red;">*required</mark></td><td><code>string</code></td><td>A human readable display name to describe the subscription.</td></tr><tr><td>billing_address1</td><td><code>string</code></td><td></td></tr><tr><td>billing_address2</td><td><code>string</code></td><td></td></tr><tr><td>billing_address3</td><td><code>string</code></td><td></td></tr><tr><td>billing_city</td><td><code>string</code></td><td></td></tr><tr><td>billing_state_or_province</td><td><code>string</code></td><td></td></tr><tr><td>billing_postal_code</td><td><code>string</code></td><td></td></tr><tr><td>billing_country_code</td><td><code>string</code></td><td></td></tr><tr><td>telephone_country_code</td><td><code>string</code></td><td></td></tr><tr><td><p>telephone</p><p></p></td><td><code>string</code></td><td></td></tr></tbody></table>

**Request**

Request JSON (`-d` command line parameter) must contain `account` key for account object. All parameters should be placed inside.

```url
curl -X PUT 'https://customers.triton.one/api/v1/accounts/c6b8c375-27ab-4531-94f4-d8d08250fcfa' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" -d '
{
    "account": {
        "name": "John Doe",
        "billing_address1": "1645 S Telegraph Rd",
        "billing_address2": "line 2",
        "billing_address3": "line 3",
        "billing_city": "Bloomfield Hills",
        "billing_state_or_province": "Michigan",
        "billing_postal_code": "48302",
        "billing_country_code": "US",
        "telephone_country_code": "US",
        "telephone": "(248) 858-2300"
    }
}'
```

**Response**

Returns a hash with the `account` key containing an account object. The response data for account object is the same as for <mark style="color:blue;">`GET /api/v1/accounts/:account-uuid`</mark> nad <mark style="color:blue;">`GET /api/v1/accounts`</mark>

```json
{
   "account": {
      "uuid": "c92a9cea-47cc-494b-b1b0-4230a2316ee5",
      "name": "John Doe",
      "billing_address1": "1645 S Telegraph Rd",
      "billing_address2": null,
      "billing_address3": null,
      "billing_city": "Bloomfield Hills",
      "billing_state_or_province": "Michigan",
      "billing_postal_code": "48302",
      "billing_country_code": "US",
      "telephone_country_code": "US",
      "telephone": "(248) 858-2300",
      ...
      "subscriptions": [
         { subscription_object_1 },
         { subscription_object_2 },
         { subscription_object_3 },
         ...
      ],
      "deactivation_reasons": [
         { deactivation_reason_object_1 },
         { deactivation_reason_object_2 },
         { deactivation_reason_object_3 },
         ...
      ]
   }
}
```


# Address Watch Lists

<mark style="color:red;">This feature is only limited to account management API tokens created with the operator or reseller role.</mark>

### Add watch list

<mark style="color:blue;">`POST /api/v1/subscriptions/:subscription-uuid/address_watch_lists`</mark>

**Parameters**

<table><thead><tr><th width="263.3333333333333">Name</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>variant</code> <mark style="color:red;">*required</mark></td><td><code>string</code></td><td>Must be either <code>collection</code> or <code>tree</code></td></tr><tr><td><code>encoded_value</code> <mark style="color:red;">*required</mark></td><td><code>string</code></td><td></td></tr></tbody></table>

### List watch lists

<mark style="color:blue;">`GET /api/v1/address_watch_lists`</mark>

**Query parameters**

| Name                | Type     | Description |
| ------------------- | -------- | ----------- |
| `variant`           | `string` |             |
| `subscription_uuid` | `string` |             |
| `subscription_type` | `string` |             |

### Delete watch list

<mark style="color:blue;">`DELETE /api/v1/address_watch_lists/:uuid`</mark>


# Subscriptions

### **Overview**

A Subscription is the primary resource, and it encapsulates all things related to the endpoints and tokens you define. This also includes a name, a start and end timestamp, and other metadata. This is one of the first resources that you should create to get started using Triton and making RPC requests. You'll need to have an account and obtain an  `account_uuid` to create a subscription.

### **List Subscriptions**

<mark style="color:blue;">`GET /api/v1/subscriptions`</mark>

Returns subscriptions that the current authentication token has access to.&#x20;

**Parameters**

All parameters are optional.

| Name               | Type      | Description                                                   |
| ------------------ | --------- | ------------------------------------------------------------- |
| subscription\_type | `string`  | Will filter the results by subscription\_type.                |
| account\_uuid      | `string`  | Will filter the results by account\_uuid.                     |
| user\_uuid         | `string`  | Will filter the results by user\_uuid.                        |
| per                | `integer` | Will limit the results to NN entries per page, defaults to 50 |
| page               | `integer` | Will return the results from passed page, defaults to 1       |

**Request**

Example requests with page filtering, number of subscriptions per page and filtering by subscription type, account uuid or user uuid.

```url
curl 'https://customers.triton.one/api/v1/subscriptions' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json"
```

```url
curl 'https://customers.triton.one/api/v1/subscriptions?subscription_type=mainnet-shared' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

```
curl 'https://customers.triton.one/api/v1/subscriptions?account_uuid=c92a9cea-47cc-494b-b1b0-4230a2316ee5' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

```
curl 'https://customers.triton.one/api/v1/subscriptions?per=10&page=2' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

#### Response

Returns a hash with the `subscriptions` key containing an array of subscriptions objects and the `meta` containing pagination data.

<pre class="language-json"><code class="lang-json">{
   "subscriptions": [
      { subscription_object_1 },
      { subscription_object_2 },
      { subscription_object_3 },
      ...
   ],
<strong>   "meta": {
</strong>      "current_page": 1,
      "next_page": null,
      "per_page": 50,
      "prev_page": null,
      "total_pages": 1,
      "total_count": 3
   }
}
</code></pre>

### Get Subscription

<mark style="color:blue;">`GET /api/v1/subscriptions/:subscription-uuid`</mark>

Get Subscription that the current authentication token has access to.

**Parameters**

`subscription-uuid` is the only parameter that needs to be provided.

**Request**

Example request containing `subscription-uuid`

```
curl 'https://customers.triton.one/api/v1/subscriptions/c92a9cea-47cc-494b-b1b0-4230a2316ee5' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

**Response**

Returns a hash with the `subscription` key containing a subscription object. The response data for subscription object is the same as for <mark style="color:blue;">`GET /api/v1/subscriptions`</mark>. Below is the sample response.

| Name                   | Type                                       | Description                                                                     |
| ---------------------- | ------------------------------------------ | ------------------------------------------------------------------------------- |
| uuid                   | `string`                                   | The generated primary key to reference this subscription.                       |
| endpoints              | `array[{endpoint_object}, ...]`            | An array of Endpoint objects, documented in the Endpoints section.              |
| tokens                 | `array[{token_object}, ...]`               | An array of Token objects, documented in the Tokens section.                    |
| supported\_rate\_tiers | `array[{supported_rate_tier_object}, ...]` | An array of Supported Rate Tiers objects, documented in the Rate Tiers section. |
| deactivation\_reasons  | `array[{deactivation_reason_object}, ...]` | An array of Deactivation Reasons objects.                                       |

```json
{
   "subscription": {
      "uuid": "5d8d4591-12cc-424a-8f39-32de0ba6acc3",
      "name": "Example App",
      "account_uuid": "4ad26b8a-a9a1-4f9b-b0fa-c13a682d64b1",
      "subscription_type": "mainnet-shared",
      "starts_at": null,
      "ends_at": null,
      "is_active": false,
      "account": { // An account that is assigned to a given subscription
         "uuid": "4ad26b8a-a9a1-4f9b-b0fa-c13a682d64b1",
         "name": "Tyler-qt65u8xzj2",
         "billing_address1": "420 Effertz Estates",
         "billing_address2": null,
         "billing_address3": null,
         "billing_city": "Padbergville",
         "billing_state_or_province": "Alabama",
         "billing_postal_code": "78007",
         "billing_country_code": "US",
         "telephone_country_code": "+61-8",
         "telephone": "870-800-5877",
         "last_time_activated": "02/05/2023",
         "is_active": true,
         "created_at": "2023-05-02T16:32:46Z",
         "updated_at": "2023-05-02T16:32:46Z",
         "users_count": 1,
         "tokens_count": 0,
         "endpoints_count": 1,
         "subscriptions_count": 2,
         "is_helio_pay_stream_active": false,
         "pay_stream_id": "639cbc90a989eacb2574a055",
         "metrics_enabled": false,
         "payment_method": "manual",
         "maximum_tokens": -1,
         "address_watch_lists": false,
         "allow_ip_ranges": false
      },
      "supported_rate_tiers": [ // A roll-up of related supported rate tiers
         {
            "uuid": "ca141949-a46f-446a-abee-a031e501fea0",
            "name": "developer",
            "active_tokens_count": 5,
            "active_endpoints_count": 4,
            "supported_subscription_type_uuids": [
               "413defdd-b011-4dda-8145-fa782524284c"
            ]
         }
      ],
      "endpoints": [ // A roll-up of related endpoints
         {
            "uuid": "23ab660b-cb09-4ca5-b49a-518b422a8d81",
            "name": "Jacobson LLC cbp4i3u71m",
            "value": null,
            "slug": "academy-strict-fnalfmab4l",
            "rate_tier": "developer",
            "is_active": true,
            "value_override": null,
            "values": [
               "academy-strict-fnalfmab4l.TEST 1",
               "academy-strict-fnalfmab4l.TWST 2"
            ],
            "default_values": [
               "academy-strict-fnalfmab4l.TEST 1",
               "academy-strict-fnalfmab4l.TWST 2"
            ],
            "allowed_origins": [
               {
                  "id": 1,
                  "uuid": "b4d5ec62-410d-45f8-8b45-44fe6641cf1f",
                  "value": "vonrueden-haag.co",
                  "endpoint_id": 1,
                  "created_at": "2023-05-02T16:32:46Z",
                  "updated_at": "2023-05-02T16:32:46Z",
                  "is_active": true
               }
            ]
         }
      ],
      "tokens": [ // A roll-up of tokens for related Subscription, Account, & Users
         {
            "uuid": "fa2fec9c-c3e6-4625-b11f-7906dff5f37c",
            "name": "Token name",
            "value": "8d43f8dc-8aa3-4fa2-b09e-361f6af8d972",
            "rate_tier": "tier1",
            "auth_username": "tylerqt-tylerwi-db8c",
            "is_active": false
         }
      ],
      "deactivation_reasons": [
         {
            "description": "Deactivation reason description",
            "created_at": "2023-07-03T12:00:22Z"
         }
      ]
   }
}
```

### **Create Subscription**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller role.</mark>

<mark style="color:blue;">`POST /api/v1/subscriptions`</mark>

**Parameters**

| Name                                                          | Type       | Description                                                                                                                          |
| ------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| account\_uuid <mark style="color:red;">\*required</mark>      | `string`   | Primary key of the account that the subscription should belong to.                                                                   |
| subscription\_type <mark style="color:red;">\*required</mark> | `string`   | Name of the subscription type. Obtained from \<link>Subscription Types                                                               |
| name <mark style="color:red;">\*required</mark>               | `string`   | A human readable display name to describe the subscription.                                                                          |
| starts\_at                                                    | `datetime` | The scheduled timestamp for the subscription to become active. Defaults to null, which means the subscription is immediately active. |
| ends\_at                                                      | `datetime` | The timestamp for which the subscription becomes inactive. Defaults to null, which means the subscription never becomes inactive.    |

**Request**

Request JSON (`-d` command line parameter) must contain `subscription` key for a subscription object. All parameters should be placed inside.

```url
curl -X POST 'https://customers.triton.one/api/v1/subscriptions' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept: application/json" -d '
{
    "subscription": {
        "account_uuid": "c92a9cea-47cc-494b-b1b0-4230a2316ee5", // obtain from UI
        "subscription_type": "mainnet-shared",
        "name": "Example App",
        "starts_at": "2023-07-10T10:25:30Z",
        "ends_at": "2023-07-30T05:00:00Z"
    }
}'
```

**Response**

Returns a hash with the `subscription` key containing a subscription object. Note that `endpoints`, `tokens` and `deactivation_reasons` arrays are empty because this is newly created subscription. Below is the sample response.&#x20;

```json
 {
   "subscription": {
      "uuid": "5d8d4591-12cc-424a-8f39-32de0ba6acc3",
      "name":"Example App",
      "account_uuid": "4ad26b8a-a9a1-4f9b-b0fa-c13a682d64b1",
      "subscription_type": "mainnet-shared",
      "starts_at": null,
      "ends_at": null,
      "is_active": false,
      "account": {
         "uuid": "4ad26b8a-a9a1-4f9b-b0fa-c13a682d64b1",
         "name": "Tyler-qt65u8xzj2",
         "billing_address1": "420 Effertz Estates",
         "billing_address2": null,
         "billing_address3": null,
         "billing_city": "Padbergville",
         "billing_state_or_province": "Alabama",
         "billing_postal_code": "78007",
         "billing_country_code": "US",
         "telephone_country_code": "+61-8",
         "telephone": "870-800-5877",
         "last_time_activated": "02/05/2023",
         "is_active": true,
         "created_at": "2023-05-02T16:32:46Z",
         "updated_at": "2023-05-02T16:32:46Z",
         "users_count": 1,
         "tokens_count": 0,
         "endpoints_count": 1,
         "subscriptions_count": 2,
         "is_helio_pay_stream_active": false,
         "pay_stream_id": "639cbc90a989eacb2574a055",
         "metrics_enabled": false,
         "payment_method": "manual",
         "maximum_tokens": -1,
         "address_watch_lists": false,
         "allow_ip_ranges": false
      },
      "supported_rate_tiers": [
         {
            "uuid": "ca141949-a46f-446a-abee-a031e501fea0",
            "name": "developer",
            "active_tokens_count": 5,
            "active_endpoints_count": 4,
            "supported_subscription_type_uuids": [
               "413defdd-b011-4dda-8145-fa782524284c"
            ]
         }
      ],
      "endpoints_count": 0,
      "tokens_count": 0,
      "endpoints": [],
      "tokens": [],
      "deactivation_reasons": []
   }
}
 
```

### **Update Subscription**

<mark style="color:blue;">`PUT /api/v1/subscriptions/:subscription_uuid`</mark>

Update attributes like `name` on an existing Subscription that the current authentication token has access to. Any other updatable attributes for a Subscription like Endpoints and Tokens should be updated via their distinct account management APIs.

**Parameters**

| Name | Type     | Description                                                 |
| ---- | -------- | ----------------------------------------------------------- |
| name | `string` | A human readable display name to describe the subscription. |

**Request**

Request JSON (`-d` command line parameter) must contain `subscription` key for a subscription object. All parameters should be placed inside.

```url
curl -X PUT 'https://customers.triton.one/api/v1/subscriptions/c92a9cea-47cc-494b-b1b0-4230a2316ee5' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept: application/json" -d '
{
    "subscription": {
        "name": "Example App"
    }
}'
```

**Response**

Returns a hash with the `subscription` key containing a subscription object. The response data for subscription object is the same as for <mark style="color:blue;">`GET /api/v1/subscriptions/:subscription-uuid`</mark> nad <mark style="color:blue;">`GET /api/v1/subscription`</mark>

```json
{
   "subscription": {
      "name": "Example App" // subscription attributes are the same as for GET and POST 
      ...
      "supported_rate_tiers": [ // A roll-up of related supported rate tiers
         { supported_rate_tier_object_1 },
         { supported_rate_tier_object_2 },
         { supported_rate_tier_object_3 },
         ...
      ],
      "endpoints": [ // A roll-up of related endpoints
         { endpoint_object_1 },
         { endpoint_object_2 },
         { endpoint_object_3 },
         ...
      ],
      "tokens": [ // A roll-up of tokens for related Subscription, Account, & Users
         { token_object_1 },
         { token_object_2 },
         { token_object_3 },
         ...
      ],
      "deactivation_reasons": [
         { deactivation_reason_object_1 },
         { deactivation_reason_object_2 },
         { deactivation_reason_object_3 },
         ...
      ]
   }
}
```

### **Activate Subscription**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller role.</mark>

<mark style="color:blue;">`PUT /api/v1/subscriptions/:subscription_uuid/activate`</mark>

Activate a subscription so that it can be used.

**Parameters**

<mark style="color:blue;">`subscription_uuid`</mark> is the only parameter that needs to be provided.

**Request**

```url
curl -X PUT 'https://customers.triton.one/api/v1/subscriptions/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/activate' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json"
```

**Response**

```json
204 No Content
```

### **Deactivate Subscription**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller role.</mark>

<mark style="color:blue;">`PUT /api/v1/subscriptions/:subscription_uuid/deactivate`</mark>

Deactivate a subscription so that it cannot be used.

**Parameters**

All parameters are optional.

| Name                 | Type     | Description                                                       |
| -------------------- | -------- | ----------------------------------------------------------------- |
| deactivation\_reason | `string` | A human readable description to describe the deactivation reason. |

**Request**

Example request with `deactivation_reason`.

```url
curl -X PUT 'https://customers.triton.one/api/v1/subscriptions/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/deactivate' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" -d '
{
    "deactivation_reason": "Deactivation reason description"
}' 
```

**Response**

```json
204 No Content
```


# Subscription Types

### **Overview**

A Subscription Type defines what kind of pool a subscription is for. This is how endpoints are distinguished between a dedicated pool and a shared pool. It also can determine which Solana RPC environment the subscription and endpoints target.

The developer subscription is used to encapsulate the devnet and testnet Solana environments. Any endpoints or tokens created under a developer Subscription will automatically be usable for both Solana environments, typically at no cost.

### **List Subscription Types**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller role.</mark>

<mark style="color:blue;">`GET /api/v1/subscription_types`</mark>

Returns all subscription types that the current authentication token has access to.

**Parameters**

Currently, there are no parameters that would be used.

**Request**

<pre><code><strong>curl 'https://customers.triton.one/api/v1/subscription_types' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
</strong></code></pre>

**Response**

Returns all subscription types that a Subscription can get created with.

```json
{
   "subscription_types": [
      {
         "uuid": "413defdd-b011-4dda-8145-fa782524284c",
         "name": "developer",
         "pool_name": "test pool name",
         "subscriptions_count": 10,
         "endpoint_default_suffixes": [
            "TEST 1",
            "TWST 2"
         ]
      },
      {
         "uuid": "8ddbdbc8-b513-4ba5-a833-2ce8402a9c50",
         "name": "mainnet-shared",
         "pool_name": "pool name",
         "subscriptions_count": 10,
         "endpoint_default_suffixes": [
            "endpoint suffix"
         ]
      },
      {
         "uuid": "b97a3264-2c0a-4af5-85ff-14dce864afc9",
         "name": "mainnet-dedicated",
         "pool_name": "pool name",
         "subscriptions_count": 5,
         "endpoint_default_suffixes": [
            "endpoint suffix"
         ]
      }
   ]
}
```

### **Create Subscription Type / Update Subscription Type**

<mark style="color:red;">This API is only limited to account management API tokens created with the admin role.</mark>


# Endpoints

### **Overview**

An Endpoint is what clients will call to consume the RPC. Endpoints belong to a Subscription, which holds its metadata. An endpoint's name is assigned to you when it's created and is randomly assigned.

### List Endpoints

<mark style="color:blue;">`GET /api/v1/endpoints`</mark>

Returns all endpoints to which the current authentication token has access.

**Parameters**

<table><thead><tr><th width="220">Name</th><th width="216.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>account_uuid  <mark style="color:red;">*</mark></td><td><code>string</code></td><td>Will filter the results by account_uuid.</td></tr><tr><td>subscription_uuid <mark style="color:red;">*</mark></td><td><code>string</code></td><td>Will limit the results by subscription_uuid.</td></tr></tbody></table>

<mark style="color:red;">\* Either a</mark> <mark style="color:red;"></mark><mark style="color:red;">`subscription_uuid`</mark> <mark style="color:red;"></mark><mark style="color:red;">or</mark> <mark style="color:red;"></mark><mark style="color:red;">`account_uuid`</mark> <mark style="color:red;"></mark><mark style="color:red;">parameter is required</mark>

**Request**

Example requests with account\_uuid and subscription\_uuid filtering.

```url
curl 'https://customers.triton.one/api/v1/endpoints?account_uuid=c92a9cea-47cc-494b-b1b0-4230a2316ee5' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

```
curl 'https://customers.triton.one/api/v1/endpoints?subscription_uuid=74ea9d9a-4b2a-4f01-af47-1c175f8a2af6' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

#### Response

Returns a hash with the `endpoints` key containing an array of endpoints objects.

```json
{
   "endpoints": [
      {
         "uuid": "23ab660b-cb09-4ca5-b49a-518b422a8d81",
         "name": "Jacobson LLC cbp4i3u71m",
         "value": null,
         "slug": "academy-strict-fnalfmab4l",
         "rate_tier": "developer",
         "is_active": true,
         "value_override": null,
         "values": [
            "academy-strict-fnalfmab4l.TEST 1",
            "academy-strict-fnalfmab4l.TWST 2",
            "academy-strict-fnalfmab4l.value-3"
         ],
         "default_values": [
            "academy-strict-fnalfmab4l.TEST 1",
            "academy-strict-fnalfmab4l.TWST 2",
            "academy-strict-fnalfmab4l.value-3"
         ],
         "allowed_origins": [
            {
               "uuid": "b4d5ec62-410d-45f8-8b45-44fe6641cf1f",
               "value": "vonrueden-haag.co",
               "is_active": true
            }
         ],
         "deactivation_reasons": [
            {
               "description": "Deactivation reason description",
               "created_at": "2023-06-26T20:16:05Z"
            }
         ]
      },
      { endpoint_object_2 },
      { endpoint_object_3 },
      ...
   ]
}
```

### **Get Endpoint**

<mark style="color:red;">This API is not available.</mark>

### **Create Endpoint**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller role.</mark>

<mark style="color:blue;">`POST /api/v1/subscriptions/:subscription_uuid/endpoints`</mark>

**Parameters**

| Name                                                  | Type                                                | Description                                                                                                                                                |
| ----------------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name <mark style="color:red;">\*required</mark>       | `string`                                            | A human-readable name to describe the endpoint.                                                                                                            |
| rate\_tier <mark style="color:red;">\*required</mark> | `string`                                            | The name of the Rate Tier for this endpoint, which defines rate limits on the Endpoint. Each endpoint incurs costs based on the Rate Tier defined for it.  |
| allowed\_origins                                      | `array[{ value: dns_cname }, { value: dns_cname }]` | An array of DNS CNAME records that define origins that can access the endpoint. This is commonly used by frontends that can't obscure an API access token. |

**Request**

Example requests with account\_uuid and subscription\_uuid filtering. All new allowed origins are automatically enabled.

```url
curl -X POST 'https://customers.triton.one/api/v1/subscriptions/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/endpoints' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" -d '
{
    "endpoint": {
        "name": "Endpoint 123",
        "rate_tier": "developer",
        "allowed_origins":  [{ value: "example.com" }, { value: "frontend.website.com" }]
    }
}' 
```

**Response**

All submitted keys, plus the following are returned:

| Name   | Type                             | Description                                                              |
| ------ | -------------------------------- | ------------------------------------------------------------------------ |
| uuid   | `string`                         | The generated primary key to reference this endpoint.                    |
| values | `array[string (DNS CNAME), ...]` | The endpoint values that should be called by clients to consume the RPC. |

```json
{
   "endpoint": {
      "uuid": "a5131884-1ce4-4415-98f8-d040901ecbd0",
      "name": "Endpoint 123",
      "slug": "tylerqt-example-3c6e",
      "rate_tier": "developer",
      "is_active": false,
      "value_override": null,
      "values": [
         "tylerqt-example-3c6e.TEST 1",
         "tylerqt-example-3c6e.TWST 2",
         "tylerqt-example-3c6e.value-3"
      ],
      "default_values": [
         "tylerqt-example-3c6e.TEST 1",
         "tylerqt-example-3c6e.TWST 2",
         "tylerqt-example-3c6e.value-3"
      ],
      "allowed_origins": [
         {
            "uuid": "4d63807d-84f2-491c-898e-9190fa334592",
            "value": "example.com",
            "is_active": true
         },
         {
            "uuid": "a21d362b-f488-4778-8e5c-d3c917efd764",
            "value": "frontend.website.com",
            "is_active": true
         }
      ],
      "deactivation_reasons": []
   }
}    
```

### **Update Endpoint**

<mark style="color:blue;">`PUT /api/v1/endpoints/:endpoint_uuid`</mark>

Update an endpoint. Only allowed\_origins can be updated. Accepts updates as full-updates, where the passed in list overwrites any existing allowed\_origins.

**Parameters**

| Name                                                                                                    | Type                                                                                        | Description                                                                                                                                                               |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name                                                                                                    | `string`                                                                                    | A human-readable name to describe the endpoint.                                                                                                                           |
| rate\_tier <mark style="color:red;">\*limited to REST API tokens created with the reseller role.</mark> | `string`                                                                                    | A Rate Tier name. It's assigned to an Endpoint in order to determine how much the endpoint costs                                                                          |
| allowed\_origins <mark style="color:red;">\*limited to one allowed origin for standard role.</mark>     | `array[{ value: dns_cname, is_active: boolean }, { value: dns_cname, is_active: boolean }]` | An array of DNS CNAME and is\_active records that define origins that can access the endpoint. This is commonly used by frontends that can't obscure an API access token. |

**Request**

Example requests with account\_uuid and subscription\_uuid filtering.

```url
curl -X PUT 'https://customers.triton.one/api/v1/endpoints/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" -d '
{
    "endpoint": {
        "name": "Endpoint 123",
        "rate_tier": "developer",
        "allowed_origins": [{ value: "example.com", is_active: true }, { value: "frontend.website.com", is_active: true }]
    }
}' 
```

**Response**

All submitted keys, plus the following are returned:

| Name   | Type                             | Description                                                              |
| ------ | -------------------------------- | ------------------------------------------------------------------------ |
| uuid   | `string`                         | The generated primary key to reference this endpoint.                    |
| values | `array[string (DNS CNAME), ...]` | The endpoint values that should be called by clients to consume the RPC. |

```json
{
   "endpoint": {
      "uuid": "6ec86e74-783b-4779-b626-937a5c1e2a89",
      "name": "New Endpoint Name",
      "value": null,
      "slug": "tylerqt-example-f988",
      "rate_tier": "developer",
      "is_active": false,
      "value_override": null,
      "values": [
         "tylerqt-example-f988.TEST 1",
         "tylerqt-example-f988.TWST 2",
         "tylerqt-example-f988.value-3"
      ],
      "default_values": [
         "tylerqt-example-f988.TEST 1",
         "tylerqt-example-f988.TWST 2",
         "tylerqt-example-f988.value-3"
      ],
      "allowed_origins": [
         {
            "uuid": "32d511f0-3fb0-4570-8922-f8bc988b41a1",
            "value": "example.com",
            "is_active": true
         }
      ],
      "deactivation_reasons": [
          {
            "description": "Deactivation reason description",
            "created_at": "2023-07-03T12:00:22Z"
         }
      ]
   }
}
```

### **Activate Endpoint**

<mark style="color:red;">This API is only limited to accoutn management API tokens created with the reseller role.</mark>

<mark style="color:blue;">`PUT /api/v1/endpoints/:endpoint_uuid/activate`</mark>

Activate an endpoint so that it can be used.

**Parameters**

<mark style="color:blue;">`endpoint_uuid`</mark> is the only parameter that needs to be provided.

**Request**

```url
curl -X PUT 'https://customers.triton.one/api/v1/endpoints/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/activate' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json"
```

**Response**

```json
204 No Content
```

### **Deactivate Endpoint**

<mark style="color:red;">This API is only limited to account managemrnt API tokens created with the reseller role.</mark>

<mark style="color:blue;">`PUT /api/v1/endpoints/:endpoint_uuid/deactivate`</mark>

Deactivate an endpoint so that it cannot be used.

**Parameters**

All parameters are optional.

| Name                 | Type     | Description                                                       |
| -------------------- | -------- | ----------------------------------------------------------------- |
| deactivation\_reason | `string` | A human readable description to describe the deactivation reason. |

**Request**

Example request with optional `deactivation_reason`.

```url
curl -X PUT 'https://customers.triton.one/api/v1/endpoints/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/deactivate' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" -d '
{
    "deactivation_reason": "Deactivation reason description"
}' 
```

**Response**

```json
204 No Content
```


# Tokens

### **Overview**

Create private access tokens to allow your backend services to make authenticated RPC requests to an Endpoint. This allows you to make requests from any backend origin.

<mark style="color:red;">This token is private information and should never be exposed on a front-end.</mark>

On the load balancers, we support "user:password" style basic authentication if you need to use that form of auth. In those cases, the password is the token. The token\_user is randomly generated, so every token has a unique user.

### List Tokens

<mark style="color:blue;">`GET /api/v1/tokens`</mark>

Returns all tokens that the current authentication token has access to.

**Parameters**

<table><thead><tr><th width="220">Name</th><th width="216.33333333333331">Type</th><th>Description</th></tr></thead><tbody><tr><td>account_uuid  <mark style="color:red;">*</mark></td><td><code>string</code></td><td>Will filter the results by account_uuid.</td></tr><tr><td>subscription_uuid <mark style="color:red;">*</mark></td><td><code>string</code></td><td>Will limit the results by subscription_uuid.</td></tr><tr><td>per</td><td><code>string</code></td><td></td></tr><tr><td>page</td><td><code>string</code></td><td></td></tr></tbody></table>

<mark style="color:red;">\* Either a</mark> <mark style="color:red;"></mark><mark style="color:red;">`subscription_uuid`</mark> <mark style="color:red;"></mark><mark style="color:red;">or</mark> <mark style="color:red;"></mark><mark style="color:red;">`account_uuid`</mark> <mark style="color:red;"></mark><mark style="color:red;">parameter is required</mark>

**Request**

Example requests with page filtering, number of tokens per page and filtering by account uuid and subscription uuid.

```url
curl 'https://customers.triton.one/api/v1/tokens?account_uuid=c92a9cea-47cc-494b-b1b0-4230a2316ee5' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

<pre><code><strong>curl 'https://customers.triton.one/api/v1/tokens?subscription_uuid=74ea9d9a-4b2a-4f01-af47-1c175f8a2af6' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
</strong></code></pre>

```
curl 'https://customers.triton.one/api/v1/tokens?per=10&page=2&subscription_uuid=74ea9d9a-4b2a-4f01-af47-1c175f8a2af6' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" 
```

#### Response

Returns a hash with the `tokens` key containing an array of tokens objects and the `meta` containing pagination data.

```json
{
   "tokens": [
      {
         "uuid": "fa2fec9c-c3e6-4625-b11f-7906dff5f37c",
         "name": "token-name",
         "value": "8d43f8dc-8aa3-4fa2-b09e-361f6af8d972",
         "rate_tier": "tier1",
         "auth_username": "tylerqt-tylerwi-db8c",
         "is_active": false,
         "deactivation_reasons": [      
         ]
      },
      { token_object_2 },
      { token_object_3 },
      ...
   ],
   "meta": {
      "current_page": 1,
      "next_page": null,
      "per_page": 10,
      "prev_page": null,
      "total_pages": 1,
      "total_count": 1
   }
}
```

### **Get Token**

<mark style="color:red;">This API is not available.</mark>

### **Create Token**

<mark style="color:blue;">`POST /api/v1/subscriptions/:subscription_uuid/tokens`</mark>

Add a new token that can be used to access the Subscription's Endpoints

**Parameters**

| Name                                                  | Type     | Description                                                                                                                                       |
| ----------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| name <mark style="color:red;">\*required</mark>       | `string` | A human readable name to describe the token.                                                                                                      |
| rate\_tier <mark style="color:red;">\*required</mark> | `string` | The name of the \<link>Rate Tier for this token, which defines rate limits for it. Each token incurs costs based on the Rate Tier defined for it. |

**Request**

Example requests with account\_uuid and subscription\_uuid filtering.

```url
curl -X POST 'https://customers.triton.one/api/v1/subscriptions/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/tokens' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" -d '
{
    "token": {
        "name": "Token 123",
        "rate_tier": "developer" 
    }
}' 
```

**Response**

All submitted keys, plus the following are returned:

| Name           | Type     | Description                                                                |
| -------------- | -------- | -------------------------------------------------------------------------- |
| uuid           | `string` | The generated primary key to reference this token.                         |
| auth\_username | `string` | A randomly generated value that can be for basic authentication if needed. |
| value          | `string` | The value used for authentication.                                         |

```json
{
   "token":  {
      "uuid": "b51e9812-ece9-4cb0-81f1-0c4bbdc8a3b7",
      "name": "Token 123",
      "value": "b4274ebd-e2f5-46fd-8e99-8aff8f9f4a85",
      "rate_tier": "developer",
      "auth_username": "tylerqt-example-73d1",
      "is_active": false,
      "deactivation_reasons": []
   }
}
```

### **Activate Token**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller and operator role.</mark>

<mark style="color:blue;">`PUT /api/v1/tokens/:token_uuid/activate`</mark>

Activate a token so that it can be used.

**Parameters**

<mark style="color:blue;">`token_uuid`</mark> is the only parameter that needs to be provided.

**Request**

```url
curl -X PUT 'https://customers.triton.one/api/v1/tokens/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/activate' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json"
```

**Response**

```json
204 No Content
```

### **Deactivate Token**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller and operator role.</mark>

<mark style="color:blue;">`PUT /api/v1/tokens/:token_uuid/deactivate`</mark>

Deactivate a token so that it cannot be used.

**Parameters**

All parameters are optional.

| Name                 | Type     | Description                                                       |
| -------------------- | -------- | ----------------------------------------------------------------- |
| deactivation\_reason | `string` | A human readable description to describe the deactivation reason. |

**Request**

Example request with `deactivation_reason`.

```url
curl -X PUT 'https://customers.triton.one/api/v1/tokens/74ea9d9a-4b2a-4f01-af47-1c175f8a2af6/deactivate' -H "Authorization: secret-api-token" -H "Content-Type: application/json" -H "Accept:application/json" -d '
{
    "deactivation_reason": "Deactivation reason description"
}' 
```

**Response**

```json
204 No Content
```


# Rate limits

### **Overview**

A rate limit caps how many requests per second an endpoint accepts. Triton enforces it per IP across all nodes to keep endpoints stable and protect against abusive traffic&#x20;

Our goal is to allow legitimate application traffic to flow smoothly while mitigating the impact of aggressive bots.

Endpoints authenticated with a token get a more favourable rate limit than endpoints restricted by allowed origins, since tokens are typically used by backend servers (many users per IP) and allowed origins by browsers (one user per IP).

Rate limits do not affect pricing. Our products are billed by usage (per million requests plus per GB of bandwidth).

### **Get rate limits**

<mark style="color:red;">This API is only limited to account management API tokens created with the reseller role.</mark>

<mark style="color:blue;">`GET /api/v1/rate_tiers`</mark>

Returns all Rate Tier types that an Endpoint and Token can get created with.

```json
{
  "rate_tiers": [
    { "name": "free" },
    { "name": "tier1" },
    { "name": "tier2" },
    { "name": "tier3" },
    { "name": "dedi" }
  ]
} 
```


# Introduction

We provide multiple APIs that support traders. These APIs operate on different chains and provide facilities to make trading in the DeFI space easier.


# Bundle simulation with Jito

Triton offers support for **Jito RPC for all plans**. This primarily gives access to the improved simulation support that Jito offers and allows you to simulate bundles of transactions.

> 🔧 Note: Triton One provides the Jito-enabled RPC node only. It is the customer's responsibility to have their IP whitelisted with Jito’s Block Engine in order to use certain features.

The `sendBundle` method is a Jito-exclusive RPC call and requires an active connection to the Jito Block Engine.

Resources

* 🔍 [Jito Searcher Examples](https://github.com/jito-labs/searcher-examples)
* 📄 [Jito Documentation – Low Latency Transactions](https://docs.jito.wtf/lowlatencytxnsend)

### simulateBundle

<mark style="color:green;">`POST`</mark> `https://xyz.mainnet.rpcpool.com/`

Simulates a transaction bundle.

#### Request Body

| Name                                           | Type   | Description                                                                                                                                       |
| ---------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| transactions<mark style="color:red;">\*</mark> | Vector | List of transactions to simulate.                                                                                                                 |
| config                                         | Object | (optional) Object of type RpcSimulateBundleConfig, provide configuration for the transaction simulation. See description of this parameter below. |

{% tabs %}
{% tab title="200: OK JSON RPC response body (RpcSimulateBundleResult)" %}
Returns a JSON RPC response corresponding to the following Rust struct:

```
pub struct RpcSimulateBundleResult {
    pub summary: RpcBundleSimulationSummary,
    pub transaction_results: Vec<RpcSimulateBundleTransactionResult>,
}

pub enum RpcBundleSimulationSummary {
    /// error and offending transaction signature if applicable
    Failed {
        error: RpcBundleExecutionError,
        tx_signature: Option<String>,
    },
    Succeeded,
}

pub enum RpcBundleExecutionError {
    #[error("The bank has hit the max allotted time for processing transactions")]
    BankProcessingTimeLimitReached,

    #[error("Error locking bundle because a transaction is malformed")]
    BundleLockError,

    #[error("Bundle execution timed out")]
    BundleExecutionTimeout,

    #[error("The bundle exceeds the cost model")]
    ExceedsCostModel,

    #[error("Invalid pre or post accounts")]
    InvalidPreOrPostAccounts,

    #[error("PoH record error: {0}")]
    PohRecordError(String),

    #[error("Tip payment error: {0}")]
    TipError(String),

    #[error("A transaction in the bundle failed to execute: [signature={0}, error={1}]")]
    TransactionFailure(Signature, String),
}

pub struct RpcSimulateBundleTransactionResult {
    pub err: Option<TransactionError>,
    pub logs: Option<Vec<String>>,
    pub pre_execution_accounts: Option<Vec<UiAccount>>,
    pub post_execution_accounts: Option<Vec<UiAccount>>,
    pub units_consumed: Option<u64>,
    pub return_data: Option<UiTransactionReturnData>,
}
```

{% endtab %}
{% endtabs %}

<details>

<summary>RpcSimulateBundleConfig: Bundle configuration</summary>

You can pass an optional configuration parameter when simulating bundle. The optional configuration takes the following format:

```
pub struct RpcSimulateBundleConfig {
    /// Gives the state of accounts pre/post transaction execution.
    /// The length of each of these must be equal to the number transactions.   
    pub pre_execution_accounts_configs: Vec<Option<RpcSimulateTransactionAccountsConfig>>,
    pub post_execution_accounts_configs: Vec<Option<RpcSimulateTransactionAccountsConfig>>,

    /// Specifies the encoding scheme of the contained transactions.
    pub transaction_encoding: Option<UiTransactionEncoding>,

    /// Specifies the bank to run simulation against.
    pub simulation_bank: Option<SimulationSlotConfig>,

    /// Opt to skip sig-verify for faster performance.
    #[serde(default)]
    pub skip_sig_verify: bool,

    /// Replace recent blockhash to simulate old transactions without resigning.
    #[serde(default)]
    pub replace_recent_blockhash: bool,
}

pub struct RpcSimulateTransactionAccountsConfig {
    pub encoding: Option<UiAccountEncoding>,
    pub addresses: Vec<String>,
}

pub enum SimulationSlotConfig {
    /// Simulate on top of bank with the provided commitment.
    Commitment(CommitmentConfig),

    /// Simulate on the provided slot's bank.
    Slot(Slot),

    /// Simulates on top of the RPC's highest slot's bank i.e. the working bank.
    Tip,
}
```

</details>


# Titan Swap API

The Titan Swap API is available for all customers with an active Solana Subscription.

### What is Titan?

The Titan Swap API allows you to stream price updates in real-time. The Argos routing engine serves as a meta-aggregator, sourcing data from multiple DEX aggregators and RFQ sources.

Titan Prime offers a unique websocket implementation that streams quote information. This significantly lowers latency and allows front-ends to update prices in real-time without overwhelming the server with HTTP requests.

#### Key Features

* **Real-time** **Streaming**: WebSocket implementation for continuous live price updates
* **Meta-Aggregator**: Sources from multiple DEX aggregators and RFQ sources to minimize slippage
* **Low Latency**: Stream quotes faster than REST polling
* **Dynamically Allocated Real Time Routing:** [DART](https://titan-exchange.gitbook.io/titan/developer-doc/dart-swap-api/overview) dynamically re-optimizes trades at the exact moment of execution, not just at quote time — guaranteeing best execution when it matters most.

#### Use Cases

Titan Swap API is ideal for:

* Trading interfaces requiring real-time price updates
* Price monitoring dashboards
* Trading bots that need low-latency price feeds
* Wallets with live swap pricing

### Getting Started

To access the Titan Swap API through Triton One, use the following pathing:

```
wss://<your-endpoint>.rpcpool.com/<your-token>/titan/api/v1/ws
```

Please make sure you use <mark style="color:$success;">`wss://`</mark> and not <mark style="color:$success;">`https://`</mark> as this is a websocket-based service.

#### Example Connection

```javascript
const ws = new WebSocket('wss://your-endpoint.rpcpool.com/your-token/titan/api/v1/ws');
ws.onopen = () => {
  console.log('Connected to Titan Swap API');
};
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Price update:', data);
};
```

### Executing a Swap

When you receive quote updates via the WebSocket stream, each quote contains swap route information:

1. **Connect to WebSocke**t: Establish connection (as shown above)
2. **Request Swap Quotes**: Send a <mark style="color:$success;">`NewSwapQuoteStream`</mark> request with your swap parameters
3. **Receive Quote Stream**: Get continuous updates with the best routes from multiple providers
4. **Select a Quote**: Choose the best quote based on your criteria (price, provider, etc.)
5. **Execute**: The quote contains either:

* **Instructions**: Build and sign a transaction with the provided instructions
* **Transaction**: A ready-to-sign transaction that you can directly sign and submit

Each <mark style="color:$success;">`SwapRoute`</mark> in the stream includes all the data needed to execute the swap.

For complete details on the request/response format and executing swaps from streaming quotes, refer to the official documentation.

### Pricing

The cost for using streaming services with Triton One is $0.08 per GB.

### Availability

Titan Swap API is available for all customers with an active Solana Subscription, on both shared RPC pools and dedicated nodes.

### API Documentation

To learn about how to use the Titan Swap API, please refer to the Titan Swap API docs here: <https://titan-exchange.gitbook.io/titan/titan-developer-docs/apis/swap-api#websocket-connections>

A TypeScript SDK is also available: <https://www.npmjs.com/package/@titanexchange/sdk-ts>

Read more: <https://blog.triton.one/titan-prime-api-for-solana-swaps-is-live-on-triton/>

<br>

<br>


# Introduction

Running a validator helps protect and decentralize the Solana network and earns continued rewards through the stake you can attract to your validator.

Our hosted validators utilize the infrastructure we have built over time as a top RPC provider and running validators on the Solana network.

On the validators we run, we ensure:

* Access by a limited team.
* Node identity keys in-memory only.
* 24/7 monitoring to ensure consistent voting.

Triton One owns the validator identity key. You or your team control the vote account withdrawal authority. Control over the withdrawal authority means you have complete control over the vote account.

Before you commit to running a validator, you should make sure that:

1. You are willing to fund vote fees until you have attracted enough stake to break even; this is approximately 1 SOL/day.
2. You can attract enough delegations to break even (TX fees - voting expenses) and make a profit eventually. Currently, this is in the range of 375 000 delegated SOL.

If you merely wish to access staking rewards, you can also stake with existing validators without running your own; see [https://validators.app](https://validators.app/) for details about existing validators and stake pools.

Validators deployed through Triton are compatible with all current Solana validator clients that adhere to protocol specifications. The choice of client is left entirely to the validator owner. Triton monitors compatibility and vote performance across supported clients, which include:

* Agave – the official reference implementation of the Solana validator, maintained by (Anza)(<https://anza.xyz/>) . Agave is feature-complete and receives all protocol updates first, serving as the baseline for all other clients.
* Jito-Solana – a fork of Agave maintained by (Jito Labs)(<https://jito.network/>), integrating an MEV auction system. This client allows validators to receive transaction bundles from a private relay and earn additional rewards through MEV tips, with minimal configuration overhead.
* Paladin – a fork of Jito-Solana maintained by the (Paladin)(<https://www.paladin.one/>) team. It offers stricter bundle filtering and supports a protected transaction pathway that mitigates frontrunning and sandwich attacks. It is designed to improve fairness in MEV distribution while remaining fully compatible with Solana’s consensus.
* Frankendancer – a hybrid validator client maintained by (Jump Crypto)(<https://jumpcrypto.com/firedancer/>), combining a Firedancer networking and block ingestion stack with consensus execution provided by Agave. It is engineered for high performance and low-latency block processing and is already in production on mainnet with active participation.

Each of these clients is capable of full validator participation. Triton validates client behavior continuously to ensure correct protocol operation.

To participate in the Triton SWQoS – Cascade Routing Layer, which provides a prioritized stream of transactions and the opportunity to generate additional revenue, non-triton validators must configure their infrastructure to accept authenticated traffic from Triton’s transaction distribution layer. This system is opt-in and compatible with supported clients. For configuration and operational details, see: (Providing Transaction Bandwidth)(<https://docs.triton.one/chains/solana/cascade/providing-transaction-bandwidth>)


# Vote account setup

NOTE: Should we host your validator, we will set up the vote account for you. The information below is for transparency and educational purposes.

To set up a validator, three kinds of keys need to be present:

* Node identity key pair.
* Vote key/account (public key only).
* Withdrawal key pair.

### Node Identity Key Pair

The node identity key pair is owned and held by Triton. Clients guarantee a minimum balance of 10 SOL to keep the validator running. If the balance drops below 10 SOL, the client must send SOL directly to the validator. Contact our support to receive the validator public key if you are creating your vote account.

### Withdrawal Key Pair

The withdrawal key pair is the one that can be used to modify the vote account; it must be kept safe! You use this key pair to withdraw rewards from the vote account. We recommend you keep this in a hardware wallet or cold storage. This should never be a hotkey or used with online/web wallets. Losing the keys to this account may result in significant losses.

To create a withdrawal key pair, you can run the following:

```
solana-keygen new -o ~/authorized-withdrawer-keypair.json
```

### Vote account

The final step involves setting up the vote account. You will generate a keypair for the vote account, but once it is configured, only the public key of the vote account matters. The withdrawal keypair is the one that will be used to control the vote account:

```
$ solana-keygen new -o ~/vote-account-keypair.json
$ solana create-vote-account ~/vote-account-keypair.json <pubkey for node identity> ~/authorized-withdrawer-keypair.json
```

### Further reading

For more documentation about vote account management, we strongly encourage you to read through the documents on Solana's website here:

{% embed url="<https://docs.solana.com/running-validator/vote-accounts>" %}


# Node identity protection

The public-private key pair of the node identity enables the node to vote on behalf of your vote account. We hold this key pair and deploy it to the node.&#x20;

This key pairs account must have a sufficient balance to pay voting fees. This key also accrues rewards that help to offset vote fees (or entirely cover them, depending on stake). Therefore, the balance of the node identity may need to be maintained and topped up regularly.

The node identity must be kept safe and secure. Anyone possessing the node identity could set up a second validator that creates issues for your primary validator (and, in the future, potentially slashing). Malicious parties could also receive the rewards the key accrues or empty its balance.

To protect the node identity, we adopt a setup that ensures in-memory-only keys. When our validators boot, they use a temporary node identity that cannot vote for your vote account. This means that once the validator has started up and caught up with the network, it will be in voting-disabled mode.&#x20;

One of our node operators will then authenticate with our secured, encrypted secret storage and enable the validator node to perform a one-time fetch of the real node identity, which is then activated in the Solana validator's memory.&#x20;

This ensures that the node identity is never stored in an unencrypted form anywhere. It is also never transferred anywhere between the validator node and our encrypted storage. This transit of the node key happens over an entirely authenticated and encrypted stream.

Without access to the validator memory space, you would be unable to retrieve the node identity, and there is no durable storage of the identity on the node itself.


