Nitro sequencer feed
Connect to Dwellir's Robinhood sequencer feed over WebSocket. Read ordered Nitro messages with a three-day trial or the $100 monthly add-on, plus usage.
Dwellir's Robinhood sequencer feed streams ordered Nitro messages over WebSocket. Use it to build feed consumers and inspect transaction ordering.
wss://api-robinhood-mainnet-sequencer.n.dwellir.com/YOUR_API_KEYThe server starts sending data after the connection opens. No subscription request is required.
Access and pricing
Open Robinhood in the dashboard and select the sequencer endpoint. Start its three-day trial, or select the Robinhood Mainnet Sequencer Feed add-on.
| Item | Terms |
|---|---|
| Monthly access | $100 per month, in addition to your base plan and usage |
| Eligible paid plans | Developer, Growth, and Scale |
| Trial | Three days without the add-on access fee |
| Usage during trial | Counts against your plan's response allowance |
| Usage unit | One response per WebSocket data envelope, including confirmation-only envelopes |
The add-on is optional. Starting a trial does not purchase it or automatically convert the trial into a paid subscription. Select the paid add-on to continue after the trial.
Use an API key from the organization that has feed access. An API key alone does not unlock this restricted endpoint.
An envelope can contain multiple sequenced items. Each envelope counts as one response, regardless of its item count. Repeated data sent after reconnecting also counts toward usage.
Choose the right connection
| Connection | Data | Client |
|---|---|---|
| Sequencer feed | Ordered Nitro messages before Ethereum settlement | A WebSocket client and Nitro payload decoder |
| Archive RPC | Blocks, receipts, logs, state, and traces | ethers, viem, or another JSON-RPC client |
| RPC subscriptions | New blocks and contract logs | eth_subscribe("newHeads") or eth_subscribe("logs") |
The sequencer feed reports messages after the sequencer orders them. It does not expose the private pending transaction queue or provide Ethereum settlement guarantees.
Do not send eth_subscribe requests to the feed URL. Keep your existing archive RPC connection for transaction submission and execution results.
Connect with Node.js
Install ws, then save the example as feed.mjs:
npm install wsimport WebSocket from 'ws';
const apiKey = process.env.DWELLIR_API_KEY;
if (!apiKey) throw new Error('Set DWELLIR_API_KEY');
const ws = new WebSocket(
`wss://api-robinhood-mainnet-sequencer.n.dwellir.com/${apiKey}`,
);
let lastSequenceNumber;
ws.on('open', () => console.log('Connected to the Robinhood sequencer feed'));
ws.on('message', (data) => {
try {
const envelope = JSON.parse(data.toString());
for (const item of envelope.messages ?? []) {
const sequenceNumber = item.sequenceNumber;
if (!Number.isSafeInteger(sequenceNumber)) {
throw new Error('Invalid or unsafe sequence number');
}
if (lastSequenceNumber !== undefined) {
if (sequenceNumber <= lastSequenceNumber) continue;
if (sequenceNumber !== lastSequenceNumber + 1) {
console.warn('Sequence gap:', lastSequenceNumber, '->', sequenceNumber);
}
}
const payload = item.message.message;
const bytes = Buffer.from(payload.l2Msg, 'base64');
console.log({ sequenceNumber, kind: payload.header.kind, payloadBytes: bytes.length });
lastSequenceNumber = sequenceNumber;
}
if (envelope.confirmedSequenceNumberMessage) {
console.log('Feed confirmation:', envelope.confirmedSequenceNumberMessage);
}
} catch (error) {
console.error('Cannot process feed data:', error.message);
ws.close(1000, 'Consumer stopped');
}
});
ws.on('error', (error) => console.error('WebSocket error:', error.message));
ws.on('close', (code) => console.log('Feed closed:', code));Set DWELLIR_API_KEY in your environment and run:
node feed.mjsThe example logs each item's sequence number, message kind, and decoded byte length. It skips duplicate sequence numbers within one connection. It logs gaps and continues with later items, without recovering missing data.
It closes the connection if an envelope or item cannot be processed. It does not decode transactions or reconnect automatically.
Confirmation-only envelopes have no messages array, so the example handles them separately.
Read the message payload
A WebSocket envelope contains a messages array. Each entry represents one sequenced Nitro item, which can contain several transactions.
The following item comes from a Dwellir Robinhood relay sample saved on September 15, 2026. Its message timestamp is 2026-09-15T10:23:29Z.
Only l2Msg is abbreviated below. All other fields match the captured item. Download the complete item to run the decoding example.
{
"sequenceNumber": 63584698,
"message": {
"message": {
"header": {
"kind": 3,
"sender": "0xa4b000000000000000000073657175656e636572",
"blockNumber": 25982243,
"timestamp": 1789467809,
"requestId": null,
"baseFeeL1": 0
},
"l2Msg": "<omitted: 29,252 base64 characters>"
},
"delayedMessagesRead": 281088
},
"blockHash": "0xc2f05da0f6adcff40d34c8c7f6c049c6c1cdf916691a748d709be144ab7cba1f",
"signatureV2": "8SQPCb4wLCtys/WhDskmTB5wMqxJ6e7ExEd8lrWSg2JQPAWrCPtMdX7bTPsPlezNZbBwwOi5M5YpNROJkU2xHQA="
}This is one extracted item, not a complete WebSocket envelope. Its sequence number identifies its position in the feed, not a transaction hash.
Follow the nested fields
There are two message levels inside each item. The outer level adds metadata. The inner level contains the incoming-message header and binary payload.
| Field within an item | Meaning in this sample |
|---|---|
sequenceNumber | Feed position 63584698 |
message.delayedMessagesRead | Cumulative delayed-inbox messages consumed, here 281088 |
message.message.header.kind | 3 identifies a Nitro L2 message |
message.message.header.sender | Nitro's sequencer sender address, not the signed transaction's sender |
message.message.header.blockNumber | L1 block 25982243, not the Robinhood L2 block number |
message.message.header.timestamp | Unix timestamp in seconds, here 1789467809 |
message.message.header.requestId | Optional request identifier; null in this item |
message.message.header.baseFeeL1 | L1 base-fee field; 0 in this item, not the transaction's execution gas price |
message.message.l2Msg | Base64-encoded bytes containing a Nitro transaction batch |
blockHash | Block hash carried with the feed item, not a transaction hash |
signatureV2 | Base64-encoded feed signature; 65 bytes in this sample |
These fields follow Nitro's feed item, metadata, and incoming message types.
signatureV2 covers feed data using Nitro's signature scheme. It is separate from each transaction's signature. The example below decodes transactions but does not verify the feed signature.
A confirmation-only envelope instead contains confirmedSequenceNumberMessage with a sequenceNumber. It can omit messages entirely. A feed confirmation does not establish Ethereum settlement.
Decode the binary batch
Base64 decoding this item's l2Msg produces 21,937 bytes. Its first bytes are:
03 | 00 00 00 00 00 00 00 6e | 04 | 02 f8 6a 82 12 37 ...| Bytes | Interpretation |
|---|---|
03 | Nitro L2 batch kind |
00 00 00 00 00 00 00 6e | Eight-byte big-endian length of the first child, 110 bytes |
04 | Nitro signed-transaction kind inside that child |
02 f8 6a ... | The remaining 109 bytes encode an Ethereum type-2 signed transaction |
The outer header's kind: 3 and the payload's 0x03 belong to different type definitions. Inspect both when decoding.
This batch contains 9 signed transactions. Each child has its own length prefix and kind byte. Nitro also supports other message kinds and nested batches. See its L2 parser.
Save the downloaded item as sequencer-item-63584698.json. Save this script as inspect-feed.mjs, then run node inspect-feed.mjs:
import { readFileSync } from 'node:fs';
const item = JSON.parse(readFileSync('sequencer-item-63584698.json', 'utf8'));
const payload = Buffer.from(item.message.message.l2Msg, 'base64');
if (item.message.message.header.kind !== 3 || payload[0] !== 3) {
throw new Error('This example expects a Nitro L2 batch');
}
const transactions = [];
let offset = 1;
while (offset < payload.length) {
if (offset + 8 > payload.length) throw new Error('Truncated length prefix');
const length = payload.readBigUInt64BE(offset);
offset += 8;
if (length < 2n || length > BigInt(payload.length - offset)) {
throw new Error('Invalid child length');
}
const child = payload.subarray(offset, offset + Number(length));
if (child[0] !== 4) throw new Error('This example only handles signed-transaction children');
transactions.push(`0x${child.subarray(1).toString('hex')}`);
offset += Number(length);
}
console.log({
payloadBytes: payload.length,
transactionCount: transactions.length,
firstSignedTransaction: transactions[0],
});The script reads every child and removes its Nitro kind byte. It rejects other child kinds instead of treating them as signed transactions.
Decode the first transaction
Install ethers v6 with npm install ethers@6. Append the following code to inspect-feed.mjs and run it again:
import { Transaction } from 'ethers';
const tx = Transaction.from(transactions[0]);
console.log({
hash: tx.hash,
type: tx.type,
chainId: tx.chainId.toString(),
nonce: tx.nonce,
from: tx.from,
to: tx.to,
valueWei: tx.value.toString(),
gasLimit: tx.gasLimit.toString(),
maxFeePerGasWei: tx.maxFeePerGas.toString(),
maxPriorityFeePerGasWei: tx.maxPriorityFeePerGas.toString(),
data: tx.data,
});The first transaction in the captured item decodes to:
{
"hash": "0xbdffb350d63b66fd527ae31dde15dd0567fd74b65f9c6b08c9543c9c1103a8c8",
"type": 2,
"chainId": "4663",
"nonce": 278,
"from": "0x606e7523F19f37C0F1cEaD4C0A86E661Ee264b87",
"to": "0x606e7523F19f37C0F1cEaD4C0A86E661Ee264b87",
"valueWei": "0",
"gasLimit": "21000",
"maxFeePerGasWei": "1000000000",
"maxPriorityFeePerGasWei": "1",
"data": "0x"
}It is a zero-value transaction to the sender's own address, with no call data. Chain ID 4663 identifies Robinhood mainnet.
The fee fields are signed transaction limits, not the fee paid. Use its hash with eth_getTransactionReceipt to inspect execution results.
The archive receipt for this transaction reports success in Robinhood L2 block 63584698. Its block hash matches this feed item's blockHash.
For a consumer that handles every Nitro message kind, follow Arbitrum's feed format guide and the versioned Nitro parser.
Handle interruptions
For a persistent consumer, reconnect with backoff after a disconnect. Save the last processed sequence number after your application commits each item.
Deduplicate repeated sequence numbers across connections. Detect gaps before advancing your saved position. The example only logs gaps; applications that require complete data must recover them.
Do not assume the feed can replay arbitrary history or resume from a requested sequence number. Recover block and log history through the archive RPC endpoint, where it meets your data needs.
If the WebSocket handshake returns 403, check the API key's organization and its trial or paid add-on access. If the connection closes, check access and usage limits before reconnecting.
Robinhood also operates its own public upstream feed. Dwellir's endpoint adds authenticated access through your Dwellir organization.
rpc_modules
Inspect which JSON-RPC namespaces are enabled on your Robinhood Chain endpoint. Useful for capability checks, client diagnostics, and RPC feature discovery.
Ronin - Gaming-Focused Blockchain Documentation
Complete guide to Ronin blockchain integration with Dwellir RPC. Learn how to build on Ronin, access JSON-RPC methods, and optimize your gaming dApp performance.