diff --git a/great_ape_safe/ape_api/badger.py b/great_ape_safe/ape_api/badger.py index 5372b783..7e80b2a1 100644 --- a/great_ape_safe/ape_api/badger.py +++ b/great_ape_safe/ape_api/badger.py @@ -5,6 +5,7 @@ import pandas as pd from brownie import chain, interface, ZERO_ADDRESS +from eth_utils import to_checksum_address from eth_abi import encode_abi from helpers.addresses import r @@ -49,9 +50,23 @@ def __init__(self, safe): self.station = self.safe.contract(r.badger_wallets.gas_station) self.rewards = self.safe.contract(r.hidden_hand.rewards_distributor) + # paladin + self.paladin_merkle_tree = self.safe.contract( + r.paladin.multi_merkle_distributor + ) + self.paladin_merkle_tree_legacy = self.safe.contract( + r.paladin.merkle_distributor_legacy + ) + # misc self.api_url = "https://api.badger.com/v2/" self.api_hh_url = f"https://api.hiddenhand.finance/reward/{chain.id}/" + self.api_paladin_claim_url = ( + f"https://api.paladin.vote/quest/v2/copilot/claims/" + ) + self.api_paladin_clawback_incentives_url = ( + f"https://api.paladin.vote/quest/v2/copilot/user/" + ) def claim_all(self, json_file_path=None): """ @@ -210,6 +225,82 @@ def transform_claimable(amount_string, n_decimals): return dict(zip(aggregate["tokens"], aggregate["amounts"])) + def get_paladin_data(self, address=None): + """ + get paladin data for a particular address for claiming its rewards (merkle tree data) + """ + address = address if address else self.safe.address + url = self.api_paladin_claim_url + address + r = requests.get(url) + if not r.ok: + r.raise_for_status() + return r.json()["claims"] + + def get_paladin_quests_info(self, address=None): + """ + get paladin data for a particular address related to posted quests + which are closed currently and there may be withdrawable incentives unallocated + """ + address = address if address else self.safe.address + url = self.api_paladin_clawback_incentives_url + address + r = requests.get(url) + if not r.ok: + r.raise_for_status() + return r.json()["quests"] + + def claim_bribes_from_paladin(self): + """ + grabs the available claimable tokens from Paladin endpoint, + shape the endpoint information and claims tokens for the safe. + """ + data = [self.get_paladin_data()[-1]] + + claim_data = [] + claim_data_legacy = [] + for entry in data: + if ( + to_checksum_address(entry["distributor"]) + == self.paladin_merkle_tree.address + ): + # https://etherscan.io/address/0xccd73d064ed07964ad2144fdfd1b99e7e6b5f626#code#F2#L162 + claim_data.append( + ( + entry["questId"], + entry["period"], + entry["index"], + entry["amount"], + entry["proofs"], + ) + ) + else: + claim_data_legacy.append( + ( + entry["questId"], + entry["period"], + entry["index"], + entry["amount"], + entry["proofs"], + ) + ) + + if len(claim_data) > 0: + self.paladin_merkle_tree.multiClaim(self.safe, claim_data) + if len(claim_data_legacy) > 0: + self.paladin_merkle_tree_legacy.multiClaim(self.safe, claim_data_legacy) + + def claw_back_incentives_from_paladin(self): + """ + Inspects the quests available in Paladin endpoint for the safe, + check if there are still pending questID, which needs to be claim back. + """ + data = self.get_paladin_quests_info() + + for entry in data: + if int(entry["general"]["withdrawable"]) > 0: + self.safe.contract(entry["general"]["board"]).withdrawUnusedRewards( + entry["general"]["questId"], self.safe + ) + def sweep_reward_token(self, token_addr): """ Sweeps reward token from the vestedCVX strategy into diff --git a/helpers/addresses.py b/helpers/addresses.py index a12fdd06..1918d414 100644 --- a/helpers/addresses.py +++ b/helpers/addresses.py @@ -706,6 +706,8 @@ }, "paladin": { "quest_board_veliq": "0x1B921DBD13A280ee14BA6361c1196EB72aaa094e", + "merkle_distributor_legacy": "0x069B449d8fd744ef09cBC07FE871F52627FE22B0", + "multi_merkle_distributor": "0xCcD73d064Ed07964Ad2144fDFd1b99e7E6b5f626", "_deprecated": { "quest_board_veliq": "0xcbd27bf506aB5580Ef86Fe6a169449bc24Be471B", }, diff --git a/scripts/badger/claim_voter_hh.py b/scripts/badger/claim_voter_hh.py deleted file mode 100644 index eb1cea96..00000000 --- a/scripts/badger/claim_voter_hh.py +++ /dev/null @@ -1,21 +0,0 @@ -from great_ape_safe import GreatApeSafe -from helpers.addresses import r - - -def main(): - voter = GreatApeSafe(r.badger_wallets.treasury_voter_multisig) - trops = GreatApeSafe(r.badger_wallets.treasury_ops_multisig) - voter.init_badger() - - rewards = [x["token"] for x in voter.badger.get_hh_data()] - - trops.take_snapshot(tokens=rewards) - - voter.badger.claim_bribes_hidden_hands(claim_from_strat=False) - - for token in rewards: - token = voter.contract(token) - token.transfer(trops, token.balanceOf(voter)) - - trops.print_snapshot() - voter.post_safe_tx() diff --git a/scripts/badger/claim_voter_incentives.py b/scripts/badger/claim_voter_incentives.py new file mode 100644 index 00000000..17f3061f --- /dev/null +++ b/scripts/badger/claim_voter_incentives.py @@ -0,0 +1,29 @@ +from great_ape_safe import GreatApeSafe +from helpers.addresses import r + + +def main(): + voter = GreatApeSafe(r.badger_wallets.treasury_voter_multisig) + trops = GreatApeSafe(r.badger_wallets.treasury_ops_multisig) + voter.init_badger() + + hh_data = voter.badger.get_hh_data() + + # incentives tokens expected on epochs: $badger and/or $liquis + rewards = [r.treasury_tokens.BADGER, r.treasury_tokens.LIQ] + + trops.take_snapshot(tokens=rewards) + + if len(hh_data) > 0: + voter.badger.claim_bribes_hidden_hands(claim_from_strat=False) + + voter.badger.claim_bribes_from_paladin() + + for token in rewards: + token = voter.contract(token) + token_balance = token.balanceOf(voter) + if token_balance > 0: + token.transfer(trops, token_balance) + + trops.print_snapshot() + voter.post_safe_tx() diff --git a/scripts/badger/claw_back_incentives_paladin.py b/scripts/badger/claw_back_incentives_paladin.py new file mode 100644 index 00000000..1086bb48 --- /dev/null +++ b/scripts/badger/claw_back_incentives_paladin.py @@ -0,0 +1,19 @@ +from great_ape_safe import GreatApeSafe +from helpers.addresses import r + + +def main(): + safe = GreatApeSafe(r.badger_wallets.treasury_ops_multisig) + safe.init_badger() + + # tokens + liq = safe.contract(r.treasury_tokens.LIQ) + badger = safe.contract(r.treasury_tokens.BADGER) + + # snap + safe.take_snapshot(tokens=[badger, liq]) + + # check if there are claimable withdrawable incentives in paladin + safe.badger.claw_back_incentives_from_paladin() + + safe.post_safe_tx()