This commit is contained in:
cryptosharks131 2022-01-21 11:43:37 -05:00 committed by GitHub
parent b3c28c9c02
commit ea83dd324a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 981 additions and 166 deletions

3
.gitignore vendored
View file

@ -3,4 +3,5 @@ db.sqlite3
lndg/settings.py
frontend
node_modules
lndg-admin.txt
lndg-admin.txt
.vscode

View file

@ -127,6 +127,31 @@ Alternatively, you may also make your own task for these files with your preferr
You can serve the dashboard at all times using a webserver instead of the development server. Using a webserver will serve your static files and installing whitenoise is not required when running in this manner. Any webserver can be used to host the site if configured properly. A bash script has been included to help aide in the setup of a nginx webserver. `sudo bash nginx.sh`
## Key Features
### Suggests New Peers
LNDg will make suggestions for new peers to open channels to based on your node's successful routing history.
#### There are two unique values in LNDg:
1. Volume Score - A score based upon both the count of transactions and the volume of transactions routed through the peer
2. Savings By Volume (ppm) - The amount of sats you could have saved during rebalances if you were peered directly with this node over the total amount routed through the peer
### Channel Performance Metrics
#### LNDg will aggregate your payment and forwarding data to provide the following metrics:
1. Outbound Flow Details - This shows the amount routed outbound next to the amount rebalanced in
2. Revenue Details - This shows the revenue earned on the left, the profit (revenue - cost) in the middle and the assisted revenue (amount earned due to this channel's inbound flow) on the right
3. Inbound Flow Details - This shows the amount routed inbound next to the amount rebalanced out
4. Updates - This is the number of updates the channel has had and is directly correlated to the space it takes up in channel.db
### Password Protected Login
The initial login username is `lndg-admin` but can be easily modified by going to the page found here: `/lndg-admin`
### Suggests AR Actions
LNDg will make suggestions for actions to take around Auto-Rebalancing.
### AR-Autopilot Setting
LNDg will automatically act upon the suggestions it makes on the Suggests AR Actions page.
### HTLC Failure Stream
LNDg will listen for failure events in your htlc stream and record them to the dashboard when they occur.
### API Backend
The following data can be accessed at the /api endpoint:
`payments` `paymenthops` `invoices` `forwards` `onchain` `peers` `channels` `rebalancer` `settings` `pendinghtlcs` `failedhtlcs`
@ -134,17 +159,8 @@ The following data can be accessed at the /api endpoint:
### Peer Reconnection
LNDg will automatically try to resolve any channels that are seen as inactive, no more than every 3 minutes per peer.
### Suggests New Peers
LNDg will make suggestions for new peers to open channels to based on your node's successful routing history.
### Suggests AR Actions
LNDg will make suggestions for actions to take around Auto-Rebalancing.
### HTLC Failure Stream
LNDg will listen for failure events in your htlc stream and record them to the dashboard when they occur.
### Auto-Rebalancer
Here are some notes to help you get started using the Auto-Rebalancer (AR).
## Auto-Rebalancer
### Here are some notes to help you get started using the Auto-Rebalancer (AR).
The objective of the Auto-Rebalancer is to "refill" the liquidity on the local side (i.e. OUTBOUND) of profitable and lucarative channels. So that, when a forward comes in from another node there is always enough liquidity to route the payment and in return collect the desired routing fees.
@ -179,10 +195,10 @@ The objective of the Auto-Rebalancer is to "refill" the liquidity on the local s
```
Enabled: 1
Target Amount (%): 0.03
Target Time (min): 5
Target Time (min): 3
Target Outbound Above (%): 0.4
Global Max Fee Rate (ppm): 200
Max Cost (%): 0.5
Global Max Fee Rate (ppm): 500
Max Cost (%): 0.6
```
3. Go to section Last 10 Rebalance Requests - that will show the list of the rebalancing queue and status.
@ -197,7 +213,8 @@ If you want a channel not to be picked for rebalancing (i.e. it is already full
![image](https://user-images.githubusercontent.com/38626122/148699286-0b1d2c13-191a-4c6c-99ae-ce3d8b8ac64d.png)
![image](https://user-images.githubusercontent.com/38626122/137809583-db743233-25c1-4d3e-aaec-2a7767de2c9f.png)
### Peers, Balances, Routes, Keysends and Pending HTLCs All Open In Separate Screens
### Channel Performance, Peers, Balances, Routes, Keysends and Pending HTLCs All Open In Separate Screens
![image](https://user-images.githubusercontent.com/38626122/150556928-bb8772fb-14c4-4b7a-865e-a8350aac7f83.png)
![image](https://user-images.githubusercontent.com/38626122/137809809-1ed40cfb-9d12-447a-8e5e-82ae79605895.png)
![image](https://user-images.githubusercontent.com/38626122/137810021-4f69dcb0-5fce-4062-bc49-e75f5dd0feda.png)
![image](https://user-images.githubusercontent.com/38626122/137809882-4a87f86d-290c-456e-9606-ed669fd98561.png)

View file

@ -70,7 +70,22 @@ class AutoRebalanceForm(forms.Form):
fee_rate = forms.IntegerField(label='fee_rate', required=False)
outbound_percent = forms.FloatField(label='outbound_percent', required=False)
max_cost = forms.FloatField(label='max_cost', required=False)
autopilot = forms.IntegerField(label='autopilot', required=False)
class ARTarget(forms.Form):
updates_channel_codes = [
(0, 'base_fee'),
(1, 'fee_rate'),
(2, 'ar_amt_target'),
(3, 'ar_in_target'),
(4, 'ar_out_target'),
(5, 'ar_enabled'),
]
class UpdateChannel(forms.Form):
chan_id = forms.IntegerField(label='chan_id')
ar_target = forms.IntegerField(label='ar_target')
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)

View file

@ -0,0 +1,53 @@
# Generated by Django 3.2.7 on 2022-01-14 22:18
from django.db import migrations, models
from django.db.models import F, IntegerField
from django.db.models.functions import Round
def update_defaults(apps, schedma_editor):
channels = apps.get_model('gui', 'channels')
settings = apps.get_model('gui', 'localsettings')
try:
if settings.objects.filter(key='AR-Target%').exists():
ar_amt_target = float(settings.objects.filter(key='AR-Target%')[0].value)
else:
ar_amt_target = 0.05
channels.objects.all().update(ar_amt_target=Round(F('capacity')*ar_amt_target, output_field=IntegerField()))
except Exception as e:
print('Migration step failed:', str(e))
try:
if settings.objects.filter(key='AR-Outbound%').exists():
ar_out_target = float(settings.objects.filter(key='AR-Outbound%')[0].value)
else:
ar_out_target = 0.75
channels.objects.all().update(ar_out_target=ar_out_target*100)
except Exception as e:
print('Migration step failed:', str(e))
def revert_defaults(apps, schedma_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('gui', '0017_autopilot'),
]
operations = [
migrations.RenameField(
model_name='channels',
old_name='ar_target',
new_name='ar_in_target',
),
migrations.AddField(
model_name='channels',
name='ar_amt_target',
field=models.BigIntegerField(default=100000),
),
migrations.AddField(
model_name='channels',
name='ar_out_target',
field=models.IntegerField(default=75),
),
migrations.RunPython(update_defaults, revert_defaults),
]

View file

@ -79,7 +79,27 @@ class Channels(models.Model):
is_active = models.BooleanField()
is_open = models.BooleanField()
auto_rebalance = models.BooleanField(default=False)
ar_target = models.IntegerField(default=100)
ar_amt_target = models.BigIntegerField(default=100000)
ar_in_target = models.IntegerField(default=100)
ar_out_target = models.IntegerField(default=75)
def save(self, *args, **kwargs):
if not self.ar_out_target:
if LocalSettings.objects.filter(key='AR-Outbound%').exists():
outbound_setting = float(LocalSettings.objects.filter(key='AR-Outbound%')[0].value)
else:
LocalSettings(key='AR-Outbound%', value='0.75').save()
outbound_setting = 0.75
self.ar_out_target = int(outbound_setting * 100)
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)
else:
LocalSettings(key='AR-Target%', value='0.05').save()
amt_setting = 0.05
self.ar_amt_target = int(amt_setting * self.capacity)
super(Channels, self).save(*args, **kwargs)
class Meta:
app_label = 'gui'

View file

@ -40,6 +40,7 @@ class ChannelSerializer(serializers.HyperlinkedModelSerializer):
remote_fee_rate = serializers.ReadOnlyField()
is_active = serializers.ReadOnlyField()
is_open = serializers.ReadOnlyField()
num_updates = serializers.ReadOnlyField()
class Meta:
model = Channels
exclude = []

View file

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Actions{% endblock %}
{% block content %}
{% load humanize %}
{% if action_list %}
@ -24,8 +25,8 @@
</tr>
{% for channel in action_list %}
<tr>
<td>{{ channel.chan_id }}</a></td>
<td>{{ channel.alias }}</a></td>
<td title="{{ channel.funding_txid }}:{{ channel.output_index }}"><a href="{{ network_links }}/{{ network }}tx/{{ channel.funding_txid }}" target="_blank">{{ channel.chan_id }}</a></td>
<td title="{{ channel.remote_pubkey }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_pubkey }}" target="_blank">{{ channel.alias }}</a></td>
<td>{{ channel.capacity|intcomma }}</td>
<td>{{ channel.local_balance|intcomma }} ({{ channel.outbound_percent }}%)</td>
<td><div class="w3-orange w3-round">{% if channel.inbound_percent == 0 %}<div class="w3-container w3-round w3-blue" style="height:16px;width:100%"></div>{% elif channel.outbound_percent == 0 %}<div class="w3-container w3-round w3-orange" style="height:16px;width:100%"></div>{% else %}<div class="w3-container w3-round w3-blue" style="height:16px;width:{{ channel.outbound_percent }}%"></div>{% endif %}</div></td>
@ -38,19 +39,13 @@
<td>{{ channel.remote_fee_rate|intcomma }}</td>
<td>{{ channel.remote_base_fee|intcomma }}</td>
<td>
{% if channel.auto_rebalance == True %}
<form action="/autorebalance/" method="post">
<form action="/update_channel/" method="post">
{% csrf_token %}
<input type="submit" value="Disable">
<input type="submit" value="{% if channel.auto_rebalance == True %}Disable{% else %}Enable{% endif %}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="5">
<input type="hidden" name="target" value="0">
</form>
{% else %}
<form action="/autorebalance/" method="post">
{% csrf_token %}
<input type="submit" value="Enable">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
</form>
{% endif %}
</td>
<td title="{{ channel.reason }}">{{ channel.output }}</td>
</tr>

131
gui/templates/advanced.html Normal file
View file

@ -0,0 +1,131 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Advanced{% endblock %}
{% block content %}
{% load humanize %}
{% if channels %}
<div class="w3-container w3-padding-small">
<h2>Advanced Channel Settings</h2>
<div class="w3-container w3-padding-small" style="overflow-x: scroll">
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Channel ID</th>
<th>Peer Alias</th>
<th>Capacity</th>
<th>Outbound Liquidity</th>
<th width=10%></th>
<th>Inbound Liquidity</th>
<th>oRate</th>
<th>oBase</th>
<th>iRate</th>
<th>iBase</th>
<th title="When AR is ENABLED for the channel, the size of the rebalance attempts that should be tried during attempts to refill the channel.">Target Amt</th>
<th title="When AR is NOT ENABLED for the channel, keep pushing OUT the channel until its outbound liquidity falls below the oTarget%.">oTarget%</th>
<th title="When AR is ENABLED for the channel, keep pulling IN to the channel until its inbound liquidity falls below the iTarget%.">iTarget%</th>
<th title="When AR is ENABLED it will refill the channel with outbound liquidity.">AR</th>
<th>Active</th>
</tr>
{% for channel in channels %}
<tr>
<td title="{{ channel.funding_txid }}:{{ channel.output_index }}"><a href="{{ network_links }}/{{ network }}tx/{{ channel.funding_txid }}" target="_blank">{{ channel.chan_id }}</a></td>
<td title="{{ channel.remote_pubkey }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_pubkey }}" target="_blank">{{ channel.alias }}</a></td>
<td>{{ channel.capacity|intcomma }}</td>
<td>{{ channel.local_balance|intcomma }} ({{ channel.out_percent }}%)</td>
<td><div class="w3-orange w3-round">{% if channel.in_percent == 0 %}<div class="w3-container w3-round w3-blue" style="height:16px;width:100%"></div>{% elif channel.out_percent == 0 %}<div class="w3-container w3-round w3-orange" style="height:16px;width:100%"></div>{% else %}<div class="w3-container w3-round w3-blue" style="height:16px;width:{{ channel.out_percent }}%"></div>{% endif %}</div></td>
<td>{{ channel.remote_balance|intcomma }} ({{ channel.in_percent }}%)</td>
<td>
<form action="/update_channel/" method="post">
{% csrf_token %}
<input style="text-align:center" id="target" type="number" min="0" max="100000" name="target" value="{{ channel.local_fee_rate }}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="1">
</form>
</td>
<td>
<form action="/update_channel/" method="post">
{% csrf_token %}
<input style="text-align:center" id="target" type="number" min="0" max="100000" name="target" value="{{ channel.local_base_fee }}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="0">
</form>
</td>
<td>{{ channel.remote_fee_rate|intcomma }}</td>
<td>{{ channel.remote_base_fee|intcomma }}</td>
<td>
<form action="/update_channel/" method="post">
{% csrf_token %}
<input style="text-align:center" id="target" type="number" min="0" max="100000000" name="target" value="{{ channel.ar_amt_target }}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="2">
</form>
</td>
<td {% if channel.auto_rebalance == False %}style="background-color: #80ced6"{% else %}style="background-color: #f18973"{% endif %}>
<form action="/update_channel/" method="post">
{% csrf_token %}
<input style="text-align:center" id="target" type="number" min="0" max="100" name="target" value="{{ channel.ar_out_target }}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="4">
</form>
</td>
<td {% if channel.auto_rebalance == True %}style="background-color: #80ced6"{% else %}style="background-color: #f18973"{% endif %}>
<form action="/update_channel/" method="post">
{% csrf_token %}
<input style="text-align:center" id="target" type="number" min="0" max="100" name="target" value="{{ channel.ar_in_target }}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="3">
</form>
</td>
<td>
<form action="/update_channel/" method="post">
{% csrf_token %}
<input type="submit" value="{% if channel.auto_rebalance == True %}Disable{% else %}Enable{% endif %}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="5">
<input type="hidden" name="target" value="0">
</form>
</td>
<td>{{ channel.is_active }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endif %}
{% if not channels %}
<div class="w3-container w3-padding-small">
<center><h1>You dont have any channels to setup yet!</h1></center>
</div>
{% endif %}
{% if local_settings %}
<div class="w3-container w3-padding-small">
<h2>Update Local Settings</h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
{% for settings in local_settings %}
<tr>
<td>{{ settings.key }}</td>
<td>
<form action="/update_setting/" method="post">
{% csrf_token %}
{% if settings.key|slice:"-1:" == '%' %}
<input style="text-align:center" id="value" type="number" step="0.01" min="0" max="1" name="value" value="{{ settings.value }}">
{% elif settings.key == 'AR-Time' %}
<input style="text-align:center" id="value" type="number" min="0" max="60" name="value" value="{{ settings.value }}">
{% elif settings.key == 'AR-MaxFeeRate' %}
<input style="text-align:center" id="value" type="number" min="0" max="2500" name="value" value="{{ settings.value }}">
{% elif settings.key|slice:":3" == 'AR-' %}
<input style="text-align:center" id="value" type="number" min="0" max="1" name="value" value="{{ settings.value }}">
{% else %}
<input style="text-align:center" id="value" type="text" name="value" value="{{ settings.value }}">
{% endif %}
<input type="hidden" name="key" value="{{ settings.key }}">
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% endblock %}

View file

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Autopilot{% endblock %}
{% block content %}
{% load humanize %}
{% if autopilot %}
@ -29,7 +30,7 @@
{% if not autopilot %}
<div class="w3-container w3-padding-small">
<center><h1>No autopilot logs to see here yet!</h1></center>
<center><h6>Experimental. Advanced users may activate via api.</h6></center>
<center><h6>Experimental. This will allow LNDg to automatically act upon the suggestions found <a href="/suggested_actions" target="_blank">here</a>.</h6></center>
</div>
{% endif %}
{% endblock %}

View file

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Balances{% endblock %}
{% block content %}
{% load humanize %}
{% if utxos %}
@ -13,9 +14,9 @@
</tr>
{% for uxto in utxos %}
<tr>
<td>{{ uxto.address }}</td>
<td><a href="{{ network_links }}/{{ network }}address/{{ uxto.address }}" target="_blank">{{ uxto.address }}</a></td>
<td>{{ uxto.amount_sat|intcomma }}</td>
<td>{{ uxto.outpoint.txid_str }}</td>
<td><a href="{{ network_links }}/{{ network }}tx/{{ uxto.outpoint.txid_str }}" target="_blank">{{ uxto.outpoint.txid_str }}</a></td>
<td>{{ uxto.confirmations|intcomma }}</td>
</tr>
{% endfor %}
@ -35,9 +36,9 @@
</tr>
{% for transaction in transactions %}
<tr>
<td>{{ transaction.tx_hash }}</td>
<td><a href="{{ network_links }}/{{ network }}tx/{{ transaction.tx_hash }}" target="_blank">{{ transaction.tx_hash }}</a></td>
<td>{{ transaction.amount }}</td>
<td>{{ transaction.block_height }}</td>
<td>{% if transaction.block_height == 0 %}{{ transaction.block_height }}{% else %}<a href="{{ network_links }}/{{ network }}block/{{ transaction.block_height }}" target="_blank">{{ transaction.block_height }}</a>{% endif %}</td>
<td>{{ transaction.fee }}</td>
<td>{{ transaction.label }}</td>
</tr>
@ -45,4 +46,9 @@
</table>
</div>
{% endif %}
{% if not utxos and not transactions %}
<div class="w3-container w3-padding-small">
<center><h1>No wallet transactions found!</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -4,7 +4,7 @@
</style>
<head>
<meta charset="utf-8">
<title>LNDg</title>
<title>{% block title %}LNDg{% endblock %}</title>
{% load static %}
{% load qr_code %}
<link href="{% static 'w3style.css' %}" rel="stylesheet" type="text/css">
@ -28,7 +28,7 @@
<footer>
<div id="footer">
<div class="w3-container w3-padding-small">
<center>LNDg v1.0.1</center>
<center>LNDg v1.0.2</center>
</div>
</div>
</footer>

View file

@ -0,0 +1,65 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Channels{% endblock %}
{% block content %}
{% load humanize %}
{% if channels %}
<div class="w3-container w3-padding-small">
<h2>Channel Performance</h2>
<div class="w3-container w3-padding-small" style="overflow-x: scroll">
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th></th>
<th></th>
<th></th>
<th colspan="3">7-Day Activity And Revenue </th>
<th colspan="3">30-Day Activity And Revenue </th>
<th colspan="2">Local Fees</th>
<th colspan="2">Peer Fees</th>
<th colspan="2"> Channel Health</th>
</tr>
<tr>
<th width=1%>Channel ID</th>
<th>Peer Alias</th>
<th width=1%>Capacity</th>
<th title="Routed Out | Rebalanced In">Outbound Flow</th>
<th title="Revenue Out [Net Profits] | Assisted Revenue In">Out [Profit] | In</th>
<th title="Routed In | Rebalanced Out">Inbound Flow</th>
<th title="Routed Out | Rebalanced In">Outbound Flow</th>
<th title="Revenue Out [Net Profits] | Assisted Revenue In">Out [Profit] | In</th>
<th title="Routed In | Rebalanced Out">Inbound Flow</th>
<th width=1%>Rate</th>
<th width=1%>Base</th>
<th width=1%>Rate</th>
<th width=1%>Base</th>
<th title="The count of updates is directly correlated to the space the channel takes up in your channel database." width=1%>Updates</th>
<th title="The opener of the channel usually pays the closing costs during a co-operative close." width=1%>Opener</th>
</tr>
{% for channel in channels %}
<tr>
<td title="{{ channel.funding_txid }}:{{ channel.output_index }}"><a href="{{ network_links }}/{{ network }}tx/{{ channel.funding_txid }}" target="_blank">{{ channel.chan_id }}</a></td>
<td title="{{ channel.remote_pubkey }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_pubkey }}" target="_blank">{{ channel.alias }}</a></td>
<td>{{ channel.capacity }} M</td>
<td>{{ channel.amt_routed_out_7day|intcomma }} M ({{ channel.routed_out_7day }}) | {{ channel.amt_rebal_in_7day|intcomma }} M ({{ channel.rebal_in_7day }})</td>
<td>{{ channel.revenue_7day|intcomma }} [{{ channel.profits_7day|intcomma }}] | {{ channel.revenue_assist_7day|intcomma }}</td>
<td>{{ channel.amt_routed_in_7day|intcomma }} M ({{ channel.routed_in_7day }}) | {{ channel.amt_rebal_out_7day|intcomma }} M ({{ channel.rebal_out_7day }})</td>
<td>{{ channel.amt_routed_out_30day|intcomma }} M ({{ channel.routed_out_30day }}) | {{ channel.amt_rebal_in_30day|intcomma }} M ({{ channel.rebal_in_30day }})</td>
<td>{{ channel.revenue_30day|intcomma }} [{{ channel.profits_30day|intcomma }}] | {{ channel.revenue_assist_30day|intcomma }}</td>
<td>{{ channel.amt_routed_in_30day|intcomma }} M ({{ channel.routed_in_30day }}) | {{ channel.amt_rebal_out_30day|intcomma }} M ({{ channel.rebal_out_30day }})</td>
<td>{{ channel.local_fee_rate|intcomma }}</td>
<td>{{ channel.local_base_fee|intcomma }}</td>
<td>{{ channel.remote_fee_rate|intcomma }}</td>
<td>{{ channel.remote_base_fee|intcomma }}</td>
<td>{{ channel.num_updates|intcomma }}</td>
<td title="Block Opened: {{ channel.open_block }}">{{ channel.initiator }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endif %}
{% if not channels %}
<div class="w3-container w3-padding-small">
<center><h1>You dont have any channels to analyze yet!</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Failed HTLCs{% endblock %}
{% block content %}
{% load humanize %}
{% if failed_htlcs %}
<div class="w3-container w3-padding-small">
<h2>Last 150 Failed HTLCs</h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Timestamp</th>
<th>Chan In ID</th>
<th>Chan Out ID</th>
<th>Chan In Alias</th>
<th>Chan Out Alias</th>
<th>Forward Amount</th>
<th>Potential Fee</th>
<th>HTLC Failure</th>
<th>Failure Detail</th>
</tr>
{% for failed_htlc in failed_htlcs %}
<tr>
<td title="{{ failed_htlc.timestamp }}">{{ failed_htlc.timestamp|naturaltime }}</td>
<td>{{ failed_htlc.chan_id_in }}</td>
<td>{{ failed_htlc.chan_id_out }}</td>
<td>{{ failed_htlc.chan_in_alias }}</td>
<td>{{ failed_htlc.chan_out_alias }}</td>
<td>{{ failed_htlc.amount|intcomma }}</td>
<td>{{ failed_htlc.missed_fee|intcomma }}</td>
<td>{% if failed_htlc.wire_failure == 15 %}Temporary Channel Failure{% elif failed_htlc.wire_failure == 18 %}Unknown Next Peer{% elif failed_htlc.wire_failure == 12 %}Fee Insufficient{% else %}{{ failed_htlc.wire_failure }}{% endif %}</td>
<td>{% if failed_htlc.failure_detail == 1 %}---{% elif failed_htlc.failure_detail == 5 %}HTLC Exceeds Max{% elif failed_htlc.failure_detail == 6 %}Insufficient Balance{% elif failed_htlc.failure_detail == 20 %}Invalid Keysend{% elif failed_htlc.failure_detail == 22 %}Circular Route{% else %}{{ failed_htlc.failure_detail }}{% endif %}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% if not failed_htlcs %}
<div class="w3-container w3-padding-small">
<center><h1>You dont have any failed HTLCs yet.</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Forwards{% endblock %}
{% block content %}
{% load humanize %}
{% if forwards %}
<div class="w3-container w3-padding-small">
<h2>Last 150 Forwards</h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Timestamp</th>
<th>Amount In</th>
<th>Amount Out</th>
<th>Channel In Alias</th>
<th>Channel Out Alias</th>
<th>Channel In Id</th>
<th>Channel Out Id</th>
<th>Fees Earned</th>
<th>PPM Earned</th>
</tr>
{% for forward in forwards %}
<tr>
<td title="{{ forward.forward_date }}">{{ forward.forward_date|naturaltime }}</td>
<td>{{ forward.amt_in|intcomma }}</td>
<td>{{ forward.amt_out|intcomma }}</td>
<td>{{ forward.chan_in_alias }}</td>
<td>{{ forward.chan_out_alias }}</td>
<td>{{ forward.chan_id_in }}</td>
<td>{{ forward.chan_id_out }}</td>
<td>{{ forward.fee }}</td>
<td>{{ forward.ppm|intcomma }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% if not forwards %}
<div class="w3-container w3-padding-small">
<center><h1>You dont have any forwards yet.</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -1,8 +1,9 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Dashboard{% endblock %}
{% block content %}
{% load humanize %}
<div class="w3-container w3-padding-small">
<h3><a href="https://1ml.com/{{ network }}node/{{ node_info.identity_pubkey }}" target="_blank">{{ node_info.alias }}</a> | {{ node_info.identity_pubkey }}</h3>
<h3><a href="{{ graph_links }}/{{ network }}node/{{ node_info.identity_pubkey }}" target="_blank">{{ node_info.alias }}</a> | {{ node_info.identity_pubkey }}</h3>
<h4>Capacity: {{ capacity|intcomma }} | Active Channels: {{ node_info.num_active_channels }} / {{ total_channels }} | <a href="/peers" target="_blank">Peers</a>: {{ node_info.num_peers }} | DB Size: {{ db_size }} GB</h4>
<h4>Public Address: {% for info in node_info.uris %}{{ info }} | {% endfor %}</h4>
<h4>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 }}</h4>
@ -23,7 +24,7 @@
<div class="w3-container w3-padding-small">
<h4>Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%</h4>
<h4>Balance In Limbo: {{ limbo_balance|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }} | <a href="/pending_htlcs" target="_blank">Pending HTLCs</a>: {{ pending_htlc_count }}</h4>
<h4><a href="/suggested_opens" target="_blank">Suggested New Peers</a> | <a href="/suggested_actions" target="_blank">Suggested AR Actions</a> | <a href="/autopilot" target="_blank">Autopilot Logs</a></h4>
<h4><a href="/suggested_opens" target="_blank">Suggested New Peers</a> | <a href="/suggested_actions" target="_blank">Suggested AR Actions</a> | <a href="/autopilot" target="_blank">Autopilot Logs</a> | <a href="/channels" target="_blank">Channel Performance</a> | <a href="/advanced" target="_blank">Advanced Settings</a></h4>
</div>
{% if active_channels %}
<div class="w3-container w3-padding-small">
@ -46,13 +47,13 @@
<th>iLife</th>
<th>iRate</th>
<th>iBase</th>
<th width=4%>iTarget%</th>
<th width=4%>AR</th>
<th title="When AR is ENABLED for the channel, keep pulling IN to the channel until its inbound liquidity falls below the iTarget%." width=4%>iTarget%</th>
<th title="When AR is ENABLED it will refill the channel with outbound liquidity." width=4%>AR</th>
</tr>
{% for channel in active_channels %}
<tr>
<td title="{{ channel.funding_txid }}:{{ channel.output_index }}"><a href="https://mempool.space/{{ network }}tx/{{ channel.funding_txid }}" target="_blank">{{ channel.chan_id }}</a></td>
<td title="{{ channel.remote_pubkey }}"><a href="https://1ml.com/{{ network }}node/{{ channel.remote_pubkey }}" target="_blank">{{ channel.alias }}</a></td>
<td title="{{ channel.funding_txid }}:{{ channel.output_index }}"><a href="{{ network_links }}/{{ network }}tx/{{ channel.funding_txid }}" target="_blank">{{ channel.chan_id }}</a></td>
<td title="{{ channel.remote_pubkey }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_pubkey }}" target="_blank">{{ channel.alias }}</a></td>
<td>{{ channel.capacity|intcomma }}</td>
<td>{{ channel.local_balance|intcomma }} ({{ channel.outbound_percent }}%)</td>
<td><div class="w3-orange w3-round">{% if channel.inbound_percent == 0 %}<div class="w3-container w3-round w3-blue" style="height:16px;width:100%"></div>{% elif channel.outbound_percent == 0 %}<div class="w3-container w3-round w3-orange" style="height:16px;width:100%"></div>{% else %}<div class="w3-container w3-round w3-blue" style="height:16px;width:{{ channel.outbound_percent }}%"></div>{% endif %}</div></td>
@ -68,29 +69,24 @@
<td>{{ channel.remote_base_fee|intcomma }}</td>
<td>
{% if channel.auto_rebalance == True %}
<form action="/ar_target/" method="post">
<form action="/update_channel/" method="post">
{% csrf_token %}
<input style="text-align:center" id="ar_target" type="number" min="0" max="100" name="ar_target" value="{{ channel.ar_target }}">
<input style="text-align:center" id="target" type="number" min="0" max="100" name="target" value="{{ channel.ar_in_target }}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="3">
</form>
{% else %}
---
{% endif %}
</td>
<td>
{% if channel.auto_rebalance == True %}
<form action="/autorebalance/" method="post">
{% csrf_token %}
<input type="submit" value="Disable">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
</form>
{% else %}
<form action="/autorebalance/" method="post">
{% csrf_token %}
<input type="submit" value="Enable">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
</form>
{% endif %}
<form action="/update_channel/" method="post">
{% csrf_token %}
<input type="submit" value="{% if channel.auto_rebalance == True %}Disable{% else %}Enable{% endif %}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="5">
<input type="hidden" name="target" value="0">
</form>
</td>
</tr>
{% endfor %}
@ -122,8 +118,8 @@
</tr>
{% for channel in inactive_channels %}
<tr>
<td title="{{ channel.funding_txid }}:{{ channel.output_index }}"><a href="https://mempool.space/{{ network }}tx/{{ channel.funding_txid }}" target="_blank">{{ channel.chan_id }}</a></td>
<td title="{{ channel.remote_pubkey }}"><a href="https://1ml.com/{{ network }}node/{{ channel.remote_pubkey }}" target="_blank">{{ channel.alias }}</a></td>
<td title="{{ channel.funding_txid }}:{{ channel.output_index }}"><a href="{{ network_links }}/{{ network }}tx/{{ channel.funding_txid }}" target="_blank">{{ channel.chan_id }}</a></td>
<td title="{{ channel.remote_pubkey }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_pubkey }}" target="_blank">{{ channel.alias }}</a></td>
<td>{{ channel.capacity|intcomma }}</td>
<td>{{ channel.local_balance|intcomma }}</td>
<td><div class="w3-orange w3-round">{% if channel.inbound_percent == 0 %}<div class="w3-container w3-round w3-blue" style="height:16px;width:100%"></div>{% elif channel.outbound_percent == 0 %}<div class="w3-container w3-round w3-orange" style="height:16px;width:100%"></div>{% else %}<div class="w3-container w3-round w3-blue" style="height:16px;width:{{ channel.outbound_percent }}%"></div>{% endif %}</div></td>
@ -138,29 +134,24 @@
<td>{{ channel.initiator }}</td>
<td>
{% if channel.auto_rebalance == True %}
<form action="/ar_target/" method="post">
<form action="/update_channel/" method="post">
{% csrf_token %}
<input style="text-align:center" id="ar_target" type="number" min="0" max="100" name="ar_target" value="{{ channel.ar_target }}">
<input style="text-align:center" id="target" type="number" min="0" max="100" name="target" value="{{ channel.ar_in_target }}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="3">
</form>
{% else %}
---
{% endif %}
</td>
<td>
{% if channel.auto_rebalance == True %}
<form action="/autorebalance/" method="post">
{% csrf_token %}
<input type="submit" value="Disable">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
</form>
{% else %}
<form action="/autorebalance/" method="post">
{% csrf_token %}
<input type="submit" value="Enable">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
</form>
{% endif %}
<form action="/update_channel/" method="post">
{% csrf_token %}
<input type="submit" value="{% if channel.auto_rebalance == True %}Disable{% else %}Enable{% endif %}">
<input type="hidden" name="chan_id" value="{{ channel.chan_id }}">
<input type="hidden" name="update_target" value="5">
<input type="hidden" name="target" value="0">
</form>
</td>
</tr>
{% endfor %}
@ -182,9 +173,9 @@
{% for channel in pending_open %}
<tr>
{% with pending_funding_txid=channel.channel.channel_point|slice:":-2" %}
<td><a href='https://mempool.space/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
<td><a href='{{ network_links }}/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
{% endwith %}
<td><a href="https://1ml.com/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td><a href="{{ graph_links }}/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td>{{ channel.channel.capacity|intcomma }}</td>
<td>{{ channel.channel.local_balance|intcomma }}</td>
<td>{{ channel.channel.remote_balance|intcomma }}</td>
@ -210,9 +201,9 @@
{% for channel in pending_closed %}
<tr>
{% with pending_funding_txid=channel.channel.channel_point|slice:":-2" %}
<td><a href='https://mempool.space/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
<td><a href='{{ network_links }}/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
{% endwith %}
<td><a href="https://1ml.com/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td><a href="{{ graph_links }}/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td>{{ channel.channel.capacity|intcomma }}</td>
<td>{{ channel.channel.local_balance|intcomma }}</td>
<td>{{ channel.channel.remote_balance|intcomma }}</td>
@ -240,15 +231,15 @@
{% for channel in pending_force_closed %}
<tr>
{% with pending_funding_txid=channel.channel.channel_point|slice:":-2" %}
<td><a href='https://mempool.space/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
<td><a href='{{ network_links }}/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
{% endwith %}
<td><a href="https://1ml.com/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td><a href="{{ graph_links }}/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td>{{ channel.channel.capacity|intcomma }}</td>
<td>{{ channel.channel.local_balance|intcomma }}</td>
<td>{{ channel.channel.remote_balance|intcomma }}</td>
<td>{{ channel.limbo_balance|intcomma }}</td>
<td>{{ channel.blocks_til_maturity|intcomma }}</td>
<td><a href='https://mempool.space/{{ network }}tx/{{ channel.closing_txid }}' target="_blank">{{ channel.closing_txid }}</a></td>
<td><a href='{{ network_links }}/{{ network }}tx/{{ channel.closing_txid }}' target="_blank">{{ channel.closing_txid }}</a></td>
</tr>
{% endfor %}
</table>
@ -270,9 +261,9 @@
{% for channel in waiting_for_close %}
<tr>
{% with pending_funding_txid=channel.channel.channel_point|slice:":-2" %}
<td><a href='https://mempool.space/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
<td><a href='{{ network_links }}/{{ network }}tx/{{ pending_funding_txid }}' target="_blank">{{ channel.channel.channel_point }}</a></td>
{% endwith %}
<td><a href="https://1ml.com/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td><a href="{{ graph_links }}/{{ network }}node/{{ channel.channel.remote_node_pub }}" target="_blank">{{ channel.channel.remote_node_pub }}</a></td>
<td>{{ channel.channel.capacity|intcomma }}</td>
<td>{{ channel.channel.local_balance|intcomma }}</td>
<td>{{ channel.channel.remote_balance|intcomma }}</td>
@ -285,7 +276,7 @@
{% endif %}
{% if forwards %}
<div class="w3-container w3-padding-small">
<h2>Last 10 Payments Routed</h2>
<h2>Last 10 <a href="/forwards" target="_blank">Payments Routed</a></h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Timestamp</th>
@ -337,7 +328,7 @@
<td>{{ rebalance.value|intcomma }}</td>
<td>{{ rebalance.fee_limit|intcomma }}</td>
<td>{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}</td>
<td>{% 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 %}</td>
<td title="{{ rebalance.status }}">{% 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 %}</td>
</tr>
{% endfor %}
</table>
@ -345,7 +336,7 @@
{% endif %}
{% if payments %}
<div class="w3-container w3-padding-small">
<h2>Last 5 Payments Sent</h2>
<h2>Last 5 <a href="/payments" target="_blank">Payments Sent</a></h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Timestamp</th>
@ -376,7 +367,7 @@
{% endif %}
{% if invoices %}
<div class="w3-container w3-padding-small">
<h2>Last 5 Payments Received</h2>
<h2>Last 5 <a href="/invoices" target="_blank">Payments Received</a></h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Created</th>
@ -407,7 +398,7 @@
{% endif %}
{% if failed_htlcs %}
<div class="w3-container w3-padding-small">
<h2>Last 10 Failed HTLCs</h2>
<h2>Last 10 <a href="/failed_htlcs" target="_blank">Failed HTLCs</a></h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Timestamp</th>
@ -467,9 +458,11 @@
<label for="outbound_percent">Target Outbound Above (%): </label>
<input id="outbound_percent" type="number" step="0.01" min="0" max="1" name="outbound_percent">
<label for="fee_rate">Global Max Fee Rate (ppm): </label>
<input id="fee_rate" type="number" min="0" max="1500" name="fee_rate">
<input id="fee_rate" type="number" min="0" max="2500" name="fee_rate">
<label for="max_cost">Max Cost (%): </label>
<input id="max_cost" type="number" step="0.01" min="0" max="1" name="max_cost">
<label for="autopilot">Autopilot: </label>
<input id="autopilot" type="number" min="0" max="1" name="autopilot">
<input type="submit" value="OK">
</form>
</div>
@ -581,4 +574,4 @@
</form>
</div>
</div>
{% endblock %}
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Invoices{% endblock %}
{% block content %}
{% load humanize %}
{% if invoices %}
<div class="w3-container w3-padding-small">
<h2>Last 150 Invoices</h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Created</th>
<th>Settled</th>
<th width=25%>Payment Hash</th>
<th>Value</th>
<th>Amount Paid</th>
<th>State</th>
<th>Channel In Alias</th>
<th width=10%>Channel In</th>
<th><a href="/keysends" target="_blank">Keysend</a></th>
</tr>
{% for invoice in invoices %}
<tr>
<td title="{{ invoice.creation_date }}">{{ invoice.creation_date|naturaltime }}</td>
<td title="{{ invoice.settle_date }}">{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %}</td>
<td>{{ invoice.r_hash }}</td>
<td>{{ invoice.value|add:"0"|intcomma }}</td>
<td>{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}N/A{% endif %}</td>
<td>{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}</td>
<td>{{ invoice.chan_in_alias }}</td>
<td>{{ invoice.chan_in }}</td>
<td title="{{ invoice.message }}">{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% if not invoices %}
<div class="w3-container w3-padding-small">
<center><h1>You dont have any invoices yet.</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Keysends{% endblock %}
{% block content %}
{% load humanize %}
{% if keysends %}

View file

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Opens{% endblock %}
{% block content %}
{% load humanize %}
{% if open_list %}
@ -11,13 +12,13 @@
<th width=15%>Successful Payments Routed</th>
<th width=12%>Amount Routed</th>
<th width=12%>Fees Paid</th>
<th width=12%>Effective PPM</th>
<th width=12%>Volume Score</th>
<th width=12%>Savings By Volume</th>
<th title="Total fees paid to the peer over total routed through the peer." width=12%>Effective PPM</th>
<th title="A score factoring in both number of payments and volume of payments routed through the peer." width=12%>Volume Score</th>
<th title="The amount you could have potentially saved on rebalancing payments if you had been peered directly with this node over total volume routed through the peer." width=12%>Savings By Volume</th>
</tr>
{% for node in open_list %}
<tr>
<td>{{ node.node_pubkey }}</td>
<td><a href="{{ graph_links }}/{{ network }}node/{{ node.node_pubkey }}" target="_blank">{{ node.node_pubkey }}</a></td>
<td>{{ node.alias }}</td>
<td>{{ node.count }}</td>
<td>{{ node.amount|add:"0"|intcomma }}</td>
@ -30,4 +31,9 @@
</table>
</div>
{% endif %}
{% if not open_list %}
<div class="w3-container w3-padding-small">
<center><h1>No potential peers can be calculated yet, try waiting until you have some payment data.</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Payments{% endblock %}
{% block content %}
{% load humanize %}
{% if payments %}
<div class="w3-container w3-padding-small">
<h2>Last 150 Payments</h2>
<table class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Timestamp</th>
<th width=28%>Hash</th>
<th width=7%>Value</th>
<th width=7%>Fee Paid</th>
<th width=7%>Status</th>
<th width=12%>Chan Out Alias</th>
<th width=12%>Chan Out ID</th>
<th width=5%>Route</th>
<th width=8%>Keysend</th>
</tr>
{% for payment in payments %}
<tr>
<td title="{{ payment.creation_date }}">{{ payment.creation_date|naturaltime }}</td>
<td>{{ payment.payment_hash }}</td>
<td>{{ payment.value|add:"0"|intcomma }}</td>
<td>{{ payment.fee|intcomma }}</td>
<td>{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %}</td>
<td>{% if payment.status == 2 %}{{ payment.chan_out_alias }}{% else %}N/A{% endif %}</td>
<td>{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %}</td>
<td>{% if payment.status == 2 %}<a href="/route?={{ payment.payment_hash }}" target="_blank">Open</a>{% else %}N/A{% endif %}</td>
<td title="{{ payment.message }}">{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% if not payments %}
<div class="w3-container w3-padding-small">
<center><h1>You dont have any payments yet.</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Peers{% endblock %}
{% block content %}
{% load humanize %}
{% if peers %}
@ -14,7 +15,7 @@
</tr>
{% for peer in peers %}
<tr>
<td><a href="https://1ml.com/node/{{ peer.pubkey }}" target="_blank">{{ peer.pubkey }}</a></td>
<td><a href="{{ graph_links }}/{{ network }}node/{{ peer.pubkey }}" target="_blank">{{ peer.pubkey }}</a></td>
<td>{{ peer.address }}</td>
<td>{{ peer.inbound }}</td>
<td>{{ peer.sat_sent|intcomma }}</td>
@ -24,4 +25,9 @@
</table>
</div>
{% endif %}
{% if not peers %}
<div class="w3-container w3-padding-small">
<center><h1>No connected peers found!</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -1,4 +1,5 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - HTLCs{% endblock %}
{% block content %}
{% load humanize %}
{% if outgoing_htlcs %}
@ -55,4 +56,9 @@
</table>
</div>
{% endif %}
{% if not outgoing_htlcs and not incoming_htlcs %}
<div class="w3-container w3-padding-small">
<center><h1>No pending HTLCs were found!</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -1,6 +1,8 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Routes{% endblock %}
{% block content %}
{% load humanize %}
{% if route %}
<div class="w3-container w3-padding-small">
<h2>Route For Payment: {{ payment_hash }}</h2>
<table class="w3-table-all w3-centered w3-hoverable">
@ -8,22 +10,30 @@
<th>Step</th>
<th>Amount</th>
<th>Fee</th>
<th title="The fee in PPM paid on this hop.">PPM</th>
<th title="The cost to get the payment to this hop of the payment.">Cost To</th>
<th>Alias</th>
<th>Channel ID</th>
<th>Channel Capacity</th>
<th>Cost To</th>
</tr>
{% for hop in route %}
<tr>
<td>{{ hop.step }}</td>
<td>{{ hop.amt }}</td>
<td>{{ hop.fee }}</td>
<td>{{ hop.ppm }}</td>
<td>{{ hop.cost_to }}</td>
<td>{{ hop.alias }}</td>
<td>{{ hop.chan_id }}</td>
<td>{{ hop.chan_capacity|intcomma }}</td>
<td>{{ hop.cost_to }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{% if not route %}
<div class="w3-container w3-padding-small">
<center><h1>A route was not found for this payment hash!</h1></center>
</div>
{% endif %}
{% endblock %}

View file

@ -22,6 +22,10 @@ urlpatterns = [
path('peers', views.peers, name='peers'),
path('balances', views.balances, name='balances'),
path('pending_htlcs', views.pending_htlcs, name='pending-htlcs'),
path('failed_htlcs', views.failed_htlcs, name='failed-htlcs'),
path('payments', views.payments, name='payments'),
path('invoices', views.invoices, name='invoices'),
path('forwards', views.forwards, name='forwards'),
path('openchannel/', views.open_channel_form, name='open-channel-form'),
path('closechannel/', views.close_channel_form, name='close-channel-form'),
path('connectpeer/', views.connect_peer_form, name='connect-peer-form'),
@ -30,11 +34,14 @@ urlpatterns = [
path('rebalancer/', views.rebalance, name='rebalancer'),
path('updatechanpolicy/', views.update_chan_policy, name='updatechanpolicy'),
path('autorebalance/', views.auto_rebalance, name='auto-rebalance'),
path('ar_target/', views.ar_target, name='ar-target'),
path('update_channel/', views.update_channel, name='update-channel'),
path('update_setting/', views.update_setting, name='update-setting'),
path('suggested_opens/', views.suggested_opens, name='suggested-opens'),
path('suggested_actions/', views.suggested_actions, name='suggested-actions'),
path('keysends/', views.keysends, name='keysends'),
path('channels/', views.channels, name='channels'),
path('autopilot/', views.autopilot, name='autopilot'),
path('advanced/', views.advanced, name='advanced'),
path('api/', include(router.urls), name='api-root'),
path('api-auth/', include('rest_framework.urls'), name='api-auth'),
path('api/connectpeer/', views.connect_peer, name='connect-peer'),

View file

@ -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, Count
from django.db.models import Sum, IntegerField, Count, F
from django.db.models.functions import Round
from django.contrib.auth.decorators import login_required
from django.conf import settings
@ -8,7 +8,7 @@ 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, AutoRebalanceForm, ARTarget
from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm
from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, PendingHTLCs, FailedHTLCs, Autopilot
from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, PendingHTLCs, FailedHTLCs
from .lnd_deps import lightning_pb2 as ln
@ -16,6 +16,23 @@ from .lnd_deps import lightning_pb2_grpc as lnrpc
from .lnd_deps.lnd_connect import lnd_connect
from lndg.settings import LND_NETWORK, LND_DIR_PATH
from os import path
from pandas import DataFrame
def graph_links():
if LocalSettings.objects.filter(key='GUI-GraphLinks').exists():
graph_links = str(LocalSettings.objects.filter(key='GUI-GraphLinks')[0].value)
else:
LocalSettings(key='GUI-GraphLinks', value='https://1ml.com').save()
graph_links = 'https://1ml.com'
return graph_links
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
@login_required(login_url='/lndg-admin/login/?next=/')
def home(request):
@ -41,26 +58,36 @@ def home(request):
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')
total_forwards = forwards.count()
total_value_forwards = 0 if total_forwards == 0 else int(forwards.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
total_earned = 0 if total_forwards == 0 else forwards.aggregate(Sum('fee'))['fee__sum']
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.objects.filter(is_active=True, is_open=True).annotate(outbound_percent=(Sum('local_balance')*1000)/Sum('capacity')).annotate(inbound_percent=(Sum('remote_balance')*1000)/Sum('capacity')).order_by('outbound_percent')
total_capacity = 0 if active_channels.count() == 0 else active_channels.aggregate(Sum('capacity'))['capacity__sum']
total_inbound = 0 if total_capacity == 0 else active_channels.aggregate(Sum('remote_balance'))['remote_balance__sum']
total_outbound = 0 if total_capacity == 0 else active_channels.aggregate(Sum('local_balance'))['local_balance__sum']
total_unsettled = 0 if total_capacity == 0 else active_channels.aggregate(Sum('unsettled_balance'))['unsettled_balance__sum']
detailed_active_channels = []
filter_7day = datetime.now() - timedelta(days=7)
routed_7day = forwards.filter(forward_date__gte=filter_7day).count()
routed_7day_amt = 0 if routed_7day == 0 else int(forwards.filter(forward_date__gte=filter_7day).aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
total_earned_7day = 0 if routed_7day == 0 else forwards.filter(forward_date__gte=filter_7day).aggregate(Sum('fee'))['fee__sum']
forwards_df_7d = DataFrame.from_records(forwards.filter(forward_date__gte=filter_7day).values())
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()
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)
total_earned_7day = 0 if routed_7day == 0 else forwards_df_7d['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']
total_7day_fees = 0 if payments_7day.count() == 0 else payments_7day.aggregate(Sum('fee'))['fee__sum']
pending_htlcs = PendingHTLCs.objects.all()
pending_htlc_count = pending_htlcs.count()
pending_outbound = 0 if pending_htlcs.filter(incoming=False).count() == 0 else pending_htlcs.filter(incoming=False).aggregate(Sum('amount'))['amount__sum']
detailed_active_channels = []
for channel in active_channels:
detailed_channel = {}
detailed_channel['remote_pubkey'] = channel.remote_pubkey
@ -79,17 +106,17 @@ 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.filter(chan_id_in=channel.chan_id).count()
detailed_channel['routed_out'] = forwards.filter(chan_id_out=channel.chan_id).count()
detailed_channel['amt_routed_in'] = 0 if detailed_channel['routed_in'] == 0 else int(forwards.filter(chan_id_in=channel.chan_id).aggregate(Sum('amt_in_msat'))['amt_in_msat__sum']/10000000)/100
detailed_channel['amt_routed_out'] = 0 if detailed_channel['routed_out'] == 0 else int(forwards.filter(chan_id_out=channel.chan_id).aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/10000000)/100
detailed_channel['routed_in_7day'] = forwards.filter(forward_date__gte=filter_7day).filter(chan_id_in=channel.chan_id).count()
detailed_channel['routed_out_7day'] = forwards.filter(forward_date__gte=filter_7day).filter(chan_id_out=channel.chan_id).count()
detailed_channel['amt_routed_in_7day'] = 0 if detailed_channel['routed_in_7day'] == 0 else int(forwards.filter(forward_date__gte=filter_7day).filter(chan_id_in=channel.chan_id).aggregate(Sum('amt_in_msat'))['amt_in_msat__sum']/10000000)/100
detailed_channel['amt_routed_out_7day'] = 0 if detailed_channel['routed_out_7day'] == 0 else int(forwards.filter(forward_date__gte=filter_7day).filter(chan_id_out=channel.chan_id).aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/10000000)/100
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['htlc_count'] = pending_htlcs.filter(chan_id=channel.chan_id).count()
detailed_channel['auto_rebalance'] = channel.auto_rebalance
detailed_channel['ar_target'] = channel.ar_target
detailed_channel['ar_in_target'] = channel.ar_in_target
detailed_active_channels.append(detailed_channel)
#Get current inactive channels
inactive_channels = Channels.objects.filter(is_active=False, is_open=True).annotate(outbound_percent=(Sum('local_balance')*100)/Sum('capacity')).annotate(inbound_percent=(Sum('remote_balance')*100)/Sum('capacity')).order_by('-local_fee_rate').order_by('outbound_percent')
@ -103,7 +130,7 @@ def home(request):
#Get list of recent rebalance requests
rebalances = Rebalancer.objects.all().order_by('-requested')
total_channels = node_info.num_active_channels + node_info.num_inactive_channels
local_settings = LocalSettings.objects.all()
local_settings = LocalSettings.objects.filter(key__contains='AR-')
try:
db_size = round(path.getsize(path.expanduser(LND_DIR_PATH + '/data/graph/' + LND_NETWORK + '/channel.db'))*0.000000001, 3)
except:
@ -120,7 +147,7 @@ def home(request):
'invoices': invoices[:6],
'total_received': total_received,
'total_invoices': total_invoices,
'forwards': forwards[:15],
'forwards': forwards_df.head(15).to_dict(orient='records'),
'earned': int(total_earned),
'total_forwards': total_forwards,
'total_value_forwards': total_value_forwards,
@ -158,19 +185,111 @@ def home(request):
'7day_payments_ppm': 0 if payments_7day_amt == 0 else int((total_7day_fees/payments_7day_amt)*1000000),
'liq_ratio': 0 if total_outbound == 0 else int((total_inbound/sum_outbound)*100),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links(),
'db_size': db_size
}
return render(request, 'home.html', context)
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def channels(request):
if request.method == 'GET':
filter_7day = datetime.now() - timedelta(days=7)
filter_30day = datetime.now() - timedelta(days=30)
forwards = Forwards.objects.filter(forward_date__gte=filter_30day)
payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_30day).filter(payment_hash__in=Invoices.objects.filter(state=1).filter(settle_date__gte=filter_30day).values_list('r_hash'))
invoices = Invoices.objects.filter(state=1).filter(settle_date__gte=filter_30day).filter(r_hash__in=payments.values_list('payment_hash'))
channels = Channels.objects.filter(is_open=True)
channels_df = DataFrame.from_records(channels.values())
if channels_df.shape[0] > 0:
forwards_df_30d = DataFrame.from_records(forwards.values())
forwards_df_7d = DataFrame.from_records(forwards.filter(forward_date__gte=filter_7day).values())
forwards_df_in_30d_sum = DataFrame() if forwards_df_30d.empty else forwards_df_30d.groupby('chan_id_in', as_index=True).sum()
forwards_df_out_30d_sum = DataFrame() if forwards_df_30d.empty else forwards_df_30d.groupby('chan_id_out', as_index=True).sum()
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_30d_count = DataFrame() if forwards_df_30d.empty else forwards_df_30d.groupby('chan_id_in', as_index=True).count()
forwards_df_out_30d_count = DataFrame() if forwards_df_30d.empty else forwards_df_30d.groupby('chan_id_out', as_index=True).count()
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()
payments_df_30d = DataFrame.from_records(payments.values())
payments_df_7d = DataFrame.from_records(payments.filter(creation_date__gte=filter_7day).values())
payments_df_30d_sum = DataFrame() if payments_df_30d.empty else payments_df_30d.groupby('chan_out', as_index=True).sum()
payments_df_7d_sum = DataFrame() if payments_df_7d.empty else payments_df_7d.groupby('chan_out', as_index=True).sum()
payments_df_30d_count = DataFrame() if payments_df_30d.empty else payments_df_30d.groupby('chan_out', as_index=True).count()
payments_df_7d_count = DataFrame() if payments_df_7d.empty else payments_df_7d.groupby('chan_out', as_index=True).count()
invoices_df_30d = DataFrame.from_records(invoices.values())
invoices_df_7d = DataFrame.from_records(invoices.filter(settle_date__gte=filter_7day).values())
invoices_df_30d_sum = DataFrame() if invoices_df_30d.empty else invoices_df_30d.groupby('chan_in', as_index=True).sum()
invoices_df_7d_sum = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True).sum()
invoices_df_30d_count = DataFrame() if invoices_df_30d.empty else invoices_df_30d.groupby('chan_in', as_index=True).count()
invoices_df_7d_count = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True).count()
invoice_hashes_7d = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
invoice_hashes_30d = DataFrame() if invoices_df_30d.empty else invoices_df_30d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
channels_df['capacity'] = channels_df.apply(lambda row: round(row.capacity/1000000, 1), axis=1)
channels_df['routed_in_7day'] = channels_df.apply(lambda row: forwards_df_in_7d_count.loc[row.chan_id].amt_out_msat if (forwards_df_in_7d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['routed_out_7day'] = channels_df.apply(lambda row: forwards_df_out_7d_count.loc[row.chan_id].amt_out_msat if (forwards_df_out_7d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['routed_in_30day'] = channels_df.apply(lambda row: forwards_df_in_30d_count.loc[row.chan_id].amt_out_msat if (forwards_df_in_30d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['routed_out_30day'] = channels_df.apply(lambda row: forwards_df_out_30d_count.loc[row.chan_id].amt_out_msat if (forwards_df_out_30d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_routed_in_7day'] = channels_df.apply(lambda row: int(forwards_df_in_7d_sum.loc[row.chan_id].amt_out_msat/100000000)/10 if (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_routed_out_7day'] = channels_df.apply(lambda row: int(forwards_df_out_7d_sum.loc[row.chan_id].amt_out_msat/100000000)/10 if (forwards_df_out_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_routed_in_30day'] = channels_df.apply(lambda row: int(forwards_df_in_30d_sum.loc[row.chan_id].amt_out_msat/100000000)/10 if (forwards_df_in_30d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_routed_out_30day'] = channels_df.apply(lambda row: int(forwards_df_out_30d_sum.loc[row.chan_id].amt_out_msat/100000000)/10 if (forwards_df_out_30d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['rebal_in_30day'] = channels_df.apply(lambda row: invoices_df_30d_count.loc[row.chan_id].amt_paid if invoices_df_30d_count.empty == False and (invoices_df_30d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['rebal_out_30day'] = channels_df.apply(lambda row: payments_df_30d_count.loc[row.chan_id].value if payments_df_30d_count.empty == False and (payments_df_30d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_rebal_in_30day'] = channels_df.apply(lambda row: int(invoices_df_30d_sum.loc[row.chan_id].amt_paid/100000)/10 if invoices_df_30d_count.empty == False and (invoices_df_30d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_rebal_out_30day'] = channels_df.apply(lambda row: int(payments_df_30d_sum.loc[row.chan_id].value/100000)/10 if payments_df_30d_count.empty == False and (payments_df_30d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['rebal_in_7day'] = channels_df.apply(lambda row: invoices_df_7d_count.loc[row.chan_id].amt_paid if invoices_df_7d_count.empty == False and (invoices_df_7d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['rebal_out_7day'] = channels_df.apply(lambda row: payments_df_7d_count.loc[row.chan_id].value if payments_df_7d_count.empty == False and (payments_df_7d_count.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_rebal_in_7day'] = channels_df.apply(lambda row: int(invoices_df_7d_sum.loc[row.chan_id].amt_paid/100000)/10 if invoices_df_7d_count.empty == False and (invoices_df_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['amt_rebal_out_7day'] = channels_df.apply(lambda row: int(payments_df_7d_sum.loc[row.chan_id].value/100000)/10 if payments_df_7d_count.empty == False and (payments_df_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['revenue_7day'] = channels_df.apply(lambda row: int(forwards_df_out_7d_sum.loc[row.chan_id].fee) if forwards_df_out_7d_sum.empty == False and (forwards_df_out_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['revenue_30day'] = channels_df.apply(lambda row: int(forwards_df_out_30d_sum.loc[row.chan_id].fee) if forwards_df_out_30d_sum.empty == False and (forwards_df_out_30d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['revenue_assist_7day'] = channels_df.apply(lambda row: int(forwards_df_in_7d_sum.loc[row.chan_id].fee) if forwards_df_in_7d_sum.empty == False and (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['revenue_assist_30day'] = channels_df.apply(lambda row: int(forwards_df_in_30d_sum.loc[row.chan_id].fee) if forwards_df_in_30d_sum.empty == False and (forwards_df_in_30d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['costs_7day'] = channels_df.apply(lambda row: 0 if row['rebal_in_7day'] == 0 else int(payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[row.chan_id] if invoice_hashes_7d.empty == False and (invoice_hashes_7d.index == row.chan_id).any() else []]['fee'].sum()), axis=1)
channels_df['costs_30day'] = channels_df.apply(lambda row: 0 if row['rebal_in_30day'] == 0 else int(payments_df_30d.set_index('payment_hash', inplace=False).loc[invoice_hashes_30d[row.chan_id] if invoice_hashes_30d.empty == False and (invoice_hashes_30d.index == row.chan_id).any() else []]['fee'].sum()), axis=1)
channels_df['profits_7day'] = channels_df.apply(lambda row: 0 if row['revenue_7day'] == 0 else row['revenue_7day'] - row['costs_7day'], axis=1)
channels_df['profits_30day'] = channels_df.apply(lambda row: 0 if row['revenue_30day'] == 0 else row['revenue_30day'] - row['costs_30day'], axis=1)
channels_df['open_block'] = channels_df.apply(lambda row: row.chan_id>>40, axis=1)
context = {
'channels': channels_df.to_dict(orient='records'),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
}
return render(request, 'channels.html', context)
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def advanced(request):
if request.method == 'GET':
channels = Channels.objects.filter(is_open=True).annotate(outbound_percent=(Sum('local_balance')*1000)/Sum('capacity')).annotate(inbound_percent=(Sum('remote_balance')*1000)/Sum('capacity')).order_by('-is_active', 'outbound_percent')
channels_df = DataFrame.from_records(channels.values())
if channels_df.shape[0] > 0:
channels_df['out_percent'] = channels_df.apply(lambda row: int(round(row['outbound_percent']/10, 0)), axis=1)
channels_df['in_percent'] = channels_df.apply(lambda row: int(round(row['inbound_percent']/10, 0)), axis=1)
context = {
'channels': channels_df.to_dict(orient='records'),
'local_settings': LocalSettings.objects.all(),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
}
return render(request, 'advanced.html', context)
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def route(request):
if request.method == 'GET':
payment_hash = request.GET.urlencode()[1:]
context = {
'payment_hash': payment_hash,
'route': PaymentHops.objects.filter(payment_hash=payment_hash)
'route': PaymentHops.objects.filter(payment_hash=payment_hash).annotate(ppm=Round((Sum('fee')/Sum('amt'))*1000000, output_field=IntegerField()))
}
return render(request, 'route.html', context)
else:
@ -180,7 +299,9 @@ def route(request):
def peers(request):
if request.method == 'GET':
context = {
'peers': Peers.objects.filter(connected=True)
'peers': Peers.objects.filter(connected=True),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
return render(request, 'peers.html', context)
else:
@ -192,7 +313,9 @@ def balances(request):
stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
context = {
'utxos': stub.ListUnspent(ln.ListUnspentRequest(min_confs=0, max_confs=9999999)).utxos,
'transactions': list(Onchain.objects.filter(block_height=0)) + list(Onchain.objects.exclude(block_height=0).order_by('-block_height'))
'transactions': list(Onchain.objects.filter(block_height=0)) + list(Onchain.objects.exclude(block_height=0).order_by('-block_height')),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'network_links': network_links()
}
return render(request, 'balances.html', context)
else:
@ -208,7 +331,9 @@ def suggested_opens(request):
payments_60day = Payments.objects.filter(creation_date__gte=filter_60day).values_list('payment_hash')
open_list = PaymentHops.objects.filter(payment_hash__in=payments_60day).exclude(node_pubkey=self_pubkey).exclude(node_pubkey__in=current_peers).values('node_pubkey', 'alias').annotate(ppm=(Sum('fee')/Sum('amt'))*1000000).annotate(score=Round((Round(Count('id')/5, output_field=IntegerField())+Round(Sum('amt')/500000, output_field=IntegerField()))/10, output_field=IntegerField())).annotate(count=Count('id')).annotate(amount=Sum('amt')).annotate(fees=Sum('fee')).annotate(sum_cost_to=Sum('cost_to')/(Sum('amt')/1000000)).order_by('-score', 'ppm')[:21]
context = {
'open_list': open_list
'open_list': open_list,
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
return render(request, 'open_list.html', context)
else:
@ -240,33 +365,36 @@ def suggested_actions(request):
result['i7D'] = 0 if result['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
result['o7D'] = 0 if result['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
result['auto_rebalance'] = channel.auto_rebalance
result['ar_target'] = channel.ar_target
result['ar_target'] = channel.ar_in_target
if result['o7D'] > (result['i7D']*1.10) and result['outbound_percent'] > 75:
print('Case 1: Pass')
#print('Case 1: Pass')
continue
elif result['o7D'] > (result['i7D']*1.10) and result['inbound_percent'] > 75 and channel.auto_rebalance == False:
if channel.local_fee_rate <= channel.remote_fee_rate:
print('Case 6: Peer Fee Too High')
#print('Case 6: Peer Fee Too High')
result['output'] = 'Peer Fee Too High'
result['reason'] = 'o7D > i7D AND Inbound Liq > 75% AND Local Fee < Remote Fee'
continue
print('Case 2: Enable AR')
#print('Case 2: Enable AR')
result['output'] = 'Enable AR'
result['reason'] = 'o7D > i7D AND Inbound Liq > 75%'
elif result['o7D'] < (result['i7D']*1.10) and result['outbound_percent'] > 75 and channel.auto_rebalance == True:
print('Case 3: Disable AR')
#print('Case 3: Disable AR')
result['output'] = 'Disable AR'
result['reason'] = 'o7D < i7D AND Outbound Liq > 75%'
elif result['o7D'] < (result['i7D']*1.10) and result['inbound_percent'] > 75:
print('Case 4: Pass')
#print('Case 4: Pass')
continue
else:
print('Case 5: Pass')
#print('Case 5: Pass')
continue
if len(result) > 0:
action_list.append(result)
context = {
'action_list': action_list
'action_list': action_list,
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
}
return render(request, 'action_list.html', context)
else:
@ -283,6 +411,47 @@ def pending_htlcs(request):
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def failed_htlcs(request):
if request.method == 'GET':
context = {
'failed_htlcs': FailedHTLCs.objects.all().order_by('-timestamp')[:150],
}
return render(request, 'failed_htlcs.html', context)
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def payments(request):
if request.method == 'GET':
context = {
'payments': Payments.objects.filter(status=2).order_by('-creation_date')[:150],
}
return render(request, 'payments.html', context)
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def invoices(request):
if request.method == 'GET':
context = {
'invoices': Invoices.objects.filter(state=1).order_by('-creation_date')[:150],
}
return render(request, 'invoices.html', context)
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def forwards(request):
if request.method == 'GET':
context = {
'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')[:150],
}
return render(request, 'forwards.html', context)
else:
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def keysends(request):
if request.method == 'GET':
@ -447,11 +616,11 @@ def rebalance(request):
form = RebalancerForm(request.POST)
if form.is_valid():
try:
if Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey']).exists():
if Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey']).exists() or form.cleaned_data['last_hop_pubkey'] == '':
chan_ids = []
for channel in form.cleaned_data['outgoing_chan_ids']:
chan_ids.append(channel.chan_id)
target_alias = Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey'])[0].alias if Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey']).exists() else None
target_alias = Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey'])[0].alias if Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey']).exists() else ''
Rebalancer(value=form.cleaned_data['value'], fee_limit=form.cleaned_data['fee_limit'], outgoing_chan_ids=chan_ids, last_hop_pubkey=form.cleaned_data['last_hop_pubkey'], target_alias=target_alias, duration=form.cleaned_data['duration']).save()
messages.success(request, 'Rebalancer request created!')
else:
@ -494,7 +663,6 @@ def update_chan_policy(request):
messages.error(request, 'Error updating channel policies! Error: ' + error_msg)
else:
messages.error(request, 'Invalid Request. Please try again.')
print(form.errors)
return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
@ -521,7 +689,8 @@ def auto_rebalance(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 rebalance target percent setting to: ' + str(target_percent))
Channels.objects.all().update(ar_amt_target=Round(F('capacity')*target_percent, output_field=IntegerField()))
messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
if form.cleaned_data['target_time'] is not None:
target_time = form.cleaned_data['target_time']
try:
@ -551,7 +720,8 @@ def auto_rebalance(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 to: ' + str(outbound_percent))
Channels.objects.all().update(ar_out_target=(round(outbound_percent*100, 0)))
messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
if form.cleaned_data['fee_rate'] is not None:
fee_rate = form.cleaned_data['fee_rate']
try:
@ -572,24 +742,173 @@ def auto_rebalance(request):
db_max_cost.value = max_cost
db_max_cost.save()
messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost))
if form.cleaned_data['autopilot'] is not None:
autopilot = form.cleaned_data['autopilot']
try:
db_autopilot = LocalSettings.objects.get(key='AR-Autopilot')
except:
LocalSettings(key='AR-Autopilot', value='0').save()
db_autopilot = LocalSettings.objects.get(key='AR-Autopilot')
db_autopilot.value = autopilot
db_autopilot.save()
messages.success(request, 'Updated autopilot setting to: ' + str(autopilot))
else:
messages.error(request, 'Invalid Request. Please try again.')
return redirect('home')
return redirect(request.META.get('HTTP_REFERER'))
@login_required(login_url='/lndg-admin/login/?next=/')
def ar_target(request):
def update_channel(request):
if request.method == 'POST':
form = ARTarget(request.POST)
form = UpdateChannel(request.POST)
if form.is_valid() and Channels.objects.filter(chan_id=form.cleaned_data['chan_id']).exists():
chan_id = form.cleaned_data['chan_id']
target = form.cleaned_data['ar_target']
target = form.cleaned_data['target']
update_target = int(form.cleaned_data['update_target'])
db_channel = Channels.objects.filter(chan_id=chan_id)[0]
db_channel.ar_target = target
db_channel.save()
messages.success(request, 'Auto rebalancer inbound target for channel ' + str(chan_id) + ' updated to a value of: ' + str(target) + '%')
if update_target == 0:
stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
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 = db_channel.output_index
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=target, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=40))
db_channel.local_base_fee = target
db_channel.save()
messages.success(request, 'Base fee for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
elif update_target == 1:
stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
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 = db_channel.output_index
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(target/1000000), time_lock_delta=40))
db_channel.local_fee_rate = target
db_channel.save()
messages.success(request, 'Fee rate for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
elif update_target == 2:
db_channel.ar_amt_target = target
db_channel.save()
messages.success(request, 'Auto rebalancer target amount for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
elif update_target == 3:
db_channel.ar_in_target = target
db_channel.save()
messages.success(request, 'Auto rebalancer inbound target for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target) + '%')
elif update_target == 4:
db_channel.ar_out_target = target
db_channel.save()
messages.success(request, 'Auto rebalancer outbound target for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target) + '%')
elif update_target == 5:
db_channel.auto_rebalance = True if db_channel.auto_rebalance == False else False
db_channel.save()
messages.success(request, 'Auto rebalancer status for chanel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.auto_rebalance))
else:
messages.error(request, 'Invalid Request. Please try again.')
return redirect('home')
return redirect(request.META.get('HTTP_REFERER'))
@login_required(login_url='/lndg-admin/login/?next=/')
def update_setting(request):
if request.method == 'POST':
form = UpdateSetting(request.POST)
if form.is_valid():
key = form.cleaned_data['key']
value = form.cleaned_data['value']
if key == 'AR-Target%':
target_percent = value
try:
db_percent_target = LocalSettings.objects.get(key='AR-Target%')
except:
LocalSettings(key='AR-Target%', value='0.05').save()
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, output_field=IntegerField()))
messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
elif key == 'AR-Time':
target_time = int(value)
try:
db_time_target = LocalSettings.objects.get(key='AR-Time')
except:
LocalSettings(key='AR-Time', value='5').save()
db_time_target = LocalSettings.objects.get(key='AR-Time')
db_time_target.value = target_time
db_time_target.save()
messages.success(request, 'Updated auto rebalancer target time setting to: ' + str(target_time))
elif key == 'AR-Enabled':
enabled = int(value)
try:
db_enabled = LocalSettings.objects.get(key='AR-Enabled')
except:
LocalSettings(key='AR-Enabled', value='0').save()
db_enabled = LocalSettings.objects.get(key='AR-Enabled')
db_enabled.value = enabled
db_enabled.save()
messages.success(request, 'Updated auto rebalancer enabled setting to: ' + str(enabled))
elif key == 'AR-Outbound%':
outbound_percent = float(value)
try:
db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%')
except:
LocalSettings(key='AR-Outbound%', value='0.75').save()
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=(round(outbound_percent*100, 0)))
messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
elif key == 'AR-MaxFeeRate':
fee_rate = int(value)
try:
db_fee_rate = LocalSettings.objects.get(key='AR-MaxFeeRate')
except:
LocalSettings(key='AR-MaxFeeRate', value='100').save()
db_fee_rate = LocalSettings.objects.get(key='AR-MaxFeeRate')
db_fee_rate.value = fee_rate
db_fee_rate.save()
messages.success(request, 'Updated auto rebalancer max fee rate setting to: ' + str(fee_rate))
elif key == 'AR-MaxCost%':
max_cost = float(value)
try:
db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%')
except:
LocalSettings(key='AR-MaxCost%', value='0.50').save()
db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%')
db_max_cost.value = max_cost
db_max_cost.save()
messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost))
elif key == 'AR-Autopilot':
autopilot = int(value)
try:
db_autopilot = LocalSettings.objects.get(key='AR-Autopilot')
except:
LocalSettings(key='AR-Autopilot', value='0').save()
db_autopilot = LocalSettings.objects.get(key='AR-Autopilot')
db_autopilot.value = autopilot
db_autopilot.save()
messages.success(request, 'Updated autopilot setting to: ' + str(autopilot))
elif key == 'GUI-GraphLinks':
links = str(value)
try:
db_links = LocalSettings.objects.get(key='GUI-GraphLinks')
except:
LocalSettings(key='GUI-GraphLinks', value='0').save()
db_links = LocalSettings.objects.get(key='GUI-GraphLinks')
db_links.value = links
db_links.save()
messages.success(request, 'Updated graph links to use: ' + str(links))
elif key == 'GUI-NetLinks':
links = str(value)
try:
db_links = LocalSettings.objects.get(key='GUI-NetLinks')
except:
LocalSettings(key='GUI-NetLinks', value='0').save()
db_links = LocalSettings.objects.get(key='GUI-NetLinks')
db_links.value = links
db_links.save()
messages.success(request, 'Updated network links to use: ' + str(links))
else:
messages.error(request, 'Invalid Request. Please try again.')
else:
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
class PaymentsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Payments.objects.all()
@ -659,7 +978,6 @@ class RebalancerViewSet(viewsets.ReadOnlyModelViewSet):
serializer.save()
return Response(serializer.data)
else:
print(serializer.errors)
return Response(serializer.errors)
@api_view(['POST'])

View file

@ -1,5 +1,5 @@
import django, json, datetime
from django.db.models import Sum
from django.db.models import Sum, F
from datetime import datetime, timedelta
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
@ -74,21 +74,15 @@ def auto_schedule():
LocalSettings(key='AR-Enabled', value='0').save()
enabled = 0
if enabled == 1:
auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True).annotate(percent_outbound=(Sum('local_balance')*100)/Sum('capacity')).annotate(inbound_can=((Sum('remote_balance')*100)/Sum('capacity'))/Sum('ar_target'))
auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True).annotate(percent_outbound=(Sum('local_balance')*100)/Sum('capacity')).annotate(inbound_can=((Sum('remote_balance')*100)/Sum('capacity'))/Sum('ar_in_target'))
if len(auto_rebalance_channels) > 0:
if LocalSettings.objects.filter(key='AR-Outbound%').exists():
outbound_percent = int(float(LocalSettings.objects.filter(key='AR-Outbound%')[0].value) * 100)
else:
if not LocalSettings.objects.filter(key='AR-Outbound%').exists():
LocalSettings(key='AR-Outbound%', value='0.75').save()
outbound_percent = 0.75 * 100
outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=outbound_percent).values_list('chan_id', flat=True))
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:
if LocalSettings.objects.filter(key='AR-Target%').exists():
target_percent = float(LocalSettings.objects.filter(key='AR-Target%')[0].value)
else:
if not LocalSettings.objects.filter(key='AR-Target%').exists():
LocalSettings(key='AR-Target%', value='0.05').save()
target_percent = 0.05
if LocalSettings.objects.filter(key='AR-MaxFeeRate').exists():
max_fee_rate = int(LocalSettings.objects.filter(key='AR-MaxFeeRate')[0].value)
else:
@ -104,7 +98,7 @@ def auto_schedule():
target_fee_rate = int(target.local_fee_rate * max_cost)
if target_fee_rate > 0 and target_fee_rate > target.remote_fee_rate:
value_per_fee = int(1 / (target_fee_rate / 1000000)) if target_fee_rate <= max_fee_rate else int(1 / (max_fee_rate / 1000000))
target_value = int((target.capacity * target_percent) / value_per_fee) * value_per_fee
target_value = int(target.ar_amt_target / value_per_fee) * value_per_fee
if target_value >= value_per_fee:
if LocalSettings.objects.filter(key='AR-Time').exists():
target_time = int(LocalSettings.objects.filter(key='AR-Time')[0].value)
@ -121,8 +115,7 @@ def auto_schedule():
print('Creating Auto Rebalance Request')
print('Request for:', target.chan_id)
print('Request routing through:', outbound_cans)
print('Target % Of Value:', target_percent)
print('Target Value:', target_value)
print('Target Value:', target.ar_amt_target)
print('Target Fee:', target_fee)
print('Target Time:', target_time)
Rebalancer(value=target_value, fee_limit=target_fee, outgoing_chan_ids=outbound_cans, last_hop_pubkey=inbound_pubkey.remote_pubkey, target_alias=inbound_pubkey.alias, duration=target_time).save()
@ -145,21 +138,24 @@ def auto_enable():
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')
#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%')
#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%')
#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')
#print('Case 4: Pass')
pass
else:
print('Case 5: Pass')
#print('Case 5: Pass')
pass
def main():
rebalances = Rebalancer.objects.filter(status=0).order_by('id')

View file

@ -3,4 +3,5 @@ djangorestframework
django-qr-code
grpcio
protobuf
pytz
pytz
pandas

View file

@ -93,6 +93,8 @@ User=<run_as_user>
Group=<run_as_user>
ExecStart=/usr/bin/bash /home/<run_as_user>/lndg/htlc_stream.sh
StandardError=append:/var/log/lnd_htlc_stream_error.log
Restart=on-failure
RestartSec=60s
```
Enable and start the service to run the htlc failure stream service file.
`sudo systemctl enable htlc-stream-lndg.service`