> ## 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 Treasury and Proposals

> How the Livepeer Treasury funds ecosystem growth, what Special Purpose Entities deliver, how the treasury proposal lifecycle differs from a protocol LIP, and how to submit a funding proposal through the LivepeerGovernor contract.

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

export const StyledSteps = ({children, iconColor, titleColor, lineColor, iconSize = '24px', className = '', style = {}, ...rest}) => {
  const resolvedIconColor = iconColor || 'var(--accent-dark, #18794E)';
  const resolvedTitleColor = titleColor || 'var(--lp-color-accent)';
  const resolvedLineColor = lineColor || 'var(--lp-color-accent)';
  return <div className={['docs-styled-steps', className].filter(Boolean).join(' ')} style={style} {...rest}>
      <style>{`
        .docs-styled-steps .steps > div > div.absolute > div {
          background-color: ${resolvedIconColor};
        }
        .docs-styled-steps .steps > div > div.w-full > p {
          color: ${resolvedTitleColor};
        }
        .docs-styled-steps .steps > div > div.absolute.w-px {
          background-color: ${resolvedLineColor};
        }
        .docs-styled-steps .steps > div:last-child > div.absolute.w-px::after {
          content: '';
          position: absolute;
          bottom: 0;
          left: 50%;
          transform: translateX(-50%);
          width: 6px;
          height: 6px;
          background-color: ${resolvedLineColor};
          transform: translateX(-50%) rotate(45deg);
        }
      `}</style>
      <div>
        <Steps>{children}</Steps>
      </div>
    </div>;
};

export const DynamicTableV2 = ({tableTitle = null, headerList = [], itemsList = [], monospaceColumns = [], columnWidths = {}, columnConfig = {}, showSeparators = false, margin, className = '', style = {}, ...rest}) => {
  if (!headerList.length) {
    return <div>No headers provided</div>;
  }
  const tableRef = useRef(null);
  const [measuredColumnWidths, setMeasuredColumnWidths] = useState({});
  const measureFitColumns = () => {
    const tableElement = tableRef.current;
    if (!tableElement) {
      return;
    }
    const nextWidths = headerList.reduce((accumulator, header, index) => {
      const config = columnConfig?.[header] || ({});
      if (!config.fitContent) {
        return accumulator;
      }
      const contentNodes = tableElement.querySelectorAll(`[data-docs-column-key="${index}"] [data-docs-fit-content]`);
      let maxContentWidth = 0;
      contentNodes.forEach(node => {
        const width = Math.ceil(node.getBoundingClientRect().width);
        if (width > maxContentWidth) {
          maxContentWidth = width;
        }
      });
      if (maxContentWidth > 0) {
        accumulator[header] = `${maxContentWidth + 16}px`;
      }
      return accumulator;
    }, {});
    setMeasuredColumnWidths(currentWidths => {
      const currentEntries = Object.entries(currentWidths);
      const nextEntries = Object.entries(nextWidths);
      if (currentEntries.length === nextEntries.length && nextEntries.every(([header, width]) => currentWidths[header] === width)) {
        return currentWidths;
      }
      return nextWidths;
    });
  };
  useLayoutEffect(() => {
    measureFitColumns();
  }, [headerList, itemsList, columnConfig]);
  useEffect(() => {
    const tableElement = tableRef.current;
    if (!tableElement || typeof ResizeObserver === 'undefined') {
      return undefined;
    }
    const resizeObserver = new ResizeObserver(() => {
      measureFitColumns();
    });
    resizeObserver.observe(tableElement);
    if (tableElement.parentElement) {
      resizeObserver.observe(tableElement.parentElement);
    }
    return () => {
      resizeObserver.disconnect();
    };
  }, [headerList, itemsList, columnConfig]);
  const fitHeaders = headerList.filter(header => columnConfig?.[header]?.fitContent);
  const hasMeasuredFitColumns = fitHeaders.length === 0 || fitHeaders.every(header => Boolean(measuredColumnWidths[header]));
  const getColumnStyle = (header, isMonospace = false) => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const nowrap = Boolean(config.nowrap) || fitContent || isMonospace;
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    return {
      ...fitContent && measuredWidth ? {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      } : {},
      ...!fitContent && !fluid && preferredWidth ? {
        minWidth: preferredWidth
      } : {},
      ...nowrap ? {
        whiteSpace: 'nowrap'
      } : {
        wordWrap: 'break-word',
        overflowWrap: 'break-word'
      }
    };
  };
  const getColumnTrackStyle = header => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    if (fitContent && measuredWidth) {
      return {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      };
    }
    if (fluid) {
      return {};
    }
    if (preferredWidth) {
      return {
        width: preferredWidth
      };
    }
    return {};
  };
  const renderCellContent = (header, content) => {
    const config = columnConfig?.[header] || ({});
    if (!config.fitContent) {
      return content;
    }
    return <div data-docs-fit-content style={{
      display: 'inline-flex',
      alignItems: 'center',
      whiteSpace: 'nowrap',
      width: 'max-content',
      maxWidth: 'none'
    }}>
        {content}
      </div>;
  };
  return <div className={className} style={style} {...rest}>
      {tableTitle && <div style={{
    fontStyle: 'italic',
    margin: 0
  }}>
          <strong>{tableTitle}</strong>
        </div>}
      <div style={{
    overflowX: 'auto',
    ...margin != null && ({
      margin
    })
  }} role="region" tabIndex={0} aria-label={tableTitle ? `Scrollable table: ${tableTitle}` : 'Scrollable table'}>
        <table ref={tableRef} data-docs-dynamic-table-v2 style={{
    width: '100%',
    tableLayout: hasMeasuredFitColumns ? 'fixed' : 'auto',
    borderCollapse: 'collapse',
    fontSize: '0.9rem',
    marginTop: 0
  }}>
          <colgroup>
            {headerList.map((header, index) => <col key={index} style={getColumnTrackStyle(header)} />)}
          </colgroup>
          <thead>
            <tr style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
              {headerList.map((header, index) => <th key={index} data-docs-column-key={index} style={{
    padding: '10px 8px',
    textAlign: 'left',
    fontWeight: '600',
    color: 'var(--lp-color-on-accent)',
    verticalAlign: 'top',
    ...getColumnStyle(header)
  }}>
                  {renderCellContent(header, header)}
                </th>)}
            </tr>
          </thead>
          <tbody>
            {itemsList.filter(item => showSeparators || !item?.__separator).map((item, rowIndex) => item?.__separator ? <tr key={rowIndex} style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-accent)'
  }}>
                    <td colSpan={headerList.length} style={{
    padding: '6px 8px',
    fontWeight: '700',
    color: 'var(--lp-color-on-accent)',
    letterSpacing: '0.01em'
  }}>
                      {(item[headerList[0]] ?? item.Category) ?? 'Category'}
                    </td>
                  </tr> : <tr key={rowIndex} style={{
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
                    {headerList.map((header, colIndex) => {
    const value = (item[header] ?? item[header.toLowerCase()]) ?? '-';
    const isMonospace = monospaceColumns.includes(colIndex);
    return <td key={colIndex} data-docs-column-key={colIndex} style={{
      padding: '8px 8px',
      fontFamily: isMonospace ? 'monospace' : 'inherit',
      verticalAlign: 'top',
      ...getColumnStyle(header, isMonospace)
    }}>
                          {renderCellContent(header, isMonospace ? <code>{value}</code> : value)}
                        </td>;
  })}
                  </tr>)}
          </tbody>
        </table>
      </div>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const Quote = ({children, className = "", style = {}, ...rest}) => {
  const quoteStyle = {
    fontSize: "1rem",
    textAlign: 'center',
    opacity: 1,
    fontStyle: 'italic',
    color: 'var(--lp-color-accent)',
    border: '1px solid var(--lp-color-border-default)',
    borderRadius: "8px",
    padding: "var(--lp-spacing-4)",
    margin: '1rem 0',
    ...style
  };
  return <blockquote className={className} style={quoteStyle} {...rest}>{children}</blockquote>;
};

export const CustomCardTitle = ({icon, title, variant = "card", iconSize, style = {}, className = "", ...rest}) => {
  const variants = {
    card: {
      display: 'flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)",
      marginBottom: "var(--lp-spacing-3)",
      color: 'var(--lp-color-text-primary)',
      fontSize: '1rem',
      fontWeight: 600
    },
    accordion: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)"
    },
    tab: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: '0.4rem',
      fontSize: '0.875rem'
    }
  };
  const sizes = {
    card: 20,
    accordion: 18,
    tab: 14
  };
  const size = iconSize || sizes[variant] || 20;
  const baseStyle = variants[variant] || variants.card;
  return variant === 'card' ? <div className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </div> : <span className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </span>;
};

<Quote>
  The Livepeer Treasury is an on-chain pool funded by 10% of every round's LPT inflation. It exists so the network can fund its own growth - public goods, core development, and ecosystem initiatives - without depending on any single entity. Allocation is decided by stake-weighted vote.
</Quote>

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

## What the treasury is

The treasury is a single contract on Arbitrum One that holds LPT received from the Minter each round. It was created by [LIP-89](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0089.md), bundled into the production parameter set by [LIP-91](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0091.md), and given its 10%-of-inflation allocation by [LIP-92](https://github.com/livepeer/LIPs/blob/main/LIPs/LIP-0092.md).

The treasury exists to solve a structural problem in protocol economies: the network needs ongoing development, research, and operational work, but the protocol cannot pay for these directly. Inflation is the only sustainable source of network-funded labour. Routing a fixed share into a community-controlled pool turns inflation into a public-goods budget that LPT holders allocate by vote.

<DynamicTableV2
  headerList={["Property", "Value"]}
  itemsList={[
{ "Property": "Funding source", "Value": "10% of each round's LPT inflation, routed automatically by the Minter" },
{ "Property": "Custody", "Value": "On-chain, in the treasury contract on Arbitrum One" },
{ "Property": "Allocation", "Value": "Stake-weighted on-chain vote through LivepeerGovernor" },
{ "Property": "Established by", "Value": "LIP-89 (creation), LIP-91 (bundle), LIP-92 (10% contribution)" },
{ "Property": "Reactivated by", "Value": "LIP-101 (January 2026), restoring the 10% treasury cut" },
]}
/>

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

## How treasury funds reach builders

Most treasury funding does not flow directly to individual contributors. It flows through Special Purpose Entities (SPEs) that take responsibility for a defined area of work and report back to the community.

An SPE is a community-approved organisation - typically with a multi-person team - chartered to deliver a specific scope of work over a defined period. The SPE proposes a budget on-chain through a treasury LIP. If the proposal passes, treasury LPT is released to the SPE address. The SPE delivers the scope, publishes updates, and either renews its mandate or hands off when the work is complete.

This structure exists because most ecosystem work needs continuity - core protocol maintenance, research, real-time AI development, observability tooling - and the on-chain proposal process is too slow for grant-by-grant approval. SPEs translate one-time governance approval into ongoing execution.

<DynamicTableV2
  headerList={["SPE pattern", "What it delivers"]}
  itemsList={[
{ "SPE pattern": "Protocol R&D", "What it delivers": "Core protocol maintenance, contract upgrades, network operations - the floor of the protocol" },
{ "SPE pattern": "Research", "What it delivers": "Cryptoeconomic research, mechanism design, benchmarking, public-facing analysis" },
{ "SPE pattern": "Real-time AI / Pipelines", "What it delivers": "Network-layer pipelines (real-time AI video, BYOC frameworks) without protocol changes" },
{ "SPE pattern": "Ecosystem and Growth", "What it delivers": "Integration support, developer relations, partner onboarding" },
{ "SPE pattern": "Observability and Tooling", "What it delivers": "Dashboards, leaderboards, subgraph maintenance, public data products" },
]}
/>

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

## How treasury proposals differ from protocol LIPs

The treasury and the core protocol use different governance contracts and slightly different rules. Mixing them up is the most common mistake new proposers make.

<DynamicTableV2
  headerList={["Property", "Protocol LIP", "Treasury Proposal"]}
  itemsList={[
{ "Property": "Executing contract", "Protocol LIP": "Governor", "Treasury Proposal": "LivepeerGovernor" },
{ "Property": "What it changes", "Protocol LIP": "Protocol contracts, parameters, contract implementations", "Treasury Proposal": "Releases LPT from treasury balance to a target address" },
{ "Property": "Submission stake", "Protocol LIP": "100 LPT (returned on pass)", "Treasury Proposal": "100 LPT (returned on pass)" },
{ "Property": "Voting period", "Protocol LIP": "30 rounds (~3.75 days)", "Treasury Proposal": "30 rounds (~3.75 days)" },
{ "Property": "Quorum", "Protocol LIP": "33% of bonded LPT", "Treasury Proposal": "33% of bonded LPT" },
{ "Property": "Approval threshold", "Protocol LIP": "Majority (>50%) of votes cast", "Treasury Proposal": "Majority (>50%) of votes cast" },
{ "Property": "Required documentation", "Protocol LIP": "Full LIP in Livepeer/LIPs with technical specification", "Treasury Proposal": "Forum proposal with scope, budget, deliverables, and reporting commitments" },
]}
/>

The voting mechanics are identical. The execution path and the documentation expectations differ. A protocol LIP needs a contract spec and an implementation plan. A treasury proposal needs a budget, a deliverable, and a public commitment to report back.

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

## How to submit a treasury proposal

The treasury proposal lifecycle mirrors the protocol LIP lifecycle for the on-chain stages, but the off-chain preparation is different.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Define the scope and budget" icon="ruler-combined">
    Write a clear scope: what work, what timeline, what deliverable. Itemise the budget by category (engineering, research, infrastructure, communications). Treasury proposals without a defensible budget rarely pass.
  </StyledStep>

  <StyledStep title="Post a Request for Feedback on the forum" icon="comments">
    Submit the proposal as an RFP on the <LinkArrow label="Livepeer Forum" href="https://forum.livepeer.org/c/lips/18" newline={false} />. The forum thread is where Orchestrators and Delegators ask questions, surface concerns, and negotiate scope. A proposal that has not been workshopped on the forum is unlikely to clear quorum.
  </StyledStep>

  <StyledStep title="Refine and finalise" icon="file-pen">
    Iterate the proposal based on feedback. Lock the scope, the recipient address, and the requested LPT amount. Publish the final version on the forum with a clear "ready for on-chain submission" marker.
  </StyledStep>

  <StyledStep title="Submit on-chain" icon="arrow-up-right-from-square">
    Anyone with 100 LPT can submit the proposal to the LivepeerGovernor contract. The proposal text, recipient, and amount are committed on-chain at this stage. The 100 LPT submission stake is returned if the proposal passes.
  </StyledStep>

  <StyledStep title="Voting period" icon="ballot">
    A 30-round window opens. Stake-weighted voting determines the outcome under the standard 33% quorum and majority-of-cast-votes rules. The LivepeerGovernor uses the same voting weight as core governance - bonded LPT, with Delegator override available.
  </StyledStep>

  <StyledStep title="Execution and reporting" icon="check-to-slot">
    On a successful vote, the LivepeerGovernor releases the requested LPT to the recipient address. The funded entity is expected to deliver the agreed scope and report on progress through the forum. Treasury funding is not a one-time grant - sustained credibility requires sustained reporting.
  </StyledStep>
</StyledSteps>

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

## What treasury credibility looks like

The treasury process favours proposals that are operationally serious. The pattern is consistent across passed proposals.

* **Defensible budget.** Line items, not lump sums. Comparable benchmarks where possible.
* **Track record.** A team or contributor with prior public delivery in the Livepeer ecosystem or in adjacent protocols.
* **Concrete deliverables.** Working software, public research, measurable outputs - not "we will explore."
* **Ongoing reporting.** Forum threads, public dashboards, regular updates. Treasury funding is a relationship, not a transaction.
* **Network alignment.** Work that strengthens the protocol or the network - not work that benefits a single operator or vendor.

Proposals that miss any one of these tend to fail to clear quorum even when individual votes are favourable. The treasury rewards rigour.

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

## Where to go next

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="vault" title="Governance and Treasury Reference" />} href="/v2/about/protocol/governance-and-treasury" horizontal arrow>
    Canonical reference: full LIP catalogue, contract roles, and historical decisions.
  </Card>

  <Card title={<CustomCardTitle icon="ballot" title="Governance and Voting" />} href="/v2/about/guides/governance-and-voting" horizontal arrow>
    How LIPs work end-to-end and how to use the vote that comes with bonded LPT.
  </Card>

  <Card title={<CustomCardTitle icon="comments" title="Livepeer Forum" />} href="https://forum.livepeer.org/c/lips/18" horizontal arrow>
    Where every treasury proposal is workshopped before it reaches the chain.
  </Card>

  <Card title={<CustomCardTitle icon="github" title="LIPs Repository" />} href="https://github.com/livepeer/LIPs" horizontal arrow>
    Treasury LIPs (LIP-89, LIP-91, LIP-92, LIP-101) and the full proposal corpus.
  </Card>
</Columns>
