Skip to main content

regtest devnet (bitcoind + 2x lnd) manual runbook

Use this doc to run a local regtest devnet (Bitcoin Core + two lnd nodes). It exercises go-lcpd Lightning integration (custom messages, invoice binding, payments). This devnet is managed by ./scripts/devnet. It stores all state/logs under ./.data/devnet/ (gitignored). If a step fails, check logs/state under ./.data/devnet/. Re-run with LCPD_LOG_LEVEL=debug. This doc runs a minimal Quote → Pay → Stream flow with two roles:
  • Alice: Provider. Configure Provider mode only on Alice. Use LCPD_BACKEND=deterministic to avoid external APIs.
  • Bob: Requester.

Prerequisites

  • Go 1.24.4+
  • bitcoind / bitcoin-cli on PATH
  • lnd / lncli on PATH
  • jq on PATH (used in the commands below)

Build go-lcpd CLI tools (one-time)

Install binaries into go-lcpd/bin/:
cd go-lcpd
mkdir -p bin
GOBIN="$PWD/bin" go install ./tools/lcpd-grpcd ./tools/lcpdctl ./tools/lcpd-oneshot

Start / stop

Start:
cd go-lcpd
./scripts/devnet up
Status:
./scripts/devnet status
./scripts/devnet info
Stop:
./scripts/devnet down

Wallet init / unlock (first time)

lnd can start without an initialized wallet, but you must create a wallet before using RPCs (payments/channels/invoices). First time (interactive):
./scripts/devnet lncli alice create
./scripts/devnet lncli bob create
If unlock is required after restart (interactive):
./scripts/devnet lncli alice unlock
./scripts/devnet lncli bob unlock
Smoke check:
./scripts/devnet lncli alice getinfo
./scripts/devnet lncli bob getinfo

Fund on-chain (make Alice the miner)

  1. Create a receive address for Alice:
ADDR="$(./scripts/devnet lncli alice newaddress p2wkh | jq -r .address)"
echo "$ADDR"
  1. Mine blocks on regtest to fund Alice (mine 101 blocks to satisfy coinbase maturity):
./scripts/devnet bitcoin-cli generatetoaddress 101 "$ADDR"
./scripts/devnet lncli alice walletbalance

Connect nodes / open channel / pay (make Bob able to pay Alice)

1) Connect to Bob

BOB_PUBKEY="$(./scripts/devnet lncli bob getinfo | jq -r .identity_pubkey)"
BOB_P2P_ADDR="$(./scripts/devnet paths bob | awk -F= '/^p2p_addr=/{print $2}')"

./scripts/devnet lncli alice connect "${BOB_PUBKEY}@${BOB_P2P_ADDR}"
./scripts/devnet lncli alice listpeers

2) Open a channel from Alice → Bob (push funds so Bob can pay)

./scripts/devnet lncli alice openchannel --node_key "$BOB_PUBKEY" --local_amt 10000000 --push_amt 5000000
./scripts/devnet bitcoin-cli generatetoaddress 3 "$ADDR"
./scripts/devnet lncli alice listchannels
./scripts/devnet lncli bob listchannels

# confirm Bob has enough outbound balance to pay Alice (assumes `push_amt` shows up on Bob)
./scripts/devnet lncli bob channelbalance

3) Bob pays an invoice from Alice (connectivity/route check)

PAY_REQ="$(./scripts/devnet lncli alice addinvoice --amt 1000 | jq -r .payment_request)"
./scripts/devnet lncli bob payinvoice "$PAY_REQ"

Try go-lcpd (custom messages / Quote → Pay → Stream)

Once the two lnd nodes are connected as peers, start go-lcpd on both sides. This triggers lcp_manifest exchange over BOLT #1 custom messages. You can observe it via ListLCPPeers. In this walkthrough, Alice runs as a Provider, streams the result, and sends lcp_result without external dependencies. It uses LCPD_BACKEND=deterministic. Provider configuration is YAML-first (LCPD_PROVIDER_CONFIG_PATH).

0) Create a Provider YAML config

Example: go-lcpd/provider.devnet.yaml
cd go-lcpd
cat > provider.devnet.yaml <<'YAML'
enabled: true
quote_ttl_seconds: 300

llm:
  max_output_tokens: 512
  models:
    gpt-5.2:
      price:
        # regtest example pricing (choose any policy you like)
        input_msat_per_mtok: 1
        output_msat_per_mtok: 1
YAML

1) Start go-lcpd on Alice (Provider / separate terminal)

cd go-lcpd

export LCPD_BACKEND=deterministic
export LCPD_LOG_LEVEL=debug

export LCPD_PROVIDER_CONFIG_PATH="$PWD/provider.devnet.yaml"

export LCPD_LND_RPC_ADDR="$(./scripts/devnet paths alice | awk -F= '/^rpc_addr=/{print $2}')"
export LCPD_LND_TLS_CERT_PATH="$(./scripts/devnet paths alice | awk -F= '/^tls_cert_path=/{print $2}')"

./bin/lcpd-grpcd -grpc_addr=127.0.0.1:23051

2) Start go-lcpd on Bob (Requester-only / separate terminal)

cd go-lcpd
export LCPD_BACKEND=disabled
export LCPD_LOG_LEVEL=debug

export LCPD_LND_RPC_ADDR="$(./scripts/devnet paths bob | awk -F= '/^rpc_addr=/{print $2}')"
export LCPD_LND_TLS_CERT_PATH="$(./scripts/devnet paths bob | awk -F= '/^tls_cert_path=/{print $2}')"

./bin/lcpd-grpcd -grpc_addr=127.0.0.1:23052

3) Call ListLCPPeers (confirm manifest exchange)

cd go-lcpd
./bin/lcpdctl lcpd list-lcp-peers -s 127.0.0.1:23052 -o prettyjson
If you can see gpt-5.2 under peers[0].remoteManifest.supportedTasks[].openaiChatCompletionsV1.model, it means the Provider successfully advertised the model.

4) Send one job from Bob to Alice (Quote → Pay → Stream)

cd go-lcpd

ALICE_PUBKEY="$(./scripts/devnet lncli alice getinfo | jq -r .identity_pubkey)"

./bin/lcpd-oneshot \
  -server-addr 127.0.0.1:23052 \
  -peer-id "$ALICE_PUBKEY" \
  -pay-invoice \
  -model gpt-5.2 \
  -prompt "Say hello in one word." \
  -timeout 60s

Logs / data locations

  • state/log: ./.data/devnet/
  • bitcoind log: ./.data/devnet/logs/bitcoind.log
  • lnd log: ./.data/devnet/logs/lnd-alice.log / ./.data/devnet/logs/lnd-bob.log
To rebuild from scratch (destructive):
./scripts/devnet down
rm -rf ./.data/devnet