Home

Awesome

charge-lnd

This script matches your open Lightning channels against a number of customizable criteria and applies channel fees based on the matching policy.

Installation

See INSTALL.md

Usage

charge-lnd takes only a minimal set of parameters:

usage: charge-lnd [-h] [--lnddir LNDDIR] [--tlscert TLS_CERT_PATH] [--macaroon MACAROON_PATH] [--grpc GRPC]
                  [--circuitbreaker CIRCUITBREAKER] [--dry-run] [--check] [-v] [-vv] -c CONFIG

optional arguments:
  -h, --help            show this help message and exit
  --lnddir LNDDIR       (default ~/.lnd) lnd directory
  --tlscert TLS_CERT_PATH
                        (default [lnddir]/tls.cert) path to lnd TLS certificate
  --macaroon MACAROON_PATH
                        (default [lnddir]/data/chain/bitcoin/mainnet/charge-lnd.macaroon) path to lnd auth macaroons
  --grpc GRPC           (default localhost:10009) lnd gRPC endpoint
  --circuitbreaker CIRCUITBREAKER
                        (optional, no default) circuitbreaker gRPC endpoint host:port
  --dry-run             Do not perform actions (for testing), print what we would do to
                        stdout
  --check               Do not perform actions, only check config file for valid syntax
  -v, --verbose         Be more verbose
  -vv, --very-verbose   Be very verbose, print every matched policy
  -c CONFIG, --config CONFIG
                        path to config file

All policies are defined using an INI style config file.

Each [policy-name] defined in the config file describes a policy. A single policy consists of;

The defined criteria are compared against the open channels and their associated nodes. The fee strategy then executed to determine what the new channel fees should be.

There is a special [default] section, that will be used if none of the policies match a channel. The [default] section only contains a strategy, not any matching criteria.

All policies are evaluated top to bottom. The first matching policy is applied (except for the default policy).

A simple example:

[example-policy]
chan.min_capacity = 500000

strategy = static
base_fee_msat = 1000
fee_ppm = 10

This policy matches the channels against the chan.min_capacity criterium. Only channels with at least 500000 sats total capacity will match.

If a channel matches this policy, the static strategy is then used, which takes the base_fee_msat and fee_ppm properties defined in the policy and applies them to the channel.

If at least lnd 0.18 is used, charge-lnd also supports the experimental support of inbound fees. By default, lnd only supports negative inbound fees on the inbound channel, which then act as a “discount” on the outbound fees of the outgoing channel. However, the entire forward fee cannot become negative.

Example with inbound fees:

[example-policy]
chan.min_capacity = 500000

strategy = static
base_fee_msat = 1000
fee_ppm = 2000
inbound_base_fee_msat = -500
inbound_fee_ppm = -1000

Non-final policies

You can also define a 'non-final' policy. This is simply a policy without a strategy. It allows you to set default values for properties used by later policies, e.g. base_fee_msat, fee_ppm, min_fee_ppm_delta etc.

Processing continues after matching a non-final policy.

Example:

[mydefaults]
chan.max_capacity = 5_000_000
min_fee_ppm_delta = 10
base_fee_msat = 2000

[someotherpolicy]
chan.min_capacity = 500_000
strategy = static
fee_ppm = 50

This will, for channels that match mydefaults and after that someotherpolicy, set fees to 2 sat base fee and 50 ppm, and uses a minimum fee delta of 10 when applying a fee change for that channel.

More examples

Maintain a friends list with lower fees:

[friends]
node.id = file:///home/lnd/friends.list
strategy = static
base_fee_msat = 10
fee_ppm = 10

Use routing fees to nudge channel balances toward 50/50 channel ratios:

[discourage-routing-out-of-balance]
chan.max_ratio = 0.1
chan.min_capacity = 250000
strategy = static
base_fee_msat = 10000
fee_ppm = 500
inbound_base_fee_msat = -8000
inbound_fee_ppm = -400

[encourage-routing-to-balance]
chan.min_ratio = 0.9
chan.min_capacity = 250000
strategy = static
base_fee_msat = 1
fee_ppm = 2

More elaborate examples can be found in the examples folder.

Properties

Currently available properties:

PropertyDescriptionValues
chan.idmatch on channel IDs - comma separated list of channel IDs and/or file references<channel ID|file url>[, <channel ID|file url>..]
chan.initiatormatch on initiator status, true if we are initiatortrue|false
chan.privatematch on channel private flagtrue|false
chan.min_capacitymatch on channel capacity# of sats
chan.max_capacitymatch on channel capacity# of sats
chan.min_agematch on channel age# of blocks
chan.max_agematch on channel age# of blocks
BALANCE
chan.max_ratiomatch on channel ratio0..1
chan.min_ratiomatch on channel ratio0..1
chan.min_local_balancematch on channel local balance# of sats
chan.max_local_balancematch on channel local balance# of sats
chan.min_remote_balancematch on channel remote balance# of sats
chan.max_remote_balancematch on channel remote balance# of sats
PEER
chan.min_base_fee_msatmatch on channel peer policy# of msats
chan.max_base_fee_msatmatch on channel peer policy# of msats
chan.min_fee_ppmmatch on channel peer policy0..1000000 (parts per million)
chan.max_fee_ppmmatch on channel peer policy0..1000000 (parts per million)
chan.disabledmatch on channel disabled by peertrue|false
ACTIVITY
chan.activity_perioddefine a time period for forwards#s seconds or #m minutes or #h hours or #d days
chan.activity_period_ignore_channel_agematch on channel age less than activity_periodtrue|false
chan.min_htlcs_inmatch on minimal amount of HTLCs arriving in channel during activity period# of htlcs
chan.max_htlcs_inmatch on maximum amount of HTLCs arriving in channel during activity period# of htlcs
chan.min_htlcs_outmatch on minimal amount of HTLCs departing from channel during activity period# of htlcs
chan.max_htlcs_outmatch on maximum amount of HTLCs departing from channel during activity period# of htlcs
chan.min_sats_inmatch on minimal amount of sats arriving in channel during activity period# of sats
chan.max_sats_inmatch on maximum amount of sats arriving in channel during activity period# of sats
chan.min_sats_outmatch on minimal amount of sats departing from channel during activity period# of sats
chan.max_sats_outmatch on maximum amount of sats departing from channel during activity period# of sats
chan.min_htlcs_ratiomatch on amount of HTLCs ratio arriving in channel during activity period0..1
chan.max_htlcs_ratiomatch on amount of HTLCs ratio arriving in channel during activity period0..1
chan.min_sats_ratiomatch on amount of sats ratio arriving in channel during activity period0..1
chan.max_sats_ratiomatch on amount of sats ratio arriving in channel during activity period0..1
chan.min_count_pending_htlcsmatch on the number of pending HTLCs in the channel# pending htlcs
chan.max_count_pending_htlcsmatch on the number of pending HTLCs in the channel# pending htlcs
chan.min_next_pending_htlc_expirymatch on the blocks until the next HTLC in the channel expires# blocks
chan.max_next_pending_htlc_expirymatch on the blocks until the next HTLC in the channel expires# blocks
NODE
node.idmatch on node pubkeys - comma separated list of node pubkeys and/or file references<node pubkey|file url>[, <node pubkey|file url>..]
node.min_channelsmatch on number of channels the peer node has# of channels
node.max_channelsmatch on number of channels the peer node has# of channels
node.min_shared_channels_activematch on number of active channels the peer node has with us# of channels
node.max_shared_channels_activematch on number of active channels the peer node has with us# of channels
node.min_shared_channels_inactivematch on number of inactive channels the peer node has with us# of channels
node.max_shared_channels_inactivematch on number of inactive channels the peer node has with us# of channels
node.min_capacitymatch on node total capacity# of sats
node.max_capacitymatch on node total capacity# of sats
node.min_shared_capacity_activematch on node active shared capacity with us# of sats
node.max_shared_capacity_activematch on node active shared capacity with us# of sats
node.min_shared_capacity_inactivematch on node inactive shared capacity with us# of sats
node.max_shared_capacity_inactivematch on node inactive shared capacity with us# of sats
node.max_shared_ratiomatch on channels ratio with us0..1
node.min_shared_ratiomatch on channels ratio with us0..1
node.max_shared_ratio_activematch on active channels ratio with us0..1
node.min_shared_ratio_activematch on active channels ratio with us0..1
node.max_shared_ratio_inactivematch on inactive channels ratio with us0..1
node.min_shared_ratio_inactivematch on inactive channels ratio with us0..1
ONCHAIN
onchain.conf_targetdefines the confirmation target that is used for the determination of the onchain fee rate (default: 6)# blocks
onchain.min_fee_ratematch on the onchain fee rate# sat per vbyte
onchain.max_fee_ratematch on the onchain fee rate# sat per vbyte
onchain.synced_to_chainmatch on the synced to chain. False if lnd is not synced to chain for 5 minutes.true|false

File references should contain 1 item per line

Strategies

Available strategies:

StrategyDescriptionParameters
ignoreignores the channel completely
ignore_feesdon't make any fee changes, only update htlc size limits and time_lock_delta
staticsets fixed base fee and fee rate values for the outbound and inbound side.fee_ppm<br>base_fee_msat<br>inbound_fee_ppm<br>inbound_base_fee_msat<br>inbound_level_ppm if set we calculate inbound_fee_ppm = min(0,inbound_level_ppm - fee_ppm)
match_peersets the same base fee and fee rate values as the peer for the outbound and inbound side.if base_fee_msat, fee_ppm, inbound_base_fee_msat or inbound_fee_ppm are set the override the peer values
costcalculate cost for opening channel, and set ppm to cover cost when channel depletes.cost_factor
onchain_feesets the fees to a % equivalent of a standard onchain payment. We use lnd's internal fee estimate, which is usually based on bitcoind's fee estimate.onchain_fee_btc BTC<br>within onchain_fee_numblocks blocks.
proportionalsets outbound fee ppm according to balancedness. Inbound Fees are set like using strategy static.min_fee_ppm<br>max_fee_ppm<br>sum_peer_chans consider all channels with peer for balance calculations
disabledisables the channel in the outgoing direction. Channel will be re-enabled again if it matches another policy (except when that policy uses an 'ignore' strategy).
use_configprocess channel according to rules defined in another config file.config_file

All strategies (except the ignore strategy) will apply the following properties if defined:

PropertyDescriptionValues
base_fee_msatBase fee# msat
min_htlc_msatMinimum size (in msat) of HTLC to allow# msat
max_htlc_msatMaximum size (in msat) of HTLC to allow# msat
max_htlc_msat_ratioMaximum size of HTLC to allow as a fraction of total channel capacity0..1
time_lock_deltaTime Lock Delta# blocks
min_fee_ppm_deltaMinimum change in fees (ppm) before updating channel (default: 0)ppm delta
min_inbound_fee_ppm_deltaMinimum change in inbound fees (ppm) before updating channel (default: min_fee_ppm_delta)ppm delta
cb_max_hourly_rateCircuitbreaker: maximum number of incoming htlcs per hour# hourly rate
cb_max_pendingCircuitbreaker: maximum number of incoming htlcs at the same time# incoming pending htlcs
cb_modeCircuitbreaker: mode (0 - FAIL; 1 - QUEUE; 2 - QUEUE_PEER_INITIATED; 3 - BLOCK)0..3
cb_clear_limitCircuitbreaker: delete the peer limit and fallback to the default limittrue

Circuitbreaker Support

Optionally, it is also possible to dynamically control the circuitbreaker limits for individual peers. However, the default limit of the circuitbreaker cannot currently be changed with charge-lnd.

One channel for the peer

If any of the properties cb_max_hourly_rate, cb_max_pending, or cb_mode are set, the node limit will be adjusted. It should be noted that 0 for the first two properties is interpreted as infinite. Those properties that are not currently set will be set according to the respective default limits. If no node limit is to be set, but a limit has already been set, the reset to the default limit can be performed by setting cb_clear_limit.

Multiple channels for the peer

It can happen that different properties for individual channels are chosen when there are multiple channels with one peer. Since the circuitbreaker monitors its limits at the peer level, we aggregate the properties into a node limit:

Contributing

Contributions are highly welcome! Feel free to submit issues and pull requests. See development guide for help getting started.

Please also consider opening a channel with my node, or sending tips via keysend:

0266ad254117f16f16c3457e081e6207e91c5e414477a208cf4d9c633322799038@lightning.channels.zijn.cool:9735