> ## 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.

# Livepeer Protocol Core Mechanisms

> How the Livepeer protocol coordinates work, secures the network, and distributes value: rounds, staking, rewards, probabilistic micropayments, and the architectural split between video and AI pipelines.

export const ValueResponseField = ({description, post = null, label = "value", line = true, children, className = "", style = {}, ...props}) => {
  const hasDescription = typeof description === "function" || description != null || children != null;
  if (!hasDescription) {
    console.warn("[ValueResponseField] Missing required prop: description");
    return null;
  }
  const value = post ? [<span>
          <span style={{
    color: "gray"
  }}>{label}: </span>
          <span style={{
    color: "var(--lp-color-response-field-value)"
  }}>{post[0]}</span>
        </span>] : null;
  return <div className={[!line ? "vrf-noline" : "", className].filter(Boolean).join(" ") || undefined} style={style}>
      <style>{`
        .vrf-noline > .field {
          border-bottom: none;
          margin-bottom: -0.5rem;
          padding: 0;
        }
      `}</style>
      <ResponseField {...props} post={value}>
        {typeof description === "function" ? description() : description}
        {children}
      </ResponseField>
    </div>;
};

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>;
};

export const CardTitleTextWithArrow = ({children, className = '', style = {}, ...cardProps}) => {
  return <div className={className} style={{
    display: 'flex',
    width: 'fit-content',
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: '-1rem',
    ...style
  }}>
      <Card arrow={false} title={<span style={{
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  }}>
            {' '}
            {children}{' '}
            <span style={{
    margin: '0 -1rem 0.2rem 0.75rem'
  }}>
              <Icon icon="arrow-up-right" size={16} color="var(--lp-color-text-secondary)" />
            </span>
          </span>} {...cardProps} />
    </div>;
};

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

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 StyledStep = ({title, icon, titleSize = 'h3', iconColor = null, titleColor = null, children, className = '', style = {}, ...rest}) => {
  const styledTitle = titleColor ? <span style={{
    color: titleColor
  }}>{title}</span> : title;
  return <Step title={styledTitle} icon={icon} iconColor={iconColor || undefined} titleSize={titleSize} className={className} style={style} {...rest}>
      {children}
    </Step>;
};

export const StyledSteps = ({children, iconColor, titleColor, lineColor, iconSize = '24px', className = '', style = {}, ...rest}) => {
  const resolvedIconColor = iconColor || 'var(--accent-dark, #18794E)';
  const resolvedTitleColor = titleColor || 'var(--lp-color-accent)';
  const resolvedLineColor = lineColor || 'var(--lp-color-accent)';
  return <div className={['docs-styled-steps', className].filter(Boolean).join(' ')} style={style} {...rest}>
      <style>{`
        .docs-styled-steps .steps > div > div.absolute > div {
          background-color: ${resolvedIconColor};
        }
        .docs-styled-steps .steps > div > div.w-full > p {
          color: ${resolvedTitleColor};
        }
        .docs-styled-steps .steps > div > div.absolute.w-px {
          background-color: ${resolvedLineColor};
        }
        .docs-styled-steps .steps > div:last-child > div.absolute.w-px::after {
          content: '';
          position: absolute;
          bottom: 0;
          left: 50%;
          transform: translateX(-50%);
          width: 6px;
          height: 6px;
          background-color: ${resolvedLineColor};
          transform: translateX(-50%) rotate(45deg);
        }
      `}</style>
      <div>
        <Steps>{children}</Steps>
      </div>
    </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 FrameQuote = ({children, author, source, href, frame = true, align = 'right', borderColor, img, spacing = true, className = "", style = {}, ...props}) => {
  const alignmentMap = {
    left: 'flex-start',
    center: 'center',
    right: 'flex-end'
  };
  const content = <blockquote style={{
    display: 'flex',
    flexDirection: 'column',
    padding: '0.75rem 1rem 0.25rem 1rem',
    gap: "var(--lp-spacing-1)",
    margin: 0
  }}>
      <div style={{
    borderLeft: `4px solid var(--lp-color-accent)`,
    paddingLeft: "var(--lp-spacing-4)",
    fontStyle: 'italic'
  }}>
        {children}
      </div>
      {(author || source) && <div style={{
    display: 'flex',
    justifyContent: alignmentMap[align] || 'flex-end',
    marginLeft: align === 'left' ? "var(--lp-spacing-6)" : 0
  }}>
          <div style={{
    textAlign: align === 'center' ? 'center' : 'left'
  }}>
            {author && <div>
                {spacing && <br />}
                <Icon icon="microphone" />{' '}
                <strong>
                  <em>{author}</em>
                </strong>
              </div>}
            {source && (href ? <a href={href} target="_blank" rel="noopener noreferrer">
                  <span style={{
    opacity: 0.7,
    fontStyle: 'italic',
    borderBottom: '1px solid var(--lp-color-accent)',
    fontSize: "1rem"
  }}>
                    {source}
                  </span>{' '}
                  <Icon icon="arrow-up-right" size={12} color="var(--lp-color-accent)" />
                </a> : <span style={{
    opacity: 0.7,
    fontStyle: 'italic',
    fontSize: "1rem"
  }}>
                  {source}
                </span>)}
          </div>
        </div>}
    </blockquote>;
  return frame ? <div className={className} style={{
    border: borderColor ? `1px solid ${borderColor}` : 'none',
    borderRadius: "8px",
    overflow: 'hidden',
    ...style
  }} {...props}>
      <Frame style={{
    border: 'none'
  }}>
        {img && <img src={img.src} alt={img.alt} />}
        {content}
      </Frame>
    </div> : content;
};

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 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>;
};

<CenteredContainer style={{ width: '90%' }}>
  <Tip>The protocol coordinates work and secures the network through five mechanisms: rounds, staking and delegation, rewards, Probabilistic Micropayments, and upgradeability. Video and AI workloads share the same payment rail but follow different incentive paths.</Tip>
</CenteredContainer>

The Livepeer Protocol secures a decentralised marketplace for video and AI compute through a tightly coupled set of smart contracts on Arbitrum One. These contracts encode five mechanisms that together turn LPT into work, ETH into operator revenue, and stake into a security guarantee.

This page explains each mechanism, names the contract that implements it, and surfaces the one architectural choice that catches most readers off guard: video and AI pipelines have the same network footprint but a different relationship with the protocol.

<CustomDivider />

## What the mechanisms do

Each mechanism exists to solve a specific coordination problem. Naming the problem first makes the design legible.

<DynamicTableV2
  headerList={["Problem", "Mechanism", "Implementation"]}
  itemsList={[
{ "Problem": "Coordinate global state changes across an open set of operators", "Mechanism": "Rounds", "Implementation": "RoundsManager" },
{ "Problem": "Allocate work to operators who have skin in the game", "Mechanism": "Staking and delegation", "Implementation": "BondingManager" },
{ "Problem": "Pay for security and participation without external subsidy", "Mechanism": "Rewards and inflation", "Implementation": "Minter, BondingManager" },
{ "Problem": "Settle high-frequency, low-value-per-job payments without per-job gas cost", "Mechanism": "Probabilistic micropayments", "Implementation": "TicketBroker" },
{ "Problem": "Evolve the protocol without forcing operators to migrate addresses", "Mechanism": "Proxy-based upgradeability", "Implementation": "Controller, Governor" },
]}
/>

The four economic mechanisms (rounds, staking, rewards, payments) coordinate how operators are selected, paid, and held accountable. The fifth (upgradeability) makes the first four governable. For the contracts behind each mechanism, see <LinkArrow href="/v2/about/protocol/blockchain-contracts" label="Blockchain Contracts" newline={false} />.

<CustomDivider />

## Video and AI take different paths

The single most important architectural fact about the protocol is that video transcoding and AI inference share the same network and the same payment rail, but they do not share the same incentive layer. Reading the rest of this page without that distinction leads to wrong conclusions.

<FrameQuote>
  The video transcoding system and AI system are architecturally separate and operate independently.
</FrameQuote>

Video Orchestrators stake LPT, compete for the Active Set, call `reward()` to mint inflationary LPT, and distribute earnings to Delegators. AI services are a directory of capability endpoints. They use the same Gateway-to-Orchestrator routing and the same TicketBroker payment flow, but they do not participate in the staking pool, the Active Set, or the inflation rewards. This is by design: the AI subnet was launched without imposing the staking gate, so that AI capacity could grow on the network's economic merit before being absorbed into the full economic security model.

<ScrollableDiagram title="Video vs AI mechanism paths" maxHeight="700px">
  ```mermaid theme={"theme":{"light":"github-light","dark":"dark-plus"}}
  %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#18794E', 'primaryTextColor': '#fff', 'primaryBorderColor': '#3CB540', 'lineColor': '#3CB540', 'mainBkg': '#18794E', 'nodeBorder': '#3CB540', 'clusterBkg': 'transparent', 'clusterBorder': '#3CB540', 'titleColor': '#3CB540', 'edgeLabelBackground': 'transparent', 'textColor': '#3CB540', 'nodeTextColor': '#fff'}}}%%
  flowchart TB
      classDef shared fill:#18794E,stroke:#3CB540,stroke-width:2px,color:#fff
      classDef video fill:#1a1a1a,stroke:#3b82f6,stroke-width:2px,color:#fff
      classDef ai fill:#1a1a1a,stroke:#a855f7,stroke-width:2px,color:#fff

      Gateway["Gateway<br/>routes job"]:::shared
      TicketBroker["TicketBroker<br/>ETH micropayments"]:::shared

      subgraph Video["Video pipeline"]
          BM["BondingManager<br/>active set + staking"]:::video
          Minter["Minter<br/>LPT inflation"]:::video
          SR["ServiceRegistry<br/>via Controller"]:::video
          Reward["reward() each round<br/>per active orchestrator"]:::video
      end

      subgraph AI["AI pipeline"]
          AISR["AIServiceRegistry<br/>standalone, hardcoded"]:::ai
          AICap["Capability advertisement<br/>off-chain"]:::ai
          NoInfl["No inflation rewards"]:::ai
          NoStake["No LPT staking required"]:::ai
      end

      Gateway -->|"video segment"| SR
      Gateway -->|"AI inference request"| AISR
      Gateway -->|"signed ticket"| TicketBroker

      BM --> Minter
      Minter --> Reward

      AISR --> AICap

      TicketBroker -->|"ETH on winning ticket"| Video
      TicketBroker -->|"ETH on winning ticket"| AI
  ```
</ScrollableDiagram>

The settlement asset (ETH via probabilistic tickets) and the Orchestrator software are common to both pipelines. The staking, Active Set election, and inflation rewards are video-only.

<DynamicTableV2
  tableTitle="Mechanism comparison: video vs AI"
  headerList={["Mechanism", "Video orchestrators", "AI services"]}
  itemsList={[
{ "Mechanism": "LPT staking required", "Video orchestrators": "Yes - bond LPT to enter the transcoder pool", "AI services": "No - capability registration only" },
{ "Mechanism": "Active set election", "Video orchestrators": "Stake-weighted, top of sorted pool, gates round rewards", "AI services": "No active set; any registered orchestrator can serve" },
{ "Mechanism": "Inflationary LPT rewards", "Video orchestrators": "Yes - distributed via reward() each round", "AI services": "None at the protocol layer" },
{ "Mechanism": "ETH job fees via TicketBroker", "Video orchestrators": "Yes", "AI services": "Yes" },
{ "Mechanism": "Service discovery contract", "Video orchestrators": "ServiceRegistry, registered via Controller", "AI services": "AIServiceRegistry, address hardcoded in go-livepeer" },
{ "Mechanism": "Governance voting weight", "Video orchestrators": "Bonded stake confers voting power on LivepeerGovernor", "AI services": "None inherent; voting power follows bonded LPT only" },
]}
/>

The asymmetry is intentional. Forcing AI Orchestrators to acquire and stake LPT before serving inference would have throttled the supply side of a market that did not yet have demand-side product-market fit. Once AI demand stabilises, the question of whether and how to bring AI services under the same staking model is itself a governance question and would be settled by a future LIP.

<CustomDivider />

## Mechanism details

Each mechanism is described in three layers: what it coordinates, the contract functions that implement it, and the parameters operators or developers most commonly interact with.

<BorderedBox variant="accent">
  <Tabs>
    <Tab title="Rounds" icon="clock">
      ### Rounds and timing

      Rounds are the protocol's clock. Every reward distribution, parameter update, and Active Set election is gated to round boundaries. Without a round boundary, every contract would need its own time accounting; with one, the protocol can synchronise state changes across actors who never communicate directly.

      A round is a fixed number of Arbitrum blocks. The current round must be initialised by an on-chain transaction before reward calls and parameter changes for that round can proceed. {/* REVIEW: confirm current Arbitrum round duration. LIP-83 adjusted roundLength for Ethereum Merge; the post-Confluence value on Arbitrum One is approximately one day. */}

      **What rounds gate**

      * Bonding and unbonding state changes take effect in the next round, not the current one
      * Reward distribution is calculated per round based on active stake snapshotted at round start
      * Parameter updates by Orchestrators are blocked during the round-end lock period to prevent gambling timing
      * Earnings can only be claimed for rounds that have been initialised
      * A given Orchestrator can call `reward()` at most once per round

      **Implementation**

      The `RoundsManager` contract holds round length, current round number, initialisation status, and per-round block hashes used as randomness for ticket validation.

      <AccordionGroup>
        <Accordion title="Round length and initialisation" icon="clock">
          The `roundLength` state variable stores the round length in Arbitrum blocks. It is updated via `setRoundLength()`, callable only by the Controller owner (the Governor).

          <ValueResponseField name="initializeRound" label="function" post={["initializeRound()"]}>
            * Public function callable by any address; typically triggered by Orchestrators at the start of each round
            * Stores the round's block hash, snapshots active stake, unlocks mintable rewards
            * Reverts if the system is paused or the previous round has not yet completed
            * Idempotent: callable only once per round
          </ValueResponseField>
        </Accordion>

        <Accordion title="Round lock period" icon="lock">
          The lock period is a configurable percentage of the round length. During the lock period, Orchestrator parameter updates (`rewardCut`, `feeShare`) are blocked. This prevents an Orchestrator from changing its commission rates between the moment Delegators choose to bond and the moment rewards are calculated.
        </Accordion>
      </AccordionGroup>
    </Tab>

    <Tab title="Staking" icon="lock">
      ### Staking and delegation

      Staking is how the protocol turns LPT into the right to perform work. An Orchestrator without staked LPT cannot enter the Active Set; an Orchestrator without delegated LPT typically cannot stay in it. Bonding is the same operation for both: the difference is whether you bond to your own address (self-stake) or to someone else's (delegation).

      The Active Set is a fixed-size, stake-sorted pool of Orchestrators eligible to receive video jobs and call `reward()` each round. Position in the pool is the price-discovery mechanism: capital flows toward operators who attract delegations, which attracts more work, which attracts more delegations.

      **Mechanism summary**

      * Orchestrators bond LPT to their own address to register; total bonded stake (self plus delegated) determines Active Set position
      * Delegators bond LPT to an Orchestrator of their choice and share in that Orchestrator's rewards and fees, less the Orchestrator's commission
      * Both Delegators and Orchestrators inherit voting power on the LivepeerGovernor in proportion to bonded stake; Delegators may override their Orchestrator's vote on any individual proposal
      * Unbonding triggers a waiting period (`unbondingPeriod` rounds) before stake can be withdrawn, preventing instantaneous flight from a slashed or failing operator

      **Key functions** (BondingManager)

      <DynamicTableV2
        headerList={["Function", "Caller", "Effect"]}
        itemsList={[
{ "Function": "bond(amount, to)", "Caller": "Anyone holding LPT", "Effect": "Delegate LPT to an orchestrator address (or self)" },
{ "Function": "transcoder(rewardCut, feeShare)", "Caller": "Self-bonded address", "Effect": "Register as orchestrator; set commission and delegator share" },
{ "Function": "unbond(amount)", "Caller": "Bonded address", "Effect": "Initiate withdrawal; creates an unbonding lock" },
{ "Function": "rebond(unbondingLockId)", "Caller": "Bonded address", "Effect": "Cancel unbonding before period ends" },
{ "Function": "withdrawStake(unbondingLockId)", "Caller": "Bonded address", "Effect": "Withdraw LPT after unbonding period elapses" },
]}
      />

      <Info>
        **Slashing exists in the contract but is not currently active.** `slashTranscoder()` is implemented in BondingManager, but the Verifier role required to call it is set to the null address. Misbehaviour is currently disciplined economically through Delegator flight rather than through automated stake forfeiture. Re-enabling slashing would require a governance proposal to configure a Verifier and update compatibility paths. AI services do not have a slashing mechanism at the protocol layer and would be governed under any future video re-enablement separately. {/* REVIEW: confirm slashing remains disabled and that the Verifier role is still set to 0x000. SME: Rick. */}
      </Info>

      <Tip>**AI services do not stake.** The AIServiceRegistry is a discovery contract, not a bonding pool. AI Orchestrators advertise capabilities and serve jobs; they do not enter the Active Set or earn inflationary LPT.</Tip>
    </Tab>

    <Tab title="Rewards" icon="coins">
      ### Rewards and inflation

      The reward system answers two questions in one mechanism: how much new LPT enters circulation each round, and how that LPT is divided between operators, Delegators, and the treasury. The answers are coupled, because dilution is the cost of paying the security budget.

      **Dynamic inflation**

      Livepeer uses a dynamic inflation rate. New LPT is minted each round in proportion to the participation rate.

      * If less than the target fraction of LPT supply is bonded, inflation rises (more aggressive subsidy to attract stake)
      * If more than the target fraction is bonded, inflation falls (less dilution; security budget is sufficient)
      * Inflation bounds are governed parameters, with proposed ceiling and floor in [LIP-100](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0100.md)

      The mechanic is a feedback loop: the protocol pays exactly enough LPT inflation to keep enough stake bonded to secure the network, and no more.

      **Reward distribution flow**

      Each round, every active Orchestrator can call `reward()` exactly once. The function performs four operations atomically.

      <StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
        <StyledStep title="Mint" icon="circle-plus">
          BondingManager queries `currentMintableTokens()` from Minter, which calculates the round's new LPT based on inflation rate and total supply.
        </StyledStep>

        <StyledStep title="Treasury cut" icon="building-columns">
          `treasuryRewardCutRate` of the new LPT is routed to the Treasury contract before any operator distribution. {/* REVIEW: confirm current treasuryRewardCutRate (LIP-92 initial value: 10%; halts at 750,000 LPT balance ceiling). SME: Rick. */}
        </StyledStep>

        <StyledStep title="Orchestrator commission" icon="user-shield">
          The remaining LPT is allocated proportional to the Orchestrator's stake share of the Active Set. The Orchestrator retains its `rewardCut` percentage as commission.
        </StyledStep>

        <StyledStep title="Delegator pool" icon="users">
          The remainder accrues to Delegators in proportion to their bonded stake. Delegators must call `claimEarnings()` to crystallise the accrued LPT into withdrawable balance.
        </StyledStep>
      </StyledSteps>

      The reward call is the Orchestrator's primary recurring on-chain obligation. An Orchestrator that misses a `reward()` call loses that round's inflation share for itself and its Delegators, which is the operator-side incentive for high uptime in the absence of slashing.

      **ETH fee distribution**

      ETH fees from redeemed winning tickets follow a parallel path. Fees accumulate in the Orchestrator's earnings pool, the Orchestrator retains its configured `feeShare` portion (the share passed to Delegators), and the remainder is Delegator-claimable in the same `claimEarnings()` flow.

      <Note>The naming is a known source of confusion. `feeShare` in the BondingManager is defined as **the percentage of fees paid to Delegators by the Orchestrator**, not the Gateway's cut and not the Orchestrator's retained portion. Source: `BondingManager.sol` struct comment.</Note>
    </Tab>

    <Tab title="Payments" icon="money-bill-transfer">
      ### Probabilistic Micropayments

      Probabilistic Micropayments are the mechanism that makes streaming-rate compute economically viable on a public chain. Without them, gas cost per job would dwarf the price of the job. With them, settlement cost is amortised across thousands of off-chain tickets per on-chain redemption.

      **Mechanism**

      Each ticket is a signed off-chain message with a face value (the payout if the ticket wins) and a win probability (typically a small fraction). Expected value of a ticket equals face value multiplied by win probability, which the Gateway sets to match the contracted price of the work performed. Most tickets do not win and never touch the chain. The few that do are redeemed on-chain by the Orchestrator, which transfers the face value from the Gateway's reserve to the Orchestrator's fee pool.

      <StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
        <StyledStep title="Gateway funds deposit and reserve" icon="wallet">
          Gateway calls `fundDepositAndReserve()` on TicketBroker to lock ETH. Deposit covers active session liquidity; reserve covers redemptions if deposit is exhausted.
        </StyledStep>

        <StyledStep title="Off-chain ticket creation" icon="file-signature">
          For each unit of work, the Gateway signs a ticket with face value, win probability, and recipient parameters. The ticket is sent to the Orchestrator over HTTP, never on-chain.
        </StyledStep>

        <StyledStep title="Ticket validation" icon="shield-check">
          Orchestrator verifies the signature, computes whether the ticket wins using the recipient randomness and the round's stored block hash, and queues winners locally.
        </StyledStep>

        <StyledStep title="On-chain redemption" icon="arrow-up-right-from-square">
          Orchestrator calls `redeemWinningTicket()` on TicketBroker. The contract validates the ticket against on-chain state, transfers ETH to the Orchestrator's fee pool via BondingManager, and emits `WinningTicketRedeemed` and `WinningTicketTransfer` events.
        </StyledStep>
      </StyledSteps>

      **Why this design**

      The probabilistic structure trades away per-payment certainty for amortised settlement cost. Both sides accept the variance because expected value matches the contracted price and the variance reduces with sample size. At streaming volumes, the Orchestrator's realised earnings converge to expected earnings within minutes of work, and the Gateway's realised cost converges to its budgeted cost within the same window.

      **Withdrawal flow**

      A Gateway exiting the network calls `unlock()` on TicketBroker, waits the unlock period, then calls `withdraw()` to recover any unredeemed deposit and reserve. This is the Gateway-side equivalent of the Orchestrator unbonding period: it gives the protocol time to settle outstanding tickets before funds leave.
    </Tab>

    <Tab title="Upgrades" icon="arrow-up-from-bracket">
      ### Upgradeability

      The protocol is designed to evolve. New LIPs alter contract code without forcing operators, Gateways, or Delegators to migrate to new addresses. The mechanism is a standard proxy pattern coordinated through the Controller registry.

      **How upgrades work**

      <StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
        <StyledStep title="LIP authored and discussed" icon="file-pen">
          Author drafts the LIP, gathers community feedback on the [Forum](https://forum.livepeer.org/c/lips/18), and submits the proposal to GitHub.
        </StyledStep>

        <StyledStep title="On-chain proposal" icon="ballot">
          Author with at least 100 LPT submits the proposal to LivepeerGovernor. Voting opens for 30 rounds.
        </StyledStep>

        <StyledStep title="Governor execution" icon="check-to-slot">
          If the proposal passes (33% quorum, more than 50% For), it queues into the Treasury timelock and then to the Governor. Governor calls `setContractInfo()` on the Controller to register the new implementation under the same name hash.
        </StyledStep>

        <StyledStep title="Proxy continuity" icon="arrows-spin">
          Proxy addresses are unchanged. State is preserved. Subsequent calls to the proxy delegate to the new implementation. Operators and applications need no migration.
        </StyledStep>
      </StyledSteps>

      **Why the pattern matters**

      The Controller registry is the single point of indirection that makes the protocol governable without disrupting users. Every contract resolves peer addresses dynamically through the Controller, so a single registry update propagates new behaviour across the system. The trade-off is that the Controller's owner (the Governor) is a privileged role; the protection is that Governor is itself controlled by stake-weighted votes, not by any individual.

      For governance lifecycle and the LivepeerGovernor specification, see <LinkArrow href="/v2/about/protocol/governance-and-treasury" label="Governance and Treasury" newline={false} />.
    </Tab>

    <Tab title="Configuration" icon="sliders">
      ### Network configuration

      The protocol is deployed on Arbitrum One (active) and Ethereum Mainnet (token contract and bridge only). All operational mechanisms run on Arbitrum since the Confluence migration ([LIP-73](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0073.md)) reduced gas costs by approximately 100x while preserving Ethereum's security through the Arbitrum rollup.

      **Governable parameters**

      These are the parameters most often touched by LIPs. Each is owner-only on its contract, where the owner is the Governor and the Governor only acts on a passed vote.

      <DynamicTableV2
        headerList={["Parameter", "Contract", "Effect when changed"]}
        itemsList={[
{ "Parameter": "Active set size", "Contract": "BondingManager", "Effect when changed": "Caps the number of orchestrators eligible for round rewards" },
{ "Parameter": "unbondingPeriod", "Contract": "BondingManager", "Effect when changed": "Delay between unbond() and withdrawStake() being callable" },
{ "Parameter": "roundLength", "Contract": "RoundsManager", "Effect when changed": "Number of Arbitrum blocks per round" },
{ "Parameter": "roundLockAmount", "Contract": "RoundsManager", "Effect when changed": "Lock period at end of each round; blocks parameter updates" },
{ "Parameter": "inflation, inflationChange", "Contract": "Minter", "Effect when changed": "Per-round adjustment of the LPT issuance schedule" },
{ "Parameter": "targetBondingRate", "Contract": "Minter", "Effect when changed": "Bonding rate that inflation auto-adjusts towards" },
{ "Parameter": "treasuryRewardCutRate", "Contract": "BondingManager", "Effect when changed": "Fraction of each round's mint routed to the Treasury" },
{ "Parameter": "treasuryBalanceCeiling", "Contract": "BondingManager", "Effect when changed": "Treasury balance at which contributions automatically pause" },
]}
      />

      For current parameter values and contract addresses, see <LinkArrow href="/v2/about/protocol/blockchain-contracts" label="Blockchain Contracts" newline={false} />.
    </Tab>
  </Tabs>
</BorderedBox>

<CustomDivider />

## How the mechanisms compose

The five mechanisms are not independent. A change to any one of them ripples through the others, which is why governance proposals tend to specify second-order effects explicitly.

<DynamicTableV2
  headerList={["If you change...", "It affects..."]}
  itemsList={[
{ "If you change...": "roundLength", "It affects...": "Reward call frequency, unbonding wall-clock time, governance vote duration in days" },
{ "If you change...": "Active set size", "It affects...": "Stake threshold to receive work, distribution of inflation across operators, decentralisation of the supply side" },
{ "If you change...": "Inflation rate", "It affects...": "Real yield to delegators, opportunity cost of holding unbonded LPT, LPT/USD market dynamics" },
{ "If you change...": "treasuryRewardCutRate", "It affects...": "Public goods funding rate, dilution borne by stakers, time to ceiling at current participation" },
{ "If you change...": "Probabilistic ticket parameters", "It affects...": "Variance in operator earnings, gas cost per ETH paid out, gateway capital efficiency" },
]}
/>

The composition is the point. A unified set of parameters running in lockstep across rounds is what allows operators across jurisdictions, infrastructure stacks, and capital sizes to participate in the same market on the same terms.

<CustomDivider />

## Related pages

<CardGroup cols={2}>
  <Card title="Protocol Design" icon="cube" href="/v2/about/protocol/design" arrow horizontal>
    Why the protocol exists, the actors it coordinates, and the design choices behind the mechanisms on this page.
  </Card>

  <Card title="Blockchain Contracts" icon="file-code" href="/v2/about/protocol/blockchain-contracts" arrow horizontal>
    Every contract referenced here, with addresses, function signatures, and source.
  </Card>

  <Card title="Governance and Treasury" icon="building-columns" href="/v2/about/protocol/governance-and-treasury" arrow horizontal>
    LIP lifecycle, Governor specification, and the treasury that the reward mechanism funds.
  </Card>

  <Card title="Token and Economics" icon="coins" href="/v2/about/protocol/livepeer-token" arrow horizontal>
    LPT supply, bonding maths, and the dual-asset economics that connect the mechanisms above to value flow.
  </Card>

  <Card title="Network Architecture" icon="circle-nodes" href="/v2/about/network/architecture" arrow horizontal>
    How Orchestrator and Gateway nodes implement these mechanisms off-chain.
  </Card>

  <Card title="LIPs Repository" icon="github" href="https://github.com/livepeer/LIPs" arrow horizontal>
    Every parameter on this page is set by a passed LIP. The repository is the canonical record.
  </Card>
</CardGroup>
