Getting Started with Pulse
Connect to Raiden Pulse gRPC binary stream in minutes
1. Reserve Access
2. Get Your Endpoint
| Region | Endpoint |
|---|---|
| FRA | http://fra.pulse.raiden.wtf:16000/ |
| NYC | http://nyc.pulse.raiden.wtf:16000/ |
| AMS | http://ams.pulse.raiden.wtf:16000/ |
Each region requires a separate subscription.
3. Protocol
Raiden Pulse is protocol-compatible with the open shreder-rust-example reference client and its shreder_binary.proto. If your stack already integrates that proto, you can point it at Raiden Pulse endpoints with no code changes.
Proto summary
- Service:
ShrederBinaryService - RPC:
SubscribeBinaryTransactions— bidirectional streaming - Request:
SubscribeBinaryTransactionsRequestwith named filter map - Filter:
SubscribeRequestFilterBinaryTransactions—account_include,account_exclude,account_required - Payload: raw bytes deserialized via
bincodeinto SolanaVersionedTransaction
4. Connect
Rust (tonic)
pub mod shreder_binary {
tonic::include_proto!("shreder_binary");
}
use futures::{channel::mpsc::unbounded, sink::SinkExt};
use maplit::hashmap;
use shreder_binary::{
shreder_binary_service_client::ShrederBinaryServiceClient,
SubscribeBinaryTransactionsRequest,
SubscribeRequestFilterBinaryTransactions,
};
use solana_transaction::versioned::VersionedTransaction;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let entrypoint = "http://fra.pulse.raiden.wtf:9991";
let mut client = ShrederBinaryServiceClient::connect(entrypoint).await?;
let (mut tx_req, rx_req) = unbounded();
let request = SubscribeBinaryTransactionsRequest {
transactions: hashmap! {
"pumpfun".to_owned() => SubscribeRequestFilterBinaryTransactions {
account_exclude: vec![],
account_include: vec![],
account_required: vec![
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P".to_owned(),
],
}
},
};
tx_req.send(request).await?;
let mut stream = client
.subscribe_binary_transactions(rx_req)
.await?
.into_inner();
while let Some(response) = stream.message().await? {
let Some(update) = response.transaction else { continue };
let Some(tx) = update.transaction else { continue };
let versioned_tx: VersionedTransaction =
match bincode::deserialize(&tx.binary_transaction) {
Ok(vt) => vt,
Err(_) => continue,
};
let instruction_count = versioned_tx.message.instructions().len();
println!(
"slot={} sig={} ixs={}",
tx.slot,
tx.signature,
instruction_count,
);
}
Ok(())
}Enable tonic TLS features if your endpoint uses HTTPS:
tonic = { version = "0.10", features = ["tls", "tls-roots", "tls-webpki-roots"] }Python (grpcio + protobuf)
Generate Python bindings from shreder_binary.proto:
python -m grpc_tools.protoc -Iproto \
--python_out=. --grpc_python_out=. \
proto/shreder_binary.protoThen connect:
import grpc
from shreder_binary_pb2 import (
SubscribeBinaryTransactionsRequest,
SubscribeRequestFilterBinaryTransactions,
)
from shreder_binary_pb2_grpc import ShrederBinaryServiceStub
channel = grpc.insecure_channel("fra.pulse.raiden.wtf:9991")
stub = ShrederBinaryServiceStub(channel)
def make_requests():
yield SubscribeBinaryTransactionsRequest(
transactions={
"pumpfun": SubscribeRequestFilterBinaryTransactions(
account_required=["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
),
},
)
for response in stub.SubscribeBinaryTransactions(make_requests()):
tx = response.transaction.transaction
# tx.binary_transaction is raw bincode-encoded VersionedTransaction
print(f"slot {tx.slot} sig {tx.signature}")TypeScript (@grpc/grpc-js)
Generate TS bindings from the proto, then:
import { credentials } from '@grpc/grpc-js';
import { ShrederBinaryServiceClient } from './generated/shreder_binary_grpc_pb';
import {
SubscribeBinaryTransactionsRequest,
SubscribeRequestFilterBinaryTransactions,
} from './generated/shreder_binary_pb';
const client = new ShrederBinaryServiceClient(
'fra.pulse.raiden.wtf:9991',
credentials.createInsecure(),
);
const stream = client.subscribeBinaryTransactions();
const filter = new SubscribeRequestFilterBinaryTransactions();
filter.setAccountRequiredList(['6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P']);
const req = new SubscribeBinaryTransactionsRequest();
req.getTransactionsMap().set('pumpfun', filter);
stream.write(req);
stream.on('data', (response) => {
const tx = response.getTransaction().getTransaction();
console.log(`slot ${tx.getSlot()} sig ${tx.getSignature()}`);
});5. Filtering
Every subscription requires at least one filter entry in the transactions map. Each entry name is client-chosen (e.g. "pumpfun", "jupiter-swaps") and carries:
| Field | Purpose |
|---|---|
account_required | Transactions must touch every listed account |
account_include | Transactions may touch any listed account |
account_exclude | Transactions must not touch any listed account |
Server-side filtering — no wasted bandwidth.
6. Decoding the Payload
Each tx.binary_transaction is a bincode-encoded Solana VersionedTransaction. Deserialize client-side with your language’s bincode / serde equivalent:
- Rust:
bincode::deserialize::<VersionedTransaction>(&bytes) - Python:
solders→VersionedTransaction.from_bytes(bytes) - TS:
@solana/web3.js→VersionedTransaction.deserialize(bytes)
7. Limits
| Limit | Value |
|---|---|
| Concurrent connections | 10 |
| Named filters | 35 per connection |
| Account filters | 5,000 per named filter |
A named filter carrying more than 5,000 accounts is rejected. If you need to watch more, split them across filters, and across connections once you pass 35.
8. Pulse V2: raiden_binary.proto and resolved ALTs
Pulse V2 moves off the third-party shreder_binary proto onto Raiden’s own, raiden_binary. Both are served during the transition, so nothing breaks on the day it lands, but new integrations should expect to move.
The rename is not the point. V2 resolves address lookup tables server-side, which is what makes the migration worth doing rather than merely necessary.
What changes
shreder_binary (today) | raiden_binary (Pulse V2) | |
|---|---|---|
| Package | shreder_binary | raiden_binary |
| Service | ShrederBinaryService | RaidenBinaryService |
| RPC | SubscribeBinaryTransactions | unchanged |
| Filters | account_include / _exclude / _required | unchanged |
| Matched filter names | not reported | filters on every response |
| Server timestamp | not reported | created_at |
| ALT resolution | client-side, your problem | server-side, in loaded_writable_addresses and loaded_readonly_addresses |
| Signatures | single field | signatures, repeated |
Resolved ALTs
A V0 transaction names most of its accounts indirectly, through an address lookup table. To know which accounts it actually touches you have to resolve those tables yourself, which means fetching them, caching them, and keeping the cache correct as tables change.
Pulse V2 does that resolution server-side and ships the result alongside the transaction. The full account set is then the direct keys inside binary_transaction plus loaded_writable_addresses and loaded_readonly_addresses. Both are empty for legacy transactions, which carry no lookup tables.
If you are filtering or routing on the accounts a transaction touches, this removes an entire subsystem from your client.
The proto
syntax = "proto3";
package raiden_binary;
import "google/protobuf/timestamp.proto";
service RaidenBinaryService {
rpc SubscribeBinaryTransactions(stream SubscribeBinaryTransactionsRequest)
returns (stream SubscribeBinaryTransactionsResponse);
}
message SubscribeBinaryTransactionsRequest {
map<string, SubscribeRequestFilterBinaryTransactions> transactions = 3;
}
message SubscribeBinaryTransactionsResponse {
repeated string filters = 1;
SubscribeUpdateBinaryTransaction transaction = 4;
google.protobuf.Timestamp created_at = 11;
}
message SubscribeUpdateBinaryTransaction {
BinaryTransaction transaction = 1;
uint64 slot = 2;
// ALT-resolved accounts. Empty for legacy transactions.
repeated bytes loaded_writable_addresses = 3;
repeated bytes loaded_readonly_addresses = 4;
}
message SubscribeRequestFilterBinaryTransactions {
repeated string account_include = 3;
repeated string account_exclude = 4;
repeated string account_required = 6;
}
message BinaryTransaction {
repeated bytes signatures = 1;
// Serialized VersionedTransaction
bytes binary_transaction = 3;
}Migrating
The subscription shape is unchanged, so a filter map written for shreder_binary transfers as-is. What moves is where fields live:
- Regenerate bindings from
raiden_binary.protoand swapShrederBinaryServiceClientforRaidenBinaryServiceClient. slotis on the update, not on the transaction.signaturesis a repeated field of raw bytes rather than a single string, so take the first element and encode it yourself if you were printing it.binary_transactionis unchanged: still a bincode-encodedVersionedTransaction, decoded exactly as before.
Pricing
See Pricing.
Payment in USDC on Solana via the dashboard.