218 lines
6.8 KiB
Python
218 lines
6.8 KiB
Python
import requests
|
|
import json
|
|
import sqlite3
|
|
import logging
|
|
import time
|
|
from requests.exceptions import RequestException
|
|
import subprocess
|
|
import tempfile
|
|
import os
|
|
|
|
|
|
# Configuration
|
|
CHECK_INTERVAL = 60 # in seconds
|
|
# Maximum metadata size for a safe transaction is 95 KB, to be safe let's limit to 50 KB
|
|
MAX_METADATA_SIZE_BYTES = 50 * 1024 # 50 kilobytes in bytes
|
|
|
|
# Load configuration from JSON file
|
|
with open('config.json', 'r') as config_file:
|
|
config = json.load(config_file)
|
|
|
|
BTCPAY_INSTANCE = config['btcpay']['instance']
|
|
STORE_ID = config['btcpay']['store_id']
|
|
API_KEY = config['btcpay']['api_key']
|
|
DB_PATH = config['database']['path']
|
|
LOG_FILE = config['database']['logging']
|
|
BITCOIN_CLI_PATH = config['bitcoin_cli']['path']
|
|
BITCOIN_CLI_DATADIR = config['bitcoin_cli']['data_dir']
|
|
BITCOIN_CLI_RPC_PORT = config['bitcoin_cli']['rpc_port']
|
|
ORD_COOKIE_FILE = config['ord_command']['cookie_file']
|
|
ORD_TESTNET = config['ord_command']['testnet']
|
|
|
|
|
|
|
|
# Set up logging
|
|
logging.basicConfig(filename=LOG_FILE, 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():
|
|
|
|
command = [
|
|
BITCOIN_CLI_PATH,
|
|
BITCOIN_CLI_DATADIR,
|
|
BITCOIN_CLI_RPC_PORT,
|
|
"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)
|
|
print(data)
|
|
# Convert BTC/kvB to sat/B and round to nearest integer
|
|
btc_per_kvB = data.get("feerate", 0)
|
|
|
|
btc_per_kvB = "{:.8f}".format(btc_per_kvB) # Convert to decimal with 8 decimal places
|
|
|
|
print(btc_per_kvB)
|
|
|
|
sat_per_B = int(float(btc_per_kvB) * 100000000 / 1000)
|
|
print(sat_per_B)
|
|
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
|
|
|
|
|
|
|
|
def inscribe(metadata):
|
|
metadata_size_bytes = len(json.dumps(metadata).encode('utf-8'))
|
|
|
|
if metadata_size_bytes > MAX_METADATA_SIZE_BYTES:
|
|
logging.error(f"Metadata size {metadata_size_bytes} bytes exceeds maximum limit of 50 KB.")
|
|
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",
|
|
#ORD_COOKIE_FILE,
|
|
#ORD_TESTNET,
|
|
"wallet",
|
|
"inscribe",
|
|
"--fee-rate",
|
|
str(fee_rate), # Use dynamic fee rate
|
|
"--file",
|
|
temp_file_path
|
|
]
|
|
|
|
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)
|
|
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,))
|
|
|
|
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()
|