├── .github
└── workflows
│ ├── on-push-github.yml
│ └── on-tag-github.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── af.py
├── controller.py
├── delete_payments.py
├── docker-compose.yaml
├── gui
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── lnd_deps
│ ├── lightning_pb2.py
│ ├── lightning_pb2_grpc.py
│ ├── lnd_connect.py
│ ├── router_pb2.py
│ ├── router_pb2_grpc.py
│ ├── signer_pb2.py
│ ├── signer_pb2_grpc.py
│ ├── walletkit_pb2.py
│ ├── walletkit_pb2_grpc.py
│ ├── wtclient_pb2.py
│ └── wtclient_pb2_grpc.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20210923_1538.py
│ ├── 0003_auto_20210927_1857.py
│ ├── 0004_channels_local_commit.py
│ ├── 0005_alter_invoices_settle_date.py
│ ├── 0006_auto_20210928_1641.py
│ ├── 0007_auto_20211002_1857.py
│ ├── 0008_channels_ar_target.py
│ ├── 0009_onchain.py
│ ├── 0010_rebalancer_payment_hash.py
│ ├── 0011_pendinghtlcs.py
│ ├── 0012_auto_20211018_1637.py
│ ├── 0013_rebalancer_target_alias.py
│ ├── 0014_failedhtlcs.py
│ ├── 0015_invoices_index.py
│ ├── 0016_auto_20220106_1026.py
│ ├── 0017_autopilot.py
│ ├── 0018_auto_20220114_2218.py
│ ├── 0019_auto_20220122_1009.py
│ ├── 0020_auto_20220126_2113.py
│ ├── 0021_auto_20220221_1309.py
│ ├── 0022_auto_20220227_2235.py
│ ├── 0023_repair_closures.py
│ ├── 0024_autofees.py
│ ├── 0025_alter_closures_unique_together.py
│ ├── 0026_auto_20220420_1538.py
│ ├── 0027_rebalancer_manual.py
│ ├── 0028_auto_20220620_1105.py
│ ├── 0029_update_percent_vars.py
│ ├── 0030_auto_20220722_0912.py
│ ├── 0031_pendingchannels.py
│ ├── 0032_auto_20220913_1035.py
│ ├── 0033_auto_20220926_1658.py
│ ├── 0034_peerevents.py
│ ├── 0035_histfailedhtlc.py
│ ├── 0036_peers.py
│ ├── 0037_tradesales.py
│ ├── 0038_channels_local_inbound_base_fee_and_more.py
│ ├── 0039_inboundfeelog.py
│ └── __init__.py
├── models.py
├── serializers.py
├── static
│ ├── api.js
│ ├── charts.js
│ ├── favicon.ico
│ ├── helpers.js
│ ├── qrcode.js
│ ├── sort_table.js
│ └── w3style.css
├── templates
│ ├── action_list.html
│ ├── addresses.html
│ ├── advanced.html
│ ├── autopilot.html
│ ├── balances.html
│ ├── base.html
│ ├── batch.html
│ ├── channel.html
│ ├── channels.html
│ ├── closures.html
│ ├── error.html
│ ├── failed_htlcs.html
│ ├── fee_rates.html
│ ├── forwards.html
│ ├── home.html
│ ├── inbound_fee_log.html
│ ├── income.html
│ ├── invoices.html
│ ├── keysends.html
│ ├── local_settings.html
│ ├── logs.html
│ ├── open_list.html
│ ├── outbound_fee_log.html
│ ├── payments.html
│ ├── peerevents.html
│ ├── peers.html
│ ├── pending_htlcs.html
│ ├── rebalances.html
│ ├── rebalances_table.html
│ ├── rebalancing.html
│ ├── reset.html
│ ├── resolutions.html
│ ├── route.html
│ ├── towers.html
│ ├── trades.html
│ └── unprofitable_channels.html
├── urls.py
└── views.py
├── htlc_stream.py
├── initialize.py
├── jobs.py
├── keysend.py
├── lndg
├── __init__.py
├── asgi.py
├── urls.py
└── wsgi.py
├── manage.py
├── nginx.sh
├── p2p.py
├── postgres.md
├── quickstart.md
├── rebalancer.py
├── requirements.txt
├── systemd.md
├── systemd.sh
└── trade.py
/.github/workflows/on-push-github.yml:
--------------------------------------------------------------------------------
1 | name: Build on push
2 |
3 | permissions:
4 | packages: write
5 |
6 | on:
7 | push:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 | name: Build image
14 | runs-on: ubuntu-22.04
15 |
16 | steps:
17 | - name: Checkout project
18 | uses: actions/checkout@v3
19 |
20 | - name: Set env variables
21 | run: |
22 | echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g')" >> $GITHUB_ENV
23 | IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
24 | echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
25 |
26 | - name: Login to GitHub Container Registry
27 | uses: docker/login-action@v2
28 | with:
29 | registry: ghcr.io
30 | username: ${{ github.repository_owner }}
31 | password: ${{ secrets.GITHUB_TOKEN }}
32 |
33 | - name: Set up QEMU
34 | uses: docker/setup-qemu-action@v2
35 | id: qemu
36 |
37 | - name: Setup Docker buildx action
38 | uses: docker/setup-buildx-action@v2
39 | id: buildx
40 |
41 | - name: Run Docker buildx
42 | run: |
43 | docker buildx build \
44 | --platform linux/amd64,linux/arm64 \
45 | --tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:latest \
46 | --output "type=registry" ./
47 |
--------------------------------------------------------------------------------
/.github/workflows/on-tag-github.yml:
--------------------------------------------------------------------------------
1 | name: Build on tag
2 |
3 | permissions:
4 | packages: write
5 |
6 | on:
7 | push:
8 | tags:
9 | - v[0-9]+.[0-9]+.[0-9]+
10 | - v[0-9]+.[0-9]+.[0-9]+-*
11 |
12 | jobs:
13 | build:
14 | name: Build image
15 | runs-on: ubuntu-22.04
16 |
17 | steps:
18 | - name: Checkout project
19 | uses: actions/checkout@v3
20 |
21 | - name: Set env variables
22 | run: |
23 | echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
24 | IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
25 | echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
26 |
27 | - name: Login to GitHub Container Registry
28 | uses: docker/login-action@v2
29 | with:
30 | registry: ghcr.io
31 | username: ${{ github.repository_owner }}
32 | password: ${{ secrets.GITHUB_TOKEN }}
33 |
34 | - name: Set up QEMU
35 | uses: docker/setup-qemu-action@v2
36 | id: qemu
37 |
38 | - name: Setup Docker buildx action
39 | uses: docker/setup-buildx-action@v2
40 | id: buildx
41 |
42 | - name: Run Docker buildx
43 | run: |
44 | docker buildx build \
45 | --platform linux/amd64,linux/arm64 \
46 | --tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:$TAG \
47 | --output "type=registry" ./
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | db.sqlite3
3 | lndg/settings.py
4 | frontend
5 | node_modules
6 | lndg-admin.txt
7 | .vscode
8 | .vs
9 | gui/static/admin
10 | gui/static/rest_framework
11 | data/
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3-alpine
2 | ENV PYTHONUNBUFFERED 1
3 | RUN apk add git g++ linux-headers && git clone https://github.com/cryptosharks131/lndg /app
4 | WORKDIR /app
5 | RUN git checkout "master"
6 | RUN pip install -r requirements.txt
7 | RUN pip install supervisor whitenoise
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 cryptosharks131
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/controller.py:
--------------------------------------------------------------------------------
1 | import multiprocessing, sys
2 | import jobs, rebalancer, htlc_stream, p2p, manage
3 |
4 | def run_task(task):
5 | task()
6 |
7 | def main():
8 | tasks = [jobs.main, rebalancer.main, htlc_stream.main, p2p.main]
9 | print('Controller is starting...')
10 |
11 | processes = []
12 | for task in tasks:
13 | process = multiprocessing.Process(target=run_task, name=task.__module__, args=(task,))
14 | processes.append(process)
15 | process.start()
16 |
17 | if len(sys.argv) > 1:
18 | sys.argv[0] = "manage.py"
19 | process = multiprocessing.Process(target=manage.main(sys.argv), name="manage.py")
20 | processes.append(process)
21 | process.start()
22 |
23 | for process in processes:
24 | process.join()
25 |
26 | print('Controller is stopping...')
27 |
28 | if __name__ == '__main__':
29 | main()
30 |
--------------------------------------------------------------------------------
/delete_payments.py:
--------------------------------------------------------------------------------
1 | from gui.lnd_deps import lightning_pb2 as ln
2 | from gui.lnd_deps import lightning_pb2_grpc as lnrpc
3 | from gui.lnd_deps.lnd_connect import lnd_connect
4 |
5 | def main():
6 | stub = lnrpc.LightningStub(lnd_connect())
7 | try:
8 | stub.DeleteAllPayments(ln.DeleteAllPaymentsRequest(failed_payments_only=False, failed_htlcs_only=True))
9 | stub.DeleteAllPayments(ln.DeleteAllPaymentsRequest(failed_payments_only=True, failed_htlcs_only=False))
10 | except Exception as e:
11 | print('Exception occured: ', e)
12 | finally:
13 | print('Delete All Payments Completed')
14 |
15 | if __name__ == '__main__':
16 | main()
17 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | lndg:
3 | build: .
4 | volumes:
5 | - /root/.lnd:/root/.lnd:ro
6 | - /root/lndg/data:/app/data:rw
7 | - /root/lndg/data/lndg-controller.log:/var/log/lndg-controller.log:rw
8 | command:
9 | - sh
10 | - -c
11 | - python initialize.py -net 'mainnet' -rpc 'localhost:10009' -wn && python controller.py runserver 0.0.0.0:8000 > /var/log/lndg-controller.log 2>&1
12 | ports:
13 | - 8889:8000
--------------------------------------------------------------------------------
/gui/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cryptosharks131/lndg/fedcbaf48608372ad13c1c257d2292ced0368533/gui/__init__.py
--------------------------------------------------------------------------------
/gui/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/gui/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class GuiConfig(AppConfig):
5 | name = 'gui'
6 |
--------------------------------------------------------------------------------
/gui/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from .models import Channels
3 |
4 | class OpenChannelForm(forms.Form):
5 | peer_pubkey = forms.CharField(label='peer_pubkey', max_length=66)
6 | local_amt = forms.IntegerField(label='local_amt')
7 | sat_per_byte = forms.IntegerField(label='sat_per_btye')
8 |
9 | class CloseChannelForm(forms.Form):
10 | chan_id = forms.CharField(label='chan_id')
11 | target_fee = forms.IntegerField(label='target_fee', required=False)
12 | force = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False)
13 |
14 | class ConnectPeerForm(forms.Form):
15 | peer_id = forms.CharField(label='peer_pubkey', max_length=200)
16 |
17 | class AddTowerForm(forms.Form):
18 | tower = forms.CharField(label='tower_pubkey', max_length=200)
19 |
20 | class DeleteTowerForm(forms.Form):
21 | pubkey = forms.CharField(label='tower_pubkey', max_length=66)
22 | address = forms.CharField(label='tower_address', max_length=134)
23 |
24 | class RemoveTowerForm(forms.Form):
25 | pubkey = forms.CharField(label='tower_pubkey', max_length=66)
26 |
27 | class AddInvoiceForm(forms.Form):
28 | value = forms.IntegerField(label='value')
29 |
30 | class RebalancerForm(forms.ModelForm):
31 | class Meta:
32 | model = Channels
33 | fields = []
34 | value = forms.IntegerField(label='value')
35 | fee_limit = forms.IntegerField(label='fee_limit')
36 | outgoing_chan_ids = forms.ModelMultipleChoiceField(queryset=Channels.objects.filter(is_open=1, is_active=1), required=False)
37 | last_hop_pubkey = forms.CharField(label='funding_txid', max_length=66, required=False)
38 | duration = forms.IntegerField(label='duration')
39 |
40 | class AutoRebalanceForm(forms.Form):
41 | enabled = forms.IntegerField(label='enabled', required=False)
42 | target_percent = forms.FloatField(label='target_percent', required=False)
43 | target_time = forms.IntegerField(label='target_time', required=False)
44 | fee_rate = forms.IntegerField(label='fee_rate', required=False)
45 | outbound_percent = forms.FloatField(label='outbound_percent', required=False)
46 | inbound_percent = forms.FloatField(label='inbound_percent', required=False)
47 | max_cost = forms.FloatField(label='max_cost', required=False)
48 | variance = forms.IntegerField(label='variance', required=False)
49 | wait_period = forms.IntegerField(label='wait_period', required=False)
50 | autopilot = forms.IntegerField(label='autopilot', required=False)
51 | autopilotdays = forms.IntegerField(label='autopilotdays', required=False)
52 | workers = forms.IntegerField(label='workers', required=False)
53 | update_channels = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False)
54 |
55 | class AutoFeesForm(AutoRebalanceForm):
56 | af_enabled = forms.IntegerField(label='af_enabled', required=False)
57 | af_inbound = forms.IntegerField(label='af_inbound', required=False)
58 | af_maxRate = forms.IntegerField(label='af_maxRate', required=False)
59 | af_minRate = forms.IntegerField(label='af_minRate', required=False)
60 | af_increment = forms.IntegerField(label='af_increment', required=False)
61 | af_multiplier = forms.IntegerField(label='af_multiplier', required=False)
62 | af_failedHTLCs = forms.IntegerField(label='af_failedHTLCs', required=False)
63 | af_updateHours = forms.IntegerField(label='af_updateHours', required=False)
64 | af_lowliq = forms.IntegerField(label='af_lowliq', required=False)
65 | af_excess = forms.IntegerField(label='af_excess', required=False)
66 |
67 | class GUIForm(AutoFeesForm):
68 | gui_graphLinks = forms.CharField(label='gui_graphLinks', required=False)
69 | gui_netLinks = forms.CharField(label='gui_netLinks', required=False)
70 |
71 | class LocalSettingsForm(GUIForm):
72 | lnd_cleanPayments = forms.IntegerField(label='lnd_cleanPayments', required=False)
73 | lnd_retentionDays = forms.IntegerField(label='lnd_retentionDays', required=False)
74 |
75 | updates_channel_codes = [
76 | (0, 'base_fee'),
77 | (1, 'fee_rate'),
78 | (2, 'ar_amt_target'),
79 | (3, 'ar_in_target'),
80 | (4, 'ar_out_target'),
81 | (5, 'ar_enabled'),
82 | (6, 'ar_max_cost'),
83 | (7, 'channel_state'),
84 | (8, 'auto_fees'),
85 | (9, 'cltv'),
86 | (10, 'min_htlc'),
87 | (11, 'max_htlc'),
88 | (12, 'inbound_base_fee'),
89 | (13, 'inbound_fee_rate'),
90 | ]
91 |
92 | class UpdateChannel(forms.Form):
93 | chan_id = forms.IntegerField(label='chan_id')
94 | target = forms.IntegerField(label='target')
95 | update_target = forms.ChoiceField(label='update_target', choices=updates_channel_codes)
96 |
97 | class UpdateClosing(forms.Form):
98 | funding_txid = forms.CharField(label='funding_txid', max_length=64)
99 | funding_index = forms.IntegerField(label='funding_index')
100 | target = forms.IntegerField(label='target')
101 |
102 | class UpdateKeysend(forms.Form):
103 | r_hash = forms.CharField(label='r_hash', max_length=64)
104 |
105 | class AddAvoid(forms.Form):
106 | pubkey = forms.CharField(label='avoid_pubkey', max_length=66)
107 | notes = forms.CharField(label='avoid_notes', max_length=1000, required=False)
108 |
109 | class RemoveAvoid(forms.Form):
110 | pubkey = forms.CharField(label='avoid_pubkey', max_length=66)
111 |
112 | class UpdatePending(forms.Form):
113 | funding_txid = forms.CharField(label='funding_txid', max_length=64)
114 | output_index = forms.IntegerField(label='output_index')
115 | target = forms.IntegerField(label='target')
116 | update_target = forms.ChoiceField(label='update_target', choices=updates_channel_codes)
117 |
118 | class UpdateSetting(forms.Form):
119 | key = forms.CharField(label='setting', max_length=20)
120 | value = forms.CharField(label='value', max_length=50)
121 |
122 | class BatchOpenForm(forms.Form):
123 | pubkey1 = forms.CharField(label='pubkey1', max_length=66, required=False)
124 | amt1 = forms.IntegerField(label='amt1', required=False)
125 | pubkey2 = forms.CharField(label='pubkey2', max_length=66, required=False)
126 | amt2 = forms.IntegerField(label='amt2', required=False)
127 | pubkey3 = forms.CharField(label='pubkey3', max_length=66, required=False)
128 | amt3 = forms.IntegerField(label='amt3', required=False)
129 | pubkey4 = forms.CharField(label='pubkey4', max_length=66, required=False)
130 | amt4 = forms.IntegerField(label='amt4', required=False)
131 | pubkey5 = forms.CharField(label='pubkey5', max_length=66, required=False)
132 | amt5 = forms.IntegerField(label='amt5', required=False)
133 | pubkey6 = forms.CharField(label='pubkey6', max_length=66, required=False)
134 | amt6 = forms.IntegerField(label='amt6', required=False)
135 | pubkey7 = forms.CharField(label='pubkey7', max_length=66, required=False)
136 | amt7 = forms.IntegerField(label='amt7', required=False)
137 | pubkey8 = forms.CharField(label='pubkey8', max_length=66, required=False)
138 | amt8 = forms.IntegerField(label='amt8', required=False)
139 | pubkey9 = forms.CharField(label='pubkey9', max_length=66, required=False)
140 | amt9 = forms.IntegerField(label='amt9', required=False)
141 | pubkey10 = forms.CharField(label='pubkey10', max_length=66, required=False)
142 | amt10 = forms.IntegerField(label='amt10', required=False)
143 | fee_rate = forms.IntegerField(label='fee_rate')
--------------------------------------------------------------------------------
/gui/lnd_deps/lnd_connect.py:
--------------------------------------------------------------------------------
1 | import os, codecs, grpc
2 | from lndg import settings
3 |
4 | def get_creds():
5 | #Open connection with lnd via grpc
6 | with open(os.path.expanduser(settings.LND_MACAROON_PATH), 'rb') as f:
7 | macaroon_bytes = f.read()
8 | macaroon = codecs.encode(macaroon_bytes, 'hex')
9 | def metadata_callback(context, callback):
10 | callback([('macaroon', macaroon)], None)
11 | os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
12 | cert = open(os.path.expanduser(settings.LND_TLS_PATH), 'rb').read()
13 | cert_creds = grpc.ssl_channel_credentials(cert)
14 | auth_creds = grpc.metadata_call_credentials(metadata_callback)
15 | creds = grpc.composite_channel_credentials(cert_creds, auth_creds)
16 | return creds
17 |
18 | creds = get_creds()
19 | def lnd_connect():
20 | return grpc.secure_channel(settings.LND_RPC_SERVER, creds, options=[('grpc.max_send_message_length', int(settings.LND_MAX_MESSAGE)*1000000), ('grpc.max_receive_message_length', int(settings.LND_MAX_MESSAGE)*1000000),])
21 |
22 | def async_lnd_connect():
23 | return grpc.aio.secure_channel(settings.LND_RPC_SERVER, creds, options=[('grpc.max_send_message_length', int(settings.LND_MAX_MESSAGE)*1000000), ('grpc.max_receive_message_length', int(settings.LND_MAX_MESSAGE)*1000000),])
24 |
25 | def main():
26 | pass
27 |
28 | if __name__ == '__main__':
29 | main()
--------------------------------------------------------------------------------
/gui/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-09-21 16:40
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | import django.utils.timezone
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Channels',
18 | fields=[
19 | ('remote_pubkey', models.CharField(max_length=66)),
20 | ('chan_id', models.IntegerField(primary_key=True, serialize=False)),
21 | ('funding_txid', models.CharField(max_length=64)),
22 | ('output_index', models.IntegerField()),
23 | ('capacity', models.BigIntegerField()),
24 | ('local_balance', models.BigIntegerField()),
25 | ('remote_balance', models.BigIntegerField()),
26 | ('unsettled_balance', models.BigIntegerField()),
27 | ('initiator', models.BooleanField()),
28 | ('alias', models.CharField(max_length=32)),
29 | ('base_fee', models.IntegerField()),
30 | ('fee_rate', models.IntegerField()),
31 | ('is_active', models.BooleanField()),
32 | ('is_open', models.BooleanField()),
33 | ('auto_rebalance', models.BooleanField(default=False)),
34 | ],
35 | ),
36 | migrations.CreateModel(
37 | name='Forwards',
38 | fields=[
39 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
40 | ('forward_date', models.DateTimeField()),
41 | ('chan_id_in', models.IntegerField()),
42 | ('chan_id_out', models.IntegerField()),
43 | ('chan_in_alias', models.CharField(default='Unknown', max_length=32)),
44 | ('chan_out_alias', models.CharField(default='Unknown', max_length=32)),
45 | ('amt_in_msat', models.BigIntegerField()),
46 | ('amt_out_msat', models.BigIntegerField()),
47 | ('fee', models.FloatField()),
48 | ],
49 | ),
50 | migrations.CreateModel(
51 | name='Invoices',
52 | fields=[
53 | ('creation_date', models.DateTimeField()),
54 | ('settle_date', models.DateTimeField()),
55 | ('r_hash', models.CharField(max_length=64, primary_key=True, serialize=False)),
56 | ('value', models.FloatField()),
57 | ('amt_paid', models.BigIntegerField()),
58 | ('state', models.IntegerField()),
59 | ('chan_in', models.IntegerField(null=True)),
60 | ('chan_in_alias', models.CharField(max_length=32, null=True)),
61 | ('keysend_preimage', models.CharField(max_length=64, null=True)),
62 | ('message', models.CharField(max_length=200, null=True)),
63 | ],
64 | ),
65 | migrations.CreateModel(
66 | name='LocalSettings',
67 | fields=[
68 | ('key', models.CharField(default=None, max_length=20, primary_key=True, serialize=False)),
69 | ('value', models.CharField(default=None, max_length=50)),
70 | ],
71 | ),
72 | migrations.CreateModel(
73 | name='Payments',
74 | fields=[
75 | ('creation_date', models.DateTimeField()),
76 | ('payment_hash', models.CharField(max_length=64, primary_key=True, serialize=False)),
77 | ('value', models.FloatField()),
78 | ('fee', models.FloatField()),
79 | ('status', models.IntegerField()),
80 | ('index', models.IntegerField()),
81 | ('chan_out', models.IntegerField(null=True)),
82 | ('chan_out_alias', models.CharField(max_length=32, null=True)),
83 | ('keysend_preimage', models.CharField(max_length=64, null=True)),
84 | ('message', models.CharField(max_length=200, null=True)),
85 | ],
86 | ),
87 | migrations.CreateModel(
88 | name='Peers',
89 | fields=[
90 | ('pubkey', models.CharField(max_length=66, primary_key=True, serialize=False)),
91 | ('address', models.CharField(max_length=100)),
92 | ('sat_sent', models.BigIntegerField()),
93 | ('sat_recv', models.BigIntegerField()),
94 | ('inbound', models.BooleanField()),
95 | ('connected', models.BooleanField()),
96 | ('last_reconnected', models.DateTimeField(default=None, null=True)),
97 | ],
98 | ),
99 | migrations.CreateModel(
100 | name='Rebalancer',
101 | fields=[
102 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
103 | ('requested', models.DateTimeField(default=django.utils.timezone.now)),
104 | ('value', models.IntegerField()),
105 | ('fee_limit', models.IntegerField()),
106 | ('outgoing_chan_ids', models.TextField(default='[]')),
107 | ('last_hop_pubkey', models.CharField(default='', max_length=66)),
108 | ('duration', models.IntegerField()),
109 | ('start', models.DateTimeField(null=True)),
110 | ('stop', models.DateTimeField(null=True)),
111 | ('status', models.IntegerField(default=0)),
112 | ],
113 | ),
114 | migrations.CreateModel(
115 | name='PaymentHops',
116 | fields=[
117 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
118 | ('attempt_id', models.IntegerField()),
119 | ('step', models.IntegerField()),
120 | ('chan_id', models.IntegerField()),
121 | ('alias', models.CharField(max_length=32)),
122 | ('chan_capacity', models.BigIntegerField()),
123 | ('node_pubkey', models.CharField(max_length=66)),
124 | ('amt', models.FloatField()),
125 | ('fee', models.FloatField()),
126 | ('payment_hash', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gui.payments')),
127 | ],
128 | options={
129 | 'unique_together': {('payment_hash', 'attempt_id', 'step')},
130 | },
131 | ),
132 | ]
133 |
--------------------------------------------------------------------------------
/gui/migrations/0002_auto_20210923_1538.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-09-23 15:38
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0001_initial'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='forwards',
15 | name='chan_in_alias',
16 | field=models.CharField(max_length=32, null=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='forwards',
20 | name='chan_out_alias',
21 | field=models.CharField(max_length=32, null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/gui/migrations/0003_auto_20210927_1857.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-09-27 18:57
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0002_auto_20210923_1538'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='channels',
15 | old_name='base_fee',
16 | new_name='local_base_fee',
17 | ),
18 | migrations.RenameField(
19 | model_name='channels',
20 | old_name='fee_rate',
21 | new_name='local_fee_rate',
22 | ),
23 | migrations.AddField(
24 | model_name='channels',
25 | name='remote_base_fee',
26 | field=models.IntegerField(default=1000),
27 | preserve_default=False,
28 | ),
29 | migrations.AddField(
30 | model_name='channels',
31 | name='remote_fee_rate',
32 | field=models.IntegerField(default=1),
33 | preserve_default=False,
34 | ),
35 | ]
36 |
--------------------------------------------------------------------------------
/gui/migrations/0004_channels_local_commit.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-09-28 10:20
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0003_auto_20210927_1857'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='channels',
15 | name='local_commit',
16 | field=models.IntegerField(default=0),
17 | preserve_default=False,
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/gui/migrations/0005_alter_invoices_settle_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-09-28 15:13
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0004_channels_local_commit'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='invoices',
15 | name='settle_date',
16 | field=models.DateTimeField(null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/gui/migrations/0006_auto_20210928_1641.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-09-28 16:41
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0005_alter_invoices_settle_date'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='channels',
15 | name='local_chan_reserve',
16 | field=models.IntegerField(default=0),
17 | preserve_default=False,
18 | ),
19 | migrations.AlterField(
20 | model_name='invoices',
21 | name='settle_date',
22 | field=models.DateTimeField(default=None, null=True),
23 | ),
24 | migrations.AlterField(
25 | model_name='rebalancer',
26 | name='start',
27 | field=models.DateTimeField(default=None, null=True),
28 | ),
29 | migrations.AlterField(
30 | model_name='rebalancer',
31 | name='stop',
32 | field=models.DateTimeField(default=None, null=True),
33 | ),
34 | ]
35 |
--------------------------------------------------------------------------------
/gui/migrations/0007_auto_20211002_1857.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-10-02 18:57
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0006_auto_20210928_1641'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='invoices',
15 | name='message',
16 | field=models.CharField(max_length=255, null=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='payments',
20 | name='message',
21 | field=models.CharField(max_length=255, null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/gui/migrations/0008_channels_ar_target.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-10-10 10:29
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0007_auto_20211002_1857'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='channels',
15 | name='ar_target',
16 | field=models.IntegerField(default=100),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/gui/migrations/0009_onchain.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-10-12 15:28
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0008_channels_ar_target'),
10 | ]
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='Onchain',
15 | fields=[
16 | ('tx_hash', models.CharField(max_length=64, primary_key=True, serialize=False)),
17 | ('amount', models.BigIntegerField()),
18 | ('block_hash', models.CharField(max_length=64)),
19 | ('block_height', models.IntegerField()),
20 | ('time_stamp', models.DateTimeField()),
21 | ('fee', models.IntegerField()),
22 | ('label', models.CharField(max_length=100)),
23 | ],
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/gui/migrations/0010_rebalancer_payment_hash.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-10-16 08:40
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0009_onchain'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='rebalancer',
15 | name='payment_hash',
16 | field=models.CharField(default=None, max_length=64, null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/gui/migrations/0011_pendinghtlcs.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-10-18 15:21
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0010_rebalancer_payment_hash'),
10 | ]
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='PendingHTLCs',
15 | fields=[
16 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17 | ('incoming', models.BooleanField()),
18 | ('amount', models.BigIntegerField()),
19 | ('hash_lock', models.CharField(max_length=64)),
20 | ('expiration_height', models.IntegerField()),
21 | ('forwarding_channel', models.IntegerField()),
22 | ('alias', models.CharField(max_length=32)),
23 | ],
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/gui/migrations/0012_auto_20211018_1637.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-10-18 16:37
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0011_pendinghtlcs'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='pendinghtlcs',
15 | name='chan_id',
16 | field=models.IntegerField(default=0),
17 | preserve_default=False,
18 | ),
19 | migrations.AddField(
20 | model_name='pendinghtlcs',
21 | name='forwarding_alias',
22 | field=models.CharField(default='---', max_length=32),
23 | preserve_default=False,
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/gui/migrations/0013_rebalancer_target_alias.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-11-10 10:21
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0012_auto_20211018_1637'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='rebalancer',
15 | name='target_alias',
16 | field=models.CharField(default='', max_length=32),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/gui/migrations/0014_failedhtlcs.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-12-18 22:43
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gui', '0013_rebalancer_target_alias'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='FailedHTLCs',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
19 | ('amount', models.IntegerField()),
20 | ('chan_id_in', models.IntegerField()),
21 | ('chan_id_out', models.IntegerField()),
22 | ('chan_in_alias', models.CharField(max_length=32, null=True)),
23 | ('chan_out_alias', models.CharField(max_length=32, null=True)),
24 | ('wire_failure', models.IntegerField()),
25 | ('failure_detail', models.IntegerField()),
26 | ('missed_fee', models.FloatField()),
27 | ],
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/gui/migrations/0015_invoices_index.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-12-26 11:54
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0014_failedhtlcs'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='invoices',
15 | name='index',
16 | field=models.IntegerField(default=0),
17 | preserve_default=False,
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/gui/migrations/0016_auto_20220106_1026.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-01-06 10:26
2 |
3 | from django.db import migrations, models
4 |
5 | def update_cost_to(apps, schedma_editor):
6 | payments = apps.get_model('gui', 'payments')
7 | hops = apps.get_model('gui', 'paymenthops')
8 | for payment in payments.objects.filter(status=2).iterator():
9 | cost_to = 0
10 | for hop in hops.objects.filter(payment_hash=payment.payment_hash).order_by('step'):
11 | hop.cost_to = round(cost_to, 3)
12 | hop.save()
13 | cost_to += hop.fee
14 |
15 | def revert_cost_to(apps, schedma_editor):
16 | pass
17 |
18 | class Migration(migrations.Migration):
19 |
20 | dependencies = [
21 | ('gui', '0015_invoices_index'),
22 | ]
23 |
24 | operations = [
25 | migrations.AddField(
26 | model_name='channels',
27 | name='num_updates',
28 | field=models.IntegerField(default=0),
29 | preserve_default=False,
30 | ),
31 | migrations.AddField(
32 | model_name='paymenthops',
33 | name='cost_to',
34 | field=models.FloatField(default=0),
35 | ),
36 | migrations.RunPython(update_cost_to, revert_cost_to),
37 | migrations.AlterField(
38 | model_name='paymenthops',
39 | name='cost_to',
40 | field=models.FloatField(),
41 | ),
42 | ]
43 |
--------------------------------------------------------------------------------
/gui/migrations/0017_autopilot.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-01-06 20:37
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gui', '0016_auto_20220106_1026'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Autopilot',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
19 | ('chan_id', models.IntegerField()),
20 | ('peer_alias', models.CharField(max_length=32)),
21 | ('setting', models.CharField(max_length=20)),
22 | ('old_value', models.IntegerField()),
23 | ('new_value', models.IntegerField()),
24 | ],
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/gui/migrations/0018_auto_20220114_2218.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-01-14 22:18
2 |
3 | from django.db import migrations, models
4 | from django.db.models import F, IntegerField
5 | from django.db.models.functions import Round
6 |
7 | def update_defaults(apps, schedma_editor):
8 | channels = apps.get_model('gui', 'channels')
9 | settings = apps.get_model('gui', 'localsettings')
10 | try:
11 | if settings.objects.filter(key='AR-Target%').exists():
12 | ar_amt_target = float(settings.objects.filter(key='AR-Target%')[0].value)
13 | else:
14 | ar_amt_target = 0.05
15 | channels.objects.all().update(ar_amt_target=Round(F('capacity')*ar_amt_target, output_field=IntegerField()))
16 | except Exception as e:
17 | print('Migration step failed:', str(e))
18 | try:
19 | if settings.objects.filter(key='AR-Outbound%').exists():
20 | ar_out_target = float(settings.objects.filter(key='AR-Outbound%')[0].value)
21 | else:
22 | ar_out_target = 0.75
23 | channels.objects.all().update(ar_out_target=ar_out_target*100)
24 | except Exception as e:
25 | print('Migration step failed:', str(e))
26 |
27 | def revert_defaults(apps, schedma_editor):
28 | pass
29 |
30 | class Migration(migrations.Migration):
31 |
32 | dependencies = [
33 | ('gui', '0017_autopilot'),
34 | ]
35 |
36 | operations = [
37 | migrations.RenameField(
38 | model_name='channels',
39 | old_name='ar_target',
40 | new_name='ar_in_target',
41 | ),
42 | migrations.AddField(
43 | model_name='channels',
44 | name='ar_amt_target',
45 | field=models.BigIntegerField(default=100000),
46 | ),
47 | migrations.AddField(
48 | model_name='channels',
49 | name='ar_out_target',
50 | field=models.IntegerField(default=75),
51 | ),
52 | migrations.RunPython(update_defaults, revert_defaults),
53 | ]
54 |
--------------------------------------------------------------------------------
/gui/migrations/0019_auto_20220122_1009.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-01-22 10:09
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0018_auto_20220114_2218'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='channels',
15 | name='ar_amt_target',
16 | field=models.BigIntegerField(),
17 | ),
18 | migrations.AlterField(
19 | model_name='channels',
20 | name='ar_out_target',
21 | field=models.IntegerField(),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/gui/migrations/0020_auto_20220126_2113.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-01-26 21:13
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 | def update_defaults(apps, schedma_editor):
7 | channels = apps.get_model('gui', 'channels')
8 | settings = apps.get_model('gui', 'localsettings')
9 | try:
10 | if settings.objects.filter(key='AR-MaxCost%').exists():
11 | ar_max_cost = float(settings.objects.filter(key='AR-MaxCost%')[0].value)
12 | else:
13 | ar_max_cost = 0.75
14 | channels.objects.all().update(ar_max_cost=ar_max_cost*100)
15 | except Exception as e:
16 | print('Migration step failed:', str(e))
17 |
18 | def revert_defaults(apps, schedma_editor):
19 | pass
20 |
21 | class Migration(migrations.Migration):
22 |
23 | dependencies = [
24 | ('gui', '0019_auto_20220122_1009'),
25 | ]
26 |
27 | operations = [
28 | migrations.AddField(
29 | model_name='channels',
30 | name='ar_max_cost',
31 | field=models.IntegerField(default=65),
32 | ),
33 | migrations.AddField(
34 | model_name='channels',
35 | name='last_update',
36 | field=models.DateTimeField(default=django.utils.timezone.now),
37 | ),
38 | migrations.AddField(
39 | model_name='channels',
40 | name='local_disabled',
41 | field=models.BooleanField(default=False),
42 | preserve_default=False,
43 | ),
44 | migrations.AddField(
45 | model_name='channels',
46 | name='remote_disabled',
47 | field=models.BooleanField(default=False),
48 | preserve_default=False,
49 | ),
50 | migrations.AddField(
51 | model_name='payments',
52 | name='cleaned',
53 | field=models.BooleanField(default=False),
54 | ),
55 | migrations.AlterField(
56 | model_name='invoices',
57 | name='message',
58 | field=models.CharField(max_length=500, null=True),
59 | ),
60 | migrations.RunPython(update_defaults, revert_defaults),
61 | migrations.AlterField(
62 | model_name='channels',
63 | name='ar_max_cost',
64 | field=models.IntegerField(),
65 | ),
66 | migrations.AlterField(
67 | model_name='channels',
68 | name='last_update',
69 | field=models.DateTimeField(),
70 | ),
71 | ]
72 |
--------------------------------------------------------------------------------
/gui/migrations/0021_auto_20220221_1309.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-02-23 09:31
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | from gui.lnd_deps import lightning_pb2 as ln
6 | from gui.lnd_deps import lightning_pb2_grpc as lnrpc
7 | from gui.lnd_deps import signer_pb2 as lns
8 | from gui.lnd_deps import signer_pb2_grpc as lnsigner
9 | from gui.lnd_deps.lnd_connect import lnd_connect
10 |
11 | def update_messages(apps, schedma_editor):
12 | invoices = apps.get_model('gui', 'invoices')
13 | try:
14 | messages = invoices.objects.exclude(message=None)
15 | if len(messages) > 0:
16 | stub = lnrpc.LightningStub(lnd_connect())
17 | signerstub = lnsigner.SignerStub(lnd_connect())
18 | self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
19 | for message in messages:
20 | records = stub.LookupInvoice(ln.PaymentHash(r_hash=bytes.fromhex(message.r_hash))).htlcs[0].custom_records
21 | if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
22 | try:
23 | valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
24 | except:
25 | print('Unable to validate signature on invoice: ' + message.r_hash)
26 | valid = False
27 | sender = records[34349339].hex() if valid == True else None
28 | else:
29 | sender = None
30 | try:
31 | alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
32 | except:
33 | alias = None
34 | message.message = records[34349334].decode('utf-8', errors='ignore')[:1000]
35 | message.sender = sender
36 | message.sender_alias = alias
37 | message.save()
38 | except Exception as e:
39 | print('Migration step failed:', str(e))
40 |
41 | def revert_messages(apps, schedma_editor):
42 | pass
43 |
44 | def update_rebal_channel(apps, schedma_editor):
45 | payments = apps.get_model('gui', 'payments')
46 | hops = apps.get_model('gui', 'paymenthops')
47 | try:
48 | stub = lnrpc.LightningStub(lnd_connect())
49 | self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
50 | for payment in payments.objects.filter(status=2).iterator():
51 | last_hop = hops.objects.filter(payment_hash=payment.payment_hash).order_by('-step')[0] if hops.objects.filter(payment_hash=payment.payment_hash).exists() else None
52 | if last_hop != None and last_hop.node_pubkey == self_pubkey:
53 | payment.rebal_chan = last_hop.chan_id
54 | payment.save()
55 | except Exception as e:
56 | print('Migration step failed:', str(e))
57 |
58 | def revert_rebal_channel(apps, schedma_editor):
59 | pass
60 |
61 | class Migration(migrations.Migration):
62 |
63 | dependencies = [
64 | ('gui', '0020_auto_20220126_2113'),
65 | ]
66 |
67 | operations = [
68 | migrations.CreateModel(
69 | name='Closures',
70 | fields=[
71 | ('chan_id', models.CharField(max_length=20, primary_key=True, serialize=False)),
72 | ('closing_tx', models.CharField(max_length=64)),
73 | ('remote_pubkey', models.CharField(max_length=66)),
74 | ('capacity', models.BigIntegerField()),
75 | ('close_height', models.IntegerField()),
76 | ('settled_balance', models.BigIntegerField()),
77 | ('time_locked_balance', models.BigIntegerField()),
78 | ('close_type', models.IntegerField()),
79 | ('open_initiator', models.IntegerField()),
80 | ('close_initiator', models.IntegerField()),
81 | ('resolution_count', models.IntegerField()),
82 | ],
83 | ),
84 | migrations.AddField(
85 | model_name='channels',
86 | name='htlc_count',
87 | field=models.IntegerField(default=0),
88 | preserve_default=False,
89 | ),
90 | migrations.AddField(
91 | model_name='channels',
92 | name='pending_inbound',
93 | field=models.BigIntegerField(default=0),
94 | preserve_default=False,
95 | ),
96 | migrations.AddField(
97 | model_name='channels',
98 | name='pending_outbound',
99 | field=models.BigIntegerField(default=0),
100 | preserve_default=False,
101 | ),
102 | migrations.AddField(
103 | model_name='channels',
104 | name='private',
105 | field=models.BooleanField(default=False),
106 | preserve_default=False,
107 | ),
108 | migrations.AddField(
109 | model_name='channels',
110 | name='total_received',
111 | field=models.BigIntegerField(default=0),
112 | preserve_default=False,
113 | ),
114 | migrations.AddField(
115 | model_name='channels',
116 | name='total_sent',
117 | field=models.BigIntegerField(default=0),
118 | preserve_default=False,
119 | ),
120 | migrations.AddField(
121 | model_name='invoices',
122 | name='sender',
123 | field=models.CharField(max_length=66, null=True),
124 | ),
125 | migrations.AddField(
126 | model_name='invoices',
127 | name='sender_alias',
128 | field=models.CharField(max_length=32, null=True),
129 | ),
130 | migrations.AddField(
131 | model_name='payments',
132 | name='rebal_chan',
133 | field=models.CharField(max_length=20, null=True),
134 | ),
135 | migrations.AddField(
136 | model_name='peers',
137 | name='alias',
138 | field=models.CharField(max_length=32, null=True),
139 | ),
140 | migrations.AlterField(
141 | model_name='autopilot',
142 | name='chan_id',
143 | field=models.CharField(max_length=20),
144 | ),
145 | migrations.AlterField(
146 | model_name='channels',
147 | name='chan_id',
148 | field=models.CharField(max_length=20, primary_key=True, serialize=False),
149 | ),
150 | migrations.AlterField(
151 | model_name='failedhtlcs',
152 | name='chan_id_in',
153 | field=models.CharField(max_length=20),
154 | ),
155 | migrations.AlterField(
156 | model_name='failedhtlcs',
157 | name='chan_id_out',
158 | field=models.CharField(max_length=20),
159 | ),
160 | migrations.AlterField(
161 | model_name='forwards',
162 | name='chan_id_in',
163 | field=models.CharField(max_length=20),
164 | ),
165 | migrations.AlterField(
166 | model_name='forwards',
167 | name='chan_id_out',
168 | field=models.CharField(max_length=20),
169 | ),
170 | migrations.AlterField(
171 | model_name='invoices',
172 | name='chan_in',
173 | field=models.CharField(max_length=20, null=True),
174 | ),
175 | migrations.AlterField(
176 | model_name='invoices',
177 | name='message',
178 | field=models.CharField(max_length=1000, null=True),
179 | ),
180 | migrations.AlterField(
181 | model_name='paymenthops',
182 | name='chan_id',
183 | field=models.CharField(max_length=20),
184 | ),
185 | migrations.AlterField(
186 | model_name='payments',
187 | name='chan_out',
188 | field=models.CharField(max_length=20, null=True),
189 | ),
190 | migrations.AlterField(
191 | model_name='payments',
192 | name='message',
193 | field=models.CharField(max_length=1000, null=True),
194 | ),
195 | migrations.AlterField(
196 | model_name='pendinghtlcs',
197 | name='chan_id',
198 | field=models.CharField(max_length=20),
199 | ),
200 | migrations.CreateModel(
201 | name='Resolutions',
202 | fields=[
203 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
204 | ('resolution_type', models.IntegerField()),
205 | ('outcome', models.IntegerField()),
206 | ('outpoint_tx', models.CharField(max_length=64)),
207 | ('outpoint_index', models.IntegerField()),
208 | ('amount_sat', models.BigIntegerField()),
209 | ('sweep_txid', models.CharField(max_length=64)),
210 | ('chan_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gui.closures')),
211 | ],
212 | ),
213 | migrations.RunPython(update_rebal_channel, revert_rebal_channel),
214 | migrations.RunPython(update_messages, revert_messages),
215 | ]
216 |
--------------------------------------------------------------------------------
/gui/migrations/0022_auto_20220227_2235.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-02-27 22:35
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0021_auto_20220221_1309'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='failedhtlcs',
15 | name='chan_out_liq',
16 | field=models.BigIntegerField(null=True),
17 | ),
18 | migrations.AddField(
19 | model_name='failedhtlcs',
20 | name='chan_out_pending',
21 | field=models.BigIntegerField(null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/gui/migrations/0023_repair_closures.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-03-11 23:31
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0022_auto_20220227_2235'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='channels',
15 | name='fees_updated',
16 | field=models.DateTimeField(default=django.utils.timezone.now),
17 | ),
18 | migrations.AddField(
19 | model_name='channels',
20 | name='auto_fees',
21 | field=models.BooleanField(default=False),
22 | ),
23 | ]
--------------------------------------------------------------------------------
/gui/migrations/0024_autofees.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-03-16 17:12
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gui', '0023_repair_closures'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Autofees',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
19 | ('chan_id', models.CharField(max_length=20)),
20 | ('peer_alias', models.CharField(max_length=32)),
21 | ('setting', models.CharField(max_length=20)),
22 | ('old_value', models.IntegerField()),
23 | ('new_value', models.IntegerField()),
24 | ],
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/gui/migrations/0025_alter_closures_unique_together.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-03-23 14:59
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0024_autofees'),
10 | ]
11 |
12 | operations = [
13 | migrations.DeleteModel(
14 | name='Resolutions',
15 | ),
16 | migrations.DeleteModel(
17 | name='Closures',
18 | ),
19 | migrations.CreateModel(
20 | name='Resolutions',
21 | fields=[
22 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('chan_id', models.CharField(max_length=20)),
24 | ('resolution_type', models.IntegerField()),
25 | ('outcome', models.IntegerField()),
26 | ('outpoint_tx', models.CharField(max_length=64)),
27 | ('outpoint_index', models.IntegerField()),
28 | ('amount_sat', models.BigIntegerField()),
29 | ('sweep_txid', models.CharField(max_length=64)),
30 | ],
31 | ),
32 | migrations.CreateModel(
33 | name='Closures',
34 | fields=[
35 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
36 | ('chan_id', models.CharField(max_length=20)),
37 | ('funding_txid', models.CharField(max_length=64)),
38 | ('funding_index', models.IntegerField()),
39 | ('closing_tx', models.CharField(max_length=64)),
40 | ('remote_pubkey', models.CharField(max_length=66)),
41 | ('capacity', models.BigIntegerField()),
42 | ('close_height', models.IntegerField()),
43 | ('settled_balance', models.BigIntegerField()),
44 | ('time_locked_balance', models.BigIntegerField()),
45 | ('close_type', models.IntegerField()),
46 | ('open_initiator', models.IntegerField()),
47 | ('close_initiator', models.IntegerField()),
48 | ('resolution_count', models.IntegerField()),
49 | ],
50 | options={
51 | 'unique_together': {('funding_txid', 'funding_index')},
52 | },
53 | ),
54 | ]
55 |
--------------------------------------------------------------------------------
/gui/migrations/0026_auto_20220420_1538.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-04-20 15:38
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0025_alter_closures_unique_together'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='channels',
15 | name='local_cltv',
16 | field=models.IntegerField(default=40),
17 | preserve_default=False,
18 | ),
19 | migrations.AddField(
20 | model_name='channels',
21 | name='remote_cltv',
22 | field=models.IntegerField(default=40),
23 | preserve_default=False,
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/gui/migrations/0027_rebalancer_manual.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-05-06 01:25
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0026_auto_20220420_1538'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='rebalancer',
15 | name='manual',
16 | field=models.BooleanField(default=False),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/gui/migrations/0028_auto_20220620_1105.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-06-20 11:05
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0027_rebalancer_manual'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='pendinghtlcs',
15 | name='forwarding_channel',
16 | field=models.CharField(max_length=20),
17 | ),
18 | migrations.AlterField(
19 | model_name='rebalancer',
20 | name='fee_limit',
21 | field=models.FloatField(),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/gui/migrations/0029_update_percent_vars.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-06-27 08:47
2 | from django.db import migrations
3 |
4 | def update_percent_vars(apps, schedma_editor):
5 | settings = apps.get_model('gui', 'localsettings')
6 | try:
7 | if settings.objects.filter(key='AR-MaxCost%').exists():
8 | current_value = settings.objects.filter(key='AR-MaxCost%')[0]
9 | current_value.value = int(float(current_value.value)*100)
10 | current_value.save()
11 | if settings.objects.filter(key='AR-Outbound%').exists():
12 | current_value = settings.objects.filter(key='AR-Outbound%')[0]
13 | current_value.value = int(float(current_value.value)*100)
14 | current_value.save()
15 | if settings.objects.filter(key='AR-Target%').exists():
16 | current_value = settings.objects.filter(key='AR-Target%')[0]
17 | current_value.value = int(float(current_value.value)*100)
18 | current_value.save()
19 | except Exception as e:
20 | print('Migration step failed:', str(e))
21 |
22 | def revert_percent_vars(apps, schedma_editor):
23 | settings = apps.get_model('gui', 'localsettings')
24 | try:
25 | if settings.objects.filter(key='AR-MaxCost%').exists():
26 | current_value = settings.objects.filter(key='AR-MaxCost%')[0]
27 | current_value.value = int(current_value.value)/100
28 | current_value.save()
29 | if settings.objects.filter(key='AR-Outbound%').exists():
30 | current_value = settings.objects.filter(key='AR-Outbound%')[0]
31 | current_value.value = int(current_value.value)/100
32 | current_value.save()
33 | if settings.objects.filter(key='AR-Target%').exists():
34 | current_value = settings.objects.filter(key='AR-Target%')[0]
35 | current_value.value = int(current_value.value)/100
36 | current_value.save()
37 | except Exception as e:
38 | print('Migration reversion step failed:', str(e))
39 |
40 | class Migration(migrations.Migration):
41 |
42 | dependencies = [
43 | ('gui', '0028_auto_20220620_1105'),
44 | ]
45 |
46 | operations = [
47 | migrations.RunPython(update_percent_vars, revert_percent_vars),
48 | ]
49 |
--------------------------------------------------------------------------------
/gui/migrations/0030_auto_20220722_0912.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-07-21 05:57
2 |
3 | from django.db import migrations, models
4 | from requests import get
5 | from lndg.settings import LND_NETWORK
6 |
7 | def update_close_fees(apps, schedma_editor):
8 | channels = apps.get_model('gui', 'channels')
9 | closures = apps.get_model('gui', 'closures')
10 | resolutions = apps.get_model('gui', 'resolutions')
11 | settings = apps.get_model('gui', 'localsettings')
12 | def network_links():
13 | if settings.objects.filter(key='GUI-NetLinks').exists():
14 | network_links = str(settings.objects.filter(key='GUI-NetLinks')[0].value)
15 | else:
16 | network_links = 'https://mempool.space'
17 | return network_links
18 | def get_tx_fees(txid):
19 | base_url = network_links() + ('/testnet' if LND_NETWORK == 'testnet' else '') + '/api/tx/'
20 | try:
21 | request_data = get(base_url + txid).json()
22 | fee = request_data['fee']
23 | except Exception as e:
24 | print('Error getting closure fees for', txid, '-', str(e))
25 | fee = 0
26 | return fee
27 | try:
28 | for closure in closures.objects.exclude(open_initiator=2, close_type=0):
29 | if channels.objects.filter(chan_id=closure.chan_id).exists():
30 | channel = channels.objects.filter(chan_id=closure.chan_id)[0]
31 | closing_costs = get_tx_fees(closure.closing_tx) if closure.open_initiator == 1 else 0
32 | for resolution in resolutions.objects.filter(chan_id=closure.chan_id).exclude(resolution_type=2):
33 | closing_costs += get_tx_fees(resolution.sweep_txid)
34 | channel.closing_costs = closing_costs
35 | channel.save()
36 | except Exception as e:
37 | print('Migration step failed:', str(e))
38 |
39 | def revert_close_fees(apps, schedma_editor):
40 | pass
41 |
42 | class Migration(migrations.Migration):
43 |
44 | dependencies = [
45 | ('gui', '0029_update_percent_vars'),
46 | ]
47 |
48 | operations = [
49 | migrations.AddField(
50 | model_name='channels',
51 | name='closing_costs',
52 | field=models.IntegerField(default=0),
53 | ),
54 | migrations.AddField(
55 | model_name='rebalancer',
56 | name='fees_paid',
57 | field=models.FloatField(default=None, null=True),
58 | ),
59 | migrations.AlterField(
60 | model_name='channels',
61 | name='ar_in_target',
62 | field=models.IntegerField(),
63 | ),
64 | migrations.AlterField(
65 | model_name='channels',
66 | name='auto_fees',
67 | field=models.BooleanField(),
68 | ),
69 | migrations.RunPython(update_close_fees, revert_close_fees),
70 | ]
71 |
--------------------------------------------------------------------------------
/gui/migrations/0031_pendingchannels.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-08-05 11:21
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0030_auto_20220722_0912'),
10 | ]
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='PendingChannels',
15 | fields=[
16 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17 | ('funding_txid', models.CharField(max_length=64)),
18 | ('output_index', models.IntegerField()),
19 | ('local_base_fee', models.IntegerField(default=None, null=True)),
20 | ('local_fee_rate', models.IntegerField(default=None, null=True)),
21 | ('local_cltv', models.IntegerField(default=None, null=True)),
22 | ('auto_rebalance', models.BooleanField(default=None, null=True)),
23 | ('ar_amt_target', models.BigIntegerField(default=None, null=True)),
24 | ('ar_in_target', models.IntegerField(default=None, null=True)),
25 | ('ar_out_target', models.IntegerField(default=None, null=True)),
26 | ('ar_max_cost', models.IntegerField(default=None, null=True)),
27 | ('auto_fees', models.BooleanField(default=None, null=True)),
28 | ],
29 | options={
30 | 'unique_together': {('funding_txid', 'output_index')},
31 | },
32 | ),
33 | ]
34 |
--------------------------------------------------------------------------------
/gui/migrations/0032_auto_20220913_1035.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-09-13 10:35
2 |
3 | from django.db import migrations, models
4 |
5 | def migrate_close_fees(apps, schedma_editor):
6 | channels = apps.get_model('gui', 'channels')
7 | closures = apps.get_model('gui', 'closures')
8 | close_fees = channels.objects.filter(closing_costs__gt=0)
9 | for close_fee in close_fees:
10 | closure = closures.objects.filter(funding_txid=close_fee.funding_txid, funding_index=close_fee.output_index)[0] if closures.objects.filter(funding_txid=close_fee.funding_txid, funding_index=close_fee.output_index).exists() else None
11 | if closure:
12 | closure.closing_costs = close_fee.closing_costs
13 | closure.save()
14 |
15 | def revert_close_fees(apps, schedma_editor):
16 | pass
17 |
18 | class Migration(migrations.Migration):
19 |
20 | dependencies = [
21 | ('gui', '0031_pendingchannels'),
22 | ]
23 |
24 | operations = [
25 | migrations.AddField(
26 | model_name='closures',
27 | name='closing_costs',
28 | field=models.IntegerField(default=0),
29 | ),
30 | migrations.RunPython(migrate_close_fees, revert_close_fees),
31 | migrations.RemoveField(
32 | model_name='channels',
33 | name='closing_costs',
34 | ),
35 | ]
36 |
--------------------------------------------------------------------------------
/gui/migrations/0033_auto_20220926_1658.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-09-27 01:38
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gui', '0032_auto_20220913_1035'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='AvoidNodes',
16 | fields=[
17 | ('pubkey', models.CharField(max_length=66, primary_key=True, serialize=False)),
18 | ('notes', models.CharField(max_length=1000, null=True)),
19 | ('updated', models.DateTimeField(default=django.utils.timezone.now)),
20 | ],
21 | ),
22 | migrations.AddField(
23 | model_name='channels',
24 | name='local_max_htlc_msat',
25 | field=models.BigIntegerField(default=0),
26 | preserve_default=False,
27 | ),
28 | migrations.AddField(
29 | model_name='channels',
30 | name='local_min_htlc_msat',
31 | field=models.BigIntegerField(default=0),
32 | preserve_default=False,
33 | ),
34 | migrations.AddField(
35 | model_name='channels',
36 | name='remote_max_htlc_msat',
37 | field=models.BigIntegerField(default=0),
38 | preserve_default=False,
39 | ),
40 | migrations.AddField(
41 | model_name='channels',
42 | name='remote_min_htlc_msat',
43 | field=models.BigIntegerField(default=0),
44 | preserve_default=False,
45 | ),
46 | migrations.AddField(
47 | model_name='invoices',
48 | name='is_revenue',
49 | field=models.BooleanField(default=False),
50 | ),
51 | ]
52 |
--------------------------------------------------------------------------------
/gui/migrations/0034_peerevents.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2022-12-20 14:25
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 | def update_short_chan_id(apps, schedma_editor):
7 | channels = apps.get_model('gui', 'channels')
8 | for channel in channels.objects.all().iterator():
9 | channel.short_chan_id = str(int(channel.chan_id) >> 40) + 'x' + str(int(channel.chan_id) >> 16 & 0xFFFFFF) + 'x' + str(int(channel.chan_id) & 0xFFFF)
10 | channel.save()
11 |
12 | def revert_short_chan_id(apps, schedma_editor):
13 | pass
14 |
15 | class Migration(migrations.Migration):
16 |
17 | dependencies = [
18 | ('gui', '0033_auto_20220926_1658'),
19 | ]
20 |
21 | operations = [
22 | migrations.CreateModel(
23 | name='PeerEvents',
24 | fields=[
25 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
26 | ('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
27 | ('chan_id', models.CharField(max_length=20)),
28 | ('peer_alias', models.CharField(max_length=32)),
29 | ('event', models.CharField(max_length=20)),
30 | ('old_value', models.BigIntegerField(null=True)),
31 | ('new_value', models.BigIntegerField()),
32 | ('out_liq', models.BigIntegerField()),
33 | ],
34 | ),
35 | migrations.AddField(
36 | model_name='channels',
37 | name='short_chan_id',
38 | field=models.CharField(default=0, max_length=20),
39 | ),
40 | migrations.RunPython(update_short_chan_id, revert_short_chan_id),
41 | migrations.AlterField(
42 | model_name='channels',
43 | name='short_chan_id',
44 | field=models.CharField(max_length=20),
45 | ),
46 | ]
47 |
--------------------------------------------------------------------------------
/gui/migrations/0035_histfailedhtlc.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2023-03-03 01:15
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gui', '0034_peerevents'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='HistFailedHTLC',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('date', models.DateField(default=django.utils.timezone.now)),
19 | ('chan_id_in', models.CharField(max_length=20)),
20 | ('chan_id_out', models.CharField(max_length=20)),
21 | ('chan_in_alias', models.CharField(max_length=32, null=True)),
22 | ('chan_out_alias', models.CharField(max_length=32, null=True)),
23 | ('htlc_count', models.IntegerField()),
24 | ('amount_sum', models.BigIntegerField()),
25 | ('fee_sum', models.BigIntegerField()),
26 | ('liq_avg', models.BigIntegerField()),
27 | ('pending_avg', models.BigIntegerField()),
28 | ('balance_count', models.IntegerField()),
29 | ('downstream_count', models.IntegerField()),
30 | ('other_count', models.IntegerField()),
31 | ],
32 | options={
33 | 'unique_together': {('date', 'chan_id_in', 'chan_id_out')},
34 | },
35 | ),
36 | ]
37 |
--------------------------------------------------------------------------------
/gui/migrations/0036_peers.py:
--------------------------------------------------------------------------------
1 | from django.db import migrations, models
2 | from gui.lnd_deps import lightning_pb2 as ln
3 | from gui.lnd_deps import lightning_pb2_grpc as lnrpc
4 | from gui.lnd_deps.lnd_connect import lnd_connect
5 |
6 | def migrate_update_channels(apps, schedma_editor):
7 | try:
8 | stub = lnrpc.LightningStub(lnd_connect())
9 | lnd_channels = stub.ListChannels(ln.ListChannelsRequest()).channels
10 | db_channels = apps.get_model('gui', 'channels')
11 | for channel in lnd_channels:
12 | if db_channels.objects.filter(chan_id=channel.chan_id).exists():
13 | db_channel = db_channels.objects.filter(chan_id=channel.chan_id)[0]
14 | db_channel.push_amt = channel.push_amount_sat
15 | db_channel.close_address = channel.close_address
16 | db_channel.save()
17 | except Exception as e:
18 | print('Unable to get current channel data:', str(e))
19 |
20 | def revert_update_channels(apps, schedma_editor):
21 | pass
22 |
23 | class Migration(migrations.Migration):
24 |
25 | dependencies = [
26 | ('gui', '0035_histfailedhtlc'),
27 | ]
28 |
29 | operations = [
30 | migrations.AddField(
31 | model_name='peers',
32 | name='ping_time',
33 | field=models.BigIntegerField(default=0),
34 | ),
35 | migrations.AddField(
36 | model_name='channels',
37 | name='notes',
38 | field=models.TextField(blank=True, default=''),
39 | ),
40 | migrations.AddField(
41 | model_name='channels',
42 | name='close_address',
43 | field=models.CharField(default='', max_length=100),
44 | preserve_default=False,
45 | ),
46 | migrations.AddField(
47 | model_name='channels',
48 | name='push_amt',
49 | field=models.BigIntegerField(default=0),
50 | preserve_default=False,
51 | ),
52 | migrations.RunPython(migrate_update_channels, revert_update_channels),
53 | ]
54 |
--------------------------------------------------------------------------------
/gui/migrations/0037_tradesales.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.6 on 2023-11-08 20:52
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gui', '0036_peers'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='TradeSales',
16 | fields=[
17 | ('id', models.CharField(max_length=64, primary_key=True, serialize=False)),
18 | ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
19 | ('expiry', models.DateTimeField(null=True)),
20 | ('description', models.CharField(max_length=100)),
21 | ('price', models.BigIntegerField()),
22 | ('sale_type', models.IntegerField()),
23 | ('secret', models.CharField(max_length=1000, null=True)),
24 | ('sale_limit', models.IntegerField(null=True)),
25 | ('sale_count', models.IntegerField(default=0)),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/gui/migrations/0038_channels_local_inbound_base_fee_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.2 on 2024-05-08 22:27
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('gui', '0037_tradesales'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='channels',
15 | name='local_inbound_base_fee',
16 | field=models.IntegerField(default=0),
17 | preserve_default=False,
18 | ),
19 | migrations.AddField(
20 | model_name='channels',
21 | name='local_inbound_fee_rate',
22 | field=models.IntegerField(default=0),
23 | preserve_default=False,
24 | ),
25 | migrations.AddField(
26 | model_name='channels',
27 | name='remote_inbound_base_fee',
28 | field=models.IntegerField(default=0),
29 | preserve_default=False,
30 | ),
31 | migrations.AddField(
32 | model_name='channels',
33 | name='remote_inbound_fee_rate',
34 | field=models.IntegerField(default=0),
35 | preserve_default=False,
36 | ),
37 | migrations.AddField(
38 | model_name='forwards',
39 | name='inbound_fee',
40 | field=models.FloatField(default=0),
41 | preserve_default=False,
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/gui/migrations/0039_inboundfeelog.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.2 on 2025-03-02 15:39
2 |
3 | import django.utils.timezone
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('gui', '0038_channels_local_inbound_base_fee_and_more'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='InboundFeeLog',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
19 | ('chan_id', models.CharField(max_length=20)),
20 | ('peer_alias', models.CharField(max_length=32)),
21 | ('setting', models.CharField(max_length=20)),
22 | ('old_value', models.IntegerField()),
23 | ('new_value', models.IntegerField()),
24 | ],
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/gui/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cryptosharks131/lndg/fedcbaf48608372ad13c1c257d2292ced0368533/gui/migrations/__init__.py
--------------------------------------------------------------------------------
/gui/static/api.js:
--------------------------------------------------------------------------------
1 | async function GET(url, {method = 'GET', data} = {}){
2 | if(!data.limit) data.limit = 100000
3 | if(!data.format) data.format = 'json'
4 | return call({url, method, data})
5 | }
6 |
7 | async function POST(url, {method = 'POST', body}){
8 | return call({url, method, body})
9 | }
10 |
11 | async function PUT(url, {method = 'PUT', body}){
12 | return call({url, method, body})
13 | }
14 |
15 | async function PATCH(url, {method = 'PATCH', body}){
16 | return call({url, method, body})
17 | }
18 |
19 | async function DELETE(url, {method = 'DELETE'} = {}){
20 | return call({url, method})
21 | }
22 |
23 | async function call({url, method, data, body, headers = {'Content-Type':'application/json'}}){
24 | if(url.charAt(url.length-1) != '/') url += '/'
25 | if(method != 'GET') headers['X-CSRFToken'] = document.getElementById('api').dataset.token
26 | const result = await fetch(`/api/${url}${data ? '?': ''}${new URLSearchParams(data).toString()}`, {method, body: JSON.stringify(body), headers})
27 | return result.json()
28 | }
29 |
30 | class Sync{
31 | static PUT(url, {method = 'PUT', body}, callback){
32 | call({url, method, body}).then(res => callback(res))
33 | }
34 | static POST(url, {method = 'POST', body}, callback){
35 | call({url, method, body}).then(res => callback(res))
36 | }
37 | }
--------------------------------------------------------------------------------
/gui/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cryptosharks131/lndg/fedcbaf48608372ad13c1c257d2292ced0368533/gui/static/favicon.ico
--------------------------------------------------------------------------------
/gui/static/helpers.js:
--------------------------------------------------------------------------------
1 | //HELPER FUNCTIONS
2 | function byId(id){ return document.getElementById(id) }
3 | String.prototype.toInt = function(){ return parseInt(this.replace(/,/g,''))}
4 | String.prototype.toBool = function(if_false = 0){ return this && /^true$/i.test(this) ? 1 : if_false}
5 | Number.prototype.intcomma = function(){ return parseInt(this).toLocaleString() }
6 | HTMLElement.prototype.defaultCloneNode = HTMLElement.prototype.cloneNode
7 | HTMLElement.prototype.cloneNode = function(attrs){
8 | const el = this.defaultCloneNode(this)
9 | Object.keys(attrs).forEach(k => el[k] = attrs[k])
10 | return el
11 | }
12 | HTMLElement.prototype.render = function(transforms){
13 | for(key in transforms){
14 | const value = transforms[key]
15 | if(value instanceof HTMLElement) this.append(value)
16 | else if (key === 'style') for(prop of Object.keys(value)){ this[key][prop] = value[prop] }
17 | else this[key] = value
18 | }
19 | }
20 | //END: HELPER FUNCTIONS
21 | //-------------------------------------------------------------------------------------------------------------------------
22 | //COMMON FUNCTIONS & VARIABLES
23 | const red = 'rgba(248,81,73,0.15)'
24 | const green = 'rgba(46,160,67,0.15)'
25 | function sleep(ms){
26 | return new Promise(res => setTimeout(res, ms));
27 | }
28 | function adjustTZ(datetime){
29 | datetime = new Date(datetime).getTime() - new Date(datetime).getTimezoneOffset()*60000
30 | return new Date(datetime)
31 | }
32 | async function toggle(button){
33 | try{
34 | button.children[0].style.visibility = 'hidden';
35 | button.children[1].style.visibility = 'visible';
36 | navigator.clipboard.writeText(button.getAttribute('data-value'))
37 | await sleep(1000)
38 | }
39 | catch(e){
40 | alert(button.getAttribute('data-value'))
41 | }
42 | finally{
43 | button.children[0].style.visibility = 'visible';
44 | button.children[1].style.visibility = 'hidden'
45 | }
46 | }
47 | function use(template){
48 | return {
49 | render: function(object, id='id', row = null){
50 | const tr = row ?? document.createElement("tr")
51 | tr.objId = object[id]
52 | for (key in template){
53 | const transforms = template[key](object)
54 | const td = document.createElement("td")
55 | td.setAttribute('name', key)
56 | td.render(transforms)
57 | tr.append(td)
58 | }
59 | return tr
60 | }
61 | }
62 | }
63 | function showBannerMsg(h1Msg, result, generic=false, id="bannerMsg"){
64 | if(!generic) h1Msg = `${h1Msg} updated to:`
65 | document.getElementById('content').insertAdjacentHTML("beforebegin", `
X
${h1Msg} ${result} `);
66 | window.scrollTo(0, 0);
67 | }
68 | function flash(element, response){
69 | if (response != element.value) {
70 | element.value = response
71 | return
72 | }
73 | var rgb = window.getComputedStyle(element).backgroundColor;
74 | rgb = rgb.substring(4, rgb.length-1).replace(/ /g, '').split(',');
75 | var r = rgb[0], g = rgb[1], bOrigin = rgb[2], b = bOrigin;
76 | var add = false;
77 | var complete = false;
78 | const increment = 15;
79 | var flashOn = setInterval(() => {
80 | if(add){
81 | if(b < 255 && (b+increment) <= 255){
82 | b += increment;
83 | }else{
84 | add = false;
85 | complete = true;
86 | }
87 | }else{
88 | if(complete == true && b < bOrigin){
89 | b = bOrigin;
90 | clearInterval(flashOn);
91 | }
92 | else if(b > 0 && (b-increment) >= 0){
93 | b -= increment;
94 | }else{
95 | add = true;
96 | }
97 | }
98 | element.style.backgroundColor = 'RGB('+r+','+g+','+b+')';
99 | if(b == bOrigin) element.style.removeProperty("background-color");
100 | }, 50);
101 | }
102 | function formatDate(start, end = new Date().getTime() + new Date().getTimezoneOffset()*60000){
103 | if (end == null) return '---'
104 | end = new Date(end)
105 | if (start == null) return '---'
106 | difference = (end - new Date(start))/1000
107 | if (difference > 0) {
108 | if (difference < 60) {
109 | if (Math.floor(difference) == 1){
110 | return `a second ago`;
111 | }else{
112 | return `${Math.floor(difference)} seconds ago`;
113 | }
114 | } else if (difference < 3600) {
115 | if (Math.floor(difference / 60) == 1){
116 | return `a minute ago`;
117 | }else{
118 | return `${Math.floor(difference / 60)} minutes ago`;
119 | }
120 | } else if (difference < 86400) {
121 | if (Math.floor(difference / 3600) == 1){
122 | return `an hour ago`;
123 | }else{
124 | return `${Math.floor(difference / 3600)} hours ago`;
125 | }
126 | } else if (difference < 2620800) {
127 | if (Math.floor(difference / 86400) == 1){
128 | return `a day ago`;
129 | }else{
130 | return `${Math.floor(difference / 86400)} days ago`;
131 | }
132 | } else if (difference < 31449600) {
133 | if (Math.floor(difference / 2620800) == 1){
134 | return `a month ago`;
135 | }else{
136 | return `${Math.floor(difference / 2620800)} months ago`;
137 | }
138 | } else {
139 | if (Math.floor(difference / 31449600) == 1){
140 | return `a year ago`;
141 | }else{
142 | return `${Math.floor(difference / 31449600)} years ago`;
143 | }
144 | }
145 | } else if (difference < 0) {
146 | if (-difference < 60) {
147 | if (Math.floor(-difference) == 1){
148 | return `in a second`;
149 | }else{
150 | return `in ${Math.floor(-difference)} seconds`;
151 | }
152 | } else if (-difference < 3600) {
153 | if (Math.floor(-difference / 60) == 1){
154 | return `in a minute`;
155 | }else{
156 | return `in ${Math.floor(-difference / 60)} minutes`;
157 | }
158 | } else if (-difference < 86400) {
159 | if (Math.floor(-difference / 3600) == 1){
160 | return `in an hour`;
161 | }else{
162 | return `in ${Math.floor(-difference / 3600)} hours`;
163 | }
164 | } else if (-difference < 2620800) {
165 | if (Math.floor(-difference / 86400) == 1){
166 | return `in a day`;
167 | }else{
168 | return `in ${Math.floor(-difference / 86400)} days`;
169 | }
170 | } else if (-difference < 31449600) {
171 | if (Math.floor(-difference / 2620800) == 1){
172 | return `in a month`;
173 | }else{
174 | return `in ${Math.floor(-difference / 2620800)} months`;
175 | }
176 | } else {
177 | if (Math.floor(-difference / 31449600) == 1){
178 | return `in a year`;
179 | }else{
180 | return `in ${Math.floor(-difference / 31449600)} years`;
181 | }
182 | }
183 | } else { return 'Just now' }
184 | }
185 | //END: COMMON FUNCTIONS & VARIABLES
--------------------------------------------------------------------------------
/gui/static/sort_table.js:
--------------------------------------------------------------------------------
1 | function sortTable(header, n, type, skip=0, tag="td") {
2 | var switching, i, curr_row, next_row, shouldSwitch, dir, switchcount = 0;
3 | var upArrow = " ▲", downArrow = " ▼";
4 | var table = header.parentElement.parentElement.parentElement; //th.tr.tbody.table
5 | var rows = table.rows;
6 |
7 | switching = true;
8 | dir = "asc";
9 | while (switching) {
10 | switching = false;
11 | for (i=1+skip; i<(rows.length-1); i++) {
12 | shouldSwitch = false;
13 | curr_row = rows[i].children[n]
14 | next_row = rows[i+1].children[n]
15 | if(tag !== "td"){
16 | curr_row = curr_row.getElementsByTagName(tag)[0];
17 | next_row = next_row.getElementsByTagName(tag)[0];
18 | }
19 | if (dir == "asc") {
20 | if (type == "String" && curr_row.innerHTML.toLowerCase() > next_row.innerHTML.toLowerCase()
21 | || type == "int" && parseFloat(curr_row.innerHTML.replace(/,/g, '')) > parseFloat(next_row.innerHTML.replace(/,/g, ''))
22 | || type != "String" && type != "int" && Number(curr_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')) > Number(next_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')))
23 | {
24 | shouldSwitch = true;
25 | break;
26 | }
27 | } else if (dir == "desc") {
28 | if (type == "String" && curr_row.innerHTML.toLowerCase() < next_row.innerHTML.toLowerCase()
29 | || type == "int" && parseFloat(curr_row.innerHTML.replace(/,/g, '')) < parseFloat(next_row.innerHTML.replace(/,/g, ''))
30 | || type != "String" && type != "int" && Number(curr_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')) < Number(next_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')))
31 | {
32 | shouldSwitch = true;
33 | break;
34 | }
35 | }
36 | }
37 | if (shouldSwitch) {
38 | rows[i].parentNode.insertBefore(rows[i+1], rows[i]);
39 | switching = true;
40 | switchcount ++;
41 | } else {
42 | if (switchcount == 0 && dir == "asc") {
43 | dir = "desc";
44 | switching = true;
45 | }
46 | }
47 | }
48 |
49 | if (switchcount > 0) {
50 | for (i=0; i
7 | Suggested Action List
8 |
9 |
10 | Channel ID
11 | Peer Alias
12 | Outbound Liquidity
13 | Capacity
14 | Inbound Liquidity
15 | Unsettled
16 | oRate
17 | oBase
18 | o7D
19 | i7D
20 | iRate
21 | iBase
22 | AR
23 | Action
24 |
25 | {% for channel in action_list %}
26 |
27 | {{ channel.short_chan_id }}
28 | {% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}
29 | {{ channel.local_balance|intcomma }} {{ channel.outbound_percent }}%
30 |
31 | {{channel.capacity|intcomma}}
32 |
33 |
34 |
35 |
36 | {{ channel.remote_balance|intcomma }} {{ channel.inbound_percent }}%
37 | {{ channel.unsettled_balance|intcomma }}
38 | {{ channel.local_fee_rate|intcomma }}
39 | {{ channel.local_base_fee|intcomma }}
40 | {{ channel.o7D|intcomma }}M {{ channel.routed_out_7day }}
41 | {{ channel.i7D|intcomma }}M {{ channel.routed_in_7day }}
42 | {{ channel.remote_fee_rate|intcomma }}
43 | {{ channel.remote_base_fee|intcomma }}
44 |
45 |
52 |
53 | {{ channel.output }}
54 |
55 | {% endfor %}
56 |
57 |
58 | {% endif %}
59 | {% if not action_list %}
60 |
61 |
Nothing to see here! Great job!
62 |
63 | {% endif %}
64 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/addresses.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Addresses{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if address_data %}
6 | {% for addresses in address_data.account_with_addresses reversed %}
7 |
8 |
{% if addresses.address_type == 1 %}Legacy{% elif addresses.address_type == 4 %}Taproot{% else %}{{ addresses.address_type }}{% endif %} Addresses
9 |
10 |
11 | Change
12 | Balance
13 | Address
14 |
15 | {% for address in addresses.addresses reversed %}
16 |
17 | {{ address.is_internal }}
18 | {% if address.balance == 0 %}---{% else %}{{ address.balance|intcomma }}{% endif %}
19 | {{ address.address }}
20 |
21 | {% endfor %}
22 |
23 |
24 | {% endfor %}
25 | {% else %}
26 |
27 |
You dont have any addresses yet.
28 |
29 | {% endif %}
30 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/autopilot.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Autopilot{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if autopilot %}
6 |
29 | {% endif %}
30 | {% if not autopilot %}
31 |
32 |
No autopilot logs to see here yet!
33 |
Experimental. This will allow LNDg to automatically act upon the suggestions found here .
34 |
35 | {% endif %}
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/gui/templates/balances.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Balances{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | Broadcast Transaction
6 | Consolidate UTXOs
7 |
8 |
Broadcast A Raw Transaction
9 |
10 | Broadcast
11 | Close
12 |
13 |
14 |
Consolidate TXs
15 |
16 | Consolidate
17 | Close
18 |
19 |
20 |
Estimate A Fee Bump
21 |
22 |
23 |
24 | Estimate
25 | Close
26 |
27 |
28 | {% if pending_sweeps %}
29 |
30 |
Pending Sweeps
31 |
32 |
33 | Outpoint
34 | Amount
35 | Type
36 | Requested Rate
37 | Target Rate
38 | Conf Target
39 | Total Attempts
40 | Next Attempt
41 | Force
42 | Bump Fee
43 |
44 | {% for sweep in pending_sweeps %}
45 |
46 | {{ sweep.txid_str }}
47 | {{ sweep.amount_sat|intcomma }}
48 | {% if sweep.witness_type == 0 %}Unknown{% elif sweep.witness_type == 1 %}Commitment Time Lock{% elif sweep.witness_type == 2 %}Commitment No Delay{% elif sweep.witness_type == 3 %}Commitment Revoke{% elif sweep.witness_type == 4 %}HTLC Offered Revoke{% elif sweep.witness_type == 5 %}HTLC Accepted Revoke{% elif sweep.witness_type == 6 %}HTLC Offered Timeout Second Level{% elif sweep.witness_type == 8 %}HTLC Offered Remote Timeout{% elif sweep.witness_type == 13 %}Commitment Anchor{% else %}{{ sweep.witness_type }}{% endif %}
49 | {% if sweep.requested_sat_per_vbyte == 0 %}---{% else %}{{ sweep.requested_sat_per_vbyte|intcomma }}{% endif %}
50 | {{ sweep.sat_per_vbyte|intcomma }}
51 | {{ sweep.requested_conf_target|intcomma }}
52 | {{ sweep.broadcast_attempts|intcomma }}
53 | {{ sweep.next_broadcast_height|intcomma }}
54 | {% if sweep.force %}Yes{% else %}No{% endif %}
55 |
56 |
61 |
62 |
63 | {% endfor %}
64 |
65 |
66 | {% endif %}
67 | {% if utxos %}
68 |
69 |
Balances
70 |
71 |
72 | Addresses
73 | Amount
74 | Outpoint
75 | Confirmations
76 | Fee Bumps 🖩
77 |
78 | {% for utxo in utxos %}
79 |
80 | {{ utxo.address }}
81 | {{ utxo.amount_sat|intcomma }}
82 | {{ utxo.outpoint.txid_str }}
83 | {{ utxo.confirmations|intcomma }}
84 |
85 | {% if utxo.confirmations == 0 %}
86 |
91 | {% else %}
92 | ---
93 | {% endif %}
94 |
95 |
96 | {% endfor %}
97 |
98 |
99 | {% endif %}
100 | {% if transactions %}
101 |
102 |
Transactions
103 |
104 |
105 | TX Hash
106 | Amount
107 | Block Height
108 | Fees
109 | Label
110 |
111 | {% for transaction in transactions %}
112 |
113 | {{ transaction.tx_hash }}
114 | {{ transaction.amount|intcomma }}
115 | {% if transaction.block_height == 0 %}---{% else %}{{ transaction.block_height|intcomma }} {% endif %}
116 | {{ transaction.fee|intcomma }}
117 | {{ transaction.label }}
118 |
119 | {% endfor %}
120 |
121 |
122 | {% endif %}
123 | {% if not utxos and not transactions %}
124 |
125 |
No wallet transactions found!
126 |
127 | {% endif %}
128 |
177 | {% endblock %}
178 |
--------------------------------------------------------------------------------
/gui/templates/batch.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Batch Open{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 |
30 |
31 |
Batch Open Up To 10 Channels
32 |
56 |
57 | {% endblock %}
58 |
--------------------------------------------------------------------------------
/gui/templates/channels.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Channels{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if channels %}
6 |
7 |
Channel Performance
8 |
9 |
10 |
11 |
12 | Channel
13 | 7-Day Activity And Revenue APY: {{ apy_7day }}%
14 | 30-Day Activity And Revenue APY: {{ apy_30day }}%
15 | Channel Health
16 |
17 |
18 |
19 | Channel ID
20 | Peer Alias
21 | Capacity
22 | Routed Out | In
23 | Rebal In | Out
24 | APY | CV
25 | Out Profit | In
26 | Routed Out | Routed In
27 | Rebal In | Out
28 | APY | CV
29 | Out Profit | In
30 | Updates
31 | Opener
32 |
33 | {% for channel in channels %}
34 |
35 | {{ channel.short_chan_id }}
36 | {% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}
37 | {{ channel.mil_capacity }}M
38 | {{ channel.amt_routed_out_7day|intcomma }}M {{ channel.routed_out_7day }} | {{ channel.amt_routed_in_7day|intcomma }}M {{ channel.routed_in_7day }}
39 | {{ channel.amt_rebal_in_7day|intcomma }}M {{ channel.rebal_in_7day }} | {{ channel.amt_rebal_out_7day|intcomma }}M {{ channel.rebal_out_7day }}
40 | {{ channel.apy_7day }}% {{ channel.cv_7day }}%
41 | {{ channel.revenue_7day|intcomma }} {{ channel.profits_7day|intcomma }} | {{ channel.revenue_assist_7day|intcomma }}
42 | {{ channel.amt_routed_out_30day|intcomma }}M {{ channel.routed_out_30day }} | {{ channel.amt_routed_in_30day|intcomma }}M {{ channel.routed_in_30day }}
43 | {{ channel.amt_rebal_in_30day|intcomma }}M {{ channel.rebal_in_30day }} | {{ channel.amt_rebal_out_30day|intcomma }}M {{ channel.rebal_out_30day }}
44 | {{ channel.apy_30day }}% {{ channel.cv_30day }}%
45 | {{ channel.revenue_30day|intcomma }} {{ channel.profits_30day|intcomma }} | {{ channel.revenue_assist_30day|intcomma }}
46 | {{ channel.updates }}%
47 | {% if channel.initiator == True %}Local{% else %}Remote{% endif %}
48 |
49 | {% endfor %}
50 |
51 |
52 |
53 | {% endif %}
54 | {% if not channels %}
55 |
56 |
You dont have any channels to analyze yet!
57 |
58 | {% endif %}
59 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/closures.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Closures{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if pending_closed %}
6 |
7 |
Pending Close Channels
8 |
36 |
37 | {% endif %}
38 | {% if pending_force_closed %}
39 |
40 |
Pending Force Close Channels
41 |
69 |
70 | {% endif %}
71 | {% if waiting_for_close %}
72 |
73 |
Channels Waiting To Close
74 |
102 |
103 | {% endif %}
104 | {% if closures %}
105 |
106 |
Closures
107 |
108 |
109 | Channel ID
110 | Alias
111 | Capacity
112 | Closing TXID
113 | Settled Balance
114 | Locked Balance
115 | Close Height
116 | Close Type
117 | Opener
118 | Closer
119 | Resolutions
120 | Costs 🗘
121 |
122 | {% for closure in closures %}
123 |
124 | {% if closure.chan_id == '0' %}---{% else %}{{ closure.short_chan_id }} {% endif %}
125 | {% if closure.alias == '' %}{{ closure.remote_pubkey|slice:":12" }}{% else %}{{ closure.alias }}{% endif %}
126 | {{ closure.capacity|intcomma }}
127 | {% if closure.closing_tx == '0000000000000000000000000000000000000000000000000000000000000000' %}---{% else %}{{ closure.closing_tx }} {% endif %}
128 | {{ closure.settled_balance|intcomma }}
129 | {{ closure.time_locked_balance|intcomma }}
130 | {{ closure.close_height|intcomma }}
131 | {% if closure.close_type == 0 %}Cooperative{% elif closure.close_type == 1 %}Local Force{% elif closure.close_type == 2 %}Remote Force{% elif closure.close_type == 3 %}Breach{% elif closure.close_type == 4 %}Funding Cancelled{% elif closure.close_type == 5 %}Abandoned{% else %}{{ closure.close_type }}{% endif %}
132 | {% if closure.open_initiator == 0 %}Unknown{% elif closure.open_initiator == 1 %}Local{% elif closure.open_initiator == 2 %}Remote{% elif closure.open_initiator == 3 %}Both{% else %}{{ closure.open_initiator }}{% endif %}
133 | {% if closure.close_initiator == 0 %}Unknown{% elif closure.close_initiator == 1 %}Local{% elif closure.close_initiator == 2 %}Remote{% elif closure.close_initiator == 3 %}Both{% else %}{{ closure.close_initiator }}{% endif %}
134 | {% if closure.resolution_count > 0 %}Details {% else %}---{% endif %}
135 |
136 | {% if closure.close_type == 4 or closure.close_type == 5 %}
137 | ---
138 | {% elif closure.open_initiator == 2 and closure.resolution_count == 0 %}
139 | ---
140 | {% else %}
141 |
147 | {% endif %}
148 |
149 |
150 | {% endfor %}
151 |
152 |
153 | {% endif %}
154 | {% if not closures %}
155 |
156 |
No channel closures found!
157 |
158 | {% endif %}
159 | {% endblock %}
160 |
--------------------------------------------------------------------------------
/gui/templates/error.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Error{% endblock %}
3 | {% block content %}
4 |
5 |
An error has occured!
6 | {% if error == 'StatusCode.UNAVAILABLE' %}LNDg cannot reach the gRPC for LND. Make sure LND is running and/or your gRPC settings are correct. {% endif %}
7 | {% if error == 'StatusCode.UNKNOWN' %}LND may not be ready yet. Wait a bit longer or check your LND logs. {% endif %}
8 | Error Code: {{ error }}
9 |
10 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/failed_htlcs.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Failed HTLCs{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if agg_failed_htlcs %}
6 |
7 |
Top 21 Failed Downstream Routes
8 |
28 |
29 | {% endif %}
30 | {% if not agg_failed_htlcs %}
31 |
32 |
You dont have any failed downstream routes in the last 7 days.
33 |
34 | {% endif %}
35 |
36 |
Last Failed HTLCs
37 |
38 |
39 | Timestamp
40 | Chan In ID
41 | Chan In Alias
42 | Forward Amount
43 | Actual Outbound
44 | Chan Out Alias
45 | Chan Out ID
46 | Potential Fee
47 | HTLC Failure
48 | Failure Detail
49 |
50 |
51 |
52 |
53 | Load More
54 |
55 |
56 |
57 |
58 |
59 |
101 | {% endblock %}
102 |
--------------------------------------------------------------------------------
/gui/templates/fee_rates.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Fee Rates{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if channels %}
6 |
7 |
Suggested Fee Rates
8 |
9 |
10 |
11 | Channel ID
12 | Peer Alias
13 | Outbound Liquidity
14 | Capacity
15 | Inbound Liquidity
16 | 7Day Flow
17 | oAdjustment
18 | Suggested oRate
19 | iAdjustment
20 | Suggested iRate
21 | oRate
22 | iRate
23 | Max Cost
24 | Peer oRate
25 | Peer iRate
26 | Updated
27 |
28 |
34 |
40 |
41 |
42 | {% for channel in channels %}
43 |
44 | {{ channel.short_chan_id }}
45 | {% if channel.private == False %}{% endif %}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{% if channel.private == False %} {% endif %}
46 | {{ channel.local_balance|intcomma }} {{ channel.out_percent }}%
47 |
48 | {{channel.capacity|intcomma}}
49 |
50 |
51 |
52 |
53 | {{ channel.remote_balance|intcomma }} {{ channel.in_percent }}%
54 | {% if channel.net_routed_7day != 0 %}{{ channel.net_routed_7day }} {% if channel.net_routed_7day > 0 %}OUT{% else %}IN{% endif %} {% else %}0{% endif %}
55 | {{ channel.adjustment }}
56 | {{ channel.new_rate|intcomma }}
57 | {{ channel.inbound_adjustment|intcomma }}
58 | {{ channel.new_inbound_rate|intcomma }}
59 |
60 |
66 |
67 |
68 |
74 |
75 | {{ channel.ar_max_cost }}%
76 | {{ channel.remote_fee_rate|intcomma }}
77 | {{ channel.remote_inbound_fee_rate|intcomma }}
78 | {{ channel.fees_updated|naturaltime }}
79 |
80 |
87 |
88 |
89 | {% endfor %}
90 |
91 |
92 |
93 | {% else %}
94 |
95 |
You dont have any channels to analyze yet!
96 |
97 | {% endif %}
98 | {% if local_settings %}
99 | {% include 'local_settings.html' with settings=local_settings title='Auto-Fees' postURL='update_setting' %}
100 | {% endif %}
101 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/forwards.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Forwards{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 |
6 |
Last Forwards
7 |
8 |
9 | Timestamp
10 | Channel In Id
11 | Channel In Alias
12 | Amount In
13 | Amount Out
14 | Channel Out Alias
15 | Channel Out Id
16 | Fees Earned
17 | PPM Earned
18 |
19 |
20 |
21 |
22 | Load More
23 |
24 |
25 |
26 |
27 |
28 |
59 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/inbound_fee_log.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Inbound Fee Log{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if inbound_fee_log %}
6 |
7 |
8 |
9 |
10 | Timestamp
11 | Channel ID
12 | Peer Alias
13 | Setting
14 | Old Value
15 | New Value
16 |
17 | {% for log in inbound_fee_log %}
18 |
19 | {{ log.timestamp|naturaltime }}
20 | {{ log.chan_id }}
21 | {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %}
22 | {{ log.setting }}
23 | {{ log.old_value }}
24 | log.old_value %}style="background-color: rgba(46,160,67,0.15)"{% else %}style="background-color: rgba(248,81,73,0.15)"{% endif %}>{{ log.new_value }}
25 |
26 | {% endfor %}
27 |
28 |
29 | {% endif %}
30 | {% if not inbound_fee_log %}
31 |
32 |
No inbound fee logs to see here yet!
33 |
34 | {% endif %}
35 | {% endblock %}
36 |
--------------------------------------------------------------------------------
/gui/templates/invoices.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Invoices{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if invoices %}
6 |
7 |
Last 150 Invoices
8 |
9 |
10 | Created
11 | Settled
12 | Payment Hash
13 | Value
14 | Amount Paid
15 | State
16 | Channel In Alias
17 | Channel In
18 | Keysend
19 |
20 | {% for invoice in invoices %}
21 |
22 | {{ invoice.creation_date|naturaltime }}
23 | {% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %}
24 | {{ invoice.r_hash }}
25 | {{ invoice.value|add:"0"|intcomma }}
26 | {% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %}
27 | {% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}
28 | {% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %}
29 | {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }} {% else %}---{% endif %}
30 | {% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %}
31 |
32 | {% endfor %}
33 |
34 |
35 | {% endif %}
36 | {% if not invoices %}
37 |
38 |
You dont have any invoices yet.
39 |
40 | {% endif %}
41 | {% endblock %}
42 |
--------------------------------------------------------------------------------
/gui/templates/keysends.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Keysends{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if keysends %}
6 |
7 |
Received Keysends
8 |
9 |
10 | Revenue?
11 | Settle Date
12 | Channel In Alias
13 | Amount
14 | Message
15 |
16 | {% for keysend in keysends %}
17 |
18 |
19 |
24 |
25 | {{ keysend.settle_date|naturaltime }}
26 | {% if keysend.chan_in_alias == '' %}---{% else %}{{ keysend.chan_in_alias }}{% endif %}
27 | {{ keysend.amt_paid|intcomma }}
28 | {{ keysend.message }}{% if keysend.sender != None %} | Signed By: {% if keysend.sender_alias != None %}{{ keysend.sender_alias }}{% else %}{{ keysend.sender }}{% endif %}{% endif %}
29 |
30 | {% endfor %}
31 |
32 |
33 | {% endif %}
34 | {% if not keysends %}
35 |
36 |
You dont have any keysend messages here yet!
37 |
38 | {% endif %}
39 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/local_settings.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gui/templates/logs.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Logs{% endblock %}
3 | {% block content %}
4 |
5 |
Logs
6 |
7 |
{% for line in logs %}{{line}}{%endfor%}
8 |
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/gui/templates/open_list.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Opens{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if open_list %}
6 |
7 |
Suggested Open List
8 |
9 |
10 |
11 | Node Pubkey
12 | Node Alias
13 | Successful Payments Routed
14 | Amount Routed
15 | Fees Paid
16 | Effective PPM
17 | Volume Score
18 | Savings By Volume
19 |
20 | {% for node in open_list %}
21 |
22 | {{ node.node_pubkey }}
23 | {% if node.alias == '' %}---{% else %}{{ node.alias }}{% endif %}
24 | {{ node.count }}
25 | {{ node.amount|add:"0"|intcomma }}
26 | {{ node.fees|add:"0"|intcomma }}
27 | {{ node.ppm|add:"0"|intcomma }}
28 | {{ node.score }}
29 | {{ node.sum_cost_to|add:"0"|intcomma }}
30 |
31 | {% endfor %}
32 |
33 |
34 |
35 | {% else %}
36 |
37 |
No potential peers can be calculated yet, try waiting until you have some payment data.
38 |
39 | {% endif %}
40 | {% if avoid_list %}
41 |
42 |
Avoid/Exclude List
43 |
44 |
45 |
46 | Updated
47 | Node Pubkey
48 | Notes
49 | Remove
50 |
51 | {% for node in avoid_list %}
52 |
53 | {{ node.updated|naturaltime }}
54 | {{ node.pubkey }}
55 | {% if node.notes == '' %}---{% else %}{{ node.notes }}{% endif %}
56 |
57 |
62 |
63 |
64 | {% endfor %}
65 |
66 |
67 |
68 | {% else %}
69 |
70 |
No node added to the exclusion list yet. Add nodes here you want to avoid connecting to in the future.
71 |
72 | {% endif %}
73 |
74 |
Add Node To Exclusion List Or Update Existing Notes
75 |
83 |
84 | {% endblock %}
85 |
--------------------------------------------------------------------------------
/gui/templates/outbound_fee_log.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Outbound Fee Log{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if outbound_fee_log %}
6 |
7 |
8 |
9 |
10 | Timestamp
11 | Channel ID
12 | Peer Alias
13 | Setting
14 | Old Value
15 | New Value
16 |
17 | {% for log in outbound_fee_log %}
18 |
19 | {{ log.timestamp|naturaltime }}
20 | {{ log.chan_id }}
21 | {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %}
22 | {{ log.setting }}
23 | {{ log.old_value }}
24 | log.old_value %}style="background-color: rgba(46,160,67,0.15)"{% else %}style="background-color: rgba(248,81,73,0.15)"{% endif %}>{{ log.new_value }}
25 |
26 | {% endfor %}
27 |
28 |
29 | {% endif %}
30 | {% if not outbound_fee_log %}
31 |
32 |
No outbound fee logs to see here yet!
33 |
34 | {% endif %}
35 | {% endblock %}
36 |
--------------------------------------------------------------------------------
/gui/templates/payments.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Payments{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if payments %}
6 |
7 |
Last 150 Payments
8 |
9 |
10 | Timestamp
11 | Hash
12 | Value
13 | Fee Paid
14 | PPM Paid
15 | Status
16 | Chan Out Alias
17 | Chan Out ID
18 | Route
19 | Keysend
20 |
21 | {% for payment in payments %}
22 |
23 | {{ payment.creation_date|naturaltime }}
24 | {{ payment.payment_hash }}
25 | {{ payment.value|add:"0"|intcomma }}
26 | {{ payment.fee|intcomma }}
27 | {{ payment.ppm|intcomma }}
28 | {% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %}
29 | {% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}---{% endif %}
30 | {% if payment.status == 2 %}{{ payment.chan_out }} {% else %}---{% endif %}
31 | {% if payment.status == 2 %}Open {% else %}---{% endif %}
32 | {% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %}
33 |
34 | {% endfor %}
35 |
36 |
37 | {% endif %}
38 | {% if not payments %}
39 |
40 |
You dont have any payments yet.
41 |
42 | {% endif %}
43 | {% endblock %}
44 |
--------------------------------------------------------------------------------
/gui/templates/peerevents.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Peer Events{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 |
6 |
Last Peer Events
7 |
8 |
9 | Timestamp
10 | Channel ID
11 | Peer Alias
12 | Setting
13 | Old Value
14 | New Value
15 | Change
16 | Channel Liquidity
17 |
18 |
19 |
20 |
21 | Load More
22 |
23 |
24 |
25 |
26 |
27 |
63 | {% endblock %}
64 |
--------------------------------------------------------------------------------
/gui/templates/peers.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Peers{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if peers %}
6 |
7 |
Peers List {{ num_peers }}
8 |
9 |
10 | Peer PubKey
11 | Peer Alias
12 | Network Address
13 | Ping Time (ms)
14 | Inbound
15 | Sats Sent
16 | Sats Received
17 | Action
18 |
19 | {% for peer in peers %}
20 |
21 | {{ peer.pubkey }}
22 | {% if peer.alias != '' %}{{ peer.alias }}{% else %}---{% endif %}
23 | {{ peer.address }}
24 | {{ peer.ping_time|intcomma }}
25 | {{ peer.inbound }}
26 | {{ peer.sat_sent|intcomma }}
27 | {{ peer.sat_recv|intcomma }}
28 | ❌
29 |
30 | {% endfor %}
31 |
32 |
33 |
43 | {% endif %}
44 | {% if not peers %}
45 |
46 |
No connected peers found!
47 |
48 | {% endif %}
49 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/pending_htlcs.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - HTLCs{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if outgoing_htlcs %}
6 |
7 |
Outgoing HTLCs
8 |
9 |
10 | Channel ID
11 | Channel Alias
12 | Forwarding Channel
13 | Forwarding Alias
14 | Amount
15 | Expiration
16 | Hash Lock
17 |
18 | {% for htlc in outgoing_htlcs %}
19 |
20 | {{ htlc.chan_id }}
21 | {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}
22 | {% if htlc.forwarding_channel == '0' %}Self{% else %}{{ htlc.forwarding_channel }} {% endif %}
23 | {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}
24 | {{ htlc.amount|intcomma }}
25 | {{ htlc.hours_til_expiration }} hours
26 | {{ htlc.hash_lock }}
27 |
28 | {% endfor %}
29 |
30 |
31 | {% endif %}
32 | {% if incoming_htlcs %}
33 |
34 |
Incoming HTLCs
35 |
36 |
37 | Channel ID
38 | Channel Alias
39 | Forwarding Channel
40 | Forwarding Alias
41 | Amount
42 | Expiration
43 | Hash Lock
44 |
45 | {% for htlc in incoming_htlcs %}
46 |
47 | {{ htlc.chan_id }}
48 | {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}
49 | {% if htlc.forwarding_channel == '0' %}Self{% else %}{{ htlc.forwarding_channel }} {% endif %}
50 | {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}
51 | {{ htlc.amount|intcomma }}
52 | {{ htlc.hours_til_expiration }} hours
53 | {{ htlc.hash_lock }}
54 |
55 | {% endfor %}
56 |
57 |
58 | {% endif %}
59 | {% if not outgoing_htlcs and not incoming_htlcs %}
60 |
61 |
No pending HTLCs were found!
62 |
63 | {% endif %}
64 | {% endblock %}
65 |
--------------------------------------------------------------------------------
/gui/templates/rebalances.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Rebalances{% endblock %}
3 | {% block content %}
4 | {% include 'rebalances_table.html' with count=20 status=1 load_count=20 title='Rebalance Requests' %}
5 | {% include 'rebalances_table.html' with count=20 status=0 load_count=20 title='Rebalance Requests' %}
6 | {% include 'rebalances_table.html' with count=20 status=2 load_count=20 title='Rebalance Requests' %}
7 | {% include 'rebalances_table.html' with count=50 load_count=50 title='Rebalance Requests' %}
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/gui/templates/rebalances_table.html:
--------------------------------------------------------------------------------
1 | {% load humanize %}
2 |
3 |
{% if status == 2 %}Successful{% elif status == 0 %}Pending{% elif status == 1 %}In-Flight{% else %}Last{% endif %} {{title}}
4 |
5 |
6 | Requested
7 | Start
8 | Stop
9 | Scheduled for
10 | Elapsed
11 | Value
12 | Fee Limit
13 | Target PPM
14 | Fees Paid
15 | Last Hop
16 | Status
17 | Hash
18 | Action
19 |
20 |
21 |
22 |
23 |
24 |
25 | Load More
26 |
27 |
28 |
29 |
30 |
31 |
116 |
--------------------------------------------------------------------------------
/gui/templates/reset.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Reset{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 |
6 |
Reset Data
7 |
8 |
9 | Table
10 | Records
11 | Action
12 |
13 | {% for table in tables %}
14 |
15 | {{ table.name }}
16 | {{ table.count|intcomma }}
17 |
18 | Delete
19 |
20 |
21 | {% endfor %}
22 |
23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/resolutions.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Resolutions{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if resolutions %}
6 |
7 |
Resolutions For Channel: {{ chan_id }}
8 |
9 |
10 | Resolution Type
11 | Outcome
12 | Outpoint
13 | Amount
14 | Sweep TX
15 |
16 | {% for resolution in resolutions %}
17 |
18 | {% if resolution.resolution_type == 0 %}Unknown{% elif resolution.resolution_type == 1 %}Anchor{% elif resolution.resolution_type == 2 %}Incoming HTLC{% elif resolution.resolution_type == 3 %}Outgoing HTLC{% elif resolution.resolution_type == 4 %}Commit{% else %}{{ resolution.resolution_type }}{% endif %}
19 | {% if resolution.outcome == 0 %}Unknown{% elif resolution.outcome == 1 %}Claimed{% elif resolution.outcome == 2 %}Unclaimed{% elif resolution.outcome == 3 %}Abandoned{% elif resolution.outcome == 4 %}First Stage{% elif resolution.outcome == 5 %}Timeout{% else %}{{ resolution.outcome }}{% endif %}
20 | {{ resolution.outpoint_tx }}:{{ resolution.outpoint_index }}
21 | {{ resolution.amount_sat|intcomma }}
22 | {% if resolution.resolution_type != 2 %}{{ resolution.sweep_txid }} {% else %}---{% endif %}
23 |
24 | {% endfor %}
25 |
26 |
27 | {% endif %}
28 | {% if not resolutions %}
29 |
30 |
No resolutions were found for this channel!
31 |
32 | {% endif %}
33 | {% endblock %}
--------------------------------------------------------------------------------
/gui/templates/route.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Routes{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if route %}
6 |
7 |
Route For : {{ payment_hash }}{% if total_cost %} | Total Costs: {{ total_cost }} [{{ total_ppm }}]{% endif %}
8 |
9 |
10 | Attempt Id
11 | Step
12 | Amount
13 | Fee
14 | PPM
15 | Cost To
16 | 📍
17 | Alias
18 | Channel ID
19 | Channel Capacity
20 |
21 | {% for hop in route %}
22 |
23 | {{ hop.attempt_id }}
24 | {{ hop.step }}
25 | {{ hop.amt|intcomma }}
26 | {{ hop.fee|intcomma }}
27 | {{ hop.ppm|intcomma }}
28 | {{ hop.cost_to|intcomma }}
29 | {% if hop.step == 1 %}📍{% else %}🔻{% endif %}
30 | {% if hop.alias == '' %}---{% else %}{{ hop.alias }}{% endif %}
31 | {{ hop.chan_id }}
32 | {{ hop.chan_capacity|intcomma }}
33 |
34 | {% endfor %}
35 |
36 |
37 | {% endif %}
38 | {% if not route %}
39 |
40 |
A route was not found for this payment hash!
41 |
42 | {% endif %}
43 | {% include 'rebalances_table.html' with title='Linked Rebalance' payment_hash=payment_hash %}
44 | {% if invoices %}
45 |
46 |
Linked Invoice
47 |
48 |
49 | Created
50 | Settled
51 | Payment Hash
52 | Value
53 | Amount Paid
54 | State
55 | Channel In Alias
56 | Channel In
57 | Keysend
58 |
59 | {% for invoice in invoices %}
60 |
61 | {{ invoice.creation_date|naturaltime }}
62 | {% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %}
63 | {{ invoice.r_hash }}
64 | {{ invoice.value|add:"0"|intcomma }}
65 | {% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %}
66 | {% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}
67 | {% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %}
68 | {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }} {% else %}---{% endif %}
69 | {% if invoice.keysend_preimage != None %}Yes {% else %}No{% endif %}
70 |
71 | {% endfor %}
72 |
73 |
74 | {% endif %}
75 | {% if outgoing_htlcs %}
76 |
77 |
Outgoing HTLCs
78 |
79 |
80 | Channel ID
81 | Channel Alias
82 | Forwarding Channel
83 | Forwarding Alias
84 | Amount
85 | Expiration
86 | Hash Lock
87 |
88 | {% for htlc in outgoing_htlcs %}
89 |
90 | {{ htlc.chan_id }}
91 | {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}
92 | {% if htlc.forwarding_channel == 0 %}---{% else %}{{ htlc.forwarding_channel }} {% endif %}
93 | {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}
94 | {{ htlc.amount|intcomma }}
95 | {{ htlc.hours_til_expiration }} hours
96 | {{ htlc.hash_lock }}
97 |
98 | {% endfor %}
99 |
100 |
101 | {% endif %}
102 | {% if incoming_htlcs %}
103 |
104 |
Incoming HTLCs
105 |
106 |
107 | Channel ID
108 | Channel Alias
109 | Forwarding Channel
110 | Forwarding Alias
111 | Amount
112 | Expiration
113 | Hash Lock
114 |
115 | {% for htlc in incoming_htlcs %}
116 |
117 | {{ htlc.chan_id }}
118 | {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}
119 | {% if htlc.forwarding_channel == 0 %}---{% else %}{{ htlc.forwarding_channel }} {% endif %}
120 | {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}
121 | {{ htlc.amount|intcomma }}
122 | {{ htlc.hours_til_expiration }} hours
123 | {{ htlc.hash_lock }}
124 |
125 | {% endfor %}
126 |
127 |
128 | {% endif %}
129 | {% endblock %}
130 |
--------------------------------------------------------------------------------
/gui/templates/towers.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %} {{ block.super }} - Watch Towers{% endblock %}
3 | {% block content %}
4 | {% load humanize %}
5 | {% if stats %}
6 |
7 |
Watch Tower Stats
8 |
9 |
10 | Stat
11 | Value
12 |
13 |
14 | Total Backups
15 | {{ stats.num_backups|intcomma }}
16 |
17 |
18 | Pending Backups
19 | {{ stats.num_pending_backups|intcomma }}
20 |
21 |
22 | Failed Backups
23 | {{ stats.num_failed_backups|intcomma }}
24 |
25 |
26 | Sessions Acquired
27 | {{ stats.num_sessions_acquired|intcomma }}
28 |
29 |
30 | Sessions Exhausted
31 | {{ stats.num_sessions_exhausted|intcomma }}
32 |
33 |
34 |
35 | {% endif %}
36 | {% if active_towers %}
37 |
38 |
Active Towers
39 |
40 |
41 | Pubkey
42 | Address
43 | Sessions
44 | Remove
45 |
46 | {% for tower in active_towers %}
47 |
48 | {{ tower.pubkey }}
49 | {{ tower.address }}
50 | {{ tower.num_sessions|intcomma }}
51 |
52 |
57 |
58 |
59 | {% endfor %}
60 |
61 |
62 | {% endif %}
63 | {% if inactive_towers %}
64 |
65 |
Inactive Towers
66 |
67 |
68 | Pubkey
69 | Address
70 | Sessions
71 | Enable
72 | Delete
73 |
74 | {% for tower in inactive_towers %}
75 |
76 | {{ tower.pubkey }}
77 | {{ tower.address }}
78 | {{ tower.num_sessions|intcomma }}
79 |
80 |
85 |
86 |
87 |
93 |
94 |
95 | {% endfor %}
96 |
97 |
98 | {% endif %}
99 | {% if not active_towers and not inactive_towers %}
100 |
101 |
No watch towers found!
102 |
103 | {% endif %}
104 |
105 |
Add A Watch Tower
106 |
107 |
113 |
114 |
115 | {% endblock %}
--------------------------------------------------------------------------------
/gui/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path, include
2 | from rest_framework import routers
3 | from django.contrib import admin
4 | from . import views
5 |
6 | router = routers.DefaultRouter()
7 | router.register(r'payments', views.PaymentsViewSet)
8 | router.register(r'paymenthops', views.PaymentHopsViewSet)
9 | router.register(r'invoices', views.InvoicesViewSet)
10 | router.register(r'forwards', views.ForwardsViewSet)
11 | router.register(r'onchain', views.OnchainViewSet)
12 | router.register(r'closures', views.ClosuresViewSet)
13 | router.register(r'resolutions', views.ResolutionsViewSet)
14 | router.register(r'peers', views.PeersViewSet)
15 | router.register(r'channels', views.ChannelsViewSet)
16 | router.register(r'rebalancer', views.RebalancerViewSet)
17 | router.register(r'settings', views.LocalSettingsViewSet)
18 | router.register(r'pendinghtlcs', views.PendingHTLCViewSet)
19 | router.register(r'failedhtlcs', views.FailedHTLCViewSet)
20 | router.register(r'peerevents', views.PeerEventsViewSet)
21 | router.register(r'trades', views.TradeSalesViewSet)
22 | router.register(r'feelog', views.FeeLogViewSet)
23 | router.register(r'inboundfeelog', views.InboundFeeLogViewSet)
24 |
25 | urlpatterns = [
26 | path('', views.home, name='home'),
27 | path('route', views.route, name='route'),
28 | path('routes', views.routes, name='routes'),
29 | path('peers', views.peers, name='peers'),
30 | path('balances', views.balances, name='balances'),
31 | path('closures', views.closures, name='closures'),
32 | path('towers', views.towers, name='towers'),
33 | path('batch', views.batch, name='batch'),
34 | path('trades', views.trades, name='trades'),
35 | path('batchopen/', views.batch_open, name='batch-open'),
36 | path('resolutions', views.resolutions, name='resolutions'),
37 | path('channel', views.channel, name='channel'),
38 | path('pending_htlcs', views.pending_htlcs, name='pending-htlcs'),
39 | path('failed_htlcs', views.failed_htlcs, name='failed-htlcs'),
40 | path('payments', views.payments, name='payments'),
41 | path('invoices', views.invoices, name='invoices'),
42 | path('forwards', views.forwards, name='forwards'),
43 | path('income', views.income, name='income'),
44 | path('rebalances', views.rebalances, name='rebalances'),
45 | path('rebalancing', views.rebalancing, name='rebalancing'),
46 | path('openchannel/', views.open_channel_form, name='open-channel-form'),
47 | path('closechannel/', views.close_channel_form, name='close-channel-form'),
48 | path('connectpeer/', views.connect_peer_form, name='connect-peer-form'),
49 | path('addtower/', views.add_tower_form, name='add-tower-form'),
50 | path('deletetower/', views.delete_tower_form, name='delete-tower-form'),
51 | path('removetower/', views.remove_tower_form, name='remove-tower-form'),
52 | path('createinvoice/', views.add_invoice_form, name='add-invoice-form'),
53 | path('rebalancer/', views.rebalance, name='rebalancer'),
54 | path('update_settings/', views.update_settings, name='update-settings'),
55 | path('update_channel/', views.update_channel, name='update-channel'),
56 | path('update_pending/', views.update_pending, name='update-pending'),
57 | path('update_setting/', views.update_setting, name='update-setting'),
58 | path('update_closing/', views.update_closing, name='update-closing'),
59 | path('update_keysend/', views.update_keysend, name='update-keysend'),
60 | path('add_avoid/', views.add_avoid, name='add-avoid'),
61 | path('remove_avoid/', views.remove_avoid, name='remove-avoid'),
62 | path('get_fees/', views.get_fees, name='get-fees'),
63 | path('opens/', views.opens, name='opens'),
64 | path('unprofitable_channels/', views.unprofitable_channels, name='unprofitable-channels'),
65 | path('actions/', views.actions, name='actions'),
66 | path('fees/', views.fees, name='fees'),
67 | path('keysends/', views.keysends, name='keysends'),
68 | path('channels/', views.channels, name='channels'),
69 | path('autopilot/', views.autopilot, name='autopilot'),
70 | path('autofees/', views.outbound_fee_log, name='outbound-fee-log'),
71 | path('inbound-fee-log/', views.inbound_fee_log, name='inbound-fee-log'),
72 | path('peerevents', views.peerevents, name='peerevents'),
73 | path('advanced/', views.advanced, name='advanced'),
74 | path('logs/', views.logs, name='logs'),
75 | path('addresses/', views.addresses, name='addresses'),
76 | path('reset/', views.reset, name='reset'),
77 | path('api/', include(router.urls), name='api-root'),
78 | path('api-auth/', include('rest_framework.urls'), name='api-auth'),
79 | path('api/connectpeer/', views.connect_peer, name='connect-peer'),
80 | path('api/disconnectpeer/', views.disconnect_peer, name='disconnect-peer'),
81 | path('api/rebalance_stats/', views.rebalance_stats, name='rebalance-stats'),
82 | path('api/openchannel/', views.open_channel, name='open-channel'),
83 | path('api/closechannel/', views.close_channel, name='close-channel'),
84 | path('api/createinvoice/', views.add_invoice, name='add-invoice'),
85 | path('api/newaddress/', views.new_address, name='new-address'),
86 | path('api/consolidate/', views.consolidate_utxos, name='consolidate-utxos'),
87 | path('api/updatealias/', views.update_alias, name='update-alias'),
88 | path('api/getinfo/', views.get_info, name='get-info'),
89 | path('api/balances/', views.api_balances, name='api-balances'),
90 | path('api/income/', views.api_income, name='api-income'),
91 | path('api/pendingchannels/', views.pending_channels, name='pending-channels'),
92 | path('api/bumpfee/', views.bump_fee, name='bump-fee'),
93 | path('api/chart/', views.chart, name='chart'),
94 | path('api/chanpolicy/', views.chan_policy, name='chan-policy'),
95 | path('api/broadcast_tx/', views.broadcast_tx, name='broadcast-tx'),
96 | path('api/node_info/', views.node_info, name='node-info'),
97 | path('api/createtrade/', views.create_trade, name='create-trade'),
98 | path('api/forwards_summary/', views.forwards_summary, name='forwards-summary'),
99 | path('api/sign_message/', views.sign_message, name='sign-message'),
100 | path('api/reset/', views.reset_api, name='reset-api'),
101 | path('lndg-admin/', admin.site.urls),
102 | ]
103 |
--------------------------------------------------------------------------------
/htlc_stream.py:
--------------------------------------------------------------------------------
1 | import django
2 | from datetime import datetime
3 | from gui.lnd_deps import router_pb2 as lnr
4 | from gui.lnd_deps import router_pb2_grpc as lnrouter
5 | from gui.lnd_deps.lnd_connect import lnd_connect
6 | from os import environ
7 | from time import sleep
8 | environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
9 | django.setup()
10 | from gui.models import Channels, FailedHTLCs
11 |
12 | def main():
13 | while True:
14 | try:
15 | print(f"{datetime.now().strftime('%c')} : [HTLC] : Starting failed HTLC stream...")
16 | connection = lnd_connect()
17 | routerstub = lnrouter.RouterStub(connection)
18 | all_forwards = {}
19 | for response in routerstub.SubscribeHtlcEvents(lnr.SubscribeHtlcEventsRequest()):
20 | if response.event_type == 3 and str(response.link_fail_event) != '':
21 | in_chan_id = response.incoming_channel_id
22 | out_chan_id = response.outgoing_channel_id
23 | in_chan = Channels.objects.filter(chan_id=in_chan_id)[0] if Channels.objects.filter(chan_id=in_chan_id).exists() else None
24 | out_chan = Channels.objects.filter(chan_id=out_chan_id)[0] if Channels.objects.filter(chan_id=out_chan_id).exists() else None
25 | in_chan_alias = in_chan.alias if in_chan is not None else None
26 | out_chan_alias = out_chan.alias if out_chan is not None else None
27 | out_chan_liq = max(0, (out_chan.local_balance - out_chan.local_chan_reserve)) if out_chan is not None else None
28 | out_chan_pending = out_chan.pending_outbound if out_chan is not None else None
29 | amount = int(response.link_fail_event.info.outgoing_amt_msat/1000)
30 | wire_failure = response.link_fail_event.wire_failure
31 | failure_detail = response.link_fail_event.failure_detail
32 | missed_fee = (response.link_fail_event.info.incoming_amt_msat - response.link_fail_event.info.outgoing_amt_msat)/1000
33 | FailedHTLCs(amount=amount, chan_id_in=in_chan_id, chan_id_out=out_chan_id, chan_in_alias=in_chan_alias, chan_out_alias=out_chan_alias, chan_out_liq=out_chan_liq, chan_out_pending=out_chan_pending, wire_failure=wire_failure, failure_detail=failure_detail, missed_fee=missed_fee).save()
34 | elif response.event_type == 3 and str(response.forward_event) != '':
35 | # Add forward_event
36 | key = str(response.incoming_channel_id) + str(response.outgoing_channel_id) + str(response.incoming_htlc_id) + str(response.outgoing_htlc_id)
37 | all_forwards[key] = response.forward_event
38 | elif response.event_type == 3 and str(response.settle_event) != '':
39 | # Delete forward_event
40 | key = str(response.incoming_channel_id) + str(response.outgoing_channel_id) + str(response.incoming_htlc_id) + str(response.outgoing_htlc_id)
41 | if key in all_forwards.keys():
42 | del all_forwards[key]
43 | elif response.event_type == 3 and str(response.forward_fail_event) == '':
44 | key = str(response.incoming_channel_id) + str(response.outgoing_channel_id) + str(response.incoming_htlc_id) + str(response.outgoing_htlc_id)
45 | if key in all_forwards.keys():
46 | forward_event = all_forwards[key]
47 | in_chan_id = response.incoming_channel_id
48 | out_chan_id = response.outgoing_channel_id
49 | in_chan = Channels.objects.filter(chan_id=in_chan_id)[0] if Channels.objects.filter(chan_id=in_chan_id).exists() else None
50 | out_chan = Channels.objects.filter(chan_id=out_chan_id)[0] if Channels.objects.filter(chan_id=out_chan_id).exists() else None
51 | in_chan_alias = in_chan.alias if in_chan is not None else None
52 | out_chan_alias = out_chan.alias if out_chan is not None else None
53 | out_chan_liq = max(0, (out_chan.local_balance - out_chan.local_chan_reserve)) if out_chan is not None else None
54 | out_chan_pending = out_chan.pending_outbound if out_chan is not None else None
55 | amount = int(forward_event.info.incoming_amt_msat/1000)
56 | wire_failure = 99
57 | failure_detail = 99
58 | missed_fee = (forward_event.info.incoming_amt_msat - forward_event.info.outgoing_amt_msat)/1000
59 | FailedHTLCs(amount=amount, chan_id_in=in_chan_id, chan_id_out=out_chan_id, chan_in_alias=in_chan_alias, chan_out_alias=out_chan_alias, chan_out_liq=out_chan_liq, chan_out_pending=out_chan_pending, wire_failure=wire_failure, failure_detail=failure_detail, missed_fee=missed_fee).save()
60 | del all_forwards[key]
61 | except Exception as e:
62 | print(f"{datetime.now().strftime('%c')} : [HTLC] : Error while running failed HTLC stream: {str(e)}")
63 | sleep(20)
64 |
65 | if __name__ == '__main__':
66 | main()
--------------------------------------------------------------------------------
/keysend.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import secrets, time, struct
3 | from hashlib import sha256
4 | from gui.lnd_deps import lightning_pb2 as ln
5 | from gui.lnd_deps import lightning_pb2_grpc as lnrpc
6 | from gui.lnd_deps import router_pb2 as lnr
7 | from gui.lnd_deps import router_pb2_grpc as lnrouter
8 | from gui.lnd_deps import signer_pb2 as lns
9 | from gui.lnd_deps import signer_pb2_grpc as lnsigner
10 | from gui.lnd_deps.lnd_connect import lnd_connect
11 |
12 | def keysend(target_pubkey, msg, amount, fee_limit, timeout, sign):
13 | #Construct and send
14 | try:
15 | routerstub = lnrouter.RouterStub(lnd_connect())
16 | secret = secrets.token_bytes(32)
17 | hashed_secret = sha256(secret).hexdigest()
18 | custom_records = [(5482373484, secret),]
19 | msg = str(msg)
20 | if len(msg) > 0:
21 | custom_records.append((34349334, bytes.fromhex(msg.encode('utf-8').hex())))
22 | if sign == True:
23 | stub = lnrpc.LightningStub(lnd_connect())
24 | signerstub = lnsigner.SignerStub(lnd_connect())
25 | self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
26 | timestamp = struct.pack(">i", int(time.time()))
27 | signature = signerstub.SignMessage(lns.SignMessageReq(msg=(bytes.fromhex(self_pubkey)+bytes.fromhex(target_pubkey)+timestamp+bytes.fromhex(msg.encode('utf-8').hex())), key_loc=lns.KeyLocator(key_family=6, key_index=0))).signature
28 | custom_records.append((34349337, signature))
29 | custom_records.append((34349339, bytes.fromhex(self_pubkey)))
30 | custom_records.append((34349343, timestamp))
31 | for response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(dest=bytes.fromhex(target_pubkey), dest_custom_records=custom_records, fee_limit_sat=fee_limit, timeout_seconds=timeout, amt=amount, payment_hash=bytes.fromhex(hashed_secret))):
32 | if response.status == 1:
33 | print('In-flight')
34 | if response.status == 2:
35 | print('Succeeded')
36 | if response.status == 3:
37 | if response.failure_reason == 1:
38 | print('Failure - Timeout')
39 | elif response.failure_reason == 2:
40 | print('Failure - No Route')
41 | elif response.failure_reason == 3:
42 | print('Failure - Error')
43 | elif response.failure_reason == 4:
44 | print('Failure - Incorrect Payment Details')
45 | elif response.failure_reason == 5:
46 | print('Failure Insufficient Balance')
47 | if response.status == 0:
48 | print('Unknown Error')
49 | except Exception as e:
50 | error = str(e)
51 | details_index = error.find('details =') + 11
52 | debug_error_index = error.find('debug_error_string =') - 3
53 | error_msg = error[details_index:debug_error_index]
54 | print('Error while sending keysend payment! Error: ' + error_msg)
55 |
56 | def main(pubkey, amount, fee, msg, sign):
57 | print('Sending a %s sats payment to: %s with %s sats max-fee' % (amount, pubkey, fee))
58 | if len(msg) > 0:
59 | print('MESSAGE: %s' % msg)
60 | timeout = 10
61 | keysend(pubkey, msg, amount, fee, timeout, sign)
62 |
63 | if __name__ == '__main__':
64 | argParser = argparse.ArgumentParser(prog="python keysend.py")
65 | argParser.add_argument("-pk", "--pubkey", help='Target public key', required=True)
66 | argParser.add_argument("-a", "--amount", help='Amount in sats (default: 1)', nargs='?', default=1, type=int)
67 | argParser.add_argument("-f", "--fee", help='Max fee to send this keysend (default: 1)', nargs='?', default=1, type=int)
68 | argParser.add_argument("-m", "--msg", help='Message to be sent (default: "")', nargs='?', default='', type=str)
69 | argParser.add_argument("--sign", help='Sign this message (default: send anonymously) - if [MSG] is provided', action='store_true')
70 | args = vars(argParser.parse_args())
71 | main(**args)
--------------------------------------------------------------------------------
/lndg/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cryptosharks131/lndg/fedcbaf48608372ad13c1c257d2292ced0368533/lndg/__init__.py
--------------------------------------------------------------------------------
/lndg/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for lndg project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lndg.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/lndg/urls.py:
--------------------------------------------------------------------------------
1 | """lndg URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.urls import path, include
17 | from django.views.generic.base import RedirectView
18 | from django.contrib.staticfiles.storage import staticfiles_storage
19 | from django.contrib.auth.views import LogoutView
20 |
21 | urlpatterns = [
22 | path('favicon.ico', RedirectView.as_view(url=staticfiles_storage.url("favicon.ico"))),
23 | path('logout/', LogoutView.as_view(next_page='/'), name='logout'),
24 | path('', include('gui.urls')),
25 | ]
--------------------------------------------------------------------------------
/lndg/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for lndg project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lndg.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main(args):
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lndg.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(args)
19 |
20 |
21 | if __name__ == '__main__':
22 | main(sys.argv)
23 |
--------------------------------------------------------------------------------
/nginx.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | INSTALL_USER=${SUDO_USER:-${USER}}
4 | NODE_IP=$(hostname -I | cut -d' ' -f1)
5 |
6 | if [ "$INSTALL_USER" == 'root' ]; then
7 | HOME_DIR='/root'
8 | else
9 | HOME_DIR="/home/$INSTALL_USER"
10 | fi
11 | LNDG_DIR="$HOME_DIR/lndg"
12 |
13 | function check_path() {
14 | if [ -e $LNDG_DIR/lndg/wsgi.py ]; then
15 | echo "Using LNDg installation found at $LNDG_DIR"
16 | else
17 | echo "LNDg installation not found at $LNDG_DIR/, please provide the correct path:"
18 | read USR_DIR
19 | if [ -e $USR_DIR/lndg/wsgi.py ]; then
20 | LNDG_DIR=$USR_DIR
21 | echo "Using LNDg installation found at $LNDG_DIR"
22 | else
23 | echo "LNDg installation still not found, exiting..."
24 | exit 1
25 | fi
26 | fi
27 | }
28 |
29 | # Ensure the lndg directory exists
30 | mkdir -p $LNDG_DIR
31 | mkdir -p $LNDG_DIR/.venv/bin
32 | mkdir -p /var/log/uwsgi
33 |
34 | function install_deps() {
35 | apt-get update
36 | apt-get install -y python3-dev build-essential python3-pip uwsgi nginx
37 | python3 -m pip install uwsgi
38 | }
39 |
40 | function setup_uwsgi() {
41 | # Creating the lndg.ini file
42 | cat << EOF > $LNDG_DIR/lndg.ini
43 | # lndg.ini file
44 | [uwsgi]
45 | # Django-related settings
46 | chdir = $LNDG_DIR
47 | module = lndg.wsgi
48 | home = $LNDG_DIR/.venv
49 | logto = /var/log/uwsgi/%n.log
50 |
51 | # process-related settings
52 | master = true
53 | processes = 1
54 | socket = $LNDG_DIR/lndg.sock
55 | chmod-socket = 660
56 | vacuum = true
57 | EOF
58 |
59 | # Creating the uwsgi_params file
60 | cat << EOF > $LNDG_DIR/uwsgi_params
61 | uwsgi_param QUERY_STRING \$query_string;
62 | uwsgi_param REQUEST_METHOD \$request_method;
63 | uwsgi_param CONTENT_TYPE \$content_type;
64 | uwsgi_param CONTENT_LENGTH \$content_length;
65 |
66 | uwsgi_param REQUEST_URI "\$request_uri";
67 | uwsgi_param PATH_INFO "\$document_uri";
68 | uwsgi_param DOCUMENT_ROOT "\$document_root";
69 | uwsgi_param SERVER_PROTOCOL "\$server_protocol";
70 | uwsgi_param REQUEST_SCHEME "\$scheme";
71 | uwsgi_param HTTPS "\$https if_not_empty";
72 |
73 | uwsgi_param REMOTE_ADDR "\$remote_addr";
74 | uwsgi_param REMOTE_PORT "\$remote_port";
75 | uwsgi_param SERVER_PORT "\$server_port";
76 | uwsgi_param SERVER_NAME "\$server_name";
77 | EOF
78 |
79 | # Creating the uwsgi.service systemd unit file
80 | cat << EOF > /etc/systemd/system/uwsgi.service
81 | [Unit]
82 | Description=Lndg uWSGI app
83 | After=syslog.target
84 |
85 | [Service]
86 | ExecStart=$LNDG_DIR/.venv/bin/uwsgi --ini $LNDG_DIR/lndg.ini
87 | User=$INSTALL_USER
88 | Group=www-data
89 | Restart=on-failure
90 | KillSignal=SIGQUIT
91 | Type=notify
92 | StandardError=syslog
93 | NotifyAccess=all
94 |
95 | [Install]
96 | WantedBy=multi-user.target
97 | EOF
98 | usermod -a -G www-data $INSTALL_USER
99 | }
100 |
101 | function setup_nginx() {
102 | # Creating the Nginx configuration file
103 | cat << EOF > /etc/nginx/sites-available/lndg
104 | upstream django {
105 | server unix://$LNDG_DIR/lndg.sock; # for a file socket
106 | }
107 |
108 | server {
109 | listen 8889;
110 | server_name _;
111 | charset utf-8;
112 | client_max_body_size 75M;
113 | proxy_read_timeout 180;
114 |
115 | location /static {
116 | alias $LNDG_DIR/gui/static; # your Django project's static files - amend as required
117 | }
118 |
119 | location / {
120 | uwsgi_pass django;
121 | include $LNDG_DIR/uwsgi_params; # the uwsgi_params file
122 | }
123 | }
124 | EOF
125 |
126 | # Remove the default site and link the new site
127 | rm /etc/nginx/sites-enabled/default
128 | ln -sf /etc/nginx/sites-available/lndg /etc/nginx/sites-enabled/
129 | }
130 |
131 | function start_services() {
132 | touch /var/log/uwsgi/lndg.log
133 | touch $LNDG_DIR/lndg.sock
134 | chgrp www-data /var/log/uwsgi/lndg.log
135 | chgrp www-data $LNDG_DIR/lndg.sock
136 | chmod 660 /var/log/uwsgi/lndg.log
137 | systemctl start uwsgi
138 | systemctl restart nginx
139 | systemctl enable uwsgi
140 | systemctl enable nginx
141 | }
142 |
143 | function report_information() {
144 | echo "Nginx and uWSGI have been set up with user $INSTALL_USER at $NODE_IP:8889."
145 | }
146 |
147 | ##### Main #####
148 | echo -e "Setting up, this may take a few minutes..."
149 | check_path
150 | install_deps
151 | setup_uwsgi
152 | setup_nginx
153 | start_services
154 | report_information
--------------------------------------------------------------------------------
/p2p.py:
--------------------------------------------------------------------------------
1 | import django, multiprocessing
2 | from datetime import datetime
3 | from gui.lnd_deps import lightning_pb2_grpc as lnrpc
4 | from gui.lnd_deps.lnd_connect import lnd_connect
5 | from os import environ
6 | from time import sleep
7 | environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
8 | django.setup()
9 | from gui.models import LocalSettings
10 | from trade import serve_trades
11 |
12 | def trade():
13 | stub = lnrpc.LightningStub(lnd_connect())
14 | serve_trades(stub)
15 |
16 | def check_setting():
17 | if LocalSettings.objects.filter(key='LND-ServeTrades').exists():
18 | return int(LocalSettings.objects.filter(key='LND-ServeTrades')[0].value)
19 | else:
20 | LocalSettings(key='LND-ServeTrades', value='0').save()
21 | return 0
22 |
23 | def main():
24 | while True:
25 | current_value = None
26 | try:
27 | while True:
28 | db_value = check_setting()
29 | if current_value != db_value:
30 | if db_value == 1:
31 | print(f"{datetime.now().strftime('%c')} : [P2P] : Starting the p2p service...")
32 | django.db.connections.close_all()
33 | p2p_thread = multiprocessing.Process(target=trade)
34 | p2p_thread.start()
35 | else:
36 | if 'p2p_thread' in locals() and p2p_thread.is_alive():
37 | print(f"{datetime.now().strftime('%c')} : [P2P] : Stopping the p2p service...")
38 | p2p_thread.terminate()
39 | current_value = db_value
40 | sleep(2) # polling interval
41 | except Exception as e:
42 | print(f"{datetime.now().strftime('%c')} : [P2P] : Error running p2p service: {str(e)}")
43 | finally:
44 | if 'p2p_thread' in locals() and p2p_thread.is_alive():
45 | print(f"{datetime.now().strftime('%c')} : [P2P] : Removing any remaining processes...")
46 | p2p_thread.terminate()
47 | sleep(20)
48 |
49 | if __name__ == '__main__':
50 | main()
--------------------------------------------------------------------------------
/postgres.md:
--------------------------------------------------------------------------------
1 | # LNDg Postgres Setup
2 |
3 | - **You will need to fill in the proper password below in the paths marked ``.**
4 |
5 | ## Postgres Installation
6 | Install postgres
7 | `sudo apt install postgresql`
8 |
9 | Switch to postgres user and open the client
10 | `sudo su - postgres`
11 | `psql`
12 |
13 | Create the LNDg Database - Be sure to replace the password with your own
14 | `create user lndg;create database lndg LOCALE 'en_US.UTF-8';alter role lndg with password '';grant all privileges on database lndg to lndg;alter database lndg owner to lndg;`
15 |
16 | Exit the postgres client and user
17 | `\q`
18 | `exit`
19 |
20 | ## Setting up LNDg with postgres
21 | Enter the LNDg installation folder
22 | `cd lndg`
23 |
24 | Upgrade setuptools and get required packages
25 | `.venv/bin/pip install --upgrade setuptools`
26 | `sudo apt install gcc python3-dev libpq-dev`
27 |
28 | Build psycopg2 for postgres connection
29 | `.venv/bin/pip install psycopg2`
30 |
31 | Update the `DATABASES` section of `lndg/lndg/settings.py`, be sure to replace the password
32 | ```
33 | DATABASES = {
34 | 'default': {
35 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
36 | 'NAME': 'lndg',
37 | 'USER': 'lndg',
38 | 'PASSWORD': '',
39 | 'HOST': 'localhost',
40 | 'PORT': '5432',
41 | }
42 | }
43 | ```
44 | Initialize the postgres database
45 | `.venv/bin/python manage.py migrate`
46 |
47 | ## OPTIONAL: Migrating An Existing Database
48 | Stop the LNDg services controller and web service
49 | `sudo systemctl stop lndg-controller.service`
50 | `sudo systemctl stop uwsgi.service`
51 |
52 | Dump current data to json format
53 | `.venv/bin/python manage.py dumpdata gui.channels gui.peers gui.rebalancer gui.localsettings gui.autopilot gui.autofees gui.failedhtlcs gui.avoidnodes gui.peerevents > dump.json`
54 |
55 | Import the dump
56 | `.venv/bin/python manage.py loaddata dump.json`
57 |
58 | ## Finishing Setup
59 | Recreate a login user - any username and password may be used
60 | `.venv/bin/python manage.py createsuperuser`
61 |
62 | Restart the LNDg controller and web service
63 | `sudo systemctl restart lndg-controller.service`
64 | `sudo systemctl restart uwsgi.service`
--------------------------------------------------------------------------------
/quickstart.md:
--------------------------------------------------------------------------------
1 | # A Quick Start Guide For The AR (Auto-Rebalancer)
2 |
3 | ## Here is a quick thread to get you started with the AR in LNDg. It can take a bit to get the hang of it but once you do, it's very powerful. 💪
4 | 1. In the channels table, identify a good paying and high outflow channel. This is a great channel to start learning with as attempts are based on the enabled or targeted channel. Also check that the peer's fees on the other side is less than yours else it could never rebalance.
5 |
6 | 2. Once you have identified a well earning outflow channel, use the enable button on the right side to start targeting this channel to be refilled with outbound liquidity. We know this channel flows out well and earns sats, lets keep it filled and busy routing! 😎
7 |
8 | 3. Repeat this process for all channels that earn well and you do not want to lose outbound liquidity on (a good example would be LOOP). Don't worry too much about keeping each channel at 50/50 balance but instead keep your focus on putting liquidity back where it earns you sats.
9 |
10 | 4. Now that you have a few channels enabled and targeted, you can drop the iTarget% for each enabled channel down to your desired preference. If you keep the iTarget% at 100 then nothing will ever happen because the inbound liquidity can never go above 100.
11 |
12 | 5. To get the AR working on this channel, the inbound liquidity on the channel must be above the iTarget% and thus the 100 would never do anything. Setting an iTarget% of 60 however would refill this channel with outbound liquidity until the inbound liquidity fell below 60%.
13 |
14 | 6. A tip when entering a new value into the iTarget% bow, you must hit enter afterwards for the value to be saved! Now that we understand how to refill channels, lets talk about where we have to pull the liquidity from in order to refill these channels.
15 |
16 | 7. One setting that will affect which channels can feed others during refilling is AR-Outbound%. This can be found in the AR settings table. If you don't see a particular setting in the table yet, try to set it using the form just below the table. When targeting a channel to be refilled, LNDg will use any channel that is not already enabled AND the outbound liquidity of the channel is greater than the AR-Outbound%. Advanced users may prefer to update the oTarget% for each channel, which overrides this global setting.
17 |
18 | 8. Just to recap, we are enabling channels we want to refill with outbound and those channels will be fed liquidity by channels that are not enabled. We can control how far to push the inbound liquidity down during refilling outbound liquidity of a channel with the iTarget%.
19 |
20 | 9. Now that you have things setup on a channel basis, you can review the global settings a bit and make sure things are to your liking. Lets look at each global setting and how to use it.
21 |
22 | 10. AR-Enabled: This is used to turn the AR on and off. By setting this value to 1 you will allow LNDg to start scheduling rebalances based on your specifications. Think of this as a global on and off switch.
23 |
24 | 11. AR-Target%: This specifies how big the rebalance attempts should be as a function of the channel capacity. This can be overridden in the advanced settings by setting a channel specific target amount. Updating this value will also update all specific channel target amounts.
25 |
26 | 12. AR-Outbound%: This marks how full a channel must be in order to be considered to feed another channels during a rebalance attempt. This can be overridden as well in the advanced settings by setting a channel specific oTarget%.
27 |
28 | 13. AR-MaxCost%: This lets LNDg know what % of the fees you charge on the enabled channel to put towards rebalancing that channel. If you charge 100 ppm out on an enabled channel with a 0.50 (50%) max cost. Then the max you will ever pay for this rebalance attempt will be 50 ppm.
29 |
30 | 14. AR-Time: This determines how long we should search for a route before moving on to the next rebalance attempt. LNDg will not look again for 30 minutes after failing an attempt so try to keep things busy without making rebalances too long. Recommend staying around 3-5 mins.
31 |
32 | 15. AR-MaxFeeRate: This is a fail safe to globally cap the max fee rates on any rebalance attempt. This cap will override the max cost % calculated value if it is lower. In our max cost example, a MaxFeeRate of 40 would have capped that rebalance down to 40 ppm instead of 50.
33 |
34 | 16. AR-Autopilot: This setting will allow LNDg to automatically enable and disable channels based on the flow pattern of your node. This is a pretty great feature but go slow and try things out manually a bit before letting LNDg take over. This will help build more confidence.
35 |
36 | 17. If you want to get an idea of what LNDg thinks you should be enabling or what would LNDg do if you were to turn on AR-Autopilot, then just head over to the Suggested AR Actions page (link right above channels table). If you see things here, this is what AP would do.
37 |
38 | 18. If there are any channels you want to make sure are excluded from all rebalancing, you can either enable it and make sure the iTarget% is set to 100 or set the oTarget% to 100 as well if you want to keep it disabled.
39 |
40 | 19. You should now be all set to fire things up! Go ahead and change the AR-Enabled value to 1 and start watching for rebalances to get scheduled by refreshing the page after a few moments. A new table should appear showing all the rebalances that LNDg has started to schedule.
41 |
42 | 20. That's it! You are now on your way to maximizing your flow and potential earnings! 😎💪
43 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django
2 | djangorestframework
3 | django-filter
4 | grpcio
5 | protobuf
6 | pytz
7 | pandas
8 | requests
9 | asyncio
10 | bech32
11 | cryptography
12 | paramiko
--------------------------------------------------------------------------------
/systemd.md:
--------------------------------------------------------------------------------
1 | # Systemd Setup For Backend Tools
2 |
3 | - **You will need to fill in the proper username below in the paths marked ``.**
4 | - **This assumes you have installed lndg on the home directory of the user. For example, user `mynode` will have a home directory of `/home/mynode/`.**
5 |
6 | ## Backend Controller Setup
7 | Create a service file for `controller.py`, copying the contents below to the file and filling in the user you would like this to run under.
8 | `nano /etc/systemd/system/lndg-controller.service`
9 | ```
10 | [Unit]
11 | Description=Backend Controller For Lndg
12 | [Service]
13 | Environment=PYTHONUNBUFFERED=1
14 | User=
15 | Group=
16 | ExecStart=/home//lndg/.venv/bin/python /home//lndg/controller.py
17 | StandardOutput=append:/var/log/lndg-controller.log
18 | StandardError=append:/var/log/lndg-controller.log
19 | Restart=always
20 | RestartSec=60s
21 | [Install]
22 | WantedBy=multi-user.target
23 | ```
24 | Enable and start the service to run the backend controller.
25 | `sudo systemctl enable lndg-controller.service`
26 | `sudo systemctl start lndg-controller.service`
27 |
28 | ## Additional Commands
29 | You can also check on the status, disable or stop the backend controller.
30 | `sudo systemctl status lndg-controller.service`
31 | `sudo systemctl disable lndg-controller.service`
32 | `sudo systemctl stop lndg-controller.service`
33 |
--------------------------------------------------------------------------------
/systemd.sh:
--------------------------------------------------------------------------------
1 | INSTALL_USER=${SUDO_USER:-${USER}}
2 | REFRESH=20
3 | RED='\033[0;31m'
4 | NC='\033[0m'
5 |
6 | if [ "$INSTALL_USER" == 'root' ] >/dev/null 2>&1; then
7 | HOME_DIR='/root'
8 | else
9 | HOME_DIR="/home/$INSTALL_USER"
10 | fi
11 | LNDG_DIR="$HOME_DIR/lndg"
12 |
13 | function check_path() {
14 | if [ -e $LNDG_DIR/lndg/wsgi.py ]; then
15 | echo "Using LNDg installation found at $LNDG_DIR"
16 | else
17 | echo "LNDg installation not found at $LNDG_DIR/, please provide the correct path:"
18 | read USR_DIR
19 | if [ -e $USR_DIR/lndg/wsgi.py ]; then
20 | LNDG_DIR=$USR_DIR
21 | echo "Using LNDg installation found at $LNDG_DIR"
22 | else
23 | echo "LNDg installation still not found, exiting..."
24 | exit 1
25 | fi
26 | fi
27 | }
28 |
29 | function configure_controller() {
30 | cat << EOF > /etc/systemd/system/lndg-controller.service
31 | [Unit]
32 | Description=Run Backend Controller For Lndg
33 | [Service]
34 | Environment=PYTHONUNBUFFERED=1
35 | User=$INSTALL_USER
36 | Group=$INSTALL_USER
37 | ExecStart=$LNDG_DIR/.venv/bin/python $LNDG_DIR/controller.py
38 | StandardOutput=append:/var/log/lndg-controller.log
39 | StandardError=append:/var/log/lndg-controller.log
40 | Restart=always
41 | RestartSec=60s
42 | [Install]
43 | WantedBy=multi-user.target
44 | EOF
45 | }
46 |
47 | function enable_services() {
48 | systemctl daemon-reload
49 | sleep 3
50 | systemctl start lndg-controller.service
51 | systemctl enable lndg-controller.service >/dev/null 2>&1
52 | }
53 |
54 | function report_information() {
55 | echo -e ""
56 | echo -e "================================================================================================================================"
57 | echo -e "Backend services and rebalancer setup for LNDg via systemd using user account $INSTALL_USER and a refresh interval of $REFRESH seconds."
58 | echo -e ""
59 | echo -e "Backend Controller Status: ${RED}sudo systemctl status lndg-controller.service${NC}"
60 | echo -e ""
61 | echo -e "To disable your backend services, use the following commands."
62 | echo -e "Disable Backend Controller: ${RED}sudo systemctl disable lndg-controller.service${NC}"
63 | echo -e "Stop Backend Controller: ${RED}sudo systemctl stop lndg-controller.service${NC}"
64 | echo -e ""
65 | echo -e "To re-enable these services, simply replace the disable/stop commands with enable/start."
66 | echo -e "================================================================================================================================"
67 | }
68 |
69 | ##### Main #####
70 | echo -e "Setting up, this may take a few minutes..."
71 | check_path
72 | configure_controller
73 | enable_services
74 | report_information
--------------------------------------------------------------------------------