Skip to content

Solana subscriptions - #41

Merged
nfurfaro merged 5 commits into
mainfrom
furnic/solana-subscriptions
Oct 22, 2025
Merged

Solana subscriptions#41
nfurfaro merged 5 commits into
mainfrom
furnic/solana-subscriptions

Conversation

@nfurfaro

@nfurfaro nfurfaro commented Oct 21, 2025

Copy link
Copy Markdown
Contributor

Adds the ability for apps to subscribe to logs from Solana programs. Due to a limitation of the solana rpc method logsSubscribe, only a single program may be targeted per rpc request.
To work around this, weopen a single websocket per void subscription, but are then able to subscribe to multiple solana programs over the single connection.

Features enabled by this work include:

  • subscribe to solana program logs (tested in query/solana_program_logs/tests.rs).
  • subscribe to multile solana programs (tested in query/solana_program_logs/tests.rs).
  • subscribe to both solana logs and ethereum events (tested in oracle/tests/query_multi_chain.rs).

Notes

logsSubscribe

The Solana PubsubClient.logs_subscribe() method returns (BoxStream<'a, T>, UnsubscribeFn)
where the stream borrows from the client with lifetime 'a. This means we cannot return
the stream from an async block that owns the client, as the borrow checker prevents
returning a value that references local data.

Unlike alloy's subscribe_logs() which returns an owned subscription that can be converted
into a stream with .into_stream(), Solana's API ties the stream lifetime to the client.

To work around this, we spawn a separate task that owns both the client and the stream,
and use a tokio mpsc channel to forward events to a ReceiverStream that we can safely return.

Multiple Program Subscription Architecture

When subscribing to multiple programs, this implementation:

  1. Creates a SINGLE WebSocket connection to the Solana RPC endpoint
  2. Creates MULTIPLE subscriptions on that connection (one per program address)
  3. Each subscription monitors transactions that mention its specific program
  4. All subscription streams are merged using futures::stream::select_all()
  5. Events arrive in natural time order, interleaved across all programs

This approach is efficient because:

  • Only 1 WebSocket connection regardless of program count (vs N connections)
  • Scales well (100 programs = 1 connection with 100 subscriptions)
  • Events naturally ordered by arrival time across all programs
  • Graceful degradation: if some subscriptions fail, others continue working

Note: The Solana RpcTransactionLogsFilter::Mentions(vec![...]) uses AND semantics
(transaction must mention ALL addresses), so we need separate subscriptions to achieve
OR semantics (transaction mentioning ANY of our programs).

closes #28
closes #29
closes #30

@nfurfaro nfurfaro changed the title furnic/solana subscriptions Solana subscriptions Oct 21, 2025
@nfurfaro
nfurfaro force-pushed the furnic/solana-subscriptions branch from 75ba248 to 4e3b871 Compare October 21, 2025 22:59
@nfurfaro
nfurfaro requested review from bengybade and freesig October 21, 2025 23:00
Comment thread universal_decoder.md Outdated

@freesig freesig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work there's a lot that went into this. Just a few suggestions

Comment thread Cargo.toml Outdated
Comment thread crates/oracle/Cargo.toml Outdated
Comment thread crates/oracle/src/lib.rs Outdated
Comment thread crates/oracle/src/query/solana_program_logs.rs
Comment thread crates/oracle/src/query/solana_program_logs/websocket.rs Outdated
@nfurfaro
nfurfaro force-pushed the furnic/solana-subscriptions branch 2 times, most recently from 6e9e647 to ca9e424 Compare October 22, 2025 16:51
WIP: work on getting solana logs using websockets

<!-- ps-id: 2fa6abec-1050-4722-b43c-e77ce700ba16 -->
<!-- ps-id: a5ad8543-5885-4d10-a898-e7414710422f -->
<!-- ps-id: f5d4915c-e74c-477d-a6a8-ae8d768ccbbe -->
<!-- ps-id: 6cd37234-2a41-4af6-9f39-83b7b8259d82 -->
chore: put solana stuff behind feature flag
refactor: use async-stream

<!-- ps-id: 7398871c-c74e-4612-93d0-a66961d12b08 -->
@nfurfaro
nfurfaro force-pushed the furnic/solana-subscriptions branch from ca9e424 to adb937f Compare October 22, 2025 16:52
@nfurfaro
nfurfaro requested a review from freesig October 22, 2025 16:53

@freesig freesig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work. Awesome to see this land

@nfurfaro
nfurfaro merged commit 73b57ab into main Oct 22, 2025
15 checks passed
@nfurfaro
nfurfaro deleted the furnic/solana-subscriptions branch October 22, 2025 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

e2e test Implement Transaction Fetching Solana RPC Polling Loop

2 participants