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

> Current opportunities to participate in Livepeer through grants, programmes, and community initiatives.

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 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>;
};

Livepeer funds ecosystem work through grants, accelerator programmes, bug bounties, and treasury proposals. Each has different scope, requirements, and timelines.

<CustomDivider margin="-1rem 0 -2rem 0" />

## Grants

Grants are administered through the [Dev Hub](https://www.livepeer.org/dev-hub). Applications are rolling unless otherwise stated.

<DynamicTableV2
  headerList={["Grant Type", "Scope", "Who It's For"]}
  itemsList={[
{ "Grant Type": "Microgrants / Quick Start", Scope: "Tightly scoped, 1-month projects", "Who It's For": "Independent developers, content creators" },
{ "Grant Type": "Research & Video Innovation", Scope: "Cutting-edge video research or novel applications", "Who It's For": "Researchers, independent developers, creatives" },
{ "Grant Type": "Supply Side / Network Health", Scope: "Tools for orchestrators, delegators, and node operators", "Who It's For": "Orchestrators, delegators, tooling developers" },
{ "Grant Type": "AI Workflow Grants (ComfyUI Hacker)", Scope: "Real-time AI video workflows using ComfyUI/ComfyStream", "Who It's For": "ComfyUI creators, AI video developers" }
]}
/>

<Card title="Apply for a Grant" icon="arrow-up-right-from-square" href="https://www.livepeer.org/dev-hub">
  Browse open grants and submit applications through the Livepeer Dev Hub.
</Card>

<CustomDivider />

## Treasury Proposals (SPEs)

Any LPT holder can propose a Special Purpose Entity (SPE) - a treasury-funded project approved by on-chain governance. Proposals require a minimum of 100 LPT to submit.

**Process:** Pre-proposal on Forum → Community discussion → On-chain proposal → Voting window (\~9 days / 10 rounds) → 33% quorum + 50% passing threshold → Treasury disbursement.

<CardGroup cols={2}>
  <Card title="Governance Forum" icon="comments" href="https://forum.livepeer.org/c/governance">
    Post pre-proposals and discuss with the community before going on-chain.
  </Card>

  <Card title="Governance Process Guide" icon="book" href="https://forum.livepeer.org/t/livepeer-governance-process/2767">
    The definitive 6-step guide to creating and submitting an SPE proposal.
  </Card>

  <Card title="Explorer Treasury" icon="building-columns" href="https://explorer.livepeer.org/treasury">
    View active proposals and vote with staked LPT.
  </Card>
</CardGroup>

### How to propose an SPE

The process is defined and maintained by GovWorks. The full guide is at [forum.livepeer.org/t/livepeer-governance-process/2767](https://forum.livepeer.org/t/livepeer-governance-process/2767).

<Steps>
  <Step title="Develop your idea">
    Check existing SPEs and the [SPE Dashboard](https://www.notion.so/SPE-Dashboard-cd7e27da8dd54820b1aaea5b0b33541b) to confirm your proposal fills a genuine gap. Consider whether applying to an existing SPE's grants programme (e.g., Transformation SPE) is a better fit than a standalone treasury proposal.
  </Step>

  <Step title="Get early feedback">
    Post a Pre-Proposal in the [Treasury category](https://forum.livepeer.org/c/treasury/18) using the [SPE Proposal Template](https://docs.google.com/document/d/11I8ds1-tA1PaYU5rxJ58eodR-JfsjuyOm2pXXE-O6ow/edit). Leave at least 7 days for community discussion. Attend a Treasury Talk or Water Cooler call in Discord to get direct feedback.
  </Step>

  <Step title="Build community support">
    Tag `@Orchestrator` in the Discord `#governance` channel to notify active validators. Surface the proposal on social media. Quorum requires 33% of all active stake - visibility is critical.
  </Step>

  <Step title="Submit on-chain">
    Submit your final proposal at [explorer.livepeer.org/treasury](https://explorer.livepeer.org/treasury). You need at least **100 LPT staked** to submit. GovWorks can connect you with a staker if needed - contact them via the [GovWorks Notion Hub](https://www.notion.so/GovWorks-SPE-caa4a5442ddb4014b1f0e85aba4dce47).
  </Step>

  <Step title="Voting period">
    After a \~21-hour delay, a 10-round voting window opens (approximately 9 days). Any Orchestrator or staked Delegator can vote For, Against, or Abstain via the Explorer.
  </Step>

  <Step title="Result and implementation">
    The proposal passes with **33% quorum** and **more than 50% For** votes. Funds are released to the specified wallet address. A multi-sig is strongly recommended for team proposals. Publish a transparent execution roadmap and regular update posts on the Forum.
  </Step>
</Steps>

For full governance details, see [Governance](/v2/community/ecosystem/governance).

## Programmes

<CardGroup cols={2}>
  <Card title="AI Video Startup Programme" icon="rocket" href="https://www.encode.club/ai-video-startup-program">
    3-month accelerator with Encode Club - \$20K grant, infrastructure credits, mentorship, and investor networking.
  </Card>

  <Card title="ComfyUI Live Video Hacker" icon="wand-magic-sparkles" href="https://www.livepeer.org/dev-hub">
    Cohort-based grants for real-time AI video workflows built on ComfyUI and ComfyStream.
  </Card>

  <Card title="Real-Time Video AI Bootcamp" icon="chalkboard-user" href="https://www.encode.club">
    Technical workshops, hands-on projects, and incentivised challenges with Encode Club.
  </Card>

  <Card title="Livepeer Summit Hackathon" icon="code" href="https://summit2025.livepeer.org/">
    2-day strategy and hacking event tied to the annual Livepeer Summit.
  </Card>
</CardGroup>

<CustomDivider />

## Bug Bounty

Livepeer runs a bug bounty programme through Immunefi. Security researchers can report vulnerabilities in protocol contracts and infrastructure for reward tiers based on severity.

<Card title="Immunefi Bug Bounty" icon="shield-halved" href="https://immunefi.com">
  Report vulnerabilities and earn rewards through the Livepeer security bounty programme.
</Card>
