elerium-tooling/elerium_ordinals_daemon.py
nick 275287bedc Update elerium_ordinals_daemon.py
Added fee estimation via get_fee_rate function which calls bitcoin-cli estimatesmartfee with a conf_target of 3 blocks
2023-12-01 12:23:41 +00:00

215 lines
6.7 KiB
Python

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()