asyncapi: 3.0.0
info:
  title: Tripport — Robinhood sequencer feed
  version: 0.4.0-n9-fast
  description: |
    A continuously warm Triport receive layer merges a fan of sequencer-feed sockets.
    The first frame for each `sequenceNumber` wins and later copies are deduplicated.
    The layer runs even with no downstream clients, so a new client does not begin with
    the upstream catch-up blind spot. Triport retains the existing bounded in-memory replay
    window. Reconnecting clients can request frames after a sequence cursor with
    `?from_sequence=N`; the resume control frame says whether the requested boundary is
    still fully covered.
    The observed L2 transaction spectrum is legacy, rare 0x01 (EIP-2930), and predominantly
    0x02 (EIP-1559). Type 0x03 (blob) was not observed, and 0x04 was not confirmed as
    an L2 transaction type.
    Blob data belongs to the L1 data-availability layer, not to L2 transactions in this feed.
    Message kind `3` was observed (as were 9 and 13).
defaultContentType: application/json
servers:
  production: {$ref: '_shared.yaml#/servers/production'}
channels:
  /ws/robinhood-feed:
    address: /ws/robinhood-feed
    description: |
      Live stream of already assembled L2 blocks from the continuously warm merged receive
      layer. The layer fans out across feed sockets, accepts the first arrival for a
      `sequenceNumber`, and deduplicates later copies. Add `?from_sequence=N` when reconnecting
      to replay the bounded in-memory window. Inspect `resume.bufferComplete`; false means the
      cursor was already evicted and the consumer must reconcile externally.

      Add `?early_heads=1` to opt into an additional `head` control frame. It is emitted at
      most once for a block number and only when the merged header arrives before that block's
      feed body. Clients without the option receive the unchanged feed stream.

      The upstream can also send `confirmedSequenceNumberMessage` control frames. Triport
      forwards them and annotates frames with a reconciliation watermark; consumers must not
      treat that watermark as stronger finality than Robinhood itself provides.

      The observed L2 transaction envelope types are legacy, rare 0x01 (EIP-2930), and
      predominantly 0x02 (EIP-1559). Type 0x03 (blob) was not observed, and 0x04 is not
      confirmed as an L2 transaction type. Blob data belongs to the L1 data-availability layer
      rather than to L2 transactions in this feed. A parser MUST NOT reject or silently drop a frame solely
      because it contains another transaction type: classify that transaction as `unknown`,
      preserve the frame, and increment an unknown-transaction-type metric.
    x-measured-catchup-tail-seconds:
      minimum: 2.25
      maximum: 111
      status: measured-range
      windows: 3
      queue-dependent: true
      observations:
        - source-window: s7t23
          seconds: 2.25
          measurement: absolute-minimum-delta
        - source-window: 01c
          seconds: 111
          measurement: two-window-summary
    x-catchup-consumer-rule:
      initial-window: compare-with-independent-head
      anomalously-old-transaction: mark-or-discard
    x-transaction-type-contract:
      observed: [legacy, '0x01', '0x02']
      not-observed: ['0x03']
      not-confirmed: ['0x04']
      observations:
        - source-window: s7t23
          transactions: 11445
          counts: {legacy: 1585, '0x02': 9860, '0x03': 0}
        - source-window: s8t13
          transactions: 429
          counts: {legacy: 266, typed: 163, '0x03': 0}
        - source-window: 01c
          transactions: 2207
          counts: {legacy: 259, '0x01': 2, '0x02': 1935, '0x03': 0, '0x04-disputed': 11}
      unknown-type-policy:
        frame-action: accept
        classification: unknown
        metric: required
      notes:
        '0x03': Blob data is an L1 data-availability concern; no blob L2 transaction was observed (0/13652 in the accepted reconciliation).
        '0x04': Unresolved — the aggregator identifies 0x04 as a parser frame marker, while the 01c decoder classified 11 of 2207 transactions as 0x04 set-code; map 05c must resolve this on the live feed.
    x-wss-subscription-capacity:
      status: measured
      observations:
        - concurrent-subscriptions: 10
          first-rpc-silent-timeout-percent: 20
          approximate: true
        - concurrent-subscriptions: 100
          first-rpc-silent-timeout-percent: 74
          approximate: true
      failure-mode: silent timeout of the first RPC; no HTTP 429 and no WebSocket close
      source: infra-4-sety/integration-rh/06_stage7_resolution.md
    x-query-parameters:
      from_sequence:
        type: integer
        minimum: 0
        description: Replay frames strictly after this sequence cursor when retained.
      early_heads:
        type: integer
        enum: [1]
        description: Opt in to early head control frames; omit for the unchanged feed stream.
    x-merge-contract:
      source: continuously-warm-fusion-layer
      selection: first-arrival
      deduplication-key: sequenceNumber
      client-cold-start: false
    x-replay-window-source: proxy-agg/internal/networks/robinhood/feed/hub.go#DefaultConfig
    x-measurement-source: infra-4-sety/diagnostics/matrix/robinhood-2026-09-07-s7-feed.json
    messages:
      FeedFrame: {$ref: '#/components/messages/FeedFrame'}
operations:
  receiveFeed:
    action: receive
    channel: {$ref: '#/channels/~1ws~1robinhood-feed'}
    messages: [{$ref: '#/channels/~1ws~1robinhood-feed/messages/FeedFrame'}]
components:
  schemas:
    FeedMessage:
      type: object
      required: [sequenceNumber, blockHash, message]
      allOf:
        - anyOf:
            - required: [signatureV2]
            - required: [signature]
        - anyOf:
            - properties: {message: {properties: {message: {required: [l2Msg]}}}}
            - properties: {message: {properties: {message: {required: [rawTransaction]}}}}
            - properties: {message: {properties: {message: {required: [rawTransactions]}}}}
      properties:
        sequenceNumber: {type: integer, minimum: 0}
        blockHash: {type: string, pattern: '^0x[0-9a-fA-F]{64}$'}
        signatureV2: {type: string, description: Base64 signature observed in the feed.}
        signature: {type: string, description: Signature field used by compatible feed frames.}
        message:
          type: object
          required: [message]
          properties:
            message:
              type: object
              required: [header]
              properties:
                header:
                  type: object
                  required: [kind]
                  properties:
                    kind: {type: integer, description: 'Observed kinds: 3, 9, 13; kind 3 predominated.'}
                l2Msg:
                  type: string
                  contentEncoding: base64
                  description: >-
                    Base64 encoding of the binary envelope. The aggregator describes its
                    framing as [0x03][BE64 L][0x04][tx], where L = 1 + len(tx), but the role
                    of 0x04 remains unconfirmed because the 01c decoder classified 11 of 2207
                    transactions as 0x04 set-code. The observed transaction types are legacy,
                    rare 0x01, and predominantly 0x02. Map N5-rh-05c must resolve the 0x04
                    interpretation against the live feed.
                  x-binary-framing:
                    prefix: '0x03'
                    length: 'BE64; L = 1 + len(tx)'
                    candidate-transaction-marker: '0x04'
                    status: unconfirmed
                rawTransaction: {type: string}
                rawTransactions: {type: array, items: {type: string}}
        decodedTxHashes:
          type: array
          description: >-
            Optional decoded transaction records. This field is unavailable unless map
            N5-rh-05c-feed implements and enables its decoder flag; its presence is not promised.
          items: {$ref: '#/components/schemas/DecodedTransaction'}
          x-optional: true
          x-feature-flag: N5-rh-05c-feed/decodedTxHashes
          x-feature-flag-source: infra-4-sety/integration-rh/MASTER_PLAN_S7.md#22-дополнения-к-картам-стоящим-в-очереди-правится-промпт-не-карта
          x-implementation-status: planned-not-implemented
    TransactionType:
      type: string
      description: >-
        Observed decoded EVM transaction envelope type, or unknown for any unrecognised or
        unconfirmed type. Unknown types preserve their frame and increment a metric; they are
        never rejected solely because their type is outside the observed spectrum (invariant 16-bis).
      enum: [legacy, '0x01', '0x02', unknown]
    DecodedTransaction:
      type: object
      additionalProperties: false
      required: [hash, type]
      properties:
        hash: {type: string, pattern: '^0x[0-9a-fA-F]{64}$'}
        type: {$ref: '#/components/schemas/TransactionType'}
    Reconciliation:
      type: object
      additionalProperties: false
      required: [status, latestConfirmedSequence, frameLastSequence, confirmationLag, sourceGapsObserved]
      properties:
        status: {type: string, enum: [watermark_unavailable, watermark, pending_confirmation, confirmed]}
        latestConfirmedSequence: {type: integer, minimum: 0}
        frameLastSequence: {type: integer, minimum: 0}
        confirmationLag: {type: integer, minimum: 0}
        sourceGapsObserved: {type: integer, minimum: 0}
    DataFrame:
      type: object
      required: [version, messages]
      properties:
        version: {type: integer, const: 1}
        messages: {type: array, minItems: 1, items: {$ref: '#/components/schemas/FeedMessage'}}
        catchup: {type: boolean}
        replay: {type: boolean, description: True when delivered from Triport's bounded replay window.}
        reconciliation: {$ref: '#/components/schemas/Reconciliation'}
    ConfirmedSequenceControl:
      type: object
      required: [version, confirmedSequenceNumberMessage]
      properties:
        version: {type: integer, const: 1}
        confirmedSequenceNumberMessage:
          type: object
          required: [sequenceNumber]
          properties:
            sequenceNumber: {type: integer, minimum: 0}
        catchup: {type: boolean}
        reconciliation: {$ref: '#/components/schemas/Reconciliation'}
    ResumeControl:
      type: object
      required: [version, resume]
      properties:
        version: {type: integer, const: 1}
        resume:
          type: object
          additionalProperties: false
          required: [requestedAfterSequence, earliestAvailableSequence, latestAvailableSequence, bufferComplete]
          properties:
            requestedAfterSequence: {type: integer, minimum: 0}
            earliestAvailableSequence: {type: integer, minimum: 0}
            latestAvailableSequence: {type: integer, minimum: 0}
            bufferComplete: {type: boolean}
        reconciliation: {$ref: '#/components/schemas/Reconciliation'}
    EarlyHeadControl:
      type: object
      additionalProperties: false
      examples:
        - version: 1
          head:
            number: 123
            hash: '0x0000000000000000000000000000000000000000000000000000000000000000'
            source: ext-1
            at_ms: 1789468016629
      required: [version, head]
      properties:
        version: {type: integer, const: 1}
        head:
          type: object
          additionalProperties: false
          required: [number, hash, source, at_ms]
          properties:
            number: {type: integer, minimum: 0, description: Calibrated L2 block number corresponding to feed sequenceNumber.}
            hash: {type: string, pattern: '^0x[0-9a-fA-F]{64}$'}
            source: {type: string, minLength: 1, description: Sanitized source id; never a URL, hostname, or IP address.}
            at_ms: {type: integer, minimum: 1, description: Unix time in milliseconds when the merged layer observed the header.}
    FeedFrame:
      oneOf:
        - {$ref: '#/components/schemas/DataFrame'}
        - {$ref: '#/components/schemas/ConfirmedSequenceControl'}
        - {$ref: '#/components/schemas/ResumeControl'}
        - {$ref: '#/components/schemas/EarlyHeadControl'}
  messages:
    FeedFrame:
      title: Robinhood feed frame
      payload: {$ref: '#/components/schemas/FeedFrame'}
