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

109
python_scripts/README.md Normal file
View File

@@ -0,0 +1,109 @@
# PacaBotManager Python Client
A Python script to interact with your PacaBotManager contract on BSC mainnet using web3.py.
## Setup
1. **Install dependencies:**
```bash
pip install -r requirements.txt
```
2. **Set up environment variables:**
Create a `.env` file in the project root:
```env
PRIVATE_KEY=your_private_key_here_without_0x_prefix
```
## Usage
### Basic Usage
```bash
python python_scripts/pacabotmanager_client.py
```
### Available Functions
The script provides these main functions:
#### 1. View Functions (Read-only)
```python
# Get contract owners
client.get_owner() # Bot manager owner
client.get_paca_owner() # PACA contract owner
# Get user stakes
stakes = client.get_stakes("0x41970Ce76b656030A79E7C1FA76FC4EB93980255")
```
#### 2. Write Functions (Requires gas fees)
```python
# Clear all stakes for a user
client.clear_stakes("0x41970Ce76b656030A79E7C1FA76FC4EB93980255")
# Clear all vestings for a user
client.clear_vesting("0x41970Ce76b656030A79E7C1FA76FC4EB93980255")
# Execute multiple calls atomically
calls = [
{
'target': paca_address,
'callData': encoded_clear_stakes_call
},
{
'target': paca_address,
'callData': encoded_clear_vesting_call
}
]
client.multi_call_atomic(calls)
```
## Contract Addresses
- **PacaBotManager**: `0x4E5d3cD7743934b61041ba2ac3E9df39a0A26dcC`
- **PACA BSC**: `0x3fF44D639a4982A4436f6d737430141aBE68b4E1`
- **Network**: BSC Mainnet (Chain ID: 56)
## Safety Notes
⚠️ **WARNING**: The `clear_stakes` and `clear_vesting` functions affect real funds on mainnet!
- Only the bot manager owner can call these functions
- Always test on a fork first if possible
- Double-check user addresses before calling
- These operations are irreversible
## Example Output
```
🐍 PacaBotManager Python Client
========================================
🔑 Using account: 0xYourAddress
💰 Balance: 0.295 BNB
🤖 PacaBotManager: 0x4E5d3cD7743934b61041ba2ac3E9df39a0A26dcC
🔗 PACA Contract: 0x3fF44D639a4982A4436f6d737430141aBE68b4E1
🌐 Network: BSC Mainnet
📊 Getting stakes for user: 0x41970Ce76b656030A79E7C1FA76FC4EB93980255
📈 User has 2 stakes:
📌 Stake 1:
Amount: 100.0 ETH
Complete: False
Status: 🟢 ACTIVE
📌 Stake 2:
Amount: 20.357851028442383 ETH
Complete: False
Status: 🟢 ACTIVE
💎 Summary:
Total Stakes: 2
Active Stakes: 2
Total Active Amount: 120.357851028442383 ETH
```
## Troubleshooting
- **"PRIVATE_KEY not found"**: Make sure your `.env` file exists and contains `PRIVATE_KEY=your_key`
- **Gas estimation failed**: The function might be reverting (not authorized, invalid params, etc.)
- **Insufficient funds**: Make sure you have enough BNB for gas fees

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()

View File

@@ -0,0 +1,400 @@
#!/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()

View File

@@ -0,0 +1,3 @@
web3>=6.0.0
python-dotenv>=1.0.0
eth-account>=0.8.0

View File

@@ -0,0 +1,390 @@
#!/usr/bin/env python3
"""
PacaBotManager TEST Client - Safe Testing Version
=================================================
This is a SAFE TEST VERSION of the PacaBotManager client that:
1. Uses BSC testnet or fork for testing
2. Shows what operations would do without executing them
3. Provides dry-run functionality
4. Only executes writes when explicitly confirmed
Usage: python python_scripts/test_client.py
"""
import os
import sys
from web3 import Web3
from eth_account import Account
from dotenv import load_dotenv
import json
# Load environment variables
load_dotenv()
class PacaBotManagerTestClient:
def __init__(self, use_testnet=True):
if use_testnet:
# BSC Testnet for safe testing
self.rpc_url = "https://data-seed-prebsc-1-s1.binance.org:8545"
self.chain_name = "BSC Testnet"
self.chain_id = 97
# You'll need to deploy contracts on testnet for full testing
self.bot_manager_address = None # Deploy on testnet
self.paca_bsc_address = None # Deploy on testnet
else:
# Local fork for testing (safer than mainnet)
self.rpc_url = "http://127.0.0.1:8545" # Local hardhat fork
self.chain_name = "Local Fork"
self.chain_id = 31337
# Mainnet addresses (only for fork testing)
self.bot_manager_address = "0x4E5d3cD7743934b61041ba2ac3E9df39a0A26dcC"
self.paca_bsc_address = "0x3fF44D639a4982A4436f6d737430141aBE68b4E1"
self.w3 = Web3(Web3.HTTPProvider(self.rpc_url))
# Test mode settings
self.dry_run_mode = True # Default to dry run
self.require_confirmation = True
# 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)
try:
balance = self.w3.eth.get_balance(self.account.address)
print(f"🔑 Using account: {self.account.address}")
print(f"💰 Balance: {self.w3.from_wei(balance, 'ether')} ETH")
print(f"🌐 Network: {self.chain_name}")
print(f"🔒 Dry Run Mode: {self.dry_run_mode}")
except Exception as e:
print(f"❌ Connection failed: {e}")
print(f" Make sure the RPC endpoint is accessible: {self.rpc_url}")
sys.exit(1)
# Contract ABIs (same as main client)
self.bot_manager_abi = [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"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": [],
"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": [],
"name": "owner",
"outputs": [
{"internalType": "address", "name": "", "type": "address"}
],
"stateMutability": "view",
"type": "function"
}
]
# Create contract instances if addresses are available
if self.bot_manager_address and self.paca_bsc_address:
try:
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"🤖 BotManager: {self.bot_manager_address}")
print(f"🔗 PACA Contract: {self.paca_bsc_address}")
except Exception as e:
print(f"⚠️ Contract connection failed: {e}")
self.bot_manager = None
self.paca_contract = None
else:
print("⚠️ Contract addresses not set - deploy contracts first for full testing")
self.bot_manager = None
self.paca_contract = None
print()
def confirm_action(self, action_description):
"""Ask for user confirmation before executing"""
if not self.require_confirmation:
return True
print(f"⚠️ CONFIRMATION REQUIRED:")
print(f" Action: {action_description}")
print(f" Network: {self.chain_name}")
print(f" Dry Run: {self.dry_run_mode}")
response = input(" Continue? (yes/no): ").lower().strip()
return response in ['yes', 'y']
def simulate_clear_stakes(self, user_address):
"""Simulate what clearStakes would do without executing"""
print(f"🧪 SIMULATING clearStakes for user: {user_address}")
if not self.paca_contract:
print("❌ PACA contract not available")
return
try:
stakes = self.paca_contract.functions.getStakes(user_address).call()
print(f"📊 Current stakes analysis:")
print(f" Total stakes: {len(stakes)}")
if len(stakes) == 0:
print(" 📭 No stakes found - nothing to clear")
return
stakes_to_clear = 0
amount_to_zero = 0
for i, stake in enumerate(stakes):
amount = stake[0]
complete = stake[4]
if not complete and amount > 0:
stakes_to_clear += 1
amount_to_zero += amount
print(f" 🎯 Would clear Stake {i + 1}: {self.w3.from_wei(amount, 'ether')} ETH")
else:
print(f" ⏭️ Stake {i + 1} already complete: {self.w3.from_wei(amount, 'ether')} ETH")
print(f"\n📋 SIMULATION RESULTS:")
print(f" Stakes to clear: {stakes_to_clear}")
print(f" Total amount to zero: {self.w3.from_wei(amount_to_zero, 'ether')} ETH")
print(f" All stakes would be marked complete: ✅")
return {
'stakes_to_clear': stakes_to_clear,
'amount_to_zero': amount_to_zero,
'total_stakes': len(stakes)
}
except Exception as e:
print(f"❌ Simulation failed: {e}")
return None
def get_stakes_safe(self, user_address):
"""Safely get stakes with error handling"""
print(f"📊 Getting stakes for user: {user_address}")
if not self.paca_contract:
print("❌ PACA contract not available")
return []
try:
stakes = self.paca_contract.functions.getStakes(user_address).call()
print(f"📈 User has {len(stakes)} stakes:")
if len(stakes) == 0:
print(" 📭 No stakes found")
return 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')} 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(f"\n💎 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 test_clear_stakes(self, user_address, execute=False):
"""Test clearStakes with option to execute"""
print(f"\n{'='*60}")
print(f"🧪 TESTING clearStakes for {user_address}")
print(f" Execute: {execute}")
print(f" Dry Run Mode: {self.dry_run_mode}")
print(f"{'='*60}")
# Step 1: Get current stakes
print("\n1⃣ Getting current stakes...")
stakes_before = self.get_stakes_safe(user_address)
if not stakes_before:
print("⚠️ No stakes to test with")
return
# Step 2: Simulate what would happen
print("\n2⃣ Simulating clearStakes...")
simulation = self.simulate_clear_stakes(user_address)
if not simulation or simulation['stakes_to_clear'] == 0:
print("⚠️ No stakes to clear")
return
# Step 3: Execute if requested and confirmed
if execute and not self.dry_run_mode:
print("\n3⃣ Preparing to execute clearStakes...")
action = f"Clear {simulation['stakes_to_clear']} stakes ({self.w3.from_wei(simulation['amount_to_zero'], 'ether')} ETH)"
if not self.confirm_action(action):
print("❌ Action cancelled by user")
return
try:
if not self.bot_manager:
print("❌ Bot manager contract not available")
return
print("⚡ Executing clearStakes...")
# This would execute the actual transaction
# Implementation depends on whether you're on testnet or fork
print(" (Actual execution code would go here)")
print("✅ Transaction would be executed")
except Exception as e:
print(f"❌ Execution failed: {e}")
else:
print("\n3⃣ Execution skipped (dry run mode or not requested)")
print(" To execute: set execute=True and dry_run_mode=False")
def main():
print("🧪 PacaBotManager TEST Client")
print("=" * 40)
print("This is a SAFE testing version that simulates operations")
print()
# Ask user what type of testing they want
print("Testing options:")
print("1. Local fork testing (uses mainnet contracts on fork)")
print("2. Testnet testing (requires testnet contract deployment)")
choice = input("Choose testing mode (1 or 2): ").strip()
try:
if choice == "1":
print("\n🔄 Starting local fork testing...")
client = PacaBotManagerTestClient(use_testnet=False)
else:
print("\n🌐 Starting testnet testing...")
client = PacaBotManagerTestClient(use_testnet=True)
# Test user address
test_user = "0x41970Ce76b656030A79E7C1FA76FC4EB93980255"
print("\n📋 Available Test Operations:")
print("1. Get stakes (safe)")
print("2. Simulate clearStakes (safe)")
print("3. Test clearStakes with execution (requires confirmation)")
print()
# Run safe operations
print("🔍 Running safe operations...")
stakes = client.get_stakes_safe(test_user)
if stakes:
simulation = client.simulate_clear_stakes(test_user)
# Optionally test execution (will ask for confirmation)
execute_test = input("\nRun execution test? (yes/no): ").lower().strip()
if execute_test in ['yes', 'y']:
client.test_clear_stakes(test_user, execute=True)
print("\n✅ Test completed safely!")
except Exception as e:
print(f"💥 Test failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()