From c63c065c6c98a5ffa24bbb763814aab57944d5b1 Mon Sep 17 00:00:00 2001
From: cryptosharks131 <38626122+cryptosharks131@users.noreply.github.com>
Date: Fri, 26 Aug 2022 20:56:46 -0400
Subject: [PATCH] v1.3.0 (#120)
---
.github/workflows/on-push-github.yml | 46 ++
.github/workflows/on-tag-github.yml | 47 ++
README.md | 45 +-
gui/forms.py | 12 +-
gui/migrations/0030_auto_20220722_0912.py | 70 +++
gui/migrations/0031_pendingchannels.py | 33 ++
gui/models.py | 38 +-
gui/templates/advanced.html | 6 +-
gui/templates/base.html | 2 +-
gui/templates/channel.html | 30 +-
gui/templates/closures.html | 116 ++++-
gui/templates/failed_htlcs.html | 6 +-
gui/templates/home.html | 180 ++++++--
gui/templates/income.html | 74 +++
gui/templates/invoices.html | 10 +-
gui/templates/open_list.html | 4 +-
gui/templates/payments.html | 8 +-
gui/templates/pending_htlcs.html | 10 +-
gui/templates/rebalances.html | 45 ++
gui/templates/rebalancing.html | 22 +-
gui/templates/resolutions.html | 2 +-
gui/templates/route.html | 8 +-
gui/urls.py | 6 +-
gui/views.py | 528 +++++++++++++++++++---
initialize.py | 22 +-
jobs.py | 255 +++++++----
nginx.sh | 2 +
rebalancer.py | 82 ++--
requirements.txt | 3 +-
29 files changed, 1382 insertions(+), 330 deletions(-)
create mode 100644 .github/workflows/on-push-github.yml
create mode 100644 .github/workflows/on-tag-github.yml
create mode 100644 gui/migrations/0030_auto_20220722_0912.py
create mode 100644 gui/migrations/0031_pendingchannels.py
create mode 100644 gui/templates/income.html
create mode 100644 gui/templates/rebalances.html
diff --git a/.github/workflows/on-push-github.yml b/.github/workflows/on-push-github.yml
new file mode 100644
index 0000000..88913e4
--- /dev/null
+++ b/.github/workflows/on-push-github.yml
@@ -0,0 +1,46 @@
+name: Build on push
+
+permissions:
+ packages: write
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ build:
+ name: Build image
+ runs-on: ubuntu-22.04
+
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@v3
+
+ - name: Set env variables
+ run: |
+ echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g')" >> $GITHUB_ENV
+ IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
+ echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ id: qemu
+
+ - name: Setup Docker buildx action
+ uses: docker/setup-buildx-action@v2
+ id: buildx
+
+ - name: Run Docker buildx
+ run: |
+ docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ --tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:latest \
+ --output "type=registry" ./
diff --git a/.github/workflows/on-tag-github.yml b/.github/workflows/on-tag-github.yml
new file mode 100644
index 0000000..e27e887
--- /dev/null
+++ b/.github/workflows/on-tag-github.yml
@@ -0,0 +1,47 @@
+name: Build on tag
+
+permissions:
+ packages: write
+
+on:
+ push:
+ tags:
+ - v[0-9]+.[0-9]+.[0-9]+
+ - v[0-9]+.[0-9]+.[0-9]+-*
+
+jobs:
+ build:
+ name: Build image
+ runs-on: ubuntu-22.04
+
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@v3
+
+ - name: Set env variables
+ run: |
+ echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
+ IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
+ echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v2
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v2
+ id: qemu
+
+ - name: Setup Docker buildx action
+ uses: docker/setup-buildx-action@v2
+ id: buildx
+
+ - name: Run Docker buildx
+ run: |
+ docker buildx build \
+ --platform linux/amd64,linux/arm64 \
+ --tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:$TAG \
+ --output "type=registry" ./
diff --git a/README.md b/README.md
index c119099..c797789 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
# LNDg
Lite GUI web interface to analyze lnd data and manage your node with automation.
-Start by choosing one of the following installation methods:
-[Docker Installation](https://github.com/cryptosharks131/lndg#docker-installation-requires-docker-and-docker-compose-be-installed) | [Umbrel Installation](https://github.com/cryptosharks131/lndg#umbrel-installation) | [Manual Installation](https://github.com/cryptosharks131/lndg#manual-installation)
+Start by choosing one of the following installation methods: [Docker Installation](https://github.com/cryptosharks131/lndg#docker-installation-requires-docker-and-docker-compose-be-installed) | [Manual Installation](https://github.com/cryptosharks131/lndg#manual-installation)
+
+LNDg can also be found directly on popular apps like Umbrel and Citadel with a 1-click install from the GUI.
## Docker Installation (requires docker and docker-compose be installed)
### Build and deploy
@@ -37,46 +38,6 @@ docker-compose up -d
docker system prune -f
```
-## Manual Umbrel Installation (now available directly from the Umbrel app store)
-
-### Build and deploy
-1. Log into your umbrel via ssh
-2. Clone respository `git clone https://github.com/cryptosharks131/lndg.git`
-3. Change directory `cd lndg`
-4. Copy and replace the contents of the `docker-compose.yaml` with the below: `nano docker-compose.yaml`
-```
-services:
- lndg:
- build: .
- volumes:
- - /home/umbrel/umbrel/lnd:/root/.lnd:ro
- - /home/umbrel/lndg/data:/lndg/data:rw
- command:
- - sh
- - -c
- - python initialize.py -net 'mainnet' -server '10.21.21.9:10009' -d && supervisord && python manage.py runserver 0.0.0.0:8000
- ports:
- - 8889:8000
-networks:
- default:
- external: true
- name: umbrel_main_network
-```
-5. Deploy your docker image: `docker-compose up -d`
-6. You can now access LNDg via your browser on port 8889: `http://umbrel.local:8889`
-7. Open and copy the password from output file: `nano data/lndg-admin.txt`
-8. Use the password from the output file and the username `lndg-admin` to login
-
-### Updating
-```
-docker-compose down
-docker-compose build --no-cache
-docker-compose up -d
-
-# OPTIONAL: remove unused builds and objects
-docker system prune -f
-```
-
## Manual Installation
### Step 1 - Install lndg
1. Clone respository `git clone https://github.com/cryptosharks131/lndg.git`
diff --git a/gui/forms.py b/gui/forms.py
index a22e2a5..4b18842 100644
--- a/gui/forms.py
+++ b/gui/forms.py
@@ -80,10 +80,13 @@ class AutoRebalanceForm(forms.Form):
target_time = forms.IntegerField(label='target_time', required=False)
fee_rate = forms.IntegerField(label='fee_rate', required=False)
outbound_percent = forms.FloatField(label='outbound_percent', required=False)
+ inbound_percent = forms.FloatField(label='inbound_percent', required=False)
max_cost = forms.FloatField(label='max_cost', required=False)
variance = forms.IntegerField(label='variance', required=False)
wait_period = forms.IntegerField(label='wait_period', required=False)
autopilot = forms.IntegerField(label='autopilot', required=False)
+ autopilotdays = forms.IntegerField(label='autopilotdays', required=False)
+ targetallchannels = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False)
updates_channel_codes = [
(0, 'base_fee'),
@@ -96,6 +99,7 @@ updates_channel_codes = [
(7, 'channel_state'),
(8, 'auto_fees'),
(9, 'cltv'),
+ (10, 'closing_costs'),
]
class UpdateChannel(forms.Form):
@@ -103,6 +107,12 @@ class UpdateChannel(forms.Form):
target = forms.IntegerField(label='target')
update_target = forms.ChoiceField(label='update_target', choices=updates_channel_codes)
+class UpdatePending(forms.Form):
+ funding_txid = forms.CharField(label='funding_txid', max_length=64)
+ output_index = forms.IntegerField(label='output_index')
+ target = forms.IntegerField(label='target')
+ update_target = forms.ChoiceField(label='update_target', choices=updates_channel_codes)
+
class UpdateSetting(forms.Form):
key = forms.CharField(label='setting', max_length=20)
value = forms.CharField(label='value', max_length=50)
@@ -128,4 +138,4 @@ class BatchOpenForm(forms.Form):
amt9 = forms.IntegerField(label='amt9', required=False)
pubkey10 = forms.CharField(label='pubkey10', max_length=66, required=False)
amt10 = forms.IntegerField(label='amt10', required=False)
- fee_rate = forms.IntegerField(label='fee_rate')
\ No newline at end of file
+ fee_rate = forms.IntegerField(label='fee_rate')
diff --git a/gui/migrations/0030_auto_20220722_0912.py b/gui/migrations/0030_auto_20220722_0912.py
new file mode 100644
index 0000000..4833954
--- /dev/null
+++ b/gui/migrations/0030_auto_20220722_0912.py
@@ -0,0 +1,70 @@
+# Generated by Django 3.2.7 on 2022-07-21 05:57
+
+from django.db import migrations, models
+from requests import get
+from lndg.settings import LND_NETWORK
+
+def update_close_fees(apps, schedma_editor):
+ channels = apps.get_model('gui', 'channels')
+ closures = apps.get_model('gui', 'closures')
+ resolutions = apps.get_model('gui', 'resolutions')
+ settings = apps.get_model('gui', 'localsettings')
+ def network_links():
+ if settings.objects.filter(key='GUI-NetLinks').exists():
+ network_links = str(settings.objects.filter(key='GUI-NetLinks')[0].value)
+ else:
+ network_links = 'https://mempool.space'
+ return network_links
+ def get_tx_fees(txid):
+ base_url = network_links() + ('/testnet' if LND_NETWORK == 'testnet' else '') + '/api/tx/'
+ try:
+ request_data = get(base_url + txid).json()
+ fee = request_data['fee']
+ except Exception as e:
+ print('Error getting closure fees for', txid, '-', str(e))
+ fee = 0
+ return fee
+ try:
+ for closure in closures.objects.exclude(open_initiator=2, close_type=0):
+ if channels.objects.filter(chan_id=closure.chan_id).exists():
+ channel = channels.objects.filter(chan_id=closure.chan_id)[0]
+ closing_costs = get_tx_fees(closure.closing_tx) if closure.open_initiator == 1 else 0
+ for resolution in resolutions.objects.filter(chan_id=closure.chan_id).exclude(resolution_type=2):
+ closing_costs += get_tx_fees(resolution.sweep_txid)
+ channel.closing_costs = closing_costs
+ channel.save()
+ except Exception as e:
+ print('Migration step failed:', str(e))
+
+def revert_close_fees(apps, schedma_editor):
+ pass
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0029_update_percent_vars'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='channels',
+ name='closing_costs',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='rebalancer',
+ name='fees_paid',
+ field=models.FloatField(default=None, null=True),
+ ),
+ migrations.AlterField(
+ model_name='channels',
+ name='ar_in_target',
+ field=models.IntegerField(),
+ ),
+ migrations.AlterField(
+ model_name='channels',
+ name='auto_fees',
+ field=models.BooleanField(),
+ ),
+ migrations.RunPython(update_close_fees, revert_close_fees),
+ ]
diff --git a/gui/migrations/0031_pendingchannels.py b/gui/migrations/0031_pendingchannels.py
new file mode 100644
index 0000000..415d7fb
--- /dev/null
+++ b/gui/migrations/0031_pendingchannels.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.2.7 on 2022-08-05 11:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0030_auto_20220722_0912'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PendingChannels',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('funding_txid', models.CharField(max_length=64)),
+ ('output_index', models.IntegerField()),
+ ('local_base_fee', models.IntegerField(default=None, null=True)),
+ ('local_fee_rate', models.IntegerField(default=None, null=True)),
+ ('local_cltv', models.IntegerField(default=None, null=True)),
+ ('auto_rebalance', models.BooleanField(default=None, null=True)),
+ ('ar_amt_target', models.BigIntegerField(default=None, null=True)),
+ ('ar_in_target', models.IntegerField(default=None, null=True)),
+ ('ar_out_target', models.IntegerField(default=None, null=True)),
+ ('ar_max_cost', models.IntegerField(default=None, null=True)),
+ ('auto_fees', models.BooleanField(default=None, null=True)),
+ ],
+ options={
+ 'unique_together': {('funding_txid', 'output_index')},
+ },
+ ),
+ ]
diff --git a/gui/models.py b/gui/models.py
index 89a7bc1..1011dd7 100644
--- a/gui/models.py
+++ b/gui/models.py
@@ -95,13 +95,21 @@ class Channels(models.Model):
last_update = models.DateTimeField()
auto_rebalance = models.BooleanField(default=False)
ar_amt_target = models.BigIntegerField()
- ar_in_target = models.IntegerField(default=100)
+ ar_in_target = models.IntegerField()
ar_out_target = models.IntegerField()
ar_max_cost = models.IntegerField()
fees_updated = models.DateTimeField(default=timezone.now)
- auto_fees = models.BooleanField(default=False)
+ auto_fees = models.BooleanField()
+ closing_costs = models.IntegerField(default=0)
def save(self, *args, **kwargs):
+ if self.auto_fees is None:
+ if LocalSettings.objects.filter(key='AF-Enabled').exists():
+ enabled = int(LocalSettings.objects.filter(key='AF-Enabled')[0].value)
+ else:
+ LocalSettings(key='AF-Enabled', value='0').save()
+ enabled = 0
+ self.auto_fees = False if enabled == 0 else True
if not self.ar_out_target:
if LocalSettings.objects.filter(key='AR-Outbound%').exists():
outbound_setting = int(LocalSettings.objects.filter(key='AR-Outbound%')[0].value)
@@ -109,6 +117,13 @@ class Channels(models.Model):
LocalSettings(key='AR-Outbound%', value='75').save()
outbound_setting = 75
self.ar_out_target = outbound_setting
+ if not self.ar_in_target:
+ if LocalSettings.objects.filter(key='AR-Inbound%').exists():
+ inbound_setting = int(LocalSettings.objects.filter(key='AR-Inbound%')[0].value)
+ else:
+ LocalSettings(key='AR-Inbound%', value='100').save()
+ inbound_setting = 100
+ self.ar_in_target = inbound_setting
if not self.ar_amt_target:
if LocalSettings.objects.filter(key='AR-Target%').exists():
amt_setting = float(LocalSettings.objects.filter(key='AR-Target%')[0].value)
@@ -153,6 +168,7 @@ class Rebalancer(models.Model):
status = models.IntegerField(default=0)
payment_hash = models.CharField(max_length=64, null=True, default=None)
manual = models.BooleanField(default=False)
+ fees_paid = models.FloatField(null=True, default=None)
class Meta:
app_label = 'gui'
@@ -247,4 +263,20 @@ class Autofees(models.Model):
old_value = models.IntegerField()
new_value = models.IntegerField()
class Meta:
- app_label = 'gui'
\ No newline at end of file
+ app_label = 'gui'
+
+class PendingChannels(models.Model):
+ funding_txid = models.CharField(max_length=64)
+ output_index = models.IntegerField()
+ local_base_fee = models.IntegerField(null=True, default=None)
+ local_fee_rate = models.IntegerField(null=True, default=None)
+ local_cltv = models.IntegerField(null=True, default=None)
+ auto_rebalance = models.BooleanField(null=True, default=None)
+ ar_amt_target = models.BigIntegerField(null=True, default=None)
+ ar_in_target = models.IntegerField(null=True, default=None)
+ ar_out_target = models.IntegerField(null=True, default=None)
+ ar_max_cost = models.IntegerField(null=True, default=None)
+ auto_fees = models.BooleanField(null=True, default=None)
+ class Meta:
+ app_label = 'gui'
+ unique_together = (('funding_txid', 'output_index'),)
\ No newline at end of file
diff --git a/gui/templates/advanced.html b/gui/templates/advanced.html
index ab4ea99..7b9eabe 100644
--- a/gui/templates/advanced.html
+++ b/gui/templates/advanced.html
@@ -204,11 +204,11 @@
{% csrf_token %}
{% if settings.key == 'AR-Target%' %}
- {% elif settings.key|slice:"-1:" == '%' or settings.key == 'AR-Variance' or settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AR-WaitPeriod'%}
+ {% elif settings.key|slice:"-1:" == '%' or settings.key == 'AR-Variance' or settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AR-WaitPeriod' or settings.key == 'AR-APDays' %}
{% elif settings.key == 'AR-Time' %}
- {% elif settings.key == 'AR-MaxFeeRate' or settings.key == 'AF-MaxRate' %}
+ {% elif settings.key == 'AR-MaxFeeRate' %}
{% elif settings.key == 'AF-MaxRate' or settings.key == 'AF-MinRate' %}
@@ -227,4 +227,4 @@
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/base.html b/gui/templates/base.html
index d9944eb..7e64a45 100644
--- a/gui/templates/base.html
+++ b/gui/templates/base.html
@@ -28,7 +28,7 @@
diff --git a/gui/templates/channel.html b/gui/templates/channel.html
index 6485b3b..6260120 100644
--- a/gui/templates/channel.html
+++ b/gui/templates/channel.html
@@ -217,9 +217,9 @@
{{ forward.amt_out|intcomma }}
{% if forward.chan_in_alias == '' %}---{% else %}{{ forward.chan_in_alias }}{% endif %}
{% if forward.chan_out_alias == '' %}---{% else %}{{ forward.chan_out_alias }}{% endif %}
- {{ forward.chan_id_in }}
- {{ forward.chan_id_out }}
- {{ forward.fee }}
+ {{ forward.chan_id_in }}
+ {{ forward.chan_id_out }}
+ {{ forward.fee|intcomma }}
{{ forward.ppm|intcomma }}
{% endfor %}
@@ -235,21 +235,25 @@
Start
Stop
Scheduled Duration
+ Actual Duration
Value
Fee Limit
Target PPM
+ Fees Paid
Last Hop Alias
Status
{% for rebalance in rebalances %}
{{ rebalance.requested|naturaltime }}
- N/A{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}
- 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>N/A{% endif %}
+ ---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}
+ 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}
{{ rebalance.duration }} minutes
+ {% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}
{{ rebalance.value|intcomma }}
{{ rebalance.fee_limit|intcomma }}
{{ rebalance.ppm|intcomma }}
+ {% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}
{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}
{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful {% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}
@@ -281,9 +285,9 @@
{{ 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 %}N/A{% endif %}
- {% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %}
- {% if payment.status == 2 %}Open {% else %}N/A{% 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 %}
{% endfor %}
@@ -308,13 +312,13 @@
{% for invoice in invoices %}
{{ invoice.creation_date|naturaltime }}
- {% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %}
+ {% 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 %}N/A{% endif %}
+ {% 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 %}N/A{% endif %}
- {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }} {% else %}N/A{% 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 %}
{% endfor %}
@@ -362,4 +366,4 @@
No data found for this channel!
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/closures.html b/gui/templates/closures.html
index 402d83b..dce4c67 100644
--- a/gui/templates/closures.html
+++ b/gui/templates/closures.html
@@ -2,6 +2,105 @@
{% block title %} {{ block.super }} - Closures{% endblock %}
{% block content %}
{% load humanize %}
+{% if pending_closed %}
+
+
Pending Close Channels
+
+
+{% endif %}
+{% if pending_force_closed %}
+
+
Pending Force Close Channels
+
+
+{% endif %}
+{% if waiting_for_close %}
+
+
Channels Waiting To Close
+
+
+{% endif %}
{% if closures %}
Closures
@@ -18,6 +117,7 @@
Opener
Closer
Resolutions
+
Costs
{% for closure in closures %}
@@ -32,6 +132,20 @@
{% 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 %}
+
+ {% if closure.closing_costs == '' %}
+ ---
+ {% elif closure.open_initiator == 2 and closure.close_type == 0 %}
+ ---
+ {% else %}
+
+ {% endif %}
+
{% endfor %}
@@ -42,4 +156,4 @@
No channel closures found!
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/failed_htlcs.html b/gui/templates/failed_htlcs.html
index 4c11bea..8288ab3 100644
--- a/gui/templates/failed_htlcs.html
+++ b/gui/templates/failed_htlcs.html
@@ -21,8 +21,8 @@
{% for failed_htlc in failed_htlcs %}
{{ failed_htlc.timestamp|naturaltime }}
- {{ failed_htlc.chan_id_in }}
- {{ failed_htlc.chan_id_out }}
+ {{ 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.amount|intcomma }}
@@ -40,4 +40,4 @@
You dont have any failed HTLCs yet.
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/home.html b/gui/templates/home.html
index 6381c21..0c0ebcd 100644
--- a/gui/templates/home.html
+++ b/gui/templates/home.html
@@ -4,30 +4,28 @@
{% load humanize %}
-
Public Capacity: {{ total_capacity|intcomma }} | Active Channels: {{ node_info.num_active_channels }} / {{ total_channels }} | Peers : {{ node_info.num_peers }} | DB Size: {% if db_size > 0 %}{{ db_size }} GB{% else %}---{% endif %}
+
Public Capacity: {{ total_capacity|intcomma }} | Active Channels: {{ node_info.num_active_channels }} / {{ total_channels }} | Peers : {{ node_info.num_peers }} | DB Size: {% if db_size > 0 %}{{ db_size }} GB{% else %}---{% endif %} | Total States Updates: {% if num_updates > 0 %}{{ num_updates|intcomma }} {% else %}---{% endif %}
{% if total_private > 0 %}
Private Capacity: {{ private_capacity|intcomma }} | Locked Liquidity: {{ private_outbound|intcomma }} | Active Private Channels: {{ active_private }} / {{ total_private }} {% endif %}
Public Address: {% for info in node_info.uris %}{{ info }} | {% endfor %}
Lnd sync: {{ node_info.synced_to_graph }} | chain sync: {{ node_info.synced_to_chain }} | {% for info in node_info.chains %}{{ info }}{% endfor %} | {{ node_info.block_height }} | {{ node_info.block_hash }}
-
Wallet Balance: {{ balances.total_balance|intcomma }} | Confirmed Wallet Balance: {{ balances.confirmed_balance|intcomma }} | Unconfirmed Wallet Balance: {{ balances.unconfirmed_balance|intcomma }} | Details
+
Total Balance: {{ total_balance|intcomma }} | Onchain Balance: {{ balances.total_balance|intcomma }} | Confirmed Balance: {{ balances.confirmed_balance|intcomma }} | Unconfirmed Balance: {{ balances.unconfirmed_balance|intcomma }} | Details
-
Completed Payments: {{ total_payments }} | Sats Sent: {{ total_sent|intcomma }} | Fees Paid: {{ fees_paid|intcomma }} [{{ payments_ppm|intcomma }}]
- Paid Invoices: {{ total_invoices }} | Sats Received: {{ total_received|intcomma }}
- Lifetime Routed: {{ total_forwards|intcomma }} | Value: {{ total_value_forwards|intcomma }} | Fees Earned: {{ earned|intcomma }} [{{ routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs|intcomma }} | Percent Cost: {{ percent_cost }}%
- 7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomma }} | Fees Earned: {{ earned_7day|intcomma }} [{{ 7day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_7day|intcomma }} | Offchain Fees: {{ total_7day_fees|intcomma }} [{{ 7day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_7day }}% | Profit/Outbound: {{ profit_per_outbound|intcomma }} [{{ profit_per_outbound_real|intcomma }}] | Outbound Utilization: {{ routed_7day_percent }}%
+ 1-Day Routed: {{ routed_1day|intcomma }} | Value: {{ routed_1day_amt|intcomma }} | Fees Earned: {{ earned_1day|intcomma }} [{{ 1day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_1day|intcomma }} | Offchain Fees: {{ total_1day_fees|intcomma }} [{{ 1day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_1day }}% | Profit/Outbound: {{ profit_per_outbound_1d|intcomma }} [{{ profit_per_outbound_real_1d|intcomma }}] | Outbound Utilization: {{ routed_1day_percent }}%
+ 7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomma }} | Fees Earned: {{ earned_7day|intcomma }} [{{ 7day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_7day|intcomma }} | Offchain Fees: {{ total_7day_fees|intcomma }} [{{ 7day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_7day }}% | Profit/Outbound: {{ profit_per_outbound_7d|intcomma }} [{{ profit_per_outbound_real_7d|intcomma }}] | Outbound Utilization: {{ routed_7day_percent }}%
Total Inbound Liquidity: {{ sum_inbound|intcomma }} | Outbound Liquidity: {{ sum_outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%
Active Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }}
Inactive Inbound Liquidity: {{ inactive_inbound|intcomma }} | Outbound Liquidity: {{ inactive_outbound|intcomma }} | Unsettled Liquidity: {{ inactive_unsettled|intcomma }}
Balance In Limbo: {{ limbo_balance|intcomma }} | Total Unsettled Liquidity: {{ total_unsettled|intcomma }} | Pending HTLCs : {{ pending_htlc_count }}
-
+
{% if active_channels %}
@@ -44,10 +42,10 @@
Unsettled
oRate
oBase
+ o1D
+ i1D
o7D
i7D
- oLife
- iLife
iRate
iBase
iTarget%
@@ -64,10 +62,10 @@
{{ channel.unsettled_balance|intcomma }} ({{ channel.htlc_count }})
{{ channel.local_fee_rate|intcomma }}
{{ channel.local_base_fee|intcomma }}
+ {{ channel.amt_routed_out_1day|intcomma }} M ({{ channel.routed_out_1day }})
+ {{ channel.amt_routed_in_1day|intcomma }} M ({{ channel.routed_in_1day }})
{{ channel.amt_routed_out_7day|intcomma }} M ({{ channel.routed_out_7day }})
{{ channel.amt_routed_in_7day|intcomma }} M ({{ channel.routed_in_7day }})
- {{ channel.amt_routed_out|intcomma }} M ({{ channel.routed_out }})
- {{ channel.amt_routed_in|intcomma }} M ({{ channel.routed_in }})
{{ channel.remote_fee_rate|intcomma }}
{{ channel.remote_base_fee|intcomma }}
@@ -216,18 +214,106 @@
Capacity
Local Balance
Remote Balance
- Commit Fee
+ AF
+ Fee Rate
+ Base Fee
+ CLTV
+ Amt
+ Max Cost%
+ oTarget%
+ iTarget%
+ AR
{% for channel in pending_open %}
{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}
- {% with funding_txid=channel.channel_point|slice:":-2" %}
- {{ channel.channel_point }}
- {% endwith %}
- {{ channel.capacity|intcomma }}
+ {{ channel.channel_point }}
+ {{ channel.capacity|intcomma }}
{{ channel.local_balance|intcomma }}
{{ channel.remote_balance|intcomma }}
- {{ channel.commit_fee }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{% endfor %}
@@ -240,25 +326,23 @@
Channel ID
Peer Alias
- Channel Point
Capacity
Local Balance
Remote Balance
Balance In Limbo
Local Commit Fee
+ Closing TX
{% for channel in pending_closed %}
- {{ channel.chan_id }}
+ {{ channel.chan_id }}
{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}
- {% with funding_txid=channel.channel_point|slice:":-2" %}
- {{ channel.channel_point }}
- {% endwith %}
{{ channel.capacity|intcomma }}
{{ channel.local_balance|intcomma }}
{{ channel.remote_balance|intcomma }}
{{ channel.limbo_balance|intcomma }}
- {{ channel.commitments.local_commit_fee_sat }}
+ {{ channel.local_commit_fee_sat|intcomma }}
+ {{ channel.closing_txid }}
{% endfor %}
@@ -271,7 +355,6 @@
Channel ID
Peer Alias
- Channel Point
Capacity
Local Balance
Remote Balance
@@ -281,11 +364,8 @@
{% for channel in pending_force_closed %}
- {{ channel.chan_id }}
+ {{ channel.chan_id }}
{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}
- {% with funding_txid=channel.channel_point|slice:":-2" %}
- {{ channel.channel_point }}
- {% endwith %}
{{ channel.capacity|intcomma }}
{{ channel.local_balance|intcomma }}
{{ channel.remote_balance|intcomma }}
@@ -304,25 +384,23 @@
Channel ID
Peer Alias
- Channel Point
Capacity
Local Balance
Remote Balance
Balance In Limbo
Local Commit Fee
+ Closing TX
{% for channel in waiting_for_close %}
- {{ channel.chan_id }}
+ {{ channel.chan_id }}
{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}
- {% with funding_txid=channel.channel_point|slice:":-2" %}
- {{ channel.channel_point }}
- {% endwith %}
{{ channel.capacity|intcomma }}
{{ channel.local_balance|intcomma }}
{{ channel.remote_balance|intcomma }}
{{ channel.limbo_balance|intcomma }}
- {{ channel.commitments.local_commit_fee_sat }}
+ {{ channel.local_commit_fee_sat|intcomma }}
+ {{ channel.closing_txid }}
{% endfor %}
@@ -352,7 +430,7 @@
{% if forward.chan_out_alias == '' %}---{% else %}{{ forward.chan_out_alias }}{% endif %}
{{ forward.chan_id_in }}
{{ forward.chan_id_out }}
- {{ forward.fee }}
+ {{ forward.fee|intcomma }}
{{ forward.ppm|intcomma }}
{% endfor %}
@@ -361,28 +439,32 @@
{% endif %}
{% if rebalances %}
-
Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)
+
Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing via {{ available_count }} outbound channels)
Requested
Start
Stop
Scheduled Duration
+ Actual Duration
Value
Fee Limit
Target PPM
+ Fees Paid
Last Hop Alias
Status
{% for rebalance in rebalances %}
{{ rebalance.requested|naturaltime }}
- {% if rebalance.status == 0 %}N/A{% else %}{{ rebalance.start|naturaltime }}{% endif %}
- {% if rebalance.status > 1 %}{{ rebalance.stop|naturaltime }}{% else %}N/A{% endif %}
+ {% if rebalance.status == 0 %}---{% else %}{{ rebalance.start|naturaltime }}{% endif %}
+ {% if rebalance.status > 1 %}{{ rebalance.stop|naturaltime }}{% else %}---{% endif %}
{{ rebalance.duration }} minutes
+ {% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}
{{ rebalance.value|intcomma }}
{{ rebalance.fee_limit|intcomma }}
{{ rebalance.ppm|intcomma }}
+ {% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma}}{% else %}---{% endif %}
{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}
{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful {% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}
@@ -414,9 +496,9 @@
{{ 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 %}N/A{% endif %}
- {% if payment.status == 2 %}{% if payment.chan_out != 'MPP' %}{{ payment.chan_out }} {% else %}{{ payment.chan_out }}{% endif %}{% else %}N/A{% endif %}
- {% if payment.status == 2 %}Open {% else %}N/A{% endif %}
+ {% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}---{% endif %}
+ {% if payment.status == 2 %}{% if payment.chan_out != 'MPP' %}{{ payment.chan_out }} {% else %}{{ payment.chan_out }}{% endif %}{% else %}---{% endif %}
+ {% if payment.status == 2 %}Open {% else %}---{% endif %}
{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %}
{% endfor %}
@@ -441,13 +523,13 @@
{% for invoice in invoices %}
{{ invoice.creation_date|naturaltime }}
- {% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %}
+ {% 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 %}N/A{% endif %}
+ {% 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 %}N/A{% endif %}
- {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }} {% else %}N/A{% 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 %}
{% endfor %}
@@ -517,6 +599,8 @@
Target Outbound Above (%):
+ Target Inbound Above (%):
+
Global Max Fee Rate (ppm):
Max Cost (%):
@@ -527,6 +611,10 @@
Autopilot:
+ AutopilotDays:
+
+ Target All Channels:?
+
diff --git a/gui/templates/income.html b/gui/templates/income.html
new file mode 100644
index 0000000..109dcc9
--- /dev/null
+++ b/gui/templates/income.html
@@ -0,0 +1,74 @@
+{% extends "base.html" %}
+{% block title %} {{ block.super }} - P&L{% endblock %}
+{% block content %}
+{% load humanize %}
+
+
P&L Statement For {% if node_info.alias != "" %}{{ node_info.alias }} ({{ node_info.identity_pubkey }}){% else %}{{ node_info.identity_pubkey }}{% endif %}
+
+
+ Line Item
+ 1 Day
+ 7 Day
+ 30 Day
+ 90 Day
+ Lifetime
+
+
+ Payments Routed
+ {{ forward_count_1day|intcomma }}
+ {{ forward_count_7day|intcomma }}
+ {{ forward_count_30day|intcomma }}
+ {{ forward_count_90day|intcomma }}
+ {{ forward_count|intcomma }}
+
+
+ Value Routed
+ {{ forward_amount_1day|intcomma }}
+ {{ forward_amount_7day|intcomma }}
+ {{ forward_amount_30day|intcomma }}
+ {{ forward_amount_90day|intcomma }}
+ {{ forward_amount|intcomma }}
+
+
+ Revenue Earned
+ {{ total_revenue_1day|intcomma }} [{{ total_revenue_ppm_1day|intcomma }}]
+ {{ total_revenue_7day|intcomma }} [{{ total_revenue_ppm_7day|intcomma }}]
+ {{ total_revenue_30day|intcomma }} [{{ total_revenue_ppm_30day|intcomma }}]
+ {{ total_revenue_90day|intcomma }} [{{ total_revenue_ppm_90day|intcomma }}]
+ {{ total_revenue|intcomma }} [{{ total_revenue_ppm|intcomma }}]
+
+
+ Onchain Costs
+ {{ onchain_costs_1day|intcomma }}
+ {{ onchain_costs_7day|intcomma }}
+ {{ onchain_costs_30day|intcomma }}
+ {{ onchain_costs_90day|intcomma }}
+ {{ onchain_costs|intcomma }}
+
+
+ Offchain Costs
+ {{ total_fees_1day|intcomma }} [{{ total_fees_ppm_1day }}]
+ {{ total_fees_7day|intcomma }} [{{ total_fees_ppm_7day }}]
+ {{ total_fees_30day|intcomma }} [{{ total_fees_ppm_30day }}]
+ {{ total_fees_90day|intcomma }} [{{ total_fees_ppm_90day }}]
+ {{ total_fees|intcomma }} [{{ total_fees_ppm }}]
+
+
+ Percent Cost
+ {{ percent_cost_1day|intcomma }}%
+ {{ percent_cost_7day|intcomma }}%
+ {{ percent_cost_30day|intcomma }}%
+ {{ percent_cost_90day|intcomma }}%
+ {{ percent_cost|intcomma }}%
+
+
+ Profits
+ {{ profits_1day|intcomma }} [{{ profits_ppm_1day }}]
+ {{ profits_7day|intcomma }} [{{ profits_ppm_7day }}]
+ {{ profits_30day|intcomma }} [{{ profits_ppm_30day }}]
+ {{ profits_90day|intcomma }} [{{ profits_ppm_90day }}]
+ {{ profits|intcomma }} [{{ profits_ppm }}]
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/gui/templates/invoices.html b/gui/templates/invoices.html
index 0a306ef..fa799aa 100644
--- a/gui/templates/invoices.html
+++ b/gui/templates/invoices.html
@@ -20,13 +20,13 @@
{% for invoice in invoices %}
{{ invoice.creation_date|naturaltime }}
- {% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %}
+ {% 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 %}N/A{% endif %}
+ {% 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 %}N/A{% endif %}
- {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }} {% else %}N/A{% 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 %}
{% endfor %}
@@ -38,4 +38,4 @@
You dont have any invoices yet.
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/open_list.html b/gui/templates/open_list.html
index 0f24d3e..e023d32 100644
--- a/gui/templates/open_list.html
+++ b/gui/templates/open_list.html
@@ -21,7 +21,7 @@
{{ node.node_pubkey }}
{% if node.alias == '' %}---{% else %}{{ node.alias }}{% endif %}
- {{ node.count }}
+ {{ node.count }}
{{ node.amount|add:"0"|intcomma }}
{{ node.fees|add:"0"|intcomma }}
{{ node.ppm|add:"0"|intcomma }}
@@ -38,4 +38,4 @@
No potential peers can be calculated yet, try waiting until you have some payment data.
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/payments.html b/gui/templates/payments.html
index ed6d4bc..bf7ad3d 100644
--- a/gui/templates/payments.html
+++ b/gui/templates/payments.html
@@ -26,9 +26,9 @@
{{ 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 %}N/A{% endif %}
- {% if payment.status == 2 %}{{ payment.chan_out }} {% else %}N/A{% endif %}
- {% if payment.status == 2 %}Open {% else %}N/A{% 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 %}
{% endfor %}
@@ -40,4 +40,4 @@
You dont have any payments yet.
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/pending_htlcs.html b/gui/templates/pending_htlcs.html
index 85fe26c..babce0e 100644
--- a/gui/templates/pending_htlcs.html
+++ b/gui/templates/pending_htlcs.html
@@ -17,9 +17,9 @@
{% for htlc in outgoing_htlcs %}
- {{ htlc.chan_id }}
+ {{ htlc.chan_id }}
{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}
- {{ htlc.forwarding_channel }}
+ {% 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
@@ -44,9 +44,9 @@
{% for htlc in incoming_htlcs %}
- {{ htlc.chan_id }}
+ {{ htlc.chan_id }}
{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}
- {{ htlc.forwarding_channel }}
+ {% 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
@@ -61,4 +61,4 @@
No pending HTLCs were found!
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/rebalances.html b/gui/templates/rebalances.html
new file mode 100644
index 0000000..2e23f7e
--- /dev/null
+++ b/gui/templates/rebalances.html
@@ -0,0 +1,45 @@
+{% extends "base.html" %}
+{% block title %} {{ block.super }} - Rebalances{% endblock %}
+{% block content %}
+{% load humanize %}
+{% if rebalances %}
+
+
Last 150 Rebalances
+
+
+ Requested
+ Start
+ Stop
+ Scheduled Duration
+ Actual Duration
+ Value
+ Fee Limit
+ Target PPM
+ Fees Paid
+ Last Hop Alias
+ Status
+
+ {% for rebalance in rebalances %}
+
+ {{ rebalance.requested|naturaltime }}
+ ---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}
+ 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}
+ {{ rebalance.duration }} minutes
+ {% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}
+ {{ rebalance.value|intcomma }}
+ {{ rebalance.fee_limit|intcomma }}
+ {{ rebalance.ppm|intcomma }}
+ {% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}
+ {% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}
+ {% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful {% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}
+
+ {% endfor %}
+
+
+{% endif %}
+{% if not rebalances %}
+
+
You dont have any rebalances yet.
+
+{% endif %}
+{% endblock %}
diff --git a/gui/templates/rebalancing.html b/gui/templates/rebalancing.html
index 63927b6..284d605 100644
--- a/gui/templates/rebalancing.html
+++ b/gui/templates/rebalancing.html
@@ -4,7 +4,7 @@
{% load humanize %}
{% if channels %}
-
Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)
+
Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing via {{ available_count }} outbound channels)
@@ -21,7 +21,7 @@
Target Amt
Max Cost %
oTarget%
- iTarget%
+ iTarget%
AR
7-Day Rate
Active
@@ -94,28 +94,32 @@
{% endif %}
{% if rebalancer %}
-
Last 20 Rebalance Requests
+
diff --git a/gui/templates/route.html b/gui/templates/route.html
index e584834..ddefd13 100644
--- a/gui/templates/route.html
+++ b/gui/templates/route.html
@@ -4,7 +4,7 @@
{% load humanize %}
{% if route %}
-
Route For Payment: {{ payment_hash }}
+
Route For : {{ payment_hash }}
Step
@@ -12,6 +12,7 @@
Fee
PPM
Cost To
+ 📍
Alias
Channel ID
Channel Capacity
@@ -23,8 +24,9 @@
{{ hop.fee|intcomma }}
{{ hop.ppm|intcomma }}
{{ hop.cost_to|intcomma }}
+ {% if hop.step == 1 %}📍{% else %}🔻{% endif %}
{% if hop.alias == '' %}---{% else %}{{ hop.alias }}{% endif %}
- {{ hop.chan_id }}
+ {{ hop.chan_id }}
{{ hop.chan_capacity|intcomma }}
{% endfor %}
@@ -36,4 +38,4 @@
A route was not found for this payment hash!
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/urls.py b/gui/urls.py
index 878228a..8666d5d 100644
--- a/gui/urls.py
+++ b/gui/urls.py
@@ -21,6 +21,7 @@ router.register(r'failedhtlcs', views.FailedHTLCViewSet)
urlpatterns = [
path('', views.home, name='home'),
path('route', views.route, name='route'),
+ path('routes', views.routes, name='routes'),
path('peers', views.peers, name='peers'),
path('balances', views.balances, name='balances'),
path('closures', views.closures, name='closures'),
@@ -34,6 +35,8 @@ urlpatterns = [
path('payments', views.payments, name='payments'),
path('invoices', views.invoices, name='invoices'),
path('forwards', views.forwards, name='forwards'),
+ path('income', views.income, name='income'),
+ path('rebalances', views.rebalances, name='rebalances'),
path('rebalancing', views.rebalancing, name='rebalancing'),
path('openchannel/', views.open_channel_form, name='open-channel-form'),
path('closechannel/', views.close_channel_form, name='close-channel-form'),
@@ -47,6 +50,7 @@ urlpatterns = [
path('updatechanpolicy/', views.update_chan_policy, name='updatechanpolicy'),
path('autorebalance/', views.auto_rebalance, name='auto-rebalance'),
path('update_channel/', views.update_channel, name='update-channel'),
+ path('update_pending/', views.update_pending, name='update-pending'),
path('update_setting/', views.update_setting, name='update-setting'),
path('opens/', views.opens, name='opens'),
path('actions/', views.actions, name='actions'),
@@ -68,4 +72,4 @@ urlpatterns = [
path('api/balances/', views.api_balances, name='api-balances'),
path('api/pendingchannels/', views.pending_channels, name='pending-channels'),
path('lndg-admin/', admin.site.urls),
-]
\ No newline at end of file
+]
diff --git a/gui/views.py b/gui/views.py
index e80d1a8..559c504 100644
--- a/gui/views.py
+++ b/gui/views.py
@@ -1,6 +1,6 @@
from django.contrib import messages
from django.shortcuts import get_object_or_404, render, redirect
-from django.db.models import Sum, IntegerField, FloatField, Count, F, Q
+from django.db.models import Sum, IntegerField, Count, F, Q
from django.db.models.functions import Round
from django.contrib.auth.decorators import login_required
from django.conf import settings
@@ -8,8 +8,8 @@ from datetime import datetime, timedelta
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import api_view
-from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm
-from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees
+from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending
+from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees, PendingChannels
from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
@@ -53,38 +53,75 @@ def home(request):
pending_closed = None
pending_force_closed = None
waiting_for_close = None
+ pending_open_balance = 0
if pending_channels.pending_open_channels:
target_resp = pending_channels.pending_open_channels
peers = Peers.objects.all()
- pending_open = [{'alias':peers.filter(pubkey=target_resp[i].channel.remote_node_pub)[0].alias if peers.filter(pubkey=target_resp[i].channel.remote_node_pub).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'commit_fee':target_resp[i].commit_fee,'commit_weight':target_resp[i].commit_weight,'fee_per_kw':target_resp[i].fee_per_kw} for i in range(0,len(target_resp))]
+ pending_changes = PendingChannels.objects.all()
+ pending_open = []
+ inbound_setting = int(LocalSettings.objects.filter(key='AR-Inbound%')[0].value) if LocalSettings.objects.filter(key='AR-Inbound%').exists() else 100
+ outbound_setting = int(LocalSettings.objects.filter(key='AR-Outbound%')[0].value) if LocalSettings.objects.filter(key='AR-Outbound%').exists() else 75
+ amt_setting = float(LocalSettings.objects.filter(key='AR-Target%')[0].value) if LocalSettings.objects.filter(key='AR-Target%').exists() else 5
+ cost_setting = int(LocalSettings.objects.filter(key='AR-MaxCost%')[0].value) if LocalSettings.objects.filter(key='AR-MaxCost%').exists() else 65
+ auto_fees = int(LocalSettings.objects.filter(key='AF-Enabled')[0].value) if LocalSettings.objects.filter(key='AF-Enabled').exists() else 0
+ for i in range(0,len(target_resp)):
+ item = {}
+ pending_open_balance += target_resp[i].channel.local_balance
+ funding_txid = target_resp[i].channel.channel_point.split(':')[0]
+ output_index = target_resp[i].channel.channel_point.split(':')[1]
+ updated = pending_changes.filter(funding_txid=funding_txid,output_index=output_index).exists()
+ item['alias'] = peers.filter(pubkey=target_resp[i].channel.remote_node_pub)[0].alias if peers.filter(pubkey=target_resp[i].channel.remote_node_pub).exists() else None
+ item['remote_node_pub'] = target_resp[i].channel.remote_node_pub
+ item['channel_point'] = target_resp[i].channel.channel_point
+ item['funding_txid'] = funding_txid
+ item['output_index'] = output_index
+ item['capacity'] = target_resp[i].channel.capacity
+ item['local_balance'] = target_resp[i].channel.local_balance
+ item['remote_balance'] = target_resp[i].channel.remote_balance
+ item['local_chan_reserve_sat'] = target_resp[i].channel.local_chan_reserve_sat
+ item['remote_chan_reserve_sat'] = target_resp[i].channel.remote_chan_reserve_sat
+ item['initiator'] = target_resp[i].channel.initiator
+ item['commitment_type'] = target_resp[i].channel.commitment_type
+ item['commit_fee'] = target_resp[i].commit_fee
+ item['commit_weight'] = target_resp[i].commit_weight
+ item['fee_per_kw'] = target_resp[i].fee_per_kw
+ item['local_base_fee'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].local_base_fee if updated else ''
+ item['local_fee_rate'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].local_fee_rate if updated else ''
+ item['local_cltv'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].local_cltv if updated else ''
+ item['auto_rebalance'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_rebalance if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_rebalance != None else False
+ item['ar_amt_target'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_amt_target if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_amt_target != None else int((amt_setting/100) * target_resp[i].channel.capacity)
+ item['ar_in_target'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_in_target if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_in_target != None else inbound_setting
+ item['ar_out_target'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_out_target if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_out_target != None else outbound_setting
+ item['ar_max_cost'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_max_cost if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_max_cost != None else cost_setting
+ item['auto_fees'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_fees if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_fees != None else (False if auto_fees == 0 else True)
+ pending_open.append(item)
if pending_channels.pending_closing_channels:
target_resp = pending_channels.pending_closing_channels
- pending_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'limbo_balance':target_resp[i].limbo_balance} for i in range(0,len(target_resp))]
+ pending_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
+ 'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
+ 'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,
+ 'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat,'limbo_balance':target_resp[i].limbo_balance,
+ 'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
if pending_channels.pending_force_closing_channels:
target_resp = pending_channels.pending_force_closing_channels
- pending_force_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'closing_txid':target_resp[i].closing_txid,'limbo_balance':target_resp[i].limbo_balance,'maturity_height':target_resp[i].maturity_height,'blocks_til_maturity':target_resp[i].blocks_til_maturity,'maturity_datetime':(datetime.now()+timedelta(minutes=(10*target_resp[i].blocks_til_maturity)))} for i in range(0,len(target_resp))]
+ pending_force_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
+ 'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
+ 'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'initiator':target_resp[i].channel.initiator,
+ 'commitment_type':target_resp[i].channel.commitment_type,'closing_txid':target_resp[i].closing_txid,'limbo_balance':target_resp[i].limbo_balance,'maturity_height':target_resp[i].maturity_height,'blocks_til_maturity':target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else find_next_block_maturity(target_resp[i]),
+ 'maturity_datetime':(datetime.now()+timedelta(minutes=(10*target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else 10*find_next_block_maturity(target_resp[i]) )))} for i in range(0,len(target_resp))]
if pending_channels.waiting_close_channels:
target_resp = pending_channels.waiting_close_channels
- waiting_for_close = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'limbo_balance':target_resp[i].limbo_balance} for i in range(0,len(target_resp))]
+ waiting_for_close = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
+ 'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
+ 'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,
+ 'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat, 'limbo_balance':target_resp[i].limbo_balance,
+ 'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
#Get recorded payment events
payments = Payments.objects.exclude(status=3)
- total_payments = payments.filter(status=2).count()
- total_sent = 0 if total_payments == 0 else payments.filter(status=2).aggregate(Sum('value'))['value__sum']
- total_fees = 0 if total_payments == 0 else payments.aggregate(Sum('fee'))['fee__sum']
#Get recorded invoice details
invoices = Invoices.objects.exclude(state=2)
- total_invoices = invoices.filter(state=1).count()
- total_received = 0 if total_invoices == 0 else invoices.aggregate(Sum('amt_paid'))['amt_paid__sum']
#Get recorded forwarding events
forwards = Forwards.objects.all().annotate(amt_in=Sum('amt_in_msat')/1000).annotate(amt_out=Sum('amt_out_msat')/1000).annotate(ppm=Round((Sum('fee')*1000000000)/Sum('amt_out_msat'), output_field=IntegerField())).order_by('-id')
- forwards_df = DataFrame.from_records(forwards.values())
- total_forwards = forwards_df.shape[0]
- total_value_forwards = 0 if total_forwards == 0 else int(forwards_df['amt_out_msat'].sum()/1000)
- total_earned = 0 if total_forwards == 0 else forwards_df['fee'].sum()
- forwards_df_in_sum = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_in', as_index=True).sum()
- forwards_df_out_sum = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_out', as_index=True).sum()
- forwards_df_in_count = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_in', as_index=True).count()
- forwards_df_out_count = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_out', as_index=True).count()
#Get current active channels
active_channels = channels.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by('outbound_percent')
active_capacity = 0 if active_channels.count() == 0 else active_channels.aggregate(Sum('capacity'))['capacity__sum']
@@ -92,20 +129,33 @@ def home(request):
active_outbound = 0 if active_capacity == 0 else active_channels.aggregate(Sum('local_balance'))['local_balance__sum']
active_unsettled = 0 if active_capacity == 0 else active_channels.aggregate(Sum('unsettled_balance'))['unsettled_balance__sum']
filter_7day = datetime.now() - timedelta(days=7)
+ filter_1day = datetime.now() - timedelta(days=1)
forwards_df_7d = DataFrame.from_records(forwards.filter(forward_date__gte=filter_7day).values())
+ forwards_df_1d = DataFrame() if forwards_df_7d.empty else forwards_df_7d[forwards_df_7d['forward_date']>=filter_1day]
forwards_df_in_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_in', as_index=True).sum()
forwards_df_out_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_out', as_index=True).sum()
forwards_df_in_7d_count = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_in', as_index=True).count()
forwards_df_out_7d_count = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_out', as_index=True).count()
+ forwards_df_in_1d_sum = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_in', as_index=True).sum()
+ forwards_df_out_1d_sum = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_out', as_index=True).sum()
+ forwards_df_in_1d_count = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_in', as_index=True).count()
+ forwards_df_out_1d_count = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_out', as_index=True).count()
+ routed_1day = forwards_df_1d.shape[0]
routed_7day = forwards_df_7d.shape[0]
routed_7day_amt = 0 if routed_7day == 0 else int(forwards_df_7d['amt_out_msat'].sum()/1000)
+ routed_1day_amt = 0 if routed_1day == 0 else int(forwards_df_1d['amt_out_msat'].sum()/1000)
total_earned_7day = 0 if routed_7day == 0 else forwards_df_7d['fee'].sum()
+ total_earned_1day = 0 if routed_1day == 0 else forwards_df_1d['fee'].sum()
payments_7day = payments.filter(status=2).filter(creation_date__gte=filter_7day)
payments_7day_amt = 0 if payments_7day.count() == 0 else payments_7day.aggregate(Sum('value'))['value__sum']
+ payments_1day = payments.filter(status=2).filter(creation_date__gte=filter_1day)
+ payments_1day_amt = 0 if payments_1day.count() == 0 else payments_1day.aggregate(Sum('value'))['value__sum']
total_7day_fees = 0 if payments_7day.count() == 0 else payments_7day.aggregate(Sum('fee'))['fee__sum']
+ total_1day_fees = 0 if payments_1day.count() == 0 else payments_1day.aggregate(Sum('fee'))['fee__sum']
pending_htlc_count = channels.filter(is_open=True).aggregate(Sum('htlc_count'))['htlc_count__sum'] if channels.filter(is_open=True).exists() else 0
pending_outbound = channels.filter(is_open=True).aggregate(Sum('pending_outbound'))['pending_outbound__sum'] if channels.filter(is_open=True).exists() else 0
pending_inbound = channels.filter(is_open=True).aggregate(Sum('pending_inbound'))['pending_inbound__sum'] if channels.filter(is_open=True).exists() else 0
+ num_updates = channels.filter(is_open=True).aggregate(Sum('num_updates'))['num_updates__sum'] if channels.filter(is_open=True).exists() else 0
detailed_active_channels = []
for channel in active_channels:
detailed_channel = {}
@@ -128,14 +178,14 @@ def home(request):
detailed_channel['output_index'] = channel.output_index
detailed_channel['outbound_percent'] = int(round(channel.outbound_percent/10, 0))
detailed_channel['inbound_percent'] = int(round(channel.inbound_percent/10, 0))
- detailed_channel['routed_in'] = forwards_df_in_count.loc[channel.chan_id].amt_out_msat if (forwards_df_in_count.index == channel.chan_id).any() else 0
- detailed_channel['routed_out'] = forwards_df_out_count.loc[channel.chan_id].amt_out_msat if (forwards_df_out_count.index == channel.chan_id).any() else 0
- detailed_channel['amt_routed_in'] = int(forwards_df_in_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_in_sum.index == channel.chan_id).any() else 0
- detailed_channel['amt_routed_out'] = int(forwards_df_out_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_out_sum.index == channel.chan_id).any() else 0
detailed_channel['routed_in_7day'] = forwards_df_in_7d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_in_7d_count.index == channel.chan_id).any() else 0
detailed_channel['routed_out_7day'] = forwards_df_out_7d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_out_7d_count.index == channel.chan_id).any() else 0
detailed_channel['amt_routed_in_7day'] = int(forwards_df_in_7d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_in_7d_sum.index == channel.chan_id).any() else 0
detailed_channel['amt_routed_out_7day'] = int(forwards_df_out_7d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_out_7d_sum.index == channel.chan_id).any() else 0
+ detailed_channel['routed_in_1day'] = forwards_df_in_1d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_in_1d_count.index == channel.chan_id).any() else 0
+ detailed_channel['routed_out_1day'] = forwards_df_out_1d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_out_1d_count.index == channel.chan_id).any() else 0
+ detailed_channel['amt_routed_in_1day'] = int(forwards_df_in_1d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_in_1d_sum.index == channel.chan_id).any() else 0
+ detailed_channel['amt_routed_out_1day'] = int(forwards_df_out_1d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_out_1d_sum.index == channel.chan_id).any() else 0
detailed_channel['htlc_count'] = channel.htlc_count
detailed_channel['auto_rebalance'] = channel.auto_rebalance
detailed_channel['ar_in_target'] = channel.ar_in_target
@@ -153,10 +203,16 @@ def home(request):
sum_outbound = active_outbound + pending_outbound + inactive_outbound
sum_inbound = active_inbound + pending_inbound + inactive_inbound
onchain_txs = Onchain.objects.all()
- onchain_costs = 0 if onchain_txs.count() == 0 else onchain_txs.aggregate(Sum('fee'))['fee__sum']
onchain_costs_7day = 0 if onchain_txs.filter(time_stamp__gte=filter_7day).count() == 0 else onchain_txs.filter(time_stamp__gte=filter_7day).aggregate(Sum('fee'))['fee__sum']
- total_costs = total_fees + onchain_costs
+ onchain_costs_1day = 0 if onchain_txs.filter(time_stamp__gte=filter_1day).count() == 0 else onchain_txs.filter(time_stamp__gte=filter_1day).aggregate(Sum('fee'))['fee__sum']
+ closures_7day = Closures.objects.filter(close_height__gte=(node_info.block_height - 1008))
+ closures_1day = Closures.objects.filter(close_height__gte=(node_info.block_height - 144))
+ close_fees_7day = channels.filter(chan_id__in=closures_7day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_7day.exists() else 0
+ close_fees_1day = channels.filter(chan_id__in=closures_1day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_1day.exists() else 0
+ onchain_costs_7day += close_fees_7day
+ onchain_costs_1day += close_fees_1day
total_costs_7day = total_7day_fees + onchain_costs_7day
+ total_costs_1day = total_1day_fees + onchain_costs_1day
#Get list of recent rebalance requests
rebalances = Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')
total_channels = node_info.num_active_channels + node_info.num_inactive_channels - private_count
@@ -170,27 +226,27 @@ def home(request):
'node_info': node_info,
'total_channels': total_channels,
'balances': balances,
+ 'total_balance': balances.total_balance + sum_outbound + pending_open_balance + limbo_balance + private_outbound,
'payments': payments.annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-creation_date')[:6],
- 'total_sent': int(total_sent),
- 'fees_paid': int(total_fees),
- 'total_payments': total_payments,
'invoices': invoices.order_by('-creation_date')[:6],
- 'total_received': total_received,
- 'total_invoices': total_invoices,
- 'forwards': forwards_df.head(15).to_dict(orient='records'),
- 'earned': int(total_earned),
- 'total_forwards': total_forwards,
- 'total_value_forwards': total_value_forwards,
+ 'forwards': forwards[:15],
+ 'routed_1day': routed_1day,
'routed_7day': routed_7day,
+ 'routed_1day_amt': routed_1day_amt,
'routed_7day_amt': routed_7day_amt,
+ 'earned_1day': int(total_earned_1day),
'earned_7day': int(total_earned_7day),
+ 'routed_1day_percent': 0 if sum_outbound == 0 else int((routed_1day_amt/sum_outbound)*100),
'routed_7day_percent': 0 if sum_outbound == 0 else int((routed_7day_amt/sum_outbound)*100),
- 'profit_per_outbound': 0 if sum_outbound == 0 else int((total_earned_7day - total_7day_fees)/(sum_outbound/1000000)),
- 'profit_per_outbound_real': 0 if sum_outbound == 0 else int((total_earned_7day - total_costs_7day)/(sum_outbound/1000000)),
- 'percent_cost': 0 if total_earned == 0 else int((total_costs/total_earned)*100),
+ 'profit_per_outbound_1d': 0 if sum_outbound == 0 else int((total_earned_1day - total_1day_fees)/(sum_outbound/1000000)),
+ 'profit_per_outbound_real_1d': 0 if sum_outbound == 0 else int((total_earned_1day - total_costs_1day)/(sum_outbound/1000000)),
+ 'profit_per_outbound_7d': 0 if sum_outbound == 0 else int((total_earned_7day - total_7day_fees)/(sum_outbound/1000000)),
+ 'profit_per_outbound_real_7d': 0 if sum_outbound == 0 else int((total_earned_7day - total_costs_7day)/(sum_outbound/1000000)),
+ 'percent_cost_1day': 0 if total_earned_1day == 0 else int((total_costs_1day/total_earned_1day)*100),
'percent_cost_7day': 0 if total_earned_7day == 0 else int((total_costs_7day/total_earned_7day)*100),
- 'onchain_costs': onchain_costs,
+ 'onchain_costs_1day': onchain_costs_1day,
'onchain_costs_7day': onchain_costs_7day,
+ 'total_1day_fees': int(total_1day_fees),
'total_7day_fees': int(total_7day_fees),
'active_channels': detailed_active_channels,
'total_capacity': active_capacity + inactive_capacity,
@@ -219,17 +275,19 @@ def home(request):
'local_settings': local_settings,
'pending_htlc_count': pending_htlc_count,
'failed_htlcs': FailedHTLCs.objects.all().order_by('-id')[:10],
- 'payments_ppm': 0 if total_sent == 0 else int((total_fees/total_sent)*1000000),
- 'routed_ppm': 0 if total_value_forwards == 0 else int((total_earned/total_value_forwards)*1000000),
+ '1day_routed_ppm': 0 if routed_1day_amt == 0 else int((total_earned_1day/routed_1day_amt)*1000000),
'7day_routed_ppm': 0 if routed_7day_amt == 0 else int((total_earned_7day/routed_7day_amt)*1000000),
+ '1day_payments_ppm': 0 if payments_1day_amt == 0 else int((total_1day_fees/payments_1day_amt)*1000000),
'7day_payments_ppm': 0 if payments_7day_amt == 0 else int((total_7day_fees/payments_7day_amt)*1000000),
'liq_ratio': 0 if sum_outbound == 0 else int((sum_inbound/sum_outbound)*100),
'eligible_count': channels.filter(is_active=True, is_open=True, private=False, auto_rebalance=True).annotate(inbound_can=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).annotate(fee_ratio=(Sum('remote_fee_rate')*100)/Sum('local_fee_rate')).filter(inbound_can__gte=F('ar_in_target'), fee_ratio__lte=F('ar_max_cost')).count(),
'enabled_count': channels.filter(is_open=True, auto_rebalance=True).count(),
+ 'available_count': channels.filter(is_active=True, is_open=True, private=False, auto_rebalance=False).annotate(outbound_can=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).filter(outbound_can__gte=F('ar_out_target')).count(),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links(),
- 'db_size': db_size
+ 'db_size': db_size,
+ 'num_updates': num_updates
}
return render(request, 'home.html', context)
except Exception as e:
@@ -325,7 +383,7 @@ def channels(request):
apy_7day = 0
apy_30day = 0
context = {
- 'channels': channels_df.to_dict(orient='records'),
+ 'channels': [] if channels_df.empty else channels_df.sort_values(by=['cv_30day'], ascending=False).to_dict(orient='records'),
'apy_7day': apy_7day,
'apy_30day': apy_30day,
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
@@ -463,6 +521,22 @@ def route(request):
else:
return redirect('home')
+@login_required(login_url='/lndg-admin/login/?next=/')
+def routes(request):
+ if request.method == 'GET':
+ try:
+ pubkey = request.GET.urlencode()[1:]
+ context = {
+ 'payment_hash': pubkey,
+ 'route': PaymentHops.objects.filter(payment_hash__in=PaymentHops.objects.filter(node_pubkey=pubkey).order_by('-id').values_list('payment_hash')[:69]).annotate(ppm=Round((Sum('fee')/Sum('amt'))*1000000, output_field=IntegerField()))
+ }
+ return render(request, 'route.html', context)
+ except Exception as e:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
+ else:
+ return redirect('home')
+
@login_required(login_url='/lndg-admin/login/?next=/')
def peers(request):
if request.method == 'GET':
@@ -494,27 +568,63 @@ def balances(request):
@login_required(login_url='/lndg-admin/login/?next=/')
def closures(request):
if request.method == 'GET':
- closures_df = DataFrame.from_records(Closures.objects.all().values())
- if closures_df.empty:
- merged = DataFrame()
- else:
- channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias'))
- if channels_df.empty:
- merged = closures_df
- merged['alias'] = ''
+ try:
+ stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ pending_channels = stub.PendingChannels(ln.PendingChannelsRequest())
+ channels = Channels.objects.all()
+ pending_closed = None
+ pending_force_closed = None
+ waiting_for_close = None
+ if pending_channels.pending_closing_channels:
+ target_resp = pending_channels.pending_closing_channels
+ pending_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat, 'limbo_balance':target_resp[i].limbo_balance, 'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
+ if pending_channels.pending_force_closing_channels:
+ target_resp = pending_channels.pending_force_closing_channels
+ pending_force_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'closing_txid':target_resp[i].closing_txid,'limbo_balance':target_resp[i].limbo_balance,'maturity_height':target_resp[i].maturity_height,'blocks_til_maturity':target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else find_next_block_maturity(target_resp[i]),'maturity_datetime':(datetime.now()+timedelta(minutes=(10*target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else 10*find_next_block_maturity(target_resp[i]) )))} for i in range(0,len(target_resp))]
+ if pending_channels.waiting_close_channels:
+ target_resp = pending_channels.waiting_close_channels
+ waiting_for_close = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat, 'limbo_balance':target_resp[i].limbo_balance, 'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
+ closures_df = DataFrame.from_records(Closures.objects.all().values())
+ if closures_df.empty:
+ merged = DataFrame()
else:
- merged = merge(closures_df, channels_df, on='chan_id', how='left')
- merged['alias'] = merged['alias'].fillna('')
- context = {
- 'closures': [] if merged.empty else merged.sort_values(by=['close_height'], ascending=False).to_dict(orient='records'),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
- 'network_links': network_links(),
- 'graph_links': graph_links()
- }
- return render(request, 'closures.html', context)
+ channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias', 'closing_costs'))
+ if channels_df.empty:
+ merged = closures_df
+ merged['alias'] = ''
+ else:
+ merged = merge(closures_df, channels_df, on='chan_id', how='left')
+ merged['alias'] = merged['alias'].fillna('')
+ merged['closing_costs'] = merged['closing_costs'].fillna('')
+ context = {
+ 'pending_closed': pending_closed,
+ 'pending_force_closed': pending_force_closed,
+ 'waiting_for_close': waiting_for_close,
+ 'closures': [] if merged.empty else merged.sort_values(by=['close_height'], ascending=False).to_dict(orient='records'),
+ 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network_links': network_links(),
+ 'graph_links': graph_links()
+ }
+ return render(request, 'closures.html', context)
+ except Exception as e:
+ try:
+ error = str(e.code())
+ except:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
else:
return redirect('home')
+def find_next_block_maturity(force_closing_channel):
+ #print (f"{datetime.now().strftime('%c')} : {force_closing_channel=}")
+ if force_closing_channel.blocks_til_maturity > 0:
+ return force_closing_channel.blocks_til_maturity
+ for pending_htlc in force_closing_channel.pending_htlcs:
+ if pending_htlc.blocks_til_maturity > 0:
+ #print (f"{datetime.now().strftime('%c')} : {pending_htlc=}")
+ return pending_htlc.blocks_til_maturity
+ return -1
+
@login_required(login_url='/lndg-admin/login/?next=/')
def towers(request):
if request.method == 'GET':
@@ -626,6 +736,150 @@ def resolutions(request):
else:
return redirect('home')
+@login_required(login_url='/lndg-admin/login/?next=/')
+def income(request):
+ if request.method == 'GET':
+ stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ filter_90day = datetime.now() - timedelta(days=90)
+ filter_30day = datetime.now() - timedelta(days=30)
+ filter_7day = datetime.now() - timedelta(days=7)
+ filter_1day = datetime.now() - timedelta(days=1)
+ node_info = stub.GetInfo(ln.GetInfoRequest())
+ channels = Channels.objects.all()
+ payments = Payments.objects.filter(status=2)
+ payments_90day = payments.filter(creation_date__gte=filter_90day)
+ payments_30day = payments.filter(creation_date__gte=filter_30day)
+ payments_7day = payments.filter(creation_date__gte=filter_7day)
+ payments_1day = payments.filter(creation_date__gte=filter_1day)
+ onchain_txs = Onchain.objects.all()
+ onchain_txs_90day = onchain_txs.filter(time_stamp__gte=filter_90day)
+ onchain_txs_30day = onchain_txs.filter(time_stamp__gte=filter_30day)
+ onchain_txs_7day = onchain_txs.filter(time_stamp__gte=filter_7day)
+ onchain_txs_1day = onchain_txs.filter(time_stamp__gte=filter_1day)
+ closures = Closures.objects.all()
+ closures_90day = closures.filter(close_height__gte=(node_info.block_height - 12960))
+ closures_30day = closures.filter(close_height__gte=(node_info.block_height - 4320))
+ closures_7day = closures.filter(close_height__gte=(node_info.block_height - 1008))
+ closures_1day = closures.filter(close_height__gte=(node_info.block_height - 144))
+ forwards = Forwards.objects.all()
+ forwards_90day = forwards.filter(forward_date__gte=filter_90day)
+ forwards_30day = forwards.filter(forward_date__gte=filter_30day)
+ forwards_7day = forwards.filter(forward_date__gte=filter_7day)
+ forwards_1day = forwards.filter(forward_date__gte=filter_1day)
+ forward_count = forwards.count()
+ forward_count_90day = forwards_90day.count()
+ forward_count_30day = forwards_30day.count()
+ forward_count_7day = forwards_7day.count()
+ forward_count_1day = forwards_1day.count()
+ forward_amount = 0 if forward_count == 0 else int(forwards.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
+ forward_amount_90day = 0 if forward_count_90day == 0 else int(forwards_90day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
+ forward_amount_30day = 0 if forward_count_30day == 0 else int(forwards_30day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
+ forward_amount_7day = 0 if forward_count_7day == 0 else int(forwards_7day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
+ forward_amount_1day = 0 if forward_count_1day == 0 else int(forwards_1day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
+ total_revenue = 0 if forward_count == 0 else int(forwards.aggregate(Sum('fee'))['fee__sum'])
+ total_revenue_90day = 0 if forward_count_90day == 0 else int(forwards_90day.aggregate(Sum('fee'))['fee__sum'])
+ total_revenue_30day = 0 if forward_count_30day == 0 else int(forwards_30day.aggregate(Sum('fee'))['fee__sum'])
+ total_revenue_7day = 0 if forward_count_7day == 0 else int(forwards_7day.aggregate(Sum('fee'))['fee__sum'])
+ total_revenue_1day = 0 if forward_count_1day == 0 else int(forwards_1day.aggregate(Sum('fee'))['fee__sum'])
+ total_revenue_ppm = 0 if forward_amount == 0 else int(total_revenue/(forward_amount/1000000))
+ total_revenue_ppm_90day = 0 if forward_amount_90day == 0 else int(total_revenue_90day/(forward_amount_90day/1000000))
+ total_revenue_ppm_30day = 0 if forward_amount_30day == 0 else int(total_revenue_30day/(forward_amount_30day/1000000))
+ total_revenue_ppm_7day = 0 if forward_amount_7day == 0 else int(total_revenue_7day/(forward_amount_7day/1000000))
+ total_revenue_ppm_1day = 0 if forward_amount_1day == 0 else int(total_revenue_1day/(forward_amount_1day/1000000))
+ total_sent = 0 if payments.count() == 0 else int(payments.aggregate(Sum('value'))['value__sum'])
+ total_sent_90day = 0 if payments_90day.count() == 0 else int(payments_90day.aggregate(Sum('value'))['value__sum'])
+ total_sent_30day = 0 if payments_30day.count() == 0 else int(payments_30day.aggregate(Sum('value'))['value__sum'])
+ total_sent_7day = 0 if payments_7day.count() == 0 else int(payments_7day.aggregate(Sum('value'))['value__sum'])
+ total_sent_1day = 0 if payments_1day.count() == 0 else int(payments_1day.aggregate(Sum('value'))['value__sum'])
+ total_fees = 0 if payments.count() == 0 else int(payments.aggregate(Sum('fee'))['fee__sum'])
+ total_fees_90day = 0 if payments_90day.count() == 0 else int(payments_90day.aggregate(Sum('fee'))['fee__sum'])
+ total_fees_30day = 0 if payments_30day.count() == 0 else int(payments_30day.aggregate(Sum('fee'))['fee__sum'])
+ total_fees_7day = 0 if payments_7day.count() == 0 else int(payments_7day.aggregate(Sum('fee'))['fee__sum'])
+ total_fees_1day = 0 if payments_1day.count() == 0 else int(payments_1day.aggregate(Sum('fee'))['fee__sum'])
+ total_fees_ppm = 0 if total_sent == 0 else int(total_fees/(total_sent/1000000))
+ total_fees_ppm_90day = 0 if total_sent_90day == 0 else int(total_fees_90day/(total_sent_90day/1000000))
+ total_fees_ppm_30day = 0 if total_sent_30day == 0 else int(total_fees_30day/(total_sent_30day/1000000))
+ total_fees_ppm_7day = 0 if total_sent_7day == 0 else int(total_fees_7day/(total_sent_7day/1000000))
+ total_fees_ppm_1day = 0 if total_sent_1day == 0 else int(total_fees_1day/(total_sent_1day/1000000))
+ onchain_costs = 0 if onchain_txs.count() == 0 else onchain_txs.aggregate(Sum('fee'))['fee__sum']
+ onchain_costs_90day = 0 if onchain_txs_90day.count() == 0 else onchain_txs_90day.aggregate(Sum('fee'))['fee__sum']
+ onchain_costs_30day = 0 if onchain_txs_30day.count() == 0 else onchain_txs_30day.aggregate(Sum('fee'))['fee__sum']
+ onchain_costs_7day = 0 if onchain_txs_7day.count() == 0 else onchain_txs_7day.aggregate(Sum('fee'))['fee__sum']
+ onchain_costs_1day = 0 if onchain_txs_1day.count() == 0 else onchain_txs_1day.aggregate(Sum('fee'))['fee__sum']
+ close_fees = channels.filter(chan_id__in=closures.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures.exists() else 0
+ close_fees_90day = channels.filter(chan_id__in=closures_90day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_90day.exists() else 0
+ close_fees_30day = channels.filter(chan_id__in=closures_30day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_30day.exists() else 0
+ close_fees_7day = channels.filter(chan_id__in=closures_7day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_7day.exists() else 0
+ close_fees_1day = channels.filter(chan_id__in=closures_1day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_1day.exists() else 0
+ onchain_costs += close_fees
+ onchain_costs_90day += close_fees_90day
+ onchain_costs_30day += close_fees_30day
+ onchain_costs_7day += close_fees_7day
+ onchain_costs_1day += close_fees_1day
+ profits = int(total_revenue-total_fees-onchain_costs)
+ profits_90day = int(total_revenue_90day-total_fees_90day-onchain_costs_90day)
+ profits_30day = int(total_revenue_30day-total_fees_30day-onchain_costs_30day)
+ profits_7day = int(total_revenue_7day-total_fees_7day-onchain_costs_7day)
+ profits_1day = int(total_revenue_1day-total_fees_1day-onchain_costs_1day)
+ context = {
+ 'node_info': node_info,
+ 'forward_count': forward_count,
+ 'forward_count_90day': forward_count_90day,
+ 'forward_count_30day': forward_count_30day,
+ 'forward_count_7day': forward_count_7day,
+ 'forward_count_1day': forward_count_1day,
+ 'forward_amount': forward_amount,
+ 'forward_amount_90day': forward_amount_90day,
+ 'forward_amount_30day': forward_amount_30day,
+ 'forward_amount_7day': forward_amount_7day,
+ 'forward_amount_1day': forward_amount_1day,
+ 'total_revenue': total_revenue,
+ 'total_revenue_90day': total_revenue_90day,
+ 'total_revenue_30day': total_revenue_30day,
+ 'total_revenue_7day': total_revenue_7day,
+ 'total_revenue_1day': total_revenue_1day,
+ 'total_fees': total_fees,
+ 'total_fees_90day': total_fees_90day,
+ 'total_fees_30day': total_fees_30day,
+ 'total_fees_7day': total_fees_7day,
+ 'total_fees_1day': total_fees_1day,
+ 'total_fees_ppm': total_fees_ppm,
+ 'total_fees_ppm_90day': total_fees_ppm_90day,
+ 'total_fees_ppm_30day': total_fees_ppm_30day,
+ 'total_fees_ppm_7day': total_fees_ppm_7day,
+ 'total_fees_ppm_1day': total_fees_ppm_1day,
+ 'onchain_costs': onchain_costs,
+ 'onchain_costs_90day': onchain_costs_90day,
+ 'onchain_costs_30day': onchain_costs_30day,
+ 'onchain_costs_7day': onchain_costs_7day,
+ 'onchain_costs_1day': onchain_costs_1day,
+ 'total_revenue_ppm': total_revenue_ppm,
+ 'total_revenue_ppm_90day': total_revenue_ppm_90day,
+ 'total_revenue_ppm_30day': total_revenue_ppm_30day,
+ 'total_revenue_ppm_7day': total_revenue_ppm_7day,
+ 'total_revenue_ppm_1day': total_revenue_ppm_1day,
+ 'profits': profits,
+ 'profits_90day': profits_90day,
+ 'profits_30day': profits_30day,
+ 'profits_7day': profits_7day,
+ 'profits_1day': profits_1day,
+ 'profits_ppm': 0 if forward_amount == 0 else int(profits/(forward_amount/1000000)),
+ 'profits_ppm_90day': 0 if forward_amount_90day == 0 else int(profits_90day/(forward_amount_90day/1000000)),
+ 'profits_ppm_30day': 0 if forward_amount_30day == 0 else int(profits_30day/(forward_amount_30day/1000000)),
+ 'profits_ppm_7day': 0 if forward_amount_7day == 0 else int(profits_7day/(forward_amount_7day/1000000)),
+ 'profits_ppm_1day': 0 if forward_amount_1day == 0 else int(profits_1day/(forward_amount_1day/1000000)),
+ 'percent_cost': 0 if total_revenue == 0 else int(((total_fees+onchain_costs)/total_revenue)*100),
+ 'percent_cost_90day': 0 if total_revenue_90day == 0 else int(((total_fees_90day+onchain_costs_90day)/total_revenue_90day)*100),
+ 'percent_cost_30day': 0 if total_revenue_30day == 0 else int(((total_fees_30day+onchain_costs_30day)/total_revenue_30day)*100),
+ 'percent_cost_7day': 0 if total_revenue_7day == 0 else int(((total_fees_7day+onchain_costs_7day)/total_revenue_7day)*100),
+ 'percent_cost_1day': 0 if total_revenue_1day == 0 else int(((total_fees_1day+onchain_costs_1day)/total_revenue_1day)*100),
+ 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'graph_links': graph_links()
+ }
+ return render(request, 'income.html', context)
+ else:
+ return redirect('home')
+
@login_required(login_url='/lndg-admin/login/?next=/')
def channel(request):
if request.method == 'GET':
@@ -638,8 +892,12 @@ def channel(request):
payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(chan_out=chan_id).filter(rebal_chan__isnull=False).annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).values())
invoices_df = DataFrame.from_records(Invoices.objects.filter(state=1).filter(chan_in=chan_id).filter(r_hash__in=Payments.objects.filter(status=2).filter(rebal_chan=chan_id)).values())
channels_df = DataFrame.from_records(Channels.objects.filter(is_open=True).values())
- node_outbound = channels_df['local_balance'].sum()
- node_capacity = channels_df['capacity'].sum()
+ if channels_df.empty:
+ node_outbound = 0
+ node_capacity = 0
+ else:
+ node_outbound = channels_df['local_balance'].sum()
+ node_capacity = channels_df['capacity'].sum()
channels_df = DataFrame.from_records(Channels.objects.filter(chan_id=chan_id).values())
rebalancer_df = DataFrame.from_records(Rebalancer.objects.filter(last_hop_pubkey=channels_df['remote_pubkey'][0]).annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id').values())
failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).order_by('-id').values())
@@ -887,6 +1145,7 @@ def channel(request):
channels_df['costs_30day'] = 0 if channels_df['rebal_in_30day'][0] == 0 or invoice_hashes_30d.empty == True else int(rebal_payments_df_30d.set_index('payment_hash', inplace=False).loc[invoice_hashes_30d[chan_id]]['fee'].sum())
channels_df['costs_7day'] = 0 if channels_df['rebal_in_7day'][0] == 0 or invoice_hashes_7d.empty == True else int(rebal_payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[chan_id]]['fee'].sum())
channels_df['costs_1day'] = 0 if channels_df['rebal_in_1day'][0] == 0 or invoice_hashes_1d.empty == True else int(rebal_payments_df_1d.set_index('payment_hash', inplace=False).loc[invoice_hashes_1d[chan_id]]['fee'].sum())
+ channels_df['costs'] += channels_df['closing_costs']
channels_df['profits'] = channels_df['revenue'] - channels_df['costs']
channels_df['profits_30day'] = channels_df['revenue_30day'] - channels_df['costs_30day']
channels_df['profits_7day'] = channels_df['revenue_7day'] - channels_df['costs_7day']
@@ -1126,6 +1385,16 @@ def invoices(request):
else:
return redirect('home')
+@login_required(login_url='/lndg-admin/login/?next=/')
+def rebalances(request):
+ if request.method == 'GET':
+ context = {
+ 'rebalances': Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')[:150],
+ }
+ return render(request, 'rebalances.html', context)
+ else:
+ return redirect('home')
+
@login_required(login_url='/lndg-admin/login/?next=/')
def batch(request):
if request.method == 'GET':
@@ -1302,12 +1571,16 @@ def rebalancing(request):
eligible_df = enabled_df[enabled_df['is_active']==True][enabled_df['inbound_can']>=1][enabled_df['fee_check']<100]
eligible_count = eligible_df.shape[0]
enabled_count = enabled_df.shape[0]
+ available_df = channels_df[channels_df['auto_rebalance']==False][channels_df['is_active']==True][channels_df['percent_outbound'] / channels_df['ar_out_target']>=1]
+ available_count = available_df.shape[0]
else:
eligible_count = 0
enabled_count = 0
+ available_count = 0
context = {
'eligible_count': eligible_count,
'enabled_count': enabled_count,
+ 'available_count': available_count,
'channels': channels_df.to_dict(orient='records'),
'rebalancer': Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')[:20],
'rebalancer_form': RebalancerForm,
@@ -1593,8 +1866,11 @@ def auto_rebalance(request):
db_percent_target = LocalSettings.objects.get(key='AR-Target%')
db_percent_target.value = target_percent
db_percent_target.save()
- Channels.objects.all().update(ar_amt_target=Round(F('capacity')*(target_percent/100), output_field=IntegerField()))
- messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
+ if form.cleaned_data['targetallchannels']:
+ Channels.objects.all().update(ar_amt_target=Round(F('capacity')*(target_percent/100), output_field=IntegerField()))
+ messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
+ else:
+ messages.success(request, 'Updated auto rebalancer target amount in local settings: ' + str(target_percent))
if form.cleaned_data['target_time'] is not None:
target_time = form.cleaned_data['target_time']
try:
@@ -1624,8 +1900,25 @@ def auto_rebalance(request):
db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%')
db_outbound_target.value = outbound_percent
db_outbound_target.save()
- Channels.objects.all().update(ar_out_target=int(outbound_percent))
- messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
+ if form.cleaned_data['targetallchannels']:
+ Channels.objects.all().update(ar_out_target=int(outbound_percent))
+ messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
+ else:
+ messages.success(request, 'Updated auto rebalancer target outbound percent setting in local settings to: ' + str(outbound_percent))
+ if form.cleaned_data['inbound_percent'] is not None:
+ inbound_percent = int(form.cleaned_data['inbound_percent'])
+ try:
+ db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
+ except:
+ LocalSettings(key='AR-Inbound%', value='100').save()
+ db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
+ db_inbound_target.value = inbound_percent
+ db_inbound_target.save()
+ if form.cleaned_data['targetallchannels']:
+ Channels.objects.all().update(ar_out_target=int(outbound_percent))
+ messages.success(request, 'Updated auto rebalancer target inbound percent setting for all channels to: ' + str(inbound_percent))
+ else:
+ messages.success(request, 'Updated auto rebalancer target inbound percent setting in local settigs to: ' + str(inbound_percent))
if form.cleaned_data['fee_rate'] is not None:
fee_rate = form.cleaned_data['fee_rate']
try:
@@ -1645,8 +1938,11 @@ def auto_rebalance(request):
db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%')
db_max_cost.value = max_cost
db_max_cost.save()
- Channels.objects.all().update(ar_max_cost=int(max_cost))
- messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost))
+ if form.cleaned_data['targetallchannels']:
+ Channels.objects.all().update(ar_max_cost=int(max_cost))
+ messages.success(request, 'Updated auto rebalancer max cost setting for all channels to: ' + str(max_cost))
+ else:
+ messages.success(request, 'Updated auto rebalancer max cost setting in local settings to: ' + str(max_cost))
if form.cleaned_data['autopilot'] is not None:
autopilot = form.cleaned_data['autopilot']
try:
@@ -1657,6 +1953,16 @@ def auto_rebalance(request):
db_autopilot.value = autopilot
db_autopilot.save()
messages.success(request, 'Updated autopilot setting to: ' + str(autopilot))
+ if form.cleaned_data['autopilotdays'] is not None:
+ autopilotdays = form.cleaned_data['autopilotdays']
+ try:
+ db_autopilotdays = LocalSettings.objects.get(key='AR-APDays')
+ except:
+ LocalSettings(key='AR-APDays', value='7').save()
+ db_autopilotdays = LocalSettings.objects.get(key='AR-APDays')
+ db_autopilotdays.value = autopilotdays
+ db_autopilotdays.save()
+ messages.success(request, 'Updated autopilot days setting to: ' + str(autopilotdays))
if form.cleaned_data['variance'] is not None:
variance = form.cleaned_data['variance']
try:
@@ -1757,6 +2063,66 @@ def update_channel(request):
db_channel.local_cltv = target
db_channel.save()
messages.success(request, 'CLTV for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
+ elif update_target == 10:
+ db_channel.closing_costs = target
+ db_channel.save()
+ messages.success(request, 'Closing costs for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.closing_costs))
+ else:
+ messages.error(request, 'Invalid target code. Please try again.')
+ else:
+ messages.error(request, 'Invalid Request. Please try again.')
+ return redirect(request.META.get('HTTP_REFERER'))
+
+@login_required(login_url='/lndg-admin/login/?next=/')
+def update_pending(request):
+ if request.method == 'POST':
+ form = UpdatePending(request.POST)
+ if form.is_valid():
+ funding_txid = form.cleaned_data['funding_txid']
+ output_index = form.cleaned_data['output_index']
+ target = form.cleaned_data['target']
+ update_target = int(form.cleaned_data['update_target'])
+ if PendingChannels.objects.filter(funding_txid=funding_txid, output_index=output_index).exists():
+ pending_channel = PendingChannels.objects.filter(funding_txid=funding_txid, output_index=output_index)[0]
+ else:
+ pending_channel = PendingChannels(funding_txid=funding_txid, output_index=output_index)
+ pending_channel.save()
+ if update_target == 0:
+ pending_channel.local_base_fee = target
+ pending_channel.save()
+ messages.success(request, 'Base fee for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
+ elif update_target == 1:
+ pending_channel.local_fee_rate = target
+ pending_channel.save()
+ messages.success(request, 'Fee rate for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
+ elif update_target == 2:
+ pending_channel.ar_amt_target = target
+ pending_channel.save()
+ messages.success(request, 'Auto rebalancer target amount for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
+ elif update_target == 3:
+ pending_channel.ar_in_target = target
+ pending_channel.save()
+ messages.success(request, 'Auto rebalancer inbound target for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target) + '%')
+ elif update_target == 4:
+ pending_channel.ar_out_target = target
+ pending_channel.save()
+ messages.success(request, 'Auto rebalancer outbound target for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target) + '%')
+ elif update_target == 5:
+ pending_channel.auto_rebalance = True if pending_channel.auto_rebalance == False else False
+ pending_channel.save()
+ messages.success(request, 'Auto rebalancer status for pending pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(pending_channel.auto_rebalance))
+ elif update_target == 6:
+ pending_channel.ar_max_cost = target
+ pending_channel.save()
+ messages.success(request, 'Auto rebalancer max cost for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target) + '%')
+ elif update_target == 8:
+ pending_channel.auto_fees = True if pending_channel.auto_fees == False else False
+ pending_channel.save()
+ messages.success(request, 'Auto fees status for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(pending_channel.auto_fees))
+ elif update_target == 9:
+ pending_channel.local_cltv = target
+ pending_channel.save()
+ messages.success(request, 'CLTV for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
else:
messages.error(request, 'Invalid target code. Please try again.')
else:
@@ -1779,7 +2145,7 @@ def update_setting(request):
db_percent_target = LocalSettings.objects.get(key='AR-Target%')
db_percent_target.value = target_percent
db_percent_target.save()
- messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
+ messages.success(request, 'Updated auto rebalancer target amount to: ' + str(target_percent))
elif key == 'AR-Time':
target_time = int(value)
try:
@@ -1809,7 +2175,17 @@ def update_setting(request):
db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%')
db_outbound_target.value = outbound_percent
db_outbound_target.save()
- messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
+ messages.success(request, 'Updated auto rebalancer target outbound percent setting: ' + str(outbound_percent))
+ elif key == 'AR-Inbound%':
+ inbound_percent = int(value)
+ try:
+ db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
+ except:
+ LocalSettings(key='AR-Inbound%', value='100').save()
+ db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
+ db_inbound_target.value = inbound_percent
+ db_inbound_target.save()
+ messages.success(request, 'Updated auto rebalancer target inbound percent setting: ' + str(inbound_percent))
elif key == 'AR-MaxFeeRate':
fee_rate = int(value)
try:
@@ -1840,6 +2216,16 @@ def update_setting(request):
db_autopilot.value = autopilot
db_autopilot.save()
messages.success(request, 'Updated autopilot setting to: ' + str(autopilot))
+ elif key == 'AR-APDays':
+ apdays = int(value)
+ try:
+ db_apdays = LocalSettings.objects.get(key='AR-APDays')
+ except:
+ LocalSettings(key='AR-APDays', value='7').save()
+ db_apdays = LocalSettings.objects.get(key='AR-APDays')
+ db_apdays.value = apdays
+ db_apdays.save()
+ messages.success(request, 'Updated Autopilot Days setting to: ' + str(apdays))
elif key == 'AR-Variance':
variance = int(value)
try:
@@ -2327,4 +2713,4 @@ def pending_channels(request):
details_index = error.find('details =') + 11
debug_error_index = error.find('debug_error_string =') - 3
error_msg = error[details_index:debug_error_index]
- return Response({'error': 'Failed to get pending channels! Error: ' + error_msg})
\ No newline at end of file
+ return Response({'error': 'Failed to get pending channels! Error: ' + error_msg})
diff --git a/initialize.py b/initialize.py
index fab5f7c..b2232b8 100644
--- a/initialize.py
+++ b/initialize.py
@@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model
from django.conf import settings
BASE_DIR = Path(__file__).resolve().parent
-def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug):
+def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted):
#Generate a unique secret to be used for your django site
secret = secrets.token_urlsafe(64)
if whitenoise:
@@ -13,6 +13,12 @@ def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenois
'whitenoise.middleware.WhiteNoiseMiddleware',"""
else:
wnl = ''
+ if csrftrusted:
+ csrf = """
+CSRF_TRUSTED_ORIGINS = [%s]
+ """ % (csrftrusted)
+ else:
+ csrf = ''
settings_file = '''"""
Django settings for lndg project.
@@ -41,7 +47,7 @@ SECRET_KEY = '%s'
DEBUG = %s
ALLOWED_HOSTS = ['%s']
-
+%s
LND_DIR_PATH = '%s'
LND_NETWORK = '%s'
LND_RPC_SERVER = '%s'
@@ -151,7 +157,7 @@ USE_TZ = False
STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'gui/static/')
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
-''' % (secret, debug, node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, wnl)
+''' % (secret, debug, node_ip, csrf, lnd_dir_path, lnd_network, lnd_rpc_server, wnl)
try:
f = open("lndg/settings.py", "x")
f.close()
@@ -247,7 +253,9 @@ def main():
parser.add_argument('-wn', '--whitenoise', help = 'Add whitenoise middleware (docker requirement for static files)', action='store_true')
parser.add_argument('-d', '--docker', help = 'Single option for docker container setup (supervisord + whitenoise)', action='store_true')
parser.add_argument('-dx', '--debug', help = 'Setup the django site in debug mode', action='store_true')
+ parser.add_argument('-u', '--adminuser', help = 'Setup a custom admin username', default='lndg-admin')
parser.add_argument('-pw', '--adminpw', help = 'Setup a custom admin password', default=None)
+ parser.add_argument('-csrf', '--csrftrusted', help = 'Set trusted CSRF origins', default=None)
args = parser.parse_args()
node_ip = args.nodeip
lnd_dir_path = args.lnddir
@@ -258,11 +266,13 @@ def main():
whitenoise = args.whitenoise
docker = args.docker
debug = args.debug
+ adminuser = args.adminuser
adminpw = args.adminpw
+ csrftrusted = args.csrftrusted
if docker:
setup_supervisord = True
whitenoise = True
- write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug)
+ write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted)
if setup_supervisord:
print('Supervisord setup requested...')
write_supervisord_settings(sduser)
@@ -300,8 +310,8 @@ def main():
if get_user_model().objects.count() == 0:
print('Setting up initial user...')
try:
- call_command('createsuperuser', username='lndg-admin', email='admin@lndg.local', interactive=False)
- admin = get_user_model().objects.get(username='lndg-admin')
+ call_command('createsuperuser', username=adminuser, email='admin@lndg.local', interactive=False)
+ admin = get_user_model().objects.get(username=adminuser)
login_pw = secrets.token_urlsafe(16) if adminpw is None else adminpw
admin.set_password(login_pw)
admin.save()
diff --git a/jobs.py b/jobs.py
index 2a1dc7e..987099f 100644
--- a/jobs.py
+++ b/jobs.py
@@ -1,5 +1,5 @@
import django
-from django.db.models import Max
+from django.db.models import Max, Min
from datetime import datetime, timedelta
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
@@ -9,17 +9,24 @@ from gui.lnd_deps.lnd_connect import lnd_connect
from lndg import settings
from os import environ
from pandas import DataFrame
+from requests import get
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
django.setup()
-from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees
+from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees, PendingChannels
+from lndg.settings import LND_NETWORK
def update_payments(stub):
- #Remove anything in-flight so we can get most up to date status
- Payments.objects.filter(status=1).delete()
- #Get the number of records in the database currently
- last_index = 0 if Payments.objects.aggregate(Max('index'))['index__max'] == None else Payments.objects.aggregate(Max('index'))['index__max']
- payments = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=last_index, max_payments=100)).payments
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
+ inflight_payments = Payments.objects.filter(status=1).order_by('index')
+ for payment in inflight_payments:
+ payment_data = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=payment.index-1, max_payments=1)).payments
+ if len(payment_data) > 0 and payment.payment_hash == payment_data[0].payment_hash:
+ update_payment(stub, payment_data[0], self_pubkey)
+ else:
+ payment.status = 3
+ payment.save()
+ last_index = Payments.objects.aggregate(Max('index'))['index__max'] if Payments.objects.exists() else 0
+ payments = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=last_index, max_payments=100)).payments
for payment in payments:
try:
new_payment = Payments(creation_date=datetime.fromtimestamp(payment.creation_date), payment_hash=payment.payment_hash, value=round(payment.value_msat/1000, 3), fee=round(payment.fee_msat/1000, 3), status=payment.status, index=payment.payment_index)
@@ -57,85 +64,108 @@ def update_payments(stub):
new_payment.save()
except:
#Error inserting, try to update instead
- db_payment = Payments.objects.filter(payment_hash=payment.payment_hash)[0]
- db_payment.creation_date = datetime.fromtimestamp(payment.creation_date)
- db_payment.value = round(payment.value_msat/1000, 3)
- db_payment.fee = round(payment.fee_msat/1000, 3)
- db_payment.status = payment.status
- db_payment.index = payment.payment_index
- db_payment.save()
- if payment.status == 2:
- for attempt in payment.htlcs:
- if attempt.status == 1:
- PaymentHops.objects.filter(payment_hash=db_payment).delete()
- hops = attempt.route.hops
- hop_count = 0
- cost_to = 0
- total_hops = len(hops)
- for hop in hops:
- hop_count += 1
- try:
- alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=hop.pub_key, include_channels=False)).node.alias
- except:
- alias = ''
- fee = hop.fee_msat/1000
- PaymentHops(payment_hash=db_payment, attempt_id=attempt.attempt_id, step=hop_count, chan_id=hop.chan_id, alias=alias, chan_capacity=hop.chan_capacity, node_pubkey=hop.pub_key, amt=round(hop.amt_to_forward_msat/1000, 3), fee=round(fee, 3), cost_to=round(cost_to, 3)).save()
- cost_to += fee
- if hop_count == 1:
- if db_payment.chan_out is None:
- db_payment.chan_out = hop.chan_id
- db_payment.chan_out_alias = alias
- else:
- db_payment.chan_out = 'MPP'
- db_payment.chan_out_alias = 'MPP'
- if hop_count == total_hops and 5482373484 in hop.custom_records and db_payment.keysend_preimage is None:
- records = hop.custom_records
- message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
- db_payment.keysend_preimage = records[5482373484].hex()
- db_payment.message = message
- if hop_count == total_hops and hop.pub_key == self_pubkey and db_payment.rebal_chan is None:
- db_payment.rebal_chan = hop.chan_id
- db_payment.save()
+ update_payment(stub, payment, self_pubkey)
+
+def update_payment(stub, payment, self_pubkey):
+ db_payment = Payments.objects.filter(payment_hash=payment.payment_hash)[0]
+ db_payment.creation_date = datetime.fromtimestamp(payment.creation_date)
+ db_payment.value = round(payment.value_msat/1000, 3)
+ db_payment.fee = round(payment.fee_msat/1000, 3)
+ db_payment.status = payment.status
+ db_payment.index = payment.payment_index
+ db_payment.save()
+ if payment.status == 2:
+ PaymentHops.objects.filter(payment_hash=db_payment).delete()
+ for attempt in payment.htlcs:
+ if attempt.status == 1:
+ hops = attempt.route.hops
+ hop_count = 0
+ cost_to = 0
+ total_hops = len(hops)
+ for hop in hops:
+ hop_count += 1
+ try:
+ alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=hop.pub_key, include_channels=False)).node.alias
+ except:
+ alias = ''
+ fee = hop.fee_msat/1000
+ PaymentHops(payment_hash=db_payment, attempt_id=attempt.attempt_id, step=hop_count, chan_id=hop.chan_id, alias=alias, chan_capacity=hop.chan_capacity, node_pubkey=hop.pub_key, amt=round(hop.amt_to_forward_msat/1000, 3), fee=round(fee, 3), cost_to=round(cost_to, 3)).save()
+ cost_to += fee
+ if hop_count == 1:
+ if db_payment.chan_out is None:
+ db_payment.chan_out = hop.chan_id
+ db_payment.chan_out_alias = alias
+ else:
+ db_payment.chan_out = 'MPP'
+ db_payment.chan_out_alias = 'MPP'
+ if hop_count == total_hops and 5482373484 in hop.custom_records and db_payment.keysend_preimage is None:
+ records = hop.custom_records
+ message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
+ db_payment.keysend_preimage = records[5482373484].hex()
+ db_payment.message = message
+ if hop_count == total_hops and hop.pub_key == self_pubkey and db_payment.rebal_chan is None:
+ db_payment.rebal_chan = hop.chan_id
+ db_payment.save()
def update_invoices(stub):
- #Remove anything open so we can get most up to date status
- Invoices.objects.filter(state=0).delete()
- last_index = 0 if Invoices.objects.aggregate(Max('index'))['index__max'] == None else Invoices.objects.aggregate(Max('index'))['index__max']
+ open_invoices = Invoices.objects.filter(state=0).order_by('index')
+ for open_invoice in open_invoices:
+ invoice_data = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=open_invoice.index-1, num_max_invoices=1)).invoices
+ if len(invoice_data) > 0 and open_invoice.r_hash == invoice_data[0].r_hash.hex():
+ update_invoice(stub, invoice_data[0], open_invoice)
+ else:
+ open_invoice.state = 2
+ open_invoice.save()
+ last_index = Invoices.objects.aggregate(Max('index'))['index__max'] if Invoices.objects.exists() else 0
invoices = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=last_index, num_max_invoices=100)).invoices
for invoice in invoices:
- if invoice.state == 1:
- if len(invoice.htlcs) > 0:
- chan_in_id = invoice.htlcs[0].chan_id
- alias = Channels.objects.filter(chan_id=chan_in_id)[0].alias if Channels.objects.filter(chan_id=chan_in_id).exists() else None
- records = invoice.htlcs[0].custom_records
- keysend_preimage = records[5482373484].hex() if 5482373484 in records else None
- message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
- if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
- signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
- self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
- try:
- valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
- except:
- print('Unable to validate signature on invoice: ' + invoice.r_hash.hex())
- valid = False
- sender = records[34349339].hex() if valid == True else None
- try:
- sender_alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
- except:
- sender_alias = None
- else:
- sender = None
+ db_invoice = Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, index=invoice.add_index)
+ db_invoice.save()
+ update_invoice(stub, invoice, db_invoice)
+
+def update_invoice(stub, invoice, db_invoice):
+ if invoice.state == 1:
+ if len(invoice.htlcs) > 0:
+ chan_in_id = invoice.htlcs[0].chan_id
+ alias = Channels.objects.filter(chan_id=chan_in_id)[0].alias if Channels.objects.filter(chan_id=chan_in_id).exists() else None
+ records = invoice.htlcs[0].custom_records
+ keysend_preimage = records[5482373484].hex() if 5482373484 in records else None
+ message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
+ if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
+ signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
+ try:
+ valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
+ except:
+ print('Unable to validate signature on invoice: ' + invoice.r_hash.hex())
+ valid = False
+ sender = records[34349339].hex() if valid == True else None
+ try:
+ sender_alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
+ except:
sender_alias = None
else:
- chan_in_id = None
- alias = None
- keysend_preimage = None
- message = None
sender = None
sender_alias = None
- Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), settle_date=datetime.fromtimestamp(invoice.settle_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, chan_in=chan_in_id, chan_in_alias=alias, keysend_preimage=keysend_preimage, message=message, sender=sender, sender_alias=sender_alias, index=invoice.add_index).save()
else:
- Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, index=invoice.add_index).save()
+ chan_in_id = None
+ alias = None
+ keysend_preimage = None
+ message = None
+ sender = None
+ sender_alias = None
+ db_invoice.state = invoice.state
+ db_invoice.amt_paid = invoice.amt_paid_sat
+ db_invoice.settle_date = datetime.fromtimestamp(invoice.settle_date)
+ db_invoice.chan_in = chan_in_id
+ db_invoice.chan_in_alias = alias
+ db_invoice.keysend_preimage = keysend_preimage
+ db_invoice.message = message
+ db_invoice.sender = sender
+ db_invoice.sender_alias = sender_alias
+ else:
+ db_invoice.state = invoice.state
+ db_invoice.save()
def update_forwards(stub):
records = Forwards.objects.count()
@@ -156,6 +186,7 @@ def update_channels(stub):
if Channels.objects.filter(chan_id=channel.chan_id).exists():
#Update the channel record with the most current data
db_channel = Channels.objects.filter(chan_id=channel.chan_id)[0]
+ pending_channel = None
else:
#Create a record for this new channel
try:
@@ -173,6 +204,7 @@ def update_channels(stub):
db_channel.output_index = index
db_channel.capacity = channel.capacity
db_channel.private = channel.private
+ pending_channel = PendingChannels.objects.filter(funding_txid=txid, output_index=index)[0] if PendingChannels.objects.filter(funding_txid=txid, output_index=index).exists() else None
try:
chan_data = stub.GetChanInfo(ln.ChanInfoRequest(chan_id=channel.chan_id))
if chan_data.node1_pub == channel.remote_pubkey:
@@ -239,6 +271,33 @@ def update_channels(stub):
db_channel.pending_outbound = pending_out
db_channel.pending_inbound = pending_in
db_channel.htlc_count = htlc_counter
+ if pending_channel:
+ if pending_channel.local_base_fee or pending_channel.local_fee_rate or pending_channel.local_cltv:
+ base_fee = pending_channel.local_base_fee if pending_channel.local_base_fee else db_channel.local_base_fee
+ fee_rate = pending_channel.local_fee_rate if pending_channel.local_fee_rate else db_channel.local_fee_rate
+ cltv = pending_channel.local_cltv if pending_channel.local_cltv else db_channel.local_cltv
+ channel_point = ln.ChannelPoint()
+ channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
+ channel_point.funding_txid_str = db_channel.funding_txid
+ channel_point.output_index = int(db_channel.output_index)
+ stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=base_fee, fee_rate=(fee_rate/1000000), time_lock_delta=cltv))
+ db_channel.local_base_fee = base_fee
+ db_channel.local_fee_rate = fee_rate
+ db_channel.local_cltv = cltv
+ db_channel.fees_updated = datetime.now()
+ if pending_channel.auto_rebalance:
+ db_channel.auto_rebalance = pending_channel.auto_rebalance
+ if pending_channel.ar_amt_target:
+ db_channel.ar_amt_target = pending_channel.ar_amt_target
+ if pending_channel.ar_in_target:
+ db_channel.ar_in_target = pending_channel.ar_in_target
+ if pending_channel.ar_out_target:
+ db_channel.ar_out_target = pending_channel.ar_out_target
+ if pending_channel.ar_max_cost:
+ db_channel.ar_max_cost = pending_channel.ar_max_cost
+ if pending_channel.auto_fees:
+ db_channel.auto_fees = pending_channel.auto_fees
+ pending_channel.delete()
db_channel.save()
counter += 1
chan_list.append(channel.chan_id)
@@ -293,6 +352,24 @@ def update_onchain(stub):
for tx in onchain_txs:
Onchain(tx_hash=tx.tx_hash, time_stamp=datetime.fromtimestamp(tx.time_stamp), amount=tx.amount, fee=tx.total_fees, block_hash=tx.block_hash, block_height=tx.block_height, label=tx.label[:100]).save()
+def network_links():
+ if LocalSettings.objects.filter(key='GUI-NetLinks').exists():
+ network_links = str(LocalSettings.objects.filter(key='GUI-NetLinks')[0].value)
+ else:
+ LocalSettings(key='GUI-NetLinks', value='https://mempool.space').save()
+ network_links = 'https://mempool.space'
+ return network_links
+
+def get_tx_fees(txid):
+ base_url = network_links() + ('/testnet' if LND_NETWORK == 'testnet' else '') + '/api/tx/'
+ try:
+ request_data = get(base_url + txid).json()
+ fee = request_data['fee']
+ except Exception as e:
+ print('Error getting closure fees for ', txid, ':', str(e))
+ fee = 0
+ return fee
+
def update_closures(stub):
closures = stub.ClosedChannels(ln.ClosedChannelsRequest()).channels
if len(closures) > Closures.objects.all().count():
@@ -301,8 +378,10 @@ def update_closures(stub):
for closure in closures:
counter += 1
if counter > skip:
+ channel = Channels.objects.filter(chan_id=closure.chan_id)[0] if Channels.objects.filter(chan_id=closure.chan_id).exists() else None
resolution_count = len(closure.resolutions)
txid, index = closure.channel_point.split(':')
+ closing_costs = get_tx_fees(closure.closing_tx_hash) if closure.open_initiator == 1 else 0
db_closure = Closures(chan_id=closure.chan_id, funding_txid=txid, funding_index=index, closing_tx=closure.closing_tx_hash, remote_pubkey=closure.remote_pubkey, capacity=closure.capacity, close_height=closure.close_height, settled_balance=closure.settled_balance, time_locked_balance=closure.time_locked_balance, close_type=closure.close_type, open_initiator=closure.open_initiator, close_initiator=closure.close_initiator, resolution_count=resolution_count)
try:
db_closure.save()
@@ -313,7 +392,12 @@ def update_closures(stub):
if resolution_count > 0:
Resolutions.objects.filter(chan_id=closure.chan_id).delete()
for resolution in closure.resolutions:
+ if resolution.resolution_type != 2:
+ closing_costs += get_tx_fees(resolution.sweep_txid)
Resolutions(chan_id=closure.chan_id, resolution_type=resolution.resolution_type, outcome=resolution.outcome, outpoint_tx=resolution.outpoint.txid_str, outpoint_index=resolution.outpoint.output_index, amount_sat=resolution.amount_sat, sweep_txid=resolution.sweep_txid).save()
+ if channel:
+ channel.closing_costs = closing_costs
+ channel.save()
def reconnect_peers(stub):
inactive_peers = Channels.objects.filter(is_open=True, is_active=False, private=False).values_list('remote_pubkey', flat=True).distinct()
@@ -336,9 +420,16 @@ def reconnect_peers(stub):
print('Unable to find node info on graph, using last known value')
host = peer.address
address = ln.LightningAddress(pubkey=inactive_peer, host=host)
- stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=True, timeout=5))
- peer.last_reconnected = datetime.now()
- peer.save()
+ try:
+ stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=True, timeout=5))
+ peer.last_reconnected = datetime.now()
+ peer.save()
+ except Exception as e:
+ error = str(e)
+ details_index = error.find('details =') + 11
+ debug_error_index = error.find('debug_error_string =') - 3
+ error_msg = error[details_index:debug_error_index]
+ print (f"{datetime.now().strftime('%c')} : Error reconnecting {inactive_peer=} {error_msg=}")
def clean_payments(stub):
if LocalSettings.objects.filter(key='LND-CleanPayments').exists():
@@ -365,11 +456,11 @@ def clean_payments(stub):
details_index = error.find('details =') + 11
debug_error_index = error.find('debug_error_string =') - 3
error_msg = error[details_index:debug_error_index]
- print('Error occured when cleaning payment: ' + payment.payment_hash)
- print('Error: ' + error_msg)
+ print (f"{datetime.now().strftime('%c')} : Error {payment.index=} {payment.status=} {payment.payment_hash=} {error_msg=}")
finally:
payment.cleaned = True
payment.save()
+ print (f"{datetime.now().strftime('%c')} : Cleaned {payment.index=} {payment.status=} {payment.cleaned=} {payment.payment_hash=}")
def auto_fees(stub):
if LocalSettings.objects.filter(key='AF-Enabled').exists():
diff --git a/nginx.sh b/nginx.sh
index 73186b9..cf158be 100644
--- a/nginx.sh
+++ b/nginx.sh
@@ -87,6 +87,8 @@ EOF
function setup_nginx() {
cat << EOF > /etc/nginx/sites-enabled/lndg
+user $INSTALL_USER
+
upstream django {
server unix://$HOME_DIR/lndg/lndg.sock; # for a file socket
}
diff --git a/rebalancer.py b/rebalancer.py
index 04ee4e6..3648fc1 100644
--- a/rebalancer.py
+++ b/rebalancer.py
@@ -21,7 +21,8 @@ def run_rebalancer(rebalance):
unknown_error.save()
auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_can=(((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity'))/Sum('ar_in_target'))
outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).exclude(remote_pubkey=rebalance.last_hop_pubkey).values_list('chan_id', flat=True))
- if len(outbound_cans) == 0:
+ if len(outbound_cans) == 0 and rebalance.manual == False:
+ print ('No outbound_cans')
return None
elif str(outbound_cans).replace('\'', '') != rebalance.outgoing_chan_ids and rebalance.manual == False:
rebalance.outgoing_chan_ids = str(outbound_cans).replace('\'', '')
@@ -34,7 +35,9 @@ def run_rebalancer(rebalance):
chan_ids = json.loads(rebalance.outgoing_chan_ids)
timeout = rebalance.duration * 60
invoice_response = stub.AddInvoice(ln.Invoice(value=rebalance.value, expiry=timeout))
+ #print('Rebalance for:', rebalance.target_alias, ' : ', rebalance.last_hop_pubkey, ' Amount:', rebalance.value, ' Duration:', rebalance.duration, ' via:', chan_ids )
for payment_response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(payment_request=str(invoice_response.payment_request), fee_limit_msat=int(rebalance.fee_limit*1000), outgoing_chan_ids=chan_ids, last_hop_pubkey=bytes.fromhex(rebalance.last_hop_pubkey), timeout_seconds=(timeout-5), allow_self_payment=True), timeout=(timeout+60)):
+ #print ('Payment Response:', payment_response.status, ' Reason:', payment_response.failure_reason, 'Payment Hash :', payment_response.payment_hash )
if payment_response.status == 1 and rebalance.status == 0:
#IN-FLIGHT
rebalance.payment_hash = payment_response.payment_hash
@@ -43,6 +46,7 @@ def run_rebalancer(rebalance):
elif payment_response.status == 2:
#SUCCESSFUL
rebalance.status = 2
+ rebalance.fees_paid = payment_response.fee_msat/1000
successful_out = payment_response.htlcs[0].route.hops[0].pub_key
elif payment_response.status == 3:
#FAILURE
@@ -64,6 +68,7 @@ def run_rebalancer(rebalance):
elif payment_response.status == 0:
rebalance.status = 400
except Exception as e:
+ #print('Exception: ', str(e))
if str(e.code()) == 'StatusCode.DEADLINE_EXCEEDED':
rebalance.status = 408
else:
@@ -113,6 +118,8 @@ def auto_schedule():
if len(auto_rebalance_channels) > 0:
if not LocalSettings.objects.filter(key='AR-Outbound%').exists():
LocalSettings(key='AR-Outbound%', value='75').save()
+ if not LocalSettings.objects.filter(key='AR-Inbound%').exists():
+ LocalSettings(key='AR-Inbound%', value='100').save()
outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True))
inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1)
if len(inbound_cans) > 0 and len(outbound_cans) > 0:
@@ -165,46 +172,61 @@ def auto_enable():
else:
LocalSettings(key='AR-Autopilot', value='0').save()
enabled = 0
+ if LocalSettings.objects.filter(key='AR-APDays').exists():
+ apdays = int(LocalSettings.objects.filter(key='AR-APDays')[0].value)
+ else:
+ LocalSettings(key='AR-APDays', value='7').save()
+ apdays = 7
if enabled == 1:
- channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity'))
- filter_7day = datetime.now() - timedelta(days=7)
- forwards = Forwards.objects.filter(forward_date__gte=filter_7day)
+ lookup_channels=Channels.objects.filter(is_active=True, is_open=True, private=False)
+ channels = lookup_channels.values('remote_pubkey').annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by()
+ filter_day = datetime.now() - timedelta(days=apdays)
+ forwards = Forwards.objects.filter(forward_date__gte=filter_day)
for channel in channels:
- outbound_percent = int(round(channel.outbound_percent/10, 0))
- inbound_percent = int(round(channel.inbound_percent/10, 0))
- routed_in_7day = forwards.filter(chan_id_in=channel.chan_id).count()
- routed_out_7day = forwards.filter(chan_id_out=channel.chan_id).count()
- i7D = 0 if routed_in_7day == 0 else int(forwards.filter(chan_id_in=channel.chan_id).aggregate(Sum('amt_in_msat'))['amt_in_msat__sum']/10000000)/100
- o7D = 0 if routed_out_7day == 0 else int(forwards.filter(chan_id_out=channel.chan_id).aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/10000000)/100
- if o7D > (i7D*1.10) and outbound_percent > 75:
- #print('Case 1: Pass')
- pass
- elif o7D > (i7D*1.10) and inbound_percent > 75 and channel.auto_rebalance == False:
- #print('Case 2: Enable AR - o7D > i7D AND Inbound Liq > 75%')
- channel.auto_rebalance = True
- channel.save()
- Autopilot(chan_id=channel.chan_id, peer_alias=channel.alias, setting='Enabled', old_value=0, new_value=1).save()
- elif o7D < (i7D*1.10) and outbound_percent > 75 and channel.auto_rebalance == True:
- #print('Case 3: Disable AR - o7D < i7D AND Outbound Liq > 75%')
- channel.auto_rebalance = False
- channel.save()
- Autopilot(chan_id=channel.chan_id, peer_alias=channel.alias, setting='Enabled', old_value=1, new_value=0).save()
- elif o7D < (i7D*1.10) and inbound_percent > 75:
- #print('Case 4: Pass')
- pass
- else:
- #print('Case 5: Pass')
- pass
+ outbound_percent = int(round(channel['outbound_percent']/10, 0))
+ inbound_percent = int(round(channel['inbound_percent']/10, 0))
+ chan_list = lookup_channels.filter(remote_pubkey=channel['remote_pubkey']).values('chan_id')
+ routed_in_apday = forwards.filter(chan_id_in__in=chan_list).count()
+ routed_out_apday = forwards.filter(chan_id_out__in=chan_list).count()
+ iapD = 0 if routed_in_apday == 0 else int(forwards.filter(chan_id_in__in=chan_list).aggregate(Sum('amt_in_msat'))['amt_in_msat__sum']/10000000)/100
+ oapD = 0 if routed_out_apday == 0 else int(forwards.filter(chan_id_out__in=chan_list).aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/10000000)/100
+
+ for peer_channel in lookup_channels.filter(chan_id__in=chan_list):
+ #print('Processing: ', peer_channel.alias, ' : ', peer_channel.chan_id, ' : ', oapD, " : ", iapD, ' : ', outbound_percent, ' : ', inbound_percent)
+
+ if oapD > (iapD*1.10) and outbound_percent > 75:
+ #print('Case 1: Pass')
+ pass
+ elif oapD > (iapD*1.10) and inbound_percent > 75 and peer_channel.auto_rebalance == False:
+ #print('Case 2: Enable AR - o7D > i7D AND Inbound Liq > 75%')
+ peer_channel.auto_rebalance = True
+ peer_channel.save()
+ Autopilot(chan_id=peer_channel.chan_id, peer_alias=peer_channel.alias, setting='Enabled', old_value=0, new_value=1).save()
+ print('Auto Pilot Enabled: ', peer_channel.alias, ' : ', peer_channel.chan_id , ' Out: ', oapD, ' In: ', iapD)
+ elif oapD < (iapD*1.10) and outbound_percent > 75 and peer_channel.auto_rebalance == True:
+ #print('Case 3: Disable AR - o7D < i7D AND Outbound Liq > 75%')
+ peer_channel.auto_rebalance = False
+ peer_channel.save()
+ Autopilot(chan_id=peer_channel.chan_id, peer_alias=peer_channel.alias, setting='Enabled', old_value=1, new_value=0).save()
+ print('Auto Pilot Disabled (3): ', peer_channel.alias, ' : ', peer_channel.chan_id, ' Out: ', oapD, ' In: ', iapD )
+ elif oapD < (iapD*1.10) and inbound_percent > 75:
+ #print('Case 4: Pass')
+ pass
+ else:
+ #print('Case 5: Pass')
+ pass
def main():
+
rebalances = Rebalancer.objects.filter(status=0).order_by('id')
if len(rebalances) == 0:
auto_enable()
auto_schedule()
else:
rebalance = rebalances[0]
+ #print('Next Rebalance for:', rebalance.target_alias, ' : ', rebalance.last_hop_pubkey, ' Amount:', rebalance.value, ' Duration:', rebalance.duration )
while rebalance != None:
rebalance = run_rebalancer(rebalance)
if __name__ == '__main__':
- main()
\ No newline at end of file
+ main()
diff --git a/requirements.txt b/requirements.txt
index d8d7fb4..a197baf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,5 @@ django-qr-code
grpcio
protobuf
pytz
-pandas
\ No newline at end of file
+pandas
+requests
\ No newline at end of file