Commit before cuna

This commit is contained in:
2025-09-04 02:48:34 +02:00
parent 7e55515063
commit 8ef7f0b9f1
32 changed files with 4668 additions and 17 deletions

View File

@@ -0,0 +1,651 @@
#!/usr/bin/env python3
"""
Interactive PacaBotManager CLI Tool
==================================
An interactive command-line tool for managing PACA stakes and vestings
through the PacaBotManager contract on BSC mainnet.
Usage: python python_scripts/interactive_bot_manager.py
"""
import os
import sys
from web3 import Web3
from eth_account import Account
from dotenv import load_dotenv
import re
from web3.middleware import ExtraDataToPOAMiddleware
# Load environment variables
load_dotenv()
class InteractivePacaBotManager:
def __init__(self, chain_id=None):
print("🐍 Interactive PacaBotManager CLI")
print("=" * 40)
# Chain configurations
self.chains = {
"bsc": {
"name": "BSC Mainnet",
"rpc_url": "https://bsc-dataseed1.binance.org",
"chain_id": 56,
"currency": "BNB",
"bot_manager": "0x4E5d3cD7743934b61041ba2ac3E9df39a0A26dcC",
"paca_contract": "0x3fF44D639a4982A4436f6d737430141aBE68b4E1",
"explorer": "https://bscscan.com"
},
"base": {
"name": "Base Mainnet",
"rpc_url": "https://virtual.base.us-east.rpc.tenderly.co/0552c4f5-a0ca-4b15-860f-fc73a3cb7983",
"chain_id": 8453,
"currency": "ETH",
"bot_manager": "0x811e82b299F58649f1e0AAD33d6ba49Fa87EA969",
"paca_contract": "0xDf2027318D27c4eD1C047B4d6247A7a705bb407b", # Correct Base PACA proxy
"explorer": "https://basescan.org"
},
"sonic": {
"name": "Sonic Network",
"rpc_url": "https://rpc.soniclabs.com",
"chain_id": 146,
"currency": "SONIC",
"bot_manager": "0x5a9A8bE051282dd5505222b9c539EB1898BB5C06",
"paca_contract": "0xa26F8128Ecb2FF2FC5618498758cC82Cf1FDad5F", # Correct Sonic PACA proxy
"explorer": "https://sonicscan.org"
}
}
# If chain_id provided, use it directly, otherwise prompt user
if chain_id:
self.current_chain = chain_id
else:
self.current_chain = self.select_chain()
if not self.current_chain:
print("❌ No chain selected. Exiting.")
sys.exit(1)
# Set up connection for selected chain
self.setup_chain_connection()
def select_chain(self):
"""Prompt user to select which blockchain to use"""
print("\n🌐 Select Blockchain Network:")
print("=" * 30)
print("1. 🟡 BSC Mainnet (Binance Smart Chain)")
print("2. 🔵 Base Mainnet")
print("3. ⚡ Sonic Network")
print("4. 🚪 Exit")
print("-" * 30)
while True:
choice = input("Select network (1-4): ").strip()
if choice == "1":
return "bsc"
elif choice == "2":
return "base"
elif choice == "3":
return "sonic"
elif choice == "4" or choice.lower() in ['exit', 'quit', 'q']:
return None
else:
print("❌ Invalid choice. Please select 1-4.")
def setup_chain_connection(self):
"""Set up Web3 connection for the selected chain"""
chain_config = self.chains[self.current_chain]
print(f"\n🔗 Connecting to {chain_config['name']}...")
# Set up RPC connection
self.rpc_url = chain_config['rpc_url']
self.w3 = Web3(Web3.HTTPProvider(self.rpc_url))
# Add middleware for BSC (POA networks)
if self.current_chain == "bsc":
self.w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
# Set contract addresses for current chain
self.bot_manager_address = chain_config['bot_manager']
self.paca_contract_address = chain_config['paca_contract']
self.currency = chain_config['currency']
self.explorer_url = chain_config['explorer']
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)
chain_config = self.chains[self.current_chain]
print(f"🔑 Connected as: {self.account.address}")
try:
balance = self.w3.eth.get_balance(self.account.address)
print(f"💰 Balance: {self.w3.from_wei(balance, 'ether'):.4f} {self.currency}")
except Exception as e:
print(f"❌ Connection failed: {e}")
sys.exit(1)
print(f"🌐 Network: {chain_config['name']}")
print(f"🤖 BotManager: {self.bot_manager_address}")
print(f"🔗 PACA Contract: {self.paca_contract_address}")
print()
# Contract ABIs
self.bot_manager_abi = [
{
"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"
}
]
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": [
{"internalType": "address", "name": "user", "type": "address"}
],
"name": "getVestings",
"outputs": [
{
"components": [
{"internalType": "uint256", "name": "amount", "type": "uint256"},
{"internalType": "uint256", "name": "bonus", "type": "uint256"},
{"internalType": "uint256", "name": "lockedUntil", "type": "uint256"},
{"internalType": "uint256", "name": "claimedAmount", "type": "uint256"},
{"internalType": "uint256", "name": "claimedBonus", "type": "uint256"},
{"internalType": "uint256", "name": "lastClaimed", "type": "uint256"},
{"internalType": "uint256", "name": "createdAt", "type": "uint256"},
{"internalType": "address", "name": "token", "type": "address"},
{"internalType": "bool", "name": "complete", "type": "bool"},
{"internalType": "uint256", "name": "usdAmount", "type": "uint256"}
],
"internalType": "struct PacaFinanceWithBoostAndScheduleBsc.Vesting[]",
"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_contract_address,
abi=self.paca_abi
)
def validate_address(self, address):
"""Validate Ethereum address format"""
if not address:
return False
# Remove 0x prefix if present
if address.startswith('0x') or address.startswith('0X'):
address = address[2:]
# Check if it's 40 hex characters
if len(address) != 40:
return False
# Check if all characters are hex
return re.match(r'^[0-9a-fA-F]{40}$', address) is not None
def get_user_address(self):
"""Prompt user for an Ethereum address"""
while True:
address = input("📍 Enter user address (0x...): ").strip()
if address.lower() in ['quit', 'exit', 'q']:
return None
if self.validate_address(address):
# Ensure proper format
if not address.startswith('0x'):
address = '0x' + address
return Web3.to_checksum_address(address)
else:
print("❌ Invalid address format. Please enter a valid Ethereum address.")
print(" Example: 0x41970Ce76b656030A79E7C1FA76FC4EB93980255")
print(" (or type 'quit' to exit)")
def send_transaction(self, tx_dict):
"""Send a transaction and wait for confirmation"""
try:
# Estimate gas first
print("⏳ Estimating gas...")
estimated_gas = 2_000_000 # self.w3.eth.estimate_gas(tx_dict)
# Add 50% buffer for safety (especially important for large operations)
gas_limit = int(estimated_gas * 1.5)
print(f"⛽ Estimated gas: {estimated_gas:,}")
print(f"⛽ Gas limit (with buffer): {gas_limit:,}")
# Add gas and nonce
tx_dict['gas'] = gas_limit
gas_price = self.w3.eth.gas_price
tx_dict['maxFeePerGas'] = int(gas_price * 1) # 20% buffer on gas price
tx_dict['maxPriorityFeePerGas'] = int(gas_price)
tx_dict['nonce'] = self.w3.eth.get_transaction_count(self.account.address)
tx_dict['type'] = 2
# Calculate total cost
max_cost = gas_limit * tx_dict['maxFeePerGas']
print(f"⛽ Max Fee Per Gas: {self.w3.from_wei(tx_dict['maxFeePerGas'], 'gwei'):.2f} gwei")
print(f"💰 Max Transaction Cost: {self.w3.from_wei(max_cost, 'ether'):.6f} {self.currency}")
# Ask for confirmation if this is an expensive transaction
if max_cost > self.w3.to_wei(0.01, 'ether'): # If more than 0.01 BNB
confirm = input(f"\n⚠️ High gas cost transaction! Continue? (yes/no): ")
if confirm.lower() not in ['yes', 'y']:
print("❌ Transaction cancelled")
return None
# 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, timeout=300)
if receipt.status == 1:
print("✅ Transaction successful!")
print(f"⛽ Gas used: {receipt.gasUsed:,}")
print(f"🔗 Explorer: {self.explorer_url}/tx/{tx_hash.hex()}")
return receipt
else:
print("❌ Transaction failed!")
return None
except Exception as e:
print(f"❌ Transaction failed: {e}")
return None
def get_stakes(self, user_address):
"""Get and display stakes for a user"""
print(f"\n📊 Getting stakes for: {user_address}")
try:
stakes = self.paca_contract.functions.getStakes(user_address).call()
if len(stakes) == 0:
print("📭 No stakes found for this user")
return stakes
print(f"📈 Found {len(stakes)} stakes:")
total_amount = 0
active_stakes = 0
for i, stake in enumerate(stakes):
amount = stake[0]
last_claimed = stake[1]
daily_reward_rate = stake[2]
unlock_time = stake[3]
complete = stake[4]
is_active = not complete and amount > 0
if is_active:
active_stakes += 1
total_amount += amount
print(f"\n 📌 Stake {i + 1}:")
print(f" Amount: {self.w3.from_wei(amount, 'ether'):.6f} ETH")
print(f" Daily Reward: {self.w3.from_wei(daily_reward_rate, 'ether'):.6f} ETH")
print(f" Complete: {complete}")
print(f" Status: {'🟢 ACTIVE' if is_active else '🔴 COMPLETED'}")
if unlock_time > 0:
import datetime
unlock_date = datetime.datetime.fromtimestamp(unlock_time)
print(f" Unlock: {unlock_date.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"\n💎 Summary:")
print(f" Total Stakes: {len(stakes)}")
print(f" Active Stakes: {active_stakes}")
print(f" Total Active: {self.w3.from_wei(total_amount, 'ether'):.6f} ETH")
return stakes
except Exception as e:
print(f"❌ Error getting stakes: {e}")
return []
def get_vestings(self, user_address):
"""Get and display vestings for a user"""
print(f"\n🔮 Getting vestings for: {user_address}")
try:
vestings = self.paca_contract.functions.getVestings(user_address).call()
if len(vestings) == 0:
print("📭 No vestings found for this user")
return vestings
print(f"📈 Found {len(vestings)} vestings:")
total_amount = 0
active_vestings = 0
for i, vesting in enumerate(vestings):
amount = vesting[0] # amount
bonus = vesting[1] # bonus
locked_until = vesting[2] # lockedUntil
claimed_amount = vesting[3] # claimedAmount
claimed_bonus = vesting[4] # claimedBonus
last_claimed = vesting[5] # lastClaimed
created_at = vesting[6] # createdAt
token = vesting[7] # token
complete = vesting[8] # complete
usd_amount = vesting[9] # usdAmount
is_active = not complete and amount > 0
if is_active:
active_vestings += 1
total_amount += amount
print(f"\n 📌 Vesting {i + 1}:")
print(f" Amount: {self.w3.from_wei(amount, 'ether'):.6f} tokens")
print(f" Bonus: {self.w3.from_wei(bonus, 'ether'):.6f} tokens")
print(f" Claimed: {self.w3.from_wei(claimed_amount, 'ether'):.6f} tokens")
print(f" USD Value: ${self.w3.from_wei(usd_amount, 'ether'):.2f}")
print(f" Token: {token}")
print(f" Complete: {complete}")
print(f" Status: {'🟢 ACTIVE' if is_active else '🔴 COMPLETED'}")
if locked_until > 0:
import datetime
unlock_date = datetime.datetime.fromtimestamp(locked_until)
print(f" Locked Until: {unlock_date.strftime('%Y-%m-%d %H:%M:%S')}")
if created_at > 0:
import datetime
created_date = datetime.datetime.fromtimestamp(created_at)
print(f" Created: {created_date.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"\n💎 Summary:")
print(f" Total Vestings: {len(vestings)}")
print(f" Active Vestings: {active_vestings}")
print(f" Total Active: {self.w3.from_wei(total_amount, 'ether'):.6f} tokens")
return vestings
except Exception as e:
print(f"❌ Error getting vestings: {e}")
return []
def clear_stakes(self, user_address):
"""Clear all stakes for a user"""
print(f"\n🔥 Preparing to clear stakes for: {user_address}")
# First show what will be cleared
stakes = self.get_stakes(user_address)
if not stakes:
return
active_stakes = sum(1 for stake in stakes if not stake[4] and stake[0] > 0)
if active_stakes == 0:
print("⚠️ No active stakes to clear!")
return
print(f"\n⚠️ WARNING: This will clear {active_stakes} active stakes!")
confirm = input("Type 'CONFIRM' to proceed: ")
if confirm != 'CONFIRM':
print("❌ Operation cancelled")
return
print("🔥 Executing clearStakes...")
tx_dict = self.bot_manager.functions.clearStakes(
self.paca_contract_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"\n🔮 Preparing to clear vestings for: {user_address}")
# First show what will be cleared
vestings = self.get_vestings(user_address)
if not vestings:
return
active_vestings = sum(1 for vesting in vestings if not vesting[8] and vesting[0] > 0)
if active_vestings == 0:
print("⚠️ No active vestings to clear!")
return
print(f"\n⚠️ WARNING: This will clear {active_vestings} active vestings!")
confirm = input("Type 'CONFIRM' to proceed: ")
if confirm != 'CONFIRM':
print("❌ Operation cancelled")
return
print("🔮 Executing clearVesting...")
tx_dict = self.bot_manager.functions.clearVesting(
self.paca_contract_address,
user_address
).build_transaction({'from': self.account.address, 'gas': 1000000})
return self.send_transaction(tx_dict)
def clear_both(self, user_address):
"""Clear both stakes and vestings atomically"""
print(f"\n🔥🔮 Preparing to clear BOTH stakes AND vestings for: {user_address}")
# Show both stakes and vestings
stakes = self.get_stakes(user_address)
vestings = self.get_vestings(user_address)
active_stakes = sum(1 for stake in stakes if not stake[4] and stake[0] > 0) if stakes else 0
active_vestings = sum(1 for vesting in vestings if not vesting[8] and vesting[0] > 0) if vestings else 0
if active_stakes == 0 and active_vestings == 0:
print("⚠️ No active stakes or vestings to clear!")
return
print(f"\n⚠️ WARNING: This will clear:")
print(f" - {active_stakes} active stakes")
print(f" - {active_vestings} active vestings")
print(" ⚠️ This operation is ATOMIC - both will be cleared together!")
confirm = input("Type 'CONFIRM' to proceed: ")
if confirm != 'CONFIRM':
print("❌ Operation cancelled")
return
print("🔥🔮 Executing atomic clear...")
# Prepare multi-call
calls = [
{
'target': self.paca_contract_address,
'callData': self.w3.keccak(text="clearStakes(address)")[:4] +
self.w3.eth.codec.encode(['address'], [user_address])
},
{
'target': self.paca_contract_address,
'callData': self.w3.keccak(text="clearVesting(address)")[:4] +
self.w3.eth.codec.encode(['address'], [user_address])
}
]
tx_dict = self.bot_manager.functions.multiCallAtomic(calls).build_transaction({
'from': self.account.address,
'gas': 1000000
})
return self.send_transaction(tx_dict)
def show_menu(self):
"""Display the main menu"""
chain_config = self.chains[self.current_chain]
print("\n" + "="*60)
print(f"🤖 PacaBotManager Operations - {chain_config['name']}")
print("="*60)
print("1. 📊 View Stakes")
print("2. 🔮 View Vestings")
print("3. 🔥 Clear Stakes")
print("4. 🔮 Clear Vestings")
print("5. 🔥🔮 Clear BOTH (Atomic)")
print("6. 🌐 Switch Chain")
print("7. 🏃 Exit")
print("-"*60)
def run(self):
"""Main interactive loop"""
print("🚀 Interactive PacaBotManager Ready!")
while True:
# try:
self.show_menu()
choice = input("Select operation (1-7): ").strip()
if choice == '7' or choice.lower() in ['exit', 'quit', 'q']:
print("👋 Goodbye!")
break
elif choice == '6':
# Switch chain
new_chain = self.select_chain()
if new_chain and new_chain != self.current_chain:
print(f"🔄 Switching from {self.chains[self.current_chain]['name']} to {self.chains[new_chain]['name']}...")
self.current_chain = new_chain
self.setup_chain_connection()
print("✅ Chain switched successfully!")
elif new_chain == self.current_chain:
print(f" Already connected to {self.chains[self.current_chain]['name']}")
continue
elif choice in ['1', '2', '3', '4', '5']:
user_address = self.get_user_address()
if user_address is None:
continue
if choice == '1':
self.get_stakes(user_address)
elif choice == '2':
self.get_vestings(user_address)
elif choice == '3':
self.clear_stakes(user_address)
elif choice == '4':
self.clear_vesting(user_address)
elif choice == '5':
self.clear_both(user_address)
else:
print("❌ Invalid choice. Please select 1-7.")
# except KeyboardInterrupt:
# print("\n\n👋 Interrupted by user. Goodbye!")
# break
# except Exception as e:
# print(f"❌ Error: {e}")
# continue
def main():
"""Main entry point"""
# try:
manager = InteractivePacaBotManager()
manager.run()
# except Exception as e:
# print(f"💥 Fatal error: {e}")
# sys.exit(1)
if __name__ == "__main__":
main()