TriportRPC

Solana Pre-Execution Transaction Stream

Transactions as the validator reconstructs them from the leader's shreds — before they execute. You see the intent before the network sees the result.

Method / EndpointWS /ws/sol-preexec
TypeTriport product (WebSocket)
NetworkSolana
AuthenticationAuthorization: Bearer $TRIPORT_API_KEY (or ?api-key= query param)
Required scopesol.preexec on the API key (* wildcard keys also work)
Tier / rate limitPro tier and above; concurrent-stream capped, account filter required on Pro

What This Is

A Solana validator reassembles transactions from the shreds it receives from the slot leader. That reassembly happens before the runtime executes anything. /ws/sol-preexec streams those reassembled transactions: signature, the full raw transaction, and the addresses resolved from address lookup tables.

Triport holds one or two upstream subscriptions to validators that expose this data, de-duplicates by signature across them, filters server-side against your account list, and fans the result out to you. You pay for one WebSocket instead of running your own multi-source geyser client.

What you do not get, by construction. These transactions have not executed. There is no status, no error, no logs, no inner instructions, no pre/post balances and no compute-unit accounting — that information does not exist yet at the moment the frame is produced. A transaction in this stream may fail, may be dropped, and the slot itself may be skipped. Confirm outcomes through a normal commitment channel such as /ws/sol-stream.

How early is it?

Measured 2026-07-31 from the US PoP over 18,040 matched signatures, against Triport's own /ws/sol-stream:

Median15 ms earlier
10th percentile39 ms earlier
Arrived first99.2% of transactions

Against confirmed-commitment consumers the gap is much larger — roughly 0.9 of a slot (~360 ms) — because confirmed waits for a vote round that this stream does not.

Read those two numbers precisely. The 15 ms figure is against a very fast processed-level firehose, and it is consistent rather than dramatic: the value here is that it wins almost every race, not that it wins by a lot. The 360 ms figure applies only if you are currently consuming confirmed.

/ws/sol-stream/ws/sol-firstshredthis channel
Levelaccount/tx/slot/block, after executionslot-leveltransaction-level, before execution
Payloadfull update with execution metadata{slot, ts, parent}signature + raw tx + ALT addresses
Answers"what actually executed""a slot started forming""which transactions are in this slot"
Guaranteesexecuted at your chosen commitmentnonenone

Endpoint

wss://triport.io/ws/sol-preexec?accounts=<pubkey>,<pubkey>

Query parameters

NameRequiredDescription
accountsyes on ProComma-separated base58 pubkeys. A frame is delivered when the transaction touches any of them, including ALT-resolved addresses. Pro allows up to 50; Business up to 200.
fmtnojson (default) or raw — see Frame format.

The filter is fixed for the lifetime of the connection. There is no in-stream filter replacement: the upstream subscription is shared across all subscribers, so a per-client filter swap would either rebuild it on every change or silently diverge from what you asked for. To change your filter, reconnect.

Why the filter is mandatory on Pro. Unfiltered, this stream is roughly 21.75 Mbit/s sustained. That is not a policy preference — it is more bandwidth per subscriber than the Pro plan costs. Business may connect without a filter; Pro connections without accounts are refused with close code 4003 (filter_required).

Authentication

wscat -c "wss://triport.io/ws/sol-preexec?accounts=$POOL" \
  -H "Authorization: Bearer $TRIPORT_API_KEY"


# or
wscat -c "wss://triport.io/ws/sol-preexec?accounts=$POOL&api-key=$TRIPORT_API_KEY"

Close codes: 4001 missing/invalid credentials; 4003 forbidden — carrying tier_insufficient (below Pro), filter_required (empty filter on Pro) or filter_too_wide (more accounts than the tier allows); 4029 you already hold the maximum number of concurrent streams for your tier. See Close codes for the shared set.

Frame format

Default (fmt=json), one object per transaction:

{
  "signature": "5ggXGeZKdiqtr5zYSSWpvdW7MmB38P1JXwnoy1L3Dod1u2Naw9Xn3hNNpGa9cQNttiqcVPqBfYaaLuDM7SUTQbqB",
  "slot": 436349534,
  "is_vote": false,
  "tx_pb_base64": "CkAB…",
  "account_keys": ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", "…"],
  "pre_exec": true,
  "ts": 1751490000123
}
FieldTypeMeaning
signaturestringbase58 transaction signature.
slotuint64Slot the transaction was reconstructed in.
is_voteboolVote transaction flag.
tx_pb_base64stringThe transaction, protobuf-encoded — see the warning below.
account_keysstring[]Static message keys first, then ALT-resolved writable, then ALT-resolved readonly, in the order instructions index them.
pre_execboolAlways true. Present so that code which mixes this stream with executed-transaction feeds cannot confuse the two.
tsint64Unix milliseconds when Triport received the upstream frame.

tx_pb_base64 is not a wire transaction. It is base64 of the protobuf encoding (solana.storage.ConfirmedBlock.Transaction), not Solana's canonical bincode wire format. You cannot pass it to sendTransaction without re-encoding. The field is named tx_pb_base64 rather than tx_base64 specifically so this is impossible to miss.

With fmt=raw the server forwards the upstream protobuf message unchanged. This is cheaper on both sides, but it binds your client to the upstream schema — prefer JSON unless you have a reason.

Delivery semantics

  • At-most-once, no replay. Live stream only; on a drop, reconnect and resume from the current moment.
  • De-duplicated by signature across upstream sources, within a 5-second window. Sources disagree by tens to hundreds of milliseconds, so this covers the skew with a wide margin — but treat your consumer as idempotent by signature anyway.
  • Multi-source by default. Individual validators episodically stall for seconds. A second source covers the gap without interrupting your stream; you do not see the failover.
  • Best-effort per client. If you stop reading and your buffer fills, the server drops frames for you rather than blocking other subscribers. Under backpressure this stream is sampled, not queued.

Example (Node.js)

import WebSocket from "ws";


const pools = ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"];
const ws = new WebSocket(
  `wss://triport.io/ws/sol-preexec?accounts=${pools.join(",")}`,
  { headers: { Authorization: `Bearer ${process.env.TRIPORT_API_KEY}` } },
);


ws.on("message", (buf) => {
  const tx = JSON.parse(buf.toString());
  // tx.pre_exec === true: this has NOT executed yet.
  // React on intent, then confirm the outcome elsewhere before treating it as fact.
  console.log(`pre-exec ${tx.signature} slot=${tx.slot}`);
});


ws.on("close", (code) => {
  // 4001 unauthorized · 4003 forbidden (tier / filter) · 4029 stream limit
  console.error("closed", code);
});

Notes

  • Pro tier. Gated on the sol_preexec tier feature, available from Pro upward. Business additionally may connect without a filter.
  • Pair with confirmation. This is an early trigger, not a guarantee. Any action taken on it should be reconciled against a commitment channel.
  • See Streaming overview, Authentication, and Rate limits & tiers for account-wide details.