#!/usr/bin/env python3 """ PacaBotManager Python Client using web3.py ============================================ This script provides a Python interface to interact with the PacaBotManager contract deployed on BSC mainnet using web3.py. Requirements: - pip install web3 python-dotenv Usage: - Set your private key in .env file: PRIVATE_KEY=your_private_key_here - Update the contract addresses if needed - Run the script to see available functions """ import os import sys from web3 import Web3 from eth_account import Account from dotenv import load_dotenv import json from web3.middleware import ExtraDataToPOAMiddleware # Load environment variables load_dotenv() class PacaBotManagerClient: def __init__(self): # BSC Mainnet RPC self.rpc_url = "https://bsc-dataseed1.binance.org" self.w3 = Web3(Web3.HTTPProvider(self.rpc_url)) self.w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) # Contract addresses self.bot_manager_address = "0x4E5d3cD7743934b61041ba2ac3E9df39a0A26dcC" self.paca_bsc_address = "0x3fF44D639a4982A4436f6d737430141aBE68b4E1" # Load private key self.private_key = os.getenv('PRIVATE_KEY') if not self.private_key: print("โŒ Error: PRIVATE_KEY not found in environment variables") print("Please add PRIVATE_KEY=your_private_key_here to your .env file") sys.exit(1) # Set up account self.account = Account.from_key(self.private_key) print(f"๐Ÿ”‘ Using account: {self.account.address}") print(f"๐Ÿ’ฐ Balance: {self.w3.from_wei(self.w3.eth.get_balance(self.account.address), 'ether')} BNB") # Contract ABIs self.bot_manager_abi = [ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "address", "name": "target", "type": "address"}, {"indexed": True, "internalType": "bytes4", "name": "selector", "type": "bytes4"}, {"indexed": False, "internalType": "bool", "name": "success", "type": "bool"} ], "name": "CallExecuted", "type": "event" }, { "anonymous": False, "inputs": [ {"indexed": True, "internalType": "address", "name": "previousOwner", "type": "address"}, {"indexed": True, "internalType": "address", "name": "newOwner", "type": "address"} ], "name": "OwnershipTransferred", "type": "event" }, { "inputs": [ {"internalType": "address", "name": "pacaContract", "type": "address"}, {"internalType": "address", "name": "user", "type": "address"} ], "name": "clearStakes", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ {"internalType": "address", "name": "pacaContract", "type": "address"}, {"internalType": "address", "name": "user", "type": "address"} ], "name": "clearVesting", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ {"internalType": "address", "name": "target", "type": "address"}, {"internalType": "bytes", "name": "callData", "type": "bytes"} ], "internalType": "struct PacaBotManager.Call[]", "name": "calls", "type": "tuple[]" } ], "name": "multiCallAtomic", "outputs": [ {"internalType": "bytes[]", "name": "returnData", "type": "bytes[]"} ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ {"internalType": "address", "name": "", "type": "address"} ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ {"internalType": "address", "name": "newOwner", "type": "address"} ], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] # PACA ABI with correct Stake struct self.paca_abi = [ { "inputs": [ {"internalType": "address", "name": "user", "type": "address"} ], "name": "getStakes", "outputs": [ { "components": [ {"internalType": "uint256", "name": "amount", "type": "uint256"}, {"internalType": "uint256", "name": "lastClaimed", "type": "uint256"}, {"internalType": "uint256", "name": "dailyRewardRate", "type": "uint256"}, {"internalType": "uint256", "name": "unlockTime", "type": "uint256"}, {"internalType": "bool", "name": "complete", "type": "bool"} ], "internalType": "struct PacaFinanceWithBoostAndScheduleBsc.Stake[]", "name": "", "type": "tuple[]" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ {"internalType": "address", "name": "", "type": "address"} ], "stateMutability": "view", "type": "function" } ] # Create contract instances self.bot_manager = self.w3.eth.contract( address=self.bot_manager_address, abi=self.bot_manager_abi ) self.paca_contract = self.w3.eth.contract( address=self.paca_bsc_address, abi=self.paca_abi ) print(f"๐Ÿค– PacaBotManager: {self.bot_manager_address}") print(f"๐Ÿ”— PACA Contract: {self.paca_bsc_address}") print(f"๐ŸŒ Network: BSC Mainnet") print() def get_gas_price(self): """Get current gas price with small buffer""" base_gas = self.w3.eth.gas_price return int(base_gas * 1.1) # 10% buffer def send_transaction(self, tx_dict): """Send a transaction and wait for confirmation""" # Add gas and nonce tx_dict['gas'] = 500000 # Conservative gas limit gas_price = self.get_gas_price() tx_dict['maxFeePerGas'] = gas_price tx_dict['maxPriorityFeePerGas'] = gas_price # 10% of max fee as priority tx_dict['nonce'] = self.w3.eth.get_transaction_count(self.account.address) tx_dict['type'] = 2 # EIP-1559 transaction print(f"โ›ฝ Max Fee Per Gas: {self.w3.from_wei(gas_price, 'gwei')} gwei") # Sign and send signed = self.w3.eth.account.sign_transaction(tx_dict, self.private_key) tx_hash = self.w3.eth.send_raw_transaction(signed.raw_transaction) print(f"๐Ÿงพ Transaction sent: {tx_hash.hex()}") print("โณ Waiting for confirmation...") # Wait for confirmation receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) if receipt.status == 1: print("โœ… Transaction successful!") else: print("โŒ Transaction failed!") print(f"โ›ฝ Gas used: {receipt.gasUsed}") return receipt def clear_stakes(self, user_address): """Clear all stakes for a user""" print(f"๐Ÿ”ฅ Clearing stakes for user: {user_address}") tx_dict = self.bot_manager.functions.clearStakes( self.paca_bsc_address, user_address ).build_transaction({ 'from': self.account.address, }) return self.send_transaction(tx_dict) def clear_vesting(self, user_address): """Clear all vestings for a user""" print(f"๐Ÿ”ฎ Clearing vestings for user: {user_address}") tx_dict = self.bot_manager.functions.clearVesting( self.paca_bsc_address, user_address ).build_transaction({ 'from': self.account.address, }) return self.send_transaction(tx_dict) def multi_call_atomic(self, calls): """Execute multiple calls atomically""" print(f"๐Ÿ”„ Executing {len(calls)} calls atomically...") tx_dict = self.bot_manager.functions.multiCallAtomic(calls).build_transaction({ 'from': self.account.address, }) return self.send_transaction(tx_dict) def clear_both_stakes_and_vesting(self, user_address): """Clear both stakes and vesting for a user in one atomic transaction""" print(f"๐Ÿ”ฅ๐Ÿ”ฎ Clearing BOTH stakes AND vesting for user: {user_address}") print("โš ๏ธ WARNING: This affects both stakes and vesting!") # Prepare both calls calls = [ { 'target': self.paca_bsc_address, 'callData': self.w3.keccak(text="clearStakes(address)")[:4] + self.w3.eth.codec.encode(['address'], [user_address]) }, { 'target': self.paca_bsc_address, 'callData': self.w3.keccak(text="clearVesting(address)")[:4] + self.w3.eth.codec.encode(['address'], [user_address]) } ] return self.multi_call_atomic(calls) def get_owner(self): """Get the owner of the bot manager contract""" owner = self.bot_manager.functions.owner().call() print(f"๐Ÿ‘ค Bot Manager Owner: {owner}") return owner def get_stakes(self, user_address): """Get stakes for a user from PACA contract""" print(f"๐Ÿ“Š Getting stakes for user: {user_address}") try: stakes = self.paca_contract.functions.getStakes(user_address).call() print(f"๐Ÿ“ˆ User has {len(stakes)} stakes:") total_amount = 0 active_stakes = 0 for i, stake in enumerate(stakes): amount = stake[0] # amount last_claimed = stake[1] # lastClaimed daily_reward_rate = stake[2] # dailyRewardRate unlock_time = stake[3] # unlockTime complete = stake[4] # complete is_active = not complete and amount > 0 if is_active: active_stakes += 1 total_amount += amount print(f" ๐Ÿ“Œ Stake {i + 1}:") print(f" Amount: {self.w3.from_wei(amount, 'ether')} ETH") print(f" Daily Reward Rate: {self.w3.from_wei(daily_reward_rate, 'ether')} ETH") print(f" Complete: {complete}") print(f" Status: {'๐ŸŸข ACTIVE' if is_active else '๐Ÿ”ด COMPLETED'}") if last_claimed > 0: import datetime last_claimed_date = datetime.datetime.fromtimestamp(last_claimed) print(f" Last Claimed: {last_claimed_date}") if unlock_time > 0: import datetime unlock_date = datetime.datetime.fromtimestamp(unlock_time) print(f" Unlock Time: {unlock_date}") print() print(f"๐Ÿ’Ž Summary:") print(f" Total Stakes: {len(stakes)}") print(f" Active Stakes: {active_stakes}") print(f" Total Active Amount: {self.w3.from_wei(total_amount, 'ether')} ETH") return stakes except Exception as e: print(f"โŒ Error getting stakes: {e}") return [] def get_paca_owner(self): """Get the owner of the PACA contract""" try: owner = self.paca_contract.functions.owner().call() print(f"๐Ÿ‘ค PACA Contract Owner: {owner}") return owner except Exception as e: print(f"โŒ Error getting PACA owner: {e}") return None def main(): print("๐Ÿ PacaBotManager Python Client") print("=" * 40) try: client = PacaBotManagerClient() # Show available commands print("๐Ÿ“‹ Available Commands:") print("1. Get Bot Manager Owner") print("2. Get PACA Contract Owner") print("3. Get Stakes for User") print("4. Clear Stakes for User") print("5. Clear Vesting for User") print("6. Clear BOTH Stakes and Vesting (Atomic)") print() # Example usage - uncomment to test specific functions # 1. Get owners client.get_owner() client.get_paca_owner() print() # 2. Get stakes for the test user test_user = "0x41970Ce76b656030A79E7C1FA76FC4EB93980255" # client.get_stakes(test_user) # 3. Uncomment to clear stakes ONLY (BE CAREFUL - this affects real funds!) # print("โš ๏ธ WARNING: This will clear real stakes!") # client.clear_stakes(test_user) # 4. Uncomment to clear vesting ONLY (BE CAREFUL - this affects real funds!) print("โš ๏ธ WARNING: This will clear real vesting!") client.clear_vesting(test_user) # 5. Example: Clear BOTH stakes and vesting in one atomic transaction # print("โš ๏ธ WARNING: This will clear both stakes AND vesting atomically!") # client.clear_both_stakes_and_vesting(test_user) except Exception as e: print(f"๐Ÿ’ฅ Error: {e}") sys.exit(1) if __name__ == "__main__": main()