mirror of
https://github.com/aljazceru/payments-rest-api.git
synced 2025-12-20 23:14:22 +01:00
creating nodeless lib
This commit is contained in:
@@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
python3 \
|
python3 \
|
||||||
python3-venv \
|
python3-venv \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
python3-full \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
@@ -17,24 +18,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 && \
|
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 && \
|
||||||
update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
|
update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
|
||||||
|
|
||||||
# Install Poetry
|
# Create a virtual environment
|
||||||
RUN pip install poetry --break-system-packages
|
RUN python3 -m venv /app/venv
|
||||||
|
|
||||||
|
# Install Poetry in the virtual environment
|
||||||
|
RUN /app/venv/bin/pip install poetry
|
||||||
|
|
||||||
# Copy project files
|
# Copy project files
|
||||||
COPY pyproject.toml .
|
COPY fly/pyproject.toml .
|
||||||
COPY main.py .
|
COPY fly/main.py .
|
||||||
|
COPY nodeless.py .
|
||||||
|
# Copy environment file template
|
||||||
|
COPY fly/.env.example .env
|
||||||
|
|
||||||
# Create a README.md file if it doesn't exist to satisfy Poetry
|
# Create a README.md file if it doesn't exist to satisfy Poetry
|
||||||
RUN touch README.md
|
RUN touch README.md
|
||||||
|
|
||||||
# Copy environment file template
|
# Install dependencies using the virtual environment's pip
|
||||||
COPY .env.example .env
|
RUN /app/venv/bin/poetry install --no-interaction --no-ansi --no-root
|
||||||
|
|
||||||
# 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
|
# Create tmp directory for Breez SDK
|
||||||
RUN mkdir -p ./tmp
|
RUN mkdir -p ./tmp
|
||||||
@@ -44,6 +45,7 @@ EXPOSE 8000
|
|||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PATH="/app/venv/bin:$PATH"
|
||||||
|
|
||||||
# Run the application
|
# Run the application (now using the venv's Python)
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["/app/venv/bin/uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
@@ -46,21 +46,15 @@ class BreezClient:
|
|||||||
)
|
)
|
||||||
return self._handle_response(response)
|
return self._handle_response(response)
|
||||||
|
|
||||||
def receive_payment(self, amount, method="LIGHTNING"):
|
def receive_payment(self, amount, method="LIGHTNING", description=None, asset_id=None):
|
||||||
"""
|
|
||||||
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 = {
|
payload = {
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
"method": method
|
"method": method
|
||||||
}
|
}
|
||||||
|
if description is not None:
|
||||||
|
payload["description"] = description
|
||||||
|
if asset_id is not None:
|
||||||
|
payload["asset_id"] = asset_id
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.api_url}/receive_payment",
|
f"{self.api_url}/receive_payment",
|
||||||
json=payload,
|
json=payload,
|
||||||
@@ -68,26 +62,17 @@ class BreezClient:
|
|||||||
)
|
)
|
||||||
return self._handle_response(response)
|
return self._handle_response(response)
|
||||||
|
|
||||||
def send_payment(self, destination, amount=None, drain=False):
|
def send_payment(self, destination, amount_sat=None, amount_asset=None, asset_id=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 = {
|
payload = {
|
||||||
"destination": destination
|
"destination": destination,
|
||||||
|
"drain": drain
|
||||||
}
|
}
|
||||||
if amount is not None:
|
if amount_sat is not None:
|
||||||
payload["amount"] = amount
|
payload["amount_sat"] = amount_sat
|
||||||
if drain:
|
if amount_asset is not None:
|
||||||
payload["drain"] = True
|
payload["amount_asset"] = amount_asset
|
||||||
|
if asset_id is not None:
|
||||||
|
payload["asset_id"] = asset_id
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.api_url}/send_payment",
|
f"{self.api_url}/send_payment",
|
||||||
json=payload,
|
json=payload,
|
||||||
@@ -105,6 +90,86 @@ class BreezClient:
|
|||||||
response = requests.get(f"{self.api_url}/health")
|
response = requests.get(f"{self.api_url}/health")
|
||||||
return self._handle_response(response)
|
return self._handle_response(response)
|
||||||
|
|
||||||
|
def send_onchain(self, address, amount_sat=None, drain=False, fee_rate_sat_per_vbyte=None):
|
||||||
|
"""
|
||||||
|
Send an onchain (Bitcoin or Liquid) payment.
|
||||||
|
Args:
|
||||||
|
address (str): Destination address
|
||||||
|
amount_sat (int, optional): Amount in satoshis
|
||||||
|
drain (bool, optional): Drain all funds
|
||||||
|
fee_rate_sat_per_vbyte (int, optional): Custom fee rate
|
||||||
|
Returns:
|
||||||
|
dict: JSON response
|
||||||
|
"""
|
||||||
|
payload = {
|
||||||
|
"address": address,
|
||||||
|
"drain": drain
|
||||||
|
}
|
||||||
|
if amount_sat is not None:
|
||||||
|
payload["amount_sat"] = amount_sat
|
||||||
|
if fee_rate_sat_per_vbyte is not None:
|
||||||
|
payload["fee_rate_sat_per_vbyte"] = fee_rate_sat_per_vbyte
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.api_url}/send_onchain",
|
||||||
|
json=payload,
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
return self._handle_response(response)
|
||||||
|
|
||||||
|
# LNURL-related endpoints (all under /v1/ln/)
|
||||||
|
def parse_input(self, input_str):
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.api_url}/v1/ln/parse_input",
|
||||||
|
json={"input": input_str},
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
return self._handle_response(response)
|
||||||
|
|
||||||
|
def prepare_lnurl_pay(self, data, amount_sat, comment=None, validate_success_action_url=True):
|
||||||
|
payload = {
|
||||||
|
"data": data,
|
||||||
|
"amount_sat": amount_sat,
|
||||||
|
"comment": comment,
|
||||||
|
"validate_success_action_url": validate_success_action_url
|
||||||
|
}
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.api_url}/v1/ln/prepare_lnurl_pay",
|
||||||
|
json=payload,
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
return self._handle_response(response)
|
||||||
|
|
||||||
|
def lnurl_pay(self, prepare_response):
|
||||||
|
payload = {"prepare_response": prepare_response}
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.api_url}/v1/ln/lnurl_pay",
|
||||||
|
json=payload,
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
return self._handle_response(response)
|
||||||
|
|
||||||
|
def lnurl_auth(self, data):
|
||||||
|
payload = {"data": data}
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.api_url}/v1/ln/lnurl_auth",
|
||||||
|
json=payload,
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
return self._handle_response(response)
|
||||||
|
|
||||||
|
def lnurl_withdraw(self, data, amount_msat, comment=None):
|
||||||
|
payload = {
|
||||||
|
"data": data,
|
||||||
|
"amount_msat": amount_msat,
|
||||||
|
"comment": comment
|
||||||
|
}
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.api_url}/v1/ln/lnurl_withdraw",
|
||||||
|
json=payload,
|
||||||
|
headers=self.headers
|
||||||
|
)
|
||||||
|
return self._handle_response(response)
|
||||||
|
|
||||||
def _handle_response(self, response):
|
def _handle_response(self, response):
|
||||||
"""Helper method to handle API responses."""
|
"""Helper method to handle API responses."""
|
||||||
try:
|
try:
|
||||||
@@ -123,7 +188,7 @@ class BreezClient:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Configuration
|
# Configuration
|
||||||
API_URL = "http://localhost:8000" # Change to your deployed API URL
|
API_URL = "http://localhost:8000" # Change to your deployed API URL
|
||||||
API_KEY = "" # Set your API key here
|
API_KEY = "kurac" # Set your API key here
|
||||||
|
|
||||||
# Initialize client
|
# Initialize client
|
||||||
breez = BreezClient(api_url=API_URL, api_key=API_KEY)
|
breez = BreezClient(api_url=API_URL, api_key=API_KEY)
|
||||||
@@ -136,13 +201,28 @@ if __name__ == "__main__":
|
|||||||
print("\n🔄 Listing Payments...")
|
print("\n🔄 Listing Payments...")
|
||||||
print(json.dumps(breez.list_payments(), indent=2))
|
print(json.dumps(breez.list_payments(), indent=2))
|
||||||
|
|
||||||
# Generate an invoice to receive payment
|
# LNURL Example Usage
|
||||||
#print("\n💰 Generating invoice to receive payment...")
|
# lnurl = "lnurl1dp68gurn8ghj7mrww4exctnrdakj7mrww4exctnrdakj7mrww4exctnrdakj7" # Replace with a real LNURL
|
||||||
#invoice = breez.receive_payment(amount=1000, method="LIGHTNING")
|
# print("\n🔎 Parsing LNURL...")
|
||||||
#print(json.dumps(invoice, indent=2))
|
# parsed = breez.parse_input(lnurl)
|
||||||
#print(f"Invoice: {invoice.get('destination', 'Error generating invoice')}")
|
# print(json.dumps(parsed, indent=2))
|
||||||
|
# if parsed.get("type") == "LN_URL_PAY":
|
||||||
# Send payment example (commented out for safety)
|
# print("\n📝 Preparing LNURL-Pay...")
|
||||||
#print("\n🚀 Sending Payment...")
|
# prepare = breez.prepare_lnurl_pay(parsed["data"], amount_sat=1000)
|
||||||
#result = breez.send_payment(destination="", amount=1111)
|
# print(json.dumps(prepare, indent=2))
|
||||||
|
# print("\n🚀 Executing LNURL-Pay...")
|
||||||
|
# result = breez.lnurl_pay(prepare)
|
||||||
|
# print(json.dumps(result, indent=2))
|
||||||
|
# elif parsed.get("type") == "LN_URL_AUTH":
|
||||||
|
# print("\n🔐 Executing LNURL-Auth...")
|
||||||
|
# result = breez.lnurl_auth(parsed["data"])
|
||||||
|
# print(json.dumps(result, indent=2))
|
||||||
|
# elif parsed.get("type") == "LN_URL_WITHDRAW":
|
||||||
|
# print("\n💸 Executing LNURL-Withdraw...")
|
||||||
|
# result = breez.lnurl_withdraw(parsed["data"], amount_msat=1000_000)
|
||||||
|
# print(json.dumps(result, indent=2))
|
||||||
|
|
||||||
|
# Onchain payment example (commented out for safety)
|
||||||
|
# print("\n⛓️ Sending Onchain Payment...")
|
||||||
|
# result = breez.send_onchain(address="bitcoin_address_here", amount_sat=10000)
|
||||||
# print(json.dumps(result, indent=2))
|
# print(json.dumps(result, indent=2))
|
||||||
360
fly/main.py
360
fly/main.py
@@ -1,73 +1,53 @@
|
|||||||
from fastapi import FastAPI, Depends, HTTPException, Header, Query
|
from fastapi import FastAPI, Depends, HTTPException, Header, Query, APIRouter
|
||||||
from fastapi.security.api_key import APIKeyHeader
|
from fastapi.security.api_key import APIKeyHeader
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from typing import Optional, Dict, List, Any, Union
|
from typing import Optional, Dict, List, Any, Union
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from breez_sdk_liquid import (
|
from nodeless import PaymentHandler
|
||||||
LiquidNetwork,
|
|
||||||
PayAmount,
|
|
||||||
ConnectRequest,
|
|
||||||
PrepareSendRequest,
|
|
||||||
SendPaymentRequest,
|
|
||||||
PrepareReceiveRequest,
|
|
||||||
ReceivePaymentRequest,
|
|
||||||
EventListener,
|
|
||||||
SdkEvent,
|
|
||||||
connect,
|
|
||||||
default_config,
|
|
||||||
PaymentMethod,
|
|
||||||
ListPaymentsRequest,
|
|
||||||
ReceiveAmount
|
|
||||||
)
|
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# Create FastAPI app
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Breez Nodeless Payments API",
|
title="Breez Nodeless Payments API",
|
||||||
description="A FastAPI implementation of Breez SDK for Lightning/Liquid payments",
|
description="A FastAPI implementation of Breez SDK for Lightning/Liquid payments",
|
||||||
version="1.0.0"
|
version="1.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
# API Key authentication
|
|
||||||
API_KEY_NAME = "x-api-key"
|
API_KEY_NAME = "x-api-key"
|
||||||
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
||||||
|
|
||||||
# Configure API key from environment variable
|
|
||||||
API_KEY = os.getenv("API_SECRET")
|
API_KEY = os.getenv("API_SECRET")
|
||||||
BREEZ_API_KEY = os.getenv("BREEZ_API_KEY")
|
|
||||||
SEED_PHRASE = os.getenv("SEED_PHRASE")
|
|
||||||
|
|
||||||
# Models for request/response
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
ln_router = APIRouter(prefix="/v1/lnurl", tags=["lnurl"])
|
||||||
|
|
||||||
|
# --- Models ---
|
||||||
class PaymentMethodEnum(str, Enum):
|
class PaymentMethodEnum(str, Enum):
|
||||||
LIGHTNING = "LIGHTNING"
|
LIGHTNING = "LIGHTNING"
|
||||||
LIQUID = "LIQUID"
|
BITCOIN_ADDRESS = "BITCOIN_ADDRESS"
|
||||||
|
LIQUID_ADDRESS = "LIQUID_ADDRESS"
|
||||||
|
|
||||||
class ReceivePaymentBody(BaseModel):
|
class ReceivePaymentBody(BaseModel):
|
||||||
amount: int = Field(..., description="Amount in satoshis to receive")
|
amount: int = Field(..., description="Amount in satoshis to receive")
|
||||||
method: PaymentMethodEnum = Field(PaymentMethodEnum.LIGHTNING, description="Payment method")
|
method: PaymentMethodEnum = Field(PaymentMethodEnum.LIGHTNING, description="Payment method")
|
||||||
|
description: Optional[str] = Field(None, description="Optional description for invoice")
|
||||||
|
asset_id: Optional[str] = Field(None, description="Asset ID for Liquid asset (optional)")
|
||||||
|
|
||||||
class SendPaymentBody(BaseModel):
|
class SendPaymentBody(BaseModel):
|
||||||
destination: str = Field(..., description="Payment destination (invoice or address)")
|
destination: str = Field(..., description="Payment destination (invoice or address)")
|
||||||
amount: Optional[int] = Field(None, description="Amount in satoshis to send")
|
amount_sat: Optional[int] = Field(None, description="Amount in satoshis to send (for Bitcoin)")
|
||||||
|
amount_asset: Optional[float] = Field(None, description="Amount to send (for asset payments)")
|
||||||
|
asset_id: Optional[str] = Field(None, description="Asset ID for Liquid asset (optional)")
|
||||||
drain: bool = Field(False, description="Whether to drain the wallet")
|
drain: bool = Field(False, description="Whether to drain the wallet")
|
||||||
|
|
||||||
class ListPaymentsParams:
|
class SendOnchainBody(BaseModel):
|
||||||
def __init__(
|
address: str = Field(..., description="Destination Bitcoin or Liquid address")
|
||||||
self,
|
amount_sat: Optional[int] = Field(None, description="Amount in satoshis to send (ignored if drain)")
|
||||||
from_timestamp: Optional[int] = Query(None, description="Filter payments from this timestamp"),
|
drain: bool = Field(False, description="Send all funds")
|
||||||
to_timestamp: Optional[int] = Query(None, description="Filter payments to this timestamp"),
|
fee_rate_sat_per_vbyte: Optional[int] = Field(None, description="Custom fee rate (optional)")
|
||||||
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):
|
class PaymentResponse(BaseModel):
|
||||||
timestamp: int
|
timestamp: int
|
||||||
@@ -75,9 +55,11 @@ class PaymentResponse(BaseModel):
|
|||||||
fees_sat: int
|
fees_sat: int
|
||||||
payment_type: str
|
payment_type: str
|
||||||
status: str
|
status: str
|
||||||
details: str
|
details: Any
|
||||||
destination: str
|
destination: Optional[str] = None
|
||||||
tx_id: Optional[str] = None
|
tx_id: Optional[str] = None
|
||||||
|
payment_hash: Optional[str] = None
|
||||||
|
swap_id: Optional[str] = None
|
||||||
|
|
||||||
class PaymentListResponse(BaseModel):
|
class PaymentListResponse(BaseModel):
|
||||||
payments: List[PaymentResponse]
|
payments: List[PaymentResponse]
|
||||||
@@ -87,139 +69,42 @@ class ReceiveResponse(BaseModel):
|
|||||||
fees_sat: int
|
fees_sat: int
|
||||||
|
|
||||||
class SendResponse(BaseModel):
|
class SendResponse(BaseModel):
|
||||||
payment_status: str
|
status: str
|
||||||
destination: str
|
destination: Optional[str] = None
|
||||||
fees_sat: int
|
fees_sat: Optional[int] = None
|
||||||
|
payment_hash: Optional[str] = None
|
||||||
|
swap_id: Optional[str] = None
|
||||||
|
|
||||||
# Breez SDK Event Listener
|
class SendOnchainResponse(BaseModel):
|
||||||
class SdkListener(EventListener):
|
status: str
|
||||||
def __init__(self):
|
address: str
|
||||||
self.synced = False
|
fees_sat: Optional[int] = None
|
||||||
self.paid = []
|
|
||||||
|
|
||||||
def on_event(self, event):
|
# LNURL Models
|
||||||
if isinstance(event, SdkEvent.SYNCED):
|
class ParseInputBody(BaseModel):
|
||||||
self.synced = True
|
input: str
|
||||||
if isinstance(event, SdkEvent.PAYMENT_SUCCEEDED):
|
|
||||||
if event.details.destination:
|
|
||||||
self.paid.append(event.details.destination)
|
|
||||||
|
|
||||||
def is_paid(self, destination: str):
|
class PrepareLnurlPayBody(BaseModel):
|
||||||
return destination in self.paid
|
data: Dict[str, Any] # The .data dict from parse_input
|
||||||
|
amount_sat: int
|
||||||
|
comment: Optional[str] = None
|
||||||
|
validate_success_action_url: Optional[bool] = True
|
||||||
|
|
||||||
# Initialize Breez SDK client
|
class LnurlPayBody(BaseModel):
|
||||||
class BreezClient:
|
prepare_response: Dict[str, Any] # The dict from prepare_lnurl_pay
|
||||||
_instance = None
|
|
||||||
|
|
||||||
def __new__(cls):
|
class LnurlAuthBody(BaseModel):
|
||||||
if cls._instance is None:
|
data: Dict[str, Any] # The .data dict from parse_input
|
||||||
cls._instance = super(BreezClient, cls).__new__(cls)
|
|
||||||
cls._instance.initialize()
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def initialize(self):
|
class LnurlWithdrawBody(BaseModel):
|
||||||
if not BREEZ_API_KEY:
|
data: Dict[str, Any] # The .data dict from parse_input
|
||||||
raise Exception("Missing Breez API key in environment variables")
|
amount_msat: int
|
||||||
if not SEED_PHRASE:
|
comment: Optional[str] = None
|
||||||
raise Exception("Missing seed phrase in environment variables")
|
|
||||||
|
|
||||||
config = default_config(LiquidNetwork.MAINNET, BREEZ_API_KEY)
|
# --- Dependencies ---
|
||||||
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)):
|
async def get_api_key(api_key: str = Header(None, alias=API_KEY_NAME)):
|
||||||
if not API_KEY:
|
if not API_KEY:
|
||||||
raise HTTPException(status_code=500, detail="API key not configured on server")
|
raise HTTPException(status_code=500, detail="API key not configured on server")
|
||||||
|
|
||||||
if api_key != API_KEY:
|
if api_key != API_KEY:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=401,
|
status_code=401,
|
||||||
@@ -228,22 +113,30 @@ async def get_api_key(api_key: str = Header(None, alias=API_KEY_NAME)):
|
|||||||
)
|
)
|
||||||
return api_key
|
return api_key
|
||||||
|
|
||||||
# Dependency for Breez client
|
def get_payment_handler():
|
||||||
def get_breez_client():
|
|
||||||
try:
|
try:
|
||||||
return BreezClient()
|
return PaymentHandler()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Failed to initialize Breez client: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Failed to initialize PaymentHandler: {str(e)}")
|
||||||
|
|
||||||
# API Routes
|
# --- API Endpoints ---
|
||||||
@app.get("/list_payments", response_model=PaymentListResponse)
|
@app.get("/list_payments", response_model=PaymentListResponse)
|
||||||
async def list_payments(
|
async def list_payments(
|
||||||
params: ListPaymentsParams = Depends(),
|
from_timestamp: Optional[int] = Query(None),
|
||||||
|
to_timestamp: Optional[int] = Query(None),
|
||||||
|
offset: Optional[int] = Query(None),
|
||||||
|
limit: Optional[int] = Query(None),
|
||||||
api_key: str = Depends(get_api_key),
|
api_key: str = Depends(get_api_key),
|
||||||
client: BreezClient = Depends(get_breez_client)
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
payments = client.list_payments(params)
|
params = {
|
||||||
|
"from_timestamp": from_timestamp,
|
||||||
|
"to_timestamp": to_timestamp,
|
||||||
|
"offset": offset,
|
||||||
|
"limit": limit
|
||||||
|
}
|
||||||
|
payments = handler.list_payments(params)
|
||||||
return {"payments": payments}
|
return {"payments": payments}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
@@ -252,12 +145,14 @@ async def list_payments(
|
|||||||
async def receive_payment(
|
async def receive_payment(
|
||||||
request: ReceivePaymentBody,
|
request: ReceivePaymentBody,
|
||||||
api_key: str = Depends(get_api_key),
|
api_key: str = Depends(get_api_key),
|
||||||
client: BreezClient = Depends(get_breez_client)
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
result = client.receive_payment(
|
result = handler.receive_payment(
|
||||||
amount=request.amount,
|
amount=request.amount,
|
||||||
payment_method=request.method
|
payment_method=request.method.value,
|
||||||
|
description=request.description,
|
||||||
|
asset_id=request.asset_id
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -267,23 +162,130 @@ async def receive_payment(
|
|||||||
async def send_payment(
|
async def send_payment(
|
||||||
request: SendPaymentBody,
|
request: SendPaymentBody,
|
||||||
api_key: str = Depends(get_api_key),
|
api_key: str = Depends(get_api_key),
|
||||||
client: BreezClient = Depends(get_breez_client)
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
result = client.send_payment(
|
result = handler.send_payment(
|
||||||
destination=request.destination,
|
destination=request.destination,
|
||||||
amount=request.amount,
|
amount_sat=request.amount_sat,
|
||||||
|
amount_asset=request.amount_asset,
|
||||||
|
asset_id=request.asset_id,
|
||||||
drain=request.drain
|
drain=request.drain
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
# Health check endpoint
|
@app.post("/send_onchain", response_model=SendOnchainResponse)
|
||||||
|
async def send_onchain(
|
||||||
|
request: SendOnchainBody,
|
||||||
|
api_key: str = Depends(get_api_key),
|
||||||
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
# Prepare onchain payment
|
||||||
|
prepare = handler.prepare_pay_onchain(
|
||||||
|
amount_sat=request.amount_sat,
|
||||||
|
drain=request.drain,
|
||||||
|
fee_rate_sat_per_vbyte=request.fee_rate_sat_per_vbyte
|
||||||
|
)
|
||||||
|
# Execute onchain payment
|
||||||
|
handler.pay_onchain(
|
||||||
|
address=request.address,
|
||||||
|
prepare_response=prepare
|
||||||
|
)
|
||||||
|
return {"status": "initiated", "address": request.address, "fees_sat": prepare.get("total_fees_sat")}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.get("/onchain_limits")
|
||||||
|
async def onchain_limits(
|
||||||
|
api_key: str = Depends(get_api_key),
|
||||||
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return handler.fetch_onchain_limits()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health():
|
async def health():
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
@ln_router.post("/parse_input")
|
||||||
|
async def parse_input(
|
||||||
|
request: ParseInputBody,
|
||||||
|
api_key: str = Depends(get_api_key),
|
||||||
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return handler.parse_input(request.input)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@ln_router.post("/prepare")
|
||||||
|
async def prepare(
|
||||||
|
request: PrepareLnurlPayBody,
|
||||||
|
api_key: str = Depends(get_api_key),
|
||||||
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
from breez_sdk_liquid import LnUrlPayRequestData
|
||||||
|
data_obj = LnUrlPayRequestData(**request.data)
|
||||||
|
return handler.prepare_lnurl_pay(
|
||||||
|
data=data_obj,
|
||||||
|
amount_sat=request.amount_sat,
|
||||||
|
comment=request.comment,
|
||||||
|
validate_success_action_url=request.validate_success_action_url
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@ln_router.post("/pay")
|
||||||
|
async def pay(
|
||||||
|
request: LnurlPayBody,
|
||||||
|
api_key: str = Depends(get_api_key),
|
||||||
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
from breez_sdk_liquid import PrepareLnUrlPayResponse
|
||||||
|
prepare_obj = PrepareLnUrlPayResponse(**request.prepare_response)
|
||||||
|
return handler.lnurl_pay(prepare_obj)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@ln_router.post("/auth")
|
||||||
|
async def auth(
|
||||||
|
request: LnurlAuthBody,
|
||||||
|
api_key: str = Depends(get_api_key),
|
||||||
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
from breez_sdk_liquid import LnUrlAuthRequestData
|
||||||
|
data_obj = LnUrlAuthRequestData(**request.data)
|
||||||
|
return {"success": handler.lnurl_auth(data_obj)}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@ln_router.post("/withdraw")
|
||||||
|
async def withdraw(
|
||||||
|
request: LnurlWithdrawBody,
|
||||||
|
api_key: str = Depends(get_api_key),
|
||||||
|
handler: PaymentHandler = Depends(get_payment_handler)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
from breez_sdk_liquid import LnUrlWithdrawRequestData
|
||||||
|
data_obj = LnUrlWithdrawRequestData(**request.data)
|
||||||
|
return handler.lnurl_withdraw(
|
||||||
|
data=data_obj,
|
||||||
|
amount_msat=request.amount_msat,
|
||||||
|
comment=request.comment
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
app.include_router(ln_router)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
@@ -10,7 +10,7 @@ package-mode = false
|
|||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
fastapi = "^0.111.0"
|
fastapi = "^0.111.0"
|
||||||
uvicorn = {extras = ["standard"], version = "^0.30.1"}
|
uvicorn = {extras = ["standard"], version = "^0.30.1"}
|
||||||
breez-sdk-liquid = "*"
|
breez-sdk-liquid = "0.8.2"
|
||||||
python-dotenv = "^1.0.1"
|
python-dotenv = "^1.0.1"
|
||||||
requests = "^2.31.0"
|
requests = "^2.31.0"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user