Helius enhanced WebSockets
Stream transactions and account changes from Helius enhanced (Atlas) WebSockets with transactionSubscribe and accountSubscribe.
On this page
- Quick example
- Configuration
- HeliusWebsocket
- Filters
- RpcTransactionsConfig (from helius::
types) - Behaviour
- Troubleshooting
- Failed to create Helius client: Unable to connect to server: HTTP error: 401 Unauthorized
- Every transaction logs Failed to decode transaction
- Error decoding Helius WS Account event
- No Clock updates received for 5 seconds, triggering reconnection
- Transactions stop after Error sending transaction update
- See also
HeliusWebsocket connects to Helius enhanced WebSockets (wss:/), runs one transactionSubscribe with your filter and one accountSubscribe per account you list, and emits transaction and account updates. It watches the Clock sysvar to detect a stalled connection and reconnects everything when it does. Use it for live transactions on Helius when you don't need gRPC.
| Crate | carbon-helius-atlas-ws-datasource 2.0.0 |
| Install | cargo add carbon-helius-atlas-ws-datasource@2. |
| Emits | Transaction, AccountUpdate, AccountDeletion |
| Mode | Live. Runs until cancelled or until reconnects keep failing. |
| Provider | A Helius API key on a plan that includes enhanced WebSockets. You also need the helius crate (2.0.0) for the filter types. |
| API docs | docs.rs |
| Source | datasources/ |
Quick example#
This program subscribes to successful Orca Whirlpool transactions and prints each swapV2. It compiles as shown; to run it, set HELIUS_.
cargo new helius-ws && cd helius-ws
cargo add carbon-core@2.0.0 carbon-log-metrics@2.0.0 carbon-orca-whirlpool-decoder@2.0.0 carbon-helius-atlas-ws-datasource@2.0.0
cargo add helius@2.0.0
cargo add tokio@1 --features full
cargo add env_logger@0.11 log@0.4use {
carbon_core::{
error::CarbonResult, instruction::InstructionProcessorInputType, pipeline::Pipeline,
processor::Processor,
},
carbon_helius_atlas_ws_datasource::{Filters, HeliusWebsocket},
carbon_log_metrics::LogMetrics,
carbon_orca_whirlpool_decoder::{
instructions::OrcaWhirlpoolInstruction, OrcaWhirlpoolDecoder, PROGRAM_ID,
},
helius::types::{
enhanced_websocket::TransactionDetails, Cluster, RpcTransactionsConfig,
TransactionCommitment, TransactionSubscribeFilter, TransactionSubscribeOptions,
UiEnhancedTransactionEncoding,
},
std::{env, sync::Arc},
};
#[tokio::main]
async fn main() -> CarbonResult<()> {
env_logger::init();
let transactions = RpcTransactionsConfig {
filter: TransactionSubscribeFilter {
vote: Some(false),
failed: Some(false),
account_include: Some(vec![PROGRAM_ID.to_string()]),
..Default::default()
},
options: TransactionSubscribeOptions {
commitment: Some(TransactionCommitment::Confirmed),
// Required: the helius default is JsonParsed, which Carbon can't decode.
encoding: Some(UiEnhancedTransactionEncoding::Base64),
transaction_details: Some(TransactionDetails::Full),
show_rewards: Some(false),
max_supported_transaction_version: Some(1),
},
};
let datasource = HeliusWebsocket::new(
env::var("HELIUS_API_KEY").expect("HELIUS_API_KEY must be set"),
Filters::new(vec![], Some(transactions))?,
Cluster::MainnetBeta,
);
Pipeline::builder()
.datasource(datasource)
.metrics(Arc::new(LogMetrics::new()))
.instruction(OrcaWhirlpoolDecoder, SwapLogger)
.build()?
.run()
.await
}
struct SwapLogger;
impl Processor<InstructionProcessorInputType<'_, OrcaWhirlpoolInstruction>> for SwapLogger {
async fn process(
&mut self,
input: &InstructionProcessorInputType<'_, OrcaWhirlpoolInstruction>,
) -> CarbonResult<()> {
let tx = &input.metadata.transaction_metadata;
if let OrcaWhirlpoolInstruction::SwapV2 { data, accounts, .. } = input.decoded_instruction {
println!("slot {} swapV2 pool={} amount={}", tx.slot, accounts.whirlpool, data.amount);
}
Ok(())
}
}HELIUS_API_KEY=your-helius-api-key RUST_LOG=info cargo runEach matching swap prints one slot … swapV2 pool=… amount=… line. With an invalid key the connection is refused and retried every 3 seconds:
[ERROR carbon_helius_atlas_ws_datasource] Failed to create Helius client: Unable to connect to server: HTTP error: 401 UnauthorizedTransactionDetails must be imported from helius::: helius:: is a different type used by Helius' HTTP API.
Configuration#
HeliusWebsocket#
pub const fn new(api_key: String, filters: Filters, cluster: Cluster) -> Self
pub fn with_ping_interval_secs(self, ping_interval_secs: u64) -> Self
pub fn with_pong_timeout_secs(self, pong_timeout_secs: u64) -> Self
pub fn with_transaction_idle_timeout_secs(self, transaction_idle_timeout_secs: u64) -> Self| Field | Type | Default | Meaning |
|---|---|---|---|
api_key | String | — | Helius API key. |
filters | Filters | — | What to subscribe to. |
cluster | helius:: | — | MainnetBeta or Devnet. StakedMainnetBeta has no enhanced WebSocket URL and fails to connect. |
ping_ | Option<u64> | None (10 s) | Interval between WebSocket pings. |
pong_ | Option<u64> | None (3 missed pongs) | Close the connection after this long without a pong, rounded up to whole ping intervals. 0 disables the check. |
transaction_ | Option<u64> | None | Reconnect when no transaction arrives for this long. Leave unset for quiet filters. |
For example:
let datasource = HeliusWebsocket::new(api_key, filters, Cluster::MainnetBeta)
.with_ping_interval_secs(10)
.with_pong_timeout_secs(30)
.with_transaction_idle_timeout_secs(60);Filters#
pub fn new(accounts: Vec<Pubkey>, transactions: Option<RpcTransactionsConfig>) -> CarbonResult<Self>| Field | Type | Meaning |
|---|---|---|
accounts | Vec<Pubkey> | One accountSubscribe per address. Opened without options, so the server's default encoding and commitment apply. |
transactions | Option< | One transactionSubscribe. |
new returns Err(Custom("Error creating Filters for the Helius WebSocket: accounts and transactions can't be both empty")) when both are empty.
RpcTransactionsConfig (from helius:: types)#
filter: TransactionSubscribeFilter:
| Field | Type | Meaning |
|---|---|---|
vote | Option< | Include or exclude vote transactions. |
failed | Option< | Include or exclude failed transactions. Carbon drops failed transactions anyway. |
signature | Option< | A single signature. |
account_ | Option< | Any of these accounts (OR). |
account_ | Option< | None of these accounts. |
account_ | Option< | All of these accounts (AND). |
options: TransactionSubscribeOptions. Its Default is not usable with Carbon, because it asks for JsonParsed:
| Field | Type | Set to | Meaning |
|---|---|---|---|
commitment | Option< | Confirmed | Processed, Confirmed or Finalized. |
encoding | Option< | Base64 | Required. Any JSON encoding makes every transaction fail with Failed to decode transaction. |
transaction_ | Option< | Full | Other levels are skipped with Skipping non-full Helius transaction notification. |
show_ | Option< | as needed | Include rewards in the metadata. |
max_ | Option<u8> | Some(1) | Highest transaction version to receive. |
Behaviour#
- Subscriptions. Each connection runs a Clock sysvar subscription, one task per account, and one transaction task.
- Stall detection. If no Clock update arrives for 5 seconds, or the Clock jumps more than 10 slots, all subscriptions are closed and reopened. With
transaction_, a silent transaction stream does the same.idle_ timeout_ secs - Reconnects. Between connections the datasource waits 3 seconds. Building the client is retried up to 30 times in a row, then
consumereturns an error. Missed data is not replayed. - Partial failures. If one
accountSubscribeor thetransactionSubscribecall fails, or its stream closes, only that task stops (with an error in the log); the others keep running until the next reconnect. - Transaction updates. Failed transactions are dropped.
slotcomes from the notification;index,block_timeandblock_hashareNone.is_voteis copied from your filter'svotevalue, not from the transaction. Return data inmeta.holds the base64 text as bytes rather than the decoded bytes.return_ data - Account updates.
AccountUpdate(orAccountDeletionat zero lamports) with the notification slot andtransaction_.signature: None - Backpressure. Updates use
try_send. When the pipeline channel is full, the update is dropped and that subscription's task stops until the next reconnect. - Metrics. Counters
helius_,atlas_ ws_ transaction_ updates_ received_ total helius_,atlas_ ws_ account_ updates_ received_ total helius_; histogramsatlas_ ws_ account_ deletions_ received_ total helius_,atlas_ ws_ transaction_ process_ time_ nanoseconds helius_,atlas_ ws_ account_ process_ time_ nanoseconds helius_,atlas_ ws_ account_ deletion_ process_ time_ nanoseconds helius_.atlas_ ws_ clock_ process_ time_ nanoseconds
Troubleshooting#
Failed to create Helius client: Unable to connect to server: HTTP error: 401 Unauthorized#
The API key is wrong, or your plan doesn't include enhanced WebSockets. After 30 attempts the datasource gives up.
Every transaction logs Failed to decode transaction#
options. is JsonParsed (the helius default) or another JSON encoding. Set Some(UiEnhancedTransactionEncoding::.
Error decoding Helius WS Account event#
Account subscriptions use the server's default encoding, which Carbon can't always decode (for example for larger accounts). For program-owned accounts, use Yellowstone or RpcProgramSubscribe with Base64 instead.
No Clock updates received for 5 seconds, triggering reconnection#
The connection stalled; the datasource reconnects on its own. Frequent occurrences point to network problems or an overloaded process.
Transactions stop after Error sending transaction update#
The pipeline channel was full, and the transaction task stopped. It resumes at the next reconnect. Raise the pipeline's channel_ or speed up processors.
See also#
- Helius LaserStream: gRPC on Helius, with replay.
- Stream with RPC
- Datasources concept and update types