Skip to content

Commit 1d05e01

Browse files
committed
indev
1 parent f0f7ec4 commit 1d05e01

8 files changed

Lines changed: 857 additions & 31 deletions

File tree

alpha_homora_v2/abi/TraderJoeLP_ABI.json

Lines changed: 658 additions & 0 deletions
Large diffs are not rendered by default.

alpha_homora_v2/oracle.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
def get_token_price(token_symbol: str):
1010
"""Get the realtime USD price of a token"""
11-
path = join(abspath((dirname(__file__))), "resources", "coingecko_token_id.csv")
11+
path = join(abspath((dirname(__file__))), "resources", "token_metadata.csv")
1212
id_df = pd.read_csv(path, index_col=0)
13-
token_id = id_df['id'].loc[token_symbol]
13+
token_id = id_df['coingecko_id'].loc[token_symbol]
1414

1515
return cg.get_price(ids=token_id, vs_currencies='usd')[token_id]['usd']

alpha_homora_v2/position.py

Lines changed: 131 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Optional, Union
55

66
from .resources.abi_reference import *
7-
from .util import ContractInstanceFunc, cov_from
7+
from .util import ContractInstanceFunc, cov_from, get_token_info_from_ref
88
from .spell import SpellClient, PangolinV2Client, TraderJoeV1Client
99
from .oracle import get_token_price
1010

@@ -123,18 +123,17 @@ def get_rewards_value(self) -> tuple[float, float, str, str]:
123123
:return:
124124
- reward_amount (float) (in the native reward token)
125125
- reward_value (float) (in USD)
126-
- reward_token_address (address str)
126+
- reward_token_address (str)
127127
- reward_token_symbol (str)
128128
"""
129+
if self.dex != "Pangolin V2":
130+
raise NotImplementedError("This feature is currently only available for positions on the Pangolin V2 DEX")
131+
129132
owner, coll_token, coll_id, collateral_size = self.get_position_info()
130-
print("Untouched Collateral Size:", collateral_size)
131133

132134
pool_info = self.platform.get_pool_info(coll_id)
133-
if self.dex == "Pangolin V2":
134-
entryRewardPerShare = pool_info['entryRewardPerShare'] / 1e18
135-
accRewardPerShare = pool_info['accRewardPerShare'] / 1e18
136-
else:
137-
raise NotImplementedError("Currently this feature is only available for positions on the Pangolin V2 DEX")
135+
entryRewardPerShare = pool_info['entryRewardPerShare'] / 1e18
136+
accRewardPerShare = pool_info['accRewardPerShare'] / 1e18
138137

139138
if accRewardPerShare >= entryRewardPerShare:
140139
reward_amount = collateral_size * (accRewardPerShare - entryRewardPerShare) / 1e12
@@ -155,10 +154,130 @@ def get_debt_ratio(self) -> float:
155154

156155
return borrow_credit / collateral_credit
157156

158-
def get_position_value(self) -> tuple:
159-
"""Returns the value of the position in USD and AVAX"""
160-
raise NotImplementedError
161-
157+
def get_position_value(self):
158+
"""
159+
Get equity, debt, and total position value in AVAX and USD.
160+
161+
:return: (dict)
162+
- equity_avax (float)
163+
- equity_usd (float)
164+
- debt_avax (float)
165+
- debt_usd (float)
166+
- position_avax (float)
167+
- position_usd (float)
168+
"""
169+
# Get pool info & underlying token metadata
170+
pool_info = self.get_pool_info()
171+
underlying_token_data = [get_token_info_from_ref(token) for token in self.get_pool_info()['tokens']]
172+
173+
# Get AVAX price once since operation is heavily reliant on this value
174+
avax_price = get_token_price("AVAX")
175+
176+
# Get token pair liquidity pool data:
177+
pool_instance = self.platform.get_lp_contract(pool_info['lpTokenAddress'])
178+
collateral_size = self.get_position_info()[-1]
179+
r0, r1, last_block_time = pool_instance.functions.getReserves().call()
180+
supply = pool_instance.functions.totalSupply().call()
181+
182+
# Process values by token to get full totals:
183+
debt_value_usd = 0
184+
debt_value_avax = 0
185+
position_value_usd = 0
186+
position_value_avax = 0
187+
for i, token_reserve_amt in enumerate([r0, r1]):
188+
token_price_usd = get_token_price(underlying_token_data[i]["symbol"])
189+
precision = int(underlying_token_data[i]['precision'])
190+
191+
owned_reserve_amt = (token_reserve_amt * collateral_size // supply) / 10 ** precision
192+
owned_reserve_amt_usd = owned_reserve_amt * token_price_usd
193+
# print(f"{underlying_token_data[i]['symbol']} owned_reserve_amt_usd:", owned_reserve_amt_usd)
194+
195+
# Get & calculate token debt for underlying token:
196+
borrow_bal = self.homora_bank.functions.\
197+
borrowBalanceCurrent(self.pos_id, Web3.toChecksumAddress(underlying_token_data[i]['address'])).call()
198+
token_debt = borrow_bal / 10 ** precision
199+
token_debt_usd = token_debt * token_price_usd
200+
token_debt_avax = token_debt_usd / avax_price
201+
202+
# Add debt values to total debt valye count
203+
debt_value_usd += token_debt_usd
204+
debt_value_avax += token_debt_avax
205+
206+
# Add owned reserve values to total position value count
207+
position_value_usd += owned_reserve_amt_usd
208+
position_value_avax += owned_reserve_amt_usd * (1 / avax_price)
209+
210+
# Derive equity values from position and debt:
211+
total_equity_avax = position_value_avax - debt_value_avax
212+
total_equity_usd = position_value_usd - debt_value_usd
213+
214+
return {"equity_avax": total_equity_avax, "equity_usd": total_equity_usd,
215+
"debt_avax": debt_value_avax, "debt_usd": debt_value_usd,
216+
"position_avax": position_value_avax, "position_usd": position_value_usd}
217+
218+
# Deprecated but left temporarily for backup and reference:
219+
# def get_position_value(self):
220+
# """
221+
# Get equity value, debt value, and total position value in AVAX and USD.
222+
#
223+
# :return: (dict)
224+
# - equity_avax (float)
225+
# - equity_usd (float)
226+
# - debt_avax (float)
227+
# - debt_usd (float)
228+
# - position_avax (float)
229+
# - position_usd (float)
230+
# """
231+
# pool_info = self.get_pool_info()
232+
# underlying_token_data = [get_token_info_from_ref(token) for token in self.get_pool_info()['tokens']]
233+
# lp_address = pool_info['lpTokenAddress']
234+
# avax_price = get_token_price("AVAX")
235+
#
236+
# pool_instance = self.platform.get_lp_contract(lp_address)
237+
#
238+
# collateral_size = self.get_position_info()[-1]
239+
#
240+
# r0, r1, last_block_time = pool_instance.functions.getReserves().call()
241+
# supply = pool_instance.functions.totalSupply().call()
242+
#
243+
# token0_amount = (r0 * collateral_size / supply) / 10 ** int(underlying_token_data[0]['precision'])
244+
# token1_amount = (r1 * collateral_size / supply) / 10 ** int(underlying_token_data[1]['precision'])
245+
246+
# position_value_usd = token0_amount + (token1_amount * avax_price)
247+
# position_value_avax = position_value_usd * (1 / avax_price)
248+
#
249+
# print("Position Value:", position_value_usd)
250+
#
251+
# # Get debts for underlying tokens:
252+
# total_debt_usd = 0.0
253+
# total_debt_avax = 0.0
254+
# for i, token in enumerate(pool_info['tokens']):
255+
# borrow_bal = self.homora_bank.functions.borrowBalanceCurrent(self.pos_id,
256+
# Web3.toChecksumAddress(token)).call()
257+
# mtd = get_token_info_from_ref(token)
258+
# if mtd is None:
259+
# raise ValueError(f"Could not locate token metadata for {token}")
260+
#
261+
# token_price_usd = get_token_price(mtd['symbol'])
262+
#
263+
# bbal_in_token = borrow_bal / 10 ** int(mtd['precision'])
264+
# bbal_in_usd = bbal_in_token * token_price_usd
265+
# bbal_in_avax = bbal_in_token / get_token_price('AVAX')
266+
#
267+
# total_debt_usd += bbal_in_usd
268+
# total_debt_avax += bbal_in_avax
269+
#
270+
# print(f"{mtd['symbol']} | "
271+
# f"token{i}_amount: {[token0_amount, token1_amount][i]} | "
272+
# f"r{i}: {[r0, r1][i]}")
273+
#
274+
# equity_avax = position_value_avax - total_debt_avax
275+
# equity_usd = position_value_usd - total_debt_usd
276+
#
277+
# return {"equity_avax": equity_avax, "equity_usd": equity_usd,
278+
# "debt_avax": total_debt_avax, "debt_usd": total_debt_usd,
279+
# "position_avax": position_value_avax, "position_usd": position_value_usd}
280+
162281
""" ------------------------------------------ UTILITY ------------------------------------------ """
163282
def get_platform(self, identifier: str) -> SpellClient:
164283
"""Determine what dex the position is on (i.e. Trader Joe, Pangolin V2, Sushiswap, etc)"""
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""
2-
Config file contains ABI filename and contract address pairs for each ABI
2+
Config file contains tuples:
3+
(ABI filename, contract address)
4+
For each required ABI.
35
46
These pairs can be passed into the ContractInstanceFunc class using unpacking e.g. ContractInstanceFunc(*HomoraBank_ABI)
57
"""
@@ -12,14 +14,8 @@
1214
WMasterchefJoeV2_ABI = "WMasterchefJoeV2_ABI.json",'0xB41DE9c1f50697cC3Fd63F24EdE2B40f6269CBcb'
1315
MasterChefJoeV2_ABI = "MasterChefJoeV2_ABI.json", "0xd6a4F121CA35509aF06A0Be99093d08462f53052"
1416
WMiniChefPNG_ABI = 'WMiniChefPNG.json', '0xa67CF61b0b9BC39c6df04095A118e53BFb9303c7'
15-
PangolinLiquidity_ABI = 'PangolinLiquidityABI.json','0xbd918Ed441767fe7924e99F6a0E0B568ac1970D9'
1617
USDCe_ABI = 'USDC.e_ABI.json', '0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664'
1718
MiniChefV2_ABI = 'Minichef_v2ABI.json', '0x1f806f7C8dED893fd3caE279191ad7Aa3798E928'
1819
SushiswapSpellV1_ABI = "SushiswapSpellV1.json", "0xc4a59cfed3fe06bdb5c21de75a70b20db280d8fe"
19-
20-
# Chainlink Price Feeds
21-
22-
23-
# OTHER:
24-
PNG_DECIMALS = 18
25-
JOE_DECIMALS = 18
20+
PangolinLiquidity_ABI = 'PangolinLiquidityABI.json', None
21+
TraderJoeLP_ABI = 'TraderJoeLP_ABI.json', None

alpha_homora_v2/resources/coingecko_token_id.csv

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
symbol,coingecko_id,precision,address
2+
AVAX,avalanche-2,18,fvweahmxkfeig8snevq42hc6whryy3efyavebmqdndgcgxn5z
3+
PNG,pangolin,18,0x60781c2586d68229fde47564546784ab3faca982
4+
ETH,ethereum,18,0xf20d962a6c8f70c731bd838a3a388d7d48fa6e15
5+
JOE,joe,18,0x6e84a6216ea6dacc71ee8e6b0a5b7322eebc0fdd
6+
USDC,usd,6,0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e
7+
USDC.e,usd,6,0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664
8+
WETH.e,ethereum,18,0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab
9+
WAVAX,avalanche-2,18,0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7
10+
DAI.e,dai,18,0xd586e7f844cea2f87f50152665bcbc2c279d8d70
11+
WBTC.e,bitcoin,8,0x50b7545627a5162f82a992c33b87adc75187b218
12+
USDT.e,usd,6,0xc7198437980c041c805a1edcba50c1ce5db95118
13+
LINK.e,link,18,0x5947bb275c521040051d82396192181b413227a3
14+
ALPHA.e,aavegotchi-alpha,18,0x2147efff675e4a4ee1c2f918d181cdbd7a8e208f
15+
MIM,mim,18,0x130966628846bfd36ff31a822705796e8cb8c18d
16+
UST.axl,usd,6,0x260bbf5698121eb85e7a74f2e45e16ce762ebe11

alpha_homora_v2/spell.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from os.path import join, abspath, dirname
33
from os import getcwd, pardir
44

5+
import web3.eth
56
from web3 import Web3
67
from web3.middleware import geth_poa_middleware
78
from web3.contract import ContractFunction
@@ -18,6 +19,7 @@ class SpellClient:
1819
def __init__(self, web3_provider: Web3, abi_filename: str, contract_address: str,
1920
coll_contract_abi_filename: str, coll_contract_address: str,
2021
staking_contract_abi_filename: str, staking_contract_address: str):
22+
self.w3_provider = web3_provider
2123
self.spell_contract = ContractInstanceFunc(web3_provider,
2224
abi_filename, contract_address)
2325
self.address = Web3.toChecksumAddress(contract_address)
@@ -60,6 +62,17 @@ def get_pool_info(self, coll_id) -> dict:
6062
6163
Returns a dict with key value pairs since the output is variable depending on the platform.
6264
"""
65+
pass
66+
67+
def get_lp_contract(self, lp_token_address: str) -> web3.eth.Contract:
68+
"""
69+
Returns a contract instance for the liquidity pool matching the given address.
70+
This address can be found using the get_pool_info() class method in the positions.AlphaHomoraV2Position.
71+
72+
:param lp_token_address: The contract address for the liquidity pool token on the Spell's network.
73+
:return: Contract instance for using the LP pool methods.
74+
"""
75+
pass
6376

6477

6578
class SushiswapSpellsV1Client(SpellClient):
@@ -125,6 +138,9 @@ def get_pool_info(self, coll_id) -> dict:
125138
"allocPoint": pool_info[1], "lastRewardTimestamp": pool_info[2], "accRewardPerShare": pool_info[3],
126139
"rewarderAddress": pool_info[4]}
127140

141+
def get_lp_contract(self, lp_token_address: str) -> web3.eth.Contract:
142+
return ContractInstanceFunc(self.w3_provider, TraderJoeLP_ABI[0], lp_token_address)
143+
128144

129145
class PangolinV2Client(SpellClient):
130146
def __init__(self, web3_provider: Web3):
@@ -180,3 +196,6 @@ def get_pool_info(self, coll_id) -> dict:
180196
# return pid, entryRewardPerShare, *pool_info
181197
return {"pid": pid, "entryRewardPerShare": entryRewardPerShare, "accRewardPerShare": pool_info[0],
182198
"lastRewardTimestamp": pool_info[1], "allocPoint": pool_info[2]}
199+
200+
def get_lp_contract(self, lp_token_address: str) -> web3.eth.Contract:
201+
return ContractInstanceFunc(self.w3_provider, PangolinLiquidity_ABI[0], lp_token_address)

alpha_homora_v2/util.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from os.path import join, abspath, dirname
22
from os import getcwd, pardir
33
import json
4+
from typing import Union
5+
import csv
46

57
import requests
68
from web3 import Web3
7-
from web3.eth import Contract
89
from web3.middleware import geth_poa_middleware
10+
import web3.eth
911

1012

1113
def cov_from(amount):
@@ -21,7 +23,7 @@ def get_web3_provider(network_rpc_url: str) -> Web3:
2123
return provider
2224

2325

24-
def ContractInstanceFunc(web3_provider: Web3, json_abi_file, contract_address):
26+
def ContractInstanceFunc(web3_provider: Web3, json_abi_file, contract_address) -> web3.eth.Contract:
2527
"""
2628
Set up the contract instance with the provided ABI and address
2729
@@ -50,6 +52,27 @@ def store_abi(abi_url: str, abi_filename: str, abi_path: str = None) -> None:
5052
"""
5153
contract_abi = requests.get(abi_url).json()
5254

53-
path = join(abspath(join(dirname(__file__), pardir)), "abi", abi_filename) if abi_path is None else abi_path
55+
path = join(join(dirname(__file__)), "abi", abi_filename) if abi_path is None else abi_path
5456
with open(path, "w") as json_file:
5557
json_file.write(json.dumps(contract_abi, indent=2))
58+
59+
def get_token_info_from_ref(identifier: str) -> Union[dict, None]:
60+
"""
61+
Get the token info row (dict) from the reference file (resources/token_metadata.csv)
62+
63+
:param identifier: Either the token symbol or the token address
64+
:return: The token info as a dict:
65+
{'symbol (str)', 'coingecko_id (str)', 'precision (str need to convert to int)', 'address (str)'}
66+
"""
67+
path = join(abspath((dirname(__file__))), "resources", "token_metadata.csv")
68+
with open(path) as csv_file:
69+
reader = csv.DictReader(csv_file)
70+
for row in reader:
71+
if row["symbol"].upper() == identifier.upper() or row['address'] == identifier.lower():
72+
return row
73+
return None
74+
75+
def get_all_pool_underlying_token_addresses() -> dict:
76+
"""Did not use exchange identifier because this was used to aggregate all supported tokens for the reference in resources"""
77+
r = requests.get("https://homora-api.alphafinance.io/v2/43114/pools").json()
78+
return {pool['name']: [(pool['name'].split('/')[i], token) for i, token in enumerate(pool['tokens'])] for pool in r}

0 commit comments

Comments
 (0)