import requests import json import sqlite3 import logging import time from requests.exceptions import RequestException import subprocess import tempfile import os # Configuration BTCPAY_INSTANCE = "https://btcpay.kosmos.org" STORE_ID = "BLt2tAvgDHaYL6eCpbcNTybaPEUS6cYBCoXZLMBw8APg" API_KEY = "8daa71e669ca230acd5fb437fee6cfdd659c92f6" DB_PATH = "elerium_orders.db" CHECK_INTERVAL = 60 # in seconds # Maximum weight units for a safe transaction MAX_INSCRIPTION_SIZE = 390000 # Maximum size in weight units # Set up logging logging.basicConfig(filename='elerium_daemon.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # Database setup conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() # Create table if it doesn't exist cursor.execute('''CREATE TABLE IF NOT EXISTS orders (invoice_id TEXT PRIMARY KEY, status TEXT)''') conn.commit() # Headers for BTCPay Server API requests headers = { "Authorization": f"token {API_KEY}", "Content-Type": "application/json" } def get_fee_rate(): # Assuming bitcoin-cli is located in the same directory command = [ "/media/n/backup/bitcoin-core/bitcoin-25.0/bin/bitcoin-cli", "-datadir=/media/n/backup/bitcoin-core/bitcoin-25.0/data", "-rpcport=18332", "estimatesmartfee", "3", # conf_target for 3 blocks "conservative" # estimate_mode ] try: result = subprocess.run(command, capture_output=True, text=True, check=True) output = result.stdout data = json.loads(output) # Convert BTC/kvB to sat/B and round to nearest integer btc_per_kvB = data.get("feerate", 0) sat_per_B = int(btc_per_kvB * 100000000 / 1000) return sat_per_B except subprocess.CalledProcessError as e: error_msg = e.stderr logging.error(f"Fee rate estimation failed: {error_msg}") return 1 # Default fee rate or handle error accordingly def inscribe(metadata): metadata_size = len(json.dumps(metadata)) if metadata_size > MAX_INSCRIPTION_SIZE: logging.error(f"Metadata size {metadata_size} exceeds maximum limit.") return None fee_rate = get_fee_rate() print("FEE RATE: "+ str(fee_rate)) with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.json') as temp_file: # Write the metadata to the temporary file json.dump(metadata, temp_file) temp_file_path = temp_file.name command = [ "ord", "--cookie-file=/media/n/backup/bitcoin-core/bitcoin-25.0/data/testnet3/.cookie", "--testnet", "wallet", "inscribe", "--fee-rate", str(fee_rate), # Use dynamic fee rate "--file", temp_file_path ] # Print the command print("Command to be executed:", ' '.join(command)) # Ask user for confirmation user_input = input("Do you want to continue with the inscription? (Y/N): ") if user_input.lower() != 'y': print("Inscription cancelled.") exit() return None try: result = subprocess.run(command, capture_output=True, text=True, check=True) output = result.stdout # Assuming the correct output is in JSON format data = json.loads(output) if "inscriptions" in data: ordinalsId = data["inscriptions"][0]["id"] return ordinalsId else: raise ValueError("Unexpected response format") except subprocess.CalledProcessError as e: error_msg = e.stderr if "error: failed to connect to Bitcoin Core RPC" in error_msg: raise ConnectionError("Failed to connect to Bitcoin Core RPC") else: raise RuntimeError(f"Command execution failed: {error_msg}") finally: # Clean up the temporary file os.remove(temp_file_path) def fetch_invoices(): endpoint = f"{BTCPAY_INSTANCE}/api/v1/stores/{STORE_ID}/invoices" try: response = requests.get(endpoint, headers=headers) #print(response) response.raise_for_status() return response.json() except RequestException as e: logging.error(f"Error fetching invoices: {e}") print(f"Error fetching invoices: {e}") return [] def is_inscribed(invoice): metadata = invoice.get('metadata', {}) ordinals_id = metadata.get('ordinalsId', '') # Check if ordinals_id is not None and has a length greater than 1 is_inscribed = ordinals_id is not None and len(ordinals_id) > 1 # Debugging print statement print(f"Invoice ID: {invoice['id']}, Ordinals ID: {ordinals_id}, Is inscribed: {is_inscribed}") return is_inscribed def process_invoices(invoices): for invoice in invoices: if invoice['status'] != 'Settled': continue # Skip non-settled invoices invoice_id = invoice['id'] cursor.execute("SELECT status FROM orders WHERE invoice_id = ?", (invoice_id,)) result = cursor.fetchone() if not is_inscribed(invoice): print("Found non-inscribed order: " + str(invoice)) # 1. Inscribing the order metadata = invoice.get('metadata', {}) ordinalsid = inscribe(metadata=metadata) # 2. Prepare the invoice data for update update_data = invoice.copy() update_data['metadata'] = metadata.copy() update_data['metadata']['ordinalsId'] = ordinalsid # 3. Update the invoice on BTCPay Server update_endpoint = f"{BTCPAY_INSTANCE}/api/v1/stores/{STORE_ID}/invoices/{invoice_id}" response = requests.put(update_endpoint, json=update_data, headers=headers) if response.status_code == 200: logging.info(f"Updated invoice {invoice_id} with ordinalsId "+ ordinalsid) print(f"Updated invoice {invoice_id} with ordinalsId.") # Update the local database as well cursor.execute("INSERT OR REPLACE INTO orders (invoice_id, status) VALUES (?, ?)", (invoice_id, 'inscribed')) conn.commit() else: logging.error(f"Failed to update invoice {invoice_id}: {response.text}") print(f"Failed to update invoice {invoice_id}: {response.text}") else: print("Already inscribed order: " + str(invoice)) def main_loop(): while True: invoices = fetch_invoices() if invoices: process_invoices(invoices) time.sleep(CHECK_INTERVAL) if __name__ == "__main__": try: main_loop() except KeyboardInterrupt: logging.info("Elerium Daemon stopped manually.") print("Elerium Daemon stopped manually.") finally: conn.close()