# Aperture gRPC (Beta)

### What is Aperture gRPC?

Aperture gRPC reconstructs validator shreds into a structured gRPC stream compatible with the Yellowstone subscription model, while bypassing traditional RPC node processing.

In practice, that means earlier transaction visibility than standard Yellowstone-style streams, with server-side filtering and a client experience that feels familiar if you already use Yellowstone.

{% hint style="info" %}
Aperture is designed for applications where reaction time matters more than full post-execution metadata.
{% endhint %}

### What Data is Available?

Aperture gRPC is designed for **low-latency transactions streaming**.

Following transaction data is available:

* signatures
* slot number
* recent blockhash
* is\_vote
* account keys
* address table lookups (for versioned only)
* instructions

Because the stream is reconstructed directly from shreds before full transaction execution completes, it does not include the extended replay metadata typically available in Yellowstone transaction feeds, such as:

* errors
* balance changes
* inner instructions (CPI)
* program logs
* compute unit usage
* transaction status

Think of Aperture gRPC as receiving the transaction message **as soon as it can be decoded from shreds**, not after a validator has completed full execution and metadata generation.

{% hint style="info" %}
Note: In Aperture, Address Lookup Tables (ALTs) are retrieved for v1 transactions only. However, ALT data may be incomplete compared to Yellowstone gRPC, as Aperture does not perform full transaction replay.
{% endhint %}

### Why Use Aperture?

Use **Aperture gRPC** when your application needs:

* the earliest possible visibility into incoming Solana transactions
* Yellowstone-style subscription ergonomics
* server-side filtering by signature or mentioned accounts
* reduced bandwidth usage compared to ShredStream when filters are applied
* minimal client-side decoding compared to raw UDP shreds or ShredStream gRPC

Typical use cases include:

* trading systems
* searchers
* real-time analytics
* ingestion pipelines
* latency-sensitive backend services

### Compatibility Model

Aperture gRPC follows the Yellowstone's subscription model. The same general request shape applies: a bidirectional stream where the client sends a `SubscribeRequest` and receives matching updates continuously.

For transaction subscriptions, the familiar Yellowstone filter semantics apply:

* if all transaction filter fields are empty, all transactions are streamed
* different fields are combined with logical **AND**
* values inside each array are combined with logical **OR**

Common transaction filters include:

* `vote`
* `signature`
* `account_include`
* `account_exclude`
* `account_required`

### Important Limitations

**Aperture gRPC prioritises speed, so it is not a substitute for every Yellowstone use case.**

Consider using **Yellowstone gRPC** instead, when you need:

* full transaction execution metadata
* balance updates
* program logs
* fully resolved address lookup tables
* account state changes and block data

{% hint style="warning" %}
Aperture should be treated as an **early transaction stream,** not as a final source of execution truth.
{% endhint %}

A decoded transaction may still fail, never confirm, or land on a fork that does not survive.

### Subscription Examples

Replace the `YOUR-TOKEN-HERE` placeholder below with your Aperture gRPC token.

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

```rust
use futures::StreamExt;
use std::collections::HashMap;
use std::time::Duration;
use yellowstone_grpc_client::{ClientTlsConfig, GeyserGrpcClient};
use yellowstone_grpc_proto::geyser::{
    subscribe_update::UpdateOneof,
    CommitmentLevel,
    SubscribeRequest,
    SubscribeRequestFilterTransactions,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();

    let mut client = GeyserGrpcClient::build_from_shared("https://aperture-grpc.rpcfast.com:443")?
        .x_token(Some("YOUR-TOKEN-HERE"))?
        .http2_keep_alive_interval(Duration::from_secs(30))
        .keep_alive_timeout(Duration::from_secs(10))
        .keep_alive_while_idle(true)
        .tcp_keepalive(Some(Duration::from_secs(30)))
        .tcp_nodelay(true)
        .tls_config(ClientTlsConfig::new().with_native_roots())?
        .connect()
        .await?;

    let mut tx_filters = HashMap::new();
    tx_filters.insert(
        "pumpfun".to_string(),
        SubscribeRequestFilterTransactions {
            vote: Some(false),
            failed: None,
            signature: None,
            account_include: vec![
                "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA".to_string(),
            ],
            account_exclude: vec![],
            account_required: vec![],
        },
    );

    let request = SubscribeRequest {
        transactions: tx_filters,
        commitment: Some(CommitmentLevel::Processed as i32),
        ..Default::default()
    };

    let (_sink, mut stream) = client.subscribe_with_request(Some(request)).await?;

    println!("Aperture stream started");

    while let Some(message) = stream.next().await {
        let message = message?;
        if let Some(UpdateOneof::Transaction(tx_update)) = message.update_oneof {
            println!("slot={}", tx_update.slot);

            if let Some(tx) = tx_update.transaction {
                if !tx.signature.is_empty() {
                    println!("signature={}", bs58::encode(&tx.signature).into_string());
                }
            }
        }
    }

    Ok(())
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import Client, {
  CommitmentLevel,
  SubscribeRequest,
  ChannelOptions
} from "@triton-one/yellowstone-grpc";
import bs58 from "bs58";

const X_TOKEN = "YOUR-TOKEN-HERE";

async function main() {
  const channelOptions: ChannelOptions = {
    grpcHttp2KeepAliveInterval: 30_000,
    grpcKeepAliveTimeout: 10_000,
    grpcKeepAliveWhileIdle: true,
    grpcTcpKeepalive: 1,
  };

  const client = new Client(
    "https://aperture-grpc.rpcfast.com:443",
    X_TOKEN,
    channelOptions
  );

  await client.connect();

  const stream = await client.subscribe();

  stream.on("data", (update) => {
    if (update.transaction?.transaction?.transaction?.signatures?.length) {
      const signature = bs58.encode(update.transaction.transaction.transaction.signatures[0]);
      console.log("signature:", signature);
      console.log("slot:", update.transaction.slot);
    }

    if (update.pong) {
      console.log("pong");
    }
  });

  stream.on("error", (err) => {
    console.error("stream error:", err);
  });

  const request: SubscribeRequest = {
    slots: {},
    accounts: {},
    transactions: {
      pumpfun: {
        vote: false,
        accountInclude: ["pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"],
        accountExclude: [],
        accountRequired: [],
      },
    },
    transactionsStatus: {},
    blocks: {},
    blocksMeta: {},
    entry: {},
    accountsDataSlice: [],
    commitment: CommitmentLevel.PROCESSED,
  };

  await new Promise<void>((resolve, reject) => {
    stream.write(request, (err: Error | null | undefined) => {
      if (err) reject(err);
      else resolve();
    });
  });

  setInterval(() => {
    const ping: SubscribeRequest = {
      ping: { id: 1 },
      slots: {},
      accounts: {},
      transactions: {},
      transactionsStatus: {},
      blocks: {},
      blocksMeta: {},
      entry: {},
      accountsDataSlice: [],
    };

    stream.write(ping, () => {});
  }, 1_000);
}

main().catch(console.error);

```

{% endtab %}

{% tab title="grpcurl" %}

```shell
grpcurl \
  -proto geyser.proto \
  -H "x-token: YOUR_TOKEN" \
  -d '{
    "slots": {},
    "accounts": {},
    "transactions": {
      "pumpfun": {
        "vote": false,
        "account_include": [
          "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"
        ]
      }
    },
    "blocks": {},
    "blocks_meta": {},
    "accounts_data_slice": [],
    "commitment": "PROCESSED"
  }' \
  aperture-grpc.rpcfast.com:443 \
  geyser.Geyser/Subscribe
```

{% endtab %}
{% endtabs %}

### Choosing Commitment Level

Aperture gRPC always streams data at `processed` commitment level, so specifying any higher commitment level is ignored.

### Keep-alive and Reconnection

For long-lived gRPC streams, it is recommended that you use both:

* *TCP keepalive*, so dead TCP connections are detected earlier.
* *HTTP/2 keepalive*, so intermediaries such as load balancers, proxies, or NAT devices are less likely to silently drop idle connections.

This is especially important for applications that keep a subscription open for a long time with uneven traffic patterns.

### Aperture vs. Shredstream vs. Yellowstone

|                     | Aperture gRPC                                                  | Shredstream gRPC                      | Yellowstone gRPC                      |
| ------------------- | -------------------------------------------------------------- | ------------------------------------- | ------------------------------------- |
| **Data source**     | Validator shreds                                               | Validator shreds                      | RPC Node                              |
| **Output format**   | Yellowstone-compatible transactions, slots, and entries stream | Raw Solana entry stream               | Full Yellowstone stream               |
| **Latency profile** | \~30–40 ms faster than Yellowstone                             | Slightly slower than Aperture (\~1ms) | Higher than shred-derived streams     |
| **Filtering**       | Server-side                                                    | Client-side only                      | Server-side                           |
| **Client work**     | Low                                                            | Highest                               | Low                                   |
| **Best fit**        | Shred-level latency with easy integration                      | Maximum low-level control             | Rich metadata and broader replay data |

#### Aperture vs. Yellowstone

**Aperture gRPC delivers transactions earlier** by decoding them directly from validator shreds instead of waiting for the full Geyser pipeline.

This typically reduces latency by 30–40 ms, while trading away extended replay metadata.

**Yellowstone gRPC remains the better fit** when your system depends on:

* transaction status
* balance updates
* inner instructions
* logs
* richer post-execution context

#### Aperture vs. Shredstream

**Shredstream gRPC** exposes a lower-level Solana entry gRPC stream and leaves decoding and filtering to the client.

**Aperture gRPC** shines with even better latency characteristics, streams already-decoded transaction data in Yellowstone gRPC format and supports server-side filtering. That reduces implementation complexity for teams that want shred-level speed without building a shred-decoding pipeline themselves, as well as saves bandwidth due to server-side filtering.&#x20;

<figure><img src="https://1798863941-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FRRBph8Hty4VwnYxGM39r%2Fuploads%2FUrTvA2X9zkewIgURXpQF%2Fimage.png?alt=media&#x26;token=e13151c7-d1bc-48cb-86ed-f94afb94e10f" alt=""><figcaption></figcaption></figure>

### Summary

**Aperture gRPC** is the right tool when you want:

* the earliest practical Solana transaction visibility
* Yellowstone-style subscriptions and filtering
* less infrastructure and parsing work than raw shred or entry feeds

Use **Yellowstone gRPC** when correctness and metadata depth matter more than raw arrival time.

Use **Shredstream gRPC** only when you intentionally want the full control over low-level decoding and processing and don't mind higher bandwidth usage.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rpcfast.com/rpc-fast-saas-solana/data-streaming/aperture-grpc-beta.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
