Merge pull request #1 from breez/fly

fly.io poc
This commit is contained in:
2025-04-30 08:20:35 +02:00
committed by GitHub
25 changed files with 877 additions and 150 deletions

2
.gitignore vendored
View File

@@ -128,6 +128,8 @@ celerybeat.pid
*.sage.py
# Environments
*/.env
*/.venv
.env
.venv
env/

155
README.md
View File

@@ -1,156 +1,11 @@
# Nodeless payments
This is a proof of concept implementation for deploying the Breez SDK (Nodeless implementation) as a lambda function to AWS. It provides a REST api with close to zero cost of hosting.
# Breez Nodeless SDK
Experimental deployments of Breez Nodeless SDK to various cloud platforms as REST API.
Currently implemented endpoints:
- /send_payment (bolt11)
- /receive_payment (bolt11)
- /list_payments
### API Key Security
- X-API-KEY header serves as authorization method for accessing the API. Anyone that knows the API url and API_SECRET can access your funds, so make sure to protect this secret and to generate a unique and long string. You can use generators like [this](https://1password.com/password-generator) or [this](https://www.uuidgenerator.net/).
- Encrypted secrets are stored in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) and are accessed each time any endpoint is called (in the background docker container is started for each REST API call).
## Requirements for deployment
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
- [Access to AWS account](https://signin.aws.amazon.com/signup?request_type=register)
- [Breez SDK - Nodeless implementation API key](https://breez.technology/request-api-key/#contact-us-form-sdk)
- 12 words BIP 39 seed (TBA: use Misty Breez to generate it)
## Deployment
Deployment to AWS with [cloudformation](./cloudformation.yaml).
### Install CLI
Follow [AWS guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to install it on your computer.
### Create credentials
There are several ways of creating credentials to deploy in AWS. Ideally, you want to generate temporary credentials that are gonna be revoked after this deployment. You can create create credentials that have the same permissions as your root account. This will enable you to run all the CLI commands. Follow these steps to create an access key:
* Select *Security Credentials* from your account's menu:
<img src="./docs/screenshot0.jpg" width="50%">
<img src="./docs/screenshot1.jpg" width="30%">
* Follow the steps to create an access key:
<img src="./docs/screenshot2.jpg" width="50%">
<img src="./docs/screenshot3.jpg" width="50%">
<img src="./docs/screenshot4.jpg" width="50%">
### Configure CLI
Now that you have AWS CLI installed and credentials ready, it's time for the last step of the requirements: configuring the AWS CLI to work with your account credentials.
You will also have to choose a default region where you want to deploy your API. You can see the list of all regions [here](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html). You should pick the region that is closest to your business. For quick reference:
* **US**: *us-east-1*, *us-west-1*
* **Europe**: *eu-central-1*, *eu-west-1*
* **LATAM**: *sa-east-1*
* **Asia**: *ap-southeast-1*
Once you have an API key, an API secret and region string, you're ready to configure the CLI.
Open a command line interface in your OS and type `aws configure` and press enter. Now, copy/paste the API key, press enter, then copy/paste the API secret and press enter. Do the same for the region string. You can leave the default output format blank.
Here's an example:
```
# aws configure
AWS Access Key ID [None]: AKIA44HIGHQYZHRTZ7WP
AWS Secret Access Key [None]: qKVd5nMA7y8DbEuvF6kFbKTcYrAow8rH9KDxWGkT
Default region name [None]: us-east-1
Default output format [None]:
```
### Create SSM parameters for Breez credentials
From the command line, run the following commands:
```
aws ssm put-parameter --name "/breez-nodeless/api_key" --value "<REPLACE_WITH_BREEZ_API_KEY>" --type SecureString
```
```
aws ssm put-parameter --name "/breez-nodeless/seed_phrase" --value "<REPLACE_WITH_SEED_WORDS>" --type SecureString
```
```
aws ssm put-parameter --name "/breez-nodeless/api_secret" --value "<REPLACE_WITH_DESIRED_API_AUTHENTICATION_KEY>" --type SecureString
```
### Deploy Cloudformation stack
* Download this configuration file: [cloudformation.yaml](https://raw.githubusercontent.com/breez/nodeless-payments/refs/heads/main/cloudformation.yaml).
* Deploy the stack:
```
aws cloudformation create-stack --stack-name breez-integration --template-body file://cloudformation.yaml --capabilities CAPABILITY_IAM
```
* Monitor the stack creation (wait until it changes to *CREATE_COMPLETE*):
```
aws cloudformation describe-stacks --stack-name breez-integration --query Stacks[0].StackStatus
```
* Retrieve the API endpoints:
```
aws cloudformation describe-stacks --stack-name breez-integration --query 'Stacks[0].Outputs'
```
Output should look like this:
```
root@2edec8635e65:/# aws cloudformation describe-stacks --stack-name breez-integration --query 'Stacks[0].Outputs'
[
{
"OutputKey": "ApiGatewayBaseURL",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod",
"Description": "Base URL for API Gateway"
},
{
"OutputKey": "SendEndpoint",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod/send_payment",
"Description": "Send endpoint URL"
},
{
"OutputKey": "PaymentsEndpoint",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod/list_payments",
"Description": "Payments endpoint URL"
},
{
"OutputKey": "ReceiveEndpoint",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod/receive_payment",
"Description": "Receive endpoint URL"
}
]
```
* If the deployment was successful, you should deactivate your API key now.
### Example usage
#### Python
You can use `example-client.py`file from this to test the functionality. Take Base URL from the output of last command (see *ApiGatewayBaseURL* example above) and API_SECRET and edit the `example-client.py` with correct values
```
API_URL = "YOUR-URL-HERE"
API_KEY = "YOUR-SECRET-HERE"
```
For example-client to work, you need to have python installed together with requests library:
```
pip install requests
```
Then run:
```
python example-client.py
```
#### curl
If you don't have python installed, you can also just run a curl command.
For example, for the *list_payments* endpoint, run:
```
curl -X POST "<YOUR-URL-HERE>/list_payments" -H "Content-Type: application/json" -H "x-api-key: <API_SECRET>" -d '{}'
```
### Cleanup
To remove the stack you deployed you need to run the command delete-stack. This command starts the process to delete the stack, but it takes a while.
```
aws cloudformation delete-stack --stack-name breez-integration
```
You can use the same status command to see if its been successfully deleted:
```
aws cloudformation describe-stacks --stack-name breez-integration --query Stacks[0].StackStatus
```
You should also cleanup the parameters:
```
aws ssm delete-parameter --name "/breez-nodeless/api_key"
aws ssm delete-parameter --name "/breez-nodeless/seed_phrase"
aws ssm delete-parameter --name "/breez-nodeless/api_secret"
```
## Options
- [fly.io](./fly/README.md) - deploy fly.io app
- [aws lambda](./lambda/README.md) - lambda functions (wip)

6
fly/.env.example Normal file
View File

@@ -0,0 +1,6 @@
# Breez SDK configuration
BREEZ_API_KEY=your_breez_api_key_here
SEED_PHRASE=your_mnemonic_seed_phrase_here
# API security
API_SECRET=your_api_secret_here

37
fly/DEV.md Normal file
View File

@@ -0,0 +1,37 @@
### Local Development
1. Clone the repository:
```bash
git clone https://github.com/breez/nodeless-payments.git
cd nodeless-payments/fly
```
2. Install dependencies with Poetry:
```bash
poetry install
```
3. Create a `.env` file with your configuration:
```bash
cp .env.example .env
# Edit .env file with your actual credentials
```
4. Run the application:
```bash
poetry run uvicorn main:app --reload
```
5. Visit `http://localhost:8000/docs` in your browser to see the API documentation
### Docker
1. Build the Docker image:
```bash
docker build -t breez-nodeless .
```
2. Run the container:
```bash
docker run -p 8000:8000 --env-file .env breez-nodeless
```

49
fly/Dockerfile Normal file
View File

@@ -0,0 +1,49 @@
FROM ubuntu:24.04
WORKDIR /app
# Install system dependencies and Python 3.12
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
curl \
python3 \
python3-venv \
python3-pip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set python and pip alternatives
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 && \
update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
# Install Poetry
RUN pip install poetry --break-system-packages
# Copy project files
COPY pyproject.toml .
COPY main.py .
# Create a README.md file if it doesn't exist to satisfy Poetry
RUN touch README.md
# Copy environment file template
COPY .env.example .env
# Configure Poetry to not create a virtual environment
RUN poetry config virtualenvs.create false
# Install dependencies without installing the project itself
RUN poetry install --no-interaction --no-ansi --no-root
# Create tmp directory for Breez SDK
RUN mkdir -p ./tmp
# Expose the port
EXPOSE 8000
# Set environment variables
ENV PYTHONUNBUFFERED=1
# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

129
fly/README.md Normal file
View File

@@ -0,0 +1,129 @@
# Breez Nodeless FastAPI
A FastAPI implementation of the Breez Nodeless SDK. This service provides a REST API for sending and receiving payments via the Lightning Network running on fly.io.
## Prerequisites
- Python 3.10+
- Poetry (package manager)
- Breez Nodeless SDK API key (get one from [Breez](https://breez.technology/))
- A valid seed phrase for the Breez SDK wallet
## Installation
## Deployment to Fly.io
1. Install the Fly CLI:
```bash
# macOS
brew install flyctl
# Linux
curl -L https://fly.io/install.sh | sh
# Windows
pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"
```
2. Log in to Fly:
```bash
fly auth login
```
3. Launch the app:
```bash
fly launch
```
4. Set secrets:
```bash
fly secrets set BREEZ_API_KEY=your_breez_api_key
fly secrets set SEED_PHRASE=your_mnemonic_seed_phrase
fly secrets set API_SECRET=your_api_secret
```
5. Deploy the app:
```bash
fly deploy
```
## API Endpoints
### Health Check
```
GET /health
```
Check if the API is up and running.
### List Payments
```
GET /list_payments
```
Query Parameters:
- `from_timestamp` (optional): Filter payments from this timestamp
- `to_timestamp` (optional): Filter payments to this timestamp
- `offset` (optional): Pagination offset
- `limit` (optional): Pagination limit
### Receive Payment
```
POST /receive_payment
```
Request Body:
```json
{
"amount": 10000,
"method": "LIGHTNING"
}
```
### Send Payment
```
POST /send_payment
```
Request Body:
```json
{
"destination": "lnbc...",
"amount": 10000,
"drain": false
}
```
## Client Usage
See `client.py` for a Python client implementation and example usage.
### Example usage
#### Python
You can use `example-client.py`file from this to test the functionality. Take the URL flyctl returned at deploy and API_SECRET and edit the `example-client.py` with correct values
```
API_URL = "YOUR-URL-HERE"
API_KEY = "YOUR-SECRET-HERE"
```
For example-client to work, you need to have python installed together with requests library:
```
pip install requests
```
Then run:
```
python example-client.py
```
#### curl
If you don't have python installed, you can also just run a curl command.
For example, for the *list_payments* endpoint, run:
```
curl -X POST "<YOUR-URL-HERE>/list_payments" -H "Content-Type: application/json" -H "x-api-key: <API_SECRET>" -d '{}'
```

148
fly/example_client.py Normal file
View File

@@ -0,0 +1,148 @@
import requests
import json
class BreezClient:
def __init__(self, api_url, api_key):
"""
Initialize the Breez client.
Args:
api_url (str): The base URL of the Breez API
api_key (str): The API key for authentication
"""
self.api_url = api_url
self.headers = {
'Content-Type': 'application/json',
'x-api-key': api_key
}
def list_payments(self, from_timestamp=None, to_timestamp=None, offset=None, limit=None):
"""
List all payments with optional filters.
Args:
from_timestamp (int, optional): Filter payments from this timestamp
to_timestamp (int, optional): Filter payments to this timestamp
offset (int, optional): Pagination offset
limit (int, optional): Pagination limit
Returns:
dict: JSON response with payment list
"""
params = {}
if from_timestamp is not None:
params["from_timestamp"] = from_timestamp
if to_timestamp is not None:
params["to_timestamp"] = to_timestamp
if offset is not None:
params["offset"] = offset
if limit is not None:
params["limit"] = limit
response = requests.get(
f"{self.api_url}/list_payments",
params=params,
headers=self.headers
)
return self._handle_response(response)
def receive_payment(self, amount, method="LIGHTNING"):
"""
Generate a Lightning/Bitcoin/Liquid invoice to receive payment.
Args:
amount (int): Amount in satoshis to receive
method (str, optional): Payment method (LIGHTNING or LIQUID)
Returns:
dict: JSON response with invoice details
"""
payload = {
"amount": amount,
"method": method
}
response = requests.post(
f"{self.api_url}/receive_payment",
json=payload,
headers=self.headers
)
return self._handle_response(response)
def send_payment(self, destination, amount=None, drain=False):
"""
Send a payment via Lightning or Liquid.
Args:
destination (str): Payment destination (invoice or address)
amount (int, optional): Amount in satoshis to send
drain (bool, optional): Whether to drain the wallet
Returns:
dict: JSON response with payment details
"""
payload = {
"destination": destination
}
if amount is not None:
payload["amount"] = amount
if drain:
payload["drain"] = True
response = requests.post(
f"{self.api_url}/send_payment",
json=payload,
headers=self.headers
)
return self._handle_response(response)
def health_check(self):
"""
Check if the API is healthy and responding.
Returns:
dict: JSON response with health status
"""
response = requests.get(f"{self.api_url}/health")
return self._handle_response(response)
def _handle_response(self, response):
"""Helper method to handle API responses."""
try:
if response.status_code == 200:
return response.json()
else:
return {
"error": f"Request failed with status {response.status_code}",
"details": response.text
}
except Exception as e:
return {"error": f"Failed to process response: {str(e)}"}
# Example usage
if __name__ == "__main__":
# Configuration
API_URL = "http://localhost:8000" # Change to your deployed API URL
API_KEY = "" # Set your API key here
# Initialize client
breez = BreezClient(api_url=API_URL, api_key=API_KEY)
# Check API health
print("🔍 Checking API health...")
print(breez.health_check())
# List payments
print("\n🔄 Listing Payments...")
print(json.dumps(breez.list_payments(), indent=2))
# Generate an invoice to receive payment
#print("\n💰 Generating invoice to receive payment...")
#invoice = breez.receive_payment(amount=1000, method="LIGHTNING")
#print(json.dumps(invoice, indent=2))
#print(f"Invoice: {invoice.get('destination', 'Error generating invoice')}")
# Send payment example (commented out for safety)
#print("\n🚀 Sending Payment...")
#result = breez.send_payment(destination="", amount=1111)
#print(json.dumps(result, indent=2))

37
fly/fly.toml Normal file
View File

@@ -0,0 +1,37 @@
# fly.toml app configuration file generated for breez-nodeless-api on 2025-04-29T18:26:24+02:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = 'breez-nodeless-api'
primary_region = 'ewr'
[build]
dockerfile = 'Dockerfile'
[env]
PORT = '8000'
[[mounts]]
source = 'breez_data'
destination = '/app/tmp'
[http_service]
internal_port = 8000
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 1
processes = ['app']
[http_service.concurrency]
type = 'connections'
hard_limit = 1000
soft_limit = 500
[http_service.http_options]
[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1

289
fly/main.py Normal file
View File

@@ -0,0 +1,289 @@
from fastapi import FastAPI, Depends, HTTPException, Header, Query
from fastapi.security.api_key import APIKeyHeader
from pydantic import BaseModel, Field
from typing import Optional, Dict, List, Any, Union
import os
import json
from dotenv import load_dotenv
from enum import Enum
from breez_sdk_liquid import (
LiquidNetwork,
PayAmount,
ConnectRequest,
PrepareSendRequest,
SendPaymentRequest,
PrepareReceiveRequest,
ReceivePaymentRequest,
EventListener,
SdkEvent,
connect,
default_config,
PaymentMethod,
ListPaymentsRequest,
ReceiveAmount
)
# Load environment variables
load_dotenv()
# Create FastAPI app
app = FastAPI(
title="Breez Nodeless Payments API",
description="A FastAPI implementation of Breez SDK for Lightning/Liquid payments",
version="1.0.0"
)
# API Key authentication
API_KEY_NAME = "x-api-key"
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
# Configure API key from environment variable
API_KEY = os.getenv("API_SECRET")
BREEZ_API_KEY = os.getenv("BREEZ_API_KEY")
SEED_PHRASE = os.getenv("SEED_PHRASE")
# Models for request/response
class PaymentMethodEnum(str, Enum):
LIGHTNING = "LIGHTNING"
LIQUID = "LIQUID"
class ReceivePaymentBody(BaseModel):
amount: int = Field(..., description="Amount in satoshis to receive")
method: PaymentMethodEnum = Field(PaymentMethodEnum.LIGHTNING, description="Payment method")
class SendPaymentBody(BaseModel):
destination: str = Field(..., description="Payment destination (invoice or address)")
amount: Optional[int] = Field(None, description="Amount in satoshis to send")
drain: bool = Field(False, description="Whether to drain the wallet")
class ListPaymentsParams:
def __init__(
self,
from_timestamp: Optional[int] = Query(None, description="Filter payments from this timestamp"),
to_timestamp: Optional[int] = Query(None, description="Filter payments to this timestamp"),
offset: Optional[int] = Query(None, description="Pagination offset"),
limit: Optional[int] = Query(None, description="Pagination limit")
):
self.from_timestamp = from_timestamp
self.to_timestamp = to_timestamp
self.offset = offset
self.limit = limit
class PaymentResponse(BaseModel):
timestamp: int
amount_sat: int
fees_sat: int
payment_type: str
status: str
details: str
destination: str
tx_id: Optional[str] = None
class PaymentListResponse(BaseModel):
payments: List[PaymentResponse]
class ReceiveResponse(BaseModel):
destination: str
fees_sat: int
class SendResponse(BaseModel):
payment_status: str
destination: str
fees_sat: int
# Breez SDK Event Listener
class SdkListener(EventListener):
def __init__(self):
self.synced = False
self.paid = []
def on_event(self, event):
if isinstance(event, SdkEvent.SYNCED):
self.synced = True
if isinstance(event, SdkEvent.PAYMENT_SUCCEEDED):
if event.details.destination:
self.paid.append(event.details.destination)
def is_paid(self, destination: str):
return destination in self.paid
# Initialize Breez SDK client
class BreezClient:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(BreezClient, cls).__new__(cls)
cls._instance.initialize()
return cls._instance
def initialize(self):
if not BREEZ_API_KEY:
raise Exception("Missing Breez API key in environment variables")
if not SEED_PHRASE:
raise Exception("Missing seed phrase in environment variables")
config = default_config(LiquidNetwork.MAINNET, BREEZ_API_KEY)
config.working_dir = './tmp'
connect_request = ConnectRequest(config=config, mnemonic=SEED_PHRASE)
self.instance = connect(connect_request)
self.listener = SdkListener()
self.instance.add_event_listener(self.listener)
self.is_initialized = True
def wait_for_sync(self, timeout_seconds: int = 30):
"""Wait for the SDK to sync before proceeding."""
import time
start_time = time.time()
while time.time() - start_time < timeout_seconds:
if self.listener.synced:
return True
time.sleep(1)
raise Exception("Sync timeout: SDK did not sync within the allocated time.")
def list_payments(self, params: ListPaymentsParams) -> List[Dict[str, Any]]:
self.wait_for_sync()
req = ListPaymentsRequest(
from_timestamp=params.from_timestamp,
to_timestamp=params.to_timestamp,
offset=params.offset,
limit=params.limit
)
payments = self.instance.list_payments(req)
payment_list = []
for payment in payments:
payment_dict = {
'timestamp': payment.timestamp,
'amount_sat': payment.amount_sat,
'fees_sat': payment.fees_sat,
'payment_type': str(payment.payment_type),
'status': str(payment.status),
'details': str(payment.details),
'destination': payment.destination,
'tx_id': payment.tx_id
}
payment_list.append(payment_dict)
return payment_list
def receive_payment(self, amount: int, payment_method: str = 'LIGHTNING') -> Dict[str, Any]:
try:
self.wait_for_sync()
except Exception as e:
raise Exception(f"Error during SDK sync: {e}")
try:
if isinstance(amount, int):
receive_amount = ReceiveAmount.BITCOIN(amount)
else:
receive_amount = amount
prepare_req = PrepareReceiveRequest(payment_method=getattr(PaymentMethod, payment_method), amount=receive_amount)
except Exception as e:
raise Exception(f"Error preparing receive request: {e}")
try:
prepare_res = self.instance.prepare_receive_payment(prepare_req)
except Exception as e:
raise Exception(f"Error preparing receive payment: {e}")
try:
req = ReceivePaymentRequest(prepare_response=prepare_res)
res = self.instance.receive_payment(req)
except Exception as e:
raise Exception(f"Error receiving payment: {e}")
return {
'destination': res.destination,
'fees_sat': prepare_res.fees_sat
}
def send_payment(self, destination: str, amount: Optional[int] = None, drain: bool = False) -> Dict[str, Any]:
self.wait_for_sync()
pay_amount = PayAmount.DRAIN if drain else PayAmount.BITCOIN(amount) if amount else None
prepare_req = PrepareSendRequest(destination=destination, amount=pay_amount)
prepare_res = self.instance.prepare_send_payment(prepare_req)
req = SendPaymentRequest(prepare_response=prepare_res)
res = self.instance.send_payment(req)
return {
'payment_status': 'success',
'destination': res.payment.destination,
'fees_sat': prepare_res.fees_sat
}
# Dependency for API key validation
async def get_api_key(api_key: str = Header(None, alias=API_KEY_NAME)):
if not API_KEY:
raise HTTPException(status_code=500, detail="API key not configured on server")
if api_key != API_KEY:
raise HTTPException(
status_code=401,
detail="Invalid API Key",
headers={"WWW-Authenticate": "ApiKey"},
)
return api_key
# Dependency for Breez client
def get_breez_client():
try:
return BreezClient()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to initialize Breez client: {str(e)}")
# API Routes
@app.get("/list_payments", response_model=PaymentListResponse)
async def list_payments(
params: ListPaymentsParams = Depends(),
api_key: str = Depends(get_api_key),
client: BreezClient = Depends(get_breez_client)
):
try:
payments = client.list_payments(params)
return {"payments": payments}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/receive_payment", response_model=ReceiveResponse)
async def receive_payment(
request: ReceivePaymentBody,
api_key: str = Depends(get_api_key),
client: BreezClient = Depends(get_breez_client)
):
try:
result = client.receive_payment(
amount=request.amount,
payment_method=request.method
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/send_payment", response_model=SendResponse)
async def send_payment(
request: SendPaymentBody,
api_key: str = Depends(get_api_key),
client: BreezClient = Depends(get_breez_client)
):
try:
result = client.send_payment(
destination=request.destination,
amount=request.amount,
drain=request.drain
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Health check endpoint
@app.get("/health")
async def health():
return {"status": "ok"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

19
fly/pyproject.toml Normal file
View File

@@ -0,0 +1,19 @@
[tool.poetry]
name = "breez-fastapi"
version = "0.1.0"
description = "A FastAPI implementation of Breez SDK for Lightning/Liquid payments"
authors = ["Your Name <your.email@example.com>"]
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.111.0"
uvicorn = {extras = ["standard"], version = "^0.30.1"}
breez-sdk-liquid = "*"
python-dotenv = "^1.0.1"
requests = "^2.31.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

156
lambda/README.md Normal file
View File

@@ -0,0 +1,156 @@
# Nodeless payments
This is a proof of concept implementation for deploying the Breez SDK (Nodeless implementation) as a lambda function to AWS. It provides a REST api with close to zero cost of hosting.
Currently implemented endpoints:
- /send_payment (bolt11)
- /receive_payment (bolt11)
- /list_payments
### API Key Security
- X-API-KEY header serves as authorization method for accessing the API. Anyone that knows the API url and API_SECRET can access your funds, so make sure to protect this secret and to generate a unique and long string. You can use generators like [this](https://1password.com/password-generator) or [this](https://www.uuidgenerator.net/).
- Encrypted secrets are stored in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) and are accessed each time any endpoint is called (in the background docker container is started for each REST API call).
## Requirements for deployment
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
- [Access to AWS account](https://signin.aws.amazon.com/signup?request_type=register)
- [Breez SDK - Nodeless implementation API key](https://breez.technology/request-api-key/#contact-us-form-sdk)
- 12 words BIP 39 seed (TBA: use Misty Breez to generate it)
## Deployment
Deployment to AWS with [cloudformation](./cloudformation.yaml).
### Install CLI
Follow [AWS guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to install it on your computer.
### Create credentials
There are several ways of creating credentials to deploy in AWS. Ideally, you want to generate temporary credentials that are gonna be revoked after this deployment. You can create create credentials that have the same permissions as your root account. This will enable you to run all the CLI commands. Follow these steps to create an access key:
* Select *Security Credentials* from your account's menu:
<img src="./docs/screenshot0.jpg" width="50%">
<img src="./docs/screenshot1.jpg" width="30%">
* Follow the steps to create an access key:
<img src="./docs/screenshot2.jpg" width="50%">
<img src="./docs/screenshot3.jpg" width="50%">
<img src="./docs/screenshot4.jpg" width="50%">
### Configure CLI
Now that you have AWS CLI installed and credentials ready, it's time for the last step of the requirements: configuring the AWS CLI to work with your account credentials.
You will also have to choose a default region where you want to deploy your API. You can see the list of all regions [here](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html). You should pick the region that is closest to your business. For quick reference:
* **US**: *us-east-1*, *us-west-1*
* **Europe**: *eu-central-1*, *eu-west-1*
* **LATAM**: *sa-east-1*
* **Asia**: *ap-southeast-1*
Once you have an API key, an API secret and region string, you're ready to configure the CLI.
Open a command line interface in your OS and type `aws configure` and press enter. Now, copy/paste the API key, press enter, then copy/paste the API secret and press enter. Do the same for the region string. You can leave the default output format blank.
Here's an example:
```
# aws configure
AWS Access Key ID [None]: AKIA44HIGHQYZHRTZ7WP
AWS Secret Access Key [None]: qKVd5nMA7y8DbEuvF6kFbKTcYrAow8rH9KDxWGkT
Default region name [None]: us-east-1
Default output format [None]:
```
### Create SSM parameters for Breez credentials
From the command line, run the following commands:
```
aws ssm put-parameter --name "/breez-nodeless/api_key" --value "<REPLACE_WITH_BREEZ_API_KEY>" --type SecureString
```
```
aws ssm put-parameter --name "/breez-nodeless/seed_phrase" --value "<REPLACE_WITH_SEED_WORDS>" --type SecureString
```
```
aws ssm put-parameter --name "/breez-nodeless/api_secret" --value "<REPLACE_WITH_DESIRED_API_AUTHENTICATION_KEY>" --type SecureString
```
### Deploy Cloudformation stack
* Download this configuration file: [cloudformation.yaml](https://raw.githubusercontent.com/breez/nodeless-payments/refs/heads/main/cloudformation.yaml).
* Deploy the stack:
```
aws cloudformation create-stack --stack-name breez-integration --template-body file://cloudformation.yaml --capabilities CAPABILITY_IAM
```
* Monitor the stack creation (wait until it changes to *CREATE_COMPLETE*):
```
aws cloudformation describe-stacks --stack-name breez-integration --query Stacks[0].StackStatus
```
* Retrieve the API endpoints:
```
aws cloudformation describe-stacks --stack-name breez-integration --query 'Stacks[0].Outputs'
```
Output should look like this:
```
root@2edec8635e65:/# aws cloudformation describe-stacks --stack-name breez-integration --query 'Stacks[0].Outputs'
[
{
"OutputKey": "ApiGatewayBaseURL",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod",
"Description": "Base URL for API Gateway"
},
{
"OutputKey": "SendEndpoint",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod/send_payment",
"Description": "Send endpoint URL"
},
{
"OutputKey": "PaymentsEndpoint",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod/list_payments",
"Description": "Payments endpoint URL"
},
{
"OutputKey": "ReceiveEndpoint",
"OutputValue": "https://yxzjorems5.execute-api.us-east-1.amazonaws.com/prod/receive_payment",
"Description": "Receive endpoint URL"
}
]
```
* If the deployment was successful, you should deactivate your API key now.
### Example usage
#### Python
You can use `example-client.py`file from this to test the functionality. Take Base URL from the output of last command (see *ApiGatewayBaseURL* example above) and API_SECRET and edit the `example-client.py` with correct values
```
API_URL = "YOUR-URL-HERE"
API_KEY = "YOUR-SECRET-HERE"
```
For example-client to work, you need to have python installed together with requests library:
```
pip install requests
```
Then run:
```
python example-client.py
```
#### curl
If you don't have python installed, you can also just run a curl command.
For example, for the *list_payments* endpoint, run:
```
curl -X POST "<YOUR-URL-HERE>/list_payments" -H "Content-Type: application/json" -H "x-api-key: <API_SECRET>" -d '{}'
```
### Cleanup
To remove the stack you deployed you need to run the command delete-stack. This command starts the process to delete the stack, but it takes a while.
```
aws cloudformation delete-stack --stack-name breez-integration
```
You can use the same status command to see if its been successfully deleted:
```
aws cloudformation describe-stacks --stack-name breez-integration --query Stacks[0].StackStatus
```
You should also cleanup the parameters:
```
aws ssm delete-parameter --name "/breez-nodeless/api_key"
aws ssm delete-parameter --name "/breez-nodeless/seed_phrase"
aws ssm delete-parameter --name "/breez-nodeless/api_secret"
```

View File

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 338 KiB

View File

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View File

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB