400 lines
15 KiB
Python
400 lines
15 KiB
Python
#!/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() |