Skip to content

unconfirmedlabs/junstream

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

junstream

Real-time Sui blockchain data over SSE and WebSocket. Powered by jun.

SSE Streams

Connect to any stream with curl or EventSource:

curl -N http://localhost:3000/stream/transactions
const es = new EventSource("http://localhost:3000/stream/transactions");
es.addEventListener("transactions", (e) => {
  const tx = JSON.parse(e.data);
  console.log(tx.digest, tx.sender);
});

Available streams

Endpoint Description
/stream/checkpoints Checkpoint summaries
/stream/transactions Transaction records
/stream/move_calls Move function calls
/stream/balance_changes Per-transaction balance changes
/stream/object_changes Object state changes
/stream/transaction_dependencies Transaction dependency edges
/stream/transaction_inputs Programmable transaction inputs
/stream/commands All PTB commands
/stream/system_transactions Non-programmable (system) transactions
/stream/unchanged_consensus_objects Read-only consensus object refs
/stream/events Raw events

Event schemas

checkpoints

{
  "sequence_number": "262251530",
  "epoch": "1090",
  "digest": "...",
  "timestamp": "2026-04-07T01:27:58.308Z",
  "tx_count": 8
}

transactions

{
  "digest": "...",
  "sender": "0x...",
  "success": true,
  "computation_cost": "634980",
  "storage_cost": "23628400",
  "storage_rebate": "19268964",
  "move_call_count": 2,
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z",
  "epoch": "1090"
}

move_calls

{
  "tx_digest": "...",
  "call_index": 0,
  "package": "0x...",
  "module": "pool",
  "function": "swap",
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

balance_changes

{
  "tx_digest": "...",
  "checkpoint_seq": "262251530",
  "address": "0x...",
  "coin_type": "0x2::sui::SUI",
  "amount": "-1000000",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

object_changes

{
  "tx_digest": "...",
  "object_id": "0x...",
  "change_type": "MUTATED",
  "object_type": "0x2::coin::Coin<0x2::sui::SUI>",
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

transaction_dependencies

{
  "tx_digest": "...",
  "depends_on_digest": "...",
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

transaction_inputs

{
  "tx_digest": "...",
  "input_index": 0,
  "kind": "SHARED",
  "object_id": "0x...",
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

commands

{
  "tx_digest": "...",
  "command_index": 0,
  "kind": "MoveCall",
  "package": "0x...",
  "module": "pool",
  "function": "swap",
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

system_transactions

{
  "tx_digest": "...",
  "kind": "ConsensusCommitPrologueV1",
  "data": {},
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

unchanged_consensus_objects

{
  "tx_digest": "...",
  "object_id": "0x...",
  "kind": "ReadOnly",
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

events

{
  "tx_digest": "...",
  "event_seq": 0,
  "package_id": "0x...",
  "module": "pool",
  "event_type": "0xdee9::clob_v2::OrderPlaced",
  "sender": "0x...",
  "contents": "...",
  "checkpoint_seq": "262251530",
  "timestamp": "2026-04-07T01:27:58.308Z"
}

WebSocket

Connect to ws://localhost:3000/ws for bidirectional streaming with filtering and field selection.

Subscribe

Single subject:

{
  "subscribe": "transactions",
  "filters": { "sender": "0x..." },
  "fields": ["digest", "sender", "timestamp"]
}

Multiple subjects at once:

{
  "subscriptions": [
    "events",
    { "subject": "transactions", "filters": { "sender": "0x..." } },
    { "subject": "balance_changes", "filters": { "coin_type": "0x2::sui::SUI" }, "fields": ["address", "amount"] }
  ]
}

Unsubscribe

{ "unsubscribe": "transactions" }
{ "unsubscribe": ["transactions", "events"] }

Messages

{ "subject": "transactions", "data": { "digest": "...", "sender": "0x...", "timestamp": "..." } }

Re-subscribing to the same subject replaces the previous subscription.

Filters

Exact match{ "sender": "0xabc..." }

Multiple values (OR){ "coin_type": "0x2::sui::SUI,0xdba3...::usdc::USDC" }

Prefix match{ "event_type": "0xdee9*" } (trailing *)

Negation{ "!sender": "0x000...0" } (prefix !) or { "sender!": "0x000...0" } (suffix !)

Health

GET /health → { "status": "ok" }

Configuration

Variable Default Description
JUNSTREAM_NATS_URL nats://localhost:4222 NATS server URL
JUNSTREAM_NATS_PREFIX jun NATS subject prefix
PORT 3000 HTTP listen port

Architecture

Sui fullnode (gRPC) → jun publisher → NATS → junstream proxy → SSE / WebSocket
  • Publisher: Runs jun index stream-chain, decodes checkpoints via native Rust decoder, broadcasts all record types to NATS subjects
  • NATS: Message broker connecting publisher to proxy
  • Proxy: Subscribes to NATS subjects, serves SSE streams and WebSocket connections

SDK

Install the TypeScript client:

bun add @unconfirmed/junstream
import { junstream } from "@unconfirmed/junstream";

const client = junstream("http://localhost:3000");

// Async iterator
for await (const tx of client.stream("transactions")) {
  console.log(tx.digest, tx.sender);
}

// Callback
const unsub = client.subscribe("balance_changes", (bc) => {
  console.log(bc.address, bc.amount);
}, { filter: { coin_type: "0x2::sui::SUI" } });

// WebSocket (multi-stream)
const ws = client.ws();
ws.subscribe("transactions", (tx) => console.log(tx.digest));
ws.subscribe("events", (ev) => console.log(ev.event_type));

About

Real-time Sui blockchain data streamed over SSE and WebSocket.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors