Skip to content

Commit 93010c5

Browse files
committed
Add wallet event stream, preimage and serviceFee fields
- Add events.stream() for real-time wallet-level SSE events (sync + async) - Add preimage field to InvoiceResponse and PaymentResponse - Add service_fee field to PaymentResponse - Add WalletEvent, WalletEventType types - Bump version to 0.4.0 Made-with: Cursor
1 parent 3524c67 commit 93010c5

File tree

4 files changed

+83
-1
lines changed

4 files changed

+83
-1
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "lnbot"
7-
version = "0.3.0"
7+
version = "0.4.0"
88
description = "Official Python SDK for LnBot — Bitcoin for AI Agents. Send and receive sats over Lightning with a few lines of code."
99
readme = "README.md"
1010
license = "MIT"

src/lnbot/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
PaymentEvent,
2828
PaymentResponse,
2929
PaymentStatus,
30+
WalletEvent,
31+
WalletEventType,
3032
RecoveryBackupResponse,
3133
RecoveryRestoreResponse,
3234
RestorePasskeyBeginResponse,
@@ -60,6 +62,8 @@
6062
"PaymentResponse",
6163
"PaymentStatus",
6264
"PaymentEvent",
65+
"WalletEvent",
66+
"WalletEventType",
6367
"AddressResponse",
6468
"TransferAddressResponse",
6569
"TransactionResponse",

src/lnbot/client.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
InvoiceResponse,
2929
PaymentEvent,
3030
PaymentResponse,
31+
WalletEvent,
3132
RecoveryBackupResponse,
3233
RecoveryRestoreResponse,
3334
RestorePasskeyBeginResponse,
@@ -269,6 +270,34 @@ def delete(self, webhook_id: str) -> None:
269270
self._c._delete(f"/v1/webhooks/{webhook_id}")
270271

271272

273+
class EventsResource:
274+
"""Real-time wallet event stream."""
275+
276+
def __init__(self, client: LnBot) -> None:
277+
self._c = client
278+
279+
def stream(self) -> Iterator[WalletEvent]:
280+
"""Stream all wallet events via SSE."""
281+
headers = {"Accept": "text/event-stream", "User-Agent": _USER_AGENT}
282+
if self._c._api_key:
283+
headers["Authorization"] = f"Bearer {self._c._api_key}"
284+
with self._c._http.stream("GET", f"{self._c._base_url}/v1/events", headers=headers) as resp:
285+
_raise_for_status(resp)
286+
for line in resp.iter_lines():
287+
if line.startswith("data:"):
288+
raw = line[5:].strip()
289+
if raw:
290+
try:
291+
data = json.loads(raw)
292+
yield WalletEvent(
293+
event=data.get("event", ""),
294+
created_at=data.get("createdAt", ""),
295+
data=data.get("data", {}),
296+
)
297+
except (json.JSONDecodeError, TypeError):
298+
pass
299+
300+
272301
class BackupResource:
273302
"""Wallet backup via recovery passphrase or passkey."""
274303

@@ -337,6 +366,7 @@ def __init__(
337366
self.addresses = AddressesResource(self)
338367
self.transactions = TransactionsResource(self)
339368
self.webhooks = WebhooksResource(self)
369+
self.events = EventsResource(self)
340370
self.backup = BackupResource(self)
341371
self.restore = RestoreResource(self)
342372

@@ -561,6 +591,34 @@ async def delete(self, webhook_id: str) -> None:
561591
await self._c._delete(f"/v1/webhooks/{webhook_id}")
562592

563593

594+
class AsyncEventsResource:
595+
"""Real-time wallet event stream (async)."""
596+
597+
def __init__(self, client: AsyncLnBot) -> None:
598+
self._c = client
599+
600+
async def stream(self) -> AsyncIterator[WalletEvent]:
601+
"""Stream all wallet events via SSE."""
602+
headers = {"Accept": "text/event-stream", "User-Agent": _USER_AGENT}
603+
if self._c._api_key:
604+
headers["Authorization"] = f"Bearer {self._c._api_key}"
605+
async with self._c._http.stream("GET", f"{self._c._base_url}/v1/events", headers=headers) as resp:
606+
_raise_for_status(resp)
607+
async for line in resp.aiter_lines():
608+
if line.startswith("data:"):
609+
raw = line[5:].strip()
610+
if raw:
611+
try:
612+
data = json.loads(raw)
613+
yield WalletEvent(
614+
event=data.get("event", ""),
615+
created_at=data.get("createdAt", ""),
616+
data=data.get("data", {}),
617+
)
618+
except (json.JSONDecodeError, TypeError):
619+
pass
620+
621+
564622
class AsyncBackupResource:
565623
"""Wallet backup via recovery passphrase or passkey (async)."""
566624

@@ -629,6 +687,7 @@ def __init__(
629687
self.addresses = AsyncAddressesResource(self)
630688
self.transactions = AsyncTransactionsResource(self)
631689
self.webhooks = AsyncWebhooksResource(self)
690+
self.events = AsyncEventsResource(self)
632691
self.backup = AsyncBackupResource(self)
633692
self.restore = AsyncRestoreResource(self)
634693

src/lnbot/types.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class InvoiceResponse:
6565
bolt11: str
6666
reference: str | None = None
6767
memo: str | None = None
68+
preimage: str | None = None
6869
tx_number: int | None = None
6970
created_at: str | None = None
7071
settled_at: str | None = None
@@ -92,9 +93,11 @@ class PaymentResponse:
9293
status: PaymentStatus
9394
amount: int
9495
max_fee: int
96+
service_fee: int
9597
address: str
9698
actual_fee: int | None = None
9799
reference: str | None = None
100+
preimage: str | None = None
98101
tx_number: int | None = None
99102
failure_reason: str | None = None
100103
created_at: str | None = None
@@ -211,6 +214,22 @@ class PaymentEvent:
211214
data: PaymentResponse
212215

213216

217+
WalletEventType = Literal[
218+
"invoice.created",
219+
"invoice.settled",
220+
"payment.created",
221+
"payment.settled",
222+
"payment.failed",
223+
]
224+
225+
226+
@dataclass(frozen=True)
227+
class WalletEvent:
228+
event: WalletEventType
229+
created_at: str
230+
data: dict[str, Any]
231+
232+
214233
# ---------------------------------------------------------------------------
215234
# JSON key mapping (snake_case <-> camelCase)
216235
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)