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

# Contributor quickstart

> Get the Livepeer protocol running locally, set up a testnet with one orchestrator and one gateway, and submit your first test job.

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

<CenteredContainer preset="readable90">
  <Tip>This quickstart gets you from zero to a running local testnet in approximately 60 minutes. All traffic stays local -- no mainnet funds, no real LPT required.</Tip>
</CenteredContainer>

<CustomDivider />

The Livepeer Protocol node is `go-livepeer`. It is the entry point for all protocol contributions: video transcoding, AI pipeline integration, payment mechanics, and on-chain interactions. The local testnet simulates the full network on your machine.

<CustomDivider />

## Prerequisites

* **Go 1.21 or later** -- `go version`
* **Docker** -- required for AI worker development
* **FFmpeg** -- for generating test video streams
* **git**

Optional: NVIDIA GPU with CUDA 12+ for AI pipeline testing.

Install FFmpeg:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
# macOS
brew install ffmpeg

# Ubuntu / Debian
sudo apt-get install ffmpeg
```

<CustomDivider />

## Get the codebase running

<StyledSteps iconColor="#2d9a67" titleColor="var(--accent)">
  <StyledStep title="Clone and build go-livepeer" icon="download">
    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    git clone https://github.com/livepeer/go-livepeer.git
    cd go-livepeer

    # Build the binary
    make
    # Output: ./livepeer
    ```

    The build takes 2-5 minutes on first run. Subsequent builds are incremental.

    Verify the build:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    ./livepeer --version
    # Livepeer version: v0.8.10
    ```
  </StyledStep>

  <StyledStep title="Start a local Ethereum node" icon="link">
    The testnet uses a local Ethereum node (hardhat or geth dev mode). The easiest path is using the provided Docker compose setup:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    # From the go-livepeer repo root
    docker compose -f docker/docker-compose.yml up -d eth-node
    ```

    This starts a local Ethereum node on `http://localhost:8545` pre-funded with test accounts.

    Check it is running:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    curl -X POST http://localhost:8545 \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
    # Expect: {"result":"0x0",...}
    ```
  </StyledStep>

  <StyledStep title="Deploy testnet contracts" icon="file-contract">
    Deploy the Livepeer Protocol contracts to your local Ethereum node:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    cd docker
    ./deploy-contracts.sh
    # Outputs contract addresses -- save these
    ```

    The script deploys the full protocol: BondingManager, RoundsManager, Minter, TicketBroker, and Governor. Contract addresses are written to `deployments/devnet/` in the repo.

    For a full walkthrough of contract deployment options, see the <a href="/v2/developers/guides/local-development/local-testnet">testnet contract deployment guide</a>.
  </StyledStep>

  <StyledStep title="Start an orchestrator" icon="server">
    Open a new terminal. Start `go-livepeer` in Orchestrator mode pointing at your local Ethereum node:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    ./livepeer \
      -orchestrator \
      -network devnet \
      -ethUrl http://localhost:8545 \
      -ethAcctAddr <your-test-account-address> \
      -pricePerUnit 1 \
      -serviceAddr 127.0.0.1:8935
    ```

    The Orchestrator starts on `127.0.0.1:8935` and registers with the local network. You should see:

    ```
    [INFO] Orchestrator registered: 127.0.0.1:8935
    [INFO] Waiting for jobs...
    ```
  </StyledStep>

  <StyledStep title="Start a gateway" icon="gateway">
    Open a third terminal. Start `go-livepeer` in Gateway mode:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    ./livepeer \
      -gateway \
      -network devnet \
      -ethUrl http://localhost:8545 \
      -ethAcctAddr <another-test-account-address> \
      -orchAddr 127.0.0.1:8935
    ```

    The Gateway connects to the local Orchestrator and is ready to accept jobs on `http://127.0.0.1:8935`.
  </StyledStep>

  <StyledStep title="Submit a test job" icon="flask">
    Send a test transcoding job to the Gateway using FFmpeg:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    # Generate a 10-second test stream
    ffmpeg -re -f lavfi -i testsrc=size=640x480:rate=30 \
      -t 10 -c:v libx264 -preset fast \
      -f rtmp rtmp://127.0.0.1:1935/live/test

    # The gateway transcodes it and outputs to a local HLS segment
    ```

    Check the Orchestrator terminal -- you should see job processing logs. Check the Gateway terminal for segment delivery confirmation.
  </StyledStep>
</StyledSteps>

<CustomDivider />

## Next steps

With a running local testnet, three productive paths exist:

**Fix a bug** -- Browse [good first issues](https://github.com/livepeer/go-livepeer/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) in go-livepeer. The local testnet lets you reproduce issues and verify fixes without mainnet funds.

**Add an AI pipeline** -- The `ai-runner` repository (Python) handles AI inference. Clone it separately, test a new pipeline locally, and integrate it with go-livepeer via the AI worker interface.

**Contribute to ComfyStream or PyTrickle** -- Python repositories with lower Go prerequisite. Good entry points for AI pipeline contributors.

<CustomDivider />

## Related pages

<CardGroup cols={2}>
  <Card title="Contribution Guide" icon="code-branch" href="/v2/developers/resources/contributing">
    PR process, coding standards, and how to contribute by repo.
  </Card>

  <Card title="Local Testnet Deployment" icon="server" href="/v2/developers/guides/local-development/local-testnet">
    Full testnet setup with AI worker and advanced configuration.
  </Card>

  <Card title="OSS Contributions" icon="people-group" href="/v2/developers/resources/contributing">
    Open issues, bounties, and contribution paths by repository.
  </Card>
</CardGroup>
