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

# Governance and Economics

> How decisions are made and how value flows through Livepeer.

export const QuadGrid = ({children, icon = "arrows-spin", iconSize = 50, iconColor = "var(--lp-color-accent)", iconBackgroundColor = "transparent", gap, spinDuration = "10s", className = "", style = {}, ...rest}) => {
  if (children == null) {
    console.warn("[QuadGrid] Missing children");
    return null;
  }
  return <div className={className} style={{
    position: "relative",
    ...style
  }} {...rest}>
      <style>{`
        @keyframes quadGridSpin {
          from { transform: rotate(0deg); }
          to { transform: rotate(360deg); }
        }
        .lp-quad-grid-layout {
          display: grid;
          grid-template-columns: repeat(2, minmax(0, 1fr));
          grid-auto-rows: 1fr;
          gap: var(--lp-quad-grid-gap, 0);
        }
        @media (max-width: 768px) {
          .lp-quad-grid-layout {
            grid-template-columns: 1fr;
          }
        }
        @media (prefers-reduced-motion: reduce) {
          .lp-quad-grid-icon {
            animation: none !important;
          }
        }
      `}</style>
      <div className="lp-quad-grid-layout" style={{
    "--lp-quad-grid-gap": gap
  }}>
        {children}
      </div>
      <div style={{
    position: "absolute",
    top: "50%",
    left: "50%",
    transform: "translate(-50%, -50%)",
    zIndex: 10
  }}>
        <div className="lp-quad-grid-icon" style={{
    backgroundColor: iconBackgroundColor,
    borderRadius: "50%",
    padding: "var(--lp-spacing-2)",
    animation: `quadGridSpin ${spinDuration} linear infinite`
  }}>
          <Icon icon={icon} size={iconSize} color={iconColor} />
        </div>
      </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 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 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 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 Image = ({src, alt = "", caption, icon, hint, fullwidth = true, className = "", style = {}, ...rest}) => {
  icon = icon ? icon : "arrow-turn-down-right";
  return <Frame caption={caption} hint={hint} className={className} style={style} {...rest}>
      <img src={src} alt={alt} style={{
    width: fullwidth ? "100%" : undefined
  }} />
    </Frame>;
};

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 width="90%">
  <Tip>Livepeer is self-governing and self-funding. LPT-weighted votes set the rules, ETH from buyers pays operators per job, and a fixed share of LPT inflation funds a community treasury that buys public goods.</Tip>
</CenteredContainer>

Two control loops sit on top of one shared token. The **governance loop** turns staked LPT into binding decisions about protocol upgrades and treasury spending. The **economics loop** turns ETH from Gateways into operator revenue and LPT inflation into rewards for the people securing the network. Both run on Arbitrum One smart contracts that any participant can audit, fork, or exit at any time.

This page explains both loops at conceptual depth. The deep technical pages on Token, Treasury, and Governance live in the [Protocol](/v2/about/protocol/design) section.

<CustomDivider />

## How decisions are made

Governance decides two things: which contract code runs the protocol, and how the on-chain treasury spends its LPT. Both are controlled by the same stake-weighted voting mechanism, run on a custom Governor contract derived from OpenZeppelin's Compound-style governor pattern, and recorded permanently on Arbitrum One.

<Quote>
  Livepeer is a community-driven protocol, where token holders have the ability to vote on proposals to upgrade protocol mechanisms or to spend from the treasury. Voting is conducted on-chain via the Livepeer Governor contract, with the protocol contracts enforcing the outcome.
</Quote>

The pipeline is hybrid by design. Off-chain discussion on the [Livepeer Forum](https://forum.livepeer.org/c/lips/18) lets ideas gather feedback before they cost anything; on-chain proposals lock that feedback into binding code. A proposal becomes a [Livepeer Improvement Proposal (LIP)](https://github.com/livepeer/LIPs), modelled on Ethereum's EIP process, with a numbered specification, an author, and a status. Any holder with at least 100 LPT can submit one to the Governor. That stake is returned if the proposal passes and acts as a spam filter rather than a barrier.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Discussion" icon="comment-dots">
    Idea published on the [Livepeer Forum](https://forum.livepeer.org/c/lips/18). Open to anyone. No stake required.
  </StyledStep>

  <StyledStep title="Draft (RFP)" icon="comments">
    Pre-proposal posted as a Request For Feedback. Community refines scope, parameters, and rationale before code is written.
  </StyledStep>

  <StyledStep title="LIP submission" icon="file-pen">
    Finalised proposal published in [Livepeer/LIPs](https://github.com/livepeer/LIPs) with formal specification.
  </StyledStep>

  <StyledStep title="On-chain proposal" icon="arrow-up-right-from-square">
    Author with at least 100 LPT submits to the Governor contract. Proposal stake is locked.
  </StyledStep>

  <StyledStep title="Voting" icon="ballot">
    Voting period opens for 30 rounds (around 3.75 days). Any address with 1 LPT or more of staked weight can vote For, Against, or Abstain.
  </StyledStep>

  <StyledStep title="Quorum and approval" icon="people">
    Proposal passes if at least 33 percent of all staked LPT participates and more than 50 percent of votes cast are For.
  </StyledStep>

  <StyledStep title="Execution" icon="check-to-slot">
    Governor automatically executes the proposal: parameter change, contract upgrade, or treasury disbursement. No off-chain step is required.
  </StyledStep>
</StyledSteps>

Voting power is **stake-weighted, not headcount-weighted**. An Orchestrator's vote carries the weight of its self-bond plus the LPT delegated to it. Delegators who disagree with their Orchestrator can detach their vote and cast it independently in the same proposal, which makes voting power follow capital rather than custody. The 33 percent quorum and the 50 percent threshold together rule out minority capture: a determined faction holding a third of the stake can block a proposal, but only a majority of participating stake can pass one.

<Tip>Three controls keep governance rational: a 100 LPT proposal stake (anti-spam), a 33 percent quorum (anti-minority-rule), and a Delegator override (anti-custody-capture).</Tip>

The governance contract enforces the outcome automatically. There is no multisig step, no foundation veto, and no off-chain ratification. If a proposal passes the Governor's checks, the change happens on the next round; if it fails, the stake is returned and the protocol is unchanged. This is the property a serious analyst would test for first: the system's rules can only be changed by the same mechanism that runs the system.

The same Governor pattern handles treasury spending, on a separate Governor contract instance with the same parameters. A proposal to allocate funds passes or fails by the same 33/50 rule and the same execution flow. The only operational difference is that a passed treasury proposal triggers an LPT transfer rather than a contract parameter update.

The [**Livepeer Foundation**](/v2/home/about/ecosystem#livepeer-foundation) and the **Special Purpose Entities (SPEs)** that surround it sit outside the on-chain process by design. The Foundation coordinates roadmap discussion, runs governance operations (often through GovWorks-style functions), and helps SPEs scope grant proposals before they reach the Governor. SPEs are the operational arms that actually deploy treasury capital into specific projects: protocol R\&D, AI tooling, ecosystem grants, audits. None of them have unilateral authority. Every funding decision still passes through the on-chain vote.

For voting workflows, delegation mechanics, and how to participate as a Delegator, see the [Delegators tab](/v2/Delegators/portal). For the deep mechanics of the Governor contracts, parameter values, and historical LIPs, see <LinkArrow href="/v2/about/protocol/governance-and-treasury" label="Protocol → Governance and Treasury" newline={false} />.

<CustomDivider />

## How value flows

Two value flows run in parallel. They share participants and a token, but they answer different questions.

<DynamicTableV2
  headerList={["Flow", "Asset", "Source", "Destination", "Question it answers"]}
  itemsList={[
{ "Flow": "Work-for-pay", "Asset": "ETH", "Source": "Gateways", "Destination": "Orchestrators and delegators", "Question it answers": "Did this job get done?" },
{ "Flow": "Inflation incentive", "Asset": "LPT", "Source": "Protocol mint", "Destination": "Orchestrators, delegators, treasury", "Question it answers": "Is the right capital staked to secure the network?" },
]}
/>

### Flow 1: ETH for work

Buyers (Gateways and the platforms behind them) pre-fund a deposit and a reserve in the [TicketBroker](/v2/about/protocol/blockchain-contracts) contract. For every unit of work an Orchestrator processes, the Gateway issues a signed off-chain ticket with a face value and a small win probability. Most tickets do not win and never touch the chain. The few that do are redeemable on-chain by the Orchestrator, who pulls the face value from the Gateway's reserve. The expected value matches the contracted price; the cost of settlement is roughly one percent of per-job on-chain settlement.

This is the **probabilistic micropayment** design. It is the load-bearing economic primitive that makes streaming-rate compute economics work on a public chain. Without it, the gas cost per job would dwarf the price of the job itself.

<ScrollableDiagram title="ETH work-for-pay flow" maxHeight="500px">
  ```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
      Users["End users / apps"] -->|Pay in fiat or crypto| Gateways
      Gateways -->|Deposit + reserve in ETH| TicketBroker
      Gateways -->|Off-chain signed tickets| Orch["Orchestrators"]
      Orch -->|Redeem winning tickets| TicketBroker
      TicketBroker -->|Transfer ETH| Orch
      Orch -->|"Fee share %"| Delegators
  ```
</ScrollableDiagram>

When an Orchestrator redeems winning tickets, the ETH lands in its earnings pool. The Orchestrator keeps its configured **Fee Cut** (typically published as the percentage it retains) and the remainder accrues to its Delegators in proportion to their bonded stake. Delegators do not receive ETH automatically; they call `claimEarnings` to crystallise the accrued fees and rewards into withdrawable balance. The mechanic is identical to a dividend with reinvestment optionality.

### Flow 2: LPT inflation as security budget

Each round (roughly one day on Arbitrum), the [Minter](/v2/about/protocol/blockchain-contracts) issues new LPT proportional to the total bonded stake and the current inflation rate. The protocol uses dynamic inflation: when participation is below the target rate, inflation rises to attract more stake; when participation is above target, inflation falls. This is the same insight as Ethereum's PoS issuance curve, applied to a work-token rather than a validator-token.

The freshly minted LPT splits three ways before any operator touches it.

<DynamicTableV2
  headerList={["Recipient", "Share of new LPT", "Mechanism"]}
  itemsList={[
{ "Recipient": "Treasury", "Share of new LPT": "10% (current parameter)", "Mechanism": "Routed inside the reward call before operator distribution per LIP-92" },
{ "Recipient": "Orchestrators", "Share of new LPT": "Operator-set reward cut on the remainder", "Mechanism": "Retained as commission for running the node" },
{ "Recipient": "Delegators", "Share of new LPT": "Remainder after operator's reward cut", "Mechanism": "Distributed pro-rata by stake share to that orchestrator" },
]}
/>

The **Reward Cut** and **Fee Cut** parameters are the single most important configuration any Orchestrator publishes. They are the Orchestrator's price for its services as a stake aggregator: a 5 percent Reward Cut keeps 5 percent of the inflation rewards flowing to the Orchestrator's address and sends 95 percent to Delegators. Delegators read these numbers, compare them to performance history, and route their LPT accordingly. Capital follows the spread between commission and reliability, which is the price-discovery mechanism for the Active Set.

### Why the dual-asset model

The choice to settle work in ETH and incentivise security in LPT is the most consequential design decision in the protocol's economics, and it is worth naming explicitly.

<BorderedBox variant="accent">
  **ETH as medium of exchange.** Buyers do not need to acquire LPT to pay for work. They hold the asset they already use for everything else on Ethereum. This collapses the friction of onboarding Gateways and applications: a streaming platform integrating Livepeer does not have to take on an LPT treasury exposure to use the network.

  **LPT as work token.** Orchestrators and Delegators stake LPT to participate. The asset's value tracks the right to perform work and earn fees, not the right to hold. This separates the security budget from the payment medium and prevents the failure mode where rising token prices make the network too expensive to use.

  **Slashing as the bond.** Stake is forfeitable on protocol violation. Misbehaviour costs the operator and their Delegators directly, with 50 percent of the slashed amount burned and 50 percent routed to the treasury. The bond is the security guarantee that work was done honestly.
</BorderedBox>

This is the structural difference between Livepeer and a single-token DePIN where the same asset pays for work and secures the network. In a single-token system, the price of the service rises with the token and the security budget is contingent on speculation. In a dual-asset system, the cost of work is dollar-denominated through ETH, and the security budget compounds independently through LPT staking returns.

<CustomDivider />

## The token (LPT)

LPT is a **work token**. It does not entitle the holder to a share of fees by holding alone; it entitles the holder to perform or back work that earns fees. Three functions distinguish it from a generic governance token.

<QuadGrid icon="coins" iconSize={40} gap="0.5rem">
  <BorderedBox variant="accent">
    **Stake**
    Bond LPT to an Orchestrator (your own address or someone else's) to enter the Active Set or to receive a share of the rewards and fees that Orchestrator earns. Stake is the gating mechanism for both running the network and earning from it.
  </BorderedBox>

  <BorderedBox variant="accent">
    **Vote**
    Bonded LPT confers voting weight in protocol and treasury governance, in proportion to the amount staked. Delegators inherit voting weight from the Orchestrator they bond to and can override that vote at any time.
  </BorderedBox>

  <BorderedBox variant="accent">
    **Slashable bond**
    Bonded LPT is the collateral that secures honest behaviour. A protocol violation reduces the operator's stake, with the loss split between burn and treasury. Slashing is the cryptoeconomic guarantee behind every job receipt.
  </BorderedBox>

  <BorderedBox variant="accent">
    **Account unit**
    LPT is the unit of account inside the protocol's reward and slashing accounting. ETH is the medium of exchange for work; LPT is the medium of accounting for participation rights.
  </BorderedBox>
</QuadGrid>

The supply curve is dynamic, not fixed. Inflation responds to the participation rate: more bonded supply means lower inflation, less bonded supply means higher inflation, with bounds set by [LIP-100](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0100.md). The holder who does not stake is paying a continuous dilution cost to subsidise those who do, which is the protocol's structural answer to free-riding. The economic effect is identical to a productive bond: yield exists and accrues to the participants who actually do the work of securing the network.

The LPT contract itself lives on Ethereum mainnet for historical and bridge reasons, while staking and protocol activity live on Arbitrum One since the [Confluence migration (LIP-73)](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0073.md). Operators bridge LPT to L2 to participate; the bridge architecture is documented in <LinkArrow href="/v2/about/protocol/architecture" label="Protocol → Architecture" newline={false} />.

For full mechanics including bonding maths, claim flows, and slashing conditions, see <LinkArrow href="/v2/about/protocol/livepeer-token" label="Protocol → Token and Economics" newline={false} />.

<CustomDivider />

## The treasury

The Livepeer Treasury is an on-chain LPT pool, controlled by the same governance vote mechanism as the protocol, deployed at [`0x363cdB9BaE210Ef182c60b5a496139E980330127`](https://arbiscan.io/address/0x363cdB9BaE210Ef182c60b5a496139E980330127#code) on Arbitrum One. It exists to fund public goods: work that benefits the entire network and that no individual operator has the incentive to fund alone.

Funding is automatic and continuous. [LIP-92](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0092.md) routes 10 percent of each round's newly minted LPT into the treasury before operator distribution, until the treasury balance reaches a ceiling of 750,000 LPT. {/* REVIEW: 10%/750k LPT are LIP-92 initial values. Confirm whether the treasuryRewardCutRate has been updated by a subsequent LIP. SME: Rick. */} The ceiling is itself governable: when reached, contributions pause and the community must vote to resume them, which forces a periodic re-evaluation of public goods funding rather than indefinite passive accumulation. Two secondary inflows feed the treasury continuously: 50 percent of any slashed LPT, and any remainder when Gateway fee deposits expire unused.

<DynamicTableV2
  tableTitle="Treasury funding sources"
  headerList={["Source", "Mechanism", "Cap or trigger"]}
  itemsList={[
{ "Source": "Inflation contribution", "Mechanism": "10% of each round's mint, routed pre-distribution", "Cap or trigger": "Pauses at 750,000 LPT balance ceiling per LIP-92" },
{ "Source": "Slashing penalties", "Mechanism": "50% of slashed LPT (other 50% burned)", "Cap or trigger": "Triggered by protocol violation" },
{ "Source": "Fee remainders", "Mechanism": "Unredeemed gateway ETH deposits at expiry", "Cap or trigger": "Continuous; tracked on TicketBroker" },
{ "Source": "Direct LIP transfers", "Mechanism": "Community or multisig deposits via passed proposal", "Cap or trigger": "Per-proposal" },
]}
/>

Spending requires a passed LIP. The treasury cannot be drawn against by the Foundation, by Orchestrators acting individually, or by any party other than the on-chain Governor, which only acts when a proposal clears the same 33 percent quorum and 50 percent majority that gates protocol changes. Disbursements typically fund SPEs that scope, deliver, and report on specific initiatives: protocol engineering, audits, AI infrastructure, documentation, ecosystem grants. Every transfer is visible on the [treasury explorer](https://explorer.livepeer.org/treasury) and on Arbiscan as a `TreasuryWithdrawal` event.

<Tip>The treasury is an on-chain endowment, not a foundation budget. The Foundation may shape proposals and oversee delivery, but only stakers can authorise spending.</Tip>

For the Foundation's role, the SPE structure, treasury governance details, and historical disbursement patterns, see <LinkArrow href="/v2/about/protocol/governance-and-treasury" label="Protocol → Governance and Treasury" newline={false} />.

<CustomDivider />

## What the design guarantees

A serious analyst evaluating Livepeer's governance and economics ought to test five claims. The protocol answers each of them in code rather than in policy.

<DynamicTableV2
  headerList={["Test", "Mechanism that addresses it"]}
  itemsList={[
{ "Test": "Can the rules be changed without the rule-makers' consent?", "Mechanism that addresses it": "No. The Governor enforces both upgrades and treasury disbursements via stake-weighted vote. There is no admin key, no multisig veto, and no off-chain ratification." },
{ "Test": "Can a minority block or capture the system?", "Mechanism that addresses it": "Blocking yes, capture no. 33% quorum stops apathy from passing bad proposals; 50% threshold stops a determined minority from passing anything. Delegators can override their orchestrator's vote, breaking custody-as-control." },
{ "Test": "Is the security budget sustainable without external subsidy?", "Mechanism that addresses it": "Yes. Inflation is paid by holders to stakers, denominated in the network's own asset. The protocol does not depend on venture funding to pay for security." },
{ "Test": "Does the cost of using the network track the price of speculation in the network?", "Mechanism that addresses it": "No. Buyers pay in ETH at the price of the work, not in LPT at the price of the token. Token appreciation does not raise the cost of the service." },
{ "Test": "Is there a credible exit for participants who disagree with governance?", "Mechanism that addresses it": "Yes. LPT can be unbonded and sold; orchestrators can leave the active set; the protocol is open-source and forkable; gateways can withdraw deposits and route to alternative infrastructure." },
]}
/>

The design choices that make these answers stand up are well-trodden in protocol design literature, but the combination is specific. A Compound-style Governor with delegate override is from the DeFi governance lineage. A work-token paid in a separate medium of exchange is from the Stake Capital and Multicoin Capital "work token" thesis. A capped treasury that requires periodic re-authorisation is from public-finance theory rather than from prior protocols. Stack them, and the result is a system that runs on continuous community consent rather than on founder authority or foundation discretion.

The market test is whether participants behave as if these guarantees hold. Two signals are visible on-chain: the Active Set turnover (operators leaving and joining at the margin without governance disruption) and the treasury approval rate (proposals passing or failing through normal voting rather than through political capture). Both are monitorable on the [Livepeer Explorer](https://explorer.livepeer.org). The system is self-documenting in the sense that an external observer can verify its claims without trusting any participant.

<CustomDivider />

## Where to go deeper

<CardGroup cols={2}>
  <Card title="Token and Economics" icon="coins" href="/v2/about/protocol/livepeer-token" arrow horizontal>
    Bonding maths, reward and fee accounting, slashing conditions, and the full LPT contract architecture.
  </Card>

  <Card title="Governance and Treasury" icon="building-columns" href="/v2/about/protocol/governance-and-treasury" arrow horizontal>
    Governor contract specification, LIP catalogue, treasury contract details, and the Foundation and SPE structure.
  </Card>

  <Card title="Protocol Mechanisms" icon="cogs" href="/v2/about/protocol/mechanisms" arrow horizontal>
    Probabilistic Micropayments, rounds, the Active Set, and the cryptoeconomic primitives behind the economics described here.
  </Card>

  <Card title="Delegators" icon="user-check" href="/v2/delegators/portal" arrow horizontal>
    How to delegate LPT, how to vote, how to evaluate Orchestrators, and how to participate in governance as a token holder.
  </Card>

  <Card title="Livepeer Improvement Proposals" icon="github" href="https://github.com/livepeer/LIPs" arrow horizontal>
    The full canonical record of every protocol change, including LIP-89 (treasury), LIP-92 (treasury funding), and LIP-73 (Arbitrum migration).
  </Card>

  <Card title="Live governance and treasury" icon="chart-line" href="https://explorer.livepeer.org/voting" arrow horizontal>
    Active proposals, vote results, treasury balance, and disbursement history on the Livepeer Explorer.
  </Card>
</CardGroup>
