From f9c35678a5cf11abd111de6420d41c71f3eacc3e Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 9 Mar 2026 14:08:59 +0100 Subject: [PATCH] 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 --- requirements.txt | 2 + src/api.py | 160 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/api.py diff --git a/requirements.txt b/requirements.txt index 87feb17..8065b76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,5 @@ six==1.17.0 pytest bip-utils pycryptodome +fastapi +uvicorn diff --git a/src/api.py b/src/api.py new file mode 100644 index 0000000..8a59ed6 --- /dev/null +++ b/src/api.py @@ -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"}