In this stage of the tutorial, we will learn how to set up a local cluster of nodes Alice, Bob, and Charlie, have them talk to each other, set up channels, and route payments between one another. We will also establish a baseline understanding of the different components that must work together as part of developing on lnd.

This tutorial assumes you have completed installation of Go, btcd, and lnd on simnet. If not, please refer to the installation instructions. Note that for the purposes of this tutorial it is not necessary to sync testnet, and the last section of the installation instructions you’ll need to complete is “Installing btcd.”

The schema will be the following. Keep in mind that you can easily extend this network to include additional nodes David, Eve, etc. by simply running more local lnd instances.

   (1)                        (1)                         (1)
+ ----- +                   + --- +                   + ------- +
| Alice | <--- channel ---> | Bob | <--- channel ---> | Charlie |
+ ----- +                   + --- +                   + ------- +
    |                          |                           |
    |                          |                           |
    + - - - -  - - - - - - - - + - - - - - - - - - - - - - +
                      + --------------- +
                      | BTC/LTC network | <--- (2)
                      + --------------- +

Understanding the components


lnd is the main component that we will interact with. lnd stands for Lightning Network Daemon, and handles channel opening/closing, routing and sending payments, and managing all the Lightning Network state that is separate from the underlying Bitcoin network itself.

Running an lnd node means that it is listening for payments, watching the blockchain, etc. By default it is awaiting user input.

lncli is the command line client used to interact with your lnd nodes. Typically, each lnd node will be running in its own terminal window, so that you can see its log outputs. lncli commands are thus run from a different terminal window.


btcd represents the gateway that lnd nodes will use to interact with the Bitcoin / Litecoin network. lnd needs btcd for creating on-chain addresses or transactions, watching the blockchain for updates, and opening/closing channels. In our current schema, all three of the nodes are connected to the same btcd instance. In a more realistic scenario, each of the lnd nodes will be connected to their own instances of btcd or equivalent.

We will also be using simnet instead of testnet. Simnet is a development/test network run locally that allows us to generate blocks at will, so we can avoid the time-consuming process of waiting for blocks to arrive for any on-chain functionality.

Setting up our environment

Developing on lnd can be quite complex since there are many more moving pieces, so to simplify that process, we will walk through a recommended workflow.

Running btcd

Let’s start by running btcd, if you don’t have it up already. Open up a new terminal window, ensure you have your $GOPATH set, and run:

btcd --txindex --simnet --rpcuser=kek --rpcpass=kek

(Note: this tutorial requires opening quite a few terminal windows. It may be convenient to use multiplexers such as tmux or screen if you’re familiar with them.)

Breaking down the components:

  • --txindex is required so that the lnd client is able to query historical transactions from btcd.
  • --simnet specifies that we are using the simnet network. This can be changed to --testnet, or omitted entirely to connect to the actual Bitcoin / Litecoin network.
  • --rpcuser and rpcpass sets a default password for authenticating to the btcd instance.

Starting lnd (Alice’s node)

Now, let’s set up the three lnd nodes. To keep things as clean and separate as possible, open up a new terminal window, ensure you have $GOPATH set and $GOPATH/bin in your PATH, and create a new directory under $GOPATH called dev that will represent our development space. We will create separate folders to store the state for alice, bob, and charlie, and run all of our lnd nodes on different localhost ports instead of using Docker to make our networking a bit easier.

# Create our development space
mkdir dev
cd dev

# Create folders for each of our nodes
mkdir alice bob charlie

The directory structure should now look like this:

$ tree $GOPATH -L 2

├── bin
│   └── ...
├── dev
│   ├── alice
│   ├── bob
│   └── charlie
├── pkg
│   └── ...
├── rpc
│   └── ...
└── src
    └── ...

Start up the Alice node from within the alice directory:

cd $GOPATH/dev/alice
alice$ lnd --rpclisten=localhost:10001 --listen=localhost:10011 --restlisten=localhost:8001 --datadir=data --logdir=log --debuglevel=info --bitcoin.simnet --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek 

The Alice node should now be running and displaying output ending with a line beginning with “Waiting for wallet encryption password.”

Breaking down the components:

  • --rpclisten: The host:port to listen for the RPC server. This is the primary way an application will communicate with lnd
  • --listen: The host:port to listen on for incoming P2P connections. This is at the networking level, and is distinct from the Lightning channel networks and Bitcoin/Litcoin network itself.
  • --restlisten: The host:port exposing a REST api for interacting with lnd over HTTP. For example, you can get Alice’s channel balance by making a GET request to localhost:8001/v1/channels. This is not needed for this tutorial, but you can see some examples here.
  • --datadir: The directory that lnd’s data will be stored inside
  • --logdir: The directory to log output.
  • --debuglevel: The logging level for all subsystems. Can be set to trace, debug, info, warn, error, critical.
  • --bitcoin.simnet: Specifies whether to use simnet or testnet
  • Specifies that bitcoin is active. Can also include to activate Litecoin.
  • --bitcoin.node=btcd: Use the btcd full node to interface with the blockchain. Note that when using Litecoin, the option is --litecoin.node=btcd.
  • --btcd.rpcuser and --btcd.rpcpass: The username and password for the btcd instance. Note that when using Litecoin, the options are --ltcd.rpcuser and --ltcd.rpcpass.

Starting Bob’s node and Charlie’s node

Just as we did with Alice, start up the Bob node from within the bob directory, and the Charlie node from within the charlie directory. Doing so will configure the datadir and logdir to be in separate locations so that there is never a conflict.

Keep in mind that for each additional terminal window you set, you will need to set $GOPATH and include $GOPATH/bin in your PATH. Consider creating a setup script that includes the following lines:

export GOPATH=~/gocode # if you exactly followed the install guide
export PATH=$PATH:$GOPATH/bin

and run it every time you start a new terminal window working on lnd.

Run Bob and Charlie:

# In a new terminal window
cd $GOPATH/dev/bob
bob$ lnd --rpclisten=localhost:10002 --listen=localhost:10012 --restlisten=localhost:8002 --datadir=data --logdir=log --debuglevel=info --bitcoin.simnet --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek 

# In another terminal window
cd $GOPATH/dev/charlie
charlie$ lnd --rpclisten=localhost:10003 --listen=localhost:10013 --restlisten=localhost:8003 --datadir=data --logdir=log --debuglevel=info --bitcoin.simnet --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek

Configuring lnd.conf

To skip having to type out a bunch of flags on the command line every time, we can instead modify our lnd.conf, and the arguments specified therein will be loaded into lnd automatically. Any additional configuration added as a command line argument will be applied after reading from lnd.conf, and will overwrite the lnd.conf option if applicable.

  • On MacOS, lnd.conf is located at: /Users/[username]/Library/Application\ Support/Lnd/lnd.conf
  • On Linux: ~/.lnd/lnd.conf

Here is an example lnd.conf that can save us from re-specifying a bunch of command line options:

[Application Options]



Now, when we start nodes, we only have to type

alice$ lnd --rpclisten=localhost:10001 --listen=localhost:10011 --restlisten=localhost:8001
bob$ lnd --rpclisten=localhost:10002 --listen=localhost:10012 --restlisten=localhost:8002
charlie$ lnd --rpclisten=localhost:10003 --listen=localhost:10013 --restlisten=localhost:8003


Working with lncli and authentication

Now that we have our lnd nodes up and running, let’s interact with them! To control lnd we will need to use lncli, the command line interface.

lnd uses macaroons for authentication to the rpc server. lncli typically looks for an admin.macaroon file in the Lnd home directory, but since we changed the location of our application data, we have to set --macaroonpath in the following command. To disable macaroons, pass the --no-macaroons flag into both lncli and lnd.

lnd allows you to encrypt your wallet with a passphrase and optionally encrypt your cipher seed passphrase as well. This can be turned off by passing --noencryptwallet into lnd or lnd.conf. We recommend going through this process at least once to familiarize yourself with the security and authentication features around lnd.

We will test our rpc connection to the Alice node. Notice that in the following command we specify the --rpcserver here, which corresponds to --rpcport=10001 that we set when starting the Alice lnd node.

Open up a new terminal window, set $GOPATH and include $GOPATH/bin in your PATH as usual. Let’s create Alice’s wallet and set her passphrase:

cd $GOPATH/dev/alice
alice$ lncli --rpcserver=localhost:10001 --macaroonpath=data/admin.macaroon create

You’ll be asked to input and confirm a wallet password for Alice, which must be longer than 8 characters. You also have the option to add a passphrase to your cipher seed. For now, just skip this step by entering “n” when prompted about whether you have an existing mnemonic, and pressing enter to proceed without the passphrase.

You can now request some basic information as follows:

alice$ lncli --rpcserver=localhost:10001 --macaroonpath=data/admin.macaroon getinfo

lncli just made an RPC call to the Alice lnd node. This is a good way to test if your nodes are up and running and lncli is functioning properly. Note that in future sessions you may need to call lncli unlock to unlock the node with the password you just set.

Open up new terminal windows and do the same for Bob and Charlie. alice$ or bob$ denotes running the command from the Alice or Bob lncli window respectively.

# In a new terminal window, setting $GOPATH, etc.
cd $GOPATH/dev/bob
bob$ lncli --rpcserver=localhost:10002 --macaroonpath=data/admin.macaroon create
# Note that you'll have to enter an 8+ character password and "n" for the mnemonic.

# In a new terminal window:
cd $GOPATH/dev/charlie
charlie$ lncli --rpcserver=localhost:10003 --macaroonpath=data/admin.macaroon create
# Note that you'll have to enter an 8+ character password and "n" for the mnemonic.

To avoid typing the --rpcserver=localhost:1000X and --macaroonpath flag every time, we can set some aliases. Add the following to your .bashrc:

alias lncli-alice="lncli --rpcserver=localhost:10001 --macaroonpath=data/admin.macaroon"
alias lncli-bob="lncli --rpcserver=localhost:10002 --macaroonpath=data/admin.macaroon"
alias lncli-charlie="lncli --rpcserver=localhost:10003 --macaroonpath=data/admin.macaroon"

To make sure this was applied to all of your current terminal windows, rerun your .bashrc file:

alice$ source ~/.bashrc
bob$ source ~/.bashrc
charlie$ source ~/.bashrc

For simplicity, the rest of the tutorial will assume that this step was complete.

lncli options

To see all the commands available for lncli, simply type lncli --help or lncli -h.

Setting up Bitcoin addresses

Let’s create a new Bitcoin address for Alice. This will be the address that stores Alice’s on-chain balance. np2wkh specifes the type of address and stands for Pay to Nested Witness Key Hash.

alice$ lncli-alice newaddress np2wkh
    "address": <ALICE_ADDRESS>

And for Bob and Charlie:

bob$ lncli-bob newaddress np2wkh
    "address": <BOB_ADDRESS>

charlie$ lncli-charlie newaddress np2wkh
    "address": <CHARLIE_ADDRESS>

Funding Alice

That’s a lot of configuration! At this point, we’ve generated onchain addresses for Alice, Bob, and Charlie. Now, we will get some practice working with btcd and fund these addresses with some simnet Bitcoin.

Quit btcd and re-run it, setting Alice as the recipient of all mining rewards:

btcd --simnet --txindex --rpcuser=kek --rpcpass=kek --miningaddr=<ALICE_ADDRESS>

Generate 400 blocks, so that Alice gets the reward. We need at least 100 blocks because coinbase funds can’t be spent until after 100 confirmations, and we need about 300 to activate segwit. In a new window with $GOPATH and $PATH set:

alice$ btcctl --simnet --rpcuser=kek --rpcpass=kek generate 400

Check that segwit is active:

btcctl --simnet --rpcuser=kek --rpcpass=kek getblockchaininfo | grep -A 1 segwit

Check Alice’s wallet balance.

alice$ lncli-alice walletbalance

It’s no fun if only Alice has money. Let’s give some to Charlie as well.

# Quit the btcd process that was previously started with Alice's mining address,
# and then restart it with:
btcd --txindex --simnet --rpcuser=kek --rpcpass=kek --miningaddr=<CHARLIE_ADDRESS>

# Generate more blocks
btcctl --simnet --rpcuser=kek --rpcpass=kek generate 100

# Check Charlie's balance
charlie$ lncli-charlie walletbalance

Creating the P2P Network

Now that Alice and Charlie have some simnet Bitcoin, let’s start connecting them together.

Connect Alice to Bob:

# Get Bob's identity pubkey:
bob$ lncli-bob getinfo
    ----->"identity_pubkey": <BOB_PUBKEY>,
    "alias": "",
    "num_pending_channels": 0,
    "num_active_channels": 0,
    "num_peers": 0,
    "block_height": 450,
    "block_hash": "2a84b7a2c3be81536ef92cf382e37ab77f7cfbcf229f7d553bb2abff3e86231c",
    "synced_to_chain": true,
    "testnet": false,
    "chains": [

# Connect Alice and Bob together
alice$ lncli-alice connect <BOB_PUBKEY>@localhost:10012


Notice that localhost:10012 corresponds to the --peerport=10012 flag we set when starting the Bob lnd node.

Let’s check that Alice and Bob are now aware of each other.

# Check that Alice has added Bob as a peer:
alice$ lncli-alice listpeers
    "peers": [
            "pub_key": <BOB_PUBKEY>,
            "address": "",
            "bytes_sent": "7",
            "bytes_recv": "7",
            "sat_sent": "0",
            "sat_recv": "0",
            "inbound": false,
            "ping_time": "0"

# Check that Bob has added Alice as a peer:
bob$ lncli-bob listpeers
    "peers": [
            "pub_key": <ALICE_PUBKEY>,
            "address": "",
            "bytes_sent": "318",
            "bytes_recv": "318",
            "sat_sent": "0",
            "sat_recv": "0",
            "inbound": true,
            "ping_time": "5788"

Finish up the P2P network by connecting Bob to Charlie:

charlie$ lncli-charlie connect <BOB_PUBKEY>@localhost:10012

Setting up Lightning Network

Before we can send payment, we will need to set up payment channels from Alice to Bob, and Bob to Charlie.

First, let’s open the Alice<–>Bob channel.

alice$ lncli-alice openchannel --node_key=<BOB_PUBKEY> --local_amt=1000000
  • --local_amt specifies the amount of money that Alice will commit to the channel. To see the full list of options, you can try lncli openchannel --help.

We now need to mine six blocks so that the channel is considered valid:

btcctl --simnet --rpcuser=kek --rpcpass=kek generate 6

Check that Alice<–>Bob channel was created:

alice$ lncli-alice listchannels
    "channels": [
            "active": true,
            "remote_pubkey": <BOB_PUBKEY>,
            "channel_point": "2622b779a8acca471a738b0796cd62e4457b79b33265cbfa687aadccc329023a:0",
            "chan_id": "495879744192512",
            "capacity": "1000000",
            "local_balance": "991312",
            "remote_balance": "0",
            "commit_fee": "8688",
            "commit_weight": "600",
            "fee_per_kw": "12000",
            "unsettled_balance": "0",
            "total_satoshis_sent": "0",
            "total_satoshis_received": "0",
            "num_updates": "0"

Sending single hop payments

Finally, to the exciting part - sending payments! Let’s send a payment from Alice to Bob.

First, Bob will need to generate an invoice:

bob$ lncli-bob addinvoice --amt=10000
        "r_hash": "<a_random_rhash_value>",
        "pay_req": "<encoded_invoice>",

Send the payment from Alice to Bob:

alice$ lncli-alice sendpayment --pay_req=<encoded_invoice>
	"payment_preimage": "baf6929fc95b3824fb774a4b75f6c8a1ad3aaef04efbf26cc064904729a21e28",
	"payment_route": {
		"total_time_lock": 1,
		"total_amt": 10000,
		"hops": [
				"chan_id": 495879744192512,
				"chan_capacity": 1000000,
				"amt_to_forward": 10000

# Check that Alice's channel balance was decremented accordingly:
alice$ lncli-alice listchannels

# Check that Bob's channel was credited with the payment amount:
bob$ lncli-bob listchannels

Multi-hop payments

Now that we know how to send single-hop payments, sending multi hop payments is not that much more difficult. Let’s set up a channel from Bob<–>Charlie:

charlie$ lncli-charlie openchannel --node_key=<BOB_PUBKEY> --local_amt=800000 --push_amt=200000

# Mine the channel funding tx
btcctl --simnet --rpcuser=kek --rpcpass=kek generate 6

Note that this time, we supplied the --push_amt argument, which specifies the amount of money we want to other party to have at the first channel state.

Let’s make a payment from Alice to Charlie by routing through Bob:

charlie$ lncli-charlie addinvoice --amt=10000
alice$ lncli-alice sendpayment --pay_req=<encoded_invoice>

# Check that Charlie's channel was credited with the payment amount (e.g. that
# the `remote_balance` has been decremented by 10000)
charlie$ lncli-charlie listchannels

Closing channels

For practice, let’s try closing a channel. We will reopen it in the next stage of the tutorial.

alice$ lncli-alice listchannels
    "channels": [
            "active": true,
            "remote_pubkey": "0343bc80b914aebf8e50eb0b8e445fc79b9e6e8e5e018fa8c5f85c7d429c117b38",
       ---->"channel_point": "3511ae8a52c97d957eaf65f828504e68d0991f0276adff94c6ba91c7f6cd4275:0",
            "chan_id": "1337006139441152",
            "capacity": "1005000",
            "local_balance": "990000",
            "remote_balance": "10000",
            "unsettled_balance": "0",
            "total_satoshis_sent": "10000",
            "total_satoshis_received": "0",
            "num_updates": "2"

The Channel point consists of two numbers separated by a colon, which uniquely identifies the channel. The first number is funding_txid and the second number is output_index.

# Close the Alice<-->Bob channel from Alice's side.
alice$ lncli-alice closechannel --funding_txid=<funding_txid> --output_index=<output_index>

# Mine a block including the channel close transaction to close the channel:
btcctl --simnet --rpcuser=kek --rpcpass=kek generate 1

# Check that Bob's on-chain balance was credited by his settled amount in the
# channel. Recall that Bob previously had no on-chain Bitcoin:
alice$ lncli-bob walletbalance
    "balance": "20001"

At this point, you’ve learned how to work with btcd, btcctl, lnd, and lncli. In Stage 2, we will learn how to set up and interact with lnd using a web GUI client.

In the future, you can try running through this workflow on testnet instead of simnet, where you can interact with and send payments through the testnet Lightning Faucet node. For more information, see the “Connect to faucet lightning node” section in the Docker guide or check out the Lightning Network faucet repository.