script to calculate the APY for pools
This commit is contained in:
278
script/apy_calculator_multi.py
Normal file
278
script/apy_calculator_multi.py
Normal file
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to calculate APY for all PartyPools.
|
||||
Fetches all pools from PartyPlanner and calculates APY for each one.
|
||||
"""
|
||||
|
||||
from web3 import Web3
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
# Configuration
|
||||
RPC_URL = "https://eth-sepolia.g.alchemy.com/v2/sJ7rLYKJzdRqXUs9cOiLVvHN8aTI30dn"
|
||||
CHAIN_ID = 11155111
|
||||
DEPLOYMENT_JSON_PATH = "../liqp-deployments.json"
|
||||
DEPLOYMENT_ARTIFACTS_PATH = "../deployment"
|
||||
TIME_RANGE_SECONDS = 3600 # 1 hour
|
||||
|
||||
# ABIs will be loaded dynamically from deployment artifacts
|
||||
ERC20_ABI = []
|
||||
PARTY_INFO_ABI = []
|
||||
PARTY_PLANNER_ABI = []
|
||||
POOL_ABI = []
|
||||
|
||||
|
||||
def load_abi_from_deployment(chain_id, contract_name):
|
||||
"""Load ABI from deployment artifacts"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
artifact_path = os.path.join(
|
||||
script_dir,
|
||||
DEPLOYMENT_ARTIFACTS_PATH,
|
||||
str(chain_id),
|
||||
'v1',
|
||||
'out',
|
||||
f'{contract_name}.sol',
|
||||
f'{contract_name}.json'
|
||||
)
|
||||
|
||||
try:
|
||||
with open(artifact_path, 'r') as f:
|
||||
artifact_data = json.load(f)
|
||||
return artifact_data.get('abi', [])
|
||||
except Exception as e:
|
||||
print(f"Error loading ABI for {contract_name}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def q64_64_to_float(q64_64_value):
|
||||
"""Convert ABDK Q64.64 fixed-point to float"""
|
||||
return q64_64_value / (2 ** 64)
|
||||
|
||||
|
||||
def find_block_by_timestamp(w3, target_timestamp, low, high):
|
||||
"""Binary search to find block closest to target timestamp"""
|
||||
if low >= high:
|
||||
return low
|
||||
mid = (low + high) // 2
|
||||
mid_block = w3.eth.get_block(mid)
|
||||
mid_timestamp = mid_block['timestamp']
|
||||
if mid_timestamp < target_timestamp:
|
||||
return find_block_by_timestamp(w3, target_timestamp, mid + 1, high)
|
||||
else:
|
||||
return find_block_by_timestamp(w3, target_timestamp, low, mid)
|
||||
|
||||
|
||||
def calculate_pool_apy(w3, pool_address, party_info_contract, from_block, current_block):
|
||||
"""Calculate APY for a single pool"""
|
||||
try:
|
||||
pool_address = Web3.to_checksum_address(pool_address)
|
||||
pool_contract = w3.eth.contract(address=pool_address, abi=POOL_ABI)
|
||||
token_addresses = pool_contract.functions.allTokens().call()
|
||||
|
||||
# Get token info and calculate TVL
|
||||
token_info = {}
|
||||
stablecoin_decimals = None
|
||||
pool_tvl_usd = 0.0
|
||||
token_symbols = []
|
||||
|
||||
for idx, token_addr in enumerate(token_addresses):
|
||||
token_contract = w3.eth.contract(address=token_addr, abi=ERC20_ABI)
|
||||
decimals = token_contract.functions.decimals().call()
|
||||
symbol = token_contract.functions.symbol().call()
|
||||
token_symbols.append(symbol)
|
||||
balance_raw = token_contract.functions.balanceOf(pool_address).call()
|
||||
balance_human = balance_raw / (10 ** decimals)
|
||||
|
||||
if idx == 0:
|
||||
stablecoin_decimals = decimals
|
||||
price_usd = 1.0
|
||||
else:
|
||||
price_q64_64 = party_info_contract.functions.price(pool_address, idx, 0).call()
|
||||
price_ratio = q64_64_to_float(price_q64_64)
|
||||
decimal_adjustment = 10 ** (stablecoin_decimals - decimals)
|
||||
price_usd = price_ratio * decimal_adjustment
|
||||
|
||||
token_value_usd = balance_human * price_usd
|
||||
pool_tvl_usd += token_value_usd
|
||||
token_info[token_addr.lower()] = {'decimals': decimals, 'price_usd': price_usd}
|
||||
|
||||
# Fetch events and calculate fees
|
||||
total_fees_usd = 0.0
|
||||
event_count = 0
|
||||
|
||||
# Process Swap events
|
||||
try:
|
||||
logs = pool_contract.events.Swap().get_logs(from_block=from_block, to_block=current_block)
|
||||
event_count += len(logs)
|
||||
for log in logs:
|
||||
token = token_info.get(log['args']['tokenIn'].lower())
|
||||
if token:
|
||||
fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
|
||||
total_fees_usd += fee
|
||||
print(f" Swap - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
|
||||
except Exception as e:
|
||||
print(f" Error fetching Swap events: {e}")
|
||||
|
||||
# Process SwapMint events
|
||||
try:
|
||||
logs = pool_contract.events.SwapMint().get_logs(from_block=from_block, to_block=current_block)
|
||||
event_count += len(logs)
|
||||
for log in logs:
|
||||
token = token_info.get(log['args']['tokenIn'].lower())
|
||||
if token:
|
||||
fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
|
||||
total_fees_usd += fee
|
||||
print(f" SwapMint - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
|
||||
except Exception as e:
|
||||
print(f" Error fetching SwapMint events: {e}")
|
||||
|
||||
# Process BurnSwap events
|
||||
try:
|
||||
logs = pool_contract.events.BurnSwap().get_logs(from_block=from_block, to_block=current_block)
|
||||
event_count += len(logs)
|
||||
for log in logs:
|
||||
token = token_info.get(log['args']['tokenOut'].lower())
|
||||
if token:
|
||||
fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
|
||||
total_fees_usd += fee
|
||||
print(f" BurnSwap - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
|
||||
except Exception as e:
|
||||
print(f" Error fetching BurnSwap events: {e}")
|
||||
|
||||
# Process Flash events
|
||||
try:
|
||||
logs = pool_contract.events.Flash().get_logs(from_block=from_block, to_block=current_block)
|
||||
event_count += len(logs)
|
||||
for log in logs:
|
||||
token = token_info.get(log['args']['token'].lower())
|
||||
if token:
|
||||
fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
|
||||
total_fees_usd += fee
|
||||
print(f" Flash - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
|
||||
except Exception as e:
|
||||
print(f" Error fetching Flash events: {e}")
|
||||
|
||||
# Calculate APY
|
||||
if pool_tvl_usd > 0 and TIME_RANGE_SECONDS:
|
||||
days = TIME_RANGE_SECONDS / 86400
|
||||
daily_return = (total_fees_usd / pool_tvl_usd) / days
|
||||
apy = (pow(1 + daily_return, 365) - 1) * 100
|
||||
else:
|
||||
apy = 0
|
||||
|
||||
return {
|
||||
'pool_address': pool_address,
|
||||
'tokens': token_symbols,
|
||||
'tvl_usd': pool_tvl_usd,
|
||||
'fees_usd': total_fees_usd,
|
||||
'event_count': event_count,
|
||||
'apy': apy
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error processing pool {pool_address}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
w3 = Web3(Web3.HTTPProvider(RPC_URL))
|
||||
|
||||
if not w3.is_connected():
|
||||
print(f"Error: Cannot connect to RPC node at {RPC_URL}")
|
||||
return
|
||||
|
||||
chain_id = w3.eth.chain_id
|
||||
print(f"Connected to chain {chain_id}")
|
||||
print(f"Current block: {w3.eth.block_number}")
|
||||
|
||||
# Load ABIs from deployment artifacts
|
||||
print("Loading ABIs from deployment artifacts...")
|
||||
erc20_abi = load_abi_from_deployment(chain_id, 'ERC20')
|
||||
party_info_abi = load_abi_from_deployment(chain_id, 'PartyInfo')
|
||||
party_planner_abi = load_abi_from_deployment(chain_id, 'PartyPlanner')
|
||||
pool_abi = load_abi_from_deployment(chain_id, 'PartyPool')
|
||||
|
||||
if not erc20_abi or not party_info_abi or not party_planner_abi or not pool_abi:
|
||||
print("Error: Failed to load one or more ABIs from deployment artifacts")
|
||||
return
|
||||
|
||||
# Update global ABIs
|
||||
global ERC20_ABI, PARTY_INFO_ABI, PARTY_PLANNER_ABI, POOL_ABI
|
||||
ERC20_ABI = erc20_abi
|
||||
PARTY_INFO_ABI = party_info_abi
|
||||
PARTY_PLANNER_ABI = party_planner_abi
|
||||
POOL_ABI = pool_abi
|
||||
|
||||
# Load deployment addresses
|
||||
# tim is adding comments
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
deployment_path = os.path.join(script_dir, DEPLOYMENT_JSON_PATH)
|
||||
|
||||
try:
|
||||
with open(deployment_path, 'r') as f:
|
||||
deployment_data = json.load(f)
|
||||
chain_data = deployment_data.get(str(chain_id), {}).get('v1', {})
|
||||
party_info_address = chain_data.get('PartyInfo')
|
||||
party_planner_address = chain_data.get('PartyPlanner')
|
||||
|
||||
if not party_info_address or not party_planner_address:
|
||||
print(f"Error: PartyInfo or PartyPlanner not found in deployment JSON")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"Error: Could not load deployment JSON: {e}")
|
||||
return
|
||||
|
||||
party_info_address = Web3.to_checksum_address(party_info_address)
|
||||
party_planner_address = Web3.to_checksum_address(party_planner_address)
|
||||
|
||||
print(f"PartyInfo: {party_info_address}")
|
||||
print(f"PartyPlanner: {party_planner_address}")
|
||||
|
||||
# Get all pools from PartyPlanner
|
||||
party_planner = w3.eth.contract(address=party_planner_address, abi=PARTY_PLANNER_ABI)
|
||||
party_info_contract = w3.eth.contract(address=party_info_address, abi=PARTY_INFO_ABI)
|
||||
|
||||
pool_count = party_planner.functions.poolCount().call()
|
||||
print(f"\nTotal pools: {pool_count}")
|
||||
|
||||
if pool_count == 0:
|
||||
print("No pools found")
|
||||
return
|
||||
|
||||
# Get all pools
|
||||
all_pools = party_planner.functions.getAllPools(0, pool_count).call()
|
||||
print(f"Fetched {len(all_pools)} pool addresses\n")
|
||||
|
||||
# Calculate block range for last 24 hours
|
||||
current_block = w3.eth.block_number
|
||||
current_timestamp = w3.eth.get_block(current_block)['timestamp']
|
||||
target_timestamp = current_timestamp - TIME_RANGE_SECONDS
|
||||
from_block = find_block_by_timestamp(w3, target_timestamp, 0, current_block)
|
||||
|
||||
print(f"Analyzing events from block {from_block} to {current_block}")
|
||||
print(f"Time range: last {TIME_RANGE_SECONDS / 3600:.1f} hours\n")
|
||||
|
||||
# Calculate APY for each pool
|
||||
print(f"{'='*110}")
|
||||
print(f"{'Pool Address':<45} {'Tokens':<20} {'TVL (USD)':<15} {'Fees (24h)':<15} {'Events':<8} {'APY':<10}")
|
||||
print(f"{'='*110}")
|
||||
|
||||
results = []
|
||||
for pool_addr in all_pools:
|
||||
result = calculate_pool_apy(w3, pool_addr, party_info_contract, from_block, current_block)
|
||||
if result:
|
||||
results.append(result)
|
||||
token_str = '/'.join(result['tokens'][:3])
|
||||
print(f"{result['pool_address']:<45} {token_str:<20} ${result['tvl_usd']:>13,.2f} ${result['fees_usd']:>13,.6f} {result['event_count']:>7} {result['apy']:>9.2f}%")
|
||||
|
||||
print(f"{'='*110}")
|
||||
print(f"\nSummary:")
|
||||
print(f" Total Pools: {len(results)}")
|
||||
print(f" Total TVL: ${sum(r['tvl_usd'] for r in results):,.2f}")
|
||||
print(f" Total 24h Fees: ${sum(r['fees_usd'] for r in results):,.6f}")
|
||||
if results:
|
||||
print(f" Avg APY: {sum(r['apy'] for r in results) / len(results):.2f}%")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user