diff --git a/gui/forms.py b/gui/forms.py index 9b147b54..4c922275 100644 --- a/gui/forms.py +++ b/gui/forms.py @@ -63,20 +63,37 @@ class Meta: last_hop_pubkey = forms.CharField(label='funding_txid', max_length=66, required=False) duration = forms.IntegerField(label='duration') -class AutoRebalanceForm(forms.Form): - chan_id = forms.IntegerField(label='chan_id', required=False) - enabled = forms.IntegerField(label='enabled', required=False) - target_percent = forms.FloatField(label='target_percent', required=False) - target_time = forms.IntegerField(label='target_time', required=False) - fee_rate = forms.IntegerField(label='fee_rate', required=False) - outbound_percent = forms.FloatField(label='outbound_percent', required=False) - inbound_percent = forms.FloatField(label='inbound_percent', required=False) - max_cost = forms.FloatField(label='max_cost', required=False) - variance = forms.IntegerField(label='variance', required=False) - wait_period = forms.IntegerField(label='wait_period', required=False) - autopilot = forms.IntegerField(label='autopilot', required=False) +class AutoRebalanceForm(forms.Form): + enabled = forms.IntegerField(label='enabled', required=False) + target_percent = forms.FloatField(label='target_percent', required=False) + target_time = forms.IntegerField(label='target_time', required=False) + fee_rate = forms.IntegerField(label='fee_rate', required=False) + outbound_percent = forms.FloatField(label='outbound_percent', required=False) + inbound_percent = forms.FloatField(label='inbound_percent', required=False) + max_cost = forms.FloatField(label='max_cost', required=False) + variance = forms.IntegerField(label='variance', required=False) + wait_period = forms.IntegerField(label='wait_period', required=False) + autopilot = forms.IntegerField(label='autopilot', required=False) autopilotdays = forms.IntegerField(label='autopilotdays', required=False) - targetallchannels = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False) + workers = forms.IntegerField(label='workers', required=False) + update_channels = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False) + +class AutoFeesForm(AutoRebalanceForm): + af_enabled = forms.IntegerField(label='af_enabled', required=False) + af_maxRate = forms.IntegerField(label='af_maxRate', required=False) + af_minRate = forms.IntegerField(label='af_minRate', required=False) + af_increment = forms.IntegerField(label='af_increment', required=False) + af_multiplier = forms.IntegerField(label='af_multiplier', required=False) + af_failedHTLCs = forms.IntegerField(label='af_failedHTLCs', required=False) + af_updateHours = forms.IntegerField(label='af_updateHours', required=False) + +class GUIForm(AutoFeesForm): + gui_graphLinks = forms.CharField(label='gui_graphLinks', required=False) + gui_netLinks = forms.CharField(label='gui_netLinks', required=False) + +class LocalSettingsForm(GUIForm): + lnd_cleanPayments = forms.IntegerField(label='lnd_cleanPayments', required=False) + lnd_retentionDays = forms.IntegerField(label='lnd_retentionDays', required=False) updates_channel_codes = [ (0, 'base_fee'), diff --git a/gui/lnd_deps/walletkit_pb2.py b/gui/lnd_deps/walletkit_pb2.py new file mode 100644 index 00000000..7c4be8ec --- /dev/null +++ b/gui/lnd_deps/walletkit_pb2.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: walletkit.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import lightning_pb2 as lightning__pb2 +from . import signer_pb2 as signer__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fwalletkit.proto\x12\twalletrpc\x1a\x0flightning.proto\x1a\x0csigner.proto\"e\n\x12ListUnspentRequest\x12\x11\n\tmin_confs\x18\x01 \x01(\x05\x12\x11\n\tmax_confs\x18\x02 \x01(\x05\x12\x0f\n\x07\x61\x63\x63ount\x18\x03 \x01(\t\x12\x18\n\x10unconfirmed_only\x18\x04 \x01(\x08\"1\n\x13ListUnspentResponse\x12\x1a\n\x05utxos\x18\x01 \x03(\x0b\x32\x0b.lnrpc.Utxo\"_\n\x12LeaseOutputRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12!\n\x08outpoint\x18\x02 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12\x1a\n\x12\x65xpiration_seconds\x18\x03 \x01(\x04\")\n\x13LeaseOutputResponse\x12\x12\n\nexpiration\x18\x01 \x01(\x04\"E\n\x14ReleaseOutputRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12!\n\x08outpoint\x18\x02 \x01(\x0b\x32\x0f.lnrpc.OutPoint\"\x17\n\x15ReleaseOutputResponse\"6\n\x06KeyReq\x12\x18\n\x10key_finger_print\x18\x01 \x01(\x05\x12\x12\n\nkey_family\x18\x02 \x01(\x05\"T\n\x0b\x41\x64\x64rRequest\x12\x0f\n\x07\x61\x63\x63ount\x18\x01 \x01(\t\x12$\n\x04type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\x12\x0e\n\x06\x63hange\x18\x03 \x01(\x08\"\x1c\n\x0c\x41\x64\x64rResponse\x12\x0c\n\x04\x61\x64\x64r\x18\x01 \x01(\t\"\xe7\x01\n\x07\x41\x63\x63ount\x12\x0c\n\x04name\x18\x01 \x01(\t\x12,\n\x0c\x61\x64\x64ress_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\x12\x1b\n\x13\x65xtended_public_key\x18\x03 \x01(\t\x12\x1e\n\x16master_key_fingerprint\x18\x04 \x01(\x0c\x12\x17\n\x0f\x64\x65rivation_path\x18\x05 \x01(\t\x12\x1a\n\x12\x65xternal_key_count\x18\x06 \x01(\r\x12\x1a\n\x12internal_key_count\x18\x07 \x01(\r\x12\x12\n\nwatch_only\x18\x08 \x01(\x08\"H\n\x0f\x41\x64\x64ressProperty\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x13\n\x0bis_internal\x18\x02 \x01(\x08\x12\x0f\n\x07\x62\x61lance\x18\x03 \x01(\x03\"\x9a\x01\n\x14\x41\x63\x63ountWithAddresses\x12\x0c\n\x04name\x18\x01 \x01(\t\x12,\n\x0c\x61\x64\x64ress_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\x12\x17\n\x0f\x64\x65rivation_path\x18\x03 \x01(\t\x12-\n\taddresses\x18\x04 \x03(\x0b\x32\x1a.walletrpc.AddressProperty\"Q\n\x13ListAccountsRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12,\n\x0c\x61\x64\x64ress_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\"<\n\x14ListAccountsResponse\x12$\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x12.walletrpc.Account\"<\n\x16RequiredReserveRequest\x12\"\n\x1a\x61\x64\x64itional_public_channels\x18\x01 \x01(\r\"3\n\x17RequiredReserveResponse\x12\x18\n\x10required_reserve\x18\x01 \x01(\x03\"J\n\x14ListAddressesRequest\x12\x14\n\x0c\x61\x63\x63ount_name\x18\x01 \x01(\t\x12\x1c\n\x14show_custom_accounts\x18\x02 \x01(\x08\"X\n\x15ListAddressesResponse\x12?\n\x16\x61\x63\x63ount_with_addresses\x18\x01 \x03(\x0b\x32\x1f.walletrpc.AccountWithAddresses\"7\n\x1aSignMessageWithAddrRequest\x12\x0b\n\x03msg\x18\x01 \x01(\x0c\x12\x0c\n\x04\x61\x64\x64r\x18\x02 \x01(\t\"0\n\x1bSignMessageWithAddrResponse\x12\x11\n\tsignature\x18\x01 \x01(\t\"L\n\x1cVerifyMessageWithAddrRequest\x12\x0b\n\x03msg\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\t\x12\x0c\n\x04\x61\x64\x64r\x18\x03 \x01(\t\">\n\x1dVerifyMessageWithAddrResponse\x12\r\n\x05valid\x18\x01 \x01(\x08\x12\x0e\n\x06pubkey\x18\x02 \x01(\x0c\"\xa0\x01\n\x14ImportAccountRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x13\x65xtended_public_key\x18\x02 \x01(\t\x12\x1e\n\x16master_key_fingerprint\x18\x03 \x01(\x0c\x12,\n\x0c\x61\x64\x64ress_type\x18\x04 \x01(\x0e\x32\x16.walletrpc.AddressType\x12\x0f\n\x07\x64ry_run\x18\x05 \x01(\x08\"|\n\x15ImportAccountResponse\x12#\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x12.walletrpc.Account\x12\x1e\n\x16\x64ry_run_external_addrs\x18\x02 \x03(\t\x12\x1e\n\x16\x64ry_run_internal_addrs\x18\x03 \x03(\t\"Z\n\x16ImportPublicKeyRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12,\n\x0c\x61\x64\x64ress_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\"\x19\n\x17ImportPublicKeyResponse\"\xe2\x01\n\x16ImportTapscriptRequest\x12\x1b\n\x13internal_public_key\x18\x01 \x01(\x0c\x12\x31\n\tfull_tree\x18\x02 \x01(\x0b\x32\x1c.walletrpc.TapscriptFullTreeH\x00\x12;\n\x0epartial_reveal\x18\x03 \x01(\x0b\x32!.walletrpc.TapscriptPartialRevealH\x00\x12\x18\n\x0eroot_hash_only\x18\x04 \x01(\x0cH\x00\x12\x17\n\rfull_key_only\x18\x05 \x01(\x08H\x00\x42\x08\n\x06script\";\n\x11TapscriptFullTree\x12&\n\nall_leaves\x18\x01 \x03(\x0b\x32\x12.walletrpc.TapLeaf\"/\n\x07TapLeaf\x12\x14\n\x0cleaf_version\x18\x01 \x01(\r\x12\x0e\n\x06script\x18\x02 \x01(\x0c\"a\n\x16TapscriptPartialReveal\x12)\n\rrevealed_leaf\x18\x01 \x01(\x0b\x32\x12.walletrpc.TapLeaf\x12\x1c\n\x14\x66ull_inclusion_proof\x18\x02 \x01(\x0c\"/\n\x17ImportTapscriptResponse\x12\x14\n\x0cp2tr_address\x18\x01 \x01(\t\",\n\x0bTransaction\x12\x0e\n\x06tx_hex\x18\x01 \x01(\x0c\x12\r\n\x05label\x18\x02 \x01(\t\"(\n\x0fPublishResponse\x12\x15\n\rpublish_error\x18\x01 \x01(\t\"\x86\x01\n\x12SendOutputsRequest\x12\x12\n\nsat_per_kw\x18\x01 \x01(\x03\x12\x1f\n\x07outputs\x18\x02 \x03(\x0b\x32\x0e.signrpc.TxOut\x12\r\n\x05label\x18\x03 \x01(\t\x12\x11\n\tmin_confs\x18\x04 \x01(\x05\x12\x19\n\x11spend_unconfirmed\x18\x05 \x01(\x08\"%\n\x13SendOutputsResponse\x12\x0e\n\x06raw_tx\x18\x01 \x01(\x0c\")\n\x12\x45stimateFeeRequest\x12\x13\n\x0b\x63onf_target\x18\x01 \x01(\x05\")\n\x13\x45stimateFeeResponse\x12\x12\n\nsat_per_kw\x18\x01 \x01(\x03\"\xd2\x02\n\x0cPendingSweep\x12!\n\x08outpoint\x18\x01 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12,\n\x0cwitness_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.WitnessType\x12\x12\n\namount_sat\x18\x03 \x01(\r\x12\x18\n\x0csat_per_byte\x18\x04 \x01(\rB\x02\x18\x01\x12\x1a\n\x12\x62roadcast_attempts\x18\x05 \x01(\r\x12\x1d\n\x15next_broadcast_height\x18\x06 \x01(\r\x12\x1d\n\x15requested_conf_target\x18\x08 \x01(\r\x12\"\n\x16requested_sat_per_byte\x18\t \x01(\rB\x02\x18\x01\x12\x15\n\rsat_per_vbyte\x18\n \x01(\x04\x12\x1f\n\x17requested_sat_per_vbyte\x18\x0b \x01(\x04\x12\r\n\x05\x66orce\x18\x07 \x01(\x08\"\x16\n\x14PendingSweepsRequest\"H\n\x15PendingSweepsResponse\x12/\n\x0epending_sweeps\x18\x01 \x03(\x0b\x32\x17.walletrpc.PendingSweep\"\x88\x01\n\x0e\x42umpFeeRequest\x12!\n\x08outpoint\x18\x01 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12\x13\n\x0btarget_conf\x18\x02 \x01(\r\x12\x18\n\x0csat_per_byte\x18\x03 \x01(\rB\x02\x18\x01\x12\r\n\x05\x66orce\x18\x04 \x01(\x08\x12\x15\n\rsat_per_vbyte\x18\x05 \x01(\x04\"\x11\n\x0f\x42umpFeeResponse\"$\n\x11ListSweepsRequest\x12\x0f\n\x07verbose\x18\x01 \x01(\x08\"\xcc\x01\n\x12ListSweepsResponse\x12\x38\n\x13transaction_details\x18\x01 \x01(\x0b\x32\x19.lnrpc.TransactionDetailsH\x00\x12G\n\x0ftransaction_ids\x18\x02 \x01(\x0b\x32,.walletrpc.ListSweepsResponse.TransactionIDsH\x00\x1a)\n\x0eTransactionIDs\x12\x17\n\x0ftransaction_ids\x18\x01 \x03(\tB\x08\n\x06sweeps\"I\n\x17LabelTransactionRequest\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\r\n\x05label\x18\x02 \x01(\t\x12\x11\n\toverwrite\x18\x03 \x01(\x08\"\x1a\n\x18LabelTransactionResponse\"\xfd\x01\n\x0f\x46undPsbtRequest\x12\x0e\n\x04psbt\x18\x01 \x01(\x0cH\x00\x12$\n\x03raw\x18\x02 \x01(\x0b\x32\x15.walletrpc.TxTemplateH\x00\x12\x15\n\x0btarget_conf\x18\x03 \x01(\rH\x01\x12\x17\n\rsat_per_vbyte\x18\x04 \x01(\x04H\x01\x12\x0f\n\x07\x61\x63\x63ount\x18\x05 \x01(\t\x12\x11\n\tmin_confs\x18\x06 \x01(\x05\x12\x19\n\x11spend_unconfirmed\x18\x07 \x01(\x08\x12\x31\n\x0b\x63hange_type\x18\x08 \x01(\x0e\x32\x1c.walletrpc.ChangeAddressTypeB\n\n\x08templateB\x06\n\x04\x66\x65\x65s\"p\n\x10\x46undPsbtResponse\x12\x13\n\x0b\x66unded_psbt\x18\x01 \x01(\x0c\x12\x1b\n\x13\x63hange_output_index\x18\x02 \x01(\x05\x12*\n\x0clocked_utxos\x18\x03 \x03(\x0b\x32\x14.walletrpc.UtxoLease\"\x92\x01\n\nTxTemplate\x12\x1f\n\x06inputs\x18\x01 \x03(\x0b\x32\x0f.lnrpc.OutPoint\x12\x33\n\x07outputs\x18\x02 \x03(\x0b\x32\".walletrpc.TxTemplate.OutputsEntry\x1a.\n\x0cOutputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"p\n\tUtxoLease\x12\n\n\x02id\x18\x01 \x01(\x0c\x12!\n\x08outpoint\x18\x02 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12\x12\n\nexpiration\x18\x03 \x01(\x04\x12\x11\n\tpk_script\x18\x04 \x01(\x0c\x12\r\n\x05value\x18\x05 \x01(\x04\"&\n\x0fSignPsbtRequest\x12\x13\n\x0b\x66unded_psbt\x18\x01 \x01(\x0c\">\n\x10SignPsbtResponse\x12\x13\n\x0bsigned_psbt\x18\x01 \x01(\x0c\x12\x15\n\rsigned_inputs\x18\x02 \x03(\r\";\n\x13\x46inalizePsbtRequest\x12\x13\n\x0b\x66unded_psbt\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x63\x63ount\x18\x05 \x01(\t\"A\n\x14\x46inalizePsbtResponse\x12\x13\n\x0bsigned_psbt\x18\x01 \x01(\x0c\x12\x14\n\x0craw_final_tx\x18\x02 \x01(\x0c\"\x13\n\x11ListLeasesRequest\"@\n\x12ListLeasesResponse\x12*\n\x0clocked_utxos\x18\x01 \x03(\x0b\x32\x14.walletrpc.UtxoLease*\x8e\x01\n\x0b\x41\x64\x64ressType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x17\n\x13WITNESS_PUBKEY_HASH\x10\x01\x12\x1e\n\x1aNESTED_WITNESS_PUBKEY_HASH\x10\x02\x12%\n!HYBRID_NESTED_WITNESS_PUBKEY_HASH\x10\x03\x12\x12\n\x0eTAPROOT_PUBKEY\x10\x04*\x99\x03\n\x0bWitnessType\x12\x13\n\x0fUNKNOWN_WITNESS\x10\x00\x12\x18\n\x14\x43OMMITMENT_TIME_LOCK\x10\x01\x12\x17\n\x13\x43OMMITMENT_NO_DELAY\x10\x02\x12\x15\n\x11\x43OMMITMENT_REVOKE\x10\x03\x12\x17\n\x13HTLC_OFFERED_REVOKE\x10\x04\x12\x18\n\x14HTLC_ACCEPTED_REVOKE\x10\x05\x12%\n!HTLC_OFFERED_TIMEOUT_SECOND_LEVEL\x10\x06\x12&\n\"HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL\x10\x07\x12\x1f\n\x1bHTLC_OFFERED_REMOTE_TIMEOUT\x10\x08\x12 \n\x1cHTLC_ACCEPTED_REMOTE_SUCCESS\x10\t\x12\x1c\n\x18HTLC_SECOND_LEVEL_REVOKE\x10\n\x12\x14\n\x10WITNESS_KEY_HASH\x10\x0b\x12\x1b\n\x17NESTED_WITNESS_KEY_HASH\x10\x0c\x12\x15\n\x11\x43OMMITMENT_ANCHOR\x10\r*V\n\x11\x43hangeAddressType\x12#\n\x1f\x43HANGE_ADDRESS_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x43HANGE_ADDRESS_TYPE_P2TR\x10\x01\x32\xd1\x0f\n\tWalletKit\x12L\n\x0bListUnspent\x12\x1d.walletrpc.ListUnspentRequest\x1a\x1e.walletrpc.ListUnspentResponse\x12L\n\x0bLeaseOutput\x12\x1d.walletrpc.LeaseOutputRequest\x1a\x1e.walletrpc.LeaseOutputResponse\x12R\n\rReleaseOutput\x12\x1f.walletrpc.ReleaseOutputRequest\x1a .walletrpc.ReleaseOutputResponse\x12I\n\nListLeases\x12\x1c.walletrpc.ListLeasesRequest\x1a\x1d.walletrpc.ListLeasesResponse\x12:\n\rDeriveNextKey\x12\x11.walletrpc.KeyReq\x1a\x16.signrpc.KeyDescriptor\x12\x38\n\tDeriveKey\x12\x13.signrpc.KeyLocator\x1a\x16.signrpc.KeyDescriptor\x12;\n\x08NextAddr\x12\x16.walletrpc.AddrRequest\x1a\x17.walletrpc.AddrResponse\x12O\n\x0cListAccounts\x12\x1e.walletrpc.ListAccountsRequest\x1a\x1f.walletrpc.ListAccountsResponse\x12X\n\x0fRequiredReserve\x12!.walletrpc.RequiredReserveRequest\x1a\".walletrpc.RequiredReserveResponse\x12R\n\rListAddresses\x12\x1f.walletrpc.ListAddressesRequest\x1a .walletrpc.ListAddressesResponse\x12\x64\n\x13SignMessageWithAddr\x12%.walletrpc.SignMessageWithAddrRequest\x1a&.walletrpc.SignMessageWithAddrResponse\x12j\n\x15VerifyMessageWithAddr\x12\'.walletrpc.VerifyMessageWithAddrRequest\x1a(.walletrpc.VerifyMessageWithAddrResponse\x12R\n\rImportAccount\x12\x1f.walletrpc.ImportAccountRequest\x1a .walletrpc.ImportAccountResponse\x12X\n\x0fImportPublicKey\x12!.walletrpc.ImportPublicKeyRequest\x1a\".walletrpc.ImportPublicKeyResponse\x12X\n\x0fImportTapscript\x12!.walletrpc.ImportTapscriptRequest\x1a\".walletrpc.ImportTapscriptResponse\x12H\n\x12PublishTransaction\x12\x16.walletrpc.Transaction\x1a\x1a.walletrpc.PublishResponse\x12L\n\x0bSendOutputs\x12\x1d.walletrpc.SendOutputsRequest\x1a\x1e.walletrpc.SendOutputsResponse\x12L\n\x0b\x45stimateFee\x12\x1d.walletrpc.EstimateFeeRequest\x1a\x1e.walletrpc.EstimateFeeResponse\x12R\n\rPendingSweeps\x12\x1f.walletrpc.PendingSweepsRequest\x1a .walletrpc.PendingSweepsResponse\x12@\n\x07\x42umpFee\x12\x19.walletrpc.BumpFeeRequest\x1a\x1a.walletrpc.BumpFeeResponse\x12I\n\nListSweeps\x12\x1c.walletrpc.ListSweepsRequest\x1a\x1d.walletrpc.ListSweepsResponse\x12[\n\x10LabelTransaction\x12\".walletrpc.LabelTransactionRequest\x1a#.walletrpc.LabelTransactionResponse\x12\x43\n\x08\x46undPsbt\x12\x1a.walletrpc.FundPsbtRequest\x1a\x1b.walletrpc.FundPsbtResponse\x12\x43\n\x08SignPsbt\x12\x1a.walletrpc.SignPsbtRequest\x1a\x1b.walletrpc.SignPsbtResponse\x12O\n\x0c\x46inalizePsbt\x12\x1e.walletrpc.FinalizePsbtRequest\x1a\x1f.walletrpc.FinalizePsbtResponseB1Z/github.com/lightningnetwork/lnd/lnrpc/walletrpcb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'walletkit_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'Z/github.com/lightningnetwork/lnd/lnrpc/walletrpc' + _PENDINGSWEEP.fields_by_name['sat_per_byte']._options = None + _PENDINGSWEEP.fields_by_name['sat_per_byte']._serialized_options = b'\030\001' + _PENDINGSWEEP.fields_by_name['requested_sat_per_byte']._options = None + _PENDINGSWEEP.fields_by_name['requested_sat_per_byte']._serialized_options = b'\030\001' + _BUMPFEEREQUEST.fields_by_name['sat_per_byte']._options = None + _BUMPFEEREQUEST.fields_by_name['sat_per_byte']._serialized_options = b'\030\001' + _TXTEMPLATE_OUTPUTSENTRY._options = None + _TXTEMPLATE_OUTPUTSENTRY._serialized_options = b'8\001' + _ADDRESSTYPE._serialized_start=4906 + _ADDRESSTYPE._serialized_end=5048 + _WITNESSTYPE._serialized_start=5051 + _WITNESSTYPE._serialized_end=5460 + _CHANGEADDRESSTYPE._serialized_start=5462 + _CHANGEADDRESSTYPE._serialized_end=5548 + _LISTUNSPENTREQUEST._serialized_start=61 + _LISTUNSPENTREQUEST._serialized_end=162 + _LISTUNSPENTRESPONSE._serialized_start=164 + _LISTUNSPENTRESPONSE._serialized_end=213 + _LEASEOUTPUTREQUEST._serialized_start=215 + _LEASEOUTPUTREQUEST._serialized_end=310 + _LEASEOUTPUTRESPONSE._serialized_start=312 + _LEASEOUTPUTRESPONSE._serialized_end=353 + _RELEASEOUTPUTREQUEST._serialized_start=355 + _RELEASEOUTPUTREQUEST._serialized_end=424 + _RELEASEOUTPUTRESPONSE._serialized_start=426 + _RELEASEOUTPUTRESPONSE._serialized_end=449 + _KEYREQ._serialized_start=451 + _KEYREQ._serialized_end=505 + _ADDRREQUEST._serialized_start=507 + _ADDRREQUEST._serialized_end=591 + _ADDRRESPONSE._serialized_start=593 + _ADDRRESPONSE._serialized_end=621 + _ACCOUNT._serialized_start=624 + _ACCOUNT._serialized_end=855 + _ADDRESSPROPERTY._serialized_start=857 + _ADDRESSPROPERTY._serialized_end=929 + _ACCOUNTWITHADDRESSES._serialized_start=932 + _ACCOUNTWITHADDRESSES._serialized_end=1086 + _LISTACCOUNTSREQUEST._serialized_start=1088 + _LISTACCOUNTSREQUEST._serialized_end=1169 + _LISTACCOUNTSRESPONSE._serialized_start=1171 + _LISTACCOUNTSRESPONSE._serialized_end=1231 + _REQUIREDRESERVEREQUEST._serialized_start=1233 + _REQUIREDRESERVEREQUEST._serialized_end=1293 + _REQUIREDRESERVERESPONSE._serialized_start=1295 + _REQUIREDRESERVERESPONSE._serialized_end=1346 + _LISTADDRESSESREQUEST._serialized_start=1348 + _LISTADDRESSESREQUEST._serialized_end=1422 + _LISTADDRESSESRESPONSE._serialized_start=1424 + _LISTADDRESSESRESPONSE._serialized_end=1512 + _SIGNMESSAGEWITHADDRREQUEST._serialized_start=1514 + _SIGNMESSAGEWITHADDRREQUEST._serialized_end=1569 + _SIGNMESSAGEWITHADDRRESPONSE._serialized_start=1571 + _SIGNMESSAGEWITHADDRRESPONSE._serialized_end=1619 + _VERIFYMESSAGEWITHADDRREQUEST._serialized_start=1621 + _VERIFYMESSAGEWITHADDRREQUEST._serialized_end=1697 + _VERIFYMESSAGEWITHADDRRESPONSE._serialized_start=1699 + _VERIFYMESSAGEWITHADDRRESPONSE._serialized_end=1761 + _IMPORTACCOUNTREQUEST._serialized_start=1764 + _IMPORTACCOUNTREQUEST._serialized_end=1924 + _IMPORTACCOUNTRESPONSE._serialized_start=1926 + _IMPORTACCOUNTRESPONSE._serialized_end=2050 + _IMPORTPUBLICKEYREQUEST._serialized_start=2052 + _IMPORTPUBLICKEYREQUEST._serialized_end=2142 + _IMPORTPUBLICKEYRESPONSE._serialized_start=2144 + _IMPORTPUBLICKEYRESPONSE._serialized_end=2169 + _IMPORTTAPSCRIPTREQUEST._serialized_start=2172 + _IMPORTTAPSCRIPTREQUEST._serialized_end=2398 + _TAPSCRIPTFULLTREE._serialized_start=2400 + _TAPSCRIPTFULLTREE._serialized_end=2459 + _TAPLEAF._serialized_start=2461 + _TAPLEAF._serialized_end=2508 + _TAPSCRIPTPARTIALREVEAL._serialized_start=2510 + _TAPSCRIPTPARTIALREVEAL._serialized_end=2607 + _IMPORTTAPSCRIPTRESPONSE._serialized_start=2609 + _IMPORTTAPSCRIPTRESPONSE._serialized_end=2656 + _TRANSACTION._serialized_start=2658 + _TRANSACTION._serialized_end=2702 + _PUBLISHRESPONSE._serialized_start=2704 + _PUBLISHRESPONSE._serialized_end=2744 + _SENDOUTPUTSREQUEST._serialized_start=2747 + _SENDOUTPUTSREQUEST._serialized_end=2881 + _SENDOUTPUTSRESPONSE._serialized_start=2883 + _SENDOUTPUTSRESPONSE._serialized_end=2920 + _ESTIMATEFEEREQUEST._serialized_start=2922 + _ESTIMATEFEEREQUEST._serialized_end=2963 + _ESTIMATEFEERESPONSE._serialized_start=2965 + _ESTIMATEFEERESPONSE._serialized_end=3006 + _PENDINGSWEEP._serialized_start=3009 + _PENDINGSWEEP._serialized_end=3347 + _PENDINGSWEEPSREQUEST._serialized_start=3349 + _PENDINGSWEEPSREQUEST._serialized_end=3371 + _PENDINGSWEEPSRESPONSE._serialized_start=3373 + _PENDINGSWEEPSRESPONSE._serialized_end=3445 + _BUMPFEEREQUEST._serialized_start=3448 + _BUMPFEEREQUEST._serialized_end=3584 + _BUMPFEERESPONSE._serialized_start=3586 + _BUMPFEERESPONSE._serialized_end=3603 + _LISTSWEEPSREQUEST._serialized_start=3605 + _LISTSWEEPSREQUEST._serialized_end=3641 + _LISTSWEEPSRESPONSE._serialized_start=3644 + _LISTSWEEPSRESPONSE._serialized_end=3848 + _LISTSWEEPSRESPONSE_TRANSACTIONIDS._serialized_start=3797 + _LISTSWEEPSRESPONSE_TRANSACTIONIDS._serialized_end=3838 + _LABELTRANSACTIONREQUEST._serialized_start=3850 + _LABELTRANSACTIONREQUEST._serialized_end=3923 + _LABELTRANSACTIONRESPONSE._serialized_start=3925 + _LABELTRANSACTIONRESPONSE._serialized_end=3951 + _FUNDPSBTREQUEST._serialized_start=3954 + _FUNDPSBTREQUEST._serialized_end=4207 + _FUNDPSBTRESPONSE._serialized_start=4209 + _FUNDPSBTRESPONSE._serialized_end=4321 + _TXTEMPLATE._serialized_start=4324 + _TXTEMPLATE._serialized_end=4470 + _TXTEMPLATE_OUTPUTSENTRY._serialized_start=4424 + _TXTEMPLATE_OUTPUTSENTRY._serialized_end=4470 + _UTXOLEASE._serialized_start=4472 + _UTXOLEASE._serialized_end=4584 + _SIGNPSBTREQUEST._serialized_start=4586 + _SIGNPSBTREQUEST._serialized_end=4624 + _SIGNPSBTRESPONSE._serialized_start=4626 + _SIGNPSBTRESPONSE._serialized_end=4688 + _FINALIZEPSBTREQUEST._serialized_start=4690 + _FINALIZEPSBTREQUEST._serialized_end=4749 + _FINALIZEPSBTRESPONSE._serialized_start=4751 + _FINALIZEPSBTRESPONSE._serialized_end=4816 + _LISTLEASESREQUEST._serialized_start=4818 + _LISTLEASESREQUEST._serialized_end=4837 + _LISTLEASESRESPONSE._serialized_start=4839 + _LISTLEASESRESPONSE._serialized_end=4903 + _WALLETKIT._serialized_start=5551 + _WALLETKIT._serialized_end=7552 +# @@protoc_insertion_point(module_scope) diff --git a/gui/lnd_deps/walletkit_pb2_grpc.py b/gui/lnd_deps/walletkit_pb2_grpc.py new file mode 100644 index 00000000..f2196377 --- /dev/null +++ b/gui/lnd_deps/walletkit_pb2_grpc.py @@ -0,0 +1,1082 @@ +# 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 +from . import walletkit_pb2 as walletkit__pb2 + + +class WalletKitStub(object): + """WalletKit is a service that gives access to the core functionalities of the + daemon's wallet. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListUnspent = channel.unary_unary( + '/walletrpc.WalletKit/ListUnspent', + request_serializer=walletkit__pb2.ListUnspentRequest.SerializeToString, + response_deserializer=walletkit__pb2.ListUnspentResponse.FromString, + ) + self.LeaseOutput = channel.unary_unary( + '/walletrpc.WalletKit/LeaseOutput', + request_serializer=walletkit__pb2.LeaseOutputRequest.SerializeToString, + response_deserializer=walletkit__pb2.LeaseOutputResponse.FromString, + ) + self.ReleaseOutput = channel.unary_unary( + '/walletrpc.WalletKit/ReleaseOutput', + request_serializer=walletkit__pb2.ReleaseOutputRequest.SerializeToString, + response_deserializer=walletkit__pb2.ReleaseOutputResponse.FromString, + ) + self.ListLeases = channel.unary_unary( + '/walletrpc.WalletKit/ListLeases', + request_serializer=walletkit__pb2.ListLeasesRequest.SerializeToString, + response_deserializer=walletkit__pb2.ListLeasesResponse.FromString, + ) + self.DeriveNextKey = channel.unary_unary( + '/walletrpc.WalletKit/DeriveNextKey', + request_serializer=walletkit__pb2.KeyReq.SerializeToString, + response_deserializer=signer__pb2.KeyDescriptor.FromString, + ) + self.DeriveKey = channel.unary_unary( + '/walletrpc.WalletKit/DeriveKey', + request_serializer=signer__pb2.KeyLocator.SerializeToString, + response_deserializer=signer__pb2.KeyDescriptor.FromString, + ) + self.NextAddr = channel.unary_unary( + '/walletrpc.WalletKit/NextAddr', + request_serializer=walletkit__pb2.AddrRequest.SerializeToString, + response_deserializer=walletkit__pb2.AddrResponse.FromString, + ) + self.ListAccounts = channel.unary_unary( + '/walletrpc.WalletKit/ListAccounts', + request_serializer=walletkit__pb2.ListAccountsRequest.SerializeToString, + response_deserializer=walletkit__pb2.ListAccountsResponse.FromString, + ) + self.RequiredReserve = channel.unary_unary( + '/walletrpc.WalletKit/RequiredReserve', + request_serializer=walletkit__pb2.RequiredReserveRequest.SerializeToString, + response_deserializer=walletkit__pb2.RequiredReserveResponse.FromString, + ) + self.ListAddresses = channel.unary_unary( + '/walletrpc.WalletKit/ListAddresses', + request_serializer=walletkit__pb2.ListAddressesRequest.SerializeToString, + response_deserializer=walletkit__pb2.ListAddressesResponse.FromString, + ) + self.SignMessageWithAddr = channel.unary_unary( + '/walletrpc.WalletKit/SignMessageWithAddr', + request_serializer=walletkit__pb2.SignMessageWithAddrRequest.SerializeToString, + response_deserializer=walletkit__pb2.SignMessageWithAddrResponse.FromString, + ) + self.VerifyMessageWithAddr = channel.unary_unary( + '/walletrpc.WalletKit/VerifyMessageWithAddr', + request_serializer=walletkit__pb2.VerifyMessageWithAddrRequest.SerializeToString, + response_deserializer=walletkit__pb2.VerifyMessageWithAddrResponse.FromString, + ) + self.ImportAccount = channel.unary_unary( + '/walletrpc.WalletKit/ImportAccount', + request_serializer=walletkit__pb2.ImportAccountRequest.SerializeToString, + response_deserializer=walletkit__pb2.ImportAccountResponse.FromString, + ) + self.ImportPublicKey = channel.unary_unary( + '/walletrpc.WalletKit/ImportPublicKey', + request_serializer=walletkit__pb2.ImportPublicKeyRequest.SerializeToString, + response_deserializer=walletkit__pb2.ImportPublicKeyResponse.FromString, + ) + self.ImportTapscript = channel.unary_unary( + '/walletrpc.WalletKit/ImportTapscript', + request_serializer=walletkit__pb2.ImportTapscriptRequest.SerializeToString, + response_deserializer=walletkit__pb2.ImportTapscriptResponse.FromString, + ) + self.PublishTransaction = channel.unary_unary( + '/walletrpc.WalletKit/PublishTransaction', + request_serializer=walletkit__pb2.Transaction.SerializeToString, + response_deserializer=walletkit__pb2.PublishResponse.FromString, + ) + self.SendOutputs = channel.unary_unary( + '/walletrpc.WalletKit/SendOutputs', + request_serializer=walletkit__pb2.SendOutputsRequest.SerializeToString, + response_deserializer=walletkit__pb2.SendOutputsResponse.FromString, + ) + self.EstimateFee = channel.unary_unary( + '/walletrpc.WalletKit/EstimateFee', + request_serializer=walletkit__pb2.EstimateFeeRequest.SerializeToString, + response_deserializer=walletkit__pb2.EstimateFeeResponse.FromString, + ) + self.PendingSweeps = channel.unary_unary( + '/walletrpc.WalletKit/PendingSweeps', + request_serializer=walletkit__pb2.PendingSweepsRequest.SerializeToString, + response_deserializer=walletkit__pb2.PendingSweepsResponse.FromString, + ) + self.BumpFee = channel.unary_unary( + '/walletrpc.WalletKit/BumpFee', + request_serializer=walletkit__pb2.BumpFeeRequest.SerializeToString, + response_deserializer=walletkit__pb2.BumpFeeResponse.FromString, + ) + self.ListSweeps = channel.unary_unary( + '/walletrpc.WalletKit/ListSweeps', + request_serializer=walletkit__pb2.ListSweepsRequest.SerializeToString, + response_deserializer=walletkit__pb2.ListSweepsResponse.FromString, + ) + self.LabelTransaction = channel.unary_unary( + '/walletrpc.WalletKit/LabelTransaction', + request_serializer=walletkit__pb2.LabelTransactionRequest.SerializeToString, + response_deserializer=walletkit__pb2.LabelTransactionResponse.FromString, + ) + self.FundPsbt = channel.unary_unary( + '/walletrpc.WalletKit/FundPsbt', + request_serializer=walletkit__pb2.FundPsbtRequest.SerializeToString, + response_deserializer=walletkit__pb2.FundPsbtResponse.FromString, + ) + self.SignPsbt = channel.unary_unary( + '/walletrpc.WalletKit/SignPsbt', + request_serializer=walletkit__pb2.SignPsbtRequest.SerializeToString, + response_deserializer=walletkit__pb2.SignPsbtResponse.FromString, + ) + self.FinalizePsbt = channel.unary_unary( + '/walletrpc.WalletKit/FinalizePsbt', + request_serializer=walletkit__pb2.FinalizePsbtRequest.SerializeToString, + response_deserializer=walletkit__pb2.FinalizePsbtResponse.FromString, + ) + + +class WalletKitServicer(object): + """WalletKit is a service that gives access to the core functionalities of the + daemon's wallet. + """ + + def ListUnspent(self, request, context): + """ + ListUnspent returns a list of all utxos spendable by the wallet with a + number of confirmations between the specified minimum and maximum. By + default, all utxos are listed. To list only the unconfirmed utxos, set + the unconfirmed_only to true. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def LeaseOutput(self, request, context): + """ + LeaseOutput locks an output to the given ID, preventing it from being + available for any future coin selection attempts. The absolute time of the + lock's expiration is returned. The expiration of the lock can be extended by + successive invocations of this RPC. Outputs can be unlocked before their + expiration through `ReleaseOutput`. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ReleaseOutput(self, request, context): + """ + ReleaseOutput unlocks an output, allowing it to be available for coin + selection if it remains unspent. The ID should match the one used to + originally lock the output. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListLeases(self, request, context): + """ + ListLeases lists all currently locked utxos. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeriveNextKey(self, request, context): + """ + DeriveNextKey attempts to derive the *next* key within the key family + (account in BIP43) specified. This method should return the next external + child within this branch. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeriveKey(self, request, context): + """ + DeriveKey attempts to derive an arbitrary key specified by the passed + KeyLocator. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def NextAddr(self, request, context): + """ + NextAddr returns the next unused address within the wallet. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListAccounts(self, request, context): + """ + ListAccounts retrieves all accounts belonging to the wallet by default. A + name and key scope filter can be provided to filter through all of the + wallet accounts and return only those matching. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RequiredReserve(self, request, context): + """ + RequiredReserve returns the minimum amount of satoshis that should be kept + in the wallet in order to fee bump anchor channels if necessary. The value + scales with the number of public anchor channels but is capped at a maximum. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListAddresses(self, request, context): + """ + ListAddresses retrieves all the addresses along with their balance. An + account name filter can be provided to filter through all of the + wallet accounts and return the addresses of only those matching. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SignMessageWithAddr(self, request, context): + """ + SignMessageWithAddr returns the compact signature (base64 encoded) created + with the private key of the provided address. This requires the address + to be solely based on a public key lock (no scripts). Obviously the internal + lnd wallet has to possess the private key of the address otherwise + an error is returned. + + This method aims to provide full compatibility with the bitcoin-core and + btcd implementation. Bitcoin-core's algorithm is not specified in a + BIP and only applicable for legacy addresses. This method enhances the + signing for additional address types: P2WKH, NP2WKH, P2TR. + For P2TR addresses this represents a special case. ECDSA is used to create + a compact signature which makes the public key of the signature recoverable. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def VerifyMessageWithAddr(self, request, context): + """ + VerifyMessageWithAddr returns the validity and the recovered public key of + the provided compact signature (base64 encoded). The verification is + twofold. First the validity of the signature itself is checked and then + it is verified that the recovered public key of the signature equals + the public key of the provided address. There is no dependence on the + private key of the address therefore also external addresses are allowed + to verify signatures. + Supported address types are P2PKH, P2WKH, NP2WKH, P2TR. + + This method is the counterpart of the related signing method + (SignMessageWithAddr) and aims to provide full compatibility to + bitcoin-core's implementation. Although bitcoin-core/btcd only provide + this functionality for legacy addresses this function enhances it to + the address types: P2PKH, P2WKH, NP2WKH, P2TR. + + The verification for P2TR addresses is a special case and requires the + ECDSA compact signature to compare the reovered public key to the internal + taproot key. The compact ECDSA signature format was used because there + are still no known compact signature schemes for schnorr signatures. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ImportAccount(self, request, context): + """ + ImportAccount imports an account backed by an account extended public key. + The master key fingerprint denotes the fingerprint of the root key + corresponding to the account public key (also known as the key with + derivation path m/). This may be required by some hardware wallets for + proper identification and signing. + + The address type can usually be inferred from the key's version, but may be + required for certain keys to map them into the proper scope. + + For BIP-0044 keys, an address type must be specified as we intend to not + support importing BIP-0044 keys into the wallet using the legacy + pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force + the standard BIP-0049 derivation scheme, while a witness address type will + force the standard BIP-0084 derivation scheme. + + For BIP-0049 keys, an address type must also be specified to make a + distinction between the standard BIP-0049 address schema (nested witness + pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys + externally, witness pubkeys internally). + + NOTE: Events (deposits/spends) for keys derived from an account will only be + detected by lnd if they happen after the import. Rescans to detect past + events will be supported later on. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ImportPublicKey(self, request, context): + """ + ImportPublicKey imports a public key as watch-only into the wallet. The + public key is converted into a simple address of the given type and that + address script is watched on chain. For Taproot keys, this will only watch + the BIP-0086 style output script. Use ImportTapscript for more advanced key + spend or script spend outputs. + + NOTE: Events (deposits/spends) for a key will only be detected by lnd if + they happen after the import. Rescans to detect past events will be + supported later on. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ImportTapscript(self, request, context): + """ + ImportTapscript imports a Taproot script and internal key and adds the + resulting Taproot output key as a watch-only output script into the wallet. + For BIP-0086 style Taproot keys (no root hash commitment and no script spend + path) use ImportPublicKey. + + NOTE: Events (deposits/spends) for a key will only be detected by lnd if + they happen after the import. Rescans to detect past events will be + supported later on. + + NOTE: Taproot keys imported through this RPC currently _cannot_ be used for + funding PSBTs. Only tracking the balance and UTXOs is currently supported. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PublishTransaction(self, request, context): + """ + PublishTransaction attempts to publish the passed transaction to the + network. Once this returns without an error, the wallet will continually + attempt to re-broadcast the transaction on start up, until it enters the + chain. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SendOutputs(self, request, context): + """ + SendOutputs is similar to the existing sendmany call in Bitcoind, and + allows the caller to create a transaction that sends to several outputs at + once. This is ideal when wanting to batch create a set of transactions. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EstimateFee(self, request, context): + """ + EstimateFee attempts to query the internal fee estimator of the wallet to + determine the fee (in sat/kw) to attach to a transaction in order to + achieve the confirmation target. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PendingSweeps(self, request, context): + """ + PendingSweeps returns lists of on-chain outputs that lnd is currently + attempting to sweep within its central batching engine. Outputs with similar + fee rates are batched together in order to sweep them within a single + transaction. + + NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + remain supported. This is an advanced API that depends on the internals of + the UtxoSweeper, so things may change. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def BumpFee(self, request, context): + """ + BumpFee bumps the fee of an arbitrary input within a transaction. This RPC + takes a different approach than bitcoind's bumpfee command. lnd has a + central batching engine in which inputs with similar fee rates are batched + together to save on transaction fees. Due to this, we cannot rely on + bumping the fee on a specific transaction, since transactions can change at + any point with the addition of new inputs. The list of inputs that + currently exist within lnd's central batching engine can be retrieved + through the PendingSweeps RPC. + + When bumping the fee of an input that currently exists within lnd's central + batching engine, a higher fee transaction will be created that replaces the + lower fee transaction through the Replace-By-Fee (RBF) policy. If it + + This RPC also serves useful when wanting to perform a Child-Pays-For-Parent + (CPFP), where the child transaction pays for its parent's fee. This can be + done by specifying an outpoint within the low fee transaction that is under + the control of the wallet. + + The fee preference can be expressed either as a specific fee rate or a delta + of blocks in which the output should be swept on-chain within. If a fee + preference is not explicitly specified, then an error is returned. + + Note that this RPC currently doesn't perform any validation checks on the + fee preference being provided. For now, the responsibility of ensuring that + the new fee preference is sufficient is delegated to the user. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListSweeps(self, request, context): + """ + ListSweeps returns a list of the sweep transactions our node has produced. + Note that these sweeps may not be confirmed yet, as we record sweeps on + broadcast, not confirmation. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def LabelTransaction(self, request, context): + """ + LabelTransaction adds a label to a transaction. If the transaction already + has a label the call will fail unless the overwrite bool is set. This will + overwrite the exiting transaction label. Labels must not be empty, and + cannot exceed 500 characters. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def FundPsbt(self, request, context): + """ + FundPsbt creates a fully populated PSBT that contains enough inputs to fund + the outputs specified in the template. There are two ways of specifying a + template: Either by passing in a PSBT with at least one output declared or + by passing in a raw TxTemplate message. + + If there are no inputs specified in the template, coin selection is + performed automatically. If the template does contain any inputs, it is + assumed that full coin selection happened externally and no additional + inputs are added. If the specified inputs aren't enough to fund the outputs + with the given fee rate, an error is returned. + + After either selecting or verifying the inputs, all input UTXOs are locked + with an internal app ID. + + NOTE: If this method returns without an error, it is the caller's + responsibility to either spend the locked UTXOs (by finalizing and then + publishing the transaction) or to unlock/release the locked UTXOs in case of + an error on the caller's side. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SignPsbt(self, request, context): + """ + SignPsbt expects a partial transaction with all inputs and outputs fully + declared and tries to sign all unsigned inputs that have all required fields + (UTXO information, BIP32 derivation information, witness or sig scripts) + set. + If no error is returned, the PSBT is ready to be given to the next signer or + to be finalized if lnd was the last signer. + + NOTE: This RPC only signs inputs (and only those it can sign), it does not + perform any other tasks (such as coin selection, UTXO locking or + input/output/fee value validation, PSBT finalization). Any input that is + incomplete will be skipped. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def FinalizePsbt(self, request, context): + """ + FinalizePsbt expects a partial transaction with all inputs and outputs fully + declared and tries to sign all inputs that belong to the wallet. Lnd must be + the last signer of the transaction. That means, if there are any unsigned + non-witness inputs or inputs without UTXO information attached or inputs + without witness data that do not belong to lnd's wallet, this method will + fail. If no error is returned, the PSBT is ready to be extracted and the + final TX within to be broadcast. + + NOTE: This method does NOT publish the transaction once finalized. It is the + caller's responsibility to either publish the transaction on success or + unlock/release any locked UTXOs in case of an error in this method. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_WalletKitServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListUnspent': grpc.unary_unary_rpc_method_handler( + servicer.ListUnspent, + request_deserializer=walletkit__pb2.ListUnspentRequest.FromString, + response_serializer=walletkit__pb2.ListUnspentResponse.SerializeToString, + ), + 'LeaseOutput': grpc.unary_unary_rpc_method_handler( + servicer.LeaseOutput, + request_deserializer=walletkit__pb2.LeaseOutputRequest.FromString, + response_serializer=walletkit__pb2.LeaseOutputResponse.SerializeToString, + ), + 'ReleaseOutput': grpc.unary_unary_rpc_method_handler( + servicer.ReleaseOutput, + request_deserializer=walletkit__pb2.ReleaseOutputRequest.FromString, + response_serializer=walletkit__pb2.ReleaseOutputResponse.SerializeToString, + ), + 'ListLeases': grpc.unary_unary_rpc_method_handler( + servicer.ListLeases, + request_deserializer=walletkit__pb2.ListLeasesRequest.FromString, + response_serializer=walletkit__pb2.ListLeasesResponse.SerializeToString, + ), + 'DeriveNextKey': grpc.unary_unary_rpc_method_handler( + servicer.DeriveNextKey, + request_deserializer=walletkit__pb2.KeyReq.FromString, + response_serializer=signer__pb2.KeyDescriptor.SerializeToString, + ), + 'DeriveKey': grpc.unary_unary_rpc_method_handler( + servicer.DeriveKey, + request_deserializer=signer__pb2.KeyLocator.FromString, + response_serializer=signer__pb2.KeyDescriptor.SerializeToString, + ), + 'NextAddr': grpc.unary_unary_rpc_method_handler( + servicer.NextAddr, + request_deserializer=walletkit__pb2.AddrRequest.FromString, + response_serializer=walletkit__pb2.AddrResponse.SerializeToString, + ), + 'ListAccounts': grpc.unary_unary_rpc_method_handler( + servicer.ListAccounts, + request_deserializer=walletkit__pb2.ListAccountsRequest.FromString, + response_serializer=walletkit__pb2.ListAccountsResponse.SerializeToString, + ), + 'RequiredReserve': grpc.unary_unary_rpc_method_handler( + servicer.RequiredReserve, + request_deserializer=walletkit__pb2.RequiredReserveRequest.FromString, + response_serializer=walletkit__pb2.RequiredReserveResponse.SerializeToString, + ), + 'ListAddresses': grpc.unary_unary_rpc_method_handler( + servicer.ListAddresses, + request_deserializer=walletkit__pb2.ListAddressesRequest.FromString, + response_serializer=walletkit__pb2.ListAddressesResponse.SerializeToString, + ), + 'SignMessageWithAddr': grpc.unary_unary_rpc_method_handler( + servicer.SignMessageWithAddr, + request_deserializer=walletkit__pb2.SignMessageWithAddrRequest.FromString, + response_serializer=walletkit__pb2.SignMessageWithAddrResponse.SerializeToString, + ), + 'VerifyMessageWithAddr': grpc.unary_unary_rpc_method_handler( + servicer.VerifyMessageWithAddr, + request_deserializer=walletkit__pb2.VerifyMessageWithAddrRequest.FromString, + response_serializer=walletkit__pb2.VerifyMessageWithAddrResponse.SerializeToString, + ), + 'ImportAccount': grpc.unary_unary_rpc_method_handler( + servicer.ImportAccount, + request_deserializer=walletkit__pb2.ImportAccountRequest.FromString, + response_serializer=walletkit__pb2.ImportAccountResponse.SerializeToString, + ), + 'ImportPublicKey': grpc.unary_unary_rpc_method_handler( + servicer.ImportPublicKey, + request_deserializer=walletkit__pb2.ImportPublicKeyRequest.FromString, + response_serializer=walletkit__pb2.ImportPublicKeyResponse.SerializeToString, + ), + 'ImportTapscript': grpc.unary_unary_rpc_method_handler( + servicer.ImportTapscript, + request_deserializer=walletkit__pb2.ImportTapscriptRequest.FromString, + response_serializer=walletkit__pb2.ImportTapscriptResponse.SerializeToString, + ), + 'PublishTransaction': grpc.unary_unary_rpc_method_handler( + servicer.PublishTransaction, + request_deserializer=walletkit__pb2.Transaction.FromString, + response_serializer=walletkit__pb2.PublishResponse.SerializeToString, + ), + 'SendOutputs': grpc.unary_unary_rpc_method_handler( + servicer.SendOutputs, + request_deserializer=walletkit__pb2.SendOutputsRequest.FromString, + response_serializer=walletkit__pb2.SendOutputsResponse.SerializeToString, + ), + 'EstimateFee': grpc.unary_unary_rpc_method_handler( + servicer.EstimateFee, + request_deserializer=walletkit__pb2.EstimateFeeRequest.FromString, + response_serializer=walletkit__pb2.EstimateFeeResponse.SerializeToString, + ), + 'PendingSweeps': grpc.unary_unary_rpc_method_handler( + servicer.PendingSweeps, + request_deserializer=walletkit__pb2.PendingSweepsRequest.FromString, + response_serializer=walletkit__pb2.PendingSweepsResponse.SerializeToString, + ), + 'BumpFee': grpc.unary_unary_rpc_method_handler( + servicer.BumpFee, + request_deserializer=walletkit__pb2.BumpFeeRequest.FromString, + response_serializer=walletkit__pb2.BumpFeeResponse.SerializeToString, + ), + 'ListSweeps': grpc.unary_unary_rpc_method_handler( + servicer.ListSweeps, + request_deserializer=walletkit__pb2.ListSweepsRequest.FromString, + response_serializer=walletkit__pb2.ListSweepsResponse.SerializeToString, + ), + 'LabelTransaction': grpc.unary_unary_rpc_method_handler( + servicer.LabelTransaction, + request_deserializer=walletkit__pb2.LabelTransactionRequest.FromString, + response_serializer=walletkit__pb2.LabelTransactionResponse.SerializeToString, + ), + 'FundPsbt': grpc.unary_unary_rpc_method_handler( + servicer.FundPsbt, + request_deserializer=walletkit__pb2.FundPsbtRequest.FromString, + response_serializer=walletkit__pb2.FundPsbtResponse.SerializeToString, + ), + 'SignPsbt': grpc.unary_unary_rpc_method_handler( + servicer.SignPsbt, + request_deserializer=walletkit__pb2.SignPsbtRequest.FromString, + response_serializer=walletkit__pb2.SignPsbtResponse.SerializeToString, + ), + 'FinalizePsbt': grpc.unary_unary_rpc_method_handler( + servicer.FinalizePsbt, + request_deserializer=walletkit__pb2.FinalizePsbtRequest.FromString, + response_serializer=walletkit__pb2.FinalizePsbtResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'walletrpc.WalletKit', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class WalletKit(object): + """WalletKit is a service that gives access to the core functionalities of the + daemon's wallet. + """ + + @staticmethod + def ListUnspent(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, '/walletrpc.WalletKit/ListUnspent', + walletkit__pb2.ListUnspentRequest.SerializeToString, + walletkit__pb2.ListUnspentResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def LeaseOutput(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, '/walletrpc.WalletKit/LeaseOutput', + walletkit__pb2.LeaseOutputRequest.SerializeToString, + walletkit__pb2.LeaseOutputResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ReleaseOutput(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, '/walletrpc.WalletKit/ReleaseOutput', + walletkit__pb2.ReleaseOutputRequest.SerializeToString, + walletkit__pb2.ReleaseOutputResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListLeases(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, '/walletrpc.WalletKit/ListLeases', + walletkit__pb2.ListLeasesRequest.SerializeToString, + walletkit__pb2.ListLeasesResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeriveNextKey(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, '/walletrpc.WalletKit/DeriveNextKey', + walletkit__pb2.KeyReq.SerializeToString, + signer__pb2.KeyDescriptor.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeriveKey(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, '/walletrpc.WalletKit/DeriveKey', + signer__pb2.KeyLocator.SerializeToString, + signer__pb2.KeyDescriptor.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def NextAddr(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, '/walletrpc.WalletKit/NextAddr', + walletkit__pb2.AddrRequest.SerializeToString, + walletkit__pb2.AddrResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListAccounts(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, '/walletrpc.WalletKit/ListAccounts', + walletkit__pb2.ListAccountsRequest.SerializeToString, + walletkit__pb2.ListAccountsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def RequiredReserve(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, '/walletrpc.WalletKit/RequiredReserve', + walletkit__pb2.RequiredReserveRequest.SerializeToString, + walletkit__pb2.RequiredReserveResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListAddresses(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, '/walletrpc.WalletKit/ListAddresses', + walletkit__pb2.ListAddressesRequest.SerializeToString, + walletkit__pb2.ListAddressesResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SignMessageWithAddr(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, '/walletrpc.WalletKit/SignMessageWithAddr', + walletkit__pb2.SignMessageWithAddrRequest.SerializeToString, + walletkit__pb2.SignMessageWithAddrResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def VerifyMessageWithAddr(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, '/walletrpc.WalletKit/VerifyMessageWithAddr', + walletkit__pb2.VerifyMessageWithAddrRequest.SerializeToString, + walletkit__pb2.VerifyMessageWithAddrResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ImportAccount(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, '/walletrpc.WalletKit/ImportAccount', + walletkit__pb2.ImportAccountRequest.SerializeToString, + walletkit__pb2.ImportAccountResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ImportPublicKey(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, '/walletrpc.WalletKit/ImportPublicKey', + walletkit__pb2.ImportPublicKeyRequest.SerializeToString, + walletkit__pb2.ImportPublicKeyResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ImportTapscript(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, '/walletrpc.WalletKit/ImportTapscript', + walletkit__pb2.ImportTapscriptRequest.SerializeToString, + walletkit__pb2.ImportTapscriptResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def PublishTransaction(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, '/walletrpc.WalletKit/PublishTransaction', + walletkit__pb2.Transaction.SerializeToString, + walletkit__pb2.PublishResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SendOutputs(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, '/walletrpc.WalletKit/SendOutputs', + walletkit__pb2.SendOutputsRequest.SerializeToString, + walletkit__pb2.SendOutputsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def EstimateFee(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, '/walletrpc.WalletKit/EstimateFee', + walletkit__pb2.EstimateFeeRequest.SerializeToString, + walletkit__pb2.EstimateFeeResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def PendingSweeps(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, '/walletrpc.WalletKit/PendingSweeps', + walletkit__pb2.PendingSweepsRequest.SerializeToString, + walletkit__pb2.PendingSweepsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def BumpFee(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, '/walletrpc.WalletKit/BumpFee', + walletkit__pb2.BumpFeeRequest.SerializeToString, + walletkit__pb2.BumpFeeResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListSweeps(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, '/walletrpc.WalletKit/ListSweeps', + walletkit__pb2.ListSweepsRequest.SerializeToString, + walletkit__pb2.ListSweepsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def LabelTransaction(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, '/walletrpc.WalletKit/LabelTransaction', + walletkit__pb2.LabelTransactionRequest.SerializeToString, + walletkit__pb2.LabelTransactionResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def FundPsbt(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, '/walletrpc.WalletKit/FundPsbt', + walletkit__pb2.FundPsbtRequest.SerializeToString, + walletkit__pb2.FundPsbtResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SignPsbt(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, '/walletrpc.WalletKit/SignPsbt', + walletkit__pb2.SignPsbtRequest.SerializeToString, + walletkit__pb2.SignPsbtResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def FinalizePsbt(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, '/walletrpc.WalletKit/FinalizePsbt', + walletkit__pb2.FinalizePsbtRequest.SerializeToString, + walletkit__pb2.FinalizePsbtResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/gui/migrations/0035_histfailedhtlc.py b/gui/migrations/0035_histfailedhtlc.py new file mode 100644 index 00000000..675385bd --- /dev/null +++ b/gui/migrations/0035_histfailedhtlc.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.7 on 2023-03-03 01:15 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('gui', '0034_peerevents'), + ] + + operations = [ + migrations.CreateModel( + name='HistFailedHTLC', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField(default=django.utils.timezone.now)), + ('chan_id_in', models.CharField(max_length=20)), + ('chan_id_out', models.CharField(max_length=20)), + ('chan_in_alias', models.CharField(max_length=32, null=True)), + ('chan_out_alias', models.CharField(max_length=32, null=True)), + ('htlc_count', models.IntegerField()), + ('amount_sum', models.BigIntegerField()), + ('fee_sum', models.BigIntegerField()), + ('liq_avg', models.BigIntegerField()), + ('pending_avg', models.BigIntegerField()), + ('balance_count', models.IntegerField()), + ('downstream_count', models.IntegerField()), + ('other_count', models.IntegerField()), + ], + options={ + 'unique_together': {('date', 'chan_id_in', 'chan_id_out')}, + }, + ), + ] diff --git a/gui/models.py b/gui/models.py index 70c63a34..6abd6737 100644 --- a/gui/models.py +++ b/gui/models.py @@ -303,4 +303,22 @@ class PeerEvents(models.Model): new_value = models.BigIntegerField() out_liq = models.BigIntegerField() class Meta: - app_label = 'gui' \ No newline at end of file + app_label = 'gui' + +class HistFailedHTLC(models.Model): + date = models.DateField(default=timezone.now) + 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) + htlc_count = models.IntegerField() + amount_sum = models.BigIntegerField() + fee_sum = models.BigIntegerField() + liq_avg = models.BigIntegerField() + pending_avg = models.BigIntegerField() + balance_count = models.IntegerField() + downstream_count = models.IntegerField() + other_count = models.IntegerField() + class Meta: + app_label = 'gui' + unique_together = (('date', 'chan_id_in', 'chan_id_out'),) \ No newline at end of file diff --git a/gui/serializers.py b/gui/serializers.py index dce5daf6..bcf2009f 100644 --- a/gui/serializers.py +++ b/gui/serializers.py @@ -85,7 +85,8 @@ class RebalancerSerializer(serializers.HyperlinkedModelSerializer): requested = serializers.ReadOnlyField() start = serializers.ReadOnlyField() stop = serializers.ReadOnlyField() - status = serializers.ReadOnlyField() + fees_paid = serializers.ReadOnlyField() + payment_hash = serializers.ReadOnlyField() class Meta: model = Rebalancer exclude = [] @@ -103,6 +104,12 @@ class CloseChannelSerializer(serializers.Serializer): target_fee = serializers.IntegerField(label='target_fee') force = serializers.BooleanField(default=False) +class BumpFeeSerializer(serializers.Serializer): + txid = serializers.CharField(label='txid') + index = serializers.IntegerField(label='index') + target_fee = serializers.IntegerField(label='target_fee') + force = serializers.BooleanField(default=False) + class AddInvoiceSerializer(serializers.Serializer): value = serializers.IntegerField(label='value') diff --git a/gui/static/api.js b/gui/static/api.js new file mode 100644 index 00000000..88920635 --- /dev/null +++ b/gui/static/api.js @@ -0,0 +1,119 @@ +async function GET(url, {method = 'GET', data = null}){ + if(!data.limit) data.limit = 100000 + if(!data.format) data.format = 'json' + return call({url, method, data}) +} + +async function POST(url, {method = 'POST', body}){ + return call({url, method, body}) +} + +async function PUT(url, {method = 'PUT', body}){ + return call({url, method, body}) +} + +async function PATCH(url, {method = 'PATCH', body}){ + return call({url, method, body}) +} + +async function DELETE(url, {method = 'DELETE'}){ + return call({url, method}) +} + +async function call({url, method, data, body, headers = {'Content-Type':'application/json'}}){ + if(url.charAt(url.length-1) != '/') url += '/' + if(method != 'GET') headers['X-CSRFToken'] = document.getElementById('api').dataset.token + const result = await fetch(`api/${url}${data ? '?': ''}${new URLSearchParams(data).toString()}`, {method, body: JSON.stringify(body), headers}) + return result.json() +} + +class Sync{ + static PUT(url, {method = 'PUT', body}, callback){ + call({url, method, body}).then(res => callback(res)) + } +} + +function showBannerMsg(h1Msg, result){ + document.getElementById('content').insertAdjacentHTML("beforebegin", `
X

${h1Msg} updated to: ${result}

`); + window.scrollTo(0, 0); +} + +function flash(element, response){ + if (response != element.value) { + element.value = response + return + } + var rgb = window.getComputedStyle(element).backgroundColor; + rgb = rgb.substring(4, rgb.length-1).replace(/ /g, '').split(','); + var r = rgb[0], g = rgb[1], bOrigin = rgb[2], b = bOrigin; + var add = false; + var complete = false; + const increment = 15; + var flashOn = setInterval(() => { + if(add){ + if(b < 255 && (b+increment) <= 255){ + b += increment; + }else{ + add = false; + complete = true; + } + }else{ + if(complete == true && b < bOrigin){ + b = bOrigin; + clearInterval(flashOn); + } + else if(b > 0 && (b-increment) >= 0){ + b -= increment; + }else{ + add = true; + } + } + element.style.backgroundColor = 'RGB('+r+','+g+','+b+')'; + if(b == bOrigin) element.style.removeProperty("background-color"); + }, 50); +} + +function formatDate(start, end = new Date().getTime() + new Date().getTimezoneOffset()*60000){ + if (end == null) return '---' + end = new Date(end) + if (start == null) return '---' + difference = (end - new Date(start))/1000 + if (difference < 0) return 'Just now' + if (difference < 60) { + if (Math.floor(difference) == 1){ + return `a second ago`; + }else{ + return `${Math.floor(difference)} seconds ago`; + } + } else if (difference < 3600) { + if (Math.floor(difference / 60) == 1){ + return `a minute ago`; + }else{ + return `${Math.floor(difference / 60)} minutes ago`; + } + } else if (difference < 86400) { + if (Math.floor(difference / 3600) == 1){ + return `an hour ago`; + }else{ + return `${Math.floor(difference / 3600)} hours ago`; + } + } else if (difference < 2620800) { + if (Math.floor(difference / 86400) == 1){ + return `a day ago`; + }else{ + return `${Math.floor(difference / 86400)} days ago`; + } + } else if (difference < 31449600) { + if (Math.floor(difference / 2620800) == 1){ + return `a month ago`; + }else{ + return `${Math.floor(difference / 2620800)} months ago`; + } + } else { + if (Math.floor(difference / 31449600) == 1){ + return `a year ago`; + }else{ + return `${Math.floor(difference / 31449600)} years ago`; + } + } +} \ No newline at end of file diff --git a/gui/static/dark_mode.js b/gui/static/dark_mode.js index 74a72e3b..8be8397c 100644 --- a/gui/static/dark_mode.js +++ b/gui/static/dark_mode.js @@ -11,31 +11,15 @@ function getCookie(cname) { } } return ""; +} +function applyTheme(){ + if(getCookie("darkmode") == "true"){ + toggleTheme(); } - -function darkMode() { - let darkmode = getCookie("darkmode"); - if (darkmode != "") { - if (darkmode == "true") { - document.cookie = "darkmode=false"; - } else { - document.cookie = "darkmode=true"; - } - } else { - document.cookie = "darkmode=true"; - } - var element = document.body; - element.classList.toggle("dark-mode"); - element.classList.toggle("dark-mode-table1"); - element.classList.toggle("dark-mode-table2"); } -function checkDarkMode() { - let darkmode = getCookie("darkmode"); - if (darkmode == "true") { - var element = document.body; - element.classList.toggle("dark-mode"); - element.classList.toggle("dark-mode-table1"); - element.classList.toggle("dark-mode-table2"); - } +function toggleTheme() { + var element = document.body; + element.classList.toggle("dark-mode") + document.cookie = `darkmode=${element.classList.contains("dark-mode")}` } \ No newline at end of file diff --git a/gui/static/qrcode.js b/gui/static/qrcode.js new file mode 100644 index 00000000..993e88f3 --- /dev/null +++ b/gui/static/qrcode.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/gui/static/sort_table.js b/gui/static/sort_table.js index 2f3e0a13..3b30be14 100644 --- a/gui/static/sort_table.js +++ b/gui/static/sort_table.js @@ -1,35 +1,33 @@ -function sortTable(n, type, tableName, skip=0, link=false) { - var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0; +function sortTable(header, n, type, skip=0, tag="td") { + var switching, i, curr_row, next_row, shouldSwitch, dir, switchcount = 0; var upArrow = " ▲", downArrow = " ▼"; - table = document.getElementById(tableName); - header = table.rows[0+skip].getElementsByTagName("TH")[n]; + var table = header.parentElement.parentElement.parentElement; //th.tr.tbody.table + var rows = table.rows; switching = true; dir = "asc"; while (switching) { switching = false; - rows = table.rows; for (i=1+skip; i<(rows.length-1); i++) { shouldSwitch = false; - if (link == true) { - x = rows[i].getElementsByTagName("A")[n]; - y = rows[i+1].getElementsByTagName("A")[n]; - } else { - x = rows[i].getElementsByTagName("TD")[n]; - y = rows[i+1].getElementsByTagName("TD")[n]; - } + curr_row = rows[i].children[n] + next_row = rows[i+1].children[n] + if(tag !== "td"){ + curr_row = curr_row.getElementsByTagName(tag)[0]; + next_row = next_row.getElementsByTagName(tag)[0]; + } if (dir == "asc") { - if (type == "String" && x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase() - || type == "int" && parseInt(x.innerHTML.replace(/,/g, '')) > parseInt(y.innerHTML.replace(/,/g, '')) - || type != "String" && type != "int" && Number(x.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')) > Number(y.innerHTML.toLowerCase().split(type)[0].replace(/,/g, ''))) + if (type == "String" && curr_row.innerHTML.toLowerCase() > next_row.innerHTML.toLowerCase() + || type == "int" && parseInt(curr_row.innerHTML.replace(/,/g, '')) > parseInt(next_row.innerHTML.replace(/,/g, '')) + || type != "String" && type != "int" && Number(curr_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')) > Number(next_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, ''))) { shouldSwitch = true; break; } } else if (dir == "desc") { - if (type == "String" && x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase() - || type == "int" && parseInt(x.innerHTML.replace(/,/g, '')) < parseInt(y.innerHTML.replace(/,/g, '')) - || type != "String" && type != "int" && Number(x.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')) < Number(y.innerHTML.toLowerCase().split(type)[0].replace(/,/g, ''))) + if (type == "String" && curr_row.innerHTML.toLowerCase() < next_row.innerHTML.toLowerCase() + || type == "int" && parseInt(curr_row.innerHTML.replace(/,/g, '')) < parseInt(next_row.innerHTML.replace(/,/g, '')) + || type != "String" && type != "int" && Number(curr_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, '')) < Number(next_row.innerHTML.toLowerCase().split(type)[0].replace(/,/g, ''))) { shouldSwitch = true; break; diff --git a/gui/static/w3style.css b/gui/static/w3style.css index 4041dd85..e1f3ea39 100644 --- a/gui/static/w3style.css +++ b/gui/static/w3style.css @@ -30,9 +30,10 @@ h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font- h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} hr{border:0;border-top:1px solid #eee;margin:20px 0} .w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} -.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} +table {border-radius: 10px} table th{background-color: #f0f0f5;} table tbody tr td{border-top: 1px solid #c9d1d9} +th{position:sticky;overflow: hidden;top:0px;z-index:10}tr:first-child th:first-child{border-radius: 10px 0 0 0}tr:first-child th:last-child{border-radius: 0 10px 0 0}tr:last-child td:first-child{border-radius: 0 0 0 10px}tr:last-child td:last-child{border-radius: 0 0 10px 0} .w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} -.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} +.w3-table,.w3-table-all{border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} .w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} .w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} .w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} @@ -228,11 +229,31 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} .w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} .w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} .w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} -.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} +.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e55!important} .w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} .w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} .w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} .w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} -.dark-mode {background-color:#616161;color:white} -.dark-mode-table1 tr {background-color:#616161!important} -.dark-mode-table2 tr:nth-child(even){background-color:#888888!important} \ No newline at end of file +.dark-mode {color-scheme: dark;background-color: #0d1117;color: #c9d1d9} +.dark-mode .w3-table-all, .dark-mode thead th {border: 1px solid rgba(200,200,200,.2)!important;} +.dark-mode table tr {background-color: #161b22;border: 1px solid #30363d;} .dark-mode table tbody tr td {border-top: 1px solid #30363d} +input:not([type=submit]):not([type=checkbox]), select {-moz-appearance: none; -webkit-appearance: none; appearance: none;border-radius: 6px;border-color: rgba(0,0,0,0.1);background-color: white; color: black;padding: 1px 2px;border: 2px solid rgba(0,0,0,0.1);min-height: 28px;} +.dark-mode input:not([type=submit]):not([type=checkbox]), .dark-mode select{-moz-appearance: none; -webkit-appearance: none; appearance: none;border-color: rgba(240,246,252,0.1);background-color: #0d1117;color: #c9d1d9;} +input[type=submit] {cursor: pointer;border-radius: 6px;border-color: rgba(0,0,0,0.1);}input:disabled{cursor: default;opacity:0.3} +.dark-mode input[type=submit]{background-color: #30363d;color: #c9d1d9;border-color: rgba(240,246,252,0.1);} +.dark-mode table th {background-color: #0d1119;} +button {cursor: pointer;border-radius: 6px;border-color: rgba(0,0,0,0.1);} +.dark-mode button {background-color: #30363d;color: #c9d1d9;border-radius: 6px;border-color: rgba(240,246,252,0.1);} +a {color: #0969da} a svg{fill:#30363d}a svg:hover path{fill:#0969da}.dark-mode a {color: #58a6ff}.dark-mode a svg{fill:#c9d1d9}.dark-mode a svg:hover path{fill:#58a6ff} +a:link {text-decoration: none} +.dark-mode .w3-hoverable tbody tr:hover,.dark-mode .w3-ul.w3-hoverable li:hover {background-color: #30363d !important} +.dark-mode .w3-green{background-color: #238636}.dark-mode .w3-border-green{border-color: #238636} +.dark-mode .w3-red{background-color: #f85149}.dark-mode .w3-border-red{border-color: #f85149} +@keyframes fill { from { width: 0%; }} +.progress {min-width: 200px;height: 8px;width: 100%;position: relative;} +.progress .value {display: flex;height: 100%;animation: fill 1s ease forwards} +input:not([type=submit]):not([type=checkbox]):focus, select:focus {outline: none !important;border: 2px solid #0969da;box-shadow: inset 0 0 0 1px #0969da;} +.dark-mode input:not([type=submit]):not([type=checkbox]):focus, .dark-mode select:focus {outline: none !important;border:2px solid #58a6ff;box-shadow: inset 0 0 0 1px #58a6ff;} +.input {position:relative;float:left;padding:0px 4px}.input::after{position:absolute;right: 25px;top:2px;opacity:.8;content:attr(data-unit);color:#555599} +.encrypted td:not(th){-webkit-text-security:circle;}.encrypted thead th:not(a){-webkit-text-security:circle;} +.soft-encrypted table td, .soft-encrypted table thead{color: transparent;text-shadow: 0 0 5px rgba(0,0,0,0.1);} diff --git a/gui/templates/action_list.html b/gui/templates/action_list.html index 9ecfe64a..0b446a20 100644 --- a/gui/templates/action_list.html +++ b/gui/templates/action_list.html @@ -9,9 +9,8 @@

Suggested Action List

Channel ID Peer Alias - Capacity Outbound Liquidity - + Capacity Inbound Liquidity Unsettled oRate @@ -27,9 +26,13 @@

Suggested Action List

{{ channel.short_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 %}
+ + +
+ +
+ {{ channel.remote_balance|intcomma }} ({{ channel.inbound_percent }}%) {{ channel.unsettled_balance|intcomma }} {{ channel.local_fee_rate|intcomma }} diff --git a/gui/templates/advanced.html b/gui/templates/advanced.html index a04516a8..e0db7978 100644 --- a/gui/templates/advanced.html +++ b/gui/templates/advanced.html @@ -8,7 +8,7 @@

Advanced Channel Settings

- + - - - - - - - + + + + + - - + + - + {% for channel in channels %} - - + - - - - - - - - + + - - - + {% endfor %}
{% csrf_token %} @@ -73,39 +73,41 @@

Advanced Channel Settings

Channel IDPeer AliasCapacityOutbound LiquidityInbound LiquidityChannel IDPeer AliasOutbound LiquidityCapacityInbound Liquidity Channel State oRate oBase oCLTV minHTLC maxHTLCiRateiBaseiRateiBase Target Amt Max Cost % oTarget% iTarget% ARActiveActive
{{ channel.short_chan_id }} {% if channel.private == False %}{% endif %}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{% if channel.private == False %}{% endif %}{{ channel.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 and channel.private == False %}
{% csrf_token %} @@ -118,7 +120,7 @@

Advanced Channel Settings

--- {% endif %}
+ {% csrf_token %} @@ -126,7 +128,7 @@

Advanced Channel Settings

+
{% csrf_token %} @@ -134,7 +136,7 @@

Advanced Channel Settings

+
{% csrf_token %} @@ -142,7 +144,7 @@

Advanced Channel Settings

+
{% csrf_token %} @@ -150,7 +152,7 @@

Advanced Channel Settings

+
{% csrf_token %} @@ -158,8 +160,8 @@

Advanced Channel Settings

{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }}{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }}
{% csrf_token %} @@ -176,7 +178,7 @@

Advanced Channel Settings

+
{% csrf_token %} @@ -184,7 +186,7 @@

Advanced Channel Settings

+
{% csrf_token %} @@ -214,44 +216,6 @@

Advanced Channel Settings

{% endif %} {% if local_settings %} -
-

Update Local Settings

- - - - - - {% for settings in local_settings %} - - - - - {% endfor %} -
KeyValue
{{ settings.key }} - - {% csrf_token %} - {% if settings.key == 'AR-Target%' %} - - {% elif settings.key|slice:"-1:" == '%' or settings.key == 'AR-Variance' or settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AR-WaitPeriod' or settings.key == 'AR-APDays' or settings.key == 'AF-UpdateHours' %} - - {% elif settings.key == 'AR-Time' %} - - {% elif settings.key == 'AR-Workers' %} - - {% elif settings.key == 'AR-MaxFeeRate' %} - - {% elif settings.key == 'AF-MaxRate' or settings.key == 'AF-MinRate' %} - - {% elif settings.key == 'LND-CleanPayments' or settings.key|slice:":3" == 'AR-' or settings.key|slice:":3" == 'AF-'%} - - {% elif settings.key == 'LND-RetentionDays' %} - - {% else %} - - {% endif %} - - -
-
+{% include 'local_settings.html' with settings=local_settings title='Update Local' %} {% endif %} {% endblock %} diff --git a/gui/templates/autofees.html b/gui/templates/autofees.html index d420df49..debff713 100644 --- a/gui/templates/autofees.html +++ b/gui/templates/autofees.html @@ -21,7 +21,7 @@

Our Fees Logs

{% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} {{ log.setting }} {{ log.old_value }} log.old_value %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{{ log.new_value }} log.old_value %}style="background-color: rgba(46,160,67,0.15)"{% else %}style="background-color: rgba(248,81,73,0.15)"{% endif %}>{{ log.new_value }}
diff --git a/gui/templates/autopilot.html b/gui/templates/autopilot.html index 178ec153..b904d349 100644 --- a/gui/templates/autopilot.html +++ b/gui/templates/autopilot.html @@ -21,7 +21,7 @@

Autopilot Logs

{% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} {{ log.setting }} {{ log.old_value }} - {{ log.new_value }} + {{ log.new_value }} {% endfor %} diff --git a/gui/templates/balances.html b/gui/templates/balances.html index 8d9ad30d..3ea2917c 100644 --- a/gui/templates/balances.html +++ b/gui/templates/balances.html @@ -11,6 +11,7 @@

Balances

Amount Outpoint Confirmations + Fee Bumps {% for utxo in utxos %} @@ -18,6 +19,17 @@

Balances

{{ utxo.amount_sat|intcomma }} {{ utxo.outpoint.txid_str }} {{ utxo.confirmations|intcomma }} + + {% if utxo.confirmations == 0 %} +
+ + + +
+ {% else %} + --- + {% endif %} + {% endfor %} @@ -51,4 +63,12 @@

Transactions

No wallet transactions found!

{% endif %} + {% endblock %} \ No newline at end of file diff --git a/gui/templates/base.html b/gui/templates/base.html index 407c3b70..d8c54742 100644 --- a/gui/templates/base.html +++ b/gui/templates/base.html @@ -3,8 +3,10 @@ + + {% block meta %}{% endblock %} @@ -12,30 +14,113 @@ {% load qr_code %} - +
- {% if messages %} -
- {% for message in messages %} -

{{ message.message }}

- {% if message.message|slice:":17" == "Deposit Address: " or message.message|slice:":17" == "Invoice created! " %}{% qr_from_text message.message|slice:"17:" size="s" image_format="png" error_correction="L" %}{% endif %} - {% endfor %} + {% if messages %} +
+ {% for message in messages %} +
+ X +

{{ message.message }}

+ {% with sliced_msg=message.message|slice:":17" %} + {% if sliced_msg == "Deposit Address: " or sliced_msg == "Invoice created! " %} + {% qr_from_text message.message|slice:"17:" size="s" image_format="png" error_correction="L" %} + {% endif %} + {% endwith %}
- {% endif %} - - {% block content %}{% endblock %} + {% endfor %} +
+ {% endif %} + + {% block content %}{% endblock %}
+ diff --git a/gui/templates/batch.html b/gui/templates/batch.html index 3d33aeea..c8adab02 100644 --- a/gui/templates/batch.html +++ b/gui/templates/batch.html @@ -2,6 +2,13 @@ {% block title %} {{ block.super }} - Batch Open{% endblock %} {% block content %} {% load humanize %} +

Batch Open Up To 10 Channels

@@ -10,15 +17,16 @@

Batch Open Up To 10 Channels

ID Pubkey - Amt + Amount (sats) {% for num in iterator %} {{ num }} - - + + {% endfor %} + #Remaining Onchain Balance: {{balances.total_balance|intcomma}}Total: 0
    Opening Fee Rate (sats/vbyte): diff --git a/gui/templates/channel.html b/gui/templates/channel.html index b9bc0bef..c24a8718 100644 --- a/gui/templates/channel.html +++ b/gui/templates/channel.html @@ -57,7 +57,7 @@

    Channel Settings

    Active - + {% csrf_token %} @@ -66,14 +66,14 @@

    Channel Settings

    - {{ channel.fees_updated|naturaltime }} + {{ channel.fees_updated|naturaltime }} {% 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 %}
    {% csrf_token %} @@ -86,7 +86,7 @@

    Channel Settings

    --- {% endif %} - + {% csrf_token %} @@ -94,7 +94,7 @@

    Channel Settings

    - +
    {% csrf_token %} @@ -102,8 +102,8 @@

    Channel Settings

    - {{ channel.remote_fee_rate|intcomma }} - {{ channel.remote_base_fee|intcomma }} + {{ channel.remote_fee_rate|intcomma }} + {{ channel.remote_base_fee|intcomma }}
    {% csrf_token %} @@ -120,7 +120,7 @@

    Channel Settings

    - +
    {% csrf_token %} @@ -128,7 +128,7 @@

    Channel Settings

    - +
    {% csrf_token %} @@ -136,7 +136,7 @@

    Channel Settings

    - +
    {% csrf_token %} @@ -145,8 +145,8 @@

    Channel Settings

    - = 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 %} + = 1 and channel.fee_check < 100 and channel.auto_rebalance == True %}style="background-color: rgba(56,139,253,0.15)">True ({{ channel.steps }}){% else %}style="background-color: rgba(248,81,73,0.15);">False{% endif %} + True{% else %}style="background-color: rgba(248,81,73,0.15);">False{% endif %}
@@ -216,8 +216,8 @@

30 Days Our Fees Logs

{% if log.peer_alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ log.peer_alias }}{% endif %} {{ log.setting }} {{ log.old_value }} - log.old_value %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{{ log.new_value }} - log.old_value %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{{ log.change }}% + log.old_value %}style="background-color: rgba(46,160,67,0.15)"{% else %}style="background-color: rgba(248,81,73,0.15);"{% endif %}>{{ log.new_value }} + log.old_value %}style="background-color: rgba(46,160,67,0.15)"{% else %}style="background-color: rgba(248,81,73,0.15);"{% endif %}>{{ log.change }}% {% endfor %} @@ -284,7 +284,7 @@

Last 5 Rebalance Requests

{{ rebalance.ppm|intcomma }} {% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %} {% if rebalance.target_alias == '' %}---{% 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 == 406 %}No Sources{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% elif rebalance.status == 499 %}Cancelled{% else %}{{ rebalance.status }}{% endif %} {% if rebalance.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %} {% endfor %} @@ -381,7 +381,7 @@

Last 5 Failed HTLCs

{{ 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.wire_failure == 11 %}Amount Below Minimum{% elif failed_htlc.wire_failure == 12 %}Fee Insufficient{% elif failed_htlc.wire_failure == 15 %}Temporary Channel Failure{% elif failed_htlc.wire_failure == 18 %}Unknown Next Peer{% elif failed_htlc.wire_failure == 22 %}Expiry Too Far{% 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 == 13 %}Invoice Not Open{% elif failed_htlc.failure_detail == 20 %}Invalid Keysend{% elif failed_htlc.failure_detail == 22 %}Circular Route{% else %}{{ failed_htlc.failure_detail }}{% endif %} {% endfor %} @@ -411,8 +411,8 @@

Last 10 Peer Log Events

{% if log.peer_alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ log.peer_alias }}{% endif %} {{ log.event }} {% if log.old_value == "" %}---{% elif log.event == "Connection" %}{% if log.old_value == 1 %}Online{% else %}Offline{% endif %}{% elif log.event == "Disabled" %}{% if log.old_value == 1 %}True{% else %}False{% endif %}{% else %}{{ log.old_value|intcomma }}{% endif %} - log.old_value and log.event != "Disabled" %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{% if log.event == "Connection" %}{% if log.new_value == 1 %}Online{% else %}Offline{% endif %}{% elif log.event == "Disabled" %}{% if log.new_value == 1 %}True{% else %}False{% endif %}{% else %}{{ log.new_value|intcomma }}{% endif %} - ---{% elif log.new_value > log.old_value %}style="background-color: #d5fadb">{{ log.change }}%{% else %}style="background-color: #fadbd5">{{ log.change|intcomma }}%{% endif %} + log.old_value and log.event != "Disabled" %}style="background-color: rgba(46,160,67,0.15)"{% else %}style="background-color: rgba(248,81,73,0.15);"{% endif %}>{% if log.event == "Connection" %}{% if log.new_value == 1 %}Online{% else %}Offline{% endif %}{% elif log.event == "Disabled" %}{% if log.new_value == 1 %}True{% else %}False{% endif %}{% else %}{{ log.new_value|intcomma }}{% endif %} + ---{% elif log.new_value > log.old_value %}style="background-color: rgba(46,160,67,0.15)">{{ log.change }}%{% else %}style="background-color: rgba(248,81,73,0.15);">{{ log.change|intcomma }}%{% endif %} {{ log.out_percent }}% | {{ log.in_percent }}% {% endfor %} diff --git a/gui/templates/channels.html b/gui/templates/channels.html index 99587dae..bddc4a70 100644 --- a/gui/templates/channels.html +++ b/gui/templates/channels.html @@ -16,19 +16,19 @@

Channel Performance

Channel Health - Channel ID - Peer Alias - Capacity - Outbound Flow - APY | CV - Out [Profit] | In - Inbound Flow - Outbound Flow - APY | CV - Out [Profit] | In - Inbound Flow - Updates - Opener + Channel ID + Peer Alias + Capacity + Outbound Flow + APY | CV + Out [Profit] | In + Inbound Flow + Outbound Flow + APY | CV + Out [Profit] | In + Inbound Flow + Updates + Opener {% for channel in channels %} diff --git a/gui/templates/failed_htlcs.html b/gui/templates/failed_htlcs.html index cb55420a..5a29615c 100644 --- a/gui/templates/failed_htlcs.html +++ b/gui/templates/failed_htlcs.html @@ -28,7 +28,7 @@

Last 150 Failed HTLCs

{{ 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{% elif failed_htlc.wire_failure == 22 %}Expiry Too Far{% else %}{{ failed_htlc.wire_failure }}{% endif %} + {% if failed_htlc.wire_failure == 11 %}Amount Below Minimum{% elif failed_htlc.wire_failure == 12 %}Fee Insufficient{% elif failed_htlc.wire_failure == 15 %}Temporary Channel Failure{% elif failed_htlc.wire_failure == 18 %}Unknown Next Peer{% elif failed_htlc.wire_failure == 22 %}Expiry Too Far{% 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 == 13 %}Invoice Not Open{% elif failed_htlc.failure_detail == 20 %}Invalid Keysend{% elif failed_htlc.failure_detail == 22 %}Circular Route{% else %}{{ failed_htlc.failure_detail }}{% endif %} {% endfor %} @@ -40,4 +40,34 @@

Last 150 Failed HTLCs

You dont have any failed HTLCs yet.

{% endif %} +{% if agg_failed_htlcs %} +
+

Top 21 Failed Downstream Routes

+ + + + + + + + + + {% for failed_htlc in agg_failed_htlcs %} + + + + + + + + + {% endfor %} +
Chan In IDChan Out IDChan In AliasChan Out AliasCountVolume
{{ failed_htlc.chan_id_in }}{{ failed_htlc.chan_id_out }}{% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %}{% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %}{{ failed_htlc.count|intcomma }}{{ failed_htlc.volume|intcomma }}
+
+{% endif %} +{% if not agg_failed_htlcs %} +
+

You dont have any failed downstream routes in the last 7 days.

+
+{% endif %} {% endblock %} diff --git a/gui/templates/fee_rates.html b/gui/templates/fee_rates.html index 431926d8..2f5b2e6e 100644 --- a/gui/templates/fee_rates.html +++ b/gui/templates/fee_rates.html @@ -2,58 +2,28 @@ {% block title %} {{ block.super }} - Fee Rates{% endblock %} {% block content %} {% load humanize %} -{% if local_settings %} -
-

Auto-Fees Settings

- - - - - - {% for settings in local_settings %} - - - - - {% endfor %} -
KeyValue
{{ settings.key }} -
- {% csrf_token %} - {% if settings.key == 'AF-MaxRate' or settings.key == 'AF-MinRate' %} - - {% elif settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AF-UpdateHours' %} - - {% else %} - - {% endif %} - -
-
-
-{% endif %} {% if channels %}

Suggested Fee Rates

- - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + - - + @@ -87,11 +61,11 @@

Suggested Fee Rates

- + - - +
Channel IDPeer AliasCapacityOutbound LiquidityInbound Liquidity7Day FlowOut RateRebal RateAssisted RatioAdjustmentSuggested RateChannel IDPeer AliasOutbound LiquidityCapacityInbound Liquidity7Day FlowOut RateRebal RateAssisted RatioAdjustmentSuggested Rate oRateoBaseMax CostiRateiBaseoBaseMax CostiRateiBase Updated
@@ -68,9 +38,13 @@

Suggested Fee Rates

{{ channel.short_chan_id }} {% if channel.private == False %}{% endif %}{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}{% if channel.private == False %}{% endif %}{{ channel.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.net_routed_7day != 0 %}{{ channel.net_routed_7day }} | {% endif %}{% if channel.net_routed_7day > 0 %}OUT{% elif channel.net_routed_7day < 0 %}IN{% else %}---{% endif %} {{ channel.out_rate }} {{ channel.local_base_fee|intcomma }}{{ channel.ar_max_cost }}%{{ channel.ar_max_cost }}% {{ channel.remote_fee_rate|intcomma }} {{ channel.remote_base_fee|intcomma }}{{ channel.fees_updated|naturaltime }} + {{ channel.fees_updated|naturaltime }}
{% csrf_token %} @@ -105,10 +79,12 @@

Suggested Fee Rates

-{% endif %} -{% if not channels %} +{% else %}

You dont have any channels to analyze yet!

{% endif %} +{% if local_settings %} +{% include 'local_settings.html' with settings=local_settings title='Auto-Fees' postURL='update_setting' %} +{% endif %} {% endblock %} \ No newline at end of file diff --git a/gui/templates/home.html b/gui/templates/home.html index 4f232786..793e7051 100644 --- a/gui/templates/home.html +++ b/gui/templates/home.html @@ -3,35 +3,157 @@ {% block meta %}{% endblock %} {% block content %} {% load humanize %} +Dashboard Last Updated: ... refreshing every 21 minutes
-

Dashboard Last Updated : ... refreshing every 21 minutes

-

{{ node_info.alias }} | {{ node_info.identity_pubkey }}

-

Public Capacity: {{ total_capacity|intcomma }} | Active Channels: {{ active_count }} / {{ total_channels }} | Peers: {{ node_info.num_peers }} | DB Size: {% if db_size > 0 %}{{ db_size }} GB{% else %}---{% endif %} | Total States Updates: {% if num_updates > 0 %}{{ num_updates|intcomma }} {% else %}---{% endif %}

- {% if total_private > 0 %}

Private Capacity: {{ private_capacity|intcomma }} | Locked Liquidity: {{ private_outbound|intcomma }} | Active Private Channels: {{ active_private }} / {{ total_private }}

{% endif %} -

Public Address: {% for info in node_info.uris %}{{ info }} | {% endfor %}

-

Lnd sync: {{ node_info.synced_to_graph }} | chain sync: {{ node_info.synced_to_chain }} | {% for info in node_info.chains %}{{ info }}{% endfor %} | {{ node_info.block_height }} | {{ node_info.block_hash }}

-
-
-

Total Balance: {{ total_balance|intcomma }} | Onchain Balance: {{ balances.total_balance|intcomma }} | Confirmed Balance: {{ balances.confirmed_balance|intcomma }} | Unconfirmed Balance: {{ balances.unconfirmed_balance|intcomma }} | Details

- - {% csrf_token %} - - +
+ LND + {% for info in node_info.chains %}{{ info }}{% endfor %} | {{ node_info.block_height }} #{{ node_info.block_hash }} +
+
+ Public Capacity: {{ total_capacity|intcomma }} + Active Channels: {{ active_count }} / {{ total_channels }} + Peers: {{ node_info.num_peers }} + DB Size: {% if db_size > 0 %}{{ db_size }} GB{% else %}---{% endif %} + Total States Updates: {% if num_updates > 0 %}{{ num_updates|intcomma }} {% else %}---{% endif %} + Sign a message +
+ {% if total_private > 0 %} +
+ Private Capacity: {{ private_capacity|intcomma }} + Locked Liquidity: {{ private_outbound|intcomma }} + Active Private Channels: {{ active_private }} / {{ total_private }} +
+ {% endif %} + Addresses: + {% for addr in node_info.uris %} +
+ {{ addr }} + + + + + + + + + + +
+ {% endfor %}
-
-

1-Day Routed: {{ routed_1day|intcomma }} | Value: {{ routed_1day_amt|intcomma }} | Fees Earned: {{ earned_1day|intcomma }} [{{ 1day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_1day|intcomma }} | Offchain Fees: {{ total_1day_fees|intcomma }} [{{ 1day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_1day }}% | Profit/Outbound: {{ profit_per_outbound_1d|intcomma }} [{{ profit_per_outbound_real_1d|intcomma }}] | Outbound Utilization: {{ routed_1day_percent }}%

-

7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomma }} | Fees Earned: {{ earned_7day|intcomma }} [{{ 7day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_7day|intcomma }} | Offchain Fees: {{ total_7day_fees|intcomma }} [{{ 7day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_7day }}% | Profit/Outbound: {{ profit_per_outbound_7d|intcomma }} [{{ profit_per_outbound_real_7d|intcomma }}] | Outbound Utilization: {{ routed_7day_percent }}%

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Ratio: {{ liq_ratio }}% + InboundOutboundUnsettled
Active{{ inbound|intcomma }}{{ outbound|intcomma }}{{ unsettled|intcomma }}
Inactive{{ inactive_inbound|intcomma }}{{ inactive_outbound|intcomma }}{{ inactive_unsettled|intcomma }}
Total{{ sum_inbound|intcomma }}{{ sum_outbound|intcomma }}{{ total_unsettled|intcomma }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Balance: {{ total_balance|intcomma }}Onchain: {{ balances.total_balance|intcomma }}Confirmed: {{ balances.confirmed_balance|intcomma }}Unconfirmed: {{ balances.unconfirmed_balance|intcomma }}Limbo: {{ limbo_balance|intcomma }} Pending HTLCs: {{ pending_htlc_count }} +
+ {% csrf_token %} + New Onchain Address +
+
PeriodRoutedFees earned | paidOnchain feesCost (%)Profit/OutboundOutbound Utilization
1-Day{{ routed_1day|intcomma }} ({{ routed_1day_amt|intcomma }} sats){{ earned_1day|intcomma }} [{{ 1day_routed_ppm|intcomma }}] | {{ total_1day_fees|intcomma }} [{{ 1day_payments_ppm|intcomma }}]{{ onchain_costs_1day|intcomma }}{{ percent_cost_1day }}%{{ profit_per_outbound_1d|intcomma }} [{{ profit_per_outbound_real_1d|intcomma }}]{{ routed_1day_percent }}%
7-Day{{ routed_7day|intcomma }} ({{ routed_7day_amt|intcomma }} sats){{ earned_7day|intcomma }} [{{ 7day_routed_ppm|intcomma }}] | {{ total_7day_fees|intcomma }} [{{ 7day_payments_ppm|intcomma }}]{{ onchain_costs_7day|intcomma }}{{ percent_cost_7day }}%{{ profit_per_outbound_7d|intcomma }} [{{ profit_per_outbound_real_7d|intcomma }}]{{ routed_7day_percent }}%
-
-

Total Inbound Liquidity: {{ sum_inbound|intcomma }} | Outbound Liquidity: {{ sum_outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%

-

Active Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }}

-

Inactive Inbound Liquidity: {{ inactive_inbound|intcomma }} | Outbound Liquidity: {{ inactive_outbound|intcomma }} | Unsettled Liquidity: {{ inactive_unsettled|intcomma }}

-

Balance In Limbo: {{ limbo_balance|intcomma }} | Total Unsettled Liquidity: {{ total_unsettled|intcomma }} | Pending HTLCs: {{ pending_htlc_count }}

-

P&L | Closures | New Peers | Peer Events | AR Actions | Fee Rates | Autopilot | Autofees | Channel Performance | Keysends | Rebalancing | Towers | Batching | Advanced Settings

+
+ {% if active_channels %}
@@ -39,21 +161,20 @@

Active Channels

- - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -61,24 +182,28 @@

Active Channels

- - + - - + + - - + + {% endfor %} @@ -106,17 +225,16 @@

Active Channels

Inactive Channels

Channel IDPeer AliasCapacityOutbound LiquidityInbound LiquidityUnsettledoRateoBaseo1Di1Do7Di7DiRateiBaseChannel IDPeer AliasOutbound LiquidityCapacityInbound LiquidityUnsettledoRateoBaseo1Di1Do7Di7DiRateiBase iTarget% AR
{{ channel.short_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 %}
+ +
+ +
+
{{ channel.remote_balance|intcomma }} ({{ channel.inbound_percent }}%) {{ channel.unsettled_balance|intcomma }} ({{ channel.htlc_count }}){{ channel.local_fee_rate|intcomma }}{{ channel.local_base_fee|intcomma }}{{ channel.local_fee_rate|intcomma }}{{ channel.local_base_fee|intcomma }} {{ channel.amt_routed_out_1day|intcomma }} M ({{ channel.routed_out_1day }}) {{ channel.amt_routed_in_1day|intcomma }} M ({{ channel.routed_in_1day }}) {{ channel.amt_routed_out_7day|intcomma }} M ({{ channel.routed_out_7day }}) {{ channel.amt_routed_in_7day|intcomma }} M ({{ channel.routed_in_7day }}){{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }}{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }} {% if channel.auto_rebalance == True %}
{% csrf_token %} - +
@@ -87,13 +212,7 @@

Active Channels

{% endif %}
-
- {% csrf_token %} - - - - -
+
- - - - - - - - - - - + + + + + + + + + + @@ -128,15 +246,19 @@

Inactive Channels

- - + - - - - + + + + @@ -174,9 +296,8 @@

Private Channels

- - + @@ -192,19 +313,23 @@

Private Channels

- - + - - + + - + {% endfor %}
Channel IDPeer AliasCapacityOutbound LiquidityInbound LiquidityUnsettledoRateoBaseiRateiBaseChannel IDPeer AliasOutbound LiquidityCapacityInbound LiquidityUnsettledoRateoBaseiRateiBase Local Commit Local Reserve Downtime
{{ channel.short_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 %}
+ +
+ +
+
{{ channel.remote_balance|intcomma }} {{ channel.unsettled_balance|intcomma }}{{ channel.local_fee_rate|intcomma }}{{ channel.local_base_fee|intcomma }}{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }}{{ channel.local_fee_rate|intcomma }}{{ channel.local_base_fee|intcomma }}{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }} {{ channel.local_commit|intcomma }} {{ channel.local_chan_reserve|intcomma }} {{ channel.last_update|naturaltime|slice:":-4" }}
Channel ID Peer AliasCapacity Outbound LiquidityCapacity Inbound Liquidity Unsettled oRate
{{ channel.short_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 %}
+ +
+ +
+
{{ channel.remote_balance|intcomma }} {{ channel.unsettled_balance|intcomma }}{{ channel.local_fee_rate|intcomma }}{{ channel.local_base_fee|intcomma }}{{ channel.local_fee_rate|intcomma }}{{ channel.local_base_fee|intcomma }} {{ channel.total_sent|intcomma }} {{ channel.total_received|intcomma }} {{ channel.local_commit|intcomma }} {{ channel.local_chan_reserve|intcomma }} {% if channel.initiator == True %}Local{% else %}Remote{% endif %}{{ channel.last_update|naturaltime|slice:":-4" }}{{ channel.last_update|naturaltime|slice:":-4" }}
@@ -237,7 +362,7 @@

Pending Open Channels

{{ channel.capacity|intcomma }} {{ channel.local_balance|intcomma }} {{ channel.remote_balance|intcomma }} - +
{% csrf_token %} @@ -292,7 +417,7 @@

Pending Open Channels

- +
{% csrf_token %} @@ -301,7 +426,7 @@

Pending Open Channels

- +
{% csrf_token %} @@ -310,7 +435,7 @@

Pending Open Channels

- +
{% csrf_token %} @@ -443,43 +568,13 @@

Last 10 Payments Routed

{% endif %} -{% if rebalances %} -
-

Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing via {{ available_count }} outbound channels)

- - - - - - - - - - - - - - - - {% for rebalance in rebalances %} - - - - - - - - - - - - - - - {% endfor %} -
RequestedStartStopScheduled DurationActual DurationValueFee LimitTarget PPMFees PaidLast Hop AliasStatusHash
{{ rebalance.requested|naturaltime }}---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}{{ rebalance.duration }} minutes{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}{{ rebalance.value|intcomma }}{{ rebalance.fee_limit|intcomma }}{{ rebalance.ppm|intcomma }}{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}{% if rebalance.target_alias == '' %}---{% 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.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %}
-
-{% endif %} +{% with elegible_str=eligible_count|intcomma enabled_str=enabled_count|intcomma available_str=available_count|intcomma %} + {% with title='Rebalance Requests (currently scheduling '|add:elegible_str|add:" of "|add:enabled_str|add:" enabled channels for rebalancing via "|add:available_str|add:" outbound channels)" %} + {% autoescape off %} + {% include 'rebalances_table.html' with count=10 title=title %} + {% endautoescape %} + {% endwith %} +{% endwith %} {% if payments %}

Last 5 Payments Sent

@@ -570,111 +665,77 @@

Last 10 Failed HTLCs

{{ 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.wire_failure == 11 %}Amount Below Minimum{% elif failed_htlc.wire_failure == 12 %}Fee Insufficient{% elif failed_htlc.wire_failure == 15 %}Temporary Channel Failure{% elif failed_htlc.wire_failure == 18 %}Unknown Next Peer{% elif failed_htlc.wire_failure == 22 %}Expiry Too Far{% 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 == 13 %}Invoice Not Open{% elif failed_htlc.failure_detail == 20 %}Invalid Keysend{% elif failed_htlc.failure_detail == 22 %}Circular Route{% else %}{{ failed_htlc.failure_detail }}{% endif %} {% endfor %}
{% endif %} -{% if local_settings %} +{% include 'local_settings.html' with settings=local_settings title="Auto-Rebalancer" url='rebalancing' %}
-

Auto-Rebalancer Settings

- - - - - - {% for settings in local_settings %} - - - - - {% endfor %} -
KeyValue
{{ settings.key }}{{ settings.value|intcomma }}
-
-{% endif %} -
-

Update Auto Rebalancer Settings

-
- +

Connect to a Peer

+
+ {% csrf_token %} - - - - - - - - - - - - - - - - - - - - - - - - +
-

Connect To A Peer

-
-
+

Open a Channel

+
+ {% csrf_token %} - - + + + + + + + + +
-

Open A Channel

-
-
+

Close a Channel

+
+ {% csrf_token %} - - - - - - - + + + +
+ +
+ + +
-

Close A Channel

-
-
+

Create Invoice

+
+ {% csrf_token %} - - - - - - + + +
-

Create Invoice

-
-
+

Sign a Message

+
+ {% csrf_token %} - - +
diff --git a/gui/templates/keysends.html b/gui/templates/keysends.html index ffd7ef10..89171c04 100644 --- a/gui/templates/keysends.html +++ b/gui/templates/keysends.html @@ -15,7 +15,7 @@

Received Keysends

{% for keysend in keysends %} - +
{% csrf_token %} diff --git a/gui/templates/local_settings.html b/gui/templates/local_settings.html new file mode 100644 index 00000000..f7c28ad8 --- /dev/null +++ b/gui/templates/local_settings.html @@ -0,0 +1,32 @@ +
+ + {% csrf_token %} +

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

+
+
+ {% for sett in settings|slice:"1:" %} +
+ + {% if sett.min == 0 and sett.max == 1 %} + + {% else %} +
+ +
+ {% endif %} +
+ {% endfor %} +
+ {% if 'update_channels' in settings.0.form_id %} + + + {% endif %} + +
+
+
+ +
\ No newline at end of file diff --git a/gui/templates/peerevents.html b/gui/templates/peerevents.html index 00ed5a91..0cfcfe52 100644 --- a/gui/templates/peerevents.html +++ b/gui/templates/peerevents.html @@ -8,13 +8,13 @@

Last 150 Peer Events

- - - - - - - + + + + + + + {% for log in peer_events %} @@ -23,8 +23,8 @@

Last 150 Peer Events

- - + + {% endfor %} diff --git a/gui/templates/peers.html b/gui/templates/peers.html index 836b4c9b..2d5b278d 100644 --- a/gui/templates/peers.html +++ b/gui/templates/peers.html @@ -7,12 +7,12 @@

Peers List ({{ num_peers }})

TimestampChannel IDPeer AliasSettingOld ValueNew ValueChangeChannel LiquidityChannel IDPeer AliasSettingOld ValueNew ValueChangeChannel Liquidity
{% if log.peer_alias == '' %}{{ log.remote_pubkey|slice:":12" }}{% else %}{{ log.peer_alias }}{% endif %} {{ log.event }} {% if log.old_value == "" %}---{% elif log.event == "Connection" %}{% if log.old_value == 1 %}Online{% else %}Offline{% endif %}{% elif log.event == "Disabled" %}{% if log.old_value == 1 %}True{% else %}False{% endif %}{% else %}{{ log.old_value|intcomma }}{% endif %} log.old_value and log.event != "Disabled" %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{% if log.event == "Connection" %}{% if log.new_value == 1 %}Online{% else %}Offline{% endif %}{% elif log.event == "Disabled" %}{% if log.new_value == 1 %}True{% else %}False{% endif %}{% else %}{{ log.new_value|intcomma }}{% endif %}---{% elif log.new_value > log.old_value %}style="background-color: #d5fadb">{{ log.change }}%{% else %}style="background-color: #fadbd5">{{ log.change|intcomma }}%{% endif %} log.old_value and log.event != "Disabled" %}style="background-color: rgba(46,160,67,0.15)"{% else %}style="background-color: rgba(248,81,73,0.15);"{% endif %}>{% if log.event == "Connection" %}{% if log.new_value == 1 %}Online{% else %}Offline{% endif %}{% elif log.event == "Disabled" %}{% if log.new_value == 1 %}True{% else %}False{% endif %}{% else %}{{ log.new_value|intcomma }}{% endif %}---{% elif log.new_value > log.old_value %}style="background-color: rgba(46,160,67,0.15)">{{ log.change }}%{% else %}style="background-color: rgba(248,81,73,0.15);">{{ log.change|intcomma }}%{% endif %} {{ log.out_percent }}% | {{ log.in_percent }}%
- - - - - - + + + + + + {% for peer in peers %} diff --git a/gui/templates/rebalances.html b/gui/templates/rebalances.html index b2f3dda2..baef364a 100644 --- a/gui/templates/rebalances.html +++ b/gui/templates/rebalances.html @@ -1,84 +1,6 @@ {% extends "base.html" %} {% block title %} {{ block.super }} - Rebalances{% endblock %} {% block content %} -{% load humanize %} -{% if rebalances_success %} -
-

Last Successful Rebalances

-
Peer PubKeyPeer AliasNetwork AddressInboundSats SentSats ReceivedPeer PubKeyPeer AliasNetwork AddressInboundSats SentSats Received
- - - - - - - - - - - - - - - {% for rebalance in rebalances_success %} - - - - - - - - - - - - - - - {% endfor %} -
RequestedStartStopScheduled DurationActual DurationValueFee LimitTarget PPMFees PaidLast Hop AliasStatusHash
{{ rebalance.requested|naturaltime }}---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}{{ rebalance.duration }} minutes{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}{{ rebalance.value|intcomma }}{{ rebalance.fee_limit|intcomma }}{{ rebalance.ppm|intcomma }}{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}{% if rebalance.target_alias == '' %}---{% 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.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %}
-
-{% endif %} -{% if rebalances %} -
-

Last 150 Rebalances

- - - - - - - - - - - - - - - - {% for rebalance in rebalances %} - - - - - - - - - - - - - - - {% endfor %} -
RequestedStartStopScheduled DurationActual DurationValueFee LimitTarget PPMFees PaidLast Hop AliasStatusHash
{{ rebalance.requested|naturaltime }}---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}{{ rebalance.duration }} minutes{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}{{ rebalance.value|intcomma }}{{ rebalance.fee_limit|intcomma }}{{ rebalance.ppm|intcomma }}{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}{% if rebalance.target_alias == '' %}---{% 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.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %}
-
-{% endif %} -{% if not rebalances %} -
-

You dont have any rebalances yet.

-
-{% endif %} +{% include 'rebalances_table.html' with count=69 status=2 title='Rebalances' %} +{% include 'rebalances_table.html' with count=150 title='Rebalances' %} {% endblock %} diff --git a/gui/templates/rebalances_table.html b/gui/templates/rebalances_table.html new file mode 100644 index 00000000..67574f83 --- /dev/null +++ b/gui/templates/rebalances_table.html @@ -0,0 +1,91 @@ +{% load humanize %} +
+

Last {{count}} {{title}}

+ + + + + + + + + + + + + + + + + + +
RequestedStartStopScheduled forElapsedValueFee LimitTarget PPMFees PaidLast HopStatusHashAction
+
+ diff --git a/gui/templates/rebalancing.html b/gui/templates/rebalancing.html index 2f7944bd..4b6cdd57 100644 --- a/gui/templates/rebalancing.html +++ b/gui/templates/rebalancing.html @@ -2,211 +2,177 @@ {% 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 via {{ available_count }} outbound channels)

-
- - - - - - - - - - - - - - - - - - - - - {% for channel in channels %} - - - - - - - - - - - - - - - - - - - - {% endfor %} +
+
+
Channel IDPeer AliasCapacityOutbound LiquidityInbound LiquidityRebal Out?Enabled?Fee RatioRebal In?Target AmtMax Cost %oTarget%iTarget%AR7-Day RateActive
{{ channel.short_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 %} -
- {% csrf_token %} - - - -
-
-
- {% csrf_token %} - - - -
-
-
- {% csrf_token %} - - - -
-
-
- {% csrf_token %} - - - -
-
-
- {% csrf_token %} - - - - -
-
{% if channel.attempts == 0 %}---{% else %}{{ channel.success_rate }}% ({{ channel.success }}/{{ channel.attempts }}){% endif %}True{% else %}style="background-color: #fadbd5">False{% endif %}
+ + + + + + + + + + + + + + + + + + + + + + +
PKChannel IDPeer AliasOutbound LiquidityCapacityInbound LiquidityRebal Out?Fee RatioRebal In?Target AmtMax Cost %oTarget%iTarget%AR7-Day RateActive
-
-{% endif %} -{% if not channels %} -
-

You dont have any channels to rebalance yet.

-
-{% endif %} -{% if rebalancer %} -
-

Last 20 Rebalance Requests

- - - - - - - - - - - - - - - - {% for rebalance in rebalancer %} - - - - - - - - - - - - - - - {% endfor %} -
RequestedStartStopScheduled DurationActual DurationValueFee LimitTarget PPMFees PaidLast Hop AliasStatusHash
{{ rebalance.requested|naturaltime }}---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}{{ rebalance.duration }} minutes{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}{{ rebalance.value|intcomma }}{{ rebalance.fee_limit|intcomma }}{{ rebalance.ppm|intcomma }}{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}{% if rebalance.target_alias == '' %}---{% 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.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %}
-
-{% endif %} -{% if not rebalancer %} -
-

You dont have any rebalancer requests yet.

-
-{% endif %} -{% if local_settings %} -
-

Auto-Rebalancer Settings

- - - - - - {% for settings in local_settings %} - - - - - {% endfor %} -
KeyValue
{{ settings.key }}{{ settings.value|intcomma }}
-
-{% endif %} -
-

Update Auto Rebalancer Settings

-
-
- {% csrf_token %} - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-{% if channels %} -
-

Manual Rebalancer Request

-
-
+
+
Manual Rebalance Request:
+ {% csrf_token %} - - - - - - - - + + + + + + + + + + -
    -
  • - {% for channel in rebalancer_form.outgoing_chan_ids %} -
  • {{ channel }}
  • - {% endfor %} -
-{% endif %} +{% include 'rebalances_table.html' with count=20 title='Rebalance Requests' %} +{% include 'local_settings.html' with settings=local_settings title='Auto-Rebalancer' %} + {% endblock %} diff --git a/gui/templates/route.html b/gui/templates/route.html index 5094f1f5..83d86cd8 100644 --- a/gui/templates/route.html +++ b/gui/templates/route.html @@ -40,41 +40,7 @@

Route For : {{ payment_hash }}{% if total_cost %} | Total Costs: {{ total_co

A route was not found for this payment hash!

{% endif %} -{% if rebalances %} -
-

Associated Rebalances

- - - - - - - - - - - - - - - {% for rebalance in rebalances %} - - - - - - - - - - - - - - {% endfor %} -
RequestedStartStopScheduled DurationActual DurationValueFee LimitTarget PPMFees PaidLast Hop AliasStatus
{{ rebalance.requested|naturaltime }}---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}{{ rebalance.duration }} minutes{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}{{ rebalance.value|intcomma }}{{ rebalance.fee_limit|intcomma }}{{ rebalance.ppm|intcomma }}{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}
-
-{% endif %} +{% include 'rebalances_table.html' with count=150 title='Rebalances' payment_hash=payment_hash %} {% if invoices %}

Linked Invoice

diff --git a/gui/urls.py b/gui/urls.py index 08c0b2a8..a7acaf17 100644 --- a/gui/urls.py +++ b/gui/urls.py @@ -47,7 +47,7 @@ path('newaddress/', views.new_address_form, name='new-address-form'), path('createinvoice/', views.add_invoice_form, name='add-invoice-form'), path('rebalancer/', views.rebalance, name='rebalancer'), - path('autorebalance/', views.auto_rebalance, name='auto-rebalance'), + path('update_settings/', views.update_settings, name='update-settings'), path('update_channel/', views.update_channel, name='update-channel'), path('update_pending/', views.update_pending, name='update-pending'), path('update_setting/', views.update_setting, name='update-setting'), @@ -65,9 +65,11 @@ path('autofees/', views.autofees, name='autofees'), path('peerevents/', views.peerevents, name='peerevents'), path('advanced/', views.advanced, name='advanced'), + path('sign_message/', views.sign_message, name='sign-message'), path('api/', include(router.urls), name='api-root'), path('api-auth/', include('rest_framework.urls'), name='api-auth'), path('api/connectpeer/', views.connect_peer, name='connect-peer'), + path('api/rebalance_stats/', views.rebalance_stats, name='rebalance-stats'), path('api/openchannel/', views.open_channel, name='open-channel'), path('api/closechannel/', views.close_channel, name='close-channel'), path('api/createinvoice/', views.add_invoice, name='add-invoice'), @@ -77,5 +79,6 @@ path('api/balances/', views.api_balances, name='api-balances'), path('api/income/', views.api_income, name='api-income'), path('api/pendingchannels/', views.pending_channels, name='pending-channels'), + path('api/bumpfee/', views.bump_fee, name='bump-fee'), path('lndg-admin/', admin.site.urls), ] diff --git a/gui/views.py b/gui/views.py index 7922a5ae..40726be3 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, Q +from django.db.models import Sum, IntegerField, Count, Max, F, Q, Case, When from django.db.models.functions import Round from django.contrib.auth.decorators import login_required from datetime import datetime, timedelta @@ -8,15 +8,17 @@ from rest_framework.response import Response from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated -from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, UpdateChannel, UpdateSetting, AutoRebalanceForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending, UpdateClosing, UpdateKeysend, AddAvoid, RemoveAvoid +from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, UpdateChannel, UpdateSetting, LocalSettingsForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending, UpdateClosing, UpdateKeysend, AddAvoid, RemoveAvoid from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees, PendingChannels, AvoidNodes, PeerEvents -from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer +from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer, BumpFeeSerializer 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 wtclient_pb2 as wtrpc from gui.lnd_deps import wtclient_pb2_grpc as wtstub +from gui.lnd_deps import walletkit_pb2 as walletrpc +from gui.lnd_deps import walletkit_pb2_grpc as walletstub from gui.lnd_deps.lnd_connect import lnd_connect from lndg import settings from os import path @@ -27,8 +29,8 @@ def graph_links(): if LocalSettings.objects.filter(key='GUI-GraphLinks').exists(): graph_links = str(LocalSettings.objects.filter(key='GUI-GraphLinks')[0].value) else: - LocalSettings(key='GUI-GraphLinks', value='https://1ml.com').save() - graph_links = 'https://1ml.com' + LocalSettings(key='GUI-GraphLinks', value='https://amboss.space').save() + graph_links = 'https://amboss.space' return graph_links def network_links(): @@ -154,9 +156,9 @@ def home(request): #Get recorded invoice details invoices = Invoices.objects.exclude(state=2) #Get recorded forwarding events - forwards = Forwards.objects.all().annotate(amt_in=Sum('amt_in_msat')/1000).annotate(amt_out=Sum('amt_out_msat')/1000).annotate(ppm=Round((Sum('fee')*1000000000)/Sum('amt_out_msat'), output_field=IntegerField())).order_by('-id') + forwards = Forwards.objects.all().annotate(amt_in=Sum('amt_in_msat')/1000, amt_out=Sum('amt_out_msat')/1000, ppm=Round((Sum('fee')*1000000000)/Sum('amt_out_msat'), output_field=IntegerField())).order_by('-id') #Get current active channels - active_channels = channels.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by('outbound_percent') + active_channels = channels.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity'), inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by('outbound_percent') active_capacity = 0 if active_channels.count() == 0 else active_channels.aggregate(Sum('capacity'))['capacity__sum'] active_inbound = 0 if active_capacity == 0 else active_channels.aggregate(Sum('remote_balance'))['remote_balance__sum'] active_outbound = 0 if active_capacity == 0 else active_channels.aggregate(Sum('local_balance'))['local_balance__sum'] @@ -234,9 +236,9 @@ def home(request): available_count += 1 detailed_active_channels.append(detailed_channel) #Get current inactive channels - inactive_channels = channels.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') + inactive_channels = channels.filter(is_active=False, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity'), inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).order_by('outbound_percent') inactive_capacity = 0 if inactive_channels.count() == 0 else inactive_channels.aggregate(Sum('capacity'))['capacity__sum'] - private_channels = channels.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') + private_channels = channels.filter(is_open=True, private=True).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity'), 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'] inactive_inbound = 0 if inactive_channels.count() == 0 else inactive_channels.aggregate(Sum('remote_balance'))['remote_balance__sum'] private_count = private_channels.count() @@ -257,10 +259,9 @@ def home(request): total_costs_7day = total_7day_fees + onchain_costs_7day total_costs_1day = total_1day_fees + onchain_costs_1day #Get list of recent rebalance requests - rebalances = Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id') active_count = node_info.num_active_channels - active_private total_channels = node_info.num_active_channels + node_info.num_inactive_channels - private_count - local_settings = LocalSettings.objects.filter(key__contains='AR-').order_by('key') + local_settings = get_local_settings('AR-') try: db_size = round(path.getsize(path.expanduser(settings.LND_DATABASE_PATH))*0.000000001, 3) except: @@ -315,10 +316,9 @@ def home(request): 'pending_closed': pending_closed, 'pending_force_closed': pending_force_closed, 'waiting_for_close': waiting_for_close, - 'rebalances': rebalances[:12], 'local_settings': local_settings, 'pending_htlc_count': pending_htlc_count, - 'failed_htlcs': FailedHTLCs.objects.all().order_by('-id')[:10], + 'failed_htlcs': FailedHTLCs.objects.exclude(wire_failure=99).order_by('-id')[:10], '1day_routed_ppm': 0 if routed_1day_amt == 0 else int((total_earned_1day/routed_1day_amt)*1000000), '7day_routed_ppm': 0 if routed_7day_amt == 0 else int((total_earned_7day/routed_7day_amt)*1000000), '1day_payments_ppm': 0 if payments_1day_amt == 0 else int((total_1day_fees/payments_1day_amt)*1000000), @@ -476,7 +476,7 @@ def fees(request): else: LocalSettings(key='AF-UpdateHours', value='24').save() update_hours = 24 - failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(timestamp__gte=filter_1day).order_by('-id').values()) + failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.exclude(wire_failure=99).filter(timestamp__gte=filter_1day).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) @@ -527,7 +527,7 @@ def fees(request): channels_df['eligible'] = channels_df.apply(lambda row: (datetime.now()-row['fees_updated']).total_seconds() > (update_hours*3600), axis=1) context = { 'channels': [] if channels_df.empty else channels_df.sort_values(by=['out_percent']).to_dict(orient='records'), - 'local_settings': LocalSettings.objects.filter(key__contains='AF-').order_by('key'), + 'local_settings': get_local_settings('AF-'), 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '', 'graph_links': graph_links(), 'network_links': network_links() @@ -539,7 +539,7 @@ def fees(request): @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) def advanced(request): if request.method == 'GET': - 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 = Channels.objects.filter(is_open=True).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity'), 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) @@ -551,7 +551,7 @@ def advanced(request): channels_df['local_max_htlc'] = channels_df['local_max_htlc_msat']/1000 context = { 'channels': channels_df.to_dict(orient='records'), - 'local_settings': LocalSettings.objects.all().order_by('key'), + 'local_settings': get_local_settings('AF-', 'AR-', 'GUI-', 'LND-'), 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '', 'graph_links': graph_links(), 'network_links': network_links() @@ -575,10 +575,9 @@ def route(request): 'total_cost': total_cost, 'total_ppm': total_ppm, 'route': route, - 'rebalances': Rebalancer.objects.filter(payment_hash=payment_hash).annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())), 'invoices': Invoices.objects.filter(r_hash=payment_hash), - 'incoming_htlcs': PendingHTLCs.objects.filter(incoming=True, hash_lock=payment_hash).annotate(blocks_til_expiration=Sum('expiration_height')-block_height).annotate(hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock'), - 'outgoing_htlcs': PendingHTLCs.objects.filter(incoming=False, hash_lock=payment_hash).annotate(blocks_til_expiration=Sum('expiration_height')-block_height).annotate(hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock') + 'incoming_htlcs': PendingHTLCs.objects.filter(incoming=True, hash_lock=payment_hash).annotate(blocks_til_expiration=Sum('expiration_height')-block_height, hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock'), + 'outgoing_htlcs': PendingHTLCs.objects.filter(incoming=False, hash_lock=payment_hash).annotate(blocks_til_expiration=Sum('expiration_height')-block_height, hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock') } return render(request, 'route.html', context) except Exception as e: @@ -620,9 +619,9 @@ def peers(request): @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) def balances(request): if request.method == 'GET': - stub = lnrpc.LightningStub(lnd_connect()) + stub = walletstub.WalletKitStub(lnd_connect()) context = { - 'utxos': stub.ListUnspent(ln.ListUnspentRequest(min_confs=0, max_confs=9999999)).utxos, + 'utxos': stub.ListUnspent(walletrpc.ListUnspentRequest(min_confs=0, max_confs=9999999)).utxos, 'transactions': list(Onchain.objects.filter(block_height=0)) + list(Onchain.objects.exclude(block_height=0).order_by('-block_height')), 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '', 'network_links': network_links() @@ -999,7 +998,7 @@ def channel(request): node_capacity = channels_df['capacity'].sum() channels_df = DataFrame.from_records(Channels.objects.filter(chan_id=chan_id).values()) rebalancer_df = DataFrame.from_records(Rebalancer.objects.filter(last_hop_pubkey=channels_df['remote_pubkey'][0]).annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id').values()) - failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).order_by('-id').values()) + failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.exclude(wire_failure=99).filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).order_by('-id').values()) peer_info_df = DataFrame.from_records(Peers.objects.filter(pubkey=channels_df['remote_pubkey'][0]).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'] @@ -1398,9 +1397,7 @@ def opens(request): exlcude_list = AvoidNodes.objects.values_list('pubkey') filter_60day = datetime.now() - timedelta(days=60) payments_60day = Payments.objects.filter(creation_date__gte=filter_60day).values_list('payment_hash') - open_list = PaymentHops.objects.filter(payment_hash__in=payments_60day).exclude(node_pubkey=self_pubkey).exclude(node_pubkey__in=current_peers).exclude(node_pubkey__in=exlcude_list).values('node_pubkey').annotate(ppm=(Sum('fee')/Sum('amt'))*1000000).annotate(score=Round((Round(Count('id')/5, output_field=IntegerField())+Round(Sum('amt')/500000, output_field=IntegerField()))/10, output_field=IntegerField())).annotate(count=Count('id')).annotate(amount=Sum('amt')).annotate(fees=Sum('fee')).annotate(sum_cost_to=Sum('cost_to')/(Sum('amt')/1000000)).exclude(score=0).order_by('-score', 'ppm')[:21] - for open in open_list: - open['alias'] = PaymentHops.objects.filter(node_pubkey=open['node_pubkey']).order_by('-id')[0].alias + open_list = PaymentHops.objects.filter(payment_hash__in=payments_60day).exclude(node_pubkey=self_pubkey).exclude(node_pubkey__in=current_peers).exclude(node_pubkey__in=exlcude_list).values('node_pubkey').annotate(ppm=(Sum('fee')/Sum('amt'))*1000000, score=Round((Round(Count('id')/5, output_field=IntegerField())+Round(Sum('amt')/500000, output_field=IntegerField()))/10, output_field=IntegerField()), count=Count('id'), amount=Sum('amt'), fees=Sum('fee'), sum_cost_to=Sum('cost_to')/(Sum('amt')/1000000), alias=Max('alias')).exclude(score=0).order_by('-score', 'ppm')[:21] context = { 'open_list': open_list, 'avoid_list': AvoidNodes.objects.all(), @@ -1414,7 +1411,7 @@ def opens(request): @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) def actions(request): if request.method == 'GET': - 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')) + channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity'), 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 = [] @@ -1479,8 +1476,8 @@ def pending_htlcs(request): stub = lnrpc.LightningStub(lnd_connect()) block_height = stub.GetInfo(ln.GetInfoRequest()).block_height context = { - 'incoming_htlcs': PendingHTLCs.objects.filter(incoming=True).annotate(blocks_til_expiration=Sum('expiration_height')-block_height).annotate(hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock'), - 'outgoing_htlcs': PendingHTLCs.objects.filter(incoming=False).annotate(blocks_til_expiration=Sum('expiration_height')-block_height).annotate(hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock') + 'incoming_htlcs': PendingHTLCs.objects.filter(incoming=True).annotate(blocks_til_expiration=Sum('expiration_height')-block_height, hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock'), + 'outgoing_htlcs': PendingHTLCs.objects.filter(incoming=False).annotate(blocks_til_expiration=Sum('expiration_height')-block_height, hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock') } return render(request, 'pending_htlcs.html', context) else: @@ -1490,15 +1487,15 @@ def pending_htlcs(request): def failed_htlcs(request): if request.method == 'GET': try: - #print (f"{datetime.now().strftime('%c')} : {request.GET.urlencode()=}") query = None if request.GET.urlencode()[1:] == '' else request.GET.urlencode()[1:].split('_') chan_id = None if query is None or len(query) < 1 else query[0] direction = None if query is None or len(query) < 2 else query[1] - #print (f"{datetime.now().strftime('%c')} : {query=} {chan_id=} {direction=}") - failed_htlcs=FailedHTLCs.objects.all().order_by('-id')[:150] if chan_id is None else (FailedHTLCs.objects.filter(chan_id_out=chan_id).order_by('-id')[:150] if direction == "O" else FailedHTLCs.objects.filter(chan_id_in=chan_id).order_by('-id')[:150]) - + failed_htlcs = FailedHTLCs.objects.exclude(wire_failure=99).order_by('-id')[:150] if chan_id is None else (FailedHTLCs.objects.exclude(wire_failure=99).filter(chan_id_out=chan_id).order_by('-id')[:150] if direction == "O" else FailedHTLCs.objects.exclude(wire_failure=99).filter(chan_id_in=chan_id).order_by('-id')[:150]) + filter_7day = datetime.now() - timedelta(days=7) + agg_failed_htlcs = FailedHTLCs.objects.filter(timestamp__gte=filter_7day, wire_failure=99).values('chan_id_in', 'chan_id_out').annotate(count=Count('id'), volume=Sum('amount'), chan_in_alias=Max('chan_in_alias'), chan_out_alias=Max('chan_out_alias')).order_by('-count')[:21] context = { - 'failed_htlcs': failed_htlcs + 'failed_htlcs': failed_htlcs, + 'agg_failed_htlcs': agg_failed_htlcs } return render(request, 'failed_htlcs.html', context) except Exception as e: @@ -1534,13 +1531,7 @@ def invoices(request): def rebalances(request): if request.method == 'GET': try: - rebalances = Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id') - rebalances_success = rebalances.filter(status=2) - context = { - 'rebalances': rebalances[:150], - 'rebalances_success' : rebalances_success[:69] - } - return render(request, 'rebalances.html', context) + return render(request, 'rebalances.html') except Exception as e: try: error = str(e.code()) @@ -1553,8 +1544,10 @@ def rebalances(request): @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) def batch(request): if request.method == 'GET': + stub = lnrpc.LightningStub(lnd_connect()) context = { 'iterator': range(1,11), + 'balances': stub.WalletBalance(ln.WalletBalanceRequest()) } return render(request, 'batch.html', context) else: @@ -1701,7 +1694,7 @@ def batch_open(request): def forwards(request): if request.method == 'GET': context = { - 'forwards': Forwards.objects.all().annotate(amt_in=Sum('amt_in_msat')/1000).annotate(amt_out=Sum('amt_out_msat')/1000).annotate(ppm=Round((Sum('fee')*1000000000)/Sum('amt_out_msat'), output_field=IntegerField())).order_by('-id')[:150], + 'forwards': Forwards.objects.all().annotate(amt_in=Sum('amt_in_msat')/1000, amt_out=Sum('amt_out_msat')/1000, ppm=Round((Sum('fee')*1000000000)/Sum('amt_out_msat'), output_field=IntegerField())).order_by('-id')[:150], } return render(request, 'forwards.html', context) else: @@ -1710,56 +1703,9 @@ def forwards(request): @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) def rebalancing(request): if request.method == 'GET': - 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()) - filter_7day = datetime.now() - timedelta(days=7) - rebalancer_7d_df = DataFrame.from_records(Rebalancer.objects.filter(stop__gte=filter_7day).order_by('-id').values()) - if channels_df.shape[0] > 0: - channels_df['inbound_can'] = channels_df['percent_inbound'] / channels_df['ar_in_target'] - 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['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 = DataFrame() if rebalancer_7d_df.empty else 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['is_active']==True][enabled_df['inbound_can']>=1][enabled_df['fee_check']<100] - eligible_count = eligible_df.shape[0] - enabled_count = enabled_df.shape[0] - available_df = channels_df[channels_df['auto_rebalance']==False][channels_df['is_active']==True][channels_df['percent_outbound'] / channels_df['ar_out_target']>=1] - available_count = available_df.shape[0] - else: - eligible_count = 0 - enabled_count = 0 - available_count = 0 - try: - query = request.GET.urlencode()[1:] - if query == '1': - #Filter Sink (AR Enabled) - channels_df = channels_df[channels_df['auto_rebalance']==True][channels_df['is_active']==True] - elif query == '2': - #Filter Source (Eligible to rebalance out) - channels_df = channels_df[channels_df['auto_rebalance']==False][channels_df['is_active']==True].sort_values(by=['percent_outbound'], ascending=False) - else: - #Proceed - pass - except Exception as e: - try: - error = str(e.code()) - except: - error = str(e) - return render(request, 'error.html', {'error': error}) - context = { - 'eligible_count': eligible_count, - 'enabled_count': enabled_count, - 'available_count': available_count, - 'channels': channels_df.to_dict(orient='records'), - 'rebalancer': Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')[:20], 'rebalancer_form': RebalancerForm, - 'local_settings': LocalSettings.objects.filter(key__contains='AR-').order_by('key'), + 'local_settings': get_local_settings('AR-'), 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '', 'graph_links': graph_links() } @@ -2000,11 +1946,10 @@ def rebalance(request): if form.is_valid(): try: if Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey']).exists() or form.cleaned_data['last_hop_pubkey'] == '': - chan_ids = [] - for channel in form.cleaned_data['outgoing_chan_ids']: - chan_ids.append(channel.chan_id) + chan_ids = [ch.chan_id for ch in form.cleaned_data['outgoing_chan_ids']] 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 '' + target_channel = Channels.objects.filter(is_active=True, is_open=True, remote_pubkey=form.cleaned_data['last_hop_pubkey']).first() + target_alias = target_channel.alias if target_channel.alias != '' else target_channel.remote_pubkey[:12] fee_limit = round(form.cleaned_data['fee_limit']*form.cleaned_data['value']*0.000001, 3) Rebalancer(value=form.cleaned_data['value'], fee_limit=fee_limit, outgoing_chan_ids=str(chan_ids).replace('\'', ''), last_hop_pubkey=form.cleaned_data['last_hop_pubkey'], target_alias=target_alias, duration=form.cleaned_data['duration'], manual=True).save() messages.success(request, 'Rebalancer request created!') @@ -2022,149 +1967,109 @@ def rebalance(request): messages.error(request, 'Invalid Request. Please try again.') return redirect(request.META.get('HTTP_REFERER')) +def get_local_settings(*prefixes): + form = [] + if 'AR-' in prefixes: + form.append({'unit': '', 'form_id': 'update_channels', 'id': 'update_channels'}) + form.append({'unit': '', 'form_id': 'enabled', 'value': 0, 'label': 'AR Enabled', 'id': 'AR-Enabled', 'title':'This enables or disables the auto-scheduling function', 'min':0, 'max':1},) + form.append({'unit': '%', 'form_id': 'target_percent', 'value': 0, 'label': 'AR Target Amount', 'id': 'AR-Target%', 'title': 'The percentage of the total capacity to target as the rebalance amount', 'min':0.1, 'max':100}) + form.append({'unit': 'min', 'form_id': 'target_time', 'value': 0, 'label': 'AR Target Time', 'id': 'AR-Time', 'title': 'The time spent per individual rebalance attempt', 'min':1, 'max':60}) + form.append({'unit': 'ppm', 'form_id': 'fee_rate', 'value': 0, 'label': 'AR Max Fee Rate', 'id': 'AR-MaxFeeRate', 'title': 'The max rate we can ever use to refill a channel with outbound', 'min':1, 'max':2500}) + form.append({'unit': '%', 'form_id': 'outbound_percent', 'value': 0, 'label': 'AR Target Out Above', 'id': 'AR-Outbound%', 'title': 'When a channel is not enabled for targeting; the minimum outbound a channel must have to be a source for refilling another channel', 'min':1, 'max':100}) + form.append({'unit': '%', 'form_id': 'inbound_percent', 'value': 0, 'label': 'AR Target In Above', 'id': 'AR-Inbound%', 'title': 'When a channel is enabled for targeting; the maximum inbound a channel can have before selected for auto rebalance', 'min':1, 'max':100}) + form.append({'unit': '%', 'form_id': 'max_cost', 'value': 0, 'label': 'AR Max Cost', 'id': 'AR-MaxCost%', 'title': 'The ppm to target which is the percentage of the outbound fee rate for the channel being refilled', 'min':1, 'max':100}) + form.append({'unit': '%', 'form_id': 'variance', 'value': 0, 'label': 'AR Variance', 'id': 'AR-Variance', 'title': 'The percentage of the target amount to be randomly varied with every rebalance attempt', 'min':0, 'max':100}) + form.append({'unit': 'min', 'form_id': 'wait_period', 'value': 0, 'label': 'AR Wait Period', 'id': 'AR-WaitPeriod', 'title': 'The minutes we should wait after a failed attempt before trying again', 'min':1, 'max':100}) + form.append({'unit': '', 'form_id': 'autopilot', 'value': 0, 'label': 'Autopilot', 'id': 'AR-Autopilot', 'title': 'This enables or disables the Autopilot function which automatically acts upon suggestions on this page: /actions', 'min':0, 'max':1}) + form.append({'unit': 'days', 'form_id': 'autopilotdays', 'value': 0, 'label': 'Autopilot Days', 'id': 'AR-APDays', 'title': 'Number of days to consider for autopilot. Default 7', 'min':0, 'max':100}) + form.append({'unit': '', 'form_id': 'workers', 'value': 1, 'label': 'Workers', 'id': 'AR-Workers', 'title': 'Number of workers', 'min':1, 'max':12}) + if 'AF-' in prefixes: + form.append({'unit': '', 'form_id': 'af_enabled', 'value': 0, 'label': 'Autofee', 'id': 'AF-Enabled', 'title': 'Enable/Disable Auto-fee functionality', 'min':0, 'max':1}) + form.append({'unit': 'ppm', 'form_id': 'af_maxRate', 'value': 0, 'label': 'AF Max Rate', 'id': 'AF-MaxRate', 'title': 'Minimum Rate', 'min':0, 'max':5000}) + form.append({'unit': 'ppm', 'form_id': 'af_minRate', 'value': 0, 'label': 'AF Min Rate', 'id': 'AF-MinRate', 'title': 'Minimum Rate', 'min':0, 'max':5000}) + form.append({'unit': 'ppm', 'form_id': 'af_increment', 'value': 0, 'label': 'AF Increment', 'id': 'AF-Increment', 'title': 'Amount to increment on each interaction', 'min':0, 'max':100}) + form.append({'unit': '%', 'form_id': 'af_multiplier', 'value': 0, 'label': 'AF Multiplier', 'id': 'AF-Multiplier', 'title': 'Multiplier to be applied to Auto-Fee', 'min':0, 'max':100}) + form.append({'unit': '', 'form_id': 'af_failedHTLCs', 'value': 0, 'label': 'AF FailedHTLCs', 'id': 'AF-FailedHTLCs', 'title': 'Failed HTLCs', 'min':0, 'max':100}) + form.append({'unit': 'hours', 'form_id': 'af_updateHours', 'value': 0, 'label': 'AF Update', 'id': 'AF-UpdateHours', 'title': 'Number of hours to consider to update fees. Default 24', 'min':0, 'max':100}) + if 'GUI-' in prefixes: + form.append({'unit': '', 'form_id': 'gui_graphLinks', 'value': graph_links(), 'label': 'Graph URL', 'id': 'GUI-GraphLinks', 'title': 'Preferred Graph URL'}) + form.append({'unit': '', 'form_id': 'gui_netLinks', 'value': network_links(), 'label': 'NET URL', 'id': 'GUI-NetLinks', 'title': 'Preferred NET URL'}) + if 'LND-' in prefixes: + form.append({'unit': '', 'form_id': 'lnd_cleanPayments', 'value': 0, 'label': 'LND Clean Payments', 'id': 'LND-CleanPayments', 'title': 'Clean LND Payments', 'min':0, 'max':1}) + form.append({'unit': 'days', 'form_id': 'lnd_retentionDays', 'value': 30, 'label': 'LND Retention', 'id': 'LND-RetentionDays', 'title': 'LND Retention days'}) + + for prefix in prefixes: + ar_settings = LocalSettings.objects.filter(key__contains=prefix).values('key', 'value').order_by('key') + for field in form: + for sett in ar_settings: + if field['id'] == sett['key']: + field['value'] = sett['value'] + break + return form + @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) -def auto_rebalance(request): +def update_settings(request): if request.method == 'POST': - form = AutoRebalanceForm(request.POST) - if form.is_valid(): - if form.cleaned_data['chan_id'] is not None: - target_chan_id = form.cleaned_data['chan_id'] - target_channel = Channels.objects.filter(chan_id=target_chan_id) - if len(target_channel) == 1: - target_channel = target_channel[0] - target_channel.auto_rebalance = True if target_channel.auto_rebalance == False else False - target_channel.save() - messages.success(request, 'Updated auto rebalancer status for: ' + str(target_channel.chan_id)) - else: - messages.error(request, 'Failed to update auto rebalancer status of channel: ' + str(target_chan_id)) - if form.cleaned_data['target_percent'] is not None: - target_percent = float(form.cleaned_data['target_percent']) - try: - db_percent_target = LocalSettings.objects.get(key='AR-Target%') - except: - LocalSettings(key='AR-Target%', value='5').save() - db_percent_target = LocalSettings.objects.get(key='AR-Target%') - db_percent_target.value = target_percent - db_percent_target.save() - if form.cleaned_data['targetallchannels']: - Channels.objects.all().update(ar_amt_target=Round(F('capacity')*(target_percent/100), output_field=IntegerField())) - messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent)) - else: - messages.success(request, 'Updated auto rebalancer target amount in local settings: ' + str(target_percent)) - if form.cleaned_data['target_time'] is not None: - target_time = form.cleaned_data['target_time'] - try: - db_time_target = LocalSettings.objects.get(key='AR-Time') - except: - LocalSettings(key='AR-Time', value='5').save() - db_time_target = LocalSettings.objects.get(key='AR-Time') - db_time_target.value = target_time - db_time_target.save() - messages.success(request, 'Updated auto rebalancer target time setting to: ' + str(target_time)) - if form.cleaned_data['enabled'] is not None: - enabled = form.cleaned_data['enabled'] - try: - db_enabled = LocalSettings.objects.get(key='AR-Enabled') - except: - LocalSettings(key='AR-Enabled', value='0').save() - db_enabled = LocalSettings.objects.get(key='AR-Enabled') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated auto rebalancer enabled setting to: ' + str(enabled)) - if form.cleaned_data['outbound_percent'] is not None: - outbound_percent = int(form.cleaned_data['outbound_percent']) - try: - db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%') - except: - LocalSettings(key='AR-Outbound%', value='75').save() - db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%') - db_outbound_target.value = outbound_percent - db_outbound_target.save() - if form.cleaned_data['targetallchannels']: - Channels.objects.all().update(ar_out_target=int(outbound_percent)) - messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent)) - else: - messages.success(request, 'Updated auto rebalancer target outbound percent setting in local settings to: ' + str(outbound_percent)) - if form.cleaned_data['inbound_percent'] is not None: - inbound_percent = int(form.cleaned_data['inbound_percent']) - try: - db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%') - except: - LocalSettings(key='AR-Inbound%', value='100').save() - db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%') - db_inbound_target.value = inbound_percent - db_inbound_target.save() - if form.cleaned_data['targetallchannels']: - Channels.objects.all().update(ar_in_target=int(inbound_percent)) - messages.success(request, 'Updated auto rebalancer target inbound percent setting for all channels to: ' + str(inbound_percent)) - else: - messages.success(request, 'Updated auto rebalancer target inbound percent setting in local settigs to: ' + str(inbound_percent)) - if form.cleaned_data['fee_rate'] is not None: - fee_rate = form.cleaned_data['fee_rate'] - try: - db_fee_rate = LocalSettings.objects.get(key='AR-MaxFeeRate') - except: - LocalSettings(key='AR-MaxFeeRate', value='100').save() - db_fee_rate = LocalSettings.objects.get(key='AR-MaxFeeRate') - db_fee_rate.value = fee_rate - db_fee_rate.save() - messages.success(request, 'Updated auto rebalancer max fee rate setting to: ' + str(fee_rate)) - if form.cleaned_data['max_cost'] is not None: - max_cost = int(form.cleaned_data['max_cost']) - try: - db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') - except: - LocalSettings(key='AR-MaxCost%', value='65').save() - db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') - db_max_cost.value = max_cost - db_max_cost.save() - if form.cleaned_data['targetallchannels']: - Channels.objects.all().update(ar_max_cost=int(max_cost)) - messages.success(request, 'Updated auto rebalancer max cost setting for all channels to: ' + str(max_cost)) - else: - messages.success(request, 'Updated auto rebalancer max cost setting in local settings to: ' + str(max_cost)) - if form.cleaned_data['autopilot'] is not None: - autopilot = form.cleaned_data['autopilot'] - try: - db_autopilot = LocalSettings.objects.get(key='AR-Autopilot') - except: - LocalSettings(key='AR-Autopilot', value='0').save() - db_autopilot = LocalSettings.objects.get(key='AR-Autopilot') - db_autopilot.value = autopilot - db_autopilot.save() - messages.success(request, 'Updated autopilot setting to: ' + str(autopilot)) - if form.cleaned_data['autopilotdays'] is not None: - autopilotdays = form.cleaned_data['autopilotdays'] - try: - db_autopilotdays = LocalSettings.objects.get(key='AR-APDays') - except: - LocalSettings(key='AR-APDays', value='7').save() - db_autopilotdays = LocalSettings.objects.get(key='AR-APDays') - db_autopilotdays.value = autopilotdays - db_autopilotdays.save() - messages.success(request, 'Updated autopilot days setting to: ' + str(autopilotdays)) - if form.cleaned_data['variance'] is not None: - variance = form.cleaned_data['variance'] - try: - db_variance = LocalSettings.objects.get(key='AR-Variance') - except: - LocalSettings(key='AR-Variance', value='0').save() - db_variance = LocalSettings.objects.get(key='AR-Variance') - db_variance.value = variance - db_variance.save() - messages.success(request, 'Updated variance setting to: ' + str(variance)) - if form.cleaned_data['wait_period'] is not None: - wait_period = form.cleaned_data['wait_period'] - try: - db_wait_period = LocalSettings.objects.get(key='AR-WaitPeriod') - except: - LocalSettings(key='AR-WaitPeriod', value='30').save() - db_wait_period = LocalSettings.objects.get(key='AR-WaitPeriod') - db_wait_period.value = wait_period - db_wait_period.save() - messages.success(request, 'Updated wait period setting to: ' + str(wait_period)) - else: + template = [{'form_id': 'enabled', 'value': 0, 'parse': lambda x: x,'id': 'AR-Enabled'}, + {'form_id': 'target_percent', 'value': 5, 'parse': lambda x: float(x),'id': 'AR-Target%'}, + {'form_id': 'target_time', 'value': 5, 'parse': lambda x: x,'id': 'AR-Time'}, + {'form_id': 'fee_rate', 'value': 100, 'parse': lambda x: x,'id': 'AR-MaxFeeRate'}, + {'form_id': 'outbound_percent', 'value': 75, 'parse': lambda x: int(x),'id': 'AR-Outbound%'}, + {'form_id': 'inbound_percent', 'value': 100, 'parse': lambda x: int(x),'id': 'AR-Inbound%'}, + {'form_id': 'max_cost', 'value': 65, 'parse': lambda x: int(x),'id': 'AR-MaxCost%'}, + {'form_id': 'variance', 'value': 0, 'parse': lambda x: x,'id': 'AR-Variance'}, + {'form_id': 'wait_period', 'value': 30, 'parse': lambda x: x,'id': 'AR-WaitPeriod'}, + {'form_id': 'autopilot', 'value': 0, 'parse': lambda x: x,'id': 'AR-Autopilot'}, + {'form_id': 'autopilotdays', 'value': 7, 'parse': lambda x: x,'id': 'AR-APDays'}, + {'form_id': 'workers', 'value': 5, 'parse': lambda x: x,'id': 'AR-Workers'}, + #AF + {'form_id': 'af_enabled', 'value': 0, 'parse': lambda x: int(x),'id': 'AF-Enabled'}, + {'form_id': 'af_maxRate', 'value': 2500, 'parse': lambda x: int(x),'id': 'AF-MaxRate'}, + {'form_id': 'af_minRate', 'value': 0, 'parse': lambda x: int(x),'id': 'AF-MinRate'}, + {'form_id': 'af_increment', 'value': 5, 'parse': lambda x: int(x),'id': 'AF-Increment'}, + {'form_id': 'af_multiplier', 'value': 5, 'parse': lambda x: int(x),'id': 'AF-Multiplier'}, + {'form_id': 'af_failedHTLCs', 'value': 25, 'parse': lambda x: int(x),'id': 'AF-FailedHTLCs'}, + {'form_id': 'af_updateHours', 'value': 24, 'parse': lambda x: int(x),'id': 'AF-UpdateHours'}, + #GUI + {'form_id': 'gui_graphLinks', 'value': '0', 'parse': lambda x: x,'id': 'GUI-GraphLinks'}, + {'form_id': 'gui_netLinks', 'value': '0', 'parse': lambda x: x,'id': 'GUI-NetLinks'}, + #LND + {'form_id': 'lnd_cleanPayments', 'value': '0', 'parse': lambda x: x, 'id': 'LND-CleanPayments'}, + {'form_id': 'lnd_retentionDays', 'value': '0', 'parse': lambda x: x, 'id': 'LND-RetentionDays'}, + ] + + form = LocalSettingsForm(request.POST) + if not form.is_valid(): messages.error(request, 'Invalid Request. Please try again.') + else: + update_channels = form.cleaned_data['update_channels'] + for field in template: + value = form.cleaned_data[field['form_id']] + if value is not None: + value = field['parse'](value) + try: + db_value = LocalSettings.objects.get(key=field['id']) + except: + LocalSettings(key=field['id'], value=field['value']).save() + db_value = LocalSettings.objects.get(key=field['id']) + if db_value.value == str(value) or len(str(value)) == 0: + continue + db_value.value = value + db_value.save() + + if update_channels and field['id'] in ['AR-Target%', 'AR-Outbound%','AR-Inbound%','AR-MaxCost%']: + if field['id'] == 'AR-Target%': + Channels.objects.all().update(ar_amt_target=Round(F('capacity')*(value/100), output_field=IntegerField())) + elif field['id'] == 'AR-Outbound%': + Channels.objects.all().update(ar_out_target=value) + elif field['id'] == 'AR-Inbound%': + Channels.objects.all().update(ar_in_target=value) + elif field['id'] == 'AR-MaxCost%': + Channels.objects.all().update(ar_max_cost=value) + messages.success(request, 'All channels ' + field['id'] + ' updated to: ' + str(value)) + else: + messages.success(request, field['id'] + ' updated to: ' + str(value)) + return redirect(request.META.get('HTTP_REFERER')) @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) @@ -2175,23 +2080,17 @@ def update_channel(request): chan_id = form.cleaned_data['chan_id'] target = form.cleaned_data['target'] update_target = int(form.cleaned_data['update_target']) - db_channel = Channels.objects.filter(chan_id=chan_id)[0] + db_channel = Channels.objects.get(chan_id=chan_id) if update_target == 0: stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=target, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv)) db_channel.local_base_fee = target db_channel.save() messages.success(request, 'Base fee for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target)) elif update_target == 1: stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(target/1000000), time_lock_delta=db_channel.local_cltv)) old_fee_rate = db_channel.local_fee_rate db_channel.local_fee_rate = target @@ -2221,10 +2120,7 @@ def update_channel(request): messages.success(request, 'Auto rebalancer max cost for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target) + '%') elif update_target == 7: stub = lnrouter.RouterStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChanStatus(lnr.UpdateChanStatusRequest(chan_point=channel_point, action=0)) if target == 1 else stub.UpdateChanStatus(lnr.UpdateChanStatusRequest(chan_point=channel_point, action=1)) db_channel.local_disabled = False if target == 1 else True db_channel.save() @@ -2237,30 +2133,21 @@ def update_channel(request): messages.success(request, 'Auto fees status for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.auto_fees)) elif update_target == 9: stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=target)) db_channel.local_cltv = target db_channel.save() messages.success(request, 'CLTV for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(float(target))) elif update_target == 10: stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv, min_htlc_msat_specified=True, min_htlc_msat=int(target*1000))) db_channel.local_min_htlc_msat = int(target*1000) db_channel.save() messages.success(request, 'Min HTLC for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(float(target))) elif update_target == 11: stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv, max_htlc_msat=int(target*1000))) db_channel.local_max_htlc_msat = int(target*1000) db_channel.save() @@ -2328,6 +2215,13 @@ def update_pending(request): messages.error(request, 'Invalid Request. Please try again.') return redirect(request.META.get('HTTP_REFERER')) +def point(ch: Channels): + channel_point = ln.ChannelPoint() + channel_point.funding_txid_bytes = bytes.fromhex(ch.funding_txid) + channel_point.funding_txid_str = ch.funding_txid + channel_point.output_index = ch.output_index + return channel_point + @is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) def update_setting(request): if request.method == 'POST': @@ -2335,175 +2229,12 @@ def update_setting(request): if form.is_valid(): key = form.cleaned_data['key'] value = form.cleaned_data['value'] - if key == 'AR-Target%': - target_percent = float(value) - try: - db_percent_target = LocalSettings.objects.get(key='AR-Target%') - except: - LocalSettings(key='AR-Target%', value='5').save() - db_percent_target = LocalSettings.objects.get(key='AR-Target%') - db_percent_target.value = target_percent - db_percent_target.save() - messages.success(request, 'Updated auto rebalancer target amount to: ' + str(target_percent)) - elif key == 'AR-Time': - target_time = int(value) - try: - db_time_target = LocalSettings.objects.get(key='AR-Time') - except: - LocalSettings(key='AR-Time', value='5').save() - db_time_target = LocalSettings.objects.get(key='AR-Time') - db_time_target.value = target_time - db_time_target.save() - messages.success(request, 'Updated auto rebalancer target time setting to: ' + str(target_time)) - elif key == 'AR-Workers': - workers = int(value) - try: - db_workers = LocalSettings.objects.get(key='AR-Workers') - except: - LocalSettings(key='AR-Workers', value='5').save() - db_workers = LocalSettings.objects.get(key='AR-Workers') - db_workers.value = workers - db_workers.save() - messages.success(request, 'Updated auto rebalancer workers setting to: ' + str(workers)) - elif key == 'AR-Enabled': - enabled = int(value) - try: - db_enabled = LocalSettings.objects.get(key='AR-Enabled') - except: - LocalSettings(key='AR-Enabled', value='0').save() - db_enabled = LocalSettings.objects.get(key='AR-Enabled') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated auto rebalancer enabled setting to: ' + str(enabled)) - elif key == 'AR-Outbound%': - outbound_percent = int(value) - try: - db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%') - except: - LocalSettings(key='AR-Outbound%', value='75').save() - db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%') - db_outbound_target.value = outbound_percent - db_outbound_target.save() - messages.success(request, 'Updated auto rebalancer target outbound percent setting: ' + str(outbound_percent)) - elif key == 'AR-Inbound%': - inbound_percent = int(value) - try: - db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%') - except: - LocalSettings(key='AR-Inbound%', value='100').save() - db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%') - db_inbound_target.value = inbound_percent - db_inbound_target.save() - messages.success(request, 'Updated auto rebalancer target inbound percent setting: ' + str(inbound_percent)) - elif key == 'AR-MaxFeeRate': - fee_rate = int(value) - try: - db_fee_rate = LocalSettings.objects.get(key='AR-MaxFeeRate') - except: - LocalSettings(key='AR-MaxFeeRate', value='100').save() - db_fee_rate = LocalSettings.objects.get(key='AR-MaxFeeRate') - db_fee_rate.value = fee_rate - db_fee_rate.save() - messages.success(request, 'Updated auto rebalancer max fee rate setting to: ' + str(fee_rate)) - elif key == 'AR-MaxCost%': - max_cost = int(value) - try: - db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') - except: - LocalSettings(key='AR-MaxCost%', value='65').save() - db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') - db_max_cost.value = max_cost - db_max_cost.save() - messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost)) - elif key == 'AR-Autopilot': - autopilot = int(value) - try: - db_autopilot = LocalSettings.objects.get(key='AR-Autopilot') - except: - LocalSettings(key='AR-Autopilot', value='0').save() - db_autopilot = LocalSettings.objects.get(key='AR-Autopilot') - db_autopilot.value = autopilot - db_autopilot.save() - messages.success(request, 'Updated autopilot setting to: ' + str(autopilot)) - elif key == 'AR-APDays': - apdays = int(value) - try: - db_apdays = LocalSettings.objects.get(key='AR-APDays') - except: - LocalSettings(key='AR-APDays', value='7').save() - db_apdays = LocalSettings.objects.get(key='AR-APDays') - db_apdays.value = apdays - db_apdays.save() - messages.success(request, 'Updated Autopilot Days setting to: ' + str(apdays)) - elif key == 'AR-Variance': - variance = int(value) - try: - db_variance = LocalSettings.objects.get(key='AR-Variance') - except: - LocalSettings(key='AR-Variance', value='0').save() - db_variance = LocalSettings.objects.get(key='AR-Variance') - db_variance.value = variance - db_variance.save() - messages.success(request, 'Updated variance setting to: ' + str(variance)) - elif key == 'AR-WaitPeriod': - wait_period = int(value) - try: - db_wait_period = LocalSettings.objects.get(key='AR-WaitPeriod') - except: - LocalSettings(key='AR-WaitPeriod', value='0').save() - db_wait_period = LocalSettings.objects.get(key='AR-WaitPeriod') - db_wait_period.value = wait_period - db_wait_period.save() - messages.success(request, 'Updated wait period setting to: ' + str(wait_period)) - elif key == 'GUI-GraphLinks': - links = str(value) - try: - db_links = LocalSettings.objects.get(key='GUI-GraphLinks') - except: - LocalSettings(key='GUI-GraphLinks', value='0').save() - db_links = LocalSettings.objects.get(key='GUI-GraphLinks') - db_links.value = links - db_links.save() - messages.success(request, 'Updated graph links to use: ' + str(links)) - elif key == 'GUI-NetLinks': - links = str(value) - try: - db_links = LocalSettings.objects.get(key='GUI-NetLinks') - except: - LocalSettings(key='GUI-NetLinks', value='0').save() - db_links = LocalSettings.objects.get(key='GUI-NetLinks') - db_links.value = links - db_links.save() - messages.success(request, 'Updated network links to use: ' + str(links)) - elif key == 'LND-CleanPayments': - clean_payments = int(value) - try: - db_clean_payments = LocalSettings.objects.get(key='LND-CleanPayments') - except: - LocalSettings(key='LND-CleanPayments', value='0').save() - db_clean_payments = LocalSettings.objects.get(key='LND-CleanPayments') - db_clean_payments.value = clean_payments - db_clean_payments.save() - messages.success(request, 'Updated auto payment cleanup setting to: ' + str(clean_payments)) - elif key == 'LND-RetentionDays': - retention_days = int(value) - try: - db_retention_days = LocalSettings.objects.get(key='LND-RetentionDays') - except: - LocalSettings(key='LND-RetentionDays', value='0').save() - db_retention_days = LocalSettings.objects.get(key='LND-RetentionDays') - db_retention_days.value = retention_days - db_retention_days.save() - messages.success(request, 'Updated payment cleanup retention days to: ' + str(retention_days)) - elif key == 'ALL-oRate': + if key == 'ALL-oRate': target = int(value) stub = lnrpc.LightningStub(lnd_connect()) channels = Channels.objects.filter(is_open=True) for db_channel in channels: - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(target/1000000), time_lock_delta=db_channel.local_cltv)) old_fee_rate = db_channel.local_fee_rate db_channel.local_fee_rate = target @@ -2516,11 +2247,7 @@ def update_setting(request): stub = lnrpc.LightningStub(lnd_connect()) channels = Channels.objects.filter(is_open=True) for db_channel in channels: - stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=target, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv)) db_channel.local_base_fee = target db_channel.save() @@ -2530,11 +2257,7 @@ def update_setting(request): stub = lnrpc.LightningStub(lnd_connect()) channels = Channels.objects.filter(is_open=True) for db_channel in channels: - stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=target)) db_channel.local_cltv = target db_channel.save() @@ -2544,11 +2267,7 @@ def update_setting(request): stub = lnrpc.LightningStub(lnd_connect()) channels = Channels.objects.filter(is_open=True) for db_channel in channels: - stub = lnrpc.LightningStub(lnd_connect()) - channel_point = ln.ChannelPoint() - channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) - channel_point.funding_txid_str = db_channel.funding_txid - channel_point.output_index = db_channel.output_index + channel_point = point(db_channel) stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv, min_htlc_msat_specified=True, min_htlc_msat=target)) db_channel.local_min_htlc_msat = target db_channel.save() @@ -2577,67 +2296,6 @@ def update_setting(request): target = int(value) channels = Channels.objects.filter(is_open=True, private=False).update(auto_fees=target) messages.success(request, 'Auto Fees setting for all channels updated to a value of: ' + str(target)) - elif key == 'AF-Enabled': - enabled = int(value) - try: - db_enabled = LocalSettings.objects.get(key='AF-Enabled') - except: - LocalSettings(key='AF-Enabled', value='0').save() - db_enabled = LocalSettings.objects.get(key='AF-Enabled') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated autofees enabled setting to: ' + str(enabled)) - elif key == 'AF-MaxRate': - enabled = int(value) - try: - db_enabled = LocalSettings.objects.get(key='AF-MaxRate') - except: - LocalSettings(key='AF-MaxRate', value='2500').save() - db_enabled = LocalSettings.objects.get(key='AF-MaxRate') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated autofees max rate setting to: ' + str(enabled)) - elif key == 'AF-MinRate': - enabled = int(value) - try: - db_enabled = LocalSettings.objects.get(key='AF-MinRate') - except: - LocalSettings(key='AF-MinRate', value='0').save() - db_enabled = LocalSettings.objects.get(key='AF-MinRate') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated autofees min rate setting to: ' + str(enabled)) - elif key == 'AF-Increment': - enabled = int(value) - try: - db_enabled = LocalSettings.objects.get(key='AF-Increment') - except: - LocalSettings(key='AF-Increment', value='5').save() - db_enabled = LocalSettings.objects.get(key='AF-Increment') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated autofees fee increment setting to: ' + str(enabled)) - elif key == 'AF-Multiplier': - enabled = int(value) - try: - db_enabled = LocalSettings.objects.get(key='AF-Multiplier') - except: - LocalSettings(key='AF-Multiplier', value='5').save() - db_enabled = LocalSettings.objects.get(key='AF-Multiplier') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated autofees fee multiplier setting to: ' + str(enabled)) - elif key == 'AF-FailedHTLCs': - enabled = int(value) - try: - db_enabled = LocalSettings.objects.get(key='AF-FailedHTLCs') - except: - LocalSettings(key='AF-FailedHTLCs', value='25').save() - db_enabled = LocalSettings.objects.get(key='AF-FailedHTLCs') - db_enabled.value = enabled - db_enabled.save() - messages.success(request, 'Updated autofees daily failed HTLC trigger limit setting to: ' + str(enabled)) - elif key == 'AF-UpdateHours': enabled = int(value) try: db_enabled = LocalSettings.objects.get(key='AF-UpdateHours') @@ -2730,10 +2388,23 @@ def get_fees(request): return redirect(request.META.get('HTTP_REFERER')) return redirect(request.META.get('HTTP_REFERER')) +@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED) +def sign_message(request): + if request.method == 'POST': + msg = request.POST.get("msg") + stub = lnrpc.LightningStub(lnd_connect()) + req = ln.SignMessageRequest(msg=msg.encode('utf-8'), single_hash=False) + response = stub.SignMessage(req) + messages.success(request, "Signed message: " + str(response.signature)) + else: + messages.error(request, 'Invalid Request. Please try again.') + return redirect(request.META.get('HTTP_REFERER')) + class PaymentsViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else [] queryset = Payments.objects.all() serializer_class = PaymentSerializer + filterset_fields = ['status'] class PaymentHopsViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else [] @@ -2744,6 +2415,7 @@ class InvoicesViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else [] queryset = Invoices.objects.all() serializer_class = InvoiceSerializer + filterset_fields = ['state'] def update(self, request, pk=None): setting = get_object_or_404(Invoices.objects.all(), pk=pk) @@ -2807,6 +2479,7 @@ class ChannelsViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else [] queryset = Channels.objects.all() serializer_class = ChannelSerializer + filterset_fields = ['is_open', 'private', 'is_active', 'auto_rebalance'] def update(self, request, pk=None): channel = get_object_or_404(Channels.objects.all(), pk=pk) @@ -2814,21 +2487,29 @@ def update(self, request, pk=None): if serializer.is_valid(): serializer.save() return Response(serializer.data) - else: - return Response(serializer.errors) + return Response(serializer.errors) class RebalancerViewSet(viewsets.ReadOnlyModelViewSet): permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else [] - queryset = Rebalancer.objects.all() + queryset = Rebalancer.objects.all().order_by('-id') serializer_class = RebalancerSerializer - + filterset_fields = {'status':['lt','gt','exact'], 'payment_hash':['exact'], 'stop':['gt']} + def create(self, request): - serializer = RebalancerSerializer(data=request.data) + serializer = self.get_serializer(data=request.data, context={'request': request}) if serializer.is_valid(): serializer.save() return Response(serializer.data) - else: - return Response(serializer.errors) + return Response(serializer.errors) + + def update(self, request, pk): + rebalance = get_object_or_404(Rebalancer.objects.all(), pk=pk) + serializer = RebalancerSerializer(rebalance, data=request.data, context={'request': request}, partial=True) + if serializer.is_valid(): + rebalance.stop = datetime.now() + serializer.save() + return Response(serializer.data) + return Response(serializer.errors) @api_view(['POST']) @is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED) @@ -2858,6 +2539,17 @@ def connect_peer(request): else: return Response({'error': 'Invalid request!'}) +@api_view(['GET']) +@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED) +def rebalance_stats(request): + try: + filter_7day = datetime.now() - timedelta(days=7) + rebalances = Rebalancer.objects.filter(stop__gt=filter_7day).values('last_hop_pubkey').annotate(attempts=Count('last_hop_pubkey'), successes=Sum(Case(When(status=2, then=1), output_field=IntegerField()))) + return Response(rebalances) + except Exception as e: + error = str(e) + return Response({'error': 'Unable to fetch stats! Error: ' + error}) + @api_view(['POST']) @is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED) def open_channel(request): @@ -3144,3 +2836,28 @@ def pending_channels(request): debug_error_index = error.find('debug_error_string =') - 3 error_msg = error[details_index:debug_error_index] return Response({'error': 'Failed to get pending channels! Error: ' + error_msg}) + +@api_view(['POST']) +@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED) +def bump_fee(request): + serializer = BumpFeeSerializer(data=request.data) + if serializer.is_valid(): + txid = serializer.validated_data['txid'] + index = serializer.validated_data['index'] + target_fee = serializer.validated_data['target_fee'] + force = serializer.validated_data['force'] + try: + target_outpoint = ln.OutPoint() + target_outpoint.txid_str = txid + target_outpoint.output_index = index + stub = walletstub.WalletKitStub(lnd_connect()) + stub.BumpFee(walletrpc.BumpFeeRequest(outpoint=target_outpoint, sat_per_vbyte=target_fee, force=force)) + return Response({'message': f'Fee bumped to {target_fee} sats/vbyte for outpoint: {txid}:{index}'}) + 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] + return Response({'error': f'Fee bump failed! Error: {error_msg}'}) + else: + return Response({'error': 'Invalid request!'}) \ No newline at end of file diff --git a/htlc_stream.py b/htlc_stream.py index 525d6b9b..fc9978ef 100644 --- a/htlc_stream.py +++ b/htlc_stream.py @@ -12,6 +12,7 @@ def main(): try: connection = lnd_connect() routerstub = lnrouter.RouterStub(connection) + all_forwards = {} for response in routerstub.SubscribeHtlcEvents(lnr.SubscribeHtlcEventsRequest()): if response.event_type == 3 and str(response.link_fail_event) != '': in_chan_id = response.incoming_channel_id @@ -20,13 +21,40 @@ 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_liq = (out_chan.local_balance - out_chan.local_chan_reserve) 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 = (response.link_fail_event.info.incoming_amt_msat - response.link_fail_event.info.outgoing_amt_msat)/1000 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() + elif response.event_type == 3 and str(response.forward_event) != '': + # Add forward_event + key = str(response.incoming_channel_id) + str(response.outgoing_channel_id) + str(response.incoming_htlc_id) + str(response.outgoing_htlc_id) + all_forwards[key] = response.forward_event + elif response.event_type == 3 and str(response.settle_event) != '': + # Delete forward_event + key = str(response.incoming_channel_id) + str(response.outgoing_channel_id) + str(response.incoming_htlc_id) + str(response.outgoing_htlc_id) + if key in all_forwards.keys(): + del all_forwards[key] + elif response.event_type == 3 and str(response.forward_fail_event) == '': + key = str(response.incoming_channel_id) + str(response.outgoing_channel_id) + str(response.incoming_htlc_id) + str(response.outgoing_htlc_id) + if key in all_forwards.keys(): + forward_event = all_forwards[key] + in_chan_id = response.incoming_channel_id + out_chan_id = response.outgoing_channel_id + in_chan = Channels.objects.filter(chan_id=in_chan_id)[0] if Channels.objects.filter(chan_id=in_chan_id).exists() else None + 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 - out_chan.local_chan_reserve) 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(forward_event.info.incoming_amt_msat/1000) + wire_failure = 99 + failure_detail = 99 + missed_fee = (forward_event.info.incoming_amt_msat - forward_event.info.outgoing_amt_msat)/1000 + 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() + del all_forwards[key] except Exception as e: print('Error while running failed HTLC stream: ' + str(e)) sleep(20) diff --git a/initialize.py b/initialize.py index 1a2adc6f..54dbacc6 100644 --- a/initialize.py +++ b/initialize.py @@ -75,6 +75,7 @@ def write_settings(node_ip, lnd_tls_path, lnd_macaroon_path, lnd_database_path, 'django.contrib.humanize', 'gui', 'rest_framework', + 'django_filters', 'qr_code', ] @@ -148,6 +149,7 @@ def write_settings(node_ip, lnd_tls_path, lnd_macaroon_path, lnd_database_path, 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ], + 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], } # Internationalization diff --git a/jobs.py b/jobs.py index 5066dd9f..ed48b8a4 100644 --- a/jobs.py +++ b/jobs.py @@ -1,5 +1,6 @@ import django -from django.db.models import Max +from django.db.models import Max, Sum, Avg, Count +from django.db.models.functions import TruncDay from datetime import datetime, timedelta from gui.lnd_deps import lightning_pb2 as ln from gui.lnd_deps import lightning_pb2_grpc as lnrpc @@ -12,7 +13,7 @@ from requests import get environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings' django.setup() -from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees, PendingChannels, Rebalancer, PeerEvents +from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees, PendingChannels, HistFailedHTLC, PeerEvents def update_payments(stub): self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey @@ -28,13 +29,13 @@ def update_payments(stub): last_index = Payments.objects.aggregate(Max('index'))['index__max'] if Payments.objects.exists() else 0 payments = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=last_index, max_payments=100)).payments for payment in payments: - #print (f"{datetime.now().strftime('%c')} : Processing New {payment.payment_index=} {payment.status=} {payment.payment_hash=}") + #print(f"{datetime.now().strftime('%c')} : Processing New {payment.payment_index=} {payment.status=} {payment.payment_hash=}") 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) new_payment.save() except Exception as e: #Error inserting, try to update instead - print (f"{datetime.now().strftime('%c')} : Error processing {new_payment=} : {str(e)=}") + print(f"{datetime.now().strftime('%c')} : Error processing {new_payment=} : {str(e)=}") update_payment(stub, payment, self_pubkey) def update_payment(stub, payment, self_pubkey): @@ -44,13 +45,13 @@ def update_payment(stub, payment, self_pubkey): db_payment.fee = round(payment.fee_msat/1000, 3) db_payment.status = payment.status db_payment.index = payment.payment_index - if payment.status == 2 or payment.status == 1 or payment.status == 3: + if payment.status == 2 or payment.status == 1: PaymentHops.objects.filter(payment_hash=db_payment).delete() db_payment.chan_out = None db_payment.rebal_chan = None db_payment.save() for attempt in payment.htlcs: - if attempt.status == 1 or attempt.status == 0 or attempt.status == 2: + if attempt.status == 1 or attempt.status == 0: hops = attempt.route.hops hop_count = 0 cost_to = 0 @@ -66,7 +67,7 @@ def update_payment(stub, payment, self_pubkey): # Add additional HTLC information in last hop alias alias += f'[ {payment.status}-{attempt.status}-{attempt.failure.code}-{attempt.failure.failure_source_index} ]' #if hop_count == total_hops: - #print (f"{datetime.now().strftime('%c')} : Debug Hop {attempt.attempt_id=} {attempt.route.total_amt=} {hop.mpp_record.payment_addr.hex()=} {hop.mpp_record.total_amt_msat=} {hop.amp_record=} {db_payment.payment_hash=}") + #print(f"{datetime.now().strftime('%c')} : Debug Hop {attempt.attempt_id=} {attempt.route.total_amt=} {hop.mpp_record.payment_addr.hex()=} {hop.mpp_record.total_amt_msat=} {hop.amp_record=} {db_payment.payment_hash=}") if attempt.status == 1 or attempt.status == 0 or (attempt.status == 2 and attempt.failure.code in (1,2,12)): PaymentHops(payment_hash=db_payment, attempt_id=attempt.attempt_id, step=hop_count, chan_id=hop.chan_id, alias=alias, chan_capacity=hop.chan_capacity, node_pubkey=hop.pub_key, amt=round(hop.amt_to_forward_msat/1000, 3), fee=round(fee, 3), cost_to=round(cost_to, 3)).save() cost_to += fee @@ -85,73 +86,11 @@ def update_payment(stub, payment, self_pubkey): if hop_count == total_hops and hop.pub_key == self_pubkey and db_payment.rebal_chan is None: db_payment.rebal_chan = hop.chan_id db_payment.save() - try: - adjust_ar_amt( payment, db_payment.rebal_chan ) - except Exception as e: - print (f"{datetime.now().strftime('%c')} : Error adjusting AR Amount {payment=} {db_payment.rebal_chan=} : {str(e)=}") - -def adjust_ar_amt( payment, chan_id ): - if payment.status not in (2,3): - return - #skip rapid fire rebalances - last_rebalance_duration = Rebalancer.objects.filter(payment_hash=payment.payment_hash)[0].duration if Rebalancer.objects.filter(payment_hash=payment.payment_hash).exists() else 0 - #print (f"{datetime.now().strftime('%c')} : DEBUG {last_rebalance_duration=} {payment.payment_hash=}") - if last_rebalance_duration <= 1 or payment.status not in (2,3): - print (f"{datetime.now().strftime('%c')} : Skipping Liquidity Estimation {last_rebalance_duration=} {payment.payment_hash=}") - return - #To be coverted to settings later - lower_limit = 69420 - upper_limit = 2 - - if LocalSettings.objects.filter(key='AR-Target%').exists(): - ar_target = float(LocalSettings.objects.filter(key='AR-Target%')[0].value) - else: - LocalSettings(key='AR-Target%', value='5').save() - ar_target = 5 - - #Adjust AR Target Amount, increase if success reduce if failed. - db_channel = Channels.objects.filter(chan_id = chan_id)[0] if Channels.objects.filter(chan_id = chan_id).exists() else None - if payment.status == 2 and chan_id is not None: - if db_channel is not None and payment.value_msat/1000 > 1000 : - new_ar_amount = int(min(max(db_channel.ar_amt_target * 1.11, payment.value_msat/1000), db_channel.capacity*ar_target*upper_limit/100)) - if new_ar_amount > db_channel.ar_amt_target: - print (f"{datetime.now().strftime('%c')} : Increase AR Target Amount {chan_id=} {db_channel.alias=} {db_channel.ar_amt_target=} {new_ar_amount=}") - db_channel.ar_amt_target = new_ar_amount - db_channel.save() - - if payment.status == 3: - estimated_liquidity = 0 - attempt = None - for attempt in payment.htlcs: - total_hops=len(attempt.route.hops) - #Failure Codes https://github.com/lightningnetwork/lnd/blob/9f013f5058a7780075bca393acfa97aa0daec6a0/lnrpc/lightning.proto#L4200 - if (attempt.failure.code in (1,2) and attempt.failure.failure_source_index == total_hops) or attempt.failure.code == 12: - #Failure 1,2 from last hop indicating liquidity available, failure 12 shows fees in sufficient but liquidity available - estimated_liquidity += attempt.route.total_amt - chan_id=attempt.route.hops[len(attempt.route.hops)-1].chan_id - print (f"{datetime.now().strftime('%c')} : Liquidity Estimation {attempt.attempt_id=} {attempt.status=} {attempt.failure.code=} {chan_id=} {attempt.route.total_amt=} {payment.value_msat/1000=} {estimated_liquidity=} {payment.payment_hash=}") - - if estimated_liquidity == 0: - if attempt is not None: - #Could not estimate liquidity for valid attempts, reduce by half - estimated_liquidity = db_channel.ar_amt_target/2 if db_channel is not None else 0 - print (f"{datetime.now().strftime('%c')} : Liquidity Estimation not possible, halving {attempt.attempt_id=} {attempt.status=} {attempt.failure.code=} {chan_id=} {attempt.route.total_amt=} {payment.value_msat/1000=} {estimated_liquidity=} {payment.payment_hash=}") - else: - #Mostly a case of NO ROUTE - print (f"{datetime.now().strftime('%c')} : Liquidity Estimation not performed {payment.payment_hash=} {payment.status=} {chan_id=} {estimated_liquidity=} {attempt=}") - - if payment.value_msat/1000 >= lower_limit and estimated_liquidity <= payment.value_msat/1000 and estimated_liquidity > 0: - #Change AR amount. Ignore zero liquidity case which implies breakout from rapid fire AR - new_ar_amount = int(estimated_liquidity if estimated_liquidity > lower_limit else lower_limit) - if db_channel is not None and new_ar_amount < db_channel.ar_amt_target: - print (f"{datetime.now().strftime('%c')} : Decrease AR Target Amount {chan_id=} {db_channel.alias=} {db_channel.ar_amt_target=} {new_ar_amount=}") - db_channel.ar_amt_target = new_ar_amount - db_channel.save() def update_invoices(stub): open_invoices = Invoices.objects.filter(state=0).order_by('index') for open_invoice in open_invoices: - #print (f"{datetime.now().strftime('%c')} : Processing open invoice {open_invoice.index=} {open_invoice.state=} {open_invoice.r_hash=}") + #print(f"{datetime.now().strftime('%c')} : Processing open invoice {open_invoice.index=} {open_invoice.state=} {open_invoice.r_hash=}") invoice_data = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=open_invoice.index-1, num_max_invoices=1)).invoices if len(invoice_data) > 0 and open_invoice.r_hash == invoice_data[0].r_hash.hex(): update_invoice(stub, invoice_data[0], open_invoice) @@ -179,7 +118,7 @@ def update_invoice(stub, invoice, db_invoice): 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()) + print(f"{datetime.now().strftime('%c')} : Unable to validate signature on invoice: {invoice.r_hash.hex()}") valid = False sender = records[34349339].hex() if valid == True else None try: @@ -391,7 +330,7 @@ def update_channels(stub): db_channel.auto_fees = pending_channel.auto_fees pending_channel.delete() if old_fee_rate is not None and old_fee_rate != local_policy.fee_rate_milli_msat: - print (f"{datetime.now().strftime('%c')} : Ext Fee Change Detected {db_channel.chan_id=} {db_channel.alias=} {old_fee_rate=} {db_channel.local_fee_rate=}") + print(f"{datetime.now().strftime('%c')} : Ext Fee Change Detected {db_channel.chan_id=} {db_channel.alias=} {old_fee_rate=} {db_channel.local_fee_rate=}") #External Fee change detected, update auto fee log Autofees(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, setting=(f"Ext"), old_value=old_fee_rate, new_value=db_channel.local_fee_rate).save() db_channel.save() @@ -483,7 +422,7 @@ def update_closures(stub): try: db_closure.save() except Exception as e: - print('Error inserting closure:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error inserting closure: {str(e)}") Closures.objects.filter(funding_txid=txid,funding_index=index).delete() return if resolution_count > 0: @@ -503,38 +442,36 @@ def reconnect_peers(stub): if peers.filter(pubkey=inactive_peer).exists(): peer = peers.filter(pubkey=inactive_peer)[0] if peer.last_reconnected == None or (int((datetime.now() - peer.last_reconnected).total_seconds() / 60) > 2): - print (f"{datetime.now().strftime('%c')} : Reconnecting {peer.alias=} {peer.pubkey=} {peer.last_reconnected=}") + print(f"{datetime.now().strftime('%c')} : Reconnecting peer {peer.alias} {peer.pubkey=} {peer.last_reconnected=}") if peer.connected == True: - print (f"{datetime.now().strftime('%c')} : ... Inactive channel is still connected to peer, disconnecting peer. {peer.alias=} {inactive_peer=}") + print(f"{datetime.now().strftime('%c')} : ... Inactive channel is still connected to peer, disconnecting peer. {peer.alias=} {inactive_peer=}") try: response = stub.DisconnectPeer(ln.DisconnectPeerRequest(pub_key=inactive_peer)) - print (f"{datetime.now().strftime('%c')} : .... Status Disconnect {peer.alias=} {inactive_peer=} {response=}") + print(f"{datetime.now().strftime('%c')} : .... Disconnected peer {peer.alias} {inactive_peer=} {response=}") peer.connected = False peer.save() except Exception as e: - print (f"{datetime.now().strftime('%c')} : .... Error disconnecting {peer.alias} {inactive_peer=} {str(e)=}") + print(f"{datetime.now().strftime('%c')} : .... Error disconnecting peer {peer.alias} {inactive_peer=} {str(e)=}") try: node = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=inactive_peer, include_channels=False)).node host = node.addresses[0].addr except Exception as e: - print (f"{datetime.now().strftime('%c')} : ... Unable to find node info on graph, using last known value {peer.alias=} {peer.pubkey=} {peer.address=} {str(e)=}") + print(f"{datetime.now().strftime('%c')} : ... Unable to find node info on graph, using last known value for {peer.alias} {peer.pubkey=} {peer.address=} {str(e)=}") host = peer.address #address = ln.LightningAddress(pubkey=inactive_peer, host=host) - print (f"{datetime.now().strftime('%c')} : ... Attempting connection to {peer.alias=} {inactive_peer=} {host=}") + print(f"{datetime.now().strftime('%c')} : ... Attempting connection to {peer.alias} {inactive_peer=} {host=}") try: #try both the graph value and last know value stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=ln.LightningAddress(pubkey=inactive_peer, host=host), perm=True, timeout=5)) if host != peer.address and peer.address[:9] != '127.0.0.1': stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=ln.LightningAddress(pubkey=inactive_peer, host=peer.address), perm=True, timeout=5)) - #response = stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=False, timeout=5)) - #print (f"{datetime.now().strftime('%c')} : .... Status {peer.alias=} {inactive_peer=} {response=}") except Exception as e: error = str(e) details_index = error.find('details =') + 11 debug_error_index = error.find('debug_error_string =') - 3 error_msg = error[details_index:debug_error_index] - print (f"{datetime.now().strftime('%c')} : .... Error reconnecting {peer.alias} {inactive_peer=} {error_msg=}") + print(f"{datetime.now().strftime('%c')} : .... Error reconnecting {peer.alias} {inactive_peer=} {error_msg=}") peer.last_reconnected = datetime.now() peer.save() @@ -563,11 +500,10 @@ def clean_payments(stub): details_index = error.find('details =') + 11 debug_error_index = error.find('debug_error_string =') - 3 error_msg = error[details_index:debug_error_index] - print (f"{datetime.now().strftime('%c')} : Error {payment.index=} {payment.status=} {payment.payment_hash=} {error_msg=}") + print(f"{datetime.now().strftime('%c')} : Error {payment.index=} {payment.status=} {payment.payment_hash=} {error_msg=}") finally: payment.cleaned = True payment.save() - #print (f"{datetime.now().strftime('%c')} : Cleaned {payment.index=} {payment.status=} {payment.cleaned=} {payment.payment_hash=}") def auto_fees(stub): if LocalSettings.objects.filter(key='AF-Enabled').exists(): @@ -665,7 +601,7 @@ def auto_fees(stub): update_df = channels_df[channels_df['adjustment']!=0] if not update_df.empty: for target_channel in update_df.to_dict(orient='records'): - print('Updating fees for channel ' + str(target_channel['chan_id']) + ' to a value of: ' + str(target_channel['new_rate'])) + print(f"{datetime.now().strftime('%c')} : Updating fees for channel {str(target_channel['chan_id'])} to a value of: {str(target_channel['new_rate'])}") channel = Channels.objects.filter(chan_id=target_channel['chan_id'])[0] channel_point = ln.ChannelPoint() channel_point.funding_txid_bytes = bytes.fromhex(channel.funding_txid) @@ -677,8 +613,44 @@ def auto_fees(stub): channel.save() Autofees(chan_id=channel.chan_id, peer_alias=channel.alias, setting=(f"AF [ {target_channel['net_routed_7day']}:{target_channel['in_percent']}:{target_channel['out_percent']} ]"), old_value=target_channel['local_fee_rate'], new_value=target_channel['new_rate']).save() +def agg_htlcs(target_htlcs, category): + try: + target_ids = target_htlcs.values_list('id') + agg_htlcs = FailedHTLCs.objects.filter(id__in=target_ids).annotate(day=TruncDay('timestamp')).values('day', 'chan_id_in', 'chan_id_out').annotate(amount=Sum('amount'), fee=Sum('missed_fee'), liq=Avg('chan_out_liq'), pending=Avg('chan_out_pending'), count=Count('id'), chan_in_alias=Max('chan_in_alias'), chan_out_alias=Max('chan_out_alias')) + for htlc in agg_htlcs: + if HistFailedHTLC.objects.filter(date=htlc['day'],chan_id_in=htlc['chan_id_in'],chan_id_out=htlc['chan_id_out']).exists(): + htlc_itm = HistFailedHTLC.objects.filter(date=htlc['day'],chan_id_in=htlc['chan_id_in'],chan_id_out=htlc['chan_id_out']).get() + else: + htlc_itm = HistFailedHTLC(htlc_count=0, amount_sum=0, fee_sum=0, liq_avg=0, pending_avg=0, balance_count=0, downstream_count=0, other_count=0) + htlc_itm.date = htlc['day'] + htlc_itm.chan_id_in = htlc['chan_id_in'] + htlc_itm.chan_id_out = htlc['chan_id_out'] + htlc_itm.chan_in_alias = htlc['chan_in_alias'] + htlc_itm.chan_out_alias = htlc['chan_out_alias'] + htlc_itm.htlc_count += htlc['count'] + htlc_itm.amount_sum += htlc['amount'] + htlc_itm.fee_sum += htlc['fee'] + htlc_itm.liq_avg += (htlc['count']/htlc_itm.htlc_count)*((0 if htlc['liq'] is None else htlc['liq'])-htlc_itm.liq_avg) + htlc_itm.pending_avg += (htlc['count']/htlc_itm.htlc_count)*((0 if htlc['pending'] is None else htlc['pending'])-htlc_itm.pending_avg) + if category == 'balance': + htlc_itm.balance_count += htlc['count'] + elif category == 'downstream': + htlc_itm.downstream_count += htlc['count'] + elif category == 'other': + htlc_itm.other_count += htlc['count'] + htlc_itm.save() + FailedHTLCs.objects.filter(id__in=target_ids, chan_id_in=htlc['chan_id_in'], chan_id_out=htlc['chan_id_out']).annotate(day=TruncDay('timestamp')).filter(day=htlc['day']).delete() + except Exception as e: + print(f"{datetime.now().strftime('%c')} : Error processing background data: {str(e)}") + +def agg_failed_htlcs(): + time_filter = datetime.now() - timedelta(days=30) + agg_htlcs(FailedHTLCs.objects.filter(timestamp__lte=time_filter, failure_detail=6)[:100], 'balance') + agg_htlcs(FailedHTLCs.objects.filter(timestamp__lte=time_filter, failure_detail=99)[:100], 'downstream') + agg_htlcs(FailedHTLCs.objects.filter(timestamp__lte=time_filter).exclude(failure_detail__in=[6, 99])[:100], 'other') + + def main(): - #print (f"{datetime.now().strftime('%c')} : Entering Jobs") try: stub = lnrpc.LightningStub(lnd_connect()) #Update data @@ -692,8 +664,8 @@ def main(): reconnect_peers(stub) clean_payments(stub) auto_fees(stub) + agg_failed_htlcs() except Exception as e: - print (f"{datetime.now().strftime('%c')} : Error processing background data: {str(e)=}") - #print (f"{datetime.now().strftime('%c')} : Exit Jobs") + print(f"{datetime.now().strftime('%c')} : Error processing background data: {str(e)}") if __name__ == '__main__': main() diff --git a/rebalancer.py b/rebalancer.py index bc3466b9..5b99f34a 100644 --- a/rebalancer.py +++ b/rebalancer.py @@ -8,6 +8,8 @@ from gui.lnd_deps import router_pb2_grpc as lnrouter from gui.lnd_deps.lnd_connect import lnd_connect, async_lnd_connect from os import environ +from typing import List + environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings' django.setup() from gui.models import Rebalancer, Channels, LocalSettings, Forwards, Autopilot @@ -17,28 +19,33 @@ def get_out_cans(rebalance, auto_rebalance_channels): try: return list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).exclude(remote_pubkey=rebalance.last_hop_pubkey).values_list('chan_id', flat=True)) except Exception as e: - print(datetime.now(), 'Error getting outbound cands:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error getting outbound cands: {str(e)}") @sync_to_async def save_record(record): try: record.save() except Exception as e: - print(datetime.now(), 'Error saving database record:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error saving database record: {str(e)}") @sync_to_async def inbound_cans_len(inbound_cans): try: return len(inbound_cans) except Exception as e: - print(datetime.now(), 'Error getting inbound cands:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error getting inbound cands: {str(e)}") async def run_rebalancer(rebalance, worker): try: - 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')) + #Reduce potential rebalance value in percent out to avoid going below AR-OUT-Target + auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound')-rebalance.value)*100)/Sum('capacity')).annotate(inbound_can=(((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity'))/Sum('ar_in_target')) outbound_cans = await get_out_cans(rebalance, auto_rebalance_channels) if len(outbound_cans) == 0 and rebalance.manual == False: - print(datetime.now(), 'No outbound_cans') + print(f"{datetime.now().strftime('%c')} : No outbound_cans") + rebalance.status = 406 + rebalance.start = datetime.now() + rebalance.stop = datetime.now() + await save_record(rebalance) return None elif str(outbound_cans).replace('\'', '') != rebalance.outgoing_chan_ids and rebalance.manual == False: rebalance.outgoing_chan_ids = str(outbound_cans).replace('\'', '') @@ -50,9 +57,9 @@ async def run_rebalancer(rebalance, worker): chan_ids = json.loads(rebalance.outgoing_chan_ids) timeout = rebalance.duration * 60 invoice_response = stub.AddInvoice(ln.Invoice(value=rebalance.value, expiry=timeout)) - print(datetime.now(), worker, 'starting rebalance for:', rebalance.target_alias, ':', rebalance.last_hop_pubkey, 'Amount:', rebalance.value, 'Duration:', rebalance.duration, 'via:', chan_ids ) + print(f"{datetime.now().strftime('%c')} : {worker} starting rebalance for: {rebalance.target_alias} {rebalance.last_hop_pubkey=} {rebalance.value=} {rebalance.duration=} {chan_ids=}") async for payment_response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(payment_request=str(invoice_response.payment_request), fee_limit_msat=int(rebalance.fee_limit*1000), outgoing_chan_ids=chan_ids, last_hop_pubkey=bytes.fromhex(rebalance.last_hop_pubkey), timeout_seconds=(timeout-5), allow_self_payment=True), timeout=(timeout+60)): - print (datetime.now(), worker, 'got a payment response:', payment_response.status, 'with reason:', payment_response.failure_reason, 'for payment hash:', payment_response.payment_hash) + #print(f"{datetime.now().strftime('%c')} : DEBUG {worker} got a payment response: {payment_response.status=} {payment_response.failure_reason=} {payment_response.payment_hash=}") if payment_response.status == 1 and rebalance.status == 0: #IN-FLIGHT rebalance.payment_hash = payment_response.payment_hash @@ -87,39 +94,71 @@ async def run_rebalancer(rebalance, worker): rebalance.status = 408 else: rebalance.status = 400 - print(datetime.now(), 'Error while sending payment:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error while sending payment: {str(e)}") finally: rebalance.stop = datetime.now() await save_record(rebalance) - print(datetime.now(), worker, 'completed payment attempts for:', rebalance.payment_hash) + print(f"{datetime.now().strftime('%c')} : {worker} completed payment attempts for: {rebalance.payment_hash=}") original_alias = rebalance.target_alias inc=1.21 dec=2 if rebalance.status ==2: await update_channels(stub, rebalance.last_hop_pubkey, successful_out) - 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')) + #Reduce potential rebalance value in percent out to avoid going below AR-OUT-Target + auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound')-rebalance.value*inc)*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) outbound_cans = await get_out_cans(rebalance, auto_rebalance_channels) if await inbound_cans_len(inbound_cans) > 0 and len(outbound_cans) > 0: next_rebalance = Rebalancer(value=int(rebalance.value*inc), fee_limit=round(rebalance.fee_limit*inc, 3), outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=rebalance.last_hop_pubkey, target_alias=original_alias, duration=1) await save_record(next_rebalance) - print (f"{datetime.now()} RapidFire up {next_rebalance.target_alias=} {next_rebalance.value=} {rebalance.value=}") + print(f"{datetime.now().strftime('%c')} : RapidFire up {next_rebalance.target_alias=} {next_rebalance.value=} {rebalance.value=}") else: next_rebalance = None - elif rebalance.status > 2 and rebalance.duration <= 1 and rebalance.value > 69420: + # For failed rebalances, try in rapid fire with reduced balances until give up. + elif rebalance.status > 2 and rebalance.value > 69420: #Previous Rapidfire with increased value failed, try with lower value up to 69420. + if rebalance.duration > 1: + next_value = await estimate_liquidity ( payment_response ) + if next_value < 1000: + next_rebalance = None + return next_rebalance + else: + next_value = rebalance.value/dec + inbound_cans = auto_rebalance_channels.filter(remote_pubkey=rebalance.last_hop_pubkey).filter(auto_rebalance=True, inbound_can__gte=1) if await inbound_cans_len(inbound_cans) > 0 and len(outbound_cans) > 0: - next_rebalance = Rebalancer(value=int(rebalance.value/dec), fee_limit=round(rebalance.fee_limit/dec, 3), outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=rebalance.last_hop_pubkey, target_alias=original_alias, duration=1) + next_rebalance = Rebalancer(value=int(next_value), fee_limit=round(rebalance.fee_limit/(rebalance.value/next_value), 3), outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=rebalance.last_hop_pubkey, target_alias=original_alias, duration=1) await save_record(next_rebalance) - print (f"{datetime.now()} RapidFire Down {next_rebalance.target_alias=} {next_rebalance.value=} {rebalance.value=}") + print(f"{datetime.now().strftime('%c')} : RapidFire Down {next_rebalance.target_alias=} {next_rebalance.value=} {rebalance.value=}") else: next_rebalance = None else: next_rebalance = None return next_rebalance except Exception as e: - print(datetime.now(), 'Error running rebalance attempt:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error running rebalance attempt: {str(e)}") + +@sync_to_async +def estimate_liquidity( payment ): + try: + estimated_liquidity = 0 + if payment.status == 3: + attempt = None + for attempt in payment.htlcs: + total_hops=len(attempt.route.hops) + #Failure Codes https://github.com/lightningnetwork/lnd/blob/9f013f5058a7780075bca393acfa97aa0daec6a0/lnrpc/lightning.proto#L4200 + #print(f"{datetime.now().strftime('%c')} : DEBUG Liquidity Estimation {attempt.attempt_id=} {attempt.status=} {attempt.failure.code=} {attempt.failure.failure_source_index=} {total_hops=} {attempt.route.total_amt=} {payment.value_msat/1000=} {estimated_liquidity=} {payment.payment_hash=}") + if attempt.failure.failure_source_index == total_hops: + #Failure from last hop indicating liquidity available + estimated_liquidity = attempt.route.total_amt if attempt.route.total_amt > estimated_liquidity else estimated_liquidity + chan_id=attempt.route.hops[len(attempt.route.hops)-1].chan_id + #print(f"{datetime.now().strftime('%c')} : DEBUG Liquidity Estimation {attempt.attempt_id=} {attempt.status=} {attempt.failure.code=} {chan_id=} {attempt.route.total_amt=} {payment.value_msat/1000=} {estimated_liquidity=} {payment.payment_hash=}") + print(f"{datetime.now().strftime('%c')} : Estimated Liquidity {estimated_liquidity=} {payment.payment_hash=} {payment.status=} {payment.failure_reason=}") + except Exception as e: + print(f"{datetime.now().strftime('%c')} : Error estimating liquidity: {str(e)}") + estimated_liquidity = 0 + + return estimated_liquidity @sync_to_async def update_channels(stub, incoming_channel, outgoing_channel): @@ -137,10 +176,10 @@ def update_channels(stub, incoming_channel, outgoing_channel): db_channel.remote_balance = channel.remote_balance db_channel.save() except Exception as e: - print(datetime.now(), 'Error updating channel balances:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error updating channel balances: {str(e)}") @sync_to_async -def auto_schedule(): +def auto_schedule() -> List[Rebalancer]: try: #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 if LocalSettings.objects.filter(key='AR-Enabled').exists(): @@ -148,64 +187,72 @@ def auto_schedule(): else: LocalSettings(key='AR-Enabled', value='0').save() enabled = 0 - scheduled_ids = [] - if enabled == 1: - 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='75').save() - if not LocalSettings.objects.filter(key='AR-Inbound%').exists(): - LocalSettings(key='AR-Inbound%', value='100').save() - outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True)) - already_scheduled = Rebalancer.objects.exclude(last_hop_pubkey='').filter(status=0).values_list('last_hop_pubkey') - inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1).exclude(remote_pubkey__in=already_scheduled).order_by('-remote_balance') - if len(inbound_cans) > 0 and len(outbound_cans) > 0: - if LocalSettings.objects.filter(key='AR-MaxFeeRate').exists(): - max_fee_rate = int(LocalSettings.objects.filter(key='AR-MaxFeeRate')[0].value) - else: - LocalSettings(key='AR-MaxFeeRate', value='100').save() - max_fee_rate = 100 - if LocalSettings.objects.filter(key='AR-Variance').exists(): - variance = int(LocalSettings.objects.filter(key='AR-Variance')[0].value) - else: - LocalSettings(key='AR-Variance', value='0').save() - variance = 0 - if LocalSettings.objects.filter(key='AR-WaitPeriod').exists(): - wait_period = int(LocalSettings.objects.filter(key='AR-WaitPeriod')[0].value) - else: - LocalSettings(key='AR-WaitPeriod', value='30').save() - wait_period = 30 - if not LocalSettings.objects.filter(key='AR-Target%').exists(): - LocalSettings(key='AR-Target%', value='5').save() - if not LocalSettings.objects.filter(key='AR-MaxCost%').exists(): - LocalSettings(key='AR-MaxCost%', value='65').save() - for target in inbound_cans: - target_fee_rate = int(target.local_fee_rate * (target.ar_max_cost/100)) - if target_fee_rate > 0 and target_fee_rate > target.remote_fee_rate: - target_value = int(target.ar_amt_target+(target.ar_amt_target*((secrets.choice(range(-1000,1001))/1000)*variance/100))) - target_fee = round(target_fee_rate*target_value*0.000001, 3) if target_fee_rate <= max_fee_rate else round(max_fee_rate*target_value*0.000001, 3) - if 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 - # TLDR: willing to pay 1 sat for every value_per_fee sats moved - 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.status == 2 or (last_rebalance.status in [3, 4, 5, 6, 7, 400, 408] and (int((datetime.now() - last_rebalance.stop).total_seconds() / 60) > wait_period)) or (last_rebalance.status == 1 and (int((datetime.now() - last_rebalance.start).total_seconds() / 60) > wait_period))): - continue - print(datetime.now(), 'Creating Auto Rebalance Request for:', target.chan_id) - print(datetime.now(), 'Request routing through:', outbound_cans) - print(datetime.now(), 'Target Value:', target_value, '/', target.ar_amt_target) - print(datetime.now(), 'Target Fee:', target_fee) - print(datetime.now(), 'Target Time:', target_time) - new_rebalance = 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) - new_rebalance.save() - scheduled_ids.append(new_rebalance.id) - return scheduled_ids + if enabled == 0: + return [] + + 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: + return [] + + if not LocalSettings.objects.filter(key='AR-Outbound%').exists(): + LocalSettings(key='AR-Outbound%', value='75').save() + if not LocalSettings.objects.filter(key='AR-Inbound%').exists(): + LocalSettings(key='AR-Inbound%', value='100').save() + outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True)) + already_scheduled = Rebalancer.objects.exclude(last_hop_pubkey='').filter(status=0).values_list('last_hop_pubkey') + inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1).exclude(remote_pubkey__in=already_scheduled).order_by('-inbound_can') + if len(inbound_cans) == 0 or len(outbound_cans) == 0: + return [] + + if LocalSettings.objects.filter(key='AR-MaxFeeRate').exists(): + max_fee_rate = int(LocalSettings.objects.filter(key='AR-MaxFeeRate')[0].value) + else: + LocalSettings(key='AR-MaxFeeRate', value='100').save() + max_fee_rate = 100 + if LocalSettings.objects.filter(key='AR-Variance').exists(): + variance = int(LocalSettings.objects.filter(key='AR-Variance')[0].value) + else: + LocalSettings(key='AR-Variance', value='0').save() + variance = 0 + if LocalSettings.objects.filter(key='AR-WaitPeriod').exists(): + wait_period = int(LocalSettings.objects.filter(key='AR-WaitPeriod')[0].value) + else: + LocalSettings(key='AR-WaitPeriod', value='30').save() + wait_period = 30 + if not LocalSettings.objects.filter(key='AR-Target%').exists(): + LocalSettings(key='AR-Target%', value='5').save() + if not LocalSettings.objects.filter(key='AR-MaxCost%').exists(): + LocalSettings(key='AR-MaxCost%', value='65').save() + to_schedule = [] + for target in inbound_cans: + target_fee_rate = int(target.local_fee_rate * (target.ar_max_cost/100)) + if target_fee_rate > 0 and target_fee_rate > target.remote_fee_rate: + target_value = int(target.ar_amt_target+(target.ar_amt_target*((secrets.choice(range(-1000,1001))/1000)*variance/100))) + target_fee = round(target_fee_rate*target_value*0.000001, 3) if target_fee_rate <= max_fee_rate else round(max_fee_rate*target_value*0.000001, 3) + if target_fee == 0: + return [] + + 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 + # TLDR: willing to pay 1 sat for every value_per_fee sats moved + 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.status == 2 or (last_rebalance.status in [3, 4, 5, 6, 7, 400, 408, 499] and (int((datetime.now() - last_rebalance.stop).total_seconds() / 60) > wait_period)) or (last_rebalance.status == 1 and (int((datetime.now() - last_rebalance.start).total_seconds() / 60) > wait_period))): + continue + print(f"{datetime.now().strftime('%c')} : Creating Auto Rebalance Request for: {target.chan_id}") + print(f"{datetime.now().strftime('%c')} : Request routing through: {outbound_cans}") + print(f"{datetime.now().strftime('%c')} : {target_value} / {target.ar_amt_target}") + print(f"{datetime.now().strftime('%c')} : {target_fee}") + print(f"{datetime.now().strftime('%c')} : {target_time}") + new_rebalance = 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) + new_rebalance.save() + to_schedule.append(new_rebalance) + return to_schedule except Exception as e: - print(datetime.now(), 'Error scheduling rebalances:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error scheduling rebalances: {str(e)}") @sync_to_async def auto_enable(): @@ -237,7 +284,7 @@ def auto_enable(): #print('Processing: ', peer_channel.alias, ' : ', peer_channel.chan_id, ' : ', oapD, " : ", iapD, ' : ', outbound_percent, ' : ', inbound_percent) if peer_channel.ar_out_target == 100 and peer_channel.auto_rebalance == True: #Special Case for LOOP, Wos, etc. Always Auto Rebalance if enabled to keep outbound full. - print (f"{datetime.now()} Skipping AR enabled and 100% oTarget channel... {peer_channel.alias=} {peer_channel.chan_id=}") + print(f"{datetime.now().strftime('%c')} : Skipping AR enabled and 100% oTarget channel... {peer_channel.alias=} {peer_channel.chan_id=}") pass elif oapD > (iapD*1.10) and outbound_percent > 75: #print('Case 1: Pass') @@ -247,13 +294,13 @@ def auto_enable(): peer_channel.auto_rebalance = True peer_channel.save() Autopilot(chan_id=peer_channel.chan_id, peer_alias=peer_channel.alias, setting='Enabled', old_value=0, new_value=1).save() - print(datetime.now(), 'Auto Pilot Enabled: ', peer_channel.alias, ' : ', peer_channel.chan_id , ' Out: ', oapD, ' In: ', iapD) + print(f"{datetime.now().strftime('%c')} : Auto Pilot Enabled: {peer_channel.alias=} {peer_channel.chan_id=} {oapD=} {iapD=}") elif oapD < (iapD*1.10) and outbound_percent > 75 and peer_channel.auto_rebalance == True: #print('Case 3: Disable AR - o7D < i7D AND Outbound Liq > 75%') peer_channel.auto_rebalance = False peer_channel.save() Autopilot(chan_id=peer_channel.chan_id, peer_alias=peer_channel.alias, setting='Enabled', old_value=1, new_value=0).save() - print(datetime.now(), 'Auto Pilot Disabled (3): ', peer_channel.alias, ' : ', peer_channel.chan_id, ' Out: ', oapD, ' In: ', iapD ) + print(f"{datetime.now().strftime('%c')} : Auto Pilot Disabled (3): {peer_channel.alias=} {peer_channel.chan_id=} {oapD=} {iapD=}" ) elif oapD < (iapD*1.10) and inbound_percent > 75: #print('Case 4: Pass') pass @@ -261,14 +308,7 @@ def auto_enable(): #print('Case 5: Pass') pass except Exception as e: - print(datetime.now(), 'Error during auto channel enabling:', str(e)) - -@sync_to_async -def get_scheduled_rebal(id): - try: - return Rebalancer.objects.get(id=id) - except Exception as e: - print(datetime.now(), 'Error getting scheduled rebalances:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error during auto channel enabling: {str(e)}") @sync_to_async def get_pending_rebals(): @@ -276,46 +316,45 @@ def get_pending_rebals(): rebalances = Rebalancer.objects.filter(status=0).order_by('id') return rebalances, len(rebalances) except Exception as e: - print(datetime.now(), 'Error getting pending rebalances:', str(e)) + print(f"{datetime.now().strftime('%c')} : Error getting pending rebalances: {str(e)}") shutdown_rebalancer = False active_rebalances = [] async def async_queue_manager(rebalancer_queue): - print(datetime.now(), 'Queue manager is starting...') + print(f"{datetime.now().strftime('%c')} : Queue manager is starting...") pending_rebalances, rebal_count = await get_pending_rebals() if rebal_count > 0: for rebalance in pending_rebalances: await rebalancer_queue.put(rebalance) - while True: - try: + try: + while True: global active_rebalances - print(datetime.now(), 'Queue currently has', rebalancer_queue.qsize(), 'items...') - print(datetime.now(), 'There are currently', len(active_rebalances), 'tasks in progress...') - print(datetime.now(), 'Queue manager is checking for more work...') + print(f"{datetime.now().strftime('%c')} : Queue currently has {rebalancer_queue.qsize()} items...") + print(f"{datetime.now().strftime('%c')} : There are currently {len(active_rebalances)} tasks in progress...") + print(f"{datetime.now().strftime('%c')} : Queue manager is checking for more work...") await auto_enable() - scheduled_ids = await auto_schedule() - if len(scheduled_ids) > 0: - print(datetime.now(), 'Scheduling', len(scheduled_ids), 'more jobs...') - for id in scheduled_ids: - scheduled_rebal = await get_scheduled_rebal(id) - await rebalancer_queue.put(scheduled_rebal) + scheduled = await auto_schedule() + if len(scheduled) > 0: + print(f"{datetime.now().strftime('%c')} : Scheduling {len(scheduled)} more jobs...") + for rebalance in scheduled: + await rebalancer_queue.put(rebalance) elif rebalancer_queue.qsize() == 0 and len(active_rebalances) == 0: - print(datetime.now(), 'Queue is still empty, stoping the rebalancer...') + print(f"{datetime.now().strftime('%c')} : Queue is still empty, stoping the rebalancer...") global shutdown_rebalancer shutdown_rebalancer = True return await asyncio.sleep(30) - except Exception as e: - print(datetime.now(), 'Queue manager exception:', str(e)) - finally: - print(datetime.now(), 'Queue manager has shut down...') + except Exception as e: + print(f"{datetime.now().strftime('%c')} : Queue manager exception: {str(e)}") + finally: + print(f"{datetime.now().strftime('%c')} : Queue manager has shut down...") async def async_run_rebalancer(worker, rebalancer_queue): while True: global active_rebalances, shutdown_rebalancer if not rebalancer_queue.empty(): rebalance = await rebalancer_queue.get() - print(datetime.now(), worker + ' is starting a new request...') + print(f"{datetime.now().strftime('%c')} : {worker} is starting a new request...") active_rebalance_id = None if rebalance != None: active_rebalance_id = rebalance.id @@ -324,7 +363,7 @@ async def async_run_rebalancer(worker, rebalancer_queue): rebalance = await run_rebalancer(rebalance, worker) if active_rebalance_id != None: active_rebalances.remove(active_rebalance_id) - print(datetime.now(), worker + ' completed its request...') + print(f"{datetime.now().strftime('%c')} : {worker} completed its request...") else: if shutdown_rebalancer == True: return @@ -335,7 +374,7 @@ async def start_queue(worker_count=1): manager = asyncio.create_task(async_queue_manager(rebalancer_queue)) workers = [asyncio.create_task(async_run_rebalancer("Worker " + str(worker_num+1), rebalancer_queue)) for worker_num in range(worker_count)] await asyncio.gather(manager, *workers) - print(datetime.now(), 'Manager and workers have stopped...') + print(f"{datetime.now().strftime('%c')} : Manager and workers have stopped...") def main(): if Rebalancer.objects.filter(status=1).exists(): @@ -350,7 +389,7 @@ def main(): LocalSettings(key='AR-Workers', value='1').save() worker_count = 1 asyncio.run(start_queue(worker_count)) - print(datetime.now(), 'Rebalancer successfully shutdown...') + print(f"{datetime.now().strftime('%c')} : Rebalancer successfully shutdown...") if __name__ == '__main__': main() diff --git a/requirements.txt b/requirements.txt index da904434..5f1505c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ Django djangorestframework +django-filter django-qr-code grpcio protobuf