Created
March 26, 2026 05:06
-
-
Save ilyasst/2dd6dcb74b1c6dd29875ef8ddda01d36 to your computer and use it in GitHub Desktop.
node_utxoracle_meshtastic.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import requests | |
| import datetime | |
| import subprocess | |
| import re | |
| import zoneinfo | |
| import os | |
| RPC_USER = "your_rpc_user" | |
| RPC_PASS = "your_rpc_password" | |
| RPC_HOST = "192.168.x.x" | |
| RPC_PORT = 9332 | |
| RPC_URL = f"http://{RPC_USER}:{RPC_PASS}@{RPC_HOST}:{RPC_PORT}/" | |
| LAST_PRICE_FILE = os.path.expanduser("~/.last_btc_price") | |
| def rpc_call(method, params=[]): | |
| payload = { | |
| "jsonrpc": "1.0", | |
| "id": "python_script", | |
| "method": method, | |
| "params": params | |
| } | |
| response = requests.post(RPC_URL, json=payload) | |
| response.raise_for_status() | |
| res_json = response.json() | |
| if res_json.get("error"): | |
| raise Exception(f"RPC Error: {res_json['error']}") | |
| return res_json['result'] | |
| def get_montreal_time(): | |
| tz = zoneinfo.ZoneInfo("America/Montreal") | |
| return datetime.datetime.now(tz).strftime("%H:%M") | |
| def get_utxoracle_price(): | |
| result = subprocess.run( | |
| ["python", "/path/to/UTXOracle/UTXOracle.py"], | |
| capture_output=True, | |
| text=True | |
| ) | |
| match = re.search(r"price:\s*\$([\d,]+)", result.stdout) | |
| if match: | |
| return int(match.group(1).replace(",", "")) | |
| return None | |
| def get_fee(target): | |
| try: | |
| res = rpc_call("estimatesmartfee", [target]) | |
| fee_btc_per_kb = res.get("feerate", 0) | |
| if fee_btc_per_kb <= 0: | |
| return 1 | |
| sats_per_vbyte = (fee_btc_per_kb * 100_000_000) / 1000 | |
| return max(1, round(sats_per_vbyte)) | |
| except Exception: | |
| return 1 | |
| def main(): | |
| try: | |
| height = rpc_call("getblockcount") | |
| except Exception as e: | |
| print(f"Error fetching height: {e}") | |
| return | |
| time_str = get_montreal_time() | |
| price = get_utxoracle_price() | |
| if price: | |
| # Check if price changed by >= $1000 since last run | |
| if os.path.exists(LAST_PRICE_FILE): | |
| try: | |
| with open(LAST_PRICE_FILE, "r") as f: | |
| last_price = int(f.read().strip()) | |
| if abs(price - last_price) < 1000: | |
| print(f"Current price (${price}) changed by less than $1000 from last broadcast (${last_price}). Skipping message.") | |
| return | |
| except Exception as e: | |
| print(f"Error reading {LAST_PRICE_FILE}: {e}") | |
| # Save new price | |
| try: | |
| with open(LAST_PRICE_FILE, "w") as f: | |
| f.write(str(price)) | |
| except Exception as e: | |
| print(f"Error writing to {LAST_PRICE_FILE}: {e}") | |
| moscow_time = round(100_000_000 / price) | |
| price_str = f"${price:,}".replace(",", " ") | |
| else: | |
| moscow_time = "N/A" | |
| price_str = "N/A" | |
| try: | |
| mempool_info = rpc_call("getmempoolinfo") | |
| txs = mempool_info.get('size', 0) | |
| except Exception: | |
| txs = 0 | |
| low = get_fee(144) | |
| mid = get_fee(18) | |
| high = get_fee(2) | |
| avg_time_str = "N/A" | |
| diff_arrow = "" | |
| try: | |
| current_hash = rpc_call("getblockhash", [height]) | |
| current_block = rpc_call("getblock", [current_hash, 1]) | |
| current_time = current_block['time'] | |
| # Calculate the last difficulty adjustment block | |
| # Difficulty adjusts every 2016 blocks. The new epoch starts at height - (height % 2016) | |
| last_diff_height = height - (height % 2016) | |
| # We need at least 1 block in the new epoch to calculate an average | |
| if height > last_diff_height: | |
| past_hash = rpc_call("getblockhash", [last_diff_height]) | |
| past_block = rpc_call("getblock", [past_hash, 1]) | |
| past_time = past_block['time'] | |
| blocks_passed = height - last_diff_height | |
| avg_time_sec = (current_time - past_time) / blocks_passed | |
| mins = int(avg_time_sec // 60) | |
| secs = int(avg_time_sec % 60) | |
| avg_time_str = f"{mins}m{secs:02d}s" | |
| # If average time > 10 mins (600s), diff goes down. Otherwise up. | |
| if avg_time_sec > 600: | |
| diff_arrow = "⬇️" | |
| else: | |
| diff_arrow = "⬆️" | |
| else: | |
| avg_time_str = "0m00s" | |
| diff_arrow = "-" | |
| except Exception as e: | |
| print(f"Error calculating avg block time: {e}") | |
| height_str = f"{height:,}".replace(",", " ") | |
| # Format message exactly as requested | |
| message = f"{height_str}, {time_str}\n" | |
| message += f"{price_str}, {moscow_time}\n" | |
| message += f"{low}/{mid}/{high}, {txs} txs\n" | |
| message += f"{avg_time_str}, Diff {diff_arrow}" | |
| print("Sending message:") | |
| print(message) | |
| print("----------------") | |
| # Send via meshtastic | |
| subprocess.run([ | |
| "meshtastic", | |
| "--sendtext", message, | |
| "--ch-index", "1", | |
| "--port", "/dev/ttyUSB0" | |
| ]) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment