2020from .types import (
2121 AddressInvoiceResponse ,
2222 AddressResponse ,
23- ApiKeyResponse ,
2423 BackupPasskeyBeginResponse ,
2524 CreateWalletResponse ,
2625 CreateWebhookResponse ,
2726 InvoiceEvent ,
2827 InvoiceResponse ,
28+ L402ChallengeResponse ,
29+ L402PayResponse ,
2930 PaymentEvent ,
3031 PaymentResponse ,
32+ VerifyL402Response ,
3133 WalletEvent ,
3234 RecoveryBackupResponse ,
3335 RecoveryRestoreResponse ,
@@ -108,15 +110,11 @@ def update(self, *, name: str) -> WalletResponse:
108110
109111
110112class KeysResource :
111- """API key listing and rotation."""
113+ """API key rotation."""
112114
113115 def __init__ (self , client : LnBot ) -> None :
114116 self ._c = client
115117
116- def list (self ) -> list [ApiKeyResponse ]:
117- """List API keys (metadata only, keys are not returned)."""
118- return [parse (ApiKeyResponse , item ) for item in self ._c ._get ("/v1/keys" )]
119-
120118 def rotate (self , slot : int ) -> RotateApiKeyResponse :
121119 """Rotate an API key. *slot* 0 = primary, 1 = secondary."""
122120 return parse (RotateApiKeyResponse , self ._c ._post (f"/v1/keys/{ slot } /rotate" ))
@@ -136,9 +134,9 @@ def list(self, *, limit: int | None = None, after: int | None = None) -> list[In
136134 """List invoices, optionally paginated."""
137135 return [parse (InvoiceResponse , item ) for item in self ._c ._get ("/v1/invoices" , params = _qs ({"limit" : limit , "after" : after }))]
138136
139- def get (self , number : int ) -> InvoiceResponse :
140- """Get a single invoice by its number."""
141- return parse (InvoiceResponse , self ._c ._get (f"/v1/invoices/{ number } " ))
137+ def get (self , number_or_hash : int | str ) -> InvoiceResponse :
138+ """Get a single invoice by its number or payment hash ."""
139+ return parse (InvoiceResponse , self ._c ._get (f"/v1/invoices/{ number_or_hash } " ))
142140
143141 def create_for_wallet (self , * , wallet_id : str , amount : int , reference : str | None = None , comment : str | None = None ) -> AddressInvoiceResponse :
144142 """Create an invoice for a specific wallet by ID. No authentication required."""
@@ -150,13 +148,13 @@ def create_for_address(self, *, address: str, amount: int, tag: str | None = Non
150148 body = to_camel ({"address" : address , "amount" : amount , "tag" : tag , "comment" : comment })
151149 return parse (AddressInvoiceResponse , self ._c ._post ("/v1/invoices/for-address" , body ))
152150
153- def watch (self , number : int , * , timeout : int | None = None ) -> Iterator [InvoiceEvent ]:
151+ def watch (self , number_or_hash : int | str , * , timeout : int | None = None ) -> Iterator [InvoiceEvent ]:
154152 """Stream SSE events until the invoice is settled or expires."""
155153 params = _qs ({"timeout" : timeout })
156154 headers = {"Accept" : "text/event-stream" , "User-Agent" : _USER_AGENT }
157155 if self ._c ._api_key :
158156 headers ["Authorization" ] = f"Bearer { self ._c ._api_key } "
159- with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/invoices/{ number } /events" , params = params , headers = headers ) as resp :
157+ with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/invoices/{ number_or_hash } /events" , params = params , headers = headers ) as resp :
160158 _raise_for_status (resp )
161159 event_type = ""
162160 for line in resp .iter_lines ():
@@ -188,17 +186,17 @@ def list(self, *, limit: int | None = None, after: int | None = None) -> list[Pa
188186 """List payments, optionally paginated."""
189187 return [parse (PaymentResponse , item ) for item in self ._c ._get ("/v1/payments" , params = _qs ({"limit" : limit , "after" : after }))]
190188
191- def get (self , number : int ) -> PaymentResponse :
192- """Get a single payment by its number."""
193- return parse (PaymentResponse , self ._c ._get (f"/v1/payments/{ number } " ))
189+ def get (self , number_or_hash : int | str ) -> PaymentResponse :
190+ """Get a single payment by its number or payment hash ."""
191+ return parse (PaymentResponse , self ._c ._get (f"/v1/payments/{ number_or_hash } " ))
194192
195- def watch (self , number : int , * , timeout : int | None = None ) -> Iterator [PaymentEvent ]:
193+ def watch (self , number_or_hash : int | str , * , timeout : int | None = None ) -> Iterator [PaymentEvent ]:
196194 """Stream SSE events until the payment settles or fails."""
197195 params = _qs ({"timeout" : timeout })
198196 headers = {"Accept" : "text/event-stream" , "User-Agent" : _USER_AGENT }
199197 if self ._c ._api_key :
200198 headers ["Authorization" ] = f"Bearer { self ._c ._api_key } "
201- with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/payments/{ number } /events" , params = params , headers = headers ) as resp :
199+ with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/payments/{ number_or_hash } /events" , params = params , headers = headers ) as resp :
202200 _raise_for_status (resp )
203201 event_type = ""
204202 for line in resp .iter_lines ():
@@ -336,6 +334,27 @@ def passkey_complete(self, *, session_id: str, assertion: dict[str, Any]) -> Res
336334 return parse (RestorePasskeyCompleteResponse , self ._c ._post ("/v1/restore/passkey/complete" , to_camel ({"session_id" : session_id , "assertion" : assertion })))
337335
338336
337+ class L402Resource :
338+ """L402 paywall authentication."""
339+
340+ def __init__ (self , client : LnBot ) -> None :
341+ self ._c = client
342+
343+ def create_challenge (self , * , amount : int , description : str | None = None , expiry_seconds : int | None = None , caveats : list [str ] | None = None ) -> L402ChallengeResponse :
344+ """Create an L402 challenge (invoice + macaroon) for paywall authentication."""
345+ body = to_camel ({"amount" : amount , "description" : description , "expiry_seconds" : expiry_seconds , "caveats" : caveats })
346+ return parse (L402ChallengeResponse , self ._c ._post ("/v1/l402/challenges" , body ))
347+
348+ def verify (self , * , authorization : str ) -> VerifyL402Response :
349+ """Verify an L402 authorization token (stateless)."""
350+ return parse (VerifyL402Response , self ._c ._post ("/v1/l402/verify" , {"authorization" : authorization }))
351+
352+ def pay (self , * , www_authenticate : str , max_fee : int | None = None , reference : str | None = None , wait : bool | None = None , timeout : int | None = None ) -> L402PayResponse :
353+ """Pay an L402 challenge and get a ready-to-use Authorization header."""
354+ body = to_camel ({"www_authenticate" : www_authenticate , "max_fee" : max_fee , "reference" : reference , "wait" : wait , "timeout" : timeout })
355+ return parse (L402PayResponse , self ._c ._post ("/v1/l402/pay" , body ))
356+
357+
339358# ---------------------------------------------------------------------------
340359# Sync client
341360# ---------------------------------------------------------------------------
@@ -369,6 +388,7 @@ def __init__(
369388 self .events = EventsResource (self )
370389 self .backup = BackupResource (self )
371390 self .restore = RestoreResource (self )
391+ self .l402 = L402Resource (self )
372392
373393 def _get (self , path : str , * , params : dict [str , Any ] | None = None ) -> Any :
374394 resp = self ._http .get (f"{ self ._base_url } { path } " , headers = _headers (self ._api_key ), params = params )
@@ -429,15 +449,11 @@ async def update(self, *, name: str) -> WalletResponse:
429449
430450
431451class AsyncKeysResource :
432- """API key listing and rotation (async)."""
452+ """API key rotation (async)."""
433453
434454 def __init__ (self , client : AsyncLnBot ) -> None :
435455 self ._c = client
436456
437- async def list (self ) -> list [ApiKeyResponse ]:
438- """List API keys (metadata only, keys are not returned)."""
439- return [parse (ApiKeyResponse , item ) for item in await self ._c ._get ("/v1/keys" )]
440-
441457 async def rotate (self , slot : int ) -> RotateApiKeyResponse :
442458 """Rotate an API key. *slot* 0 = primary, 1 = secondary."""
443459 return parse (RotateApiKeyResponse , await self ._c ._post (f"/v1/keys/{ slot } /rotate" ))
@@ -457,9 +473,9 @@ async def list(self, *, limit: int | None = None, after: int | None = None) -> l
457473 """List invoices, optionally paginated."""
458474 return [parse (InvoiceResponse , item ) for item in await self ._c ._get ("/v1/invoices" , params = _qs ({"limit" : limit , "after" : after }))]
459475
460- async def get (self , number : int ) -> InvoiceResponse :
461- """Get a single invoice by its number."""
462- return parse (InvoiceResponse , await self ._c ._get (f"/v1/invoices/{ number } " ))
476+ async def get (self , number_or_hash : int | str ) -> InvoiceResponse :
477+ """Get a single invoice by its number or payment hash ."""
478+ return parse (InvoiceResponse , await self ._c ._get (f"/v1/invoices/{ number_or_hash } " ))
463479
464480 async def create_for_wallet (self , * , wallet_id : str , amount : int , reference : str | None = None , comment : str | None = None ) -> AddressInvoiceResponse :
465481 """Create an invoice for a specific wallet by ID. No authentication required."""
@@ -471,13 +487,13 @@ async def create_for_address(self, *, address: str, amount: int, tag: str | None
471487 body = to_camel ({"address" : address , "amount" : amount , "tag" : tag , "comment" : comment })
472488 return parse (AddressInvoiceResponse , await self ._c ._post ("/v1/invoices/for-address" , body ))
473489
474- async def watch (self , number : int , * , timeout : int | None = None ) -> AsyncIterator [InvoiceEvent ]:
490+ async def watch (self , number_or_hash : int | str , * , timeout : int | None = None ) -> AsyncIterator [InvoiceEvent ]:
475491 """Stream SSE events until the invoice is settled or expires."""
476492 params = _qs ({"timeout" : timeout })
477493 headers = {"Accept" : "text/event-stream" , "User-Agent" : _USER_AGENT }
478494 if self ._c ._api_key :
479495 headers ["Authorization" ] = f"Bearer { self ._c ._api_key } "
480- async with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/invoices/{ number } /events" , params = params , headers = headers ) as resp :
496+ async with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/invoices/{ number_or_hash } /events" , params = params , headers = headers ) as resp :
481497 _raise_for_status (resp )
482498 event_type = ""
483499 async for line in resp .aiter_lines ():
@@ -509,17 +525,17 @@ async def list(self, *, limit: int | None = None, after: int | None = None) -> l
509525 """List payments, optionally paginated."""
510526 return [parse (PaymentResponse , item ) for item in await self ._c ._get ("/v1/payments" , params = _qs ({"limit" : limit , "after" : after }))]
511527
512- async def get (self , number : int ) -> PaymentResponse :
513- """Get a single payment by its number."""
514- return parse (PaymentResponse , await self ._c ._get (f"/v1/payments/{ number } " ))
528+ async def get (self , number_or_hash : int | str ) -> PaymentResponse :
529+ """Get a single payment by its number or payment hash ."""
530+ return parse (PaymentResponse , await self ._c ._get (f"/v1/payments/{ number_or_hash } " ))
515531
516- async def watch (self , number : int , * , timeout : int | None = None ) -> AsyncIterator [PaymentEvent ]:
532+ async def watch (self , number_or_hash : int | str , * , timeout : int | None = None ) -> AsyncIterator [PaymentEvent ]:
517533 """Stream SSE events until the payment settles or fails."""
518534 params = _qs ({"timeout" : timeout })
519535 headers = {"Accept" : "text/event-stream" , "User-Agent" : _USER_AGENT }
520536 if self ._c ._api_key :
521537 headers ["Authorization" ] = f"Bearer { self ._c ._api_key } "
522- async with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/payments/{ number } /events" , params = params , headers = headers ) as resp :
538+ async with self ._c ._http .stream ("GET" , f"{ self ._c ._base_url } /v1/payments/{ number_or_hash } /events" , params = params , headers = headers ) as resp :
523539 _raise_for_status (resp )
524540 event_type = ""
525541 async for line in resp .aiter_lines ():
@@ -657,6 +673,27 @@ async def passkey_complete(self, *, session_id: str, assertion: dict[str, Any])
657673 return parse (RestorePasskeyCompleteResponse , await self ._c ._post ("/v1/restore/passkey/complete" , to_camel ({"session_id" : session_id , "assertion" : assertion })))
658674
659675
676+ class AsyncL402Resource :
677+ """L402 paywall authentication (async)."""
678+
679+ def __init__ (self , client : AsyncLnBot ) -> None :
680+ self ._c = client
681+
682+ async def create_challenge (self , * , amount : int , description : str | None = None , expiry_seconds : int | None = None , caveats : list [str ] | None = None ) -> L402ChallengeResponse :
683+ """Create an L402 challenge (invoice + macaroon) for paywall authentication."""
684+ body = to_camel ({"amount" : amount , "description" : description , "expiry_seconds" : expiry_seconds , "caveats" : caveats })
685+ return parse (L402ChallengeResponse , await self ._c ._post ("/v1/l402/challenges" , body ))
686+
687+ async def verify (self , * , authorization : str ) -> VerifyL402Response :
688+ """Verify an L402 authorization token (stateless)."""
689+ return parse (VerifyL402Response , await self ._c ._post ("/v1/l402/verify" , {"authorization" : authorization }))
690+
691+ async def pay (self , * , www_authenticate : str , max_fee : int | None = None , reference : str | None = None , wait : bool | None = None , timeout : int | None = None ) -> L402PayResponse :
692+ """Pay an L402 challenge and get a ready-to-use Authorization header."""
693+ body = to_camel ({"www_authenticate" : www_authenticate , "max_fee" : max_fee , "reference" : reference , "wait" : wait , "timeout" : timeout })
694+ return parse (L402PayResponse , await self ._c ._post ("/v1/l402/pay" , body ))
695+
696+
660697# ---------------------------------------------------------------------------
661698# Async client
662699# ---------------------------------------------------------------------------
@@ -690,6 +727,7 @@ def __init__(
690727 self .events = AsyncEventsResource (self )
691728 self .backup = AsyncBackupResource (self )
692729 self .restore = AsyncRestoreResource (self )
730+ self .l402 = AsyncL402Resource (self )
693731
694732 async def _get (self , path : str , * , params : dict [str , Any ] | None = None ) -> Any :
695733 resp = await self ._http .get (f"{ self ._base_url } { path } " , headers = _headers (self ._api_key ), params = params )
0 commit comments