iflow-mcp_breez-breez-mcp 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: iflow-mcp_breez-breez-mcp
3
+ Version: 0.1.0
4
+ Summary: Breez MCP Server - FastMCP implementation for Lightning wallet functionality
5
+ Author: Breez MCP Contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/breez/breez-mcp
8
+ Project-URL: Repository, https://github.com/breez/breez-mcp
9
+ Project-URL: Documentation, https://github.com/breez/breez-mcp#readme
10
+ Keywords: mcp,lightning,breez,bitcoin,wallet
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Office/Business :: Financial
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: fastmcp>=0.4.0
22
+ Requires-Dist: breez-sdk-spark>=0.2.7
23
+ Requires-Dist: python-dotenv>=1.0.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: uvicorn[standard]>=0.24.0
26
+
27
+ # Breez MCP Server — FastMCP Implementation
28
+
29
+ A unified MCP server that exposes Lightning functionality through the Breez SDK (Spark implementation) using FastMCP. Supports both stdio and HTTP transport modes.
30
+
31
+ ## Prerequisites
32
+
33
+ - Python 3.11+ (for local development or `uvx`)
34
+ - [Docker](https://docs.docker.com/get-docker/) (optional, for container workflows)
35
+ - [uv](https://github.com/astral-sh/uv) (optional, for ephemeral environments)
36
+ - Breez API key which you can request [here](https://breez.technology/request-api-key/#contact-us-form-sdk)
37
+
38
+ ## Configure Credentials
39
+
40
+ ```bash
41
+ cp .env.example .env
42
+ ```
43
+
44
+ Edit `.env` with your secrets. Required variables:
45
+
46
+ | Variable | Required | Default | Purpose |
47
+ |----------|----------|---------|---------|
48
+ | `BREEZ_API_KEY` | ✅ | – | Breez Spark API key |
49
+ | `BREEZ_MNEMONIC` | ✅ | – | 12-word mnemonic controlling the wallet |
50
+ | `BREEZ_NETWORK` | ❌ | `mainnet` | Set to `testnet` for sandbox usage |
51
+ | `BREEZ_DATA_DIR` | ❌ | `./data` | Wallet storage directory |
52
+ | `BREEZ_TRANSPORT_MODE` | ❌ | `stdio` | Transport mode: `stdio`, `http`, or `asgi` |
53
+ | `BREEZ_HTTP_HOST` | ❌ | `0.0.0.0` | HTTP server host (HTTP mode only) |
54
+ | `BREEZ_HTTP_PORT` | ❌ | `8000` | HTTP server port (HTTP mode only) |
55
+ | `BREEZ_HTTP_PATH` | ❌ | `/mcp` | HTTP endpoint path (HTTP mode only) |
56
+
57
+ ## Run the Server
58
+
59
+ Choose the runtime that transport mode that fits your workflow.
60
+
61
+ ### STDIO Mode (Default for MCP clients)
62
+
63
+ For use with Claude Desktop and other MCP clients:
64
+
65
+ ```bash
66
+ # Local virtualenv
67
+ python -m venv .venv
68
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
69
+ pip install -r requirements.txt
70
+ python -m src.main
71
+
72
+ # Or with uvx (no persistent venv)
73
+ uvx --from . breez-mcp
74
+ ```
75
+
76
+ ### HTTP Mode (for web API access)
77
+
78
+ For accessing the MCP server via HTTP API:
79
+
80
+ ```bash
81
+ # Set environment variable
82
+ export BREEZ_TRANSPORT_MODE=http
83
+
84
+ # Or add to .env file
85
+ echo "BREEZ_TRANSPORT_MODE=http" >> .env
86
+
87
+ # Run the server
88
+ python -m src.main
89
+ ```
90
+
91
+ The server will be available at `http://localhost:8000/mcp`
92
+
93
+ ### ASGI Mode (for external ASGI servers)
94
+
95
+ For deployment with external ASGI servers like Gunicorn:
96
+
97
+ ```bash
98
+ # Set environment variable
99
+ export BREEZ_TRANSPORT_MODE=asgi
100
+
101
+ # Run with uvicorn
102
+ uvicorn src.main:app --host 0.0.0.0 --port 8000
103
+
104
+ # Or with Gunicorn (production)
105
+ gunicorn src.main:app -w 4 -k uvicorn.workers.UvicornWorker
106
+ ```
107
+
108
+ ### Docker Compose
109
+
110
+ Run both modes simultaneously:
111
+
112
+ ```bash
113
+ # STDIO mode
114
+ docker compose --profile stdio up -d
115
+ docker compose logs -f breez-mcp-stdio
116
+
117
+ # HTTP mode
118
+ docker compose --profile http up -d
119
+ docker compose logs -f breez-mcp-http
120
+
121
+ # Stop
122
+ docker compose --profile http down
123
+ docker compose --profile stdio down
124
+ ```
125
+
126
+ ### Docker (direct)
127
+
128
+ ```bash
129
+ # Build image
130
+ docker build -t breez-mcp .
131
+
132
+ # STDIO mode (default)
133
+ docker run --rm \
134
+ -e BREEZ_API_KEY="$BREEZ_API_KEY" \
135
+ -e BREEZ_MNEMONIC="$BREEZ_MNEMONIC" \
136
+ -v $(pwd)/data:/app/data \
137
+ breez-mcp
138
+
139
+ # HTTP mode
140
+ docker run --rm -p 8000:8000 \
141
+ -e BREEZ_TRANSPORT_MODE=http \
142
+ -e BREEZ_API_KEY="$BREEZ_API_KEY" \
143
+ -e BREEZ_MNEMONIC="$BREEZ_MNEMONIC" \
144
+ -v $(pwd)/data:/app/data \
145
+ breez-mcp
146
+ ```
147
+
148
+ To keep STDIN/STDOUT attached for Claude Desktop, add `-i` to the `docker run` command.
149
+
150
+
151
+ ## Claude Desktop Integration
152
+
153
+ ### Quick install
154
+
155
+ ```bash
156
+ mcp install src.main --name "breez-mcp"
157
+ ```
158
+
159
+ Use `-f .env` or `-v KEY=value` to supply credentials during installation if desired.
160
+
161
+
162
+ ### Docker from Claude Desktop
163
+
164
+ Ensure the image exists (`docker build -t breez-mcp .`), then configure:
165
+
166
+ ```json
167
+ {
168
+ "mcpServers": {
169
+ "breez": {
170
+ "command": "docker",
171
+ "args": [
172
+ "run", "--rm", "-i",
173
+ "-e", "BREEZ_API_KEY",
174
+ "-e", "BREEZ_MNEMONIC",
175
+ "-e", "BREEZ_TRANSPORT_MODE=stdio",
176
+ "-v", "/absolute/path/to/breez-mcp/data:/app/data",
177
+ "breez-mcp"
178
+ ],
179
+ "cwd": "/absolute/path/to/breez-mcp",
180
+ "env": {
181
+ "BREEZ_API_KEY": "${env:BREEZ_API_KEY}",
182
+ "BREEZ_MNEMONIC": "${env:BREEZ_MNEMONIC}",
183
+ "BREEZ_NETWORK": "mainnet"
184
+ }
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ Docker's `-e VAR` syntax reads the value of `VAR` from the environment supplied via the `env` block.
191
+
192
+ ### uvx from Claude Desktop
193
+
194
+ ```json
195
+ {
196
+ "mcpServers": {
197
+ "breez": {
198
+ "command": "uvx",
199
+ "args": ["--from", ".", "breez-mcp"],
200
+ "cwd": "/absolute/path/to/breez-mcp",
201
+ "env": {
202
+ "BREEZ_API_KEY": "${env:BREEZ_API_KEY}",
203
+ "BREEZ_MNEMONIC": "${env:BREEZ_MNEMONIC}",
204
+ }
205
+ }
206
+ }
207
+ }
208
+ ```
209
+
210
+ ### Verification
211
+
212
+ - Restart Claude Desktop after adding the configuration.
213
+ - Run `mcp list` to ensure the server registered.
214
+ - Ask Claude prompts like “Check my wallet balance” or “Create an invoice for 1000 sats” to validate tool routing.
215
+
216
+ ## Available Tools
217
+
218
+ - `get_balance` — comprehensive wallet balance with limits and formatted amounts
219
+ - `get_node_info` — detailed node information including capabilities and sync status
220
+ - `send_payment` — send a Lightning payment with complete transaction details
221
+ - `create_invoice` — generate a BOLT11 invoice with all invoice data
222
+ - `list_payments` — comprehensive payment history with full details
223
+
224
+ ## Example Prompts
225
+
226
+ - "Check my wallet balance"
227
+ - "Create an invoice for 1000 sats for coffee"
228
+ - "Send payment to lnbc1…"
229
+ - "Show me my recent payments"
230
+
231
+ ## HTTP API Usage (HTTP Mode)
232
+
233
+ When running in HTTP mode, you can interact with the MCP server via HTTP requests:
234
+
235
+ ### Health Check
236
+ ```bash
237
+ curl http://localhost:8000/health
238
+ ```
239
+
240
+ ### List Available Tools
241
+ ```bash
242
+ curl http://localhost:8000/mcp/tools/list
243
+ ```
244
+
245
+ ### Call a Tool (MCP Protocol)
246
+ The HTTP mode follows the MCP protocol over HTTP. You'll need to send properly formatted MCP JSON-RPC requests to `http://localhost:8000/mcp`.
247
+
248
+ Example using MCP Inspector or other MCP clients:
249
+ ```json
250
+ {
251
+ "jsonrpc": "2.0",
252
+ "method": "tools/call",
253
+ "params": {
254
+ "name": "get_balance",
255
+ "arguments": {}
256
+ },
257
+ "id": 1
258
+ }
259
+ ```
260
+
261
+ Send to:
262
+ ```bash
263
+ curl -X POST http://localhost:8000/mcp \
264
+ -H "Content-Type: application/json" \
265
+ -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_balance","arguments":{}},"id":1}'
266
+ ```
267
+
268
+ ## Security Notes
269
+
270
+ - Never commit `.env`; keep secrets in your shell or a secrets manager.
271
+ - Treat the mnemonic as the wallet’s private key. Rotate immediately if leaked.
272
+ - Default network is `mainnet`. For experimentation, explicitly set `BREEZ_NETWORK=testnet`.
273
+ - When using containers, mount `./data` to preserve state between runs and prevent secret leakage in container layers.
274
+
275
+ ## Troubleshooting
276
+
277
+ - **Missing environment variables** — ensure `.env` exists or export the required variables before starting.
278
+ - **SDK connection failures** — verify required env vars, try `python list_payments_cli.py --limit 1 --verbose` to confirm SDK connectivity, and check `http://localhost:8000/health` in HTTP mode.
279
+ - **Claude Desktop cannot find the server** — double-check absolute paths in `cwd` and restart the application after configuration changes.
@@ -0,0 +1,9 @@
1
+ src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ src/config.py,sha256=FREmEXQhoIA9TzZCev6ugdMOBMLB86J0hDK1uyEBiOQ,1036
3
+ src/main.py,sha256=9DIdkrnelcAA7dlOqCLxnRBfBLOSYeq6hCDZ8TewhWk,9897
4
+ src/sdk_manager.py,sha256=oFpRq_uwo-ZP2coyKtLKrGnQJo5umK8YD6hSYs-4n5Q,3696
5
+ iflow_mcp_breez_breez_mcp-0.1.0.dist-info/METADATA,sha256=XV3HUgXiHQ3h2rWQWl-8Ap42Dd2x0rS_tLNZafGANxQ,8065
6
+ iflow_mcp_breez_breez_mcp-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
+ iflow_mcp_breez_breez_mcp-0.1.0.dist-info/entry_points.txt,sha256=l-_HAtPeY_tPHtsSmFsPIZaEjSJ9ockY1-_CqnYVSvY,59
8
+ iflow_mcp_breez_breez_mcp-0.1.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
9
+ iflow_mcp_breez_breez_mcp-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ iflow-mcp_breez-breez-mcp = src.main:run
src/__init__.py ADDED
File without changes
src/config.py ADDED
@@ -0,0 +1,25 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from breez_sdk_spark import Network
4
+
5
+ load_dotenv()
6
+
7
+ class Config:
8
+ def __init__(self):
9
+ # Check if we're in test mode
10
+ self.test_mode = os.getenv("BREEZ_TEST_MODE", "false").lower() == "true"
11
+
12
+ if self.test_mode:
13
+ # In test mode, use dummy values
14
+ self.api_key = "test_api_key"
15
+ self.mnemonic = "test mnemonic word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12"
16
+ else:
17
+ # In production mode, require real values
18
+ self.api_key = os.getenv("BREEZ_API_KEY")
19
+ self.mnemonic = os.getenv("BREEZ_MNEMONIC")
20
+
21
+ if not all([self.api_key, self.mnemonic]):
22
+ raise ValueError("Missing required environment variables: BREEZ_API_KEY, BREEZ_MNEMONIC")
23
+
24
+ self.network = Network.TESTNET if os.getenv("BREEZ_NETWORK", "mainnet").lower() == "testnet" else Network.MAINNET
25
+ self.data_dir = os.getenv("BREEZ_DATA_DIR", "./data")
src/main.py ADDED
@@ -0,0 +1,282 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import sys
5
+ import json
6
+ from contextlib import asynccontextmanager
7
+ from typing import Annotated, Dict, Any, List
8
+
9
+ # Import standard MCP SDK
10
+ from mcp.server.stdio import stdio_server
11
+ from mcp.server import Server
12
+ from mcp.types import Tool, TextContent
13
+ from pydantic import Field
14
+
15
+ from .config import Config
16
+ from .sdk_manager import SDKManager
17
+
18
+ # Global SDK manager
19
+ sdk_manager = None
20
+
21
+ # Create MCP server
22
+ server = Server("breez-mcp")
23
+
24
+ async def initialize_sdk():
25
+ """Initialize SDK"""
26
+ global sdk_manager
27
+ logging.info("Starting Breez MCP server...")
28
+ sdk_manager = SDKManager()
29
+ await sdk_manager.connect()
30
+
31
+ async def cleanup_sdk():
32
+ """Cleanup SDK"""
33
+ global sdk_manager
34
+ if sdk_manager:
35
+ await sdk_manager.disconnect()
36
+ logging.info("Server shutdown complete")
37
+
38
+ @server.list_tools()
39
+ async def list_tools() -> List[Tool]:
40
+ """List available tools"""
41
+ return [
42
+ Tool(
43
+ name="get_balance",
44
+ description="Get wallet balance with limits and formatted amounts",
45
+ inputSchema={"type": "object", "properties": {}}
46
+ ),
47
+ Tool(
48
+ name="get_node_info",
49
+ description="Get detailed node information including capabilities and sync status",
50
+ inputSchema={"type": "object", "properties": {}}
51
+ ),
52
+ Tool(
53
+ name="send_payment",
54
+ description="Send a Lightning payment with complete transaction details",
55
+ inputSchema={
56
+ "type": "object",
57
+ "properties": {
58
+ "invoice": {
59
+ "type": "string",
60
+ "description": "BOLT11 invoice to pay"
61
+ }
62
+ },
63
+ "required": ["invoice"]
64
+ }
65
+ ),
66
+ Tool(
67
+ name="create_invoice",
68
+ description="Generate a BOLT11 invoice with all invoice data",
69
+ inputSchema={
70
+ "type": "object",
71
+ "properties": {
72
+ "amount_sats": {
73
+ "type": "integer",
74
+ "description": "Amount in satoshis",
75
+ "minimum": 1
76
+ },
77
+ "description": {
78
+ "type": "string",
79
+ "description": "Payment description",
80
+ "default": "MCP Payment"
81
+ }
82
+ },
83
+ "required": ["amount_sats"]
84
+ }
85
+ ),
86
+ Tool(
87
+ name="list_payments",
88
+ description="List recent payments with full details",
89
+ inputSchema={
90
+ "type": "object",
91
+ "properties": {
92
+ "limit": {
93
+ "type": "integer",
94
+ "description": "Number of payments to return",
95
+ "minimum": 1,
96
+ "maximum": 100,
97
+ "default": 10
98
+ }
99
+ }
100
+ }
101
+ )
102
+ ]
103
+
104
+ @server.call_tool()
105
+ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
106
+ """Call a tool"""
107
+ if not sdk_manager:
108
+ return [TextContent(type="text", text="SDK not initialized")]
109
+
110
+ try:
111
+ if name == "get_balance":
112
+ result = await get_balance()
113
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
114
+
115
+ elif name == "get_node_info":
116
+ result = await get_node_info()
117
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
118
+
119
+ elif name == "send_payment":
120
+ invoice = arguments.get("invoice")
121
+ result = await send_payment(invoice)
122
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
123
+
124
+ elif name == "create_invoice":
125
+ amount_sats = arguments.get("amount_sats")
126
+ description = arguments.get("description", "MCP Payment")
127
+ result = await create_invoice(amount_sats, description)
128
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
129
+
130
+ elif name == "list_payments":
131
+ limit = arguments.get("limit", 10)
132
+ result = await list_payments(limit)
133
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
134
+
135
+ else:
136
+ return [TextContent(type="text", text=f"Unknown tool: {name}")]
137
+
138
+ except Exception as e:
139
+ logging.error(f"Error calling tool {name}: {e}")
140
+ return [TextContent(type="text", text=json.dumps({"error": str(e)}))]
141
+
142
+ async def get_balance() -> Dict[str, Any]:
143
+ """Get wallet balance"""
144
+ from breez_sdk_spark import GetInfoRequest
145
+ info = await sdk_manager.get_sdk().get_info(request=GetInfoRequest(ensure_synced=True))
146
+
147
+ balance = {
148
+ "balance_sat": getattr(info, 'balance_sats', 0) if hasattr(info, 'balance_sats') else 0,
149
+ "pending_incoming_sat": getattr(info, 'pending_incoming_sats', 0) if hasattr(info, 'pending_incoming_sats') else 0,
150
+ "pending_outgoing_sat": getattr(info, 'pending_outgoing_sats', 0) if hasattr(info, 'pending_outgoing_sats') else 0,
151
+ }
152
+
153
+ if hasattr(info, 'max_payable_sats'):
154
+ balance["max_payable_sat"] = info.max_payable_sats
155
+ if hasattr(info, 'max_receivable_sats'):
156
+ balance["max_receivable_sat"] = info.max_receivable_sats
157
+
158
+ balance["balance_formatted"] = f"{balance['balance_sat']:,} sats"
159
+ return balance
160
+
161
+ async def get_node_info() -> Dict[str, Any]:
162
+ """Get node information"""
163
+ from breez_sdk_spark import GetInfoRequest
164
+ info = await sdk_manager.get_sdk().get_info(request=GetInfoRequest(ensure_synced=True))
165
+
166
+ node_info = {}
167
+ for attr in ['id', 'node_id', 'nodeId', 'pubkey', 'public_key', 'node_pubkey']:
168
+ if hasattr(info, attr):
169
+ node_info["node_id"] = getattr(info, attr)
170
+ break
171
+
172
+ node_info["network"] = str(getattr(info, 'network', 'unknown')) if hasattr(info, 'network') else 'unknown'
173
+ node_info["channels_count"] = len(info.channels) if hasattr(info, 'channels') and info.channels else 0
174
+ node_info["synced"] = getattr(info, 'synced', True) if hasattr(info, 'synced') else True
175
+
176
+ if hasattr(info, 'balance_sats'):
177
+ node_info["balance_sat"] = info.balance_sats
178
+ node_info["balance_formatted"] = f"{info.balance_sats:,} sats"
179
+
180
+ return node_info
181
+
182
+ async def send_payment(invoice: str) -> Dict[str, Any]:
183
+ """Send a Lightning payment"""
184
+ from breez_sdk_spark import PrepareSendPaymentRequest, SendPaymentRequest
185
+
186
+ sdk = sdk_manager.get_sdk()
187
+ prepare_request = PrepareSendPaymentRequest(payment_request=invoice)
188
+ prepare_response = await sdk.prepare_send_payment(request=prepare_request)
189
+
190
+ send_request = SendPaymentRequest(prepare_response=prepare_response)
191
+ send_response = await sdk.send_payment(request=send_request)
192
+
193
+ result = {"status": "success", "message": "Payment sent successfully"}
194
+
195
+ if hasattr(send_response, 'payment') and send_response.payment:
196
+ payment = send_response.payment
197
+ result["id"] = getattr(payment, 'id', None)
198
+ result["amount_sat"] = getattr(payment, 'amount', None)
199
+ result["tx_id"] = getattr(payment, 'tx_id', None)
200
+
201
+ if hasattr(send_response, 'payment_hash'):
202
+ result["txid"] = send_response.payment_hash
203
+
204
+ return result
205
+
206
+ async def create_invoice(amount_sats: int, description: str = "MCP Payment") -> Dict[str, Any]:
207
+ """Create a Lightning invoice"""
208
+ from breez_sdk_spark import ReceivePaymentRequest, ReceivePaymentMethod
209
+
210
+ sdk = sdk_manager.get_sdk()
211
+ payment_method = ReceivePaymentMethod.BOLT11_INVOICE(
212
+ description=description,
213
+ amount_sats=amount_sats
214
+ )
215
+ request = ReceivePaymentRequest(payment_method=payment_method)
216
+ response = await sdk.receive_payment(request=request)
217
+
218
+ result = {
219
+ "status": "success",
220
+ "message": "Invoice created successfully",
221
+ "amount_sat": amount_sats,
222
+ "description": description
223
+ }
224
+
225
+ if hasattr(response, 'payment_request'):
226
+ result["invoice"] = response.payment_request
227
+ if hasattr(response, 'payment_hash'):
228
+ result["payment_hash"] = response.payment_hash
229
+
230
+ return result
231
+
232
+ async def list_payments(limit: int = 10) -> Dict[str, Any]:
233
+ """List recent payments"""
234
+ from breez_sdk_spark import ListPaymentsRequest
235
+
236
+ sdk = sdk_manager.get_sdk()
237
+ request = ListPaymentsRequest(limit=limit, sort_ascending=False)
238
+ response = await sdk.list_payments(request=request)
239
+
240
+ result = {"payments": [], "total_count": 0}
241
+
242
+ if hasattr(response, 'payments') and response.payments:
243
+ result["total_count"] = len(response.payments)
244
+ for payment in response.payments:
245
+ payment_data = {
246
+ 'id': getattr(payment, 'id', None),
247
+ 'timestamp': getattr(payment, 'timestamp', None),
248
+ 'amount_sat': getattr(payment, 'amount', None),
249
+ 'status': str(getattr(payment, 'status', 'UNKNOWN')),
250
+ }
251
+ result["payments"].append(payment_data)
252
+
253
+ return result
254
+
255
+ def run():
256
+ """Entry point for setuptools - runs the async main function"""
257
+ asyncio.run(main())
258
+
259
+ async def main():
260
+ """Main entry point"""
261
+ # Setup logging
262
+ logging.basicConfig(
263
+ level=logging.INFO,
264
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
265
+ stream=sys.stdout
266
+ )
267
+
268
+ # Initialize SDK
269
+ await initialize_sdk()
270
+
271
+ try:
272
+ async with stdio_server() as (read_stream, write_stream):
273
+ await server.run(
274
+ read_stream,
275
+ write_stream,
276
+ server.create_initialization_options()
277
+ )
278
+ finally:
279
+ await cleanup_sdk()
280
+
281
+ if __name__ == "__main__":
282
+ run()
src/sdk_manager.py ADDED
@@ -0,0 +1,125 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ from breez_sdk_spark import (
5
+ BreezSdk,
6
+ connect,
7
+ ConnectRequest,
8
+ default_config,
9
+ Seed
10
+ )
11
+ from .config import Config
12
+
13
+ # Mock SDK for testing
14
+ class MockBreezSdk:
15
+ def __init__(self):
16
+ self.connected = True
17
+
18
+ async def disconnect(self):
19
+ self.connected = False
20
+
21
+ async def get_info(self, request=None):
22
+ return MockNodeInfo()
23
+
24
+ async def prepare_send_payment(self, request=None):
25
+ return MockPrepareResponse()
26
+
27
+ async def send_payment(self, request=None):
28
+ return MockSendPaymentResponse()
29
+
30
+ async def receive_payment(self, request=None):
31
+ return MockReceivePaymentResponse()
32
+
33
+ async def list_payments(self, request=None):
34
+ return MockListPaymentsResponse()
35
+
36
+ class MockNodeInfo:
37
+ def __init__(self):
38
+ self.id = "test_node_id_123456789"
39
+ self.balance_sats = 1000000
40
+ self.pending_incoming_sats = 0
41
+ self.pending_outgoing_sats = 50000
42
+ self.max_payable_sats = 950000
43
+ self.max_receivable_sats = 2000000
44
+ self.network = "mainnet"
45
+ self.channels = []
46
+ self.synced = True
47
+ self.block_height = 800000
48
+
49
+ class MockPrepareResponse:
50
+ def __init__(self):
51
+ pass
52
+
53
+ class MockSendPaymentResponse:
54
+ def __init__(self):
55
+ self.payment = MockPayment()
56
+ self.payment_hash = "test_payment_hash_123"
57
+
58
+ class MockReceivePaymentResponse:
59
+ def __init__(self):
60
+ self.payment_request = "lnbc1000n1p3knh2dpp5jfqh5..."
61
+ self.payment_hash = "test_invoice_hash_456"
62
+ self.fee_sats = 1
63
+
64
+ class MockPayment:
65
+ def __init__(self):
66
+ self.id = "test_payment_id_123"
67
+ self.timestamp = 1234567890
68
+ self.amount = 1000
69
+ self.fees = 10
70
+ self.payment_type = "PaymentType.SEND"
71
+ self.status = "PaymentStatus.COMPLETED"
72
+ self.destination = "lnbc..."
73
+ self.tx_id = "test_tx_id_789"
74
+ self.details = MockPaymentDetails()
75
+
76
+ class MockPaymentDetails:
77
+ def __init__(self):
78
+ self.payment_hash = "test_payment_hash_123"
79
+ self.preimage = "test_preimage_abc"
80
+ self.description = "Test payment"
81
+ self.invoice = "lnbc1000n1p3knh2d..."
82
+
83
+ class MockListPaymentsResponse:
84
+ def __init__(self):
85
+ self.payments = [MockPayment()]
86
+
87
+ class SDKManager:
88
+ def __init__(self):
89
+ self.config = Config()
90
+ self.sdk: BreezSdk = None
91
+ self.test_mode = os.getenv("BREEZ_TEST_MODE", "false").lower() == "true"
92
+
93
+ async def connect(self):
94
+ try:
95
+ if self.test_mode:
96
+ # Use mock SDK for testing
97
+ self.sdk = MockBreezSdk()
98
+ logging.info("Connected to Mock Breez SDK (test mode)")
99
+ else:
100
+ # Use real SDK
101
+ seed = Seed.MNEMONIC(mnemonic=self.config.mnemonic, passphrase=None)
102
+ config = default_config(network=self.config.network)
103
+ config.api_key = self.config.api_key
104
+
105
+ self.sdk = await connect(
106
+ request=ConnectRequest(
107
+ config=config,
108
+ seed=seed,
109
+ storage_dir=self.config.data_dir
110
+ )
111
+ )
112
+ logging.info("Connected to Breez SDK")
113
+ except Exception as e:
114
+ logging.error(f"Failed to connect: {e}")
115
+ raise
116
+
117
+ async def disconnect(self):
118
+ if self.sdk:
119
+ await self.sdk.disconnect()
120
+ logging.info("Disconnected from Breez SDK")
121
+
122
+ def get_sdk(self) -> BreezSdk:
123
+ if not self.sdk:
124
+ raise RuntimeError("SDK not connected")
125
+ return self.sdk