> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Network Architecture

> The Network's structure: three architectural layers, the off-chain coordination loop, and the on-chain anchor surfaces that bind it to the protocol.

export const BorderedBox = ({children, variant = "default", padding = "var(--lp-spacing-4)", borderRadius = "var(--lp-spacing-px-8)", margin = "", accentBar = "", style = {}, className = "", ...rest}) => {
  const variants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    accent: {
      border: "1px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    muted: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "transparent"
    }
  };
  const accentBarColors = {
    accent: "var(--lp-color-accent)",
    positive: "var(--green-9)"
  };
  return <div data-docs-bordered-box="" data-accent-bar={accentBarColors[accentBar] ? "" : undefined} className={className} style={{
    ...variants[variant],
    padding: padding,
    borderRadius: borderRadius,
    ...margin ? {
      margin
    } : {},
    ...accentBarColors[accentBar] ? {
      position: "relative",
      '--accent-bar-color': accentBarColors[accentBar]
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const ScrollableDiagram = ({children, title = '', maxHeight = '500px', minWidth = '100%', showControls = false, className = '', style = {}, ...rest}) => {
  const buildDiagramKey = (currentTitle = '', currentClassName = '') => {
    const source = `${currentTitle}|${currentClassName}|scrollable-diagram`;
    let hash = 0;
    for (let index = 0; index < source.length; index += 1) {
      hash = hash * 31 + source.charCodeAt(index) >>> 0;
    }
    return `docs-diagram-${hash.toString(36)}`;
  };
  const diagramKey = buildDiagramKey(title, className);
  const zoomName = `${diagramKey}-zoom`;
  const zoomLevels = [{
    label: '75%',
    value: 0.75
  }, {
    label: '100%',
    value: 1
  }, {
    label: '125%',
    value: 1.25
  }, {
    label: '150%',
    value: 1.5
  }];
  const containerStyle = {
    overflow: 'auto',
    maxHeight,
    border: '1px solid var(--lp-color-border-default)',
    borderRadius: "8px",
    padding: "var(--lp-spacing-4)",
    background: 'var(--lp-color-bg-card)',
    position: 'relative'
  };
  return <div className={className} style={{
    position: 'relative',
    marginBottom: "var(--lp-spacing-4)",
    ...style
  }} {...rest}>
      {title && <p style={{
    textAlign: 'center',
    fontStyle: 'italic',
    color: 'var(--lp-color-text-secondary)',
    marginBottom: "var(--lp-spacing-2)",
    fontSize: '0.875rem'
  }}>
          {title}
        </p>}

      {showControls ? <style>{`
          [data-docs-diagram-key="${diagramKey}"] [data-docs-diagram-content] {
            transform: scale(1);
            transform-origin: top left;
            width: max-content;
          }
          ${zoomLevels.map(zoomLevel => `
          #${diagramKey}-${zoomLevel.label.replace('%', '')}:checked ~ [data-docs-diagram-shell] [data-docs-diagram-content] {
            transform: scale(${zoomLevel.value});
          }
          #${diagramKey}-${zoomLevel.label.replace('%', '')}:checked ~ [data-docs-diagram-controls] label[for="${diagramKey}-${zoomLevel.label.replace('%', '')}"] {
            background: var(--lp-color-accent);
            color: var(--lp-color-on-accent);
            border-color: var(--lp-color-accent);
          }`).join('\n')}
        `}</style> : null}

      {showControls ? zoomLevels.map(zoomLevel => {
    const inputId = `${diagramKey}-${zoomLevel.label.replace('%', '')}`;
    return <input key={inputId} id={inputId} type="radio" name={zoomName} defaultChecked={zoomLevel.value === 1} style={{
      position: 'absolute',
      opacity: 0,
      pointerEvents: 'none'
    }} />;
  }) : null}

      <div data-docs-diagram-key={diagramKey} data-docs-diagram-shell style={containerStyle}>
        <div data-docs-diagram-content style={{
    minWidth,
    transformOrigin: 'top left',
    width: 'max-content'
  }}>
          {children}
        </div>
      </div>

      {showControls ? <div data-docs-diagram-controls style={{
    display: 'flex',
    justifyContent: 'flex-end',
    alignItems: 'center',
    gap: "var(--lp-spacing-2)",
    marginTop: "var(--lp-spacing-2)",
    flexWrap: 'wrap'
  }}>
          <span style={{
    fontSize: "0.75rem",
    color: 'var(--lp-color-text-muted)',
    marginRight: 'auto'
  }}>
            Scroll to pan
          </span>
          {zoomLevels.map(zoomLevel => {
    const inputId = `${diagramKey}-${zoomLevel.label.replace('%', '')}`;
    return <label key={inputId} htmlFor={inputId} style={{
      background: 'transparent',
      color: 'var(--lp-color-text-secondary)',
      border: '1px solid var(--lp-color-border-default)',
      borderRadius: "4px",
      padding: '4px 10px',
      cursor: 'pointer',
      fontSize: "0.75rem",
      fontWeight: '600'
    }}>
                {zoomLevel.label}
              </label>;
  })}
        </div> : null}
    </div>;
};

export const DynamicTableV2 = ({tableTitle = null, headerList = [], itemsList = [], monospaceColumns = [], columnWidths = {}, columnConfig = {}, showSeparators = false, margin, className = '', style = {}, ...rest}) => {
  if (!headerList.length) {
    return <div>No headers provided</div>;
  }
  const tableRef = useRef(null);
  const [measuredColumnWidths, setMeasuredColumnWidths] = useState({});
  const measureFitColumns = () => {
    const tableElement = tableRef.current;
    if (!tableElement) {
      return;
    }
    const nextWidths = headerList.reduce((accumulator, header, index) => {
      const config = columnConfig?.[header] || ({});
      if (!config.fitContent) {
        return accumulator;
      }
      const contentNodes = tableElement.querySelectorAll(`[data-docs-column-key="${index}"] [data-docs-fit-content]`);
      let maxContentWidth = 0;
      contentNodes.forEach(node => {
        const width = Math.ceil(node.getBoundingClientRect().width);
        if (width > maxContentWidth) {
          maxContentWidth = width;
        }
      });
      if (maxContentWidth > 0) {
        accumulator[header] = `${maxContentWidth + 16}px`;
      }
      return accumulator;
    }, {});
    setMeasuredColumnWidths(currentWidths => {
      const currentEntries = Object.entries(currentWidths);
      const nextEntries = Object.entries(nextWidths);
      if (currentEntries.length === nextEntries.length && nextEntries.every(([header, width]) => currentWidths[header] === width)) {
        return currentWidths;
      }
      return nextWidths;
    });
  };
  useLayoutEffect(() => {
    measureFitColumns();
  }, [headerList, itemsList, columnConfig]);
  useEffect(() => {
    const tableElement = tableRef.current;
    if (!tableElement || typeof ResizeObserver === 'undefined') {
      return undefined;
    }
    const resizeObserver = new ResizeObserver(() => {
      measureFitColumns();
    });
    resizeObserver.observe(tableElement);
    if (tableElement.parentElement) {
      resizeObserver.observe(tableElement.parentElement);
    }
    return () => {
      resizeObserver.disconnect();
    };
  }, [headerList, itemsList, columnConfig]);
  const fitHeaders = headerList.filter(header => columnConfig?.[header]?.fitContent);
  const hasMeasuredFitColumns = fitHeaders.length === 0 || fitHeaders.every(header => Boolean(measuredColumnWidths[header]));
  const getColumnStyle = (header, isMonospace = false) => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const nowrap = Boolean(config.nowrap) || fitContent || isMonospace;
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    return {
      ...fitContent && measuredWidth ? {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      } : {},
      ...!fitContent && !fluid && preferredWidth ? {
        minWidth: preferredWidth
      } : {},
      ...nowrap ? {
        whiteSpace: 'nowrap'
      } : {
        wordWrap: 'break-word',
        overflowWrap: 'break-word'
      }
    };
  };
  const getColumnTrackStyle = header => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    if (fitContent && measuredWidth) {
      return {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      };
    }
    if (fluid) {
      return {};
    }
    if (preferredWidth) {
      return {
        width: preferredWidth
      };
    }
    return {};
  };
  const renderCellContent = (header, content) => {
    const config = columnConfig?.[header] || ({});
    if (!config.fitContent) {
      return content;
    }
    return <div data-docs-fit-content style={{
      display: 'inline-flex',
      alignItems: 'center',
      whiteSpace: 'nowrap',
      width: 'max-content',
      maxWidth: 'none'
    }}>
        {content}
      </div>;
  };
  return <div className={className} style={style} {...rest}>
      {tableTitle && <div style={{
    fontStyle: 'italic',
    margin: 0
  }}>
          <strong>{tableTitle}</strong>
        </div>}
      <div style={{
    overflowX: 'auto',
    ...margin != null && ({
      margin
    })
  }} role="region" tabIndex={0} aria-label={tableTitle ? `Scrollable table: ${tableTitle}` : 'Scrollable table'}>
        <table ref={tableRef} data-docs-dynamic-table-v2 style={{
    width: '100%',
    tableLayout: hasMeasuredFitColumns ? 'fixed' : 'auto',
    borderCollapse: 'collapse',
    fontSize: '0.9rem',
    marginTop: 0
  }}>
          <colgroup>
            {headerList.map((header, index) => <col key={index} style={getColumnTrackStyle(header)} />)}
          </colgroup>
          <thead>
            <tr style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
              {headerList.map((header, index) => <th key={index} data-docs-column-key={index} style={{
    padding: '10px 8px',
    textAlign: 'left',
    fontWeight: '600',
    color: 'var(--lp-color-on-accent)',
    verticalAlign: 'top',
    ...getColumnStyle(header)
  }}>
                  {renderCellContent(header, header)}
                </th>)}
            </tr>
          </thead>
          <tbody>
            {itemsList.filter(item => showSeparators || !item?.__separator).map((item, rowIndex) => item?.__separator ? <tr key={rowIndex} style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-accent)'
  }}>
                    <td colSpan={headerList.length} style={{
    padding: '6px 8px',
    fontWeight: '700',
    color: 'var(--lp-color-on-accent)',
    letterSpacing: '0.01em'
  }}>
                      {(item[headerList[0]] ?? item.Category) ?? 'Category'}
                    </td>
                  </tr> : <tr key={rowIndex} style={{
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
                    {headerList.map((header, colIndex) => {
    const value = (item[header] ?? item[header.toLowerCase()]) ?? '-';
    const isMonospace = monospaceColumns.includes(colIndex);
    return <td key={colIndex} data-docs-column-key={colIndex} style={{
      padding: '8px 8px',
      fontFamily: isMonospace ? 'monospace' : 'inherit',
      verticalAlign: 'top',
      ...getColumnStyle(header, isMonospace)
    }}>
                          {renderCellContent(header, isMonospace ? <code>{value}</code> : value)}
                        </td>;
  })}
                  </tr>)}
          </tbody>
        </table>
      </div>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const Quote = ({children, className = "", style = {}, ...rest}) => {
  const quoteStyle = {
    fontSize: "1rem",
    textAlign: 'center',
    opacity: 1,
    fontStyle: 'italic',
    color: 'var(--lp-color-accent)',
    border: '1px solid var(--lp-color-border-default)',
    borderRadius: "8px",
    padding: "var(--lp-spacing-4)",
    margin: '1rem 0',
    ...style
  };
  return <blockquote className={className} style={quoteStyle} {...rest}>{children}</blockquote>;
};

export const Subtitle = ({style = {}, text, children, variant = 'default', fontSize = '', fontWeight = '', fontStyle = '', marginTop = '', marginBottom = '', color = '', className = '', ...rest}) => {
  const renderInlineCode = (value, keyPrefix) => {
    return value.split(/(`[^`]+`)/g).map((segment, index) => {
      if (segment.startsWith('`') && segment.endsWith('`')) {
        return <code key={`${keyPrefix}-code-${index}`}>{segment.slice(1, -1)}</code>;
      }
      return segment;
    });
  };
  const renderInlineMarkup = (value, keyPrefix = 'subtitle') => {
    if (typeof value !== 'string') {
      return value;
    }
    return value.split(/(\*\*[\s\S]+?\*\*)/g).map((segment, index) => {
      if (segment.startsWith('**') && segment.endsWith('**')) {
        const inner = segment.slice(2, -2);
        return <strong key={`${keyPrefix}-strong-${index}`}>
            {renderInlineCode(inner, `${keyPrefix}-strong-${index}`)}
          </strong>;
      }
      return renderInlineCode(segment, `${keyPrefix}-${index}`);
    });
  };
  const renderContent = (value, keyPrefix) => {
    if (Array.isArray(value)) {
      return value.map((item, index) => renderContent(item, `${keyPrefix}-${index}`));
    }
    return renderInlineMarkup(value, keyPrefix);
  };
  const variants = {
    default: {
      fontSize: '1rem',
      fontStyle: 'italic',
      color: 'var(--lp-color-accent)',
      marginBottom: 0
    },
    changelog: {
      fontSize: '0.8rem',
      fontStyle: 'normal',
      fontWeight: 700,
      color: 'var(--lp-color-text-primary)',
      marginBottom: 0
    }
  };
  const base = variants[variant] || variants.default;
  return <span className={className} style={{
    ...base,
    ...fontSize ? {
      fontSize
    } : {},
    ...fontWeight ? {
      fontWeight
    } : {},
    ...fontStyle ? {
      fontStyle
    } : {},
    ...marginTop ? {
      marginTop
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...color ? {
      color
    } : {},
    ...style
  }} {...rest}>
      {renderContent(text, 'text')}
      {renderContent(children, 'children')}
    </span>;
};

export const CustomCardTitle = ({icon, title, variant = "card", iconSize, style = {}, className = "", ...rest}) => {
  const variants = {
    card: {
      display: 'flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)",
      marginBottom: "var(--lp-spacing-3)",
      color: 'var(--lp-color-text-primary)',
      fontSize: '1rem',
      fontWeight: 600
    },
    accordion: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)"
    },
    tab: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: '0.4rem',
      fontSize: '0.875rem'
    }
  };
  const sizes = {
    card: 20,
    accordion: 18,
    tab: 14
  };
  const size = iconSize || sizes[variant] || 20;
  const baseStyle = variants[variant] || variants.card;
  return variant === 'card' ? <div className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </div> : <span className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </span>;
};

<Quote>
  The Network is a population of `go-livepeer` nodes coordinating off-chain, anchored to four protocol contracts on Arbitrum One. The Network is observable from outside through the Explorer, the subgraph, and the Network Capabilities API.
</Quote>

<CustomDivider style={{ margin: 0, marginBottom: '-2rem' }} />

## Architectural Layers

<DynamicTableV2
  headerList={['Layer', 'What it is', 'What it answers', 'Rate of change']}
  itemsList={[
{
  Layer: <Subtitle variant="changelog">**Node population**</Subtitle>,
  'What it is': 'The running `go-livepeer` processes operated by Gateways, Orchestrators, and their attached workers',
  'What it answers': 'What nodes are running, what role each plays, how they connect to clients and to each other',
  'Rate of change': 'Continuous (release-cadence weekly)',
},
{
  Layer: <Subtitle variant="changelog">**Off-chain coordination**</Subtitle>,
  'What it is': 'Direct Gateway-to-Orchestrator traffic: capability advertisement, job dispatch, ticket exchange, real-time frame transport',
  'What it answers': 'How nodes find each other, agree on price, exchange work, and pay for it without touching the chain',
  'Rate of change': 'Per-session and per-job',
},
{
  Layer: <Subtitle variant="changelog">**On-chain anchor**</Subtitle>,
  'What it is': 'Four protocol contracts on Arbitrum One that the off-chain layer reads and writes against',
  'What it answers': 'What the Network commits to permanently, where stake and settlement live, what is publicly auditable',
  'Rate of change': 'Per round (~21 hours), per winning ticket',
},
]}
/>

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## Network Topology

The Network is operated by independent participants. Orchestrators run GPU nodes and any workers attached to them. Gateways run routing and payment infrastructure. Both run the same `go-livepeer` binary in different modes. A small operator runs one process; a large operator splits the modes across separate hosts on a private subnet, with the Orchestrator on the network edge.

<ScrollableDiagram title="Network Topology and the Boundary it Anchors To" maxHeight="700px">
  ```mermaid theme={"theme":{"light":"github-light","dark":"dark-plus"}}
  %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#1a1a1a', 'primaryTextColor': '#E0E4E0', 'primaryBorderColor': '#2b9a66', 'lineColor': '#2b9a66', 'secondaryColor': '#0d0d0d', 'tertiaryColor': '#1a1a1a', 'background': '#0d0d0d', 'fontFamily': "Inter, 'Inter Fallback', -apple-system, system-ui" }}}%%
  flowchart TB
      classDef default fill:#1a1a1a,color:#E0E4E0,stroke:#2b9a66,stroke-width:2px
      classDef gw fill:#1a1a1a,color:#E0E4E0,stroke:#3b82f6,stroke-width:2px
      classDef orch fill:#1a1a1a,color:#E0E4E0,stroke:#2b9a66,stroke-width:2px
      classDef chain fill:#1a1a1a,color:#E0E4E0,stroke:#f59e0b,stroke-width:2px
      classDef obs fill:#1a1a1a,color:#E0E4E0,stroke:#a855f7,stroke-width:2px

      Client["Client app"]:::default

      subgraph DemandSide["Demand side"]
          Gateway["Gateway nodes<br/>job intake, routing, payment"]:::gw
      end

      subgraph SupplySide["Supply side"]
          Orch["Orchestrator nodes<br/>capability advertisement, job dispatch"]:::orch
          Workers["Worker processes<br/>transcoders, AI workers"]:::orch
      end

      subgraph OnChain["On-chain anchor (Arbitrum One)"]
          Bond["BondingManager"]:::chain
          Broker["TicketBroker"]:::chain
          Registry["ServiceRegistry"]:::chain
          Minter["Minter"]:::chain
      end

      subgraph Outside["Outside observability"]
          Explorer["Livepeer Explorer"]:::obs
          Subgraph["Arbitrum subgraph"]:::obs
          CapsAPI["Network Capabilities API"]:::obs
      end

      Client -->|job| Gateway
      Gateway -->|job + ticket| Orch
      Orch -->|dispatch| Workers
      Orch -.->|stake, active set| Bond
      Gateway -.->|deposit| Broker
      Orch -.->|redeem| Broker
      Orch -.->|service URIs| Registry
      Bond -.->|round rewards| Minter

      Explorer -.-> Bond
      Explorer -.-> Broker
      Subgraph -.-> Bond
      Subgraph -.-> Registry
      CapsAPI -.-> Orch
  ```
</ScrollableDiagram>

Solid arrows are off-chain traffic at job rate. Dashed arrows from the nodes to the chain are settlement and registration, at lower frequency. Dashed arrows from the observability surfaces are read-only data flows that let outside readers see what the Network is doing without operating any of it.

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## Traffic Planes

Inside the Network, four distinct kinds of traffic move on different transports at different rates. Separating them clarifies what scales, what bottlenecks, and what an outside observer can see.

<DynamicTableV2
  headerList={['Plane', 'What flows', 'Transport', 'Cadence']}
  itemsList={[
{
  Plane: <Subtitle variant="changelog">**Control**</Subtitle>,
  'What flows': 'Capability advertisement, discovery queries, session establishment',
  Transport: 'gRPC and HTTP between Gateway and Orchestrator',
  Cadence: 'Per-session (seconds to minutes)',
},
{
  Plane: <Subtitle variant="changelog">**Data**</Subtitle>,
  'What flows': 'Video segments, AI inference inputs and outputs, real-time frames',
  Transport: 'HTTP for batch jobs; trickle for real-time AI; RTMP/WHIP for live ingest',
  Cadence: 'Per-segment or per-frame (sub-second to seconds)',
},
{
  Plane: <Subtitle variant="changelog">**Payment**</Subtitle>,
  'What flows': 'Probabilistic micropayment tickets, winning ticket redemptions',
  Transport: 'Off-chain ticket exchange in HTTP headers; on-chain redemption to `TicketBroker`',
  Cadence: 'Per-segment off-chain; per-winning-ticket on-chain',
},
{
  Plane: <Subtitle variant="changelog">**Observability**</Subtitle>,
  'What flows': 'Round state, stake changes, ticket events, service URI updates, governance votes',
  Transport: 'Subgraph indexer, Explorer front-end, leaderboard API',
  Cadence: 'Continuous read; updates per round and per on-chain event',
},
]}
/>

The control and data planes are sized for production volume. The payment plane uses probabilistic settlement so the on-chain layer never sees per-segment traffic. The observability plane is read-only and decoupled from job execution.

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## Round Lifecycle

The on-chain layer advances in **rounds** of approximately 21 hours each (\~5,760 Arbitrum L2 blocks). The Active Set, which Orchestrators are eligible for video work, locks at the start of each round. Per-round inflation rewards distribute through `BondingManager`. The round is the cadence at which the protocol meets the Network.

<ScrollableDiagram title="Round Lifecycle" maxHeight="400px">
  ```mermaid theme={"theme":{"light":"github-light","dark":"dark-plus"}}
  %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#1a1a1a', 'primaryTextColor': '#E0E4E0', 'primaryBorderColor': '#2b9a66', 'lineColor': '#2b9a66', 'secondaryColor': '#0d0d0d', 'tertiaryColor': '#1a1a1a', 'background': '#0d0d0d', 'fontFamily': "Inter, 'Inter Fallback', -apple-system, system-ui" }}}%%
  stateDiagram-v2
    [*] --> NewRound
    NewRound --> Initializing: anyone calls initializeRound
    Initializing --> Active: active set locked, reward eligibility set
    Active --> RewardCalls: orchestrators call reward() during round
    RewardCalls --> Active
    Active --> Closing: round end approaches
    Closing --> NewRound: next round begins
    Active --> [*]: orchestrator unbonds, exits active set
  ```
</ScrollableDiagram>

Mid-round, capability advertisement and pricing can change. Eligibility cannot. An Orchestrator that wants to enter the Active Set bonds during a round; eligibility takes effect at the next round boundary.

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## Off-Chain Coordination

The off-chain layer is what the Network is sized for. Discovery, capability advertisement, price agreement, job dispatch, and ticket exchange all happen here, between Gateway and Orchestrator, over standard transports. The chain stays out of the hot path.

Four communication patterns run continuously across the off-chain layer:

<DynamicTableV2
  headerList={['Pattern', 'Who participates', 'What flows']}
  itemsList={[
{
  Pattern: <Subtitle variant="changelog">**Capability advertisement**</Subtitle>,
  'Who participates': 'Orchestrator publishes; Gateways read',
  'What flows': '`OrchestratorInfo` messages: capability set, price-per-unit, ticket parameters',
},
{
  Pattern: <Subtitle variant="changelog">**Discovery and selection**</Subtitle>,
  'Who participates': 'Gateway queries; protocol contracts and Orchestrators respond',
  'What flows': 'Candidate lists from `ServiceRegistry` (often via subgraph), then per-candidate `OrchestratorInfo`',
},
{
  Pattern: <Subtitle variant="changelog">**Job dispatch and result return**</Subtitle>,
  'Who participates': 'Gateway and Orchestrator',
  'What flows': 'Segments and frames in; transcoded or generated output back; tickets attached to each segment',
},
{
  Pattern: <Subtitle variant="changelog">**Real-time frame transport**</Subtitle>,
  'Who participates': 'Gateway and Orchestrator AI worker',
  'What flows': 'Live video frames over the trickle protocol for sub-second AI pipelines',
},
]}
/>

<Tip>
  The Active Set, which Orchestrators are eligible for video work, locks at the start of each round. Capability advertisement and pricing can change mid-round; eligibility cannot.
</Tip>

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## On-Chain Anchor Contracts

The Network reads from and writes to four contracts on Arbitrum One. These are the surfaces the off-chain layer relies on for ground truth.

<DynamicTableV2
  headerList={['Contract', 'What the Network uses it for']}
  itemsList={[
{
  Contract: <Subtitle variant="changelog">**`BondingManager`**</Subtitle>,
  'What the Network uses it for': 'Source of truth for Orchestrator stake, active-set membership, and reward-cut configuration. Read by Gateways during selection; written by Orchestrators when calling `reward` each round.',
},
{
  Contract: <Subtitle variant="changelog">**`TicketBroker`**</Subtitle>,
  'What the Network uses it for': 'Holds Gateway deposits and reserves. Receives winning ticket redemptions. Settles ETH from the Gateway side to the Orchestrator side.',
},
{
  Contract: <Subtitle variant="changelog">**`ServiceRegistry`**</Subtitle>,
  'What the Network uses it for': 'Holds the service URI each Orchestrator publishes. Read by Gateways during discovery to know where each Orchestrator is reachable.',
},
{
  Contract: <Subtitle variant="changelog">**`Minter`**</Subtitle>,
  'What the Network uses it for': 'Mints LPT inflation per round. Distributed to bonded stake when Orchestrators call `reward`. The reward stream that runs in parallel to job-fee settlement.',
},
]}
/>

<Card title={<CustomCardTitle icon="file-code" title="Blockchain Contracts" />} href="/v2/about/protocol/blockchain-contracts" horizontal arrow> Full contract addresses, ABIs, and reference. </Card>

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## External Observability

The Network is observable from outside through three surfaces. Researchers, evaluators, and integrators do not need to operate any node to see what the Network is doing.

<DynamicTableV2
  headerList={['Surface', 'What it exposes', 'Who uses it']}
  itemsList={[
{
  Surface: <Subtitle variant="changelog">**Livepeer Explorer**</Subtitle>,
  'What it exposes': 'Active Orchestrators, bonded supply, Active Set, round state, treasury balance, governance proposals',
  'Who uses it': 'Delegators choosing Orchestrators; researchers citing network state; operators monitoring round status',
},
{
  Surface: <Subtitle variant="changelog">**Arbitrum subgraph**</Subtitle>,
  'What it exposes': 'Indexed view of all on-chain events: stake changes, ticket redemptions, reward calls, governance votes',
  'Who uses it': 'Gateways during discovery; data products and dashboards; researchers running historical queries',
},
{
  Surface: <Subtitle variant="changelog">**Network Capabilities API**</Subtitle>,
  'What it exposes': 'Aggregated Orchestrator capability data across the network: which AI pipelines, which transcoding profiles, which BYOC containers',
  'Who uses it': 'Gateways needing capability filtering richer than `ServiceRegistry` alone; integrators evaluating coverage',
},
]}
/>

The three surfaces share a property: they let an outside reader build a real picture of the Network's state without trusting any single operator. Each surface reads ground truth from the chain or from operator-published data, not from a central authority.

<Card title={<CustomCardTitle icon="chart-line" title="Observability" />} href="/v2/about/network/observability" horizontal arrow> How to read the Network state from outside. </Card>

<CustomDivider />

## Related Pages

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="circle-nodes" title="Network Design" />} href="/v2/about/network/design" horizontal arrow>
    Purpose, properties, actors.
  </Card>

  <Card title={<CustomCardTitle icon="store" title="Marketplace Model" />} href="/v2/about/network/marketplace-model" horizontal arrow>
    Market shape, settlement, verification.
  </Card>

  <Card title={<CustomCardTitle icon="plug" title="Network Interfaces" />} href="/v2/about/network/interfaces" horizontal arrow>
    Reachable surfaces and entry points.
  </Card>

  <Card title={<CustomCardTitle icon="diagram-project" title="Infrastructure Stack" />} href="/v2/about/concepts/livepeer-stack" horizontal arrow>
    The four-layer Livepeer stack.
  </Card>
</Columns>
