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

# Understanding the Livepeer Token

> Why LPT exists as a separate asset from ETH, what it lets a holder do, and how the protocol's payment separation makes the network usable for service buyers without forcing them to acquire a protocol-specific token.

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 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>
  LPT is the Livepeer Protocol's staking and coordination asset. It secures the network, decides which Orchestrators receive work, and gives holders a vote. ETH - not LPT - pays for transcoding and AI inference. The separation is deliberate, and it shapes how the protocol scales.
</Quote>

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

## Why a separate token at all

The protocol could have charged for video and AI work in the same asset that secured the network. Most early protocol designs do exactly that. Livepeer chose payment separation - ETH for work, LPT for stake - because the two roles have different demands.

Service buyers want to pay for a service, not invest in a network. A Gateway operator running an AI video application should not have to acquire, custody, and price-hedge a protocol-specific token to call inference. ETH is the natural unit of payment on Ethereum and on Arbitrum.

Operators want skin in the game proportional to their stake in the network. An Orchestrator earning fees from production traffic should be exposed to the protocol's economic security, not just to the spot price of compute. LPT is the staking unit. It sits between operator and network, mediating selection, governance, and slashing risk.

ETH satisfies the first requirement. LPT satisfies the second. Neither token does both.

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

## The four jobs LPT does

Every function LPT performs is enforced on-chain through the Arbitrum-deployed protocol contracts.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Stakes operators into the network" icon="lock">
    Orchestrators bond LPT to themselves to be eligible for work. The Active Set of Orchestrators that receive video and AI jobs is sorted by total bonded stake - own stake plus delegated stake - from highest to lowest.
  </StyledStep>

  <StyledStep title="Provides economic security" icon="shield-halved">
    Bonded LPT is the protocol's collateral against operator misbehaviour. Slashing rules can burn an Orchestrator's stake - and the stake of those who delegated to it - for protocol violations, giving every participant a financial reason to behave honestly.
  </StyledStep>

  <StyledStep title="Routes work in proportion to stake" icon="arrows-split-up-and-left">
    Stake weight is the coordination signal that tells Gateways which Orchestrators to prefer. Higher-staked Orchestrators sit higher in the Active Set and are more likely to be selected for jobs.
  </StyledStep>

  <StyledStep title="Grants governance rights" icon="building-columns">
    Bonded LPT confers stake-weighted voting power over Livepeer Improvement Proposals (LIPs) and treasury allocations. Delegators vote through their Orchestrator by default, and can override that vote on any specific proposal.
  </StyledStep>
</StyledSteps>

<Info>
  **Slashing today.** The slashing function exists in the BondingManager contract, but the Verifier role is currently set to the null address, leaving slashing inoperative. It can be re-enabled through governance. The economic security model still relies on stake at risk - bonded LPT remains illiquid for the unbonding period, and is exposed to whatever slashing conditions governance later reactivates.
</Info>

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

## Payment separation in practice

The protocol creates two parallel value flows: ETH for compute, LPT for stake. Both flows settle on Arbitrum One. They meet at the Orchestrator.

<DynamicTableV2
  headerList={["Asset", "Who pays / receives", "What it pays for", "When it settles"]}
  itemsList={[
{
  "Asset": <Subtitle variant="changelog">**ETH**</Subtitle>,
  "Who pays / receives": "Gateway pays. Orchestrator receives.",
  "What it pays for": "Per-job compute - transcoding, AI inference, real-time AI",
  "When it settles": "Probabilistic - small fraction of tickets settle on-chain. Expected value matches work.",
},
{
  "Asset": <Subtitle variant="changelog">**LPT**</Subtitle>,
  "Who pays / receives": "Bonded by Orchestrators and Delegators. Earned by both.",
  "What it pays for": "Network security and coordination",
  "When it settles": "Each round (~24 hours on Arbitrum). Distributed by Minter to bonded participants who called reward().",
},
]}
/>

A Gateway operator never has to acquire LPT. A Delegator never has to pay for video work. An Orchestrator participates in both flows: they are paid in ETH for compute, and they earn LPT inflation for the stake they have bonded.

<Tip>
  The Orchestrator's `feeShare` parameter controls how much ETH passes through to Delegators. The Orchestrator's `rewardCut` controls how much LPT inflation the Orchestrator keeps as commission. Both are public, set by the Orchestrator, and visible on the Explorer.
</Tip>

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

## What inflation means for an LPT holder

LPT supply grows each round. Newly minted tokens go only to bonded stake. Unbonded LPT is diluted by every round it sits through.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Bonded share is below target" icon="arrow-trend-up">
    Inflation increases on the next round. Higher rewards attract more bonded stake, which secures the network.
  </StyledStep>

  <StyledStep title="Bonded share is above target" icon="arrow-trend-down">
    Inflation decreases on the next round. Lower issuance limits dilution of bonded holders once security is met.
  </StyledStep>

  <StyledStep title="Only bonded LPT earns rewards" icon="hand-holding-dollar">
    Newly minted LPT is distributed exclusively to bonded stake. Unbonded LPT is diluted by every round of issuance.
  </StyledStep>
</StyledSteps>

The dilution is not theoretical. Every round of inflation that an unbonded holder sits through reduces their share of the total supply. The mechanism intends for stakeholders to either bond directly, delegate to an Orchestrator they trust, or accept the dilution as the cost of liquidity.

A treasury cut, set by [LIP-92](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0092.md), routes 10% of each round's inflation into the on-chain treasury for ecosystem funding. The remaining 90% is distributed to bonded participants in proportion to their stake.

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

## What an LPT holder can actually do

Three actions are available to anyone holding LPT. Each one has a different operational cost and a different earning profile.

<DynamicTableV2
  headerList={["Action", "What it requires", "What it earns", "What it risks"]}
  itemsList={[
{
  "Action": "Run an orchestrator",
  "What it requires": "GPU hardware, public node, operational uptime, self-bond",
  "What it earns": "ETH fees from gateway work, plus a share of LPT inflation",
  "What it risks": "Slashing if reactivated, plus operational and reputational risk",
},
{
  "Action": "Delegate to an orchestrator",
  "What it requires": "An LPT bond and a chosen orchestrator",
  "What it earns": "Share of orchestrator's ETH fees and LPT inflation, less the orchestrator's commission",
  "What it risks": "Slashing if reactivated, exposure to orchestrator's behaviour and uptime",
},
{
  "Action": "Hold unbonded",
  "What it requires": "Nothing",
  "What it earns": "Nothing",
  "What it risks": "Steady dilution from every round of inflation",
},
]}
/>

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

## Where to go next

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="coins" title="Livepeer Token Reference" />} href="/v2/about/protocol/livepeer-token" horizontal arrow>
    The canonical reference: contract details, value flows, bonding mechanics.
  </Card>

  <Card title={<CustomCardTitle icon="vault" title="Governance and Voting" />} href="/v2/about/guides/governance-and-voting" horizontal arrow>
    How LPT-weighted voting works and how to use your voice.
  </Card>

  <Card title={<CustomCardTitle icon="hand-holding-dollar" title="Obtain LPT" />} href="https://www.livepeer.org/lpt" horizontal arrow>
    Where to acquire LPT to delegate, bond, or run an Orchestrator.
  </Card>

  <Card title={<CustomCardTitle icon="chart-line" title="Livepeer Explorer" />} href="https://explorer.livepeer.org/" horizontal arrow>
    Live network data: inflation rate, bonded supply, Orchestrators, and rewards.
  </Card>
</Columns>
