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

> What LPT is, how it differs from ETH, and the four roles it plays in the Livepeer protocol - staking, security, governance, and work coordination.

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 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 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>
  The **Livepeer Token (LPT)** is the protocol's *staking and coordination* token. It secures the network, routes work to operators, and grants governance rights. ETH - not LPT - is the medium of exchange for transcoding and AI inference jobs.
</Quote>

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

## Two tokens, two jobs

The Livepeer Protocol uses two distinct assets, each with a job that the other cannot do. ETH pays for work performed off-chain. LPT secures the right to perform that work and to share in protocol rewards.

<DynamicTableV2
  headerList={["Asset", "Role", "Used by", "Earned by"]}
  itemsList={[
{
  "Asset": <Subtitle variant="changelog">**ETH**</Subtitle>,
  "Role": "Medium of exchange for video transcoding and AI inference jobs",
  "Used by": "Gateways - to fund deposits and pay for work",
  "Earned by": "Orchestrators - via probabilistic micropayment tickets",
},
{
  "Asset": <Subtitle variant="changelog">**LPT**</Subtitle>,
  "Role": "Staking, coordination, security, and governance",
  "Used by": "Orchestrators and Delegators - bonded into the protocol",
  "Earned by": "Bonded participants - via inflationary rewards each round",
},
]}
/>

This separation is deliberate. Service buyers should not need to acquire a protocol-specific token to use the network. Operators should have economic skin in the game in a way that scales with how much of the network they want to influence and earn from. ETH satisfies the first requirement; LPT satisfies the second.

<Tip>
  Gateways pay in ETH. Orchestrators are paid in ETH for the work they perform. LPT is never used to pay for jobs.
</Tip>

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

## What LPT is

LPT is an [ERC-20 token](https://github.com/livepeer/protocol/blob/delta/contracts/token/LivepeerToken.sol) deployed on Ethereum Mainnet, with a bridged representation on [Arbitrum One](https://arbitrum.io/) where all active protocol operations occur. It is the staking asset of a delegated proof-of-stake system: holders bond LPT to participate in or support the network, and the amount they bond determines both their share of rewards and their voting weight on protocol decisions.

The initial supply of 10,000,000 LPT was distributed through the [Merkle Mine](https://github.com/livepeer/merkle-mine) at network launch in 2018, a permissionless claim mechanism that allowed any Ethereum address to mine and claim tokens directly. There was no ICO and no pre-mine. Total supply has grown since then through the protocol's inflationary reward schedule, which mints new LPT each round to participants who stake.

<CenteredContainer preset="fitContent" width="90%">
  <BorderedBox variant="accent">
    <Subtitle fontSize="1rem" marginBottom="0.5rem">**Quick reference**</Subtitle>

    * **Token standard**: ERC-20 (with EIP-2612 permit, burnable, role-based mint/burn)
    * **Origin chain**: Ethereum Mainnet
    * **Active chain**: Arbitrum One (bridged via paired LPTGateway contracts)
    * **Initial supply**: 10,000,000 LPT (Merkle Mine, 2018)
    * **Issuance**: Dynamic inflation, per round, only to bonded participants
    * **Divisibility**: 18 decimals, in line with Ethereum and standard ERC-20 tokens
  </BorderedBox>
</CenteredContainer>

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

## What LPT does in the protocol

LPT performs four functions in the Livepeer Protocol. Each function is enforced on-chain through smart contracts on Arbitrum One.

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

  <StyledStep title="Secures the network through skin in the game" icon="shield-halved">
    Bonded LPT is the protocol's economic security primitive. 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 receive video transcoding jobs from the network.
  </StyledStep>

  <StyledStep title="Grants governance rights" icon="building-columns">
    Bonded LPT confers voting power, proportional to stake, 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 (`slashTranscoder()`) 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 reactivates.
</Info>

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

## How value flows through the network

The protocol creates two parallel value flows: an ETH flow that pays for compute work, and an LPT flow that rewards the participants who secure the network. Both flows settle on Arbitrum One.

<ScrollableDiagram title="LPT and ETH Value Flows in the Livepeer Protocol" maxHeight="600px">
  ```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 LR
      classDef default stroke-width:2px

      subgraph ETHFlow["ETH Flow - Payment for Work"]
          Gateway[Gateway]
          TicketBroker[TicketBroker]
          Orchestrator1[Orchestrator]
      end

      subgraph LPTFlow["LPT Flow - Rewards for Stake"]
          Delegator[Delegator]
          BondingManager[BondingManager]
          Minter[Minter]
          Treasury[Treasury]
          Orchestrator2[Orchestrator]
      end

      Gateway -->|"1 - Fund deposit and reserve"| TicketBroker
      Gateway -.->|"2 - Off-chain tickets per job"| Orchestrator1
      Orchestrator1 -->|"3 - Redeem winning tickets"| TicketBroker
      TicketBroker -->|"4 - ETH fees"| Orchestrator1

      Delegator -->|"a - Bond LPT to orchestrator"| BondingManager
      Orchestrator2 -->|"b - Self-bond and call reward each round"| BondingManager
      Minter -->|"c - Mint new LPT"| BondingManager
      BondingManager -->|"d - Reward share"| Orchestrator2
      BondingManager -->|"e - Reward share"| Delegator
      BondingManager -->|"f - Treasury cut"| Treasury
  ```
</ScrollableDiagram>

The ETH flow pays for actual compute. Gateways pre-fund a deposit and a reserve in the [TicketBroker contract](/v2/about/protocol/blockchain-contracts#TicketBroker) and issue off-chain probabilistic micropayment tickets to Orchestrators with each transcoding or inference job. Orchestrators redeem winning tickets on-chain to receive ETH. This amortises payment costs across many jobs without losing the strong settlement guarantees of on-chain payments.

The LPT flow rewards stake. Each round, the [Minter contract](/v2/about/protocol/blockchain-contracts#minter) calculates the round's mintable LPT supply based on the current inflation rate, and the [BondingManager](/v2/about/protocol/blockchain-contracts#BondingManager) distributes those new tokens. Orchestrators that called `reward()` during their active round receive a share proportional to their bonded stake. Delegators receive the remainder of the Orchestrator's reward, after the Orchestrator's commission (`rewardCut`) is taken. A configurable fraction of each round's rewards is also routed to the protocol treasury.

<Tip>
  The two flows meet at the Orchestrator. A working Orchestrator earns ETH from the work it performs and LPT inflation from the stake it bonds. Delegators earn a share of both. The Orchestrator's `feeShare` parameter sets the percentage of ETH fees passed to Delegators. The Orchestrator's `rewardCut` parameter sets the percentage of LPT rewards the Orchestrator keeps as commission, with the remainder distributed to Delegators in proportion to their bonded stake.
</Tip>

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

## Inflation, bonding, and the target rate

Livepeer ties LPT issuance to network participation. Each round, the protocol checks how much of the total LPT supply is bonded and adjusts the next round's inflation rate up or down to nudge the bonded share toward a target.

<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's issuance.
  </StyledStep>
</StyledSteps>

This creates a clear opportunity cost for holding LPT idle. The dilution is not theoretical: every round of inflation that an unbonded holder sits through reduces their share of the total supply. The mechanism design intends for stakeholders to either bond directly, delegate to an Orchestrator they trust, or accept the dilution as the cost of liquidity.

<Note>
  Inflation parameters - the target bonding rate, the per-round inflation step, the minimum and maximum bounds - are set in the Minter contract and adjustable through governance. The exact current values can be read directly from the contract on Arbitrum One. See <LinkArrow href="/v2/about/protocol/blockchain-contracts#minter" label="Minter contract reference" newline={false} /> for the precise function signatures.
</Note>

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

## Bonding, unbonding, and rebonding

LPT participation in the protocol is gated by an unbonding period. This delay is the protocol's protection against rapid stake exit during a slashing event.

<DynamicTableV2
  headerList={["Action", "Function", "Effect"]}
  itemsList={[
{
  "Action": <Subtitle variant="changelog">**Bond**</Subtitle>,
  "Function": "`bond(amount, orchestrator)`",
  "Effect": "LPT moves from holder to BondingManager. Stake counts toward Orchestrator's Active Set position from the next round.",
},
{
  "Action": <Subtitle variant="changelog">**Unbond**</Subtitle>,
  "Function": "`unbond(amount)`",
  "Effect": "Creates an unbonding lock. Stake stops earning rewards. LPT is locked for the unbonding period.",
},
{
  "Action": <Subtitle variant="changelog">**Rebond**</Subtitle>,
  "Function": "`rebond(unbondingLockId)`",
  "Effect": "Cancels an in-progress unbond. Stake returns to active duty without resetting the unbonding period.",
},
{
  "Action": <Subtitle variant="changelog">**Withdraw stake**</Subtitle>,
  "Function": "`withdrawStake(unbondingLockId)`",
  "Effect": "After the unbonding period expires, returns LPT to the holder's wallet.",
},
{
  "Action": <Subtitle variant="changelog">**Claim earnings**</Subtitle>,
  "Function": "`claimEarnings(endRound)`",
  "Effect": "Realises accumulated LPT rewards and ETH fees from past rounds into the holder's claimable balance.",
},
]}
/>

Rounds are the protocol's time unit. On Arbitrum One, a round is configured to approximately 24 hours. State changes triggered by `bond`, `unbond`, or Orchestrator parameter updates take effect at the start of the next round, ensuring all participants compete on a stable view of the Active Set within any given round.

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

## Where to go next

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="cogs" title="Core Mechanisms" />} href="/v2/about/protocol/mechanisms" horizontal arrow>
    Staking, rewards, payments, rounds, and slashing - the protocol primitives in detail.
  </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>

  <Card title={<CustomCardTitle icon="vault" title="Governance and Treasury" />} href="/v2/about/protocol/governance-and-treasury" horizontal arrow>
    On-chain voting, proposal lifecycle, and how LPT inflation funds ecosystem work.
  </Card>

  <Card title={<CustomCardTitle icon="diagram-project" title="Protocol Design" />} href="/v2/about/protocol/design" horizontal arrow>
    Design decisions behind delegated proof-of-stake, Probabilistic Micropayments, and the role of LPT.
  </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>
