@@ -20,22 +20,30 @@ class Wallet:
2020 #last_known_balance_timestamp = 0
2121
2222 def __init__ (self ):
23- pass
23+ self . keep_running = True
2424
2525 def __str__ (self ):
2626 if isinstance (self , LNBitsWallet ):
2727 return "LNBitsWallet"
2828 elif isinstance (self , NWCWallet ):
2929 return "NWCWallet"
3030
31- def start_refresh_balance (self , balance_updated_cb ):
31+ # Need callbacks for:
32+ # - started (so the user can show the UI)
33+ # - stopped (so the user can delete/free it)
34+ # - error (so the user can show the error)
35+ # - balance
36+ # - transactions
37+ def start (self , balance_updated_cb ):
38+ self .keep_running = True
3239 _thread .stack_size (mpos .apps .good_stack_size ())
33- _thread .start_new_thread (self .fetch_balance_thread , (balance_updated_cb ,))
40+ _thread .start_new_thread (self .manage_wallet_thread , (balance_updated_cb ,))
3441
35- def destroy (self ):
36- # optional to inherit
37- pass
42+ def stop (self ):
43+ self .keep_running = False
3844
45+ def is_running (self ):
46+ return self .keep_running
3947
4048class LNBitsWallet (Wallet ):
4149
@@ -44,17 +52,28 @@ def __init__(self, lnbits_url, lnbits_readkey):
4452 self .lnbits_url = lnbits_url
4553 self .lnbits_readkey = lnbits_readkey
4654
47- def fetch_balance_thread (self , balance_updated_cb ):
48- print ("fetch_balance_thread" )
55+ def manage_wallet_thread (self , balance_updated_cb ):
56+ print ("manage_wallet_thread" )
57+ while self .keep_running :
58+ try :
59+ self .last_known_balance = fetch_balance ()
60+ balance_updated_cb ()
61+ # TODO: if the balance changed, then re-list transactions
62+ except Exception as e :
63+ print (f"WARNING: fetch_balance got exception { e } , ignorning." )
64+ print ("Sleeping a while before re-fetching balance..." )
65+ time .sleep (60 )
66+ print ("manage_wallet_thread stopping" )
67+
68+ def fetch_balance ():
4969 walleturl = self .lnbits_url + "/api/v1/wallet"
5070 headers = {
5171 "X-Api-Key" : self .lnbits_readkey ,
5272 }
5373 try :
5474 response = requests .get (walleturl , timeout = 10 , headers = headers )
5575 except Exception as e :
56- print ("GET request failed:" , e )
57- #lv.async_call(lambda l: please_wait_label.set_text(f"Error downloading app index: {e}"), None)
76+ print ("fetch_balance: get request failed:" , e )
5877 if response and response .status_code == 200 :
5978 response_text = response .text
6079 print (f"Got response text: { response_text } " )
@@ -63,42 +82,39 @@ def fetch_balance_thread(self, balance_updated_cb):
6382 balance_reply = json .loads (response_text )
6483 print (f"Got balance: { balance_reply ['balance' ]} " )
6584 balance_msat = balance_reply ['balance' ]
66- self .last_known_balance = round (balance_msat / 1000 )
67- #self.last_known_balance_timestamp = mpos.time.epoch_seconds()
68- balance_updated_cb ()
85+ return round (balance_msat / 1000 )
6986 except Exception as e :
7087 print (f"Could not parse reponse text '{ response_text } ' as JSON: { e } " )
88+ raise e
7189
7290
7391class NWCWallet (Wallet ):
7492
7593 def __init__ (self , nwc_url ):
7694 super ().__init__ ()
7795 self .nwc_url = nwc_url
78- self .relay , self .wallet_pubkey , self .secret , self .lud16 = self .parse_nwc_url (nwc_url )
96+ self .connected = False
97+
98+ def manage_wallet_thread (self , balance_updated_cb ):
99+ self .relay , self .wallet_pubkey , self .secret , self .lud16 = self .parse_nwc_url (self .nwc_url )
79100 self .private_key = PrivateKey (bytes .fromhex (self .secret ))
80101 self .relay_manager = RelayManager ()
81102 self .relay_manager .add_relay (self .relay )
82- self .connected = False
83-
84- def destroy (self ):
85- self .relay_manager .close_connections ()
86103
87- def fetch_balance_thread (self , balance_updated_cb ) :
88- # make sure connected to relay (otherwise connect)
89- if self .relay_manager .relays [self .relay ].connected != True :
90- print (f"DEBUG: Opening relay connections" )
91- self .relay_manager .open_connections ({"cert_reqs" : ssl .CERT_NONE })
92- self .connected = False
93- for _ in range (20 ):
94- time .sleep (0.5 )
95- if self .relay_manager .relays [self .relay ].connected is True :
96- self .connected = True
97- break
98- print ("Waiting for relay connection..." )
104+ print (f"DEBUG: Opening relay connections" )
105+ self .relay_manager .open_connections ({"cert_reqs" : ssl .CERT_NONE })
106+ self .connected = False
107+ for _ in range (20 ):
108+ time .sleep (0.5 )
109+ if self .relay_manager .relays [self .relay ].connected is True :
110+ self .connected = True
111+ break
112+ print ("Waiting for relay connection..." )
99113 if not self .connected :
100- print ("Failed to connect, aborting..." )
114+ print ("ERROR: could not connect to NWC relay {self.relay}, aborting..." )
115+ # TODO: call an error callback to notify the user
101116 return
117+
102118 # Set up subscription to receive response
103119 self .subscription_id = "nwc_balance_" + str (round (time .time ()))
104120 print (f"DEBUG: Setting up subscription with ID: { self .subscription_id } " )
@@ -110,6 +126,7 @@ def fetch_balance_thread(self, balance_updated_cb) :
110126 print (f"DEBUG: Subscription filters: { self .filters .to_json_array ()} " )
111127 self .relay_manager .add_subscription (self .subscription_id , self .filters )
112128 time .sleep (1 )
129+
113130 # Create get_balance request
114131 balance_request = {
115132 "method" : "get_balance" ,
@@ -123,53 +140,48 @@ def fetch_balance_thread(self, balance_updated_cb) :
123140 )
124141 print (f"DEBUG: Signing DM { json .dumps (dm )} with private key" )
125142 self .private_key .sign_event (dm ) # sign also does encryption if it's a encrypted dm
126- print (f"DEBUG: DM created with ID: { dm .id } " )
127- # Publish request
128143 print (f"DEBUG: Publishing subscription request" )
129144 request_message = [ClientMessageType .REQUEST , self .subscription_id ]
130145 request_message .extend (self .filters .to_json_array ())
131146 self .relay_manager .publish_message (json .dumps (request_message ))
132147 print (f"DEBUG: Publishing encrypted DM" )
133148 self .relay_manager .publish_event (dm )
134- # only accept events after the time it was published
135- after_time = mpos .time .epoch_seconds ()
136- after_time -= 60 # go back a bit because server clocks might be drifting
137- print (f"will only consider events after { after_time } " )
138-
139- # Wait for response
140- print (f"DEBUG: Waiting for response..." )
141- print (f"starting at { time .localtime ()} " )
142- start_time = mpos .time .epoch_seconds ()
143- balance = None
144- while mpos .time .epoch_seconds () - start_time < 60 * 2 :
145- while self .relay_manager .message_pool .has_events ():
149+
150+ print (f"DEBUG: Waiting for incoming NWC events..." )
151+ while self .keep_running :
152+ if self .relay_manager .message_pool .has_events ():
146153 print (f"DEBUG: Event received from message pool" )
147154 event_msg = self .relay_manager .message_pool .get_event ()
148155 event_created_at = event_msg .event .created_at
149156 print (f"Received at { time .localtime ()} a message with timestamp { event_created_at } " )
150- #if event_created_at < after_time:
151- # print("Skipping event because it's too old!")
152- # continue
153- #print(f"event_msg content {event_msg.event.content}")
154157 try :
155- #print(f"DEBUG: Decrypting event from public_key: {event_msg.event.public_key}")
156158 decrypted_content = self .private_key .decrypt_message (
157159 event_msg .event .content ,
158160 event_msg .event .public_key
159161 )
160162 print (f"DEBUG: Decrypted content: { decrypted_content } " )
161163 response = json .loads (decrypted_content )
162164 print (f"DEBUG: Parsed response: { response } " )
163- self .last_known_balance = round (int (response ["result" ]["balance" ]) / 1000 )
164- print (f"Got balance: { self .last_known_balance } " )
165- balance_updated_cb ()
166- break
165+ if response ["result" ]:
166+ if response ["result" ]["balance" ]:
167+ self .last_known_balance = round (int (response ["result" ]["balance" ]) / 1000 )
168+ print (f"Got balance: { self .last_known_balance } " )
169+ # TODO: if balance changed, then update list of transactions
170+ balance_updated_cb ()
171+ elif response ["result" ]["transactions" ]:
172+ print ("TODO: Response contains transactions!" )
173+ else :
174+ print ("Unsupported response, ignoring." )
175+ else :
176+ print ("Event doesn't contain result, ignoring." )
167177 except Exception as e :
168178 print (f"DEBUG: Error processing response: { e } " )
169- if balance is not None :
170- break
171179 time .sleep (1 )
172180
181+ print ("NWCWallet: manage_wallet_thread stopping, closing connections..." )
182+ self .relay_manager .close_connections ()
183+
184+
173185 def parse_nwc_url (self , nwc_url ):
174186 """Parse Nostr Wallet Connect URL to extract pubkey, relay, secret, and lud16."""
175187 print (f"DEBUG: Starting to parse NWC URL: { nwc_url } " )
@@ -185,6 +197,7 @@ def parse_nwc_url(self, nwc_url):
185197 print (f"DEBUG: No recognized prefix found in URL" )
186198 raise ValueError ("Invalid NWC URL: missing 'nostr+walletconnect://' or 'nwc:' prefix" )
187199 print (f"DEBUG: URL after prefix removal: { nwc_url } " )
200+ # TODO: urldecode because the relay might have %3A%2F%2F etc
188201 # Split into pubkey and query params
189202 parts = nwc_url .split ('?' )
190203 pubkey = parts [0 ]
@@ -201,7 +214,7 @@ def parse_nwc_url(self, nwc_url):
201214 params = parts [1 ].split ('&' )
202215 for param in params :
203216 if param .startswith ('relay=' ):
204- relay = param [6 :] # TODO: urldecode because the relay might have %3A%2F%2F etc
217+ relay = param [6 :]
205218 print (f"DEBUG: Extracted relay: { relay } " )
206219 elif param .startswith ('secret=' ):
207220 secret = param [7 :]
0 commit comments