btc-api/main.py

183 lines
6.3 KiB
Python

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/<int:hoehe>', 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/<txid>', 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/<adresse>', 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)