Skip to main content

How to read the Sequencer feed

Running an Arbitrum relay locally as a feed relay lets you subscribe to an uncompressed sequencer feed for real-time data as the sequencer accepts and orders transactions off-chain.

When connected to websocket port 9642 of the local relay, you'll receive feed data that looks something like this:

"version": 1,
"messages": [{
"sequenceNumber": 25757171,
"message": {
"message": {
"header": {
"kind": 3,
"sender": "0xa4b000000000000000000073657175656e636572",
"blockNumber": 16238523,
"timestamp": 1671691403,
"requestId": null,
"baseFeeL1": null
"delayedMessagesRead": 354560
"signature": null

Breaking this down a bit: the top-level data structure is defined by the BroadcastMessage struct:

type BroadcastMessage struct {
Version int `json:"version"`
// Note: "Messages" is slightly ambiguous naming since there are different types of messages
Messages []*BroadcastFeedMessage `json:"messages,omitempty"`
ConfirmedSequenceNumberMessage *ConfirmedSequenceNumberMessage `json:"confirmedSequenceNumberMessage,omitempty"`

The messages field is the BroadcastFeedMessage struct:

type BroadcastFeedMessage struct {
SequenceNumber arbutil.MessageIndex `json:"sequenceNumber"`
Message arbstate.MessageWithMetadata `json:"message"`
Signature []byte `json:"signature"`

Each message conforms to arbstate.MessageWithMetadata:

type MessageWithMetadata struct {
Message *arbos.L1IncomingMessage `json:"message"`
DelayedMessagesRead uint64 `json:"delayedMessagesRead"`

Finally, we get the transaction's information in the message subfield as an L1IncomingMessage:

type L1IncomingMessage struct {
Header *L1IncomingMessageHeader `json:"header"`
L2msg []byte `json:"l2Msg"`
// Only used for `L1MessageType_BatchPostingReport`
BatchGasCost *uint64 `json:"batchGasCost,omitempty" rlp:"optional"`

In this file, we can find a ParseL2Transactions function, which you can use this to decode the message.