diff --git a/.env.example b/.env.example index f0e414a9..152d7859 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,5 @@ SOLANA_NODE_URL='https://api.mainnet-beta.solana.com' BASE_NODE_URL="https://mainnet.base.org" HYPEREVM_NODE_URL="https://rpc.hyperlend.finance/archive" PLASMA_NODE_URL="https://rpc.plasma.to" +BERACHAIN_NODE_URL="https://rpc.berachain.com" DERIVE_SUBGRAPH_API_KEY='' \ No newline at end of file diff --git a/constants/berachain.py b/constants/berachain.py new file mode 100644 index 00000000..93792eb1 --- /dev/null +++ b/constants/berachain.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass +from typing import Dict + +from integrations.integration_ids import IntegrationID + +ZERO_ADRESS = "0x0000000000000000000000000000000000000000" + +@dataclass +class BerachainPoolConfig: + start_block: int + pool: str + reward_vault: str | None + + +BERACHAIN_CONFIGS: Dict[IntegrationID, BerachainPoolConfig] = { + IntegrationID.BERACHAIN_LP_sUSDe_USDe_HONEY_POOL: BerachainPoolConfig( + start_block=11814240, + pool="0x1e1ff653525875bf0f4d41d897fe46f0fa3c2dc7", + reward_vault="0x51d4dc2fe8ad332dc47a36480c20f9bb0d293f76" + ), +} diff --git a/constants/chains.py b/constants/chains.py index dee23880..d7f5a9ee 100644 --- a/constants/chains.py +++ b/constants/chains.py @@ -19,3 +19,4 @@ class Chain(Enum): TON = "Ton" HYPEREVM = "HyperEVM" PLASMA = "Plasma" + BERACHAIN = "Berachain" diff --git a/constants/summary_columns.py b/constants/summary_columns.py index 877b5527..ea805cc6 100644 --- a/constants/summary_columns.py +++ b/constants/summary_columns.py @@ -108,6 +108,8 @@ class SummaryColumn(Enum): UNISWAP_V4_POOL_PTS = ("uniswap_v4_pool_pts", SummaryColumnType.ETHENA_PTS) + BERACHAIN_LP_sUSDe_USDe_HONEY_POOL = ("berachain_sUSDe_USDe_HONEY_pool", SummaryColumnType.ETHENA_PTS) + def __init__(self, column_name: str, col_type: SummaryColumnType): self.column_name = column_name self.col_type = col_type diff --git a/integrations/berachain_bex_integration.py b/integrations/berachain_bex_integration.py new file mode 100644 index 00000000..1e95a0d4 --- /dev/null +++ b/integrations/berachain_bex_integration.py @@ -0,0 +1,42 @@ +from typing import List, Optional, Set +from integrations.integration import Integration +from integrations.integration_ids import IntegrationID + +from utils.berachain import get_pool_token_holders, get_user_balance +from constants.berachain import BERACHAIN_CONFIGS + + +class BerachainBEXIntegration(Integration): + + def __init__(self, integration_id: IntegrationID): + config = BERACHAIN_CONFIGS[integration_id] + self.start_block = config.start_block + self.pool = config.pool + self.reward_vault = config.reward_vault + + def get_balance(self, user: str, block: int | str = "latest") -> float: + pool_balance = get_user_balance(user, self.pool, block) + + reward_vault_balance = 0 if self.reward_vault is None else get_user_balance(user, self.reward_vault, block) + + return pool_balance + reward_vault_balance + + def get_participants( + self, + blocks: Optional[List[int]], + ) -> Set[str]: + return get_pool_token_holders(self.pool, self.reward_vault, self.start_block) + +if __name__ == "__main__": + berachain_bex_integration = BerachainBEXIntegration( + IntegrationID.BERACHAIN_LP_sUSDe_USDe_HONEY_POOL + ) + partecipants = berachain_bex_integration.get_participants(None) + print(partecipants) + for p in partecipants: + + print(p, + berachain_bex_integration.get_balance( + p, 11905710 + ) + ) diff --git a/integrations/integration_ids.py b/integrations/integration_ids.py index 11df7489..a514d2b7 100644 --- a/integrations/integration_ids.py +++ b/integrations/integration_ids.py @@ -580,6 +580,13 @@ class IntegrationID(Enum): Token.USDE, ) + # Berachain BEX + BERACHAIN_LP_sUSDe_USDe_HONEY_POOL = ( + "berachain_sUSDe_USDe_HONEY_pool", + "Berachain sUSDe | USDe | HONEY Pool", + Token.USDE + ) + def __init__(self, column_name: str, description: str, token: Token = Token.USDE): self.column_name = column_name self.description = description diff --git a/utils/berachain.py b/utils/berachain.py new file mode 100644 index 00000000..48572ee4 --- /dev/null +++ b/utils/berachain.py @@ -0,0 +1,64 @@ +from constants.berachain import ZERO_ADRESS +from constants.chains import Chain +from utils.web3_utils import W3_BY_CHAIN, call_with_retry, fetch_events_logs_with_retry +from web3 import Web3 + +import json + +with open("abi/ERC20_abi.json") as f: + erc20_abi = json.load(f) + +PAGE_SIZE = 2000 + +BERACHAIN_W3 = W3_BY_CHAIN[Chain.BERACHAIN]["w3"] + +def get_pool_token_holders( + token_address: str, reward_vault: str, start_block: int +) -> list: + + token_contract = BERACHAIN_W3.eth.contract( + address=Web3.to_checksum_address(token_address), abi=erc20_abi + ) + + token_holders = set() + latest_block = BERACHAIN_W3.eth.get_block_number() + + print(latest_block) + + while start_block < latest_block: + to_block = min(start_block + PAGE_SIZE, latest_block) + transfers = fetch_events_logs_with_retry( + f"Getting Berachain Pool Token Holder {token_address}", + token_contract.events.Transfer(), + start_block, + to_block, + ) + + print(start_block, to_block, len(transfers), "Getting Balancer ERC20 Transfers") + + IGNORED_ADDRESSES = [ZERO_ADRESS] + if reward_vault is not None: + IGNORED_ADDRESSES.append(reward_vault) + + for transfer in transfers: + if transfer["args"]["to"] not in IGNORED_ADDRESSES: + token_holders.add(transfer["args"]["to"]) + + start_block += PAGE_SIZE + + return list(token_holders) + +def get_user_balance( + user: str, token_address: str, block: int | str +) -> float: + + token_contract = BERACHAIN_W3.eth.contract( + address=BERACHAIN_W3.to_checksum_address(token_address), abi=erc20_abi + ) + + user_balance = call_with_retry( + token_contract.functions.balanceOf(user), + block, + ) + + return user_balance \ No newline at end of file diff --git a/utils/web3_utils.py b/utils/web3_utils.py index d345b7bd..c8a8dc4c 100644 --- a/utils/web3_utils.py +++ b/utils/web3_utils.py @@ -42,6 +42,9 @@ w3_hyperevm = Web3(Web3.HTTPProvider(HYPEREVM_NODE_URL)) PLASMA_NODE_URL = os.getenv("PLASMA_NODE_URL") w3_plasma = Web3(Web3.HTTPProvider(PLASMA_NODE_URL)) +BERACHAIN_NODE_URL = os.getenv("BERACHAIN_NODE_URL") +w3_berachain = Web3(Web3.HTTPProvider(BERACHAIN_NODE_URL)) + W3_BY_CHAIN = { Chain.ETHEREUM: { @@ -89,6 +92,9 @@ Chain.PLASMA: { "w3": w3_plasma, }, + Chain.BERACHAIN: { + "w3": w3_berachain + } } MULTICALL_ABI = [