> ## Documentation Index
> Fetch the complete documentation index at: https://na-36-mintlify-aebde2c5.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Network Marketplace Model

> How supply, demand, price, and settlement combine into the Livepeer Network's open compute marketplace, and how honest work is enforced without a central operator.

export const BorderedBox = ({children, variant = "default", padding = "var(--lp-spacing-4)", borderRadius = "var(--lp-spacing-px-8)", 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,
    ...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', className = '', ...rest}) => {
  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,
    ...style
  }} {...rest}>
      {text}
      {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 Network is an **open marketplace for real-time compute**. Operators publish what they can do; gateways route demand to them; payment settles continuously off-chain. The Protocol stays out of price discovery and only enters when a winning ticket is redeemed.
</Quote>

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

## Market Shape

The Livepeer Network is a posted-price market for GPU compute. Operators on the supply side publish their capabilities and prices. Gateways on the demand side discover, score, and route work to them. Payment runs continuously through probabilistic micropayments, with on-chain settlement only when a ticket wins.

The shape is deliberate. An auction would force every job through a bidding round, adding latency the workloads cannot absorb. On-chain settlement per job would cost more than the work itself. The marketplace runs continuously off-chain so per-pixel video and per-token AI remain economical at production volume.

<DynamicTableV2
  headerList={['Market property', 'Livepeer Network', 'Why it is shaped this way']}
  itemsList={[
{
  'Market property': <Subtitle variant="changelog">**Pricing**</Subtitle>,
  'Livepeer Network': 'Posted prices, set by orchestrators',
  'Why it is shaped this way': 'Continuous market; price competition is visible; gateways route without per-job coordination',
},
{
  'Market property': <Subtitle variant="changelog">**Discovery**</Subtitle>,
  'Livepeer Network': 'Public, multi-surface (on-chain registry, direct configuration, webhooks, network capabilities API)',
  'Why it is shaped this way': 'No gatekeeper; gateways select operators on capability and policy',
},
{
  'Market property': <Subtitle variant="changelog">**Matching**</Subtitle>,
  'Livepeer Network': 'Gateway-side scoring against operator-published capabilities',
  'Why it is shaped this way': 'Routing decisions stay close to demand; operators do not run a matching service',
},
{
  'Market property': <Subtitle variant="changelog">**Settlement**</Subtitle>,
  'Livepeer Network': 'Off-chain probabilistic micropayments with periodic on-chain redemption',
  'Why it is shaped this way': 'Settlement cost stays below the value of the work',
},
{
  'Market property': <Subtitle variant="changelog">**Trust anchor**</Subtitle>,
  'Livepeer Network': 'Bonded LPT staked by orchestrators',
  'Why it is shaped this way': 'Economic exposure makes misbehaviour costly; no central operator required',
},
]}
/>

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

## How Work and Value Move

Two flows run continuously across the marketplace. Work flows from clients through gateways to orchestrators. Value flows in the opposite direction, mostly off-chain, with periodic on-chain settlement.

<ScrollableDiagram title="Work and Value Flow Across the Marketplace" maxHeight="600px">
  ```mermaid theme={"theme":{"light":"github-light","dark":"dark-plus"}}
  %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#1a1a1a', 'primaryTextColor': '#E0E4E0', 'primaryBorderColor': '#2b9a66', 'lineColor': '#2b9a66', 'secondaryColor': '#0d0d0d', 'tertiaryColor': '#1a1a1a', 'background': '#0d0d0d', 'fontFamily': "Inter, 'Inter Fallback', -apple-system, system-ui" }}}%%
  flowchart LR
      classDef default fill:#1a1a1a,color:#E0E4E0,stroke:#2b9a66,stroke-width:2px
      classDef chain fill:#1a1a1a,color:#E0E4E0,stroke:#f59e0b,stroke-width:2px

      Client["Client"]:::default
      Gateway["Gateway"]:::default
      Orch["Orchestrator"]:::default
      Delegators["Delegators<br/>bonded to orchestrator"]:::default

      Chain["Arbitrum One<br/>TicketBroker, BondingManager, Minter"]:::chain

      Client -->|work request| Gateway
      Gateway -->|job + ticket per segment| Orch
      Orch -->|result| Gateway
      Gateway -->|result| Client

      Orch -.->|winning tickets only| Chain
      Chain -.->|ETH from gateway deposit| Orch
      Chain -.->|LPT inflation per round| Orch
      Orch -->|reward share by stake| Delegators
  ```
</ScrollableDiagram>

The diagram is the marketplace. Solid arrows are continuous off-chain traffic; dashed arrows are the periodic on-chain settlement. Most jobs never produce a dashed arrow. The marketplace runs at the speed of the solid lines, anchored by the dashed ones.

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

## Settlement Boundary

The boundary between what the Protocol records and what stays off-chain is sharp. The Protocol sees deposits, winning tickets, active-set membership, and inflation distribution. Everything else is direct gateway-to-orchestrator traffic.

<BorderedBox variant="accent">
  <Subtitle style={{ fontSize: '1rem', marginBottom: '0.5rem' }}>**Visible on-chain**</Subtitle>

  * Orchestrator service URIs and active-set membership
  * Gateway deposits and reserves in `TicketBroker`
  * Winning tickets when redeemed
  * Per-round LPT inflation distributions to bonded stake
  * Governance proposals and votes

  <Subtitle style={{ fontSize: '1rem', marginTop: '1rem', marginBottom: '0.5rem' }}>**Off-chain only**</Subtitle>

  * Capability and price advertisement
  * Per-session price agreement and ticket parameter exchange
  * Per-segment job dispatch and results
  * All probabilistic tickets that do not win the lottery
  * Discovery scoring and operator routing policy
</BorderedBox>

This split is what lets per-pixel and per-token pricing remain economical at production volume. On-chain settlement per segment would cost more than the work itself; off-chain ticketing with periodic redemption keeps the cost ratio sustainable.

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

## How Honest Work is Enforced

The Network produces verifiable work without a central operator. Three mechanisms stack: stake exposure, fast verification, and the discovery surface itself.

<StyledSteps iconColor="var(--accent)" titleColor="var(--accent)">
  <StyledStep title="Stake exposure" icon="shield-halved">
    Orchestrators bond LPT to be eligible for video work. The bonded stake is what makes them discoverable, what gives delegators a reason to back them, and what they lose value on if they misbehave or go offline. The marketplace is not anonymous; every orchestrator has economic skin in the game.
  </StyledStep>

  <StyledStep title="Fast verification" icon="circle-check">
    Gateways can re-encode a returned segment and compare it against the orchestrator's output to detect tampering. Verification is optional per-session and per-segment, applied at a sample rate the gateway chooses. A verification mismatch triggers an immediate orchestrator swap and feeds operator-side reputation signals.
  </StyledStep>

  <StyledStep title="Marketplace selection" icon="filter">
    Gateways score candidates on capability match, latency, stake-weighted reliability, and operator history. Orchestrators that fail jobs, return wrong outputs, or run high latency drop out of selection. The marketplace prunes itself; operators that perform well receive more work.
  </StyledStep>
</StyledSteps>

The three mechanisms compose. Stake makes misbehaviour costly. Verification detects it when it happens. Selection routes around operators that produce it. None of the three depends on a central authority.

<CustomDivider />

## Related Pages

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="circle-nodes" title="Network Design" />} href="/v2/about/network/design" horizontal arrow>
    Purpose, properties, actors, where to go.
  </Card>

  <Card title={<CustomCardTitle icon="diagram-project" title="Network Architecture" />} href="/v2/about/network/architecture" horizontal arrow>
    The running fleet's structure and surfaces.
  </Card>

  <Card title={<CustomCardTitle icon="cogs" title="Protocol Mechanisms" />} href="/v2/about/protocol/mechanisms" horizontal arrow>
    Staking, rewards, settlement, rounds.
  </Card>

  <Card title={<CustomCardTitle icon="microchip" title="Run an Orchestrator" />} href="/v2/orchestrators/portal" horizontal arrow>
    Operator-side pricing, staking, earnings.
  </Card>
</Columns>

{  /* ---
title: Livepeer Marketplace
sidebarTitle: Marketplace
description: >-
How the Livepeer Network marketplace matches demand and supply for real-time media compute: transcoding and AI
inference, including routing, pricing, and settlement.
lifecycleStage: discover
complexity: intermediate
pageType: concept
audience: general
lastVerified: 2026-03-17T00:00:00.000Z
purpose: concept
keywords:
- livepeer
- about
- livepeer network
- marketplace
- routing
- orchestrators
- gateways
- pricing
'og:image': /snippets/assets/media/og-images/en/about.png
---
import { DynamicTable } from '/snippets/components/displays/tables/Tables.jsx'
import { GotoCard, GotoLink } from '/snippets/components/elements/links/Links.jsx'

The Livepeer Network supports a dynamic decentralised marketplace for real-time media compute: transcoding and AI inference. Unlike static infrastructure platforms, Livepeer's open marketplace introduces real-time **bidding, routing, and pricing** of jobs across a global pool of Orchestrators. The marketplace layer, actor behaviours, session economics, and how it relates to the protocol.

## Marketplace overview

<DynamicTable
headerList={["Element", "Role"]}
itemsList={[
  { "Element": "Gateway / Client", "Role": "Submit job requests (stream, image, session intent)" },
  { "Element": "Gateway", "Role": "Matches requests to suitable Orchestrators" },
  { "Element": "Orchestrator", "Role": "Advertises availability, pricing, and capabilities" },
  { "Element": "Worker", "Role": "Executes compute task (Transcoder or AI worker)" },
  { "Element": "TicketBroker", "Role": "Receives tickets for ETH reward upon verified work (on-chain)" }
]}
/>

This market is **continuous** - Orchestrators are always available for sessions; Gateways route work off-chain without per-job on-chain gas.

## Demand: client workloads

Clients submit multiple media compute jobs through Gateways:

<DynamicTable
headerList={["Job type", "Example use case", "Payment style"]}
itemsList={[
  { "Job type": "Live stream", "Example use case": "RTMP ingest for video platforms", "Payment style": "Per-minute ETH / credits" },
  { "Job type": "AI inference", "Example use case": "Frame-by-frame image-to-image generation", "Payment style": "Per-job (frame, token)" },
  { "Job type": "File transcode", "Example use case": "Static MP4 → web formats", "Payment style": "Batch credits" }
]}
/>

**API examples:** Livepeer Studio REST, Gateway POST job, ComfyStream interface (AI).

## Supply: Orchestrator nodes

Orchestrators advertise:

- Hardware specs (GPU/CPU, memory)
- Region and latency
- Supported workloads (video, AI, or both)
- Price per segment / frame / token

They update availability via Gateway-side gRPC or REST heartbeat endpoints. Gateways use this information to route jobs to the best match.

## Routing logic

```mermaid
graph TD
  B[Client] --> G[Gateway Node]
  G --> O1[Orchestrator A]
  G --> O2[Orchestrator B]
  O1 --> W1[Worker]
  O2 --> W2[Worker]
```

The Gateway scores Orchestrators by:

- Latency to input source
- Workload match (video vs AI)
- Cost per job
- Availability and retry buffer

Sessions are **routed** off-chain to the best match; no on-chain gas is spent per job.

## Price discovery

The current Livepeer implementation uses **posted pricing** (Orchestrator-set), not auction-based. A few notes:

- Clients can be matched to the lowest available compatible provider.
- Prices may vary by:
- Region (e.g. US-East vs EU-Central)
- GPU load (AI-heavy Orchestrators may charge more)
- Quality profile (e.g. 1080p60 vs 720p30)

<Note>
In development: LIPs may introduce dynamic auction mechanisms for AI sessions (e.g. Spot job auctions). See the [Forum LIPs](https://forum.livepeer.org/c/lips/) for proposals.
</Note>

## Payments and settlement

**Clients** pay via:

- ETH tickets (settled on-chain via the protocol’s `TicketBroker`)
- Credit balance (tracked off-chain by some Gateways)

**Orchestrators:**

- Claim winning tickets to the `TicketBroker` on Arbitrum
- Accumulate ETH earnings from transcoding/AI work
- Claim inflation (LPT) rewards from the `BondingManager` each round

## Credit system extensions

Some Gateways provide user-friendly pricing in addition to direct ETH:

<DynamicTable
headerList={["Currency", "Top-up methods", "Denomination example"]}
itemsList={[
  { "Currency": "USD", "Top-up methods": "Credit card, USDC", "Denomination example": "Per minute or per job" },
  { "Currency": "ETH", "Top-up methods": "MetaMask, smart wallet", "Denomination example": "Per job or per segment" }
]}
/>

Orchestrators can price in USD-equivalent via oracle-based quoting where supported.

## Observability

Each session can be logged with:

- Latency to first response
- Retry count
- Orchestrator ID and region
- Price paid (ETH or credit)

Future marketplace indexers may surface real-time job flow stats for the network.

## Protocol–market boundaries

<DynamicTable
headerList={["Layer", "Description", "Example"]}
itemsList={[
  { "Layer": "Protocol", "Description": "Verifies work and pays ETH & LPT rewards", "Example": "TicketBroker, BondingManager" },
  { "Layer": "Marketplace", "Description": "Matches jobs to compute providers", "Example": "Gateway load balancer, routing" },
  { "Layer": "Interface layer", "Description": "Abstracts API, SDK, session negotiation", "Example": "Livepeer Studio SDK, Daydream API" }
]}
/>

## Future upgrades (LIPs proposed)

- **LIP-78:** Spot job auctions
- **LIP-81:** Credit-to-protocol sync bridge
- **LIP-85:** Orchestrator staking influence on job routing

For current status, see the [Forum LIPs](https://forum.livepeer.org/c/lips/) and [Technical roadmap](../resources/technical-roadmap).

## See also

- [Job pipelines](./job-pipelines) - End-to-end flow from ingest to settlement
- [Actors](./actors) - Gateway, Orchestrator, and Delegator roles
- [Livepeer Protocol overview](../protocol/overview) - On-chain contracts and incentives
- [Blockchain contracts](../protocol/blockchain-contracts) - TicketBroker and other contract addresses

## References

- [Livepeer Studio / Gateway docs](https://livepeer.studio/docs)
- [TicketBroker (protocol)](https://github.com/livepeer/protocol/tree/master/contracts/job)
- [Orchestrator node setup](/v2/Orchestrators/portal)
- [Forum: LIP proposals](https://forum.livepeer.org/c/lips/)
- [Livepeer AI (ComfyStream, blog)](https://blog.livepeer.org/real-time-ai-comfyui) */}
