import os import socket import json from hashlib import sha256 as hashlib_sha256 import base58 from flask import Flask, jsonify, request from bitcoinlib.services.bitcoind import BitcoindClient from dotenv import load_dotenv load_dotenv() app = Flask(__name__) RPC_USER = os.getenv('RPC_USER') RPC_PASS = os.getenv('RPC_PASS') IP_ADRESSE = '192.168.178.225' # ← Docker-Servicename statt IP ELECTRS_HOST = '192.168.178.225' # ← Docker-Servicename statt IP ELECTRS_PORT = 50001 if not RPC_USER or not RPC_PASS: raise ValueError("Fehler: RPC_USER oder RPC_PASS wurden nicht in der Umgebung gefunden!") rpc_url = f'http://{RPC_USER}:{RPC_PASS}@192.168.178.225:8332' def get_node(): return BitcoindClient(network='bitcoin', base_url=rpc_url).proxy # ========================================== # ELECTRS HILFSFUNKTIONEN # ========================================== def address_to_script(address: str) -> str: """Konvertiert Bitcoin-Adresse → Output-Script (hex)""" if address.startswith("bc1"): # P2WPKH / P2WSH (bech32) # Manuell dekodieren ohne externe bech32-Library import bech32 witver, witprog = bech32.decode("bc", address) if witprog is None: raise ValueError(f"Ungültige bech32-Adresse: {address}") if len(witprog) == 20: return "0014" + bytes(witprog).hex() # P2WPKH return "0020" + bytes(witprog).hex() # P2WSH decoded = base58.b58decode_check(address) prefix = decoded[0] payload = decoded[1:].hex() if prefix == 0x00: # 1... → P2PKH return f"76a914{payload}88ac" elif prefix == 0x05: # 3... → P2SH return f"a914{payload}87" raise ValueError(f"Unbekanntes Adressformat: {address}") def address_to_electrum_hash(address: str) -> str: """Berechnet den Electrum script_hash für eine Adresse""" script = bytes.fromhex(address_to_script(address)) return hashlib_sha256(script).digest()[::-1].hex() def electrs_query(method: str, params: list): """Sendet eine Anfrage an den lokalen Electrs-Server""" req = json.dumps({"id": 1, "method": method, "params": params}) + "\n" with socket.create_connection((ELECTRS_HOST, ELECTRS_PORT), timeout=10) as s: s.sendall(req.encode()) data = b"" while True: chunk = s.recv(4096) data += chunk if b"\n" in chunk: break result = json.loads(data.decode()) if "error" in result and result["error"]: raise Exception(f"Electrs Fehler: {result['error']}") return result["result"] # ========================================== # ENDPUNKT 1: Aktuelle Blockhöhe abfragen # ========================================== @app.route('/api/blockhoehe', methods=['GET']) def get_blockhoehe(): try: node = get_node() info = node.getblockchaininfo() return jsonify({ 'status': 'success', 'blockhoehe': info['blocks'], 'sync_fortschritt_prozent': round(info.get('verificationprogress', 0) * 100, 2) }) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 # ========================================== # ENDPUNKT 2: Bestimmten Block abfragen # ========================================== @app.route('/api/block/', methods=['GET']) def get_block(hoehe): try: node = get_node() block_hash = node.getblockhash(hoehe) block_daten = node.getblock(block_hash, 2) return jsonify({'status': 'success', 'block': block_daten}) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 # ========================================== # ENDPUNKT 3: Einzelne Transaktion abfragen # ========================================== @app.route('/api/tx/', methods=['GET']) def get_tx(txid): try: node = get_node() tx_daten = node.getrawtransaction(txid, True) return jsonify({'status': 'success', 'transaction': tx_daten}) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 # ========================================== # ENDPUNKT 4: Guthaben & UTXOs via Electrs # ========================================== @app.route('/api/guthaben/', methods=['GET']) def get_guthaben(adresse): try: script_hash = address_to_electrum_hash(adresse) # Balance (confirmed + unconfirmed) balance = electrs_query("blockchain.scripthash.get_balance", [script_hash]) # UTXOs (nur unspent outputs) utxos = electrs_query("blockchain.scripthash.listunspent", [script_hash]) confirmed = balance["confirmed"] unconfirmed = balance["unconfirmed"] return jsonify({ 'status': 'success', 'adresse': adresse, 'guthaben_btc': confirmed / 1e8, 'unconfirmed_btc': unconfirmed / 1e8, 'guthaben_sat': confirmed, 'unconfirmed_sat': unconfirmed, 'anzahl_utxos': len(utxos), 'utxos': [ { 'tx_hash': u['tx_hash'], 'tx_pos': u['tx_pos'], 'value': u['value'], # Satoshi 'height': u['height'] } for u in utxos ] }) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 # ========================================== # ENDPUNKT 5: Transaktion senden (POST) # ========================================== @app.route('/api/sendtx', methods=['POST']) def send_tx(): try: daten = request.get_json() if not daten or 'hex' not in daten: return jsonify({'status': 'error', 'message': 'Kein gueltiger Hex-String uebergeben.'}), 400 raw_tx_hex = daten['hex'] node = get_node() txid = node.sendrawtransaction(raw_tx_hex) return jsonify({ 'status': 'success', 'message': 'Transaktion erfolgreich an das Netzwerk uebermittelt!', 'txid': txid }) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 # ========================================== # Server starten # ========================================== if __name__ == '__main__': print("🚀 API Server startet...") app.run(host='0.0.0.0', port=5001)