feat: add FastAPI REST backend

Expose all address generation functions as HTTP endpoints:
- POST /api/hd/generate, /api/hd/encrypt, /api/hd/decrypt
- POST /api/p2pk, /api/p2pkh, /api/p2sh, /api/p2wpkh, /api/p2tr
- Add fastapi and uvicorn to requirements.txt
This commit is contained in:
2026-03-09 14:08:59 +01:00
parent 54d282a36b
commit f9c35678a5
2 changed files with 162 additions and 0 deletions

View File

@@ -5,3 +5,5 @@ six==1.17.0
pytest
bip-utils
pycryptodome
fastapi
uvicorn

160
src/api.py Normal file
View File

@@ -0,0 +1,160 @@
"""
FastAPI backend for the Bitcoin Address Generator.
Exposes all address generation functions as HTTP endpoints.
"""
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Any, Dict, Optional
try:
from src.hd_wallet import generate_hd_wallet, encrypt_wallet, decrypt_wallet
from src.p2pk import generate_p2pk
from src.p2pkh import generate_legacy_address
from src.p2sh import generate_p2sh_multisig
from src.p2wpkh import generate_segwit_address
from src.p2tr import generate_p2tr_address
except ImportError:
from hd_wallet import generate_hd_wallet, encrypt_wallet, decrypt_wallet
from p2pk import generate_p2pk
from p2pkh import generate_legacy_address
from p2sh import generate_p2sh_multisig
from p2wpkh import generate_segwit_address
from p2tr import generate_p2tr_address
app = FastAPI(title="Bitcoin Address Generator API")
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "http://localhost:4173", "file://", "*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── HD Wallet ──────────────────────────────────────────────────────────────────
class HDGenerateRequest(BaseModel):
network: str = "mainnet"
bip_type: str = "bip84"
mnemonic: Optional[str] = None
passphrase: str = ""
account: int = 0
num_addresses: int = 5
class HDEncryptRequest(BaseModel):
wallet: Dict[str, Any]
password: str
class HDDecryptRequest(BaseModel):
wallet: Dict[str, Any]
password: str
@app.post("/api/hd/generate")
def hd_generate(req: HDGenerateRequest):
try:
return generate_hd_wallet(
network=req.network,
bip_type=req.bip_type,
mnemonic=req.mnemonic or None,
passphrase=req.passphrase,
account=req.account,
num_addresses=req.num_addresses,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/hd/encrypt")
def hd_encrypt(req: HDEncryptRequest):
try:
return encrypt_wallet(req.wallet, req.password)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/hd/decrypt")
def hd_decrypt(req: HDDecryptRequest):
try:
return decrypt_wallet(req.wallet, req.password)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# ── Single address types ───────────────────────────────────────────────────────
class P2PKRequest(BaseModel):
network: str = "mainnet"
compressed: bool = False
class P2PKHRequest(BaseModel):
network: str = "mainnet"
compressed: bool = True
class P2SHRequest(BaseModel):
network: str = "mainnet"
m: int = 2
n: int = 3
compressed: bool = True
class P2WPKHRequest(BaseModel):
network: str = "mainnet"
compressed: bool = True
class P2TRRequest(BaseModel):
network: str = "mainnet"
@app.post("/api/p2pk")
def p2pk(req: P2PKRequest):
try:
return generate_p2pk(network=req.network, compressed=req.compressed)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/p2pkh")
def p2pkh(req: P2PKHRequest):
try:
return generate_legacy_address(network=req.network, compressed=req.compressed)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/p2sh")
def p2sh(req: P2SHRequest):
try:
return generate_p2sh_multisig(
network=req.network, m=req.m, n=req.n, compressed=req.compressed
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/p2wpkh")
def p2wpkh(req: P2WPKHRequest):
try:
return generate_segwit_address(network=req.network, compressed=req.compressed)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/api/p2tr")
def p2tr(req: P2TRRequest):
try:
return generate_p2tr_address(network=req.network)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@app.get("/api/health")
def health():
return {"status": "ok"}