diff --git a/gui/lnd_deps/signer_pb2.py b/gui/lnd_deps/signer_pb2.py
new file mode 100644
index 00000000..08a170f1
--- /dev/null
+++ b/gui/lnd_deps/signer_pb2.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: signer.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0csigner.proto\x12\x07signrpc\"3\n\nKeyLocator\x12\x12\n\nkey_family\x18\x01 \x01(\x05\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"L\n\rKeyDescriptor\x12\x15\n\rraw_key_bytes\x18\x01 \x01(\x0c\x12$\n\x07key_loc\x18\x02 \x01(\x0b\x32\x13.signrpc.KeyLocator\")\n\x05TxOut\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x11\n\tpk_script\x18\x02 \x01(\x0c\"\xc4\x01\n\x0eSignDescriptor\x12(\n\x08key_desc\x18\x01 \x01(\x0b\x32\x16.signrpc.KeyDescriptor\x12\x14\n\x0csingle_tweak\x18\x02 \x01(\x0c\x12\x14\n\x0c\x64ouble_tweak\x18\x03 \x01(\x0c\x12\x16\n\x0ewitness_script\x18\x04 \x01(\x0c\x12\x1e\n\x06output\x18\x05 \x01(\x0b\x32\x0e.signrpc.TxOut\x12\x0f\n\x07sighash\x18\x07 \x01(\r\x12\x13\n\x0binput_index\x18\x08 \x01(\x05\"L\n\x07SignReq\x12\x14\n\x0craw_tx_bytes\x18\x01 \x01(\x0c\x12+\n\nsign_descs\x18\x02 \x03(\x0b\x32\x17.signrpc.SignDescriptor\"\x1c\n\x08SignResp\x12\x10\n\x08raw_sigs\x18\x01 \x03(\x0c\"2\n\x0bInputScript\x12\x0f\n\x07witness\x18\x01 \x03(\x0c\x12\x12\n\nsig_script\x18\x02 \x01(\x0c\">\n\x0fInputScriptResp\x12+\n\rinput_scripts\x18\x01 \x03(\x0b\x32\x14.signrpc.InputScript\"m\n\x0eSignMessageReq\x12\x0b\n\x03msg\x18\x01 \x01(\x0c\x12$\n\x07key_loc\x18\x02 \x01(\x0b\x32\x13.signrpc.KeyLocator\x12\x13\n\x0b\x64ouble_hash\x18\x03 \x01(\x08\x12\x13\n\x0b\x63ompact_sig\x18\x04 \x01(\x08\"$\n\x0fSignMessageResp\x12\x11\n\tsignature\x18\x01 \x01(\x0c\"B\n\x10VerifyMessageReq\x12\x0b\n\x03msg\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0e\n\x06pubkey\x18\x03 \x01(\x0c\"\"\n\x11VerifyMessageResp\x12\r\n\x05valid\x18\x01 \x01(\x08\"\x80\x01\n\x10SharedKeyRequest\x12\x18\n\x10\x65phemeral_pubkey\x18\x01 \x01(\x0c\x12(\n\x07key_loc\x18\x02 \x01(\x0b\x32\x13.signrpc.KeyLocatorB\x02\x18\x01\x12(\n\x08key_desc\x18\x03 \x01(\x0b\x32\x16.signrpc.KeyDescriptor\"\'\n\x11SharedKeyResponse\x12\x12\n\nshared_key\x18\x01 \x01(\x0c\x32\xd4\x02\n\x06Signer\x12\x34\n\rSignOutputRaw\x12\x10.signrpc.SignReq\x1a\x11.signrpc.SignResp\x12@\n\x12\x43omputeInputScript\x12\x10.signrpc.SignReq\x1a\x18.signrpc.InputScriptResp\x12@\n\x0bSignMessage\x12\x17.signrpc.SignMessageReq\x1a\x18.signrpc.SignMessageResp\x12\x46\n\rVerifyMessage\x12\x19.signrpc.VerifyMessageReq\x1a\x1a.signrpc.VerifyMessageResp\x12H\n\x0f\x44\x65riveSharedKey\x12\x19.signrpc.SharedKeyRequest\x1a\x1a.signrpc.SharedKeyResponseB/Z-github.com/lightningnetwork/lnd/lnrpc/signrpcb\x06proto3')
+
+
+
+_KEYLOCATOR = DESCRIPTOR.message_types_by_name['KeyLocator']
+_KEYDESCRIPTOR = DESCRIPTOR.message_types_by_name['KeyDescriptor']
+_TXOUT = DESCRIPTOR.message_types_by_name['TxOut']
+_SIGNDESCRIPTOR = DESCRIPTOR.message_types_by_name['SignDescriptor']
+_SIGNREQ = DESCRIPTOR.message_types_by_name['SignReq']
+_SIGNRESP = DESCRIPTOR.message_types_by_name['SignResp']
+_INPUTSCRIPT = DESCRIPTOR.message_types_by_name['InputScript']
+_INPUTSCRIPTRESP = DESCRIPTOR.message_types_by_name['InputScriptResp']
+_SIGNMESSAGEREQ = DESCRIPTOR.message_types_by_name['SignMessageReq']
+_SIGNMESSAGERESP = DESCRIPTOR.message_types_by_name['SignMessageResp']
+_VERIFYMESSAGEREQ = DESCRIPTOR.message_types_by_name['VerifyMessageReq']
+_VERIFYMESSAGERESP = DESCRIPTOR.message_types_by_name['VerifyMessageResp']
+_SHAREDKEYREQUEST = DESCRIPTOR.message_types_by_name['SharedKeyRequest']
+_SHAREDKEYRESPONSE = DESCRIPTOR.message_types_by_name['SharedKeyResponse']
+KeyLocator = _reflection.GeneratedProtocolMessageType('KeyLocator', (_message.Message,), {
+ 'DESCRIPTOR' : _KEYLOCATOR,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.KeyLocator)
+ })
+_sym_db.RegisterMessage(KeyLocator)
+
+KeyDescriptor = _reflection.GeneratedProtocolMessageType('KeyDescriptor', (_message.Message,), {
+ 'DESCRIPTOR' : _KEYDESCRIPTOR,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.KeyDescriptor)
+ })
+_sym_db.RegisterMessage(KeyDescriptor)
+
+TxOut = _reflection.GeneratedProtocolMessageType('TxOut', (_message.Message,), {
+ 'DESCRIPTOR' : _TXOUT,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.TxOut)
+ })
+_sym_db.RegisterMessage(TxOut)
+
+SignDescriptor = _reflection.GeneratedProtocolMessageType('SignDescriptor', (_message.Message,), {
+ 'DESCRIPTOR' : _SIGNDESCRIPTOR,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.SignDescriptor)
+ })
+_sym_db.RegisterMessage(SignDescriptor)
+
+SignReq = _reflection.GeneratedProtocolMessageType('SignReq', (_message.Message,), {
+ 'DESCRIPTOR' : _SIGNREQ,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.SignReq)
+ })
+_sym_db.RegisterMessage(SignReq)
+
+SignResp = _reflection.GeneratedProtocolMessageType('SignResp', (_message.Message,), {
+ 'DESCRIPTOR' : _SIGNRESP,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.SignResp)
+ })
+_sym_db.RegisterMessage(SignResp)
+
+InputScript = _reflection.GeneratedProtocolMessageType('InputScript', (_message.Message,), {
+ 'DESCRIPTOR' : _INPUTSCRIPT,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.InputScript)
+ })
+_sym_db.RegisterMessage(InputScript)
+
+InputScriptResp = _reflection.GeneratedProtocolMessageType('InputScriptResp', (_message.Message,), {
+ 'DESCRIPTOR' : _INPUTSCRIPTRESP,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.InputScriptResp)
+ })
+_sym_db.RegisterMessage(InputScriptResp)
+
+SignMessageReq = _reflection.GeneratedProtocolMessageType('SignMessageReq', (_message.Message,), {
+ 'DESCRIPTOR' : _SIGNMESSAGEREQ,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.SignMessageReq)
+ })
+_sym_db.RegisterMessage(SignMessageReq)
+
+SignMessageResp = _reflection.GeneratedProtocolMessageType('SignMessageResp', (_message.Message,), {
+ 'DESCRIPTOR' : _SIGNMESSAGERESP,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.SignMessageResp)
+ })
+_sym_db.RegisterMessage(SignMessageResp)
+
+VerifyMessageReq = _reflection.GeneratedProtocolMessageType('VerifyMessageReq', (_message.Message,), {
+ 'DESCRIPTOR' : _VERIFYMESSAGEREQ,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.VerifyMessageReq)
+ })
+_sym_db.RegisterMessage(VerifyMessageReq)
+
+VerifyMessageResp = _reflection.GeneratedProtocolMessageType('VerifyMessageResp', (_message.Message,), {
+ 'DESCRIPTOR' : _VERIFYMESSAGERESP,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.VerifyMessageResp)
+ })
+_sym_db.RegisterMessage(VerifyMessageResp)
+
+SharedKeyRequest = _reflection.GeneratedProtocolMessageType('SharedKeyRequest', (_message.Message,), {
+ 'DESCRIPTOR' : _SHAREDKEYREQUEST,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.SharedKeyRequest)
+ })
+_sym_db.RegisterMessage(SharedKeyRequest)
+
+SharedKeyResponse = _reflection.GeneratedProtocolMessageType('SharedKeyResponse', (_message.Message,), {
+ 'DESCRIPTOR' : _SHAREDKEYRESPONSE,
+ '__module__' : 'signer_pb2'
+ # @@protoc_insertion_point(class_scope:signrpc.SharedKeyResponse)
+ })
+_sym_db.RegisterMessage(SharedKeyResponse)
+
+_SIGNER = DESCRIPTOR.services_by_name['Signer']
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ DESCRIPTOR._serialized_options = b'Z-github.com/lightningnetwork/lnd/lnrpc/signrpc'
+ _SHAREDKEYREQUEST.fields_by_name['key_loc']._options = None
+ _SHAREDKEYREQUEST.fields_by_name['key_loc']._serialized_options = b'\030\001'
+ _KEYLOCATOR._serialized_start=25
+ _KEYLOCATOR._serialized_end=76
+ _KEYDESCRIPTOR._serialized_start=78
+ _KEYDESCRIPTOR._serialized_end=154
+ _TXOUT._serialized_start=156
+ _TXOUT._serialized_end=197
+ _SIGNDESCRIPTOR._serialized_start=200
+ _SIGNDESCRIPTOR._serialized_end=396
+ _SIGNREQ._serialized_start=398
+ _SIGNREQ._serialized_end=474
+ _SIGNRESP._serialized_start=476
+ _SIGNRESP._serialized_end=504
+ _INPUTSCRIPT._serialized_start=506
+ _INPUTSCRIPT._serialized_end=556
+ _INPUTSCRIPTRESP._serialized_start=558
+ _INPUTSCRIPTRESP._serialized_end=620
+ _SIGNMESSAGEREQ._serialized_start=622
+ _SIGNMESSAGEREQ._serialized_end=731
+ _SIGNMESSAGERESP._serialized_start=733
+ _SIGNMESSAGERESP._serialized_end=769
+ _VERIFYMESSAGEREQ._serialized_start=771
+ _VERIFYMESSAGEREQ._serialized_end=837
+ _VERIFYMESSAGERESP._serialized_start=839
+ _VERIFYMESSAGERESP._serialized_end=873
+ _SHAREDKEYREQUEST._serialized_start=876
+ _SHAREDKEYREQUEST._serialized_end=1004
+ _SHAREDKEYRESPONSE._serialized_start=1006
+ _SHAREDKEYRESPONSE._serialized_end=1045
+ _SIGNER._serialized_start=1048
+ _SIGNER._serialized_end=1388
+# @@protoc_insertion_point(module_scope)
diff --git a/gui/lnd_deps/signer_pb2_grpc.py b/gui/lnd_deps/signer_pb2_grpc.py
new file mode 100644
index 00000000..42a136db
--- /dev/null
+++ b/gui/lnd_deps/signer_pb2_grpc.py
@@ -0,0 +1,245 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+
+from . import signer_pb2 as signer__pb2
+
+
+class SignerStub(object):
+ """Signer is a service that gives access to the signing functionality of the
+ daemon's wallet.
+ """
+
+ def __init__(self, channel):
+ """Constructor.
+
+ Args:
+ channel: A grpc.Channel.
+ """
+ self.SignOutputRaw = channel.unary_unary(
+ '/signrpc.Signer/SignOutputRaw',
+ request_serializer=signer__pb2.SignReq.SerializeToString,
+ response_deserializer=signer__pb2.SignResp.FromString,
+ )
+ self.ComputeInputScript = channel.unary_unary(
+ '/signrpc.Signer/ComputeInputScript',
+ request_serializer=signer__pb2.SignReq.SerializeToString,
+ response_deserializer=signer__pb2.InputScriptResp.FromString,
+ )
+ self.SignMessage = channel.unary_unary(
+ '/signrpc.Signer/SignMessage',
+ request_serializer=signer__pb2.SignMessageReq.SerializeToString,
+ response_deserializer=signer__pb2.SignMessageResp.FromString,
+ )
+ self.VerifyMessage = channel.unary_unary(
+ '/signrpc.Signer/VerifyMessage',
+ request_serializer=signer__pb2.VerifyMessageReq.SerializeToString,
+ response_deserializer=signer__pb2.VerifyMessageResp.FromString,
+ )
+ self.DeriveSharedKey = channel.unary_unary(
+ '/signrpc.Signer/DeriveSharedKey',
+ request_serializer=signer__pb2.SharedKeyRequest.SerializeToString,
+ response_deserializer=signer__pb2.SharedKeyResponse.FromString,
+ )
+
+
+class SignerServicer(object):
+ """Signer is a service that gives access to the signing functionality of the
+ daemon's wallet.
+ """
+
+ def SignOutputRaw(self, request, context):
+ """
+ SignOutputRaw is a method that can be used to generated a signature for a
+ set of inputs/outputs to a transaction. Each request specifies details
+ concerning how the outputs should be signed, which keys they should be
+ signed with, and also any optional tweaks. The return value is a fixed
+ 64-byte signature (the same format as we use on the wire in Lightning).
+
+ If we are unable to sign using the specified keys, then an error will be
+ returned.
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def ComputeInputScript(self, request, context):
+ """
+ ComputeInputScript generates a complete InputIndex for the passed
+ transaction with the signature as defined within the passed SignDescriptor.
+ This method should be capable of generating the proper input script for
+ both regular p2wkh output and p2wkh outputs nested within a regular p2sh
+ output.
+
+ Note that when using this method to sign inputs belonging to the wallet,
+ the only items of the SignDescriptor that need to be populated are pkScript
+ in the TxOut field, the value in that same field, and finally the input
+ index.
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def SignMessage(self, request, context):
+ """
+ SignMessage signs a message with the key specified in the key locator. The
+ returned signature is fixed-size LN wire format encoded.
+
+ The main difference to SignMessage in the main RPC is that a specific key is
+ used to sign the message instead of the node identity private key.
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def VerifyMessage(self, request, context):
+ """
+ VerifyMessage verifies a signature over a message using the public key
+ provided. The signature must be fixed-size LN wire format encoded.
+
+ The main difference to VerifyMessage in the main RPC is that the public key
+ used to sign the message does not have to be a node known to the network.
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+ def DeriveSharedKey(self, request, context):
+ """
+ DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key
+ derivation between the ephemeral public key in the request and the node's
+ key specified in the key_desc parameter. Either a key locator or a raw
+ public key is expected in the key_desc, if neither is supplied, defaults to
+ the node's identity private key:
+ P_shared = privKeyNode * ephemeralPubkey
+ The resulting shared public key is serialized in the compressed format and
+ hashed with sha256, resulting in the final key length of 256bit.
+ """
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+ context.set_details('Method not implemented!')
+ raise NotImplementedError('Method not implemented!')
+
+
+def add_SignerServicer_to_server(servicer, server):
+ rpc_method_handlers = {
+ 'SignOutputRaw': grpc.unary_unary_rpc_method_handler(
+ servicer.SignOutputRaw,
+ request_deserializer=signer__pb2.SignReq.FromString,
+ response_serializer=signer__pb2.SignResp.SerializeToString,
+ ),
+ 'ComputeInputScript': grpc.unary_unary_rpc_method_handler(
+ servicer.ComputeInputScript,
+ request_deserializer=signer__pb2.SignReq.FromString,
+ response_serializer=signer__pb2.InputScriptResp.SerializeToString,
+ ),
+ 'SignMessage': grpc.unary_unary_rpc_method_handler(
+ servicer.SignMessage,
+ request_deserializer=signer__pb2.SignMessageReq.FromString,
+ response_serializer=signer__pb2.SignMessageResp.SerializeToString,
+ ),
+ 'VerifyMessage': grpc.unary_unary_rpc_method_handler(
+ servicer.VerifyMessage,
+ request_deserializer=signer__pb2.VerifyMessageReq.FromString,
+ response_serializer=signer__pb2.VerifyMessageResp.SerializeToString,
+ ),
+ 'DeriveSharedKey': grpc.unary_unary_rpc_method_handler(
+ servicer.DeriveSharedKey,
+ request_deserializer=signer__pb2.SharedKeyRequest.FromString,
+ response_serializer=signer__pb2.SharedKeyResponse.SerializeToString,
+ ),
+ }
+ generic_handler = grpc.method_handlers_generic_handler(
+ 'signrpc.Signer', rpc_method_handlers)
+ server.add_generic_rpc_handlers((generic_handler,))
+
+
+ # This class is part of an EXPERIMENTAL API.
+class Signer(object):
+ """Signer is a service that gives access to the signing functionality of the
+ daemon's wallet.
+ """
+
+ @staticmethod
+ def SignOutputRaw(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/SignOutputRaw',
+ signer__pb2.SignReq.SerializeToString,
+ signer__pb2.SignResp.FromString,
+ options, channel_credentials,
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+ @staticmethod
+ def ComputeInputScript(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/ComputeInputScript',
+ signer__pb2.SignReq.SerializeToString,
+ signer__pb2.InputScriptResp.FromString,
+ options, channel_credentials,
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+ @staticmethod
+ def SignMessage(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/SignMessage',
+ signer__pb2.SignMessageReq.SerializeToString,
+ signer__pb2.SignMessageResp.FromString,
+ options, channel_credentials,
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+ @staticmethod
+ def VerifyMessage(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/VerifyMessage',
+ signer__pb2.VerifyMessageReq.SerializeToString,
+ signer__pb2.VerifyMessageResp.FromString,
+ options, channel_credentials,
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+ @staticmethod
+ def DeriveSharedKey(request,
+ target,
+ options=(),
+ channel_credentials=None,
+ call_credentials=None,
+ insecure=False,
+ compression=None,
+ wait_for_ready=None,
+ timeout=None,
+ metadata=None):
+ return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/DeriveSharedKey',
+ signer__pb2.SharedKeyRequest.SerializeToString,
+ signer__pb2.SharedKeyResponse.FromString,
+ options, channel_credentials,
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
diff --git a/gui/migrations/0021_auto_20220221_1309.py b/gui/migrations/0021_auto_20220221_1309.py
new file mode 100644
index 00000000..7395eeff
--- /dev/null
+++ b/gui/migrations/0021_auto_20220221_1309.py
@@ -0,0 +1,216 @@
+# Generated by Django 3.2.7 on 2022-02-23 09:31
+
+from django.db import migrations, models
+import django.db.models.deletion
+from gui.lnd_deps import lightning_pb2 as ln
+from gui.lnd_deps import lightning_pb2_grpc as lnrpc
+from gui.lnd_deps import signer_pb2 as lns
+from gui.lnd_deps import signer_pb2_grpc as lnsigner
+from gui.lnd_deps.lnd_connect import lnd_connect
+from lndg import settings
+
+def update_messages(apps, schedma_editor):
+ invoices = apps.get_model('gui', 'invoices')
+ try:
+ messages = invoices.objects.exclude(message=None)
+ if len(messages) > 0:
+ stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
+ for message in messages:
+ records = stub.LookupInvoice(ln.PaymentHash(r_hash=bytes.fromhex(message.r_hash))).htlcs[0].custom_records
+ if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
+ try:
+ valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
+ except:
+ print('Unable to validate signature on invoice: ' + message.r_hash)
+ valid = False
+ sender = records[34349339].hex() if valid == True else None
+ else:
+ sender = None
+ try:
+ alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
+ except:
+ alias = None
+ message.message = records[34349334].decode('utf-8', errors='ignore')[:1000]
+ message.sender = sender
+ message.sender_alias = alias
+ message.save()
+ except Exception as e:
+ print('Migration step failed:', str(e))
+
+def revert_messages(apps, schedma_editor):
+ pass
+
+def update_rebal_channel(apps, schedma_editor):
+ payments = apps.get_model('gui', 'payments')
+ hops = apps.get_model('gui', 'paymenthops')
+ try:
+ stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
+ for payment in payments.objects.filter(status=2).iterator():
+ last_hop = hops.objects.filter(payment_hash=payment.payment_hash).order_by('-step')[0] if hops.objects.filter(payment_hash=payment.payment_hash).exists() else None
+ if last_hop != None and last_hop.node_pubkey == self_pubkey:
+ payment.rebal_chan = last_hop.chan_id
+ payment.save()
+ except Exception as e:
+ print('Migration step failed:', str(e))
+
+def revert_rebal_channel(apps, schedma_editor):
+ pass
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0020_auto_20220126_2113'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Closures',
+ fields=[
+ ('chan_id', models.CharField(max_length=20, primary_key=True, serialize=False)),
+ ('closing_tx', models.CharField(max_length=64)),
+ ('remote_pubkey', models.CharField(max_length=66)),
+ ('capacity', models.BigIntegerField()),
+ ('close_height', models.IntegerField()),
+ ('settled_balance', models.BigIntegerField()),
+ ('time_locked_balance', models.BigIntegerField()),
+ ('close_type', models.IntegerField()),
+ ('open_initiator', models.IntegerField()),
+ ('close_initiator', models.IntegerField()),
+ ('resolution_count', models.IntegerField()),
+ ],
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='htlc_count',
+ field=models.IntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='pending_inbound',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='pending_outbound',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='private',
+ field=models.BooleanField(default=False),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='total_received',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='total_sent',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='invoices',
+ name='sender',
+ field=models.CharField(max_length=66, null=True),
+ ),
+ migrations.AddField(
+ model_name='invoices',
+ name='sender_alias',
+ field=models.CharField(max_length=32, null=True),
+ ),
+ migrations.AddField(
+ model_name='payments',
+ name='rebal_chan',
+ field=models.CharField(max_length=20, null=True),
+ ),
+ migrations.AddField(
+ model_name='peers',
+ name='alias',
+ field=models.CharField(max_length=32, null=True),
+ ),
+ migrations.AlterField(
+ model_name='autopilot',
+ name='chan_id',
+ field=models.CharField(max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='channels',
+ name='chan_id',
+ field=models.CharField(max_length=20, primary_key=True, serialize=False),
+ ),
+ migrations.AlterField(
+ model_name='failedhtlcs',
+ name='chan_id_in',
+ field=models.CharField(max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='failedhtlcs',
+ name='chan_id_out',
+ field=models.CharField(max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='forwards',
+ name='chan_id_in',
+ field=models.CharField(max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='forwards',
+ name='chan_id_out',
+ field=models.CharField(max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='invoices',
+ name='chan_in',
+ field=models.CharField(max_length=20, null=True),
+ ),
+ migrations.AlterField(
+ model_name='invoices',
+ name='message',
+ field=models.CharField(max_length=1000, null=True),
+ ),
+ migrations.AlterField(
+ model_name='paymenthops',
+ name='chan_id',
+ field=models.CharField(max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='payments',
+ name='chan_out',
+ field=models.CharField(max_length=20, null=True),
+ ),
+ migrations.AlterField(
+ model_name='payments',
+ name='message',
+ field=models.CharField(max_length=1000, null=True),
+ ),
+ migrations.AlterField(
+ model_name='pendinghtlcs',
+ name='chan_id',
+ field=models.CharField(max_length=20),
+ ),
+ migrations.CreateModel(
+ name='Resolutions',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('resolution_type', models.IntegerField()),
+ ('outcome', models.IntegerField()),
+ ('outpoint_tx', models.CharField(max_length=64)),
+ ('outpoint_index', models.IntegerField()),
+ ('amount_sat', models.BigIntegerField()),
+ ('sweep_txid', models.CharField(max_length=64)),
+ ('chan_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gui.closures')),
+ ],
+ ),
+ migrations.RunPython(update_rebal_channel, revert_rebal_channel),
+ migrations.RunPython(update_messages, revert_messages),
+ ]
diff --git a/gui/migrations/0022_auto_20220227_2235.py b/gui/migrations/0022_auto_20220227_2235.py
new file mode 100644
index 00000000..e3fa9e35
--- /dev/null
+++ b/gui/migrations/0022_auto_20220227_2235.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.7 on 2022-02-27 22:35
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0021_auto_20220221_1309'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='failedhtlcs',
+ name='chan_out_liq',
+ field=models.BigIntegerField(null=True),
+ ),
+ migrations.AddField(
+ model_name='failedhtlcs',
+ name='chan_out_pending',
+ field=models.BigIntegerField(null=True),
+ ),
+ ]
diff --git a/gui/models.py b/gui/models.py
index 0a5ce8c6..d6e5b703 100644
--- a/gui/models.py
+++ b/gui/models.py
@@ -9,11 +9,12 @@ class Payments(models.Model):
fee = models.FloatField()
status = models.IntegerField()
index = models.IntegerField()
- chan_out = models.IntegerField(null=True)
+ chan_out = models.CharField(max_length=20, null=True)
chan_out_alias = models.CharField(null=True, max_length=32)
keysend_preimage = models.CharField(null=True, max_length=64)
- message = models.CharField(null=True, max_length=255)
+ message = models.CharField(null=True, max_length=1000)
cleaned = models.BooleanField(default=False)
+ rebal_chan = models.CharField(max_length=20, null=True)
class Meta:
app_label = 'gui'
@@ -21,7 +22,7 @@ class PaymentHops(models.Model):
payment_hash = models.ForeignKey('Payments', on_delete=models.CASCADE)
attempt_id = models.IntegerField()
step = models.IntegerField()
- chan_id = models.IntegerField()
+ chan_id = models.CharField(max_length=20)
alias = models.CharField(max_length=32)
chan_capacity = models.BigIntegerField()
node_pubkey = models.CharField(max_length=66)
@@ -39,18 +40,20 @@ class Invoices(models.Model):
value = models.FloatField()
amt_paid = models.BigIntegerField()
state = models.IntegerField()
- chan_in = models.IntegerField(null=True)
+ chan_in = models.CharField(max_length=20, null=True)
chan_in_alias = models.CharField(null=True, max_length=32)
keysend_preimage = models.CharField(null=True, max_length=64)
- message = models.CharField(null=True, max_length=500)
+ message = models.CharField(null=True, max_length=1000)
+ sender = models.CharField(null=True, max_length=66)
+ sender_alias = models.CharField(null=True, max_length=32)
index = models.IntegerField()
class Meta:
app_label = 'gui'
class Forwards(models.Model):
forward_date = models.DateTimeField()
- chan_id_in = models.IntegerField()
- chan_id_out = models.IntegerField()
+ chan_id_in = models.CharField(max_length=20)
+ chan_id_out = models.CharField(max_length=20)
chan_in_alias = models.CharField(null=True, max_length=32)
chan_out_alias = models.CharField(null=True, max_length=32)
amt_in_msat = models.BigIntegerField()
@@ -61,7 +64,7 @@ class Meta:
class Channels(models.Model):
remote_pubkey = models.CharField(max_length=66)
- chan_id = models.IntegerField(primary_key=True)
+ chan_id = models.CharField(max_length=20, primary_key=True)
funding_txid = models.CharField(max_length=64)
output_index = models.IntegerField()
capacity = models.BigIntegerField()
@@ -73,6 +76,12 @@ class Channels(models.Model):
num_updates = models.IntegerField()
initiator = models.BooleanField()
alias = models.CharField(max_length=32)
+ total_sent = models.BigIntegerField()
+ total_received = models.BigIntegerField()
+ private = models.BooleanField()
+ pending_outbound = models.BigIntegerField()
+ pending_inbound = models.BigIntegerField()
+ htlc_count = models.IntegerField()
local_base_fee = models.IntegerField()
local_fee_rate = models.IntegerField()
local_disabled = models.BooleanField()
@@ -117,6 +126,7 @@ class Meta:
class Peers(models.Model):
pubkey = models.CharField(max_length=66, primary_key=True)
+ alias = models.CharField(null=True, max_length=32)
address = models.CharField(max_length=100)
sat_sent = models.BigIntegerField()
sat_recv = models.BigIntegerField()
@@ -158,8 +168,34 @@ class Onchain(models.Model):
class Meta:
app_label = 'gui'
+class Closures(models.Model):
+ chan_id = models.CharField(max_length=20, primary_key=True)
+ closing_tx = models.CharField(max_length=64)
+ remote_pubkey = models.CharField(max_length=66)
+ capacity = models.BigIntegerField()
+ close_height = models.IntegerField()
+ settled_balance = models.BigIntegerField()
+ time_locked_balance = models.BigIntegerField()
+ close_type = models.IntegerField()
+ open_initiator = models.IntegerField()
+ close_initiator = models.IntegerField()
+ resolution_count = models.IntegerField()
+ class Meta:
+ app_label = 'gui'
+
+class Resolutions(models.Model):
+ chan_id = models.ForeignKey('Closures', on_delete=models.CASCADE)
+ resolution_type = models.IntegerField()
+ outcome = models.IntegerField()
+ outpoint_tx = models.CharField(max_length=64)
+ outpoint_index = models.IntegerField()
+ amount_sat = models.BigIntegerField()
+ sweep_txid = models.CharField(max_length=64)
+ class Meta:
+ app_label = 'gui'
+
class PendingHTLCs(models.Model):
- chan_id = models.IntegerField()
+ chan_id = models.CharField(max_length=20)
alias = models.CharField(max_length=32)
incoming = models.BooleanField()
amount = models.BigIntegerField()
@@ -173,10 +209,12 @@ class Meta:
class FailedHTLCs(models.Model):
timestamp = models.DateTimeField(default=timezone.now)
amount = models.IntegerField()
- chan_id_in = models.IntegerField()
- chan_id_out = models.IntegerField()
+ chan_id_in = models.CharField(max_length=20)
+ chan_id_out = models.CharField(max_length=20)
chan_in_alias = models.CharField(null=True, max_length=32)
chan_out_alias = models.CharField(null=True, max_length=32)
+ chan_out_liq = models.BigIntegerField(null=True)
+ chan_out_pending = models.BigIntegerField(null=True)
wire_failure = models.IntegerField()
failure_detail = models.IntegerField()
missed_fee = models.FloatField()
@@ -185,7 +223,7 @@ class Meta:
class Autopilot(models.Model):
timestamp = models.DateTimeField(default=timezone.now)
- chan_id = models.IntegerField()
+ chan_id = models.CharField(max_length=20)
peer_alias = models.CharField(max_length=32)
setting = models.CharField(max_length=20)
old_value = models.IntegerField()
diff --git a/gui/templates/action_list.html b/gui/templates/action_list.html
index 2c1d481c..4ad2a453 100644
--- a/gui/templates/action_list.html
+++ b/gui/templates/action_list.html
@@ -25,8 +25,8 @@
Suggested Action List
{% for channel in action_list %}
- {{ channel.chan_id }} |
- {{ channel.alias }} |
+ {{ channel.chan_id }} |
+ {% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %} |
{{ channel.capacity|intcomma }} |
{{ channel.local_balance|intcomma }} ({{ channel.outbound_percent }}%) |
{% if channel.inbound_percent == 0 %} {% elif channel.outbound_percent == 0 %} {% else %} {% endif %} |
diff --git a/gui/templates/advanced.html b/gui/templates/advanced.html
index c3ffa55a..7b235639 100644
--- a/gui/templates/advanced.html
+++ b/gui/templates/advanced.html
@@ -28,14 +28,14 @@ Advanced Channel Settings
{% for channel in channels %}
- {{ channel.chan_id }} |
- {{ channel.alias }} |
+ {{ channel.chan_id }} |
+ {% if channel.private == False %}{% endif %}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{% if channel.private == False %}{% endif %} |
{{ channel.capacity|intcomma }} |
{{ channel.local_balance|intcomma }} ({{ channel.out_percent }}%) |
{% if channel.in_percent == 0 %} {% elif channel.out_percent == 0 %} {% else %} {% endif %} |
{{ channel.remote_balance|intcomma }} ({{ channel.in_percent }}%) |
-
- {% if channel.is_active == True %}
+ |
+ {% if channel.is_active == True and channel.private == False %}
|
-
+ |
|
-
+ |
|
{% for log in autopilot %}
- {{ log.timestamp|naturaltime }} |
- {{ log.chan_id }} |
- {{ log.peer_alias }} |
+ {{ log.timestamp|naturaltime }} |
+ {{ log.chan_id }} |
+ {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} |
{{ log.setting }} |
{{ log.old_value }} |
{{ log.new_value }} |
diff --git a/gui/templates/base.html b/gui/templates/base.html
index 75d7f1ad..05c3f196 100644
--- a/gui/templates/base.html
+++ b/gui/templates/base.html
@@ -28,7 +28,7 @@
diff --git a/gui/templates/channel.html b/gui/templates/channel.html
new file mode 100644
index 00000000..679fa2f9
--- /dev/null
+++ b/gui/templates/channel.html
@@ -0,0 +1,349 @@
+{% extends "base.html" %}
+{% block title %} {{ block.super }} - Channel Card{% endblock %}
+{% block content %}
+{% load humanize %}
+{% if channel %}
+
+
Details For Channel: {{ chan_id }} | Peer: {% if channel.alias != "" %}{{ channel.alias }} ({{ channel.remote_pubkey }}){% else %}{{ channel.remote_pubkey }}{% endif %}
+
Capacity: {{ channel.capacity|intcomma }} | Is Open: {{ channel.is_open }} | Is Active: {{ channel.is_active }} | Is Private: {{ channel.private }} | Channel Updates: {{ channel.num_updates|intcomma }}
+
+
Outbound Liquidity: {{ channel.local_balance|intcomma }} ({{ channel.out_percent }}%) | Inbound Liquidity: {{ channel.remote_balance|intcomma }} ({{ channel.in_percent }}%)
+
+
+
Routing Events
+ 1-Day Routed Out: {{ channel.amt_routed_out_1day|intcomma }} ({{ channel.routed_out_1day|intcomma }}) | Routed In: {{ channel.amt_routed_in_1day|intcomma }} ({{ channel.routed_in_1day|intcomma }}) | Average Out: {{ channel.average_out_1day|intcomma }} | Average In: {{ channel.average_in_1day|intcomma }}
+ 7-Day Routed Out: {{ channel.amt_routed_out_7day|intcomma }} ({{ channel.routed_out_7day|intcomma }}) | Routed In: {{ channel.amt_routed_in_7day|intcomma }} ({{ channel.routed_in_7day|intcomma }}) | Average Out: {{ channel.average_out_7day|intcomma }} | Average In: {{ channel.average_in_7day|intcomma }}
+ 30-Day Routed Out: {{ channel.amt_routed_out_30day|intcomma }} ({{ channel.routed_out_30day|intcomma }}) | Routed In: {{ channel.amt_routed_in_30day|intcomma }} ({{ channel.routed_in_30day|intcomma }}) | Average Out: {{ channel.average_out_30day|intcomma }} | Average In: {{ channel.average_in_30day|intcomma }}
+ Lifetime Routed Out: {{ channel.amt_routed_out|intcomma }} ({{ channel.routed_out|intcomma }}) | Routed In: {{ channel.amt_routed_in|intcomma }} ({{ channel.routed_in|intcomma }}) | Average Out: {{ channel.average_out|intcomma }} | Average In: {{ channel.average_in|intcomma }}
+
+
+
Rebalancing Events
+ 1-Day Rebalanced Out: {{ channel.amt_rebal_out_1day|intcomma }} ({{ channel.rebal_out_1day|intcomma }}) | Rebalanced In: {{ channel.amt_rebal_in_1day|intcomma }} ({{ channel.rebal_in_1day|intcomma }}) | Success Rate: {{ channel.success_rate_1day }}% ({{ channel.success_1day }}/{{ channel.attempts_1day }})
+ 7-Day Rebalanced Out: {{ channel.amt_rebal_out_7day|intcomma }} ({{ channel.rebal_out_7day|intcomma }}) | Rebalanced In: {{ channel.amt_rebal_in_7day|intcomma }} ({{ channel.rebal_in_7day|intcomma }}) | Success Rate: {{ channel.success_rate_7day }}% ({{ channel.success_7day }}/{{ channel.attempts_7day }})
+ 30-Day Rebalanced Out: {{ channel.amt_rebal_out_30day|intcomma }} ({{ channel.rebal_out_30day|intcomma }}) | Rebalanced In: {{ channel.amt_rebal_in_30day|intcomma }} ({{ channel.rebal_in_30day|intcomma }}) | Success Rate: {{ channel.success_rate_30day }}% ({{ channel.success_30day }}/{{ channel.attempts_30day }})
+ Lifetime Rebalanced Out: {{ channel.amt_rebal_out|intcomma }} ({{ channel.rebal_out|intcomma }}) | Rebalanced In: {{ channel.amt_rebal_in|intcomma }} ({{ channel.rebal_in|intcomma }}) | Success Rate: {{ channel.success_rate }}% ({{ channel.success }}/{{ channel.attempts }})
+
+
+
Profitability
+ 1-Day Revenue: {{ channel.revenue_1day|intcomma }} | Costs: {{ channel.costs_1day|intcomma }} | Profits: {{ channel.profits_1day|intcomma }} [{{ channel.profits_vol_1day }}] | Assisted Revenues: {{ channel.revenue_assist_1day|intcomma }} | APY: {{ channel.apy_1day }}%
+ 7-Day Revenue: {{ channel.revenue_7day|intcomma }} | Costs: {{ channel.costs_7day|intcomma }} | Profits: {{ channel.profits_7day|intcomma }} [{{ channel.profits_vol_7day }}] | Assisted Revenues: {{ channel.revenue_assist_7day|intcomma }} | APY: {{ channel.apy_7day }}%
+ 30-Day Revenue: {{ channel.revenue_30day|intcomma }} | Costs: {{ channel.costs_30day|intcomma }} | Profits: {{ channel.profits_30day|intcomma }} [{{ channel.profits_vol_30day }}] | Assisted Revenues: {{ channel.revenue_assist_30day|intcomma }} | APY: {{ channel.apy_30day }}%
+ Lifetime Revenue: {{ channel.revenue|intcomma }} | Costs: {{ channel.costs|intcomma }} | Profits: {{ channel.profits|intcomma }} [{{ channel.profits_vol }}] | Assisted Revenues: {{ channel.revenue_assist|intcomma }} | APY: {{ channel.apy }}%
+
+
+
Channel Settings
+
+
+ 7Day Flow |
+ Out Rate |
+ Rebal Rate |
+ Assisted Ratio |
+ Adjustment |
+ Suggested Rate |
+ Channel State |
+ oRate |
+ oBase |
+ iRate |
+ iBase |
+ Target Amt |
+ Max Cost % |
+ oTarget% |
+ iTarget% |
+ AR |
+ Rebal In? |
+ Active |
+
+
+ {% if channel.net_routed_7day > 0 %}OUT{% elif channel.net_routed_7day < 0 %}IN{% else %}---{% endif %}{% if channel.net_routed_7day != 0 %} | {{ channel.net_routed_7day }}{% endif %} |
+ {{ channel.out_rate }} |
+ {{ channel.rebal_ppm }} |
+ {{ channel.assisted_ratio }} |
+ {{ channel.adjustment }} |
+ {{ channel.new_rate|intcomma }} |
+
+ {% if channel.is_active == True %}
+
+ {% else %}
+ ---
+ {% endif %}
+ |
+
+
+ |
+
+
+ |
+ {{ channel.remote_fee_rate|intcomma }} |
+ {{ channel.remote_base_fee|intcomma }} |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+ = 1 and channel.fee_check < 100 and channel.auto_rebalance == True %}style="background-color: #a6dce2">True ({{ channel.steps }}){% else %}style="background-color: #fadbd5">False{% endif %} |
+ True{% else %}style="background-color: #fadbd5">False{% endif %} |
+
+
+
+{% if outgoing_htlcs %}
+
+
Outgoing HTLCs
+
+
+ Forwarding Channel |
+ Forwarding Alias |
+ Amount |
+ Expiration Height |
+ Hash Lock |
+
+ {% for htlc in outgoing_htlcs %}
+
+ {{ htlc.forwarding_channel }} |
+ {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
+ {{ htlc.amount|intcomma }} |
+ {{ htlc.expiration_height|intcomma }} |
+ {{ htlc.hash_lock }} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if incoming_htlcs %}
+
+
Incoming HTLCs
+
+
+ Forwarding Channel |
+ Forwarding Alias |
+ Amount |
+ Expiration Height |
+ Hash Lock |
+
+ {% for htlc in incoming_htlcs %}
+
+ {{ htlc.forwarding_channel }} |
+ {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
+ {{ htlc.amount|intcomma }} |
+ {{ htlc.expiration_height|intcomma }} |
+ {{ htlc.hash_lock }} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if forwards %}
+
+
+
+
+ Timestamp |
+ Amount In |
+ Amount Out |
+ Channel In Alias |
+ Channel Out Alias |
+ Channel In Id |
+ Channel Out Id |
+ Fees Earned |
+ PPM Earned |
+
+ {% for forward in forwards %}
+
+ {{ forward.forward_date|naturaltime }} |
+ {{ forward.amt_in|intcomma }} |
+ {{ forward.amt_out|intcomma }} |
+ {% if forward.chan_in_alias == '' %}---{% else %}{{ forward.chan_in_alias }}{% endif %} |
+ {% if forward.chan_out_alias == '' %}---{% else %}{{ forward.chan_out_alias }}{% endif %} |
+ {{ forward.chan_id_in }} |
+ {{ forward.chan_id_out }} |
+ {{ forward.fee }} |
+ {{ forward.ppm|intcomma }} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if rebalances %}
+
+
+
+
+ Requested |
+ Start |
+ Stop |
+ Scheduled Duration |
+ Value |
+ Fee Limit |
+ Target PPM |
+ Last Hop Alias |
+ Status |
+
+ {% for rebalance in rebalances %}
+
+ {{ rebalance.requested|naturaltime }} |
+ N/A{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} |
+ 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>N/A{% endif %} |
+ {{ rebalance.duration }} minutes |
+ {{ rebalance.value|intcomma }} |
+ {{ rebalance.fee_limit|intcomma }} |
+ {{ rebalance.ppm|intcomma }} |
+ {% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %} |
+ {% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if payments %}
+
+
+
+
+ Timestamp |
+ Hash |
+ Value |
+ Fee Paid |
+ Status |
+ Chan Out Alias |
+ Chan Out ID |
+ Route |
+ Keysend |
+
+ {% for payment in payments %}
+
+ {{ payment.creation_date|naturaltime }} |
+ {{ payment.payment_hash }} |
+ {{ payment.value|add:"0"|intcomma }} |
+ {{ payment.fee|intcomma }} |
+ {% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %} |
+ {% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %} |
+ {% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
+ {% if payment.status == 2 %}Open{% else %}N/A{% endif %} |
+ {% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if invoices %}
+
+
+
+
+ Created |
+ Settled |
+ Payment Hash |
+ Value |
+ Amount Paid |
+ State |
+ Channel In Alias |
+ Channel In |
+ Keysend |
+
+ {% for invoice in invoices %}
+
+ {{ invoice.creation_date|naturaltime }} |
+ {% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %} |
+ {{ invoice.r_hash }} |
+ {{ invoice.value|add:"0"|intcomma }} |
+ {% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}N/A{% endif %} |
+ {% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %} |
+ {% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}N/A{% endif %} |
+ {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }}{% else %}N/A{% endif %} |
+ {% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if failed_htlcs %}
+
+
+
+
+ Timestamp |
+ Chan In ID |
+ Chan Out ID |
+ Chan In Alias |
+ Chan Out Alias |
+ Forward Amount |
+ Potential Fee |
+ HTLC Failure |
+ Failure Detail |
+
+ {% for failed_htlc in failed_htlcs %}
+
+ {{ failed_htlc.timestamp|naturaltime }} |
+ {{ failed_htlc.chan_id_in }} |
+ {{ failed_htlc.chan_id_out }} |
+ {% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %} |
+ {% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %} |
+ {{ failed_htlc.amount|intcomma }} |
+ {{ failed_htlc.missed_fee|intcomma }} |
+ {% 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 %} |
+ {% 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 %} |
+
+ {% endfor %}
+
+
Failed Outgoing HTLC Events Due To Insufficient Balance
+
1-Day: {{ channel.failed_out_1day }} | 7-Day: {{ channel.failed_out_7day }} | 30-Day: {{ channel.failed_out_30day }} | Lifetime: {{ channel.failed_out }}
+
+{% endif %}
+{% endif %}
+{% if not channel %}
+
+
No data found for this channel!
+
+{% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/gui/templates/channels.html b/gui/templates/channels.html
index 486fe444..753575ea 100644
--- a/gui/templates/channels.html
+++ b/gui/templates/channels.html
@@ -11,46 +11,40 @@ Channel Performance
|
|
|
- 7-Day Activity And Revenue | {{ apy_7day }}% APY |
- 30-Day Activity And Revenue | {{ apy_30day }}% APY |
- Local Fees |
- Peer Fees |
+ 7-Day Activity And Revenue | {{ apy_7day }}% APY |
+ 30-Day Activity And Revenue | {{ apy_30day }}% APY |
Channel Health |
Channel ID |
Peer Alias |
Capacity |
- Outbound Flow |
- Out [Profit] | In |
- Inbound Flow |
- Outbound Flow |
- Out [Profit] | In |
- Inbound Flow |
- Rate |
- Base |
- Rate |
- Base |
+ Outbound Flow |
+ APY |
+ Out [Profit] | In |
+ Inbound Flow |
+ Outbound Flow |
+ APY |
+ Out [Profit] | In |
+ Inbound Flow |
Updates |
Opener |
{% for channel in channels %}
- {{ channel.chan_id }} |
- {{ channel.alias }} |
+ {{ channel.chan_id }} |
+ {% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %} |
{{ channel.capacity }} M |
{{ channel.amt_routed_out_7day|intcomma }} M ({{ channel.routed_out_7day }}) | {{ channel.amt_rebal_in_7day|intcomma }} M ({{ channel.rebal_in_7day }}) |
+ {{ channel.apy_7day }}% |
{{ channel.revenue_7day|intcomma }} [{{ channel.profits_7day|intcomma }}] | {{ channel.revenue_assist_7day|intcomma }} |
{{ channel.amt_routed_in_7day|intcomma }} M ({{ channel.routed_in_7day }}) | {{ channel.amt_rebal_out_7day|intcomma }} M ({{ channel.rebal_out_7day }}) |
{{ channel.amt_routed_out_30day|intcomma }} M ({{ channel.routed_out_30day }}) | {{ channel.amt_rebal_in_30day|intcomma }} M ({{ channel.rebal_in_30day }}) |
+ {{ channel.apy_30day }}% |
{{ channel.revenue_30day|intcomma }} [{{ channel.profits_30day|intcomma }}] | {{ channel.revenue_assist_30day|intcomma }} |
{{ channel.amt_routed_in_30day|intcomma }} M ({{ channel.routed_in_30day }}) | {{ channel.amt_rebal_out_30day|intcomma }} M ({{ channel.rebal_out_30day }}) |
- {{ channel.local_fee_rate|intcomma }} |
- {{ channel.local_base_fee|intcomma }} |
- {{ channel.remote_fee_rate|intcomma }} |
- {{ channel.remote_base_fee|intcomma }} |
{{ channel.updates }}% |
- {{ channel.initiator }} |
+ {% if channel.initiator == True %}Local{% else %}Remote{% endif %} |
{% endfor %}
diff --git a/gui/templates/closures.html b/gui/templates/closures.html
index 5ad26ab2..adec9a8c 100644
--- a/gui/templates/closures.html
+++ b/gui/templates/closures.html
@@ -8,6 +8,7 @@ Closures
Channel ID |
+ Alias |
Capacity |
Closing TXID |
Settled Balance |
@@ -16,18 +17,21 @@ Closures
Close Type |
Opener |
Closer |
+ Resolutions |
{% for closure in closures %}
- {{ closure.chan_id }} |
+ {% if closure.chan_id == '0' %}---{% else %}{{ closure.chan_id }}{% endif %} |
+ {% if closure.alias == '' %}{{ closure.remote_pubkey|slice:":12" }}{% else %}{{ closure.alias }}{% endif %} |
{{ closure.capacity|intcomma }} |
- {{ closure.closing_tx_hash }} |
+ {% if closure.closing_tx == '0000000000000000000000000000000000000000000000000000000000000000' %}---{% else %}{{ closure.closing_tx }}{% endif %} |
{{ closure.settled_balance|intcomma }} |
{{ closure.time_locked_balance|intcomma }} |
{{ closure.close_height|intcomma }} |
{% if closure.close_type == 0 %}Cooperative{% elif closure.close_type == 1 %}Local Force{% elif closure.close_type == 2 %}Remote Force{% elif closure.close_type == 3 %}Breach{% elif closure.close_type == 4 %}Funding Cancelled{% elif closure.close_type == 5 %}Abandoned{% else %}{{ closure.close_type }}{% endif %} |
{% if closure.open_initiator == 0 %}Unknown{% elif closure.open_initiator == 1 %}Local{% elif closure.open_initiator == 2 %}Remote{% elif closure.open_initiator == 3 %}Both{% else %}{{ closure.open_initiator }}{% endif %} |
{% if closure.close_initiator == 0 %}Unknown{% elif closure.close_initiator == 1 %}Local{% elif closure.close_initiator == 2 %}Remote{% elif closure.close_initiator == 3 %}Both{% else %}{{ closure.close_initiator }}{% endif %} |
+ {% if closure.resolution_count > 0 %}Details{% else %}---{% endif %} |
{% endfor %}
diff --git a/gui/templates/failed_htlcs.html b/gui/templates/failed_htlcs.html
index a294ac14..ccc4a50a 100644
--- a/gui/templates/failed_htlcs.html
+++ b/gui/templates/failed_htlcs.html
@@ -13,6 +13,7 @@ Last 150 Failed HTLCs
Chan In Alias |
Chan Out Alias |
Forward Amount |
+ Actual Outbound |
Potential Fee |
HTLC Failure |
Failure Detail |
@@ -22,9 +23,10 @@ Last 150 Failed HTLCs
{{ failed_htlc.timestamp|naturaltime }} |
{{ failed_htlc.chan_id_in }} |
{{ failed_htlc.chan_id_out }} |
- {{ failed_htlc.chan_in_alias }} |
- {{ failed_htlc.chan_out_alias }} |
+ {% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %} |
+ {% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %} |
{{ failed_htlc.amount|intcomma }} |
+ {{ failed_htlc.chan_out_liq|intcomma }} ({{ failed_htlc.chan_out_pending|intcomma }}) |
{{ failed_htlc.missed_fee|intcomma }} |
{% 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 %} |
{% 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 %} |
diff --git a/gui/templates/fee_rates.html b/gui/templates/fee_rates.html
index 3d1a469c..fa2b9801 100644
--- a/gui/templates/fee_rates.html
+++ b/gui/templates/fee_rates.html
@@ -28,8 +28,8 @@ Suggested Fee Rates
{% for channel in channels %}
- {{ channel.chan_id }} |
- {{ channel.alias }} |
+ {{ channel.chan_id }} |
+ {% if channel.private == False %}{% endif %}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{% if channel.private == False %}{% endif %} |
{{ channel.capacity|intcomma }} |
{{ channel.local_balance|intcomma }} ({{ channel.out_percent }}%) |
{% if channel.in_percent == 0 %} {% elif channel.out_percent == 0 %} {% else %} {% endif %} |
diff --git a/gui/templates/forwards.html b/gui/templates/forwards.html
index c3ee1b3e..33f3cf3d 100644
--- a/gui/templates/forwards.html
+++ b/gui/templates/forwards.html
@@ -22,10 +22,10 @@ Last 150 Forwards
{{ forward.forward_date|naturaltime }} |
{{ forward.amt_in|intcomma }} |
{{ forward.amt_out|intcomma }} |
- {{ forward.chan_in_alias }} |
- {{ forward.chan_out_alias }} |
- {{ forward.chan_id_in }} |
- {{ forward.chan_id_out }} |
+ {% if forward.chan_in_alias == '' %}---{% else %}{{ forward.chan_in_alias }}{% endif %} |
+ {% if forward.chan_out_alias == '' %}---{% else %}{{ forward.chan_out_alias }}{% endif %} |
+ {{ forward.chan_id_in }} |
+ {{ forward.chan_id_out }} |
{{ forward.fee }} |
{{ forward.ppm|intcomma }} |
diff --git a/gui/templates/home.html b/gui/templates/home.html
index ac9f2ce7..a3d6ecc9 100644
--- a/gui/templates/home.html
+++ b/gui/templates/home.html
@@ -24,7 +24,7 @@ 7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomm
Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%
Balance In Limbo: {{ limbo_balance|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }} | Pending HTLCs: {{ pending_htlc_count }}
-
+
{% if active_channels %}
@@ -52,8 +52,8 @@
Active Channels
{% for channel in active_channels %}
- {{ channel.chan_id }} |
- {{ channel.alias }} |
+ {{ channel.chan_id }} |
+ {% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %} |
{{ channel.capacity|intcomma }} |
{{ channel.local_balance|intcomma }} ({{ channel.outbound_percent }}%) |
{% if channel.inbound_percent == 0 %} {% elif channel.outbound_percent == 0 %} {% else %} {% endif %} |
@@ -113,14 +113,14 @@ Inactive Channels
Local Commit |
Local Reserve |
Downtime |
- Initiated By Me |
+ Opener |
iTarget% |
AR |
{% for channel in inactive_channels %}
- {{ channel.chan_id }} |
- {{ channel.alias }} |
+ {{ channel.chan_id }} |
+ {% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %} |
{{ channel.capacity|intcomma }} |
{{ channel.local_balance|intcomma }} |
{% if channel.inbound_percent == 0 %} {% elif channel.outbound_percent == 0 %} {% else %} {% endif %} |
@@ -133,7 +133,7 @@ Inactive Channels
{{ channel.local_commit|intcomma }} |
{{ channel.local_chan_reserve|intcomma }} |
{{ channel.last_update|naturaltime|slice:":-4" }} |
- {{ channel.initiator }} |
+ {% if channel.initiator == True %}Local{% else %}Remote{% endif %} |
{% if channel.auto_rebalance == True %}
|
@@ -309,7 +352,7 @@
{% endif %}
{% if rebalances %}
-
Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)
+
Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)
Requested |
@@ -318,6 +361,7 @@ Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{
Scheduled Duration |
Value |
Fee Limit |
+ Target PPM |
Last Hop Alias |
Status |
@@ -329,8 +373,9 @@ Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{
{{ rebalance.duration }} minutes |
{{ rebalance.value|intcomma }} |
{{ rebalance.fee_limit|intcomma }} |
+ {{ rebalance.ppm|intcomma }} |
{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %} |
- {% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+ {% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
{% endfor %}
@@ -358,8 +403,8 @@
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %} |
-
{% if payment.status == 2 %}{{ payment.chan_out_alias }}{% else %}N/A{% endif %} |
-
{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
+
{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %} |
+
{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
{% if payment.status == 2 %}Open{% else %}N/A{% endif %} |
{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %} |
@@ -390,8 +435,8 @@
{{ invoice.value|add:"0"|intcomma }} |
{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}N/A{% endif %} |
{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %} |
-
{{ invoice.chan_in_alias }} |
-
{{ invoice.chan_in }} |
+
{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}N/A{% endif %} |
+
{% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }}{% else %}N/A{% endif %} |
{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %} |
{% endfor %}
@@ -409,6 +454,7 @@
Chan In Alias |
Chan Out Alias |
Forward Amount |
+
Actual Outbound |
Potential Fee |
HTLC Failure |
Failure Detail |
@@ -416,11 +462,12 @@
{% for failed_htlc in failed_htlcs %}
{{ failed_htlc.timestamp|naturaltime }} |
- {{ failed_htlc.chan_id_in }} |
- {{ failed_htlc.chan_id_out }} |
- {{ failed_htlc.chan_in_alias }} |
- {{ failed_htlc.chan_out_alias }} |
+ {{ failed_htlc.chan_id_in }} |
+ {{ failed_htlc.chan_id_out }} |
+ {% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %} |
+ {% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %} |
{{ failed_htlc.amount|intcomma }} |
+ {{ failed_htlc.chan_out_liq|intcomma }} ({{ failed_htlc.chan_out_pending|intcomma }}) |
{{ failed_htlc.missed_fee|intcomma }} |
{% 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 %} |
{% 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 %} |
@@ -431,7 +478,7 @@
{% endif %}
{% if local_settings %}
-
Auto-Rebalancer Settings
+
Key |
@@ -469,29 +516,6 @@ Update Auto Rebalancer Settings
-
-
Manual Rebalancer Request
-
-
Connect To A Peer
@@ -544,6 +568,17 @@
Create Invoice
+
+
Update Peer Alias
+
+
+
+
Update Channel Policy
@@ -565,15 +600,4 @@
Update Channel Policy
-
-
Update Peer Alias
-
-
-
-
{% endblock %}
diff --git a/gui/templates/invoices.html b/gui/templates/invoices.html
index b46fa047..0a306efd 100644
--- a/gui/templates/invoices.html
+++ b/gui/templates/invoices.html
@@ -25,8 +25,8 @@ Last 150 Invoices
{{ invoice.value|add:"0"|intcomma }} |
{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}N/A{% endif %} |
{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %} |
- {{ invoice.chan_in_alias }} |
- {{ invoice.chan_in }} |
+ {% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}N/A{% endif %} |
+ {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }}{% else %}N/A{% endif %} |
{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %} |
{% endfor %}
diff --git a/gui/templates/keysends.html b/gui/templates/keysends.html
index 3104ab99..9f229362 100644
--- a/gui/templates/keysends.html
+++ b/gui/templates/keysends.html
@@ -5,19 +5,19 @@
{% if keysends %}
Received Keysends
-
+
- Settle Date |
- Channel In Alias |
- Amount |
+ Settle Date |
+ Channel In Alias |
+ Amount |
Message |
{% for keysend in keysends %}
{{ keysend.settle_date|naturaltime }} |
- {{ keysend.chan_in_alias }} |
+ {% if keysend.chan_in_alias == '' %}---{% else %}{{ keysend.chan_in_alias }}{% endif %} |
{{ keysend.amt_paid|intcomma }} |
- {{ keysend.message }} |
+ {{ keysend.message }}{% if keysend.sender != None %} | Signed By: {% if keysend.sender_alias != None %}{{ keysend.sender_alias }}{% else %}{{ keysend.sender }}{% endif %}{% endif %} |
{% endfor %}
diff --git a/gui/templates/open_list.html b/gui/templates/open_list.html
index 70dfcd34..0f24d3ed 100644
--- a/gui/templates/open_list.html
+++ b/gui/templates/open_list.html
@@ -20,7 +20,7 @@ Suggested Open List
{% for node in open_list %}
{{ node.node_pubkey }} |
- {{ node.alias }} |
+ {% if node.alias == '' %}---{% else %}{{ node.alias }}{% endif %} |
{{ node.count }} |
{{ node.amount|add:"0"|intcomma }} |
{{ node.fees|add:"0"|intcomma }} |
diff --git a/gui/templates/payments.html b/gui/templates/payments.html
index 48dc8173..11fd5d60 100644
--- a/gui/templates/payments.html
+++ b/gui/templates/payments.html
@@ -24,8 +24,8 @@ Last 150 Payments
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %} |
- {% if payment.status == 2 %}{{ payment.chan_out_alias }}{% else %}N/A{% endif %} |
- {% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
+ {% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %} |
+ {% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
{% if payment.status == 2 %}Open{% else %}N/A{% endif %} |
{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %} |
diff --git a/gui/templates/peers.html b/gui/templates/peers.html
index b6e797fa..92a2e7cd 100644
--- a/gui/templates/peers.html
+++ b/gui/templates/peers.html
@@ -4,10 +4,11 @@
{% load humanize %}
{% if peers %}
-
Peers List
+
Peers List ({{ num_peers }})
Peer PubKey |
+ Peer Alias |
Network Address |
Inbound |
Sats Sent |
@@ -16,6 +17,7 @@ Peers List
{% for peer in peers %}
{{ peer.pubkey }} |
+ {% if peer.alias != '' %}{{ peer.alias }}{% else %}---{% endif %} |
{{ peer.address }} |
{{ peer.inbound }} |
{{ peer.sat_sent|intcomma }} |
diff --git a/gui/templates/pending_htlcs.html b/gui/templates/pending_htlcs.html
index ab591981..8ccf498b 100644
--- a/gui/templates/pending_htlcs.html
+++ b/gui/templates/pending_htlcs.html
@@ -18,9 +18,9 @@ Outgoing HTLCs
{% for htlc in outgoing_htlcs %}
{{ htlc.chan_id }} |
- {{ htlc.alias }} |
+ {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %} |
{{ htlc.forwarding_channel }} |
- {{ htlc.forwarding_alias }} |
+ {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
{{ htlc.amount|intcomma }} |
{{ htlc.expiration_height|intcomma }} |
{{ htlc.hash_lock }} |
@@ -45,9 +45,9 @@ Incoming HTLCs
{% for htlc in incoming_htlcs %}
{{ htlc.chan_id }} |
- {{ htlc.alias }} |
+ {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %} |
{{ htlc.forwarding_channel }} |
- {{ htlc.forwarding_alias }} |
+ {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
{{ htlc.amount|intcomma }} |
{{ htlc.expiration_height|intcomma }} |
{{ htlc.hash_lock }} |
diff --git a/gui/templates/rebalancing.html b/gui/templates/rebalancing.html
new file mode 100644
index 00000000..b7b7df1e
--- /dev/null
+++ b/gui/templates/rebalancing.html
@@ -0,0 +1,196 @@
+{% extends "base.html" %}
+{% block title %} {{ block.super }} - Rebalancing{% endblock %}
+{% block content %}
+{% load humanize %}
+{% if channels %}
+
+
Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)
+
+
+
+ Channel ID |
+ Peer Alias |
+ Capacity |
+ Outbound Liquidity |
+ |
+ Inbound Liquidity |
+ Rebal Out? |
+ Enabled? |
+ Fee Ratio |
+ Rebal In? |
+ Target Amt |
+ Max Cost % |
+ oTarget% |
+ iTarget% |
+ AR |
+ 7-Day Rate |
+ Active |
+
+ {% for channel in channels %}
+
+ {{ channel.chan_id }} |
+ {% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %} |
+ {{ channel.capacity|intcomma }} |
+ {{ channel.local_balance|intcomma }} ({{ channel.percent_outbound }}%) |
+ {% if channel.percent_inbound == 0 %} {% elif channel.percent_outbound == 0 %} {% else %} {% endif %} |
+ {{ channel.remote_balance|intcomma }} ({{ channel.percent_inbound }}%) |
+ = channel.ar_out_target and channel.auto_rebalance == False %}style="background-color: #a6dce2">True{% else %}style="background-color: #fadbd5">False{% endif %} |
+ True{% else %}style="background-color: #fadbd5">False{% endif %} |
+ {{ channel.fee_ratio }}%{% else %}style="background-color: #fadbd5">{{ channel.fee_ratio }}%{% endif %} |
+ = 1 and channel.fee_check < 100 and channel.auto_rebalance == True %}style="background-color: #a6dce2">True ({{ channel.steps }}){% else %}style="background-color: #fadbd5">False{% endif %} |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+ {{ channel.success_rate }}% ({{ channel.success }}/{{ channel.attempts }}) |
+ True{% else %}style="background-color: #fadbd5">False{% endif %} |
+
+ {% endfor %}
+
+
+
+{% endif %}
+{% if not channels %}
+
+
You dont have any channels to rebalance yet.
+
+{% endif %}
+{% if rebalancer %}
+
+
Last 20 Rebalance Requests
+
+
+ Requested |
+ Start |
+ Stop |
+ Scheduled Duration |
+ Value |
+ Fee Limit |
+ Target PPM |
+ Last Hop Alias |
+ Status |
+
+ {% for rebalance in rebalancer %}
+
+ {{ rebalance.requested|naturaltime }} |
+ N/A{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} |
+ 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>N/A{% endif %} |
+ {{ rebalance.duration }} minutes |
+ {{ rebalance.value|intcomma }} |
+ {{ rebalance.fee_limit|intcomma }} |
+ {{ rebalance.ppm|intcomma }} |
+ {% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %} |
+ {% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if not rebalancer %}
+
+
You dont have any rebalancer requests yet.
+
+{% endif %}
+{% if local_settings %}
+
+
Auto-Rebalancer Settings
+
+
+ Key |
+ Value |
+
+ {% for settings in local_settings %}
+
+ {{ settings.key }} |
+ {{ settings.value|intcomma }} |
+
+ {% endfor %}
+
+
+{% endif %}
+
+
Update Auto Rebalancer Settings
+
+
+
+
+{% if channels %}
+
+
Manual Rebalancer Request
+
+
+{% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/gui/templates/resolutions.html b/gui/templates/resolutions.html
new file mode 100644
index 00000000..2a6afdbc
--- /dev/null
+++ b/gui/templates/resolutions.html
@@ -0,0 +1,35 @@
+{% extends "base.html" %}
+{% block title %} {{ block.super }} - Resolutions{% endblock %}
+{% block content %}
+{% load humanize %}
+{% if resolutions %}
+
+
Resolutions For Channel: {{ chan_id }}
+
+
+ Channel ID |
+ Resolution Type |
+ Outcome |
+ Outpoint |
+ Amount |
+ Sweep TX |
+
+ {% for resolution in resolutions %}
+
+ {{ resolution.chan_id.chan_id }} |
+ {% if resolution.resolution_type == 0 %}Unknown{% elif resolution.resolution_type == 1 %}Anchor{% elif resolution.resolution_type == 2 %}Incoming HTLC{% elif resolution.resolution_type == 3 %}Outgoing HTLC{% elif resolution.resolution_type == 4 %}Commit{% else %}{{ resolution.resolution_type }}{% endif %} |
+ {% if resolution.outcome == 0 %}Unknown{% elif resolution.outcome == 1 %}Claimed{% elif resolution.outcome == 2 %}Unclaimed{% elif resolution.outcome == 3 %}Abandoned{% elif resolution.outcome == 4 %}First Stage{% elif resolution.outcome == 5 %}Timeout{% else %}{{ resolution.outcome }}{% endif %} |
+ {{ resolution.outpoint_tx }}:{{ resolution.outpoint_index }} |
+ {{ resolution.amount_sat|intcomma }} |
+ {{ resolution.sweep_txid }} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if not resolutions %}
+
+
No resolutions were found for this channel!
+
+{% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/gui/templates/route.html b/gui/templates/route.html
index dce393c5..e584834d 100644
--- a/gui/templates/route.html
+++ b/gui/templates/route.html
@@ -19,11 +19,11 @@ Route For Payment: {{ payment_hash }}
{% for hop in route %}
{{ hop.step }} |
- {{ hop.amt }} |
- {{ hop.fee }} |
- {{ hop.ppm }} |
- {{ hop.cost_to }} |
- {{ hop.alias }} |
+ {{ hop.amt|intcomma }} |
+ {{ hop.fee|intcomma }} |
+ {{ hop.ppm|intcomma }} |
+ {{ hop.cost_to|intcomma }} |
+ {% if hop.alias == '' %}---{% else %}{{ hop.alias }}{% endif %} |
{{ hop.chan_id }} |
{{ hop.chan_capacity|intcomma }} |
diff --git a/gui/urls.py b/gui/urls.py
index 714725c0..3d57b4e6 100644
--- a/gui/urls.py
+++ b/gui/urls.py
@@ -22,11 +22,14 @@
path('peers', views.peers, name='peers'),
path('balances', views.balances, name='balances'),
path('closures', views.closures, name='closures'),
+ path('resolutions', views.resolutions, name='resolutions'),
+ path('channel', views.channel, name='channel'),
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('rebalancing', views.rebalancing, name='rebalancing'),
path('openchannel/', views.open_channel_form, name='open-channel-form'),
path('closechannel/', views.close_channel_form, name='close-channel-form'),
path('connectpeer/', views.connect_peer_form, name='connect-peer-form'),
diff --git a/gui/views.py b/gui/views.py
index c1d8046d..15caa40f 100644
--- a/gui/views.py
+++ b/gui/views.py
@@ -1,6 +1,6 @@
from django.contrib import messages
from django.shortcuts import get_object_or_404, render, redirect
-from django.db.models import Sum, IntegerField, Count, F
+from django.db.models import Sum, IntegerField, Count, F, Q
from django.db.models.functions import Round
from django.contrib.auth.decorators import login_required
from django.conf import settings
@@ -9,8 +9,8 @@
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm
-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 .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot
+from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer
from .lnd_deps import lightning_pb2 as ln
from .lnd_deps import lightning_pb2_grpc as lnrpc
from gui.lnd_deps import router_pb2 as lnr
@@ -18,7 +18,7 @@
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
+from pandas import DataFrame, merge
def graph_links():
if LocalSettings.objects.filter(key='GUI-GraphLinks').exists():
@@ -69,7 +69,7 @@ def home(request):
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')
+ active_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).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']
@@ -86,17 +86,16 @@ def home(request):
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']
+ pending_htlc_count = Channels.objects.filter(is_open=True).aggregate(Sum('htlc_count'))['htlc_count__sum'] if Channels.objects.filter(is_open=True).exists() else 0
+ pending_outbound = Channels.objects.filter(is_open=True).aggregate(Sum('pending_outbound'))['pending_outbound__sum'] if Channels.objects.filter(is_open=True).exists() else 0
detailed_active_channels = []
for channel in active_channels:
detailed_channel = {}
detailed_channel['remote_pubkey'] = channel.remote_pubkey
detailed_channel['chan_id'] = channel.chan_id
detailed_channel['capacity'] = channel.capacity
- detailed_channel['local_balance'] = channel.local_balance
- detailed_channel['remote_balance'] = channel.remote_balance
+ detailed_channel['local_balance'] = channel.local_balance + channel.pending_outbound
+ detailed_channel['remote_balance'] = channel.remote_balance + channel.pending_inbound
detailed_channel['unsettled_balance'] = channel.unsettled_balance
detailed_channel['initiator'] = channel.initiator
detailed_channel['alias'] = channel.alias
@@ -119,21 +118,23 @@ def home(request):
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['htlc_count'] = channel.htlc_count
detailed_channel['auto_rebalance'] = channel.auto_rebalance
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('outbound_percent')
+ inactive_channels = Channels.objects.filter(is_active=False, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).order_by('outbound_percent')
+ private_channels = Channels.objects.filter(is_open=True, private=True).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).order_by('outbound_percent')
inactive_outbound = 0 if inactive_channels.count() == 0 else inactive_channels.aggregate(Sum('local_balance'))['local_balance__sum']
- sum_outbound = total_outbound + pending_outbound + inactive_outbound
+ private_outbound = 0 if private_channels.count() == 0 else private_channels.aggregate(Sum('local_balance'))['local_balance__sum']
+ sum_outbound = total_outbound + pending_outbound + inactive_outbound + private_outbound
onchain_txs = Onchain.objects.all()
onchain_costs = 0 if onchain_txs.count() == 0 else onchain_txs.aggregate(Sum('fee'))['fee__sum']
onchain_costs_7day = 0 if onchain_txs.filter(time_stamp__gte=filter_7day).count() == 0 else onchain_txs.filter(time_stamp__gte=filter_7day).aggregate(Sum('fee'))['fee__sum']
total_costs = total_fees + onchain_costs
total_costs_7day = total_7day_fees + onchain_costs_7day
#Get list of recent rebalance requests
- rebalances = Rebalancer.objects.all().order_by('-requested')
+ rebalances = Rebalancer.objects.all().annotate(ppm=(Sum('fee_limit')*1000000)/Sum('value')).order_by('-id')
total_channels = node_info.num_active_channels + node_info.num_inactive_channels
local_settings = LocalSettings.objects.filter(key__contains='AR-')
try:
@@ -174,22 +175,22 @@ def home(request):
'unsettled': total_unsettled,
'limbo_balance': limbo_balance,
'inactive_channels': inactive_channels,
+ 'private_channels': private_channels,
'pending_open': pending_open,
'pending_closed': pending_closed,
'pending_force_closed': pending_force_closed,
'waiting_for_close': waiting_for_close,
'rebalances': rebalances[:12],
- 'rebalancer_form': RebalancerForm,
'chan_policy_form': ChanPolicyForm,
'local_settings': local_settings,
'pending_htlc_count': pending_htlc_count,
- 'failed_htlcs': FailedHTLCs.objects.all().order_by('-timestamp')[:10],
+ 'failed_htlcs': FailedHTLCs.objects.all().order_by('-id')[:10],
'payments_ppm': 0 if total_sent == 0 else int((total_fees/total_sent)*1000000),
'routed_ppm': 0 if total_value_forwards == 0 else int((total_earned/total_value_forwards)*1000000),
'7day_routed_ppm': 0 if routed_7day_amt == 0 else int((total_earned_7day/routed_7day_amt)*1000000),
'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),
- 'eligible_count': Channels.objects.filter(is_active=True, is_open=True, auto_rebalance=True).annotate(inbound_can=(Sum('remote_balance')*100)/Sum('capacity')).annotate(fee_ratio=(Sum('remote_fee_rate')*100)/Sum('local_fee_rate')).filter(inbound_can__gte=F('ar_in_target'), fee_ratio__lte=F('ar_max_cost')).count(),
+ 'eligible_count': Channels.objects.filter(is_active=True, is_open=True, private=False, auto_rebalance=True).annotate(inbound_can=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).annotate(fee_ratio=(Sum('remote_fee_rate')*100)/Sum('local_fee_rate')).filter(inbound_can__gte=F('ar_in_target'), fee_ratio__lte=F('ar_max_cost')).count(),
'enabled_count': Channels.objects.filter(is_open=True, auto_rebalance=True).count(),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
@@ -206,9 +207,9 @@ def channels(request):
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'))
+ payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_30day).filter(rebal_chan__isnull=False)
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 = Channels.objects.filter(is_open=True, private=False)
channels_df = DataFrame.from_records(channels.values())
if channels_df.shape[0] > 0:
forwards_df_30d = DataFrame.from_records(forwards.values())
@@ -236,6 +237,8 @@ def channels(request):
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['local_balance'] = channels_df.apply(lambda row: row.local_balance + row.pending_outbound, axis=1)
+ channels_df['remote_balance'] = channels_df.apply(lambda row: row.remote_balance + row.pending_inbound, 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)
@@ -260,11 +263,24 @@ def channels(request):
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: row['revenue_7day'] - row['costs_7day'], axis=1)
channels_df['profits_30day'] = channels_df.apply(lambda row: row['revenue_30day'] - row['costs_30day'], axis=1)
- channels_df['open_block'] = channels_df.apply(lambda row: row.chan_id>>40, axis=1)
+ channels_df['open_block'] = channels_df.apply(lambda row: int(row.chan_id)>>40, axis=1)
apy_7day = round((channels_df['profits_7day'].sum()*5214.2857)/channels_df['local_balance'].sum(), 2)
apy_30day = round((channels_df['profits_30day'].sum()*1216.6667)/channels_df['local_balance'].sum(), 2)
active_updates = channels_df['num_updates'].sum()
channels_df['updates'] = channels_df.apply(lambda row: 0 if active_updates == 0 else int(round((row['num_updates']/active_updates)*100, 0)), axis=1)
+ node_outbound = channels_df['local_balance'].sum()
+ node_capacity = channels_df['capacity'].sum()
+ if node_capacity > 0:
+ outbound_ratio = node_outbound/node_capacity
+ channels_df['apy_7day'] = channels_df.apply(lambda row: round((row['profits_7day']*5214.2857)/(row['capacity']*outbound_ratio), 2), axis=1)
+ channels_df['apy_30day'] = channels_df.apply(lambda row: round((row['profits_30day']*1216.6667)/(row['capacity']*outbound_ratio), 2), axis=1)
+ else:
+ channels_df['attempts_30day'] = 0
+ channels_df['success_30day'] = 0
+ channels_df['success_rate_30day'] = 0
+ channels_df['attempts_7day'] = 0
+ channels_df['success_7day'] = 0
+ channels_df['success_rate_7day'] = 0
context = {
'channels': channels_df.to_dict(orient='records'),
'apy_7day': apy_7day,
@@ -281,13 +297,18 @@ def channels(request):
def fees(request):
if request.method == 'GET':
filter_7day = datetime.now() - timedelta(days=7)
- forwards = Forwards.objects.filter(forward_date__gte=filter_7day, amt_out_msat__gte=1000000)
channels = Channels.objects.filter(is_open=True)
channels_df = DataFrame.from_records(channels.values())
if channels_df.shape[0] > 0:
+ failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(timestamp__gte=filter_7day).order_by('-id').values())
+ if failed_htlc_df.shape[0] > 0:
+ failed_htlc_df = failed_htlc_df[failed_htlc_df['wire_failure']==15][failed_htlc_df['failure_detail']==6][failed_htlc_df['amount']>failed_htlc_df['chan_out_liq']+failed_htlc_df['chan_out_pending']]
+ forwards = Forwards.objects.filter(forward_date__gte=filter_7day, amt_out_msat__gte=1000000)
forwards_df_7d = DataFrame.from_records(forwards.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()
+ channels_df['local_balance'] = channels_df.apply(lambda row: row.local_balance + row.pending_outbound, axis=1)
+ channels_df['remote_balance'] = channels_df.apply(lambda row: row.remote_balance + row.pending_inbound, axis=1)
channels_df['in_percent'] = channels_df.apply(lambda row: int(round((row['remote_balance']/row['capacity'])*100, 0)), axis=1)
channels_df['out_percent'] = channels_df.apply(lambda row: int(round((row['local_balance']/row['capacity'])*100, 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/1000) if (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1)
@@ -296,7 +317,8 @@ def fees(request):
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_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['out_rate'] = channels_df.apply(lambda row: int((row['revenue_7day']/row['amt_routed_out_7day'])*1000000) if row['amt_routed_out_7day'] > 0 else 0, axis=1)
- payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_7day).filter(payment_hash__in=Invoices.objects.filter(state=1).filter(settle_date__gte=filter_7day).values_list('r_hash'))
+ channels_df['failed_out_7day'] = 0 if failed_htlc_df.empty else channels_df.apply(lambda row: len(failed_htlc_df[failed_htlc_df['chan_id_out']==row.chan_id]), axis=1)
+ payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_7day).filter(rebal_chan__isnull=False)
invoices = Invoices.objects.filter(state=1).filter(settle_date__gte=filter_7day).filter(r_hash__in=payments.values_list('payment_hash'))
payments_df_7d = DataFrame.from_records(payments.filter(creation_date__gte=filter_7day).values())
invoices_df_7d = DataFrame.from_records(invoices.filter(settle_date__gte=filter_7day).values())
@@ -306,7 +328,9 @@ def fees(request):
channels_df['costs_7day'] = channels_df.apply(lambda row: 0 if row['amt_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['rebal_ppm'] = channels_df.apply(lambda row: int((row['costs_7day']/row['amt_rebal_in_7day'])*1000000) if row['amt_rebal_in_7day'] > 0 else 0, axis=1)
channels_df['max_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*1.15) if row['in_percent'] > 25 else int(row['local_fee_rate']), axis=1)
+ channels_df['max_suggestion'] = channels_df.apply(lambda row: row['local_fee_rate']+25 if row['max_suggestion'] > (row['local_fee_rate']+25) else row['max_suggestion'], axis=1)
channels_df['min_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*0.75) if row['out_percent'] > 25 else int(row['local_fee_rate']), axis=1)
+ channels_df['min_suggestion'] = channels_df.apply(lambda row: row['local_fee_rate']-50 if row['min_suggestion'] < (row['local_fee_rate']-50) else row['min_suggestion'], axis=1)
channels_df['assisted_ratio'] = channels_df.apply(lambda row: round((row['revenue_assist_7day'] if row['revenue_7day'] == 0 else row['revenue_assist_7day']/row['revenue_7day']), 2), axis=1)
channels_df['profit_margin'] = channels_df.apply(lambda row: row['out_rate']*((100-row['ar_max_cost'])/100), axis=1)
channels_df['adjusted_out_rate'] = channels_df.apply(lambda row: int(row['out_rate']+row['net_routed_7day']*row['assisted_ratio']), axis=1)
@@ -332,12 +356,14 @@ def fees(request):
@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 = Channels.objects.filter(is_open=True).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by('-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)
- channels_df['fee_ratio'] = channels_df.apply(lambda row: 0 if row['local_fee_rate'] == 0 else int(round(((row['remote_fee_rate']/row['local_fee_rate'])*1000)/10, 0)), axis=1)
+ channels_df['local_balance'] = channels_df.apply(lambda row: row.local_balance + row.pending_outbound, axis=1)
+ channels_df['remote_balance'] = channels_df.apply(lambda row: row.remote_balance + row.pending_inbound, axis=1)
+ channels_df['fee_ratio'] = channels_df.apply(lambda row: 100 if row['local_fee_rate'] == 0 else int(round(((row['remote_fee_rate']/row['local_fee_rate'])*1000)/10, 0)), axis=1)
context = {
'channels': channels_df.to_dict(orient='records'),
'local_settings': LocalSettings.objects.all(),
@@ -364,8 +390,10 @@ def route(request):
@login_required(login_url='/lndg-admin/login/?next=/')
def peers(request):
if request.method == 'GET':
+ peers = Peers.objects.filter(connected=True)
context = {
- 'peers': Peers.objects.filter(connected=True),
+ 'peers': peers,
+ 'num_peers': len(peers),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
@@ -390,16 +418,351 @@ def balances(request):
@login_required(login_url='/lndg-admin/login/?next=/')
def closures(request):
if request.method == 'GET':
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ closures_df = DataFrame.from_records(Closures.objects.all().values())
+ channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias'))
+ merged = merge(closures_df, channels_df, on='chan_id', how='left')
+ merged['alias'] = merged['alias'].fillna('')
context = {
- 'closures': stub.ClosedChannels(ln.ClosedChannelsRequest()).channels[::-1],
+ 'closures': merged.sort_values(by=['close_height'], ascending=False).to_dict(orient='records'),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
- 'network_links': network_links()
+ 'network_links': network_links(),
+ 'graph_links': graph_links()
}
return render(request, 'closures.html', context)
else:
return redirect('home')
+@login_required(login_url='/lndg-admin/login/?next=/')
+def resolutions(request):
+ if request.method == 'GET':
+ chan_id = request.GET.urlencode()[1:]
+ context = {
+ 'chan_id': chan_id,
+ 'resolutions': Resolutions.objects.filter(chan_id=chan_id),
+ 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network_links': network_links()
+ }
+ return render(request, 'resolutions.html', context)
+ else:
+ return redirect('home')
+
+@login_required(login_url='/lndg-admin/login/?next=/')
+def channel(request):
+ if request.method == 'GET':
+ chan_id = request.GET.urlencode()[1:]
+ if Channels.objects.filter(chan_id=chan_id).exists():
+ filter_1day = datetime.now() - timedelta(days=1)
+ filter_7day = datetime.now() - timedelta(days=7)
+ filter_30day = datetime.now() - timedelta(days=30)
+ forwards_df = DataFrame.from_records(Forwards.objects.filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).values())
+ payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(chan_out=chan_id).filter(rebal_chan__isnull=False).values())
+ invoices_df = DataFrame.from_records(Invoices.objects.filter(state=1).filter(chan_in=chan_id).filter(r_hash__in=Payments.objects.filter(status=2).filter(rebal_chan=chan_id)).values())
+ channels_df = DataFrame.from_records(Channels.objects.filter(is_open=True).values())
+ node_outbound = channels_df['local_balance'].sum()
+ node_capacity = channels_df['capacity'].sum()
+ channels_df = DataFrame.from_records(Channels.objects.filter(chan_id=chan_id).values())
+ rebalancer_df = DataFrame.from_records(Rebalancer.objects.filter(last_hop_pubkey=channels_df['remote_pubkey'][0]).annotate(ppm=(Sum('fee_limit')*1000000)/Sum('value')).order_by('-id').values())
+ failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).order_by('-id').values())
+ channels_df['local_balance'] = channels_df['local_balance'] + channels_df['pending_outbound']
+ channels_df['remote_balance'] = channels_df['remote_balance'] + channels_df['pending_inbound']
+ channels_df['in_percent'] = int(round((channels_df['remote_balance']/channels_df['capacity'])*100, 0))
+ channels_df['out_percent'] = int(round((channels_df['local_balance']/channels_df['capacity'])*100, 0))
+ channels_df['open_block'] = int(channels_df.chan_id)>>40
+ channels_df['routed_in'] = 0
+ channels_df['routed_in_30day'] = 0
+ channels_df['routed_in_7day'] = 0
+ channels_df['routed_in_1day'] = 0
+ channels_df['routed_out'] = 0
+ channels_df['routed_out_30day'] = 0
+ channels_df['routed_out_7day'] = 0
+ channels_df['routed_out_1day'] = 0
+ channels_df['amt_routed_in'] = 0
+ channels_df['amt_routed_in_30day'] = 0
+ channels_df['amt_routed_in_7day'] = 0
+ channels_df['amt_routed_in_1day'] = 0
+ channels_df['amt_routed_out'] = 0
+ channels_df['amt_routed_out_30day'] = 0
+ channels_df['amt_routed_out_7day'] = 0
+ channels_df['amt_routed_out_1day'] = 0
+ channels_df['average_in'] = 0
+ channels_df['average_in_30day'] = 0
+ channels_df['average_in_7day'] = 0
+ channels_df['average_in_1day'] = 0
+ channels_df['average_out'] = 0
+ channels_df['average_out_30day'] = 0
+ channels_df['average_out_7day'] = 0
+ channels_df['average_out_1day'] = 0
+ channels_df['revenue'] = 0
+ channels_df['revenue_30day'] = 0
+ channels_df['revenue_7day'] = 0
+ channels_df['revenue_1day'] = 0
+ channels_df['revenue_assist'] = 0
+ channels_df['revenue_assist_30day'] = 0
+ channels_df['revenue_assist_7day'] = 0
+ channels_df['revenue_assist_1day'] = 0
+ channels_df['rebal_out'] = 0
+ channels_df['rebal_out_30day'] = 0
+ channels_df['rebal_out_7day'] = 0
+ channels_df['rebal_out_1day'] = 0
+ channels_df['amt_rebal_out'] = 0
+ channels_df['amt_rebal_out_30day'] = 0
+ channels_df['amt_rebal_out_7day'] = 0
+ channels_df['amt_rebal_out_1day'] = 0
+ channels_df['rebal_in'] = 0
+ channels_df['rebal_in_30day'] = 0
+ channels_df['rebal_in_7day'] = 0
+ channels_df['rebal_in_1day'] = 0
+ channels_df['amt_rebal_in'] = 0
+ channels_df['amt_rebal_in_30day'] = 0
+ channels_df['amt_rebal_in_7day'] = 0
+ channels_df['amt_rebal_in_1day'] = 0
+ channels_df['costs'] = 0
+ channels_df['costs_30day'] = 0
+ channels_df['costs_7day'] = 0
+ channels_df['costs_1day'] = 0
+ channels_df['attempts'] = 0
+ channels_df['attempts_30day'] = 0
+ channels_df['attempts_7day'] = 0
+ channels_df['attempts_1day'] = 0
+ channels_df['success'] = 0
+ channels_df['success_30day'] = 0
+ channels_df['success_7day'] = 0
+ channels_df['success_1day'] = 0
+ channels_df['success_rate'] = 0
+ channels_df['success_rate_30day'] = 0
+ channels_df['success_rate_7day'] = 0
+ channels_df['success_rate_1day'] = 0
+ channels_df['failed_out'] = 0
+ channels_df['failed_out_30day'] = 0
+ channels_df['failed_out_7day'] = 0
+ channels_df['failed_out_1day'] = 0
+ start_date = None
+ if failed_htlc_df.shape[0]> 0:
+ channels_df['failed_out'] = len(failed_htlc_df[failed_htlc_df['chan_id_out']==chan_id][failed_htlc_df['wire_failure']==15][failed_htlc_df['failure_detail']==6][failed_htlc_df['amount']>failed_htlc_df['chan_out_liq']+failed_htlc_df['chan_out_pending']])
+ failed_htlc_df_30d = failed_htlc_df.loc[failed_htlc_df['timestamp'] >= filter_30day]
+ if failed_htlc_df_30d.shape[0]> 0:
+ channels_df['failed_out_30day'] = len(failed_htlc_df_30d[failed_htlc_df_30d['chan_id_out']==chan_id][failed_htlc_df_30d['wire_failure']==15][failed_htlc_df_30d['failure_detail']==6][failed_htlc_df_30d['amount']>failed_htlc_df_30d['chan_out_liq']+failed_htlc_df_30d['chan_out_pending']])
+ failed_htlc_df_7d = failed_htlc_df_30d.loc[failed_htlc_df_30d['timestamp'] >= filter_7day]
+ if failed_htlc_df_7d.shape[0]> 0:
+ channels_df['failed_out_7day'] = len(failed_htlc_df_7d[failed_htlc_df_7d['chan_id_out']==chan_id][failed_htlc_df_7d['wire_failure']==15][failed_htlc_df_7d['failure_detail']==6][failed_htlc_df_7d['amount']>failed_htlc_df_7d['chan_out_liq']+failed_htlc_df_7d['chan_out_pending']])
+ failed_htlc_df_1d = failed_htlc_df_7d.loc[failed_htlc_df_7d['timestamp'] >= filter_1day]
+ if failed_htlc_df_1d.shape[0]> 0:
+ channels_df['failed_out_1day'] = len(failed_htlc_df_1d[failed_htlc_df_1d['chan_id_out']==chan_id][failed_htlc_df_1d['wire_failure']==15][failed_htlc_df_1d['failure_detail']==6][failed_htlc_df_1d['amount']>failed_htlc_df_1d['chan_out_liq']+failed_htlc_df_1d['chan_out_pending']])
+ if rebalancer_df.shape[0]> 0:
+ channels_df['attempts'] = len(rebalancer_df[rebalancer_df['status']>=2][rebalancer_df['status']<400])
+ channels_df['success'] = len(rebalancer_df[rebalancer_df['status']==2])
+ channels_df['success_rate'] = 0 if channels_df['attempts'][0] == 0 else int((channels_df['success']/channels_df['attempts'])*100)
+ rebalancer_df_30d = rebalancer_df.loc[rebalancer_df['stop'] >= filter_30day]
+ if rebalancer_df_30d.shape[0]> 0:
+ channels_df['attempts_30day'] = len(rebalancer_df_30d[rebalancer_df_30d['status']>=2][rebalancer_df_30d['status']<400])
+ channels_df['success_30day'] = len(rebalancer_df_30d[rebalancer_df_30d['status']==2])
+ channels_df['success_rate_30day'] = 0 if channels_df['attempts_30day'][0] == 0 else int((channels_df['success_30day']/channels_df['attempts_30day'])*100)
+ rebalancer_df_7d = rebalancer_df_30d.loc[rebalancer_df_30d['stop'] >= filter_7day]
+ if rebalancer_df_7d.shape[0]> 0:
+ channels_df['attempts_7day'] = len(rebalancer_df_7d[rebalancer_df_7d['status']>=2][rebalancer_df_7d['status']<400])
+ channels_df['success_7day'] = len(rebalancer_df_7d[rebalancer_df_7d['status']==2])
+ channels_df['success_rate_7day'] = 0 if channels_df['attempts_7day'][0] == 0 else int((channels_df['success_7day']/channels_df['attempts_7day'])*100)
+ rebalancer_df_1d = rebalancer_df_7d.loc[rebalancer_df_7d['stop'] >= filter_1day]
+ if rebalancer_df_1d.shape[0]> 0:
+ channels_df['attempts_1day'] = len(rebalancer_df_1d[rebalancer_df_1d['status']>=2][rebalancer_df_1d['status']<400])
+ channels_df['success_1day'] = len(rebalancer_df_1d[rebalancer_df_1d['status']==2])
+ channels_df['success_rate_1day'] = 0 if channels_df['attempts_1day'][0] == 0 else int((channels_df['success_1day']/channels_df['attempts_1day'])*100)
+ if forwards_df.shape[0]> 0:
+ forwards_in_df = forwards_df[forwards_df['chan_id_in'] == chan_id]
+ forwards_out_df = forwards_df[forwards_df['chan_id_out'] == chan_id]
+ forwards_df['amt_in'] = (forwards_df['amt_in_msat']/1000).astype(int)
+ forwards_df['amt_out'] = (forwards_df['amt_out_msat']/1000).astype(int)
+ forwards_df['ppm'] = (forwards_df['fee']/(forwards_df['amt_out']/1000000)).astype(int)
+ else:
+ forwards_in_df = DataFrame()
+ forwards_out_df = DataFrame()
+ if forwards_in_df.shape[0]> 0:
+ forwards_in_df_count = forwards_in_df.groupby('chan_id_in', as_index=True).count()
+ forwards_in_df_sum = forwards_in_df.groupby('chan_id_in', as_index=True).sum()
+ channels_df['routed_in'] = forwards_in_df_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_in'] = int(forwards_in_df_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_in'] = 0 if channels_df['routed_in'][0] == 0 else int(channels_df['amt_routed_in']/channels_df['routed_in'])
+ channels_df['revenue_assist'] = int(forwards_in_df_sum.loc[chan_id].fee) if forwards_in_df_sum.empty == False else 0
+ forwards_in_df_30d = forwards_in_df.loc[forwards_in_df['forward_date'] >= filter_30day]
+ if forwards_in_df_30d.shape[0] > 0:
+ forwards_in_df_30d_count = forwards_in_df_30d.groupby('chan_id_in', as_index=True).count()
+ forwards_in_df_30d_sum = forwards_in_df_30d.groupby('chan_id_in', as_index=True).sum()
+ channels_df['routed_in_30day'] = forwards_in_df_30d_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_in_30day'] = int(forwards_in_df_30d_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_in_30day'] = 0 if channels_df['routed_in_30day'][0] == 0 else int(channels_df['amt_routed_in_30day']/channels_df['routed_in_30day'])
+ channels_df['revenue_assist_30day'] = int(forwards_in_df_30d_sum.loc[chan_id].fee) if forwards_in_df_30d_sum.empty == False else 0
+ forwards_in_df_7d = forwards_in_df_30d.loc[forwards_in_df_30d['forward_date'] >= filter_7day]
+ if forwards_in_df_7d.shape[0] > 0:
+ forwards_in_df_7d_count = forwards_in_df_7d.groupby('chan_id_in', as_index=True).count()
+ forwards_in_df_7d_sum = forwards_in_df_7d.groupby('chan_id_in', as_index=True).sum()
+ channels_df['routed_in_7day'] = forwards_in_df_7d_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_in_7day'] = int(forwards_in_df_7d_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_in_7day'] = 0 if channels_df['routed_in_7day'][0] == 0 else int(channels_df['amt_routed_in_7day']/channels_df['routed_in_7day'])
+ channels_df['revenue_assist_7day'] = int(forwards_in_df_7d_sum.loc[chan_id].fee)
+ forwards_in_df_1d = forwards_in_df_7d.loc[forwards_in_df_7d['forward_date'] >= filter_1day]
+ if forwards_in_df_1d.shape[0] > 0:
+ forwards_in_df_1d_count = forwards_in_df_1d.groupby('chan_id_in', as_index=True).count()
+ forwards_in_df_1d_sum = forwards_in_df_1d.groupby('chan_id_in', as_index=True).sum()
+ channels_df['routed_in_1day'] = forwards_in_df_1d_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_in_1day'] = int(forwards_in_df_1d_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_in_1day'] = 0 if channels_df['routed_in_1day'][0] == 0 else int(channels_df['amt_routed_in_1day']/channels_df['routed_in_1day'])
+ channels_df['revenue_assist_1day'] = int(forwards_in_df_1d_sum.loc[chan_id].fee)
+ if forwards_out_df.shape[0]> 0:
+ start_date = forwards_out_df['forward_date'].min()
+ forwards_out_df_out_count = forwards_out_df.groupby('chan_id_out', as_index=True).count()
+ forwards_out_df_out_sum = forwards_out_df.groupby('chan_id_out', as_index=True).sum()
+ channels_df['routed_out'] = forwards_out_df_out_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_out'] = int(forwards_out_df_out_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_out'] = 0 if channels_df['routed_out'][0] == 0 else int(channels_df['amt_routed_out']/channels_df['routed_out'])
+ channels_df['revenue'] = int(forwards_out_df_out_sum.loc[chan_id].fee) if forwards_out_df_out_sum.empty == False else 0
+ forwards_out_df_30d = forwards_out_df.loc[forwards_out_df['forward_date'] >= filter_30day]
+ if forwards_out_df_30d.shape[0] > 0:
+ forwards_out_df_out_30d_count = forwards_out_df_30d.groupby('chan_id_out', as_index=True).count()
+ forwards_out_df_out_30d_sum = forwards_out_df_30d.groupby('chan_id_out', as_index=True).sum()
+ channels_df['routed_out_30day'] = forwards_out_df_out_30d_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_out_30day'] = int(forwards_out_df_out_30d_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_out_30day'] = 0 if channels_df['routed_out_30day'][0] == 0 else int(channels_df['amt_routed_out_30day']/channels_df['routed_out_30day'])
+ channels_df['revenue_30day'] = int(forwards_out_df_out_30d_sum.loc[chan_id].fee) if forwards_out_df_out_30d_sum.empty == False else 0
+ forwards_out_df_7d = forwards_out_df_30d.loc[forwards_out_df_30d['forward_date'] >= filter_7day]
+ if forwards_out_df_7d.shape[0] > 0:
+ forwards_out_df_out_7d_count = forwards_out_df_7d.groupby('chan_id_out', as_index=True).count()
+ forwards_out_df_out_7d_sum = forwards_out_df_7d.groupby('chan_id_out', as_index=True).sum()
+ channels_df['routed_out_7day'] = forwards_out_df_out_7d_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_out_7day'] = int(forwards_out_df_out_7d_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_out_7day'] = 0 if channels_df['routed_out_7day'][0] == 0 else int(channels_df['amt_routed_out_7day']/channels_df['routed_out_7day'])
+ channels_df['revenue_7day'] = int(forwards_out_df_out_7d_sum.loc[chan_id].fee)
+ forwards_out_df_1d = forwards_out_df_7d.loc[forwards_out_df_7d['forward_date'] >= filter_1day]
+ if forwards_out_df_1d.shape[0] > 0:
+ forwards_out_df_out_1d_count = forwards_out_df_1d.groupby('chan_id_out', as_index=True).count()
+ forwards_out_df_out_1d_sum = forwards_out_df_1d.groupby('chan_id_out', as_index=True).sum()
+ channels_df['routed_out_1day'] = forwards_out_df_out_1d_count.loc[chan_id].amt_out_msat
+ channels_df['amt_routed_out_1day'] = int(forwards_out_df_out_1d_sum.loc[chan_id].amt_out_msat/1000)
+ channels_df['average_out_1day'] = 0 if channels_df['routed_out_1day'][0] == 0 else int(channels_df['amt_routed_out_1day']/channels_df['routed_out_1day'])
+ channels_df['revenue_1day'] = int(forwards_out_df_out_1d_sum.loc[chan_id].fee)
+ if payments_df.shape[0] > 0:
+ payments_df_count = payments_df.groupby('chan_out', as_index=True).count()
+ payments_df_sum = payments_df.groupby('chan_out', as_index=True).sum()
+ channels_df['rebal_out'] = payments_df_count.loc[chan_id].value
+ channels_df['amt_rebal_out'] = int(payments_df_sum.loc[chan_id].value)
+ payments_df_30d = payments_df.loc[payments_df['creation_date'] >= filter_30day]
+ if payments_df_30d.shape[0] > 0:
+ payments_df_30d_count = payments_df_30d.groupby('chan_out', as_index=True).count()
+ payments_df_30d_sum = payments_df_30d.groupby('chan_out', as_index=True).sum()
+ channels_df['rebal_out_30day'] = payments_df_30d_count.loc[chan_id].value
+ channels_df['amt_rebal_out_30day'] = int(payments_df_30d_sum.loc[chan_id].value)
+ payments_df_7d = payments_df_30d.loc[payments_df_30d['creation_date'] >= filter_7day]
+ if payments_df_7d.shape[0] > 0:
+ payments_df_7d_count = payments_df_7d.groupby('chan_out', as_index=True).count()
+ payments_df_7d_sum = payments_df_7d.groupby('chan_out', as_index=True).sum()
+ channels_df['rebal_out_7day'] = payments_df_7d_count.loc[chan_id].value
+ channels_df['amt_rebal_out_7day'] = int(payments_df_7d_sum.loc[chan_id].value)
+ payments_df_1d = payments_df_7d.loc[payments_df_7d['creation_date'] >= filter_1day]
+ if payments_df_1d.shape[0] > 0:
+ payments_df_1d_count = payments_df_1d.groupby('chan_out', as_index=True).count()
+ payments_df_1d_sum = payments_df_1d.groupby('chan_out', as_index=True).sum()
+ channels_df['rebal_out_1day'] = payments_df_1d_count.loc[chan_id].value
+ channels_df['amt_rebal_out_1day'] = int(payments_df_1d_sum.loc[chan_id].value)
+ if invoices_df.shape[0]> 0:
+ invoices_df_30d = invoices_df.loc[invoices_df['settle_date'] >= filter_30day]
+ invoices_df_7d = invoices_df_30d.loc[invoices_df_30d['settle_date'] >= filter_7day]
+ invoices_df_1d = invoices_df_7d.loc[invoices_df_7d['settle_date'] >= filter_1day]
+ invoices_df_count = DataFrame() if invoices_df.empty else invoices_df.groupby('chan_in', as_index=True).count()
+ 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()
+ invoices_df_1d_count = DataFrame() if invoices_df_1d.empty else invoices_df_1d.groupby('chan_in', as_index=True).count()
+ invoices_df_sum = DataFrame() if invoices_df.empty else invoices_df.groupby('chan_in', as_index=True).sum()
+ 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_1d_sum = DataFrame() if invoices_df_1d.empty else invoices_df_1d.groupby('chan_in', as_index=True).sum()
+ channels_df['rebal_in'] = invoices_df_count.loc[chan_id].amt_paid if invoices_df_count.empty == False else 0
+ channels_df['rebal_in_30day'] = invoices_df_30d_count.loc[chan_id].amt_paid if invoices_df_30d_count.empty == False else 0
+ channels_df['rebal_in_7day'] = invoices_df_7d_count.loc[chan_id].amt_paid if invoices_df_7d_count.empty == False else 0
+ channels_df['rebal_in_1day'] = invoices_df_1d_count.loc[chan_id].amt_paid if invoices_df_1d_count.empty == False else 0
+ channels_df['amt_rebal_in'] = int(invoices_df_sum.loc[chan_id].amt_paid) if invoices_df_count.empty == False else 0
+ channels_df['amt_rebal_in_30day'] = int(invoices_df_30d_sum.loc[chan_id].amt_paid) if invoices_df_30d_count.empty == False else 0
+ channels_df['amt_rebal_in_7day'] = int(invoices_df_7d_sum.loc[chan_id].amt_paid) if invoices_df_7d_count.empty == False else 0
+ channels_df['amt_rebal_in_1day'] = int(invoices_df_1d_sum.loc[chan_id].amt_paid) if invoices_df_1d_count.empty == False else 0
+ rebal_payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(rebal_chan=chan_id).values())
+ if rebal_payments_df.shape[0] > 0:
+ rebal_payments_df_30d = rebal_payments_df.loc[rebal_payments_df['creation_date'] >= filter_30day]
+ rebal_payments_df_7d = rebal_payments_df_30d.loc[rebal_payments_df_30d['creation_date'] >= filter_7day]
+ rebal_payments_df_1d = rebal_payments_df_7d.loc[rebal_payments_df_7d['creation_date'] >= filter_1day]
+ invoice_hashes = DataFrame() if invoices_df.empty else invoices_df.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)
+ 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_1d = DataFrame() if invoices_df_1d.empty else invoices_df_1d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
+ channels_df['costs'] = 0 if channels_df['rebal_in'][0] == 0 or invoice_hashes.empty == True else int(rebal_payments_df.set_index('payment_hash', inplace=False).loc[invoice_hashes[chan_id]]['fee'].sum())
+ channels_df['costs_30day'] = 0 if channels_df['rebal_in_30day'][0] == 0 or invoice_hashes_30d.empty == True else int(rebal_payments_df_30d.set_index('payment_hash', inplace=False).loc[invoice_hashes_30d[chan_id]]['fee'].sum())
+ channels_df['costs_7day'] = 0 if channels_df['rebal_in_7day'][0] == 0 or invoice_hashes_7d.empty == True else int(rebal_payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[chan_id]]['fee'].sum())
+ channels_df['costs_1day'] = 0 if channels_df['rebal_in_1day'][0] == 0 or invoice_hashes_1d.empty == True else int(rebal_payments_df_1d.set_index('payment_hash', inplace=False).loc[invoice_hashes_1d[chan_id]]['fee'].sum())
+ channels_df['profits'] = channels_df['revenue'] - channels_df['costs']
+ channels_df['profits_30day'] = channels_df['revenue_30day'] - channels_df['costs_30day']
+ channels_df['profits_7day'] = channels_df['revenue_7day'] - channels_df['costs_7day']
+ channels_df['profits_1day'] = channels_df['revenue_1day'] - channels_df['costs_1day']
+ channels_df['profits_vol'] = 0 if channels_df['amt_routed_out'][0] == 0 else int(channels_df['profits'] / (channels_df['amt_routed_out']/1000000))
+ channels_df['profits_vol_30day'] = 0 if channels_df['amt_routed_out_30day'][0] == 0 else int(channels_df['profits_30day'] / (channels_df['amt_routed_out_30day']/1000000))
+ channels_df['profits_vol_7day'] = 0 if channels_df['amt_routed_out_7day'][0] == 0 else int(channels_df['profits_7day'] / (channels_df['amt_routed_out_7day']/1000000))
+ channels_df['profits_vol_1day'] = 0 if channels_df['amt_routed_out_1day'][0] == 0 else int(channels_df['profits_1day'] / (channels_df['amt_routed_out_1day']/1000000))
+ channels_df['apy'] = 0.0
+ channels_df = channels_df.copy()
+ channels_df['net_routed_7day'] = round((channels_df['amt_routed_out_7day']-channels_df['amt_routed_in_7day'])/channels_df['capacity'], 1)
+ channels_df['out_rate'] = int((channels_df['revenue_7day']/channels_df['amt_routed_out_7day'])*1000000) if channels_df['amt_routed_out_7day'][0] > 0 else 0
+ channels_df['rebal_ppm'] = int((channels_df['costs_7day']/channels_df['amt_rebal_in_7day'])*1000000) if channels_df['amt_rebal_in_7day'][0] > 0 else 0
+ channels_df['max_suggestion'] = int((channels_df['out_rate'] if channels_df['out_rate'][0] > 0 else channels_df['local_fee_rate'])*1.15) if channels_df['in_percent'][0] > 25 else int(channels_df['local_fee_rate'])
+ channels_df['max_suggestion'] = channels_df['local_fee_rate']+25 if channels_df['max_suggestion'][0] > (channels_df['local_fee_rate'][0]+25) else channels_df['max_suggestion']
+ channels_df['min_suggestion'] = int((channels_df['out_rate'] if channels_df['out_rate'][0] > 0 else channels_df['local_fee_rate'])*0.75) if channels_df['out_percent'][0] > 25 else int(channels_df['local_fee_rate'])
+ channels_df['min_suggestion'] = channels_df['local_fee_rate']-50 if channels_df['min_suggestion'][0] < (channels_df['local_fee_rate'][0]-50) else channels_df['min_suggestion']
+ channels_df['assisted_ratio'] = round((channels_df['revenue_assist_7day'] if channels_df['revenue_7day'][0] == 0 else channels_df['revenue_assist_7day']/channels_df['revenue_7day']), 2)
+ channels_df['profit_margin'] = channels_df['out_rate']*((100-channels_df['ar_max_cost'])/100)
+ channels_df['adjusted_out_rate'] = int(channels_df['out_rate']+channels_df['net_routed_7day']*channels_df['assisted_ratio'])
+ channels_df['adjusted_rebal_rate'] = int(channels_df['rebal_ppm']+channels_df['profit_margin'])
+ channels_df['out_rate_only'] = int(channels_df['out_rate']+channels_df['net_routed_7day']*channels_df['out_rate']*0.02)
+ channels_df['fee_rate_only'] = int(channels_df['local_fee_rate']+channels_df['net_routed_7day']*channels_df['local_fee_rate']*0.05)
+ channels_df['new_rate'] = channels_df['adjusted_out_rate'] if channels_df['net_routed_7day'][0] != 0 else (channels_df['adjusted_rebal_rate'] if channels_df['rebal_ppm'][0] > 0 and channels_df['out_rate'][0] > 0 else (channels_df['out_rate_only'] if channels_df['out_rate'][0] > 0 else (channels_df['min_suggestion'] if channels_df['net_routed_7day'][0] == 0 and channels_df['in_percent'][0] < 25 else channels_df['fee_rate_only'])))
+ channels_df['new_rate'] = 0 if channels_df['new_rate'][0] < 0 else channels_df['new_rate']
+ channels_df['new_rate'] = channels_df['max_suggestion'] if channels_df['max_suggestion'][0] > 0 and channels_df['new_rate'][0] > channels_df['max_suggestion'][0] else channels_df['new_rate']
+ channels_df['new_rate'] = channels_df['min_suggestion'] if channels_df['new_rate'][0] < channels_df['min_suggestion'][0] else channels_df['new_rate']
+ channels_df['new_rate'] = int(round(channels_df['new_rate']/5, 0)*5)
+ channels_df['adjustment'] = int(channels_df['new_rate']-channels_df['local_fee_rate'])
+ channels_df['inbound_can'] = ((channels_df['remote_balance']*100)/channels_df['capacity'])/channels_df['ar_in_target']
+ channels_df['fee_ratio'] = 100 if channels_df['local_fee_rate'][0] == 0 else int(round(((channels_df['remote_fee_rate']/channels_df['local_fee_rate'])*1000)/10, 0))
+ channels_df['fee_check'] = 1 if channels_df['ar_max_cost'][0] == 0 else int(round(((channels_df['fee_ratio']/channels_df['ar_max_cost'])*1000)/10, 0))
+ channels_df = channels_df.copy()
+ channels_df['steps'] = 0 if channels_df['inbound_can'][0] < 1 else int(((channels_df['in_percent']-channels_df['ar_in_target'])/((channels_df['ar_amt_target']/channels_df['capacity'])*100))+0.999)
+ if node_capacity > 0:
+ outbound_ratio = node_outbound/node_capacity
+ if start_date is not None:
+ time_delta = datetime.now() - start_date.to_pydatetime()
+ days_routing = time_delta.days + (time_delta.seconds/86400)
+ channels_df['apy'] = round(((channels_df['profits']/days_routing)*36500)/(channels_df['capacity']*outbound_ratio), 2)
+ channels_df['apy_30day'] = round((channels_df['profits_30day']*1216.6667)/(channels_df['capacity']*outbound_ratio), 2)
+ channels_df['apy_7day'] = round((channels_df['profits_7day']*5214.2857)/(channels_df['capacity']*outbound_ratio), 2)
+ channels_df['apy_1day'] = round((channels_df['profits_1day']*36500)/(channels_df['capacity']*outbound_ratio), 2)
+ else:
+ channels_df = DataFrame()
+ forwards_df = DataFrame()
+ payments_df = DataFrame()
+ invoices_df = DataFrame()
+ rebalancer_df = DataFrame()
+ failed_htlc_df = DataFrame()
+ context = {
+ 'chan_id': chan_id,
+ 'channel': [] if channels_df.empty else channels_df.to_dict(orient='records')[0],
+ 'incoming_htlcs': PendingHTLCs.objects.filter(chan_id=chan_id).filter(incoming=True).order_by('hash_lock'),
+ 'outgoing_htlcs': PendingHTLCs.objects.filter(chan_id=chan_id).filter(incoming=False).order_by('hash_lock'),
+ 'forwards': [] if forwards_df.empty else forwards_df.sort_values(by=['forward_date'], ascending=False).to_dict(orient='records')[:5],
+ 'payments': [] if payments_df.empty else payments_df.sort_values(by=['creation_date'], ascending=False).to_dict(orient='records')[:5],
+ 'invoices': [] if invoices_df.empty else invoices_df.sort_values(by=['settle_date'], ascending=False).to_dict(orient='records')[:5],
+ 'rebalances': [] if rebalancer_df.empty else rebalancer_df.to_dict(orient='records')[:5],
+ 'failed_htlcs': [] if failed_htlc_df.empty else failed_htlc_df.to_dict(orient='records')[:5],
+ 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'graph_links': graph_links(),
+ 'network_links': network_links()
+ }
+ return render(request, 'channel.html', context)
+ else:
+ return redirect('home')
+
@login_required(login_url='/lndg-admin/login/?next=/')
def opens(request):
if request.method == 'GET':
@@ -421,7 +784,7 @@ def opens(request):
@login_required(login_url='/lndg-admin/login/?next=/')
def actions(request):
if request.method == 'GET':
- 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'))
+ channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity'))
filter_7day = datetime.now() - timedelta(days=7)
forwards = Forwards.objects.filter(forward_date__gte=filter_7day)
action_list = []
@@ -430,8 +793,8 @@ def actions(request):
result['chan_id'] = channel.chan_id
result['alias'] = channel.alias
result['capacity'] = channel.capacity
- result['local_balance'] = channel.local_balance
- result['remote_balance'] = channel.remote_balance
+ result['local_balance'] = channel.local_balance + channel.pending_outbound
+ result['remote_balance'] = channel.remote_balance + channel.pending_inbound
result['outbound_percent'] = int(round(channel.outbound_percent/10, 0))
result['inbound_percent'] = int(round(channel.inbound_percent/10, 0))
result['unsettled_balance'] = channel.unsettled_balance
@@ -494,7 +857,7 @@ def pending_htlcs(request):
def failed_htlcs(request):
if request.method == 'GET':
context = {
- 'failed_htlcs': FailedHTLCs.objects.all().order_by('-timestamp')[:150],
+ 'failed_htlcs': FailedHTLCs.objects.all().order_by('-id')[:150],
}
return render(request, 'failed_htlcs.html', context)
else:
@@ -530,6 +893,37 @@ def forwards(request):
else:
return redirect('home')
+@login_required(login_url='/lndg-admin/login/?next=/')
+def rebalancing(request):
+ if request.method == 'GET':
+ filter_7day = datetime.now() - timedelta(days=7)
+ rebalancer_7d_df = DataFrame.from_records(Rebalancer.objects.filter(stop__gte=filter_7day).order_by('-id').values())
+ channels_df = DataFrame.from_records(Channels.objects.filter(is_open=True, private=False).annotate(percent_inbound=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).order_by('-is_active', 'percent_outbound').values())
+ channels_df['inbound_can'] = channels_df['percent_inbound'] / channels_df['ar_in_target']
+ channels_df['fee_ratio'] = channels_df.apply(lambda row: 100 if row['local_fee_rate'] == 0 else int(round(((row['remote_fee_rate']/row['local_fee_rate'])*1000)/10, 0)), axis=1)
+ channels_df['fee_check'] = channels_df.apply(lambda row: 1 if row['ar_max_cost'] == 0 else int(round(((row['fee_ratio']/row['ar_max_cost'])*1000)/10, 0)), axis=1)
+ channels_df['steps'] = channels_df.apply(lambda row: 0 if row['inbound_can'] < 1 else int(((row['percent_inbound']-row['ar_in_target'])/((row['ar_amt_target']/row['capacity'])*100))+0.999), axis=1)
+ rebalancer_count_7d_df = rebalancer_7d_df[rebalancer_7d_df['status']>=2][rebalancer_7d_df['status']<400]
+ channels_df['attempts'] = channels_df.apply(lambda row: 0 if rebalancer_count_7d_df.empty else rebalancer_count_7d_df[rebalancer_count_7d_df['last_hop_pubkey']==row.remote_pubkey].shape[0], axis=1)
+ channels_df['success'] = channels_df.apply(lambda row: 0 if rebalancer_count_7d_df.empty else rebalancer_count_7d_df[rebalancer_count_7d_df['last_hop_pubkey']==row.remote_pubkey][rebalancer_count_7d_df['status']==2].shape[0], axis=1)
+ channels_df['success_rate'] = channels_df.apply(lambda row: 0 if row['attempts'] == 0 else int((row['success']/row['attempts'])*100), axis=1)
+ enabled_df = channels_df[channels_df['auto_rebalance']==True]
+ eligible_df = enabled_df[enabled_df['inbound_can']>=1][enabled_df['fee_check']<100]
+ eligible_count = eligible_df.shape[0]
+ enabled_count = enabled_df.shape[0]
+ context = {
+ 'eligible_count': eligible_count,
+ 'enabled_count': enabled_count,
+ 'channels': channels_df.to_dict(orient='records'),
+ 'rebalancer': Rebalancer.objects.all().annotate(ppm=(Sum('fee_limit')*1000000)/Sum('value')).order_by('-id')[:20],
+ 'rebalancer_form': RebalancerForm,
+ 'local_settings': LocalSettings.objects.filter(key__contains='AR-'),
+ 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'graph_links': graph_links()
+ }
+ return render(request, 'rebalancing.html', context)
+ else:
+ return redirect('home')
@login_required(login_url='/lndg-admin/login/?next=/')
def keysends(request):
@@ -545,7 +939,7 @@ def keysends(request):
def autopilot(request):
if request.method == 'GET':
context = {
- 'autopilot': Autopilot.objects.all().order_by('-timestamp')
+ 'autopilot': Autopilot.objects.all().order_by('-id')
}
return render(request, 'autopilot.html', context)
else:
@@ -699,9 +1093,13 @@ def rebalance(request):
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 ''
- 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!')
+ if len(chan_ids) > 0:
+ 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 ''
+ fee_limit = 0 if form.cleaned_data['value'] == 0 else int(form.cleaned_data['fee_limit']*form.cleaned_data['value']*0.000001)
+ Rebalancer(value=form.cleaned_data['value'], fee_limit=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:
+ messages.error(request, 'You must select atleast one outgoing channel.')
else:
messages.error(request, 'Target peer is invalid or unknown.')
except Exception as e:
@@ -712,7 +1110,7 @@ def rebalance(request):
messages.error(request, 'Error entering rebalancer request! Error: ' + error_msg)
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_chan_policy(request):
diff --git a/htlc_stream.py b/htlc_stream.py
index 4c3c43e3..1a41044e 100644
--- a/htlc_stream.py
+++ b/htlc_stream.py
@@ -4,6 +4,7 @@
from gui.lnd_deps.lnd_connect import lnd_connect
from lndg import settings
from os import environ
+from time import sleep
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
django.setup()
from gui.models import Channels, FailedHTLCs
@@ -20,13 +21,16 @@ def main():
out_chan = Channels.objects.filter(chan_id=out_chan_id)[0] if Channels.objects.filter(chan_id=out_chan_id).exists() else None
in_chan_alias = in_chan.alias if in_chan is not None else None
out_chan_alias = out_chan.alias if out_chan is not None else None
+ out_chan_liq = out_chan.local_balance if out_chan is not None else None
+ out_chan_pending = out_chan.pending_outbound if out_chan is not None else None
amount = int(response.link_fail_event.info.outgoing_amt_msat/1000)
wire_failure = response.link_fail_event.wire_failure
failure_detail = response.link_fail_event.failure_detail
missed_fee = 0 if out_chan == None else round(((amount/1000000) * out_chan.local_fee_rate) + (out_chan.local_base_fee/1000), 3)
- FailedHTLCs(amount=amount, chan_id_in=in_chan_id, chan_id_out=out_chan_id, chan_in_alias=in_chan_alias, chan_out_alias=out_chan_alias, wire_failure=wire_failure, failure_detail=failure_detail, missed_fee=missed_fee).save()
+ FailedHTLCs(amount=amount, chan_id_in=in_chan_id, chan_id_out=out_chan_id, chan_in_alias=in_chan_alias, chan_out_alias=out_chan_alias, chan_out_liq=out_chan_liq, chan_out_pending=out_chan_pending, wire_failure=wire_failure, failure_detail=failure_detail, missed_fee=missed_fee).save()
except Exception as e:
print('Error while running failed HTLC stream: ' + str(e))
+ sleep(20)
if __name__ == '__main__':
main()
\ No newline at end of file
diff --git a/jobs.py b/jobs.py
index 0de42748..dfdcfca9 100644
--- a/jobs.py
+++ b/jobs.py
@@ -3,12 +3,14 @@
from datetime import datetime, timedelta
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
+from gui.lnd_deps import signer_pb2 as lns
+from gui.lnd_deps import signer_pb2_grpc as lnsigner
from gui.lnd_deps.lnd_connect import lnd_connect
from lndg import settings
from os import environ
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
django.setup()
-from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, PendingHTLCs, LocalSettings
+from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings
def update_payments(stub):
#Remove anything in-flight so we can get most up to date status
@@ -16,6 +18,7 @@ def update_payments(stub):
#Get the number of records in the database currently
last_index = 0 if Payments.objects.aggregate(Max('index'))['index__max'] == None else Payments.objects.aggregate(Max('index'))['index__max']
payments = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=last_index, max_payments=100)).payments
+ self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
for payment in payments:
try:
new_payment = Payments(creation_date=datetime.fromtimestamp(payment.creation_date), payment_hash=payment.payment_hash, value=round(payment.value_msat/1000, 3), fee=round(payment.fee_msat/1000, 3), status=payment.status, index=payment.payment_index)
@@ -39,13 +42,14 @@ def update_payments(stub):
if hop_count == 1:
new_payment.chan_out = hop.chan_id
new_payment.chan_out_alias = alias
- new_payment.save()
if hop_count == total_hops and 5482373484 in hop.custom_records:
records = hop.custom_records
- message = records[34349334].decode('utf-8', errors='ignore')[:255] if 34349334 in records else None
+ message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
new_payment.keysend_preimage = records[5482373484].hex()
new_payment.message = message
- new_payment.save()
+ if hop_count == total_hops and hop.pub_key == self_pubkey:
+ new_payment.rebal_chan = hop.chan_id
+ new_payment.save()
break
except:
#Error inserting, try to update instead
@@ -79,7 +83,7 @@ def update_payments(stub):
db_payment.save()
if hop_count == total_hops and 5482373484 in hop.custom_records:
records = hop.custom_records
- message = records[34349334].decode('utf-8', errors='ignore')[:255] if 34349334 in records else None
+ message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
db_payment.keysend_preimage = records[5482373484].hex()
db_payment.message = message
db_payment.save()
@@ -92,11 +96,36 @@ def update_invoices(stub):
invoices = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=last_index, num_max_invoices=100)).invoices
for invoice in invoices:
if invoice.state == 1:
- alias = Channels.objects.filter(chan_id=invoice.htlcs[0].chan_id)[0].alias if Channels.objects.filter(chan_id=invoice.htlcs[0].chan_id).exists() else None
- records = invoice.htlcs[0].custom_records
- keysend_preimage = records[5482373484].hex() if 5482373484 in records else None
- message = records[34349334].decode('utf-8', errors='ignore')[:500] if 34349334 in records else None
- Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), settle_date=datetime.fromtimestamp(invoice.settle_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, chan_in=invoice.htlcs[0].chan_id, chan_in_alias=alias, keysend_preimage=keysend_preimage, message=message, index=invoice.add_index).save()
+ if len(invoice.htlcs) > 0:
+ chan_in_id = invoice.htlcs[0].chan_id
+ alias = Channels.objects.filter(chan_id=chan_in_id)[0].alias if Channels.objects.filter(chan_id=chan_in_id).exists() else None
+ records = invoice.htlcs[0].custom_records
+ keysend_preimage = records[5482373484].hex() if 5482373484 in records else None
+ message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
+ if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
+ signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
+ try:
+ valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
+ except:
+ print('Unable to validate signature on invoice: ' + invoice.r_hash.hex())
+ valid = False
+ sender = records[34349339].hex() if valid == True else None
+ try:
+ sender_alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
+ except:
+ sender_alias = None
+ else:
+ sender = None
+ sender_alias = None
+ else:
+ chan_in_id = None
+ alias = None
+ keysend_preimage = None
+ message = None
+ sender = None
+ sender_alias = None
+ Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), settle_date=datetime.fromtimestamp(invoice.settle_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, chan_in=chan_in_id, chan_in_alias=alias, keysend_preimage=keysend_preimage, message=message, sender=sender, sender_alias=sender_alias, index=invoice.add_index).save()
else:
Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, index=invoice.add_index).save()
@@ -119,7 +148,10 @@ def update_channels(stub):
db_channel = Channels.objects.filter(chan_id=channel.chan_id)[0]
else:
#Create a record for this new channel
- alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=channel.remote_pubkey, include_channels=False)).node.alias
+ try:
+ alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=channel.remote_pubkey, include_channels=False)).node.alias
+ except:
+ alias = ''
channel_point = channel.channel_point
txid, index = channel_point.split(':')
db_channel = Channels()
@@ -129,6 +161,8 @@ def update_channels(stub):
db_channel.alias = alias
db_channel.funding_txid = txid
db_channel.output_index = index
+ db_channel.capacity = channel.capacity
+ db_channel.private = channel.private
try:
chan_data = stub.GetChanInfo(ln.ChanInfoRequest(chan_id=channel.chan_id))
if chan_data.node1_pub == channel.remote_pubkey:
@@ -152,7 +186,6 @@ def update_channels(stub):
db_channel.remote_base_fee = 0
db_channel.remote_fee_rate = 0
db_channel.remote_disabled = False
- db_channel.capacity = channel.capacity
db_channel.local_balance = channel.local_balance
db_channel.remote_balance = channel.remote_balance
db_channel.unsettled_balance = channel.unsettled_balance
@@ -162,9 +195,11 @@ def update_channels(stub):
db_channel.last_update = datetime.now() if db_channel.is_active != channel.active else db_channel.last_update
db_channel.is_active = channel.active
db_channel.is_open = True
- db_channel.save()
- counter += 1
- chan_list.append(channel.chan_id)
+ db_channel.total_sent = channel.total_satoshis_sent
+ db_channel.total_received = channel.total_satoshis_received
+ pending_out = 0
+ pending_in = 0
+ htlc_counter = 0
if len(channel.pending_htlcs) > 0:
for htlc in channel.pending_htlcs:
pending_htlc = PendingHTLCs()
@@ -177,6 +212,17 @@ def update_channels(stub):
pending_htlc.forwarding_channel = htlc.forwarding_channel
pending_htlc.forwarding_alias = Channels.objects.filter(chan_id=htlc.forwarding_channel)[0].alias if Channels.objects.filter(chan_id=htlc.forwarding_channel).exists() else '---'
pending_htlc.save()
+ if htlc.incoming == True:
+ pending_in += htlc.amount
+ else:
+ pending_out += htlc.amount
+ htlc_counter += 1
+ db_channel.pending_outbound = pending_out
+ db_channel.pending_inbound = pending_in
+ db_channel.htlc_count = htlc_counter
+ db_channel.save()
+ counter += 1
+ chan_list.append(channel.chan_id)
records = Channels.objects.filter(is_open=True).count()
if records > counter:
#A channel must have been closed, mark it as closed
@@ -199,10 +245,19 @@ def update_peers(stub):
db_peer.sat_sent = peer.sat_sent
db_peer.sat_recv = peer.sat_recv
db_peer.inbound = peer.inbound
+ if db_peer.connected == False:
+ try:
+ db_peer.alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=peer.pub_key, include_channels=False)).node.alias
+ except:
+ db_peer.alias = ''
db_peer.connected = True
db_peer.save()
elif exists == 0:
- Peers(pubkey = peer.pub_key, address = peer.address, sat_sent = peer.sat_sent, sat_recv = peer.sat_recv, inbound = peer.inbound, connected = True).save()
+ try:
+ alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=peer.pub_key, include_channels=False)).node.alias
+ except:
+ alias = ''
+ Peers(pubkey = peer.pub_key, address = peer.address, sat_sent = peer.sat_sent, sat_recv = peer.sat_recv, inbound = peer.inbound, alias=alias, connected = True).save()
counter += 1
peer_list.append(peer.pub_key)
records = Peers.objects.filter(connected=True).count()
@@ -219,8 +274,23 @@ def update_onchain(stub):
for tx in onchain_txs:
Onchain(tx_hash=tx.tx_hash, time_stamp=datetime.fromtimestamp(tx.time_stamp), amount=tx.amount, fee=tx.total_fees, block_hash=tx.block_hash, block_height=tx.block_height, label=tx.label[:100]).save()
+def update_closures(stub):
+ closures = stub.ClosedChannels(ln.ClosedChannelsRequest()).channels
+ if len(closures) > Closures.objects.all().count():
+ counter = 0
+ skip = Closures.objects.all().count()
+ for closure in closures:
+ counter += 1
+ if counter > skip:
+ resolution_count = len(closure.resolutions)
+ db_closure = Closures(chan_id=closure.chan_id, closing_tx=closure.closing_tx_hash, remote_pubkey=closure.remote_pubkey, capacity=closure.capacity, close_height=closure.close_height, settled_balance=closure.settled_balance, time_locked_balance=closure.time_locked_balance, close_type=closure.close_type, open_initiator=closure.open_initiator, close_initiator=closure.close_initiator, resolution_count=resolution_count)
+ db_closure.save()
+ if resolution_count > 0:
+ for resolution in closure.resolutions:
+ Resolutions(chan_id=db_closure, resolution_type=resolution.resolution_type, outcome=resolution.outcome, outpoint_tx=resolution.outpoint.txid_str, outpoint_index=resolution.outpoint.output_index, amount_sat=resolution.amount_sat, sweep_txid=resolution.sweep_txid).save()
+
def reconnect_peers(stub):
- inactive_peers = Channels.objects.filter(is_open=True, is_active=False).values_list('remote_pubkey', flat=True).distinct()
+ inactive_peers = Channels.objects.filter(is_open=True, is_active=False, private=False).values_list('remote_pubkey', flat=True).distinct()
if len(inactive_peers) > 0:
peers = Peers.objects.all()
for inactive_peer in inactive_peers:
@@ -233,8 +303,12 @@ def reconnect_peers(stub):
peer.connected = False
peer.save()
print('Attempting connection to:', inactive_peer)
- node = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=inactive_peer, include_channels=False)).node
- host = node.addresses[0].addr
+ try:
+ node = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=inactive_peer, include_channels=False)).node
+ host = node.addresses[0].addr
+ except:
+ print('Unable to find node info on graph, using last known value')
+ host = peer.address
address = ln.LightningAddress(pubkey=inactive_peer, host=host)
stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=True, timeout=5))
peer.last_reconnected = datetime.now()
@@ -281,6 +355,7 @@ def main():
update_invoices(stub)
update_forwards(stub)
update_onchain(stub)
+ update_closures(stub)
reconnect_peers(stub)
clean_payments(stub)
except Exception as e:
diff --git a/keysend.py b/keysend.py
index 579850d3..defc22ef 100644
--- a/keysend.py
+++ b/keysend.py
@@ -1,37 +1,57 @@
-import secrets
+import secrets, time, struct
from hashlib import sha256
from lndg import settings
+from gui.lnd_deps import lightning_pb2 as ln
+from gui.lnd_deps import lightning_pb2_grpc as lnrpc
from gui.lnd_deps import router_pb2 as lnr
from gui.lnd_deps import router_pb2_grpc as lnrouter
+from gui.lnd_deps import signer_pb2 as lns
+from gui.lnd_deps import signer_pb2_grpc as lnsigner
from gui.lnd_deps.lnd_connect import lnd_connect
-def keysend(target_pubkey, msg, amount, fee_limit, timeout):
+def keysend(target_pubkey, msg, amount, fee_limit, timeout, sign):
#Construct and send
- routerstub = lnrouter.RouterStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
- secret = secrets.token_bytes(32)
- hashed_secret = sha256(secret).hexdigest()
- custom_records = [(5482373484, secret),]
- msg = str(msg)
- if len(msg) > 0:
- custom_records.append((34349334, bytes.fromhex(msg.encode('utf-8').hex())))
- for response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(dest=bytes.fromhex(target_pubkey), dest_custom_records=custom_records, fee_limit_sat=fee_limit, timeout_seconds=timeout, amt=amount, payment_hash=bytes.fromhex(hashed_secret))):
- if response.status == 1:
- print('In-flight')
- if response.status == 2:
- print('Succeeded')
- if response.status == 3:
- if response.failure_reason == 1:
- print('Failure - Timeout')
- elif response.failure_reason == 2:
- print('Failure - No Route')
- elif response.failure_reason == 3:
- print('Failure - Error')
- elif response.failure_reason == 4:
- print('Failure - Incorrect Payment Details')
- elif response.failure_reason == 5:
- print('Failure Insufficient Balance')
- if response.status == 0:
- print('Unknown Error')
+ try:
+ routerstub = lnrouter.RouterStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ secret = secrets.token_bytes(32)
+ hashed_secret = sha256(secret).hexdigest()
+ custom_records = [(5482373484, secret),]
+ msg = str(msg)
+ if len(msg) > 0:
+ custom_records.append((34349334, bytes.fromhex(msg.encode('utf-8').hex())))
+ if sign == True:
+ stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
+ timestamp = struct.pack(">i", int(time.time()))
+ signature = signerstub.SignMessage(lns.SignMessageReq(msg=(bytes.fromhex(self_pubkey)+bytes.fromhex(target_pubkey)+timestamp+bytes.fromhex(msg.encode('utf-8').hex())), key_loc=lns.KeyLocator(key_family=6, key_index=0))).signature
+ custom_records.append((34349337, signature))
+ custom_records.append((34349339, bytes.fromhex(self_pubkey)))
+ custom_records.append((34349343, timestamp))
+ for response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(dest=bytes.fromhex(target_pubkey), dest_custom_records=custom_records, fee_limit_sat=fee_limit, timeout_seconds=timeout, amt=amount, payment_hash=bytes.fromhex(hashed_secret))):
+ if response.status == 1:
+ print('In-flight')
+ if response.status == 2:
+ print('Succeeded')
+ if response.status == 3:
+ if response.failure_reason == 1:
+ print('Failure - Timeout')
+ elif response.failure_reason == 2:
+ print('Failure - No Route')
+ elif response.failure_reason == 3:
+ print('Failure - Error')
+ elif response.failure_reason == 4:
+ print('Failure - Incorrect Payment Details')
+ elif response.failure_reason == 5:
+ print('Failure Insufficient Balance')
+ if response.status == 0:
+ print('Unknown Error')
+ except Exception as e:
+ error = str(e)
+ details_index = error.find('details =') + 11
+ debug_error_index = error.find('debug_error_string =') - 3
+ error_msg = error[details_index:debug_error_index]
+ print('Error while sending keysend payment! Error: ' + error_msg)
def main():
#Ask user for variables
@@ -40,13 +60,18 @@ def main():
amount = int(input('Enter an amount in sats to be sent with the keysend payment (defaults to 1 sat): ') or '1')
fee_limit = int(input('Enter an amount in sats to be used as a max fee limit for sending (defaults to 1 sat): ') or '1')
msg = input('Enter an optional message to be included (leave this blank for no message): ')
+ if len(msg) > 0:
+ sign = input('Self sign the message? (defaults to sending anonymously) [y/N]: ')
+ sign = True if sign.lower() == 'yes' or sign.lower() == 'y' else False
+ else:
+ sign = False
except:
print('Invalid data entered, please try again.')
timeout = 10
print('Sending keysend payment of %s to: %s' % (amount, target_pubkey))
if len(msg) > 0:
print('Attaching this message to the keysend payment:', msg)
- keysend(target_pubkey, msg, amount, fee_limit, timeout)
+ keysend(target_pubkey, msg, amount, fee_limit, timeout, sign)
if __name__ == '__main__':
main()
\ No newline at end of file
diff --git a/rebalancer.py b/rebalancer.py
index f74671a7..4bc72539 100644
--- a/rebalancer.py
+++ b/rebalancer.py
@@ -20,7 +20,6 @@ def run_rebalancer(rebalance):
unknown_error.stop = datetime.now()
unknown_error.save()
rebalance.start = datetime.now()
- rebalance.save()
try:
#Open connection with lnd via grpc
connection = lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)
@@ -28,33 +27,35 @@ def run_rebalancer(rebalance):
routerstub = lnrouter.RouterStub(connection)
chan_ids = json.loads(rebalance.outgoing_chan_ids)
timeout = rebalance.duration * 60
- response = stub.AddInvoice(ln.Invoice(value=rebalance.value, expiry=timeout))
- for response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(payment_request=str(response.payment_request), fee_limit_sat=rebalance.fee_limit, outgoing_chan_ids=chan_ids, last_hop_pubkey=bytes.fromhex(rebalance.last_hop_pubkey), timeout_seconds=(timeout-5), allow_self_payment=True), timeout=(timeout+60)):
- if response.status == 1 and rebalance.status == 0:
+ invoice_response = stub.AddInvoice(ln.Invoice(value=rebalance.value, expiry=timeout))
+ for payment_response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(payment_request=str(invoice_response.payment_request), fee_limit_sat=rebalance.fee_limit, outgoing_chan_ids=chan_ids, last_hop_pubkey=bytes.fromhex(rebalance.last_hop_pubkey), timeout_seconds=(timeout-5), allow_self_payment=True), timeout=(timeout+60)):
+ if payment_response.status == 1 and rebalance.status == 0:
#IN-FLIGHT
+ rebalance.payment_hash = payment_response.payment_hash
rebalance.status = 1
- rebalance.payment_hash = response.payment_hash
rebalance.save()
- elif response.status == 2:
+ elif payment_response.status == 2:
#SUCCESSFUL
rebalance.status = 2
- elif response.status == 3:
+ elif payment_response.status == 3:
#FAILURE
- if response.failure_reason == 1:
+ if payment_response.failure_reason == 1:
#FAILURE_REASON_TIMEOUT
rebalance.status = 3
- elif response.failure_reason == 2:
+ elif payment_response.failure_reason == 2:
#FAILURE_REASON_NO_ROUTE
rebalance.status = 4
- elif response.failure_reason == 3:
+ elif payment_response.failure_reason == 3:
#FAILURE_REASON_ERROR
rebalance.status = 5
- elif response.failure_reason == 4:
+ elif payment_response.failure_reason == 4:
#FAILURE_REASON_INCORRECT_PAYMENT_DETAILS
rebalance.status = 6
- elif response.failure_reason == 5:
+ elif payment_response.failure_reason == 5:
#FAILURE_REASON_INSUFFICIENT_BALANCE
rebalance.status = 7
+ elif payment_response.status == 0:
+ rebalance.status = 400
except Exception as e:
if str(e.code()) == 'StatusCode.DEADLINE_EXCEEDED':
rebalance.status = 408
@@ -65,6 +66,26 @@ def run_rebalancer(rebalance):
finally:
rebalance.stop = datetime.now()
rebalance.save()
+ if rebalance.status == 2:
+ update_channel(stub, rebalance.last_hop_pubkey)
+ auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_can=(((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity'))/Sum('ar_in_target'))
+ inbound_cans = auto_rebalance_channels.filter(remote_pubkey=rebalance.last_hop_pubkey).filter(auto_rebalance=True, inbound_can__gte=1)
+ if len(inbound_cans) > 0:
+ outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True))
+ next_rebalance = Rebalancer(value=rebalance.value, fee_limit=rebalance.fee_limit, outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=rebalance.last_hop_pubkey, target_alias=rebalance.target_alias, duration=rebalance.duration)
+ next_rebalance.save()
+ else:
+ next_rebalance = None
+ else:
+ next_rebalance = None
+ return next_rebalance
+
+def update_channel(stub, peer_pubkey):
+ channel = stub.ListChannels(ln.ListChannelsRequest(peer=bytes.fromhex(peer_pubkey))).channels[0]
+ db_channel = Channels.objects.filter(chan_id=channel.chan_id)[0]
+ db_channel.local_balance = channel.local_balance
+ db_channel.remote_balance = channel.remote_balance
+ db_channel.save()
def auto_schedule():
#No rebalancer jobs have been scheduled, lets look for any channels with an auto_rebalance flag and make the best request if we find one
@@ -74,7 +95,7 @@ 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_in_target'))
+ auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_can=(((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity'))/Sum('ar_in_target'))
if len(auto_rebalance_channels) > 0:
if not LocalSettings.objects.filter(key='AR-Outbound%').exists():
LocalSettings(key='AR-Outbound%', value='0.75').save()
@@ -96,17 +117,16 @@ def auto_schedule():
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.ar_amt_target / value_per_fee) * value_per_fee
- if target_value >= value_per_fee:
+ target_fee = int(target_value * (1 / value_per_fee))
+ if target_value >= value_per_fee and target_fee > 0:
if LocalSettings.objects.filter(key='AR-Time').exists():
target_time = int(LocalSettings.objects.filter(key='AR-Time')[0].value)
else:
LocalSettings(key='AR-Time', value='5').save()
target_time = 5
- inbound_pubkey = Channels.objects.filter(chan_id=target.chan_id)[0]
# TLDR: willing to pay 1 sat for every value_per_fee sats moved
- target_fee = int(target_value * (1 / value_per_fee))
- if Rebalancer.objects.filter(last_hop_pubkey=inbound_pubkey.remote_pubkey).exclude(status=0).exists():
- last_rebalance = Rebalancer.objects.filter(last_hop_pubkey=inbound_pubkey.remote_pubkey).exclude(status=0).order_by('-id')[0]
+ if Rebalancer.objects.filter(last_hop_pubkey=target.remote_pubkey).exclude(status=0).exists():
+ last_rebalance = Rebalancer.objects.filter(last_hop_pubkey=target.remote_pubkey).exclude(status=0).order_by('-id')[0]
if not (last_rebalance.value != target_value or last_rebalance.status in [2, 6] or (last_rebalance.status in [3, 4, 5, 7, 400, 408] and (int((datetime.now() - last_rebalance.stop).total_seconds() / 60) > 30)) or (last_rebalance.status == 1 and (int((datetime.now() - last_rebalance.start).total_seconds() / 60) > 30))):
continue
print('Creating Auto Rebalance Request')
@@ -115,7 +135,7 @@ def auto_schedule():
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()
+ Rebalancer(value=target_value, fee_limit=target_fee, outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=target.remote_pubkey, target_alias=target.alias, duration=target_time).save()
def auto_enable():
if LocalSettings.objects.filter(key='AR-Autopilot').exists():
@@ -124,7 +144,7 @@ def auto_enable():
LocalSettings(key='AR-Autopilot', value='0').save()
enabled = 0
if enabled == 1:
- 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'))
+ channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity'))
filter_7day = datetime.now() - timedelta(days=7)
forwards = Forwards.objects.filter(forward_date__gte=filter_7day)
for channel in channels:
@@ -160,7 +180,9 @@ def main():
auto_enable()
auto_schedule()
else:
- run_rebalancer(rebalances[0])
+ rebalance = rebalances[0]
+ while rebalance != None:
+ rebalance = run_rebalancer(rebalance)
if __name__ == '__main__':
main()
\ No newline at end of file