You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Piggy wallet: add instant payments for LNBits and NWC (untested)
This commit is contained in:
@@ -74,15 +74,15 @@ class Wallet:
|
||||
elif isinstance(self, NWCWallet):
|
||||
return "NWCWallet"
|
||||
|
||||
def handle_new_balance(self, new_balance):
|
||||
def handle_new_balance(self, new_balance, fetchPaymentsIfChanged=True):
|
||||
if new_balance != self.last_known_balance:
|
||||
print("Balance changed!")
|
||||
self.last_known_balance = new_balance
|
||||
print("Calling balance_updated_cb")
|
||||
self.balance_updated_cb()
|
||||
# Refreshing isn't strictly necessary if it was changed by a payment notification
|
||||
print("Refreshing payments...")
|
||||
self.fetch_payments() # if the balance changed, then re-list transactions
|
||||
if fetchPaymentsIfChanged: # Fetching *all* payments isn't necessary if balance was changed by a payment notification
|
||||
print("Refreshing payments...")
|
||||
self.fetch_payments() # if the balance changed, then re-list transactions
|
||||
|
||||
def handle_new_payments(self, new_payments):
|
||||
print("handle_new_payments")
|
||||
@@ -117,16 +117,71 @@ class LNBitsWallet(Wallet):
|
||||
self.lnbits_url = lnbits_url
|
||||
self.lnbits_readkey = lnbits_readkey
|
||||
|
||||
|
||||
def parseLNBitsPayment(self, transaction):
|
||||
amount = transaction["amount"]
|
||||
amount = round(amount / 1000)
|
||||
comment = transaction["memo"]
|
||||
epoch_time = transaction["time"]
|
||||
extra = transaction["extra"]
|
||||
if extra:
|
||||
extracomment = extra["comment"]
|
||||
if extracomment:
|
||||
if extracomment.get(0): # some LNBits 0.x versions return a list instead of a string here...
|
||||
comment = extracomment.get(0)
|
||||
else:
|
||||
comment = extracomment
|
||||
return Payment(epoch_time, amount, comment)
|
||||
|
||||
# Example data: {"wallet_balance": 4936, "payment": {"checking_id": "037c14...56b3", "pending": false, "amount": 1000000, "fee": 0, "memo": "zap2oink", "time": 1711226003, "bolt11": "lnbc10u1pjl70y....qq9renr", "preimage": "0000...000", "payment_hash": "037c1438b20ef4729b1d3dc252c2809dc2a2a2e641c7fb99fe4324e182f356b3", "expiry": 1711226603.0, "extra": {"tag": "lnurlp", "link": "TkjgaB", "extra": "1000000", "comment": ["yes"], "lnaddress": "oink@demo.lnpiggy.com"}, "wallet_id": "c9168...8de4", "webhook": null, "webhook_status": null}}
|
||||
def on_message(self, class_obj, message: str):
|
||||
print(f"relay.py _on_message received: {message}")
|
||||
try:
|
||||
payment_notification = json.loads(message)
|
||||
new_balance = payment_notification.get("wallet_balance")
|
||||
if new_balance:
|
||||
self.handle_new_balance(new_balance, False) # handle new balance BUT don't trigger a full fetch_payments
|
||||
transaction = payment_notification.get("payment")
|
||||
new_payments = UniqueSortedList()
|
||||
print(f"Got transaction: {transaction}")
|
||||
paymentObj = parseLNBitsPayment(transaction)
|
||||
new_payments.add(paymentObj)
|
||||
self.handle_new_payments(new_payments)
|
||||
except Exception as e:
|
||||
print(f"websocket on_message got exception: {e}")
|
||||
|
||||
def websocket_thread(self):
|
||||
print("Opening websocket for payment notifications...")
|
||||
wsurl = self.lnbits_url + "/api/v1/ws/" + self.lnbits_readkey
|
||||
self.ws = WebSocketApp(
|
||||
wsurl,
|
||||
on_message=self.on_message,
|
||||
) # maybe add other callbacks to reconnect when disconnected etc.
|
||||
self.ws.run_forever(
|
||||
sslopt=ssl_options,
|
||||
http_proxy_host=None if proxy is None else proxy.get("host"),
|
||||
http_proxy_port=None if proxy is None else proxy.get("port"),
|
||||
proxy_type=None if proxy is None else proxy.get("type"),
|
||||
ping_interval=5
|
||||
)
|
||||
|
||||
|
||||
def wallet_manager_thread(self):
|
||||
print("wallet_manager_thread")
|
||||
websocket_running = False
|
||||
while self.keep_running:
|
||||
try:
|
||||
new_balance = self.fetch_balance()
|
||||
new_balance = self.fetch_balance() # TODO: only do this every 60 seconds, but loop the main thread more frequently
|
||||
except Exception as e:
|
||||
print(f"WARNING: wallet_manager_thread got exception {e}, ignorning.")
|
||||
if not websocket_running: # after
|
||||
websocket_running = True
|
||||
_thread.stack_size(mpos.apps.good_stack_size())
|
||||
_thread.start_new_thread(self.websocket_thread, ())
|
||||
print("Sleeping a while before re-fetching balance...")
|
||||
time.sleep(60)
|
||||
print("wallet_manager_thread stopping")
|
||||
self.ws.close()
|
||||
|
||||
def fetch_balance(self):
|
||||
walleturl = self.lnbits_url + "/api/v1/wallet"
|
||||
@@ -170,17 +225,8 @@ class LNBitsWallet(Wallet):
|
||||
new_payments = UniqueSortedList()
|
||||
for transaction in payments_reply:
|
||||
#print(f"Got transaction: {transaction}")
|
||||
amount = transaction["amount"]
|
||||
amount = round(amount / 1000)
|
||||
comment = transaction["memo"]
|
||||
epoch_time = transaction["time"]
|
||||
extra = transaction["extra"]
|
||||
if extra:
|
||||
extracomment = extra["comment"]
|
||||
if extracomment:
|
||||
comment = extracomment
|
||||
transaction = Payment(epoch_time, amount, comment)
|
||||
new_payments.add(transaction)
|
||||
paymentObj = parseLNBitsPayment(transaction)
|
||||
new_payments.add(paymentObj)
|
||||
self.handle_new_payments(new_payments)
|
||||
except Exception as e:
|
||||
print(f"Could not parse reponse text '{response_text}' as JSON: {e}")
|
||||
@@ -193,6 +239,21 @@ class NWCWallet(Wallet):
|
||||
self.nwc_url = nwc_url
|
||||
self.connected = False
|
||||
|
||||
def getCommentFromTransaction(self, transaction):
|
||||
comment = ""
|
||||
try:
|
||||
comment = transaction["description"]
|
||||
json_comment = json.loads(comment)
|
||||
for field in json_comment:
|
||||
if field[0] == "text/plain":
|
||||
comment = field[1]
|
||||
break
|
||||
else:
|
||||
print("text/plain field is missing from JSON description")
|
||||
except Exception as e:
|
||||
print(f"Info: could not parse comment as JSON, using as-is: {e}")
|
||||
return comment
|
||||
|
||||
def wallet_manager_thread(self):
|
||||
self.relay, self.wallet_pubkey, self.secret, self.lud16 = self.parse_nwc_url(self.nwc_url)
|
||||
self.private_key = PrivateKey(bytes.fromhex(self.secret))
|
||||
@@ -217,7 +278,7 @@ class NWCWallet(Wallet):
|
||||
self.subscription_id = "micropython_nwc_" + str(round(time.time()))
|
||||
print(f"DEBUG: Setting up subscription with ID: {self.subscription_id}")
|
||||
self.filters = Filters([Filter(
|
||||
kinds=[23195], # NWC replies
|
||||
kinds=[23195, 23196], # NWC reponses and notifications
|
||||
authors=[self.wallet_pubkey],
|
||||
pubkey_refs=[self.private_key.public_key.hex()]
|
||||
)])
|
||||
@@ -258,22 +319,28 @@ class NWCWallet(Wallet):
|
||||
for transaction in result["transactions"]:
|
||||
amount = transaction["amount"]
|
||||
amount = round(amount / 1000)
|
||||
comment = transaction["description"]
|
||||
try:
|
||||
json_comment = json.loads(comment)
|
||||
for field in json_comment:
|
||||
if field[0] == "text/plain":
|
||||
comment = field[1]
|
||||
break
|
||||
else:
|
||||
print("text/plain field is missing from JSON description")
|
||||
except Exception as e:
|
||||
print(f"Could not parse comment as JSON: {e}")
|
||||
pass # NWC description can also be just a regular sting, not json
|
||||
comment = self.getCommentFromTransaction(transaction)
|
||||
epoch_time = transaction["created_at"]
|
||||
payment = Payment(epoch_time, amount, comment)
|
||||
new_payments.add(payment)
|
||||
self.handle_new_payments(new_payments)
|
||||
elif result.get("notification"): # it's a notification
|
||||
notification = result.get("notification")
|
||||
amount = notification["amount"]
|
||||
amount = round(amount / 1000)
|
||||
type = notification["type"]
|
||||
if type == "outgoing":
|
||||
amount = -amount
|
||||
elif type != "incoming":
|
||||
print(f"WARNING: invalid notification type {type}, ignoring.")
|
||||
continue
|
||||
self.handle_new_balance(new_balance, False)
|
||||
epoch_time = transaction["created_at"]
|
||||
comment = self.getCommentFromTransaction(notification)
|
||||
payment = Payment(epoch_time, amount, comment)
|
||||
new_payments = UniqueSortedList()
|
||||
new_payments.add(payment)
|
||||
self.handle_new_payments(new_payments)
|
||||
else:
|
||||
print("Unsupported response, ignoring.")
|
||||
else:
|
||||
@@ -383,7 +450,7 @@ class Payment:
|
||||
|
||||
def __str__(self):
|
||||
sattext = "sats"
|
||||
if self.amount_sats is 1:
|
||||
if self.amount_sats == 1:
|
||||
sattext = "sat"
|
||||
return f"{self.amount_sats} {sattext}: {self.comment}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user