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

# How the Livepeer Protocol Functions

> A walk-through of how the Livepeer Protocol functions and the design decisions behind each mechanism - delegated proof-of-stake, probabilistic micropayments, on-chain governance, and the protocol-network boundary.

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 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 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 Livepeer Protocol is a set of rules, not a service. It coordinates an open marketplace for video and AI compute by aligning incentives, settling payments, and securing honest participation - then leaves the actual work to the off-chain Network.
</Quote>

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

## What the protocol is responsible for

The protocol does four things, and only four things. Understanding the boundary is the first step in understanding the design.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Coordinate participation" icon="building-columns">
    Define the rules under which Orchestrators, Delegators, and Gateways enter the network, take on responsibility, and exit.
  </StyledStep>

  <StyledStep title="Secure honest behaviour" icon="shield-halved">
    Hold economic stake against operators in a way that makes dishonest action more costly than honest work.
  </StyledStep>

  <StyledStep title="Settle payments" icon="ticket">
    Move ETH from Gateways to Orchestrators reliably, at low overhead, for work performed off-chain at production volume.
  </StyledStep>

  <StyledStep title="Govern openly" icon="scale-balanced">
    Allow LPT holders to propose and vote on protocol upgrades, parameter changes, and treasury allocations through on-chain voting.
  </StyledStep>
</StyledSteps>

The protocol does not transcode video. It does not run AI inference. It does not select which Orchestrator handles a given job. Those are the Network's responsibilities. The protocol's role is to make those activities legible, payable, and accountable.

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

## Five foundational design decisions

Each mechanism in the protocol resolves a specific tension. The decisions below are the ones that shape every downstream behaviour.

### 1. Delegated proof-of-stake, not proof-of-work

The protocol needed a way to admit a permissionless pool of GPU operators while still putting economic security behind their behaviour. Proof-of-work would have priced out small operators and added energy overhead with no security benefit for media work. A non-delegated stake model would have concentrated participation among capital holders who do not run hardware.

Delegated proof-of-stake separates the two roles. Orchestrators bring hardware and operational expertise. Delegators bring stake. Both share in rewards, both share in slashing risk, and both have a vote. The Active Set is sorted by total bonded stake, which couples capital weight to selection probability without requiring Delegators to operate nodes.

### 2. Off-chain execution with on-chain anchoring

A fully on-chain protocol would have to settle every video segment and every inference call as a transaction. At production volume that is impossible. A fully off-chain protocol would lose the ability to enforce honesty without a trusted intermediary.

Livepeer's protocol resolves this by encoding only the rules and the settlement points on-chain. Orchestrator selection, capability discovery, and per-segment delivery happen off-chain across the Network. The chain holds the Active Set, the bonded stake, the round timer, and the payment infrastructure. Off-chain participants compete on a stable, agreed-upon view of who is eligible to do work and who has been paid for it.

### 3. Probabilistic Micropayments over per-job billing

Per-job on-chain billing would consume more gas than the work itself for sub-cent video segments. Off-chain accumulation with periodic settlement would create credit risk between Gateways and Orchestrators.

The [TicketBroker contract](/v2/about/protocol/blockchain-contracts#TicketBroker) implements Probabilistic Micropayments: Gateways issue off-chain tickets to Orchestrators with each job, only a small fraction of which are winning tickets that settle on-chain. Expected value matches the work performed. Gas cost amortises across thousands of jobs. The settlement guarantee remains as strong as a direct on-chain transfer because every ticket is cryptographically committed and either redeemable or losing - never disputed.

### 4. On-chain governance, not foundation control

A foundation-led model would have moved faster but concentrated control. A purely off-chain governance model would have let participants disagree about which version of the rules was canonical.

Livepeer encoded governance directly into the protocol. Any LPT holder with at least 100 LPT can submit a Livepeer Improvement Proposal (LIP) on-chain. Voting is stake-weighted across all bonded LPT, with Delegators voting through their Orchestrator by default and able to override on any specific proposal. A proposal passes only if at least 33% of bonded stake participates and a majority of cast votes are in favour. Approved proposals execute through the [Governor contract](https://github.com/livepeer/protocol/blob/e8b6243c/contracts/governance/Governor.sol) without a separate execution layer.

### 5. Arbitrum One, not Ethereum mainnet

The protocol originally deployed on Ethereum mainnet. As gas costs rose, the per-round work of inflation accounting, ticket redemption, and round progression became a structural drag on participation - particularly for smaller Orchestrators and for high-volume Gateway settlement.

The [Confluence upgrade](https://livepeer.org/blog/confluence-upgrade) (LIP-73) migrated the active protocol to Arbitrum One while keeping the LPT token contract on Ethereum, with paired LPTGateway contracts handling the bridge. Arbitrum gives the protocol low fees and Ethereum-anchored security in a single move, and removes gas-cost as a barrier to running an Orchestrator or operating a Gateway.

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

## Where each decision lives in the contracts

The five decisions above map to specific contracts. Reading the code alongside the design notes is the most direct route to understanding the protocol.

<DynamicTableV2
  headerList={["Decision", "Primary contract", "Function"]}
  itemsList={[
{ "Decision": "Delegated proof-of-stake", "Primary contract": "BondingManager", "Function": "Active set ranking, bond/unbond/rebond, reward distribution" },
{ "Decision": "Round-based execution", "Primary contract": "RoundsManager", "Function": "Round progression, eligibility checkpoints" },
{ "Decision": "Probabilistic micropayments", "Primary contract": "TicketBroker", "Function": "Deposit and reserve management, winning ticket redemption" },
{ "Decision": "Inflation and rewards", "Primary contract": "Minter", "Function": "Per-round LPT issuance, inflation rate adjustment" },
{ "Decision": "On-chain governance", "Primary contract": "LivepeerGovernor + Governor", "Function": "Proposal submission, voting, execution" },
{ "Decision": "Token economics", "Primary contract": "LivepeerToken", "Function": "ERC-20 with bridge to Arbitrum One" },
]}
/>

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

## What the protocol intentionally does not do

A surprising amount of design discipline is in what the protocol leaves out.

* **It does not pick Orchestrators for a job.** Gateways do, off-chain, using the Active Set as a starting point and applying their own price, latency, and reliability criteria.
* **It does not verify transcoding output.** The slashing function exists in the BondingManager, but the Verifier role is currently set to the null address, leaving slashing inoperative until governance reactivates it. Verification is presently economic: stake at risk, plus reputation-driven Gateway selection.
* **It does not coordinate AI inference work.** AI workloads use the same actor roles and economic primitives, but their job dispatch and capability advertisement happen entirely off-chain.
* **It does not store media.** All video and inference data flows directly between Gateway and Orchestrator. The chain holds only the right to be paid and the obligation to behave honestly.

Each omission is deliberate. The protocol is the smallest set of rules that makes the marketplace possible. The Network does the work.

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

## Where to go next

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="diagram-project" title="Protocol Design Reference" />} href="/v2/about/protocol/design" horizontal arrow>
    The canonical reference: protocol purpose, design, and implementation.
  </Card>

  <Card title={<CustomCardTitle icon="cogs" title="Core Mechanisms" />} href="/v2/about/protocol/mechanisms" horizontal arrow>
    Rounds, staking, rewards, payments, upgrades - each protocol primitive in detail.
  </Card>

  <Card title={<CustomCardTitle icon="circle-nodes" title="Network Design" />} href="/v2/about/network/design" horizontal arrow>
    Where the protocol's rules end and the off-chain marketplace begins.
  </Card>

  <Card title={<CustomCardTitle icon="file-code" title="Blockchain Contracts" />} href="/v2/about/protocol/blockchain-contracts" horizontal arrow>
    Every contract, every function. BondingManager, TicketBroker, Minter, and the rest.
  </Card>
</Columns>
