├── .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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for channel in action_list %} 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 53 | 54 | 55 | {% endfor %} 56 |
Channel IDPeer AliasOutbound LiquidityCapacityInbound LiquidityUnsettledoRateoBaseo7Di7DiRateiBaseARAction
{{ channel.short_chan_id }}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{{ channel.local_balance|intcomma }} {{ channel.outbound_percent }}% 31 | 32 |
33 | 34 |
35 |
{{ channel.remote_balance|intcomma }} {{ channel.inbound_percent }}%{{ channel.unsettled_balance|intcomma }}{{ channel.local_fee_rate|intcomma }}{{ channel.local_base_fee|intcomma }}{{ channel.o7D|intcomma }}M {{ channel.routed_out_7day }}{{ channel.i7D|intcomma }}M {{ channel.routed_in_7day }}{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }} 45 |
46 | {% csrf_token %} 47 | 48 | 49 | 50 | 51 |
52 |
{{ channel.output }}
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 | 12 | 13 | 14 | 15 | {% for address in addresses.addresses reversed %} 16 | 17 | 18 | 19 | 20 | 21 | {% endfor %} 22 |
ChangeBalanceAddress
{{ address.is_internal }}{% if address.balance == 0 %}---{% else %}{{ address.balance|intcomma }}{% endif %}{{ address.address }}
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 |
7 |

Autopilot Logs

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for log in autopilot %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 |
TimestampChannel IDPeer AliasSettingOld ValueNew Value
{{ log.timestamp|naturaltime }}{{ log.chan_id }}{% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %}{{ log.setting }}{{ log.old_value }}{{ log.new_value }}
28 |
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 | 6 | 7 | 13 | 19 | 28 | {% if pending_sweeps %} 29 |
30 |

Pending Sweeps

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | {% for sweep in pending_sweeps %} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | 63 | {% endfor %} 64 |
OutpointAmountTypeRequested RateTarget RateConf TargetTotal AttemptsNext AttemptForceBump Fee
{{ sweep.txid_str }}{{ sweep.amount_sat|intcomma }}{% 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 %}{% if sweep.requested_sat_per_vbyte == 0 %}---{% else %}{{ sweep.requested_sat_per_vbyte|intcomma }}{% endif %}{{ sweep.sat_per_vbyte|intcomma }}{{ sweep.requested_conf_target|intcomma }}{{ sweep.broadcast_attempts|intcomma }}{{ sweep.next_broadcast_height|intcomma }}{% if sweep.force %}Yes{% else %}No{% endif %} 56 |
57 | 58 | 59 | 60 |
61 |
65 |
66 | {% endif %} 67 | {% if utxos %} 68 |
69 |

Balances

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {% for utxo in utxos %} 79 | 80 | 81 | 82 | 83 | 84 | 95 | 96 | {% endfor %} 97 |
AddressesAmountOutpointConfirmationsFee Bumps 🖩
{{ utxo.address }}{{ utxo.amount_sat|intcomma }}{{ utxo.outpoint.txid_str }}{{ utxo.confirmations|intcomma }} 85 | {% if utxo.confirmations == 0 %} 86 |
87 | 88 | 89 | 90 |
91 | {% else %} 92 | --- 93 | {% endif %} 94 |
98 |
99 | {% endif %} 100 | {% if transactions %} 101 |
102 |

Transactions

103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | {% for transaction in transactions %} 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | {% endfor %} 120 |
TX HashAmountBlock HeightFeesLabel
{{ transaction.tx_hash }}{{ transaction.amount|intcomma }}{% if transaction.block_height == 0 %}---{% else %}{{ transaction.block_height|intcomma }}{% endif %}{{ transaction.fee|intcomma }}{{ transaction.label }}
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 |
33 | {% csrf_token %} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {% for num in iterator %} 42 | 43 | 44 | 45 | 46 | 47 | 48 | {% endfor %} 49 | 50 |
IDPubkeyAmount (sats)
{{ num }}
#Remaining Onchain Balance: {{balances.total_balance|intcomma}}Total: 0
51 |
    52 | Opening Fee Rate (sats/vbyte): 53 | 54 |
55 |
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 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% for channel in channels %} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {% endfor %} 50 |
Channel7-Day Activity And Revenue APY: {{ apy_7day }}%30-Day Activity And Revenue APY: {{ apy_30day }}% Channel Health
Channel IDPeer AliasCapacityRouted Out | InRebal In | OutAPY | CVOut Profit | InRouted Out | Routed InRebal In | OutAPY | CVOut Profit | InUpdatesOpener
{{ channel.short_chan_id }}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{{ channel.mil_capacity }}M{{ channel.amt_routed_out_7day|intcomma }}M {{ channel.routed_out_7day }} | {{ channel.amt_routed_in_7day|intcomma }}M {{ channel.routed_in_7day }}{{ channel.amt_rebal_in_7day|intcomma }}M {{ channel.rebal_in_7day }} | {{ channel.amt_rebal_out_7day|intcomma }}M {{ channel.rebal_out_7day }}{{ channel.apy_7day }}% {{ channel.cv_7day }}%{{ channel.revenue_7day|intcomma }} {{ channel.profits_7day|intcomma }} | {{ channel.revenue_assist_7day|intcomma }}{{ channel.amt_routed_out_30day|intcomma }}M {{ channel.routed_out_30day }} | {{ channel.amt_routed_in_30day|intcomma }}M {{ channel.routed_in_30day }}{{ channel.amt_rebal_in_30day|intcomma }}M {{ channel.rebal_in_30day }} | {{ channel.amt_rebal_out_30day|intcomma }}M {{ channel.rebal_out_30day }}{{ channel.apy_30day }}% {{ channel.cv_30day }}%{{ channel.revenue_30day|intcomma }} {{ channel.profits_30day|intcomma }} | {{ channel.revenue_assist_30day|intcomma }}{{ channel.updates }}%{% if channel.initiator == True %}Local{% else %}Remote{% endif %}
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 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for channel in pending_closed %} 21 | 22 | 23 | 24 | {% with funding_txid=channel.channel_point|slice:":-2" %} 25 | 26 | {% endwith %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 |
Channel IDPeer AliasChannel PointCapacityLocal BalanceRemote BalanceBalance In LimboLocal Commit FeesClosing TX
{{ channel.short_chan_id }}{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{{ channel.channel_point }}{{ channel.capacity|intcomma }}{{ channel.local_balance|intcomma }}{{ channel.remote_balance|intcomma }}{{ channel.limbo_balance|intcomma }}{{ channel.local_commit_fee_sat }}{{ channel.closing_txid }}
36 |
37 | {% endif %} 38 | {% if pending_force_closed %} 39 |
40 |

Pending Force Close Channels

41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {% for channel in pending_force_closed %} 54 | 55 | 56 | 57 | {% with funding_txid=channel.channel_point|slice:":-2" %} 58 | 59 | {% endwith %} 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {% endfor %} 68 |
Channel IDPeer AliasChannel PointCapacityLocal BalanceRemote BalanceBalance In LimboMaturityClosing TX
{{ channel.short_chan_id }}{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{{ channel.channel_point }}{{ channel.capacity|intcomma }}{{ channel.local_balance|intcomma }}{{ channel.remote_balance|intcomma }}{{ channel.limbo_balance|intcomma }}{{ channel.maturity_datetime|naturaltime }}{{ channel.closing_txid }}
69 |
70 | {% endif %} 71 | {% if waiting_for_close %} 72 |
73 |

Channels Waiting To Close

74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | {% for channel in waiting_for_close %} 87 | 88 | 89 | 90 | {% with funding_txid=channel.channel_point|slice:":-2" %} 91 | 92 | {% endwith %} 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | {% endfor %} 101 |
Channel IDPeer AliasChannel PointCapacityLocal BalanceRemote BalanceBalance In LimboLocal Commit FeeClosing TX
{{ channel.short_chan_id }}{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{{ channel.channel_point }}{{ channel.capacity|intcomma }}{{ channel.local_balance|intcomma }}{{ channel.remote_balance|intcomma }}{{ channel.limbo_balance|intcomma }}{{ channel.local_commit_fee_sat }}{{ channel.closing_txid }}
102 |
103 | {% endif %} 104 | {% if closures %} 105 |
106 |

Closures

107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | {% for closure in closures %} 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 149 | 150 | {% endfor %} 151 |
Channel IDAliasCapacityClosing TXIDSettled BalanceLocked BalanceClose HeightClose TypeOpenerCloserResolutionsCosts 🗘
{% if closure.chan_id == '0' %}---{% else %}{{ closure.short_chan_id }}{% endif %}{% if closure.alias == '' %}{{ closure.remote_pubkey|slice:":12" }}{% else %}{{ closure.alias }}{% endif %}{{ closure.capacity|intcomma }}{% if closure.closing_tx == '0000000000000000000000000000000000000000000000000000000000000000' %}---{% else %}{{ closure.closing_tx }}{% endif %}{{ closure.settled_balance|intcomma }}{{ closure.time_locked_balance|intcomma }}{{ closure.close_height|intcomma }}{% 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 %}{% 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 %}{% 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 %}{% if closure.resolution_count > 0 %}Details{% else %}---{% endif %} 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 |
142 | {% csrf_token %} 143 | 144 | 145 | 146 |
147 | {% endif %} 148 |
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 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for failed_htlc in agg_failed_htlcs %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 |
Chan In IDChan Out IDChan In AliasChan Out AliasCountVolume
{{ failed_htlc.chan_id_in }}{{ failed_htlc.chan_id_out }}{% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %}{% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %}{{ failed_htlc.count|intcomma }}{{ failed_htlc.volume|intcomma }}
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 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 |
TimestampChan In IDChan In AliasForward AmountActual OutboundChan Out AliasChan Out IDPotential FeeHTLC FailureFailure Detail
53 | Load More 54 |
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 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 41 | 42 | {% for channel in channels %} 43 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 67 | 75 | 76 | 77 | 78 | 79 | 88 | 89 | {% endfor %} 90 |
Channel IDPeer AliasOutbound LiquidityCapacityInbound Liquidity7Day FlowoAdjustmentSuggested oRateiAdjustmentSuggested iRateoRateiRateMax CostPeer oRatePeer iRateUpdated 28 |
29 | {% csrf_token %} 30 | 31 | 32 | 33 |
34 |
35 | {% csrf_token %} 36 | 37 | 38 | 39 |
40 |
{{ channel.short_chan_id }}{% if channel.private == False %}{% endif %}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{% if channel.private == False %}{% endif %}{{ channel.local_balance|intcomma }} {{ channel.out_percent }}% 48 | 49 |
50 | 51 |
52 |
{{ channel.remote_balance|intcomma }} {{ channel.in_percent }}%{% if channel.net_routed_7day != 0 %}{{ channel.net_routed_7day }} {% if channel.net_routed_7day > 0 %}OUT{% else %}IN{% endif %}{% else %}0{% endif %}{{ channel.adjustment }}{{ channel.new_rate|intcomma }}{{ channel.inbound_adjustment|intcomma }}{{ channel.new_inbound_rate|intcomma }} 60 |
61 | {% csrf_token %} 62 | 63 | 64 | 65 |
66 |
68 |
69 | {% csrf_token %} 70 | 71 | 72 | 73 |
74 |
{{ channel.ar_max_cost }}%{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_inbound_fee_rate|intcomma }}{{ channel.fees_updated|naturaltime }} 80 |
81 | {% csrf_token %} 82 | 83 | 84 | 85 | 86 |
87 |
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 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 |
TimestampChannel In IdChannel In AliasAmount InAmount OutChannel Out AliasChannel Out IdFees EarnedPPM Earned
22 | Load More 23 |
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 |

Inbound Fee Log

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for log in inbound_fee_log %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 |
TimestampChannel IDPeer AliasSettingOld ValueNew Value
{{ log.timestamp|naturaltime }}{{ log.chan_id }}{% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %}{{ log.setting }}{{ log.old_value }} 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 }}
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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for invoice in invoices %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% endfor %} 33 |
CreatedSettledPayment HashValueAmount PaidStateChannel In AliasChannel InKeysend
{{ invoice.creation_date|naturaltime }}{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %}{{ invoice.r_hash }}{{ invoice.value|add:"0"|intcomma }}{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %}{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %}{% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }}{% else %}---{% endif %}{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %}
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 | 11 | 12 | 13 | 14 | 15 | 16 | {% for keysend in keysends %} 17 | 18 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 |
Revenue?Settle DateChannel In AliasAmountMessage
19 |
20 | {% csrf_token %} 21 | 22 | 23 |
24 |
{{ keysend.settle_date|naturaltime }}{% if keysend.chan_in_alias == '' %}---{% else %}{{ keysend.chan_in_alias }}{% endif %}{{ keysend.amt_paid|intcomma }}{{ keysend.message }}{% if keysend.sender != None %} | Signed By: {% if keysend.sender_alias != None %}{{ keysend.sender_alias }}{% else %}{{ keysend.sender }}{% endif %}{% endif %}
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 |
2 |
3 | {% csrf_token %} 4 |

{% if url %}{{title}}{% else %}{{title}}{% endif %} Settings

5 |
6 |
7 | {% for sett in settings %} 8 | {% if sett.form_id != 'update_channels' %} 9 |
10 | 11 | {% if sett.min == 0 and sett.max == 1 %} 12 | 16 | {% else %} 17 |
18 | 19 |
20 | {% endif %} 21 |
22 | {% endif %} 23 | {% endfor %} 24 |
25 | {% if 'update_channels' == settings.0.form_id %} 26 | 27 | 28 | {% endif %} 29 | 30 |
31 |
32 |
33 |
34 |
-------------------------------------------------------------------------------- /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 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for node in open_list %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% endfor %} 32 |
Node PubkeyNode AliasSuccessful Payments RoutedAmount RoutedFees PaidEffective PPMVolume ScoreSavings By Volume
{{ node.node_pubkey }}{% if node.alias == '' %}---{% else %}{{ node.alias }}{% endif %}{{ node.count }}{{ node.amount|add:"0"|intcomma }}{{ node.fees|add:"0"|intcomma }}{{ node.ppm|add:"0"|intcomma }}{{ node.score }}{{ node.sum_cost_to|add:"0"|intcomma }}
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 | 47 | 48 | 49 | 50 | 51 | {% for node in avoid_list %} 52 | 53 | 54 | 55 | 56 | 63 | 64 | {% endfor %} 65 |
UpdatedNode PubkeyNotesRemove
{{ node.updated|naturaltime }}{{ node.pubkey }}{% if node.notes == '' %}---{% else %}{{ node.notes }}{% endif %} 57 |
58 | {% csrf_token %} 59 | 60 | 61 |
62 |
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 |
76 | {% csrf_token %} 77 | 78 | 79 | 80 | 81 | 82 |
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 |

Outbound Fee Log

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for log in outbound_fee_log %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 |
TimestampChannel IDPeer AliasSettingOld ValueNew Value
{{ log.timestamp|naturaltime }}{{ log.chan_id }}{% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %}{{ log.setting }}{{ log.old_value }} 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 }}
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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% for payment in payments %} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 |
TimestampHashValueFee PaidPPM PaidStatusChan Out AliasChan Out IDRouteKeysend
{{ payment.creation_date|naturaltime }}{{ payment.payment_hash }}{{ payment.value|add:"0"|intcomma }}{{ payment.fee|intcomma }}{{ payment.ppm|intcomma }}{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %}{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}---{% endif %}{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}---{% endif %}{% if payment.status == 2 %}Open{% else %}---{% endif %}{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %}
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 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 |
TimestampChannel IDPeer AliasSettingOld ValueNew ValueChangeChannel Liquidity
21 | Load More 22 |
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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for peer in peers %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% endfor %} 31 |
Peer PubKeyPeer AliasNetwork AddressPing Time (ms)InboundSats SentSats ReceivedAction
{{ peer.pubkey }}{% if peer.alias != '' %}{{ peer.alias }}{% else %}---{% endif %}{{ peer.address }}{{ peer.ping_time|intcomma }}{{ peer.inbound }}{{ peer.sat_sent|intcomma }}{{ peer.sat_recv|intcomma }}
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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for htlc in outgoing_htlcs %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% endfor %} 29 |
Channel IDChannel AliasForwarding ChannelForwarding AliasAmountExpirationHash Lock
{{ htlc.chan_id }}{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}{% if htlc.forwarding_channel == '0' %}Self{% else %}{{ htlc.forwarding_channel }}{% endif %}{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}{{ htlc.amount|intcomma }}{{ htlc.hours_til_expiration }} hours{{ htlc.hash_lock }}
30 |
31 | {% endif %} 32 | {% if incoming_htlcs %} 33 |
34 |

Incoming HTLCs

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {% for htlc in incoming_htlcs %} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {% endfor %} 56 |
Channel IDChannel AliasForwarding ChannelForwarding AliasAmountExpirationHash Lock
{{ htlc.chan_id }}{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}{% if htlc.forwarding_channel == '0' %}Self{% else %}{{ htlc.forwarding_channel }}{% endif %}{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}{{ htlc.amount|intcomma }}{{ htlc.hours_til_expiration }} hours{{ htlc.hash_lock }}
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 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 |
RequestedStartStopScheduled forElapsedValueFee LimitTarget PPMFees PaidLast HopStatusHashAction
25 | Load More 26 |
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 | 10 | 11 | 12 | 13 | {% for table in tables %} 14 | 15 | 16 | 17 | 20 | 21 | {% endfor %} 22 |
TableRecordsAction
{{ table.name }}{{ table.count|intcomma }} 18 | 19 |
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 | 11 | 12 | 13 | 14 | 15 | 16 | {% for resolution in resolutions %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% endfor %} 25 |
Resolution TypeOutcomeOutpointAmountSweep TX
{% 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 %}{% 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 %}{{ resolution.outpoint_tx }}:{{ resolution.outpoint_index }}{{ resolution.amount_sat|intcomma }}{% if resolution.resolution_type != 2 %}{{ resolution.sweep_txid }}{% else %}---{% endif %}
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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% for hop in route %} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | {% endfor %} 35 |
Attempt IdStepAmountFeePPMCost To📍AliasChannel IDChannel Capacity
{{ hop.attempt_id }}{{ hop.step }}{{ hop.amt|intcomma }}{{ hop.fee|intcomma }}{{ hop.ppm|intcomma }}{{ hop.cost_to|intcomma }}{% if hop.step == 1 %}📍{% else %}🔻{% endif %} 30 | {% if hop.alias == '' %}---{% else %}{{ hop.alias }}{% endif %}{{ hop.chan_id }}{{ hop.chan_capacity|intcomma }}
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 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {% for invoice in invoices %} 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {% endfor %} 72 |
CreatedSettledPayment HashValueAmount PaidStateChannel In AliasChannel InKeysend
{{ invoice.creation_date|naturaltime }}{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %}{{ invoice.r_hash }}{{ invoice.value|add:"0"|intcomma }}{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %}{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %}{% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }}{% else %}---{% endif %}{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %}
73 |
74 | {% endif %} 75 | {% if outgoing_htlcs %} 76 |
77 |

Outgoing HTLCs

78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {% for htlc in outgoing_htlcs %} 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | {% endfor %} 99 |
Channel IDChannel AliasForwarding ChannelForwarding AliasAmountExpirationHash Lock
{{ htlc.chan_id }}{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}{% if htlc.forwarding_channel == 0 %}---{% else %}{{ htlc.forwarding_channel }}{% endif %}{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}{{ htlc.amount|intcomma }}{{ htlc.hours_til_expiration }} hours{{ htlc.hash_lock }}
100 |
101 | {% endif %} 102 | {% if incoming_htlcs %} 103 |
104 |

Incoming HTLCs

105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | {% for htlc in incoming_htlcs %} 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | {% endfor %} 126 |
Channel IDChannel AliasForwarding ChannelForwarding AliasAmountExpirationHash Lock
{{ htlc.chan_id }}{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}{% if htlc.forwarding_channel == 0 %}---{% else %}{{ htlc.forwarding_channel }}{% endif %}{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}{{ htlc.amount|intcomma }}{{ htlc.hours_til_expiration }} hours{{ htlc.hash_lock }}
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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
StatValue
Total Backups{{ stats.num_backups|intcomma }}
Pending Backups{{ stats.num_pending_backups|intcomma }}
Failed Backups{{ stats.num_failed_backups|intcomma }}
Sessions Acquired{{ stats.num_sessions_acquired|intcomma }}
Sessions Exhausted{{ stats.num_sessions_exhausted|intcomma }}
34 |
35 | {% endif %} 36 | {% if active_towers %} 37 |
38 |

Active Towers

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {% for tower in active_towers %} 47 | 48 | 49 | 50 | 51 | 58 | 59 | {% endfor %} 60 |
PubkeyAddressSessionsRemove
{{ tower.pubkey }}{{ tower.address }}{{ tower.num_sessions|intcomma }} 52 |
53 | {% csrf_token %} 54 | 55 | 56 |
57 |
61 |
62 | {% endif %} 63 | {% if inactive_towers %} 64 |
65 |

Inactive Towers

66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {% for tower in inactive_towers %} 75 | 76 | 77 | 78 | 79 | 86 | 94 | 95 | {% endfor %} 96 |
PubkeyAddressSessionsEnableDelete
{{ tower.pubkey }}{{ tower.address }}{{ tower.num_sessions|intcomma }} 80 |
81 | {% csrf_token %} 82 | 83 | 84 |
85 |
87 |
88 | {% csrf_token %} 89 | 90 | 91 | 92 |
93 |
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 |
108 | {% csrf_token %} 109 | 110 | 111 | 112 |
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 --------------------------------------------------------------------------------