Commit before cuna
This commit is contained in:
109
python_scripts/README.md
Normal file
109
python_scripts/README.md
Normal 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
|
||||
651
python_scripts/interactive_bot_manager.py
Normal file
651
python_scripts/interactive_bot_manager.py
Normal 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()
|
||||
400
python_scripts/pacabotmanager_client.py
Normal file
400
python_scripts/pacabotmanager_client.py
Normal 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()
|
||||
3
python_scripts/requirements.txt
Normal file
3
python_scripts/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
web3>=6.0.0
|
||||
python-dotenv>=1.0.0
|
||||
eth-account>=0.8.0
|
||||
390
python_scripts/test_client.py
Normal file
390
python_scripts/test_client.py
Normal 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()
|
||||
Reference in New Issue
Block a user