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