import json import os import math import sys from decimal import Decimal, getcontext from web3 import Web3 from web3.middleware import ExtraDataToPOAMiddleware from eth_account import Account from dotenv import load_dotenv # Add project root to path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from clp_config import CLP_PROFILES # Load Env load_dotenv() # Config for PancakeSwap PROFILE = CLP_PROFILES["PANCAKESWAP_BNB"] RPC_URL = os.environ.get(PROFILE["RPC_ENV_VAR"]) STATUS_FILE = "PANCAKESWAP_BNB_status.json" # Minimal ABI for NPM NPM_ABI = [ { "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], "name": "positions", "outputs": [ {"internalType": "uint96", "name": "nonce", "type": "uint96"}, {"internalType": "address", "name": "operator", "type": "address"}, {"internalType": "address", "name": "token0", "type": "address"}, {"internalType": "address", "name": "token1", "type": "address"}, {"internalType": "uint24", "name": "fee", "type": "uint24"}, {"internalType": "int24", "name": "tickLower", "type": "int24"}, {"internalType": "int24", "name": "tickUpper", "type": "int24"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "feeGrowthInside0LastX128", "type": "uint256"}, {"internalType": "uint256", "name": "feeGrowthInside1LastX128", "type": "uint256"}, {"internalType": "uint128", "name": "tokensOwed0", "type": "uint128"}, {"internalType": "uint128", "name": "tokensOwed1", "type": "uint128"} ], "stateMutability": "view", "type": "function" } ] def get_price_at_tick(tick): return 1.0001 ** tick def fetch_and_fix(): if not RPC_URL: print("❌ Missing RPC URL in .env") return print(f"Connecting to RPC: {RPC_URL}") w3 = Web3(Web3.HTTPProvider(RPC_URL)) w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) if not w3.is_connected(): print("❌ Failed to connect to Web3") return npm = w3.eth.contract(address=PROFILE["NPM_ADDRESS"], abi=NPM_ABI) with open(STATUS_FILE, 'r') as f: data = json.load(f) updated_count = 0 for entry in data: token_id = entry.get('token_id') status = entry.get('status') # We check ALL positions to be safe, or just the problematic ones. # Let's check any that seem to have suspect data or just refresh all active/recently active. # The user mentioned 6164702 specifically. print(f"🔍 Checking Token ID: {token_id} ({status})") try: pos = npm.functions.positions(token_id).call() # Pos structure: # 0: nonce, 1: operator, 2: token0, 3: token1, 4: fee, # 5: tickLower, 6: tickUpper, 7: liquidity ... tick_lower = pos[5] tick_upper = pos[6] liquidity = pos[7] # Calculate Ranges price_lower = get_price_at_tick(tick_lower) price_upper = get_price_at_tick(tick_upper) # Format to 4 decimals new_lower = round(price_lower, 4) new_upper = round(price_upper, 4) old_lower = entry.get('range_lower', 0) old_upper = entry.get('range_upper', 0) # Check deviation if abs(new_lower - old_lower) > 0.1 or abs(new_upper - old_upper) > 0.1: print(f" ⚠️ Mismatch Found!") print(f" Old: {old_lower} - {old_upper}") print(f" New: {new_lower} - {new_upper}") entry['range_lower'] = new_lower entry['range_upper'] = new_upper entry['liquidity'] = str(liquidity) # Fix Entry Price if it looks wrong (e.g. 0 or way off range) # If single sided (e.g. 862-869), and spot is 860. # If we provided only Token0 (BNB), we are selling BNB as it goes UP. # So we entered 'below' the range. # If we assume the user just opened it, the 'entry_price' should roughly match # the current market price or at least be consistent. # Since we don't know the exact historical price, we can't perfectly fix 'entry_price' # without event logs. # HOWEVER, for the bot's logic, 'range_lower' and 'range_upper' are critical for 'in_range' checks. # 'entry_price' is mostly for PnL est. # If entry_price is wildly different from range (e.g. 844 vs 862-869), it's confusing. # Let's see if we can infer something. # For now, we update ranges as that's the request. updated_count += 1 else: print(f" ✅ Data looks solid.") except Exception as e: print(f" ❌ Error fetching chain data: {e}") if updated_count > 0: with open(STATUS_FILE, 'w') as f: json.dump(data, f, indent=2) print(f"💾 Updated {updated_count} entries in {STATUS_FILE}") else: print("No updates needed.") if __name__ == "__main__": fetch_and_fix()