For developers / JSON-RPC

Subscriptions

Live

Four subscription kinds, on a server that is not the HTTP listener. This is where the mini-block stream lives, which is the one surface here you will not find on another chain.

A separate server

Subscriptions do not exist on the HTTP port

eth_subscribe against https://rpc.picklechain.xyz answers -32601. The node registers no jsonrpsee subscription anywhere; the socket is a separate hand-written server on its own listener, and it is the only place subscriptions are served.

A replica has no WebSocket server at all - the listener is spawned only on the leader path. Subscriptions are a leader-only surface.

EndpointValue
Publicwss://rpc.picklechain.xyz/ws/
Localws://127.0.0.1:8546

Read from sequencer/src/main.rs:252

Both /ws and /ws/ are served, as two separate nginx locations with no redirect between them: a WebSocket client mostly does not follow a 301 on its handshake, so whichever spelling was redirected would simply fail to connect.

The handshake

eth_subscribe and pickle_subscribe are accepted interchangeably and behave identically:

json
// ->
{"jsonrpc":"2.0","id":1,"method":"pickle_subscribe","params":["miniBlocks"]}

// <-
{"jsonrpc":"2.0","id":1,"result":"0x1"}

// <- then, per event
{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1","result":{…}}}
  • Subscription ids come from a per-connection counter starting at 1, so two connections both see 0x1. Do not treat an id as globally unique.
  • Notifications always arrive under the method name eth_subscription, even for a subscription opened as pickle_subscribe. Unwrap the payload at params.result.
  • eth_unsubscribe and pickle_unsubscribe both return a boolean: true if the id was found on this connection, false otherwise.

Read from sequencer/src/ws.rs:243

The four kinds

One notification per sealed EVM block.

Worth knowing. The payload is a nine-field summary - number, hash, parentHash, timestamp, mixHash, baseFeePerGas, transactionCount, miniBlockFrom, miniBlockTo - and not the full header that eth_getBlockByNumber returns. transactionCount is a decimal number. A client that needs gasLimit, gasUsed or logsBloom must re-fetch the block.

sequencer/src/rpc.rs:1881

miniBlocks

alias: newMiniBlocks

The preconfirmation stream: one notification per sealed mini-block, at the mini-block scheduling target. Both spellings are the same stream.

Worth knowing. Mini-block fields break the eth_* hex convention on purpose: number, evmBlockNumber, timestampUs and each receipt's gasUsed are decimal JSON numbers, and the timestamp is in microseconds.

sequencer/src/ws.rs:268

Logs as they are indexed, with the standard address and topics filtering. An absent or empty filter means every log.

Worth knowing. A malformed topics filter answers -32602 before an id is allocated, so a failed subscribe leaves you with no subscription rather than a silent one.

sequencer/src/ws.rs:268

Transaction hashes, as a bare string payload.

Worth knowing. "Pending" does not mean what it means elsewhere. There is no public mempool; a hash is published only AFTER the transaction has executed and been indexed. Treat this as an executed-transaction feed, not a mempool feed.

sequencer/src/rpc.rs:854

A worked example

The mini-block stream, with the fallback the first-party feed uses. The fallback matters: a dropped socket is normal, and the polling path is how you catch up without gaps.

javascript
const socket = new WebSocket("wss://rpc.picklechain.xyz/ws/");

socket.addEventListener("open", () => {
  socket.send(JSON.stringify({
    jsonrpc: "2.0", id: 1, method: "pickle_subscribe", params: ["miniBlocks"],
  }));
});

socket.addEventListener("message", (event) => {
  const message = JSON.parse(event.data);
  // The subscribe reply carries an id; notifications carry params.result.
  if (!message.params) return;
  const mini = message.params.result;

  // Decimal numbers, not hex quantities - and microseconds, not seconds.
  console.log(mini.number, mini.transactions.length, mini.timestampUs);
});

// A slow handler is DROPPED from the stream rather than buffered, so do the
// work elsewhere and keep this callback cheap.

If the socket closes, poll pickle_miniBlockNumber and then pickle_getMiniBlockByNumber from your last seen height. Bound the catch-up - the reference client fetches at most 100 per pass - or a long disconnection turns into a burst of requests against the shared heavy-read budget.

Limits and the drop policy

LimitValue
Message size256 KiBframe and message both; oversized answers -32005 "message too large"
Connections256excess connections are dropped at accept, with no error frame - your client sees a closed socket, not a rejection
Subscriptions per connection32-32005 "subscription limit reached"
Requests per second100 per connection-32005 "request rate limit exceeded"
Stream depth1024 per streama subscriber that falls behind is DROPPED from the stream rather than buffered, so a slow handler loses notifications silently. Do the work off the socket

Read from sequencer/src/ws.rs:16, sequencer/src/rpc.rs:38

Slow subscribers lose data silently

Each stream is a broadcast channel 1024 deep. A subscriber that falls behind is dropped from the stream rather than allowed to grow the node's memory - you do not get an error frame, you get a gap. At the mini-block scheduling target that buffer is a few seconds of headroom, so any per-event work heavier than a push onto a queue belongs off the socket.

None of these limits is environment-tunable, and the connection cap is enforced at accept: the 257th connection is closed without an error frame, which looks like a network failure rather than a rejection.

Ordinary calls over the socket

Any frame that is not a subscribe or unsubscribe is forwarded to the same handler the HTTP port uses, so every method in the reference is callable over the socket.

Error codes differ by transport

Error codes are re-wrapped on the way out. Every non-send failure comes back as -32603 regardless of its original code, so an eth_call revert is -32000 over HTTP and -32603 over the socket. A client that branches on the code needs to know which transport it is on.

Read from sequencer/src/ws.rs:313