Skip to content

Commit a3fa115

Browse files
authored
Merge pull request #56 from LawsonGraham/thala-adapter
Thala adapter
2 parents a6fc40c + 8850d86 commit a3fa115

File tree

9 files changed

+2312
-1438
lines changed

9 files changed

+2312
-1438
lines changed

constants/chains.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Chain(Enum):
1313
LYRA = "Lyra"
1414
SWELL = "Swell"
1515
SOLANA = "Solana"
16+
APTOS = "Aptos"
1617
BASE = "Base"
1718
APTOS = "Aptos"
1819
SEPOLIA = "Sepolia"

constants/example_integrations.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
KAMINO_SUSDE_COLLATERAL_START_BLOCK_EXAMPLE = 20471904
1919

20+
THALA_SUSDE_START_BLOCK = 2393932881
21+
2022
ECHELON_SUSDE_COLLATERAL_START_BLOCK = 2379805052
2123

2224
RATEX_EXAMPLE_USDE_START_BLOCK = 21202656

constants/summary_columns.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class SummaryColumn(Enum):
1919
AMBIENT_SCROLL_SHARDS = ("ambient_scroll_shards", SummaryColumnType.ETHENA_PTS)
2020
AMBIENT_SWELL_SHARDS = ("ambient_swell_shards", SummaryColumnType.ETHENA_PTS)
2121

22+
THALA_SHARDS = ("thala_shards", SummaryColumnType.ETHENA_PTS)
23+
2224
ECHELON_SHARDS = ("echelon_shards", SummaryColumnType.ETHENA_PTS)
2325

2426
NURI_SHARDS = ("nuri_shards", SummaryColumnType.ETHENA_PTS)

constants/thala.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
SUSDE_LPT_COIN = "0x7ca61cf9aa2239412154145e863823814b9fec37ef34b469718c5f690919e69e::coins::Coin5"
2+
3+
SUSDE_LPT_PID = 27
4+
5+
ETHENA_ADDRESS_API_URL="https://app.thala.fi/api/ethena-addresses"
6+
7+
THALA_FARMING_V1_ADDRESS = "0x6b3720cd988adeaf721ed9d4730da4324d52364871a68eac62b46d21e4d2fa99"
8+
9+
THALASWAP_V2_ADDRESS = "0x7730cd28ee1cdc9e999336cbc430f99e7c44397c0aa77516f6f23a78559bb5"
10+
11+
SUSDE_LPT_ADDRESS = "0xce9e3b2437fd2cddc5c14f6c4259fc7d3cef160b820837591aa48170bb509368"

integrations/integration_ids.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,20 @@ class IntegrationID(Enum):
6767
)
6868
PENDLE_ZIRCUIT_USDE_YT = ("pendle_zircuit_usde_yt_held", "Pendle Zircuit USDe YT")
6969

70+
# Thala
71+
THALA_SUSDE_LP = (
72+
"thala_susde_usdc_lp",
73+
"Thala sUSDe/USDC LP",
74+
Token.SUSDE,
75+
)
76+
7077
# Echelon
7178
ECHELON_SUSDE_COLLATERAL = (
7279
"echelon_susde_collateral",
7380
"Echelon sUSDe Collateral",
7481
Token.SUSDE,
7582
)
83+
7684
# Stake DAO
7785
STAKEDAO_SUSDE_JULY_LPT = (
7886
"stakedao_susde_july_effective_lpt_held",

integrations/thala_integration.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import logging
2+
import subprocess
3+
import json
4+
from typing import Dict, List, Optional
5+
import requests
6+
7+
from dotenv import load_dotenv
8+
from constants.summary_columns import SummaryColumn
9+
from constants.example_integrations import (
10+
THALA_SUSDE_START_BLOCK,
11+
)
12+
from constants.thala import (
13+
ETHENA_ADDRESS_API_URL,
14+
SUSDE_LPT_ADDRESS,
15+
SUSDE_LPT_COIN,
16+
SUSDE_LPT_PID,
17+
THALA_FARMING_V1_ADDRESS,
18+
THALASWAP_V2_ADDRESS,
19+
)
20+
from constants.chains import Chain
21+
from integrations.integration_ids import IntegrationID as IntID
22+
from integrations.l2_delegation_integration import L2DelegationIntegration
23+
24+
load_dotenv()
25+
26+
27+
class ThalaAptosIntegration(L2DelegationIntegration):
28+
def __init__(
29+
self,
30+
integration_id: IntID,
31+
start_block: int,
32+
token_address: str,
33+
decimals: int,
34+
chain: Chain = Chain.APTOS,
35+
reward_multiplier: int = 1,
36+
):
37+
super().__init__(
38+
integration_id=integration_id,
39+
start_block=start_block,
40+
chain=chain,
41+
summary_cols=[SummaryColumn.THALA_SHARDS],
42+
reward_multiplier=reward_multiplier,
43+
)
44+
self.token_address = token_address
45+
self.decimals = str(decimals)
46+
self.thala_ts_location = "ts/thala_balances.ts"
47+
48+
def get_l2_block_balances(
49+
self, cached_data: Dict[int, Dict[str, float]], blocks: List[int]
50+
) -> Dict[int, Dict[str, float]]:
51+
logging.info("Getting block data for Thala sUSDe LP...")
52+
# Ensure blocks are sorted smallest to largest
53+
block_data: Dict[int, Dict[str, float]] = {}
54+
sorted_blocks = sorted(blocks)
55+
56+
# Populate block data from smallest to largest
57+
for block in sorted_blocks:
58+
user_addresses = self.get_thala_block_participants(block)
59+
60+
result = self.get_thala_block_data(block, user_addresses)
61+
62+
# Store the balances and cache the exchange rate
63+
if result:
64+
block_data[block] = result
65+
66+
return block_data
67+
68+
def get_thala_block_participants(self, block: int) -> List[str]:
69+
try:
70+
response = requests.get(
71+
f"{ETHENA_ADDRESS_API_URL}?block={block}", timeout=10
72+
)
73+
response.raise_for_status()
74+
75+
data = response.json()["data"]
76+
if not isinstance(data, list):
77+
logging.warning(f"Unexpected response format from API: {data}")
78+
return []
79+
80+
return [addr for addr in data if isinstance(addr, str)]
81+
82+
except requests.RequestException as e:
83+
logging.error(f"Request failed for block {block}: {str(e)}")
84+
return []
85+
except Exception as e:
86+
logging.error(f"Error processing participants for block {block}: {str(e)}")
87+
return []
88+
89+
def get_thala_block_data(
90+
self, block: int, user_addresses: Optional[List[str]] = None
91+
):
92+
print("Getting participants data for block: ", block)
93+
if not user_addresses:
94+
user_addresses = []
95+
try:
96+
response = subprocess.run(
97+
[
98+
"ts-node",
99+
self.thala_ts_location,
100+
THALA_FARMING_V1_ADDRESS,
101+
THALASWAP_V2_ADDRESS,
102+
str(SUSDE_LPT_PID),
103+
str(SUSDE_LPT_ADDRESS),
104+
str(self.decimals),
105+
str(block),
106+
json.dumps(user_addresses),
107+
],
108+
capture_output=True,
109+
text=True,
110+
check=True,
111+
)
112+
113+
try:
114+
result = json.loads(response.stdout)
115+
return result
116+
except json.JSONDecodeError as e:
117+
print(f"JSON Decode Error: {e}")
118+
print(f"Raw output: {response.stdout}")
119+
raise
120+
121+
except subprocess.CalledProcessError as e:
122+
print(f"Process error: {e}")
123+
print(f"stderr: {e.stderr}")
124+
raise
125+
except Exception as e:
126+
print(f"Unexpected error: {e}")
127+
raise
128+
129+
130+
if __name__ == "__main__":
131+
example_integration = ThalaAptosIntegration(
132+
integration_id=IntID.THALA_SUSDE_LP,
133+
start_block=THALA_SUSDE_START_BLOCK,
134+
token_address=SUSDE_LPT_COIN,
135+
decimals=8,
136+
chain=Chain.APTOS,
137+
reward_multiplier=5,
138+
)
139+
140+
example_integration_output = example_integration.get_l2_block_balances(
141+
cached_data={},
142+
blocks=list(range(THALA_SUSDE_START_BLOCK, THALA_SUSDE_START_BLOCK + 25306000, 1500000)),
143+
)
144+
145+
print("=" * 120)
146+
print("Run without cached data", example_integration_output)
147+
print("=" * 120, "\n" * 5)

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@aptos-labs/ts-sdk": "^1.33.1",
1414
"@kamino-finance/klend-sdk": "^3.2.7",
1515
"@solana/web3.js": "^1.95.2",
16-
"dotenv": "^16.4.5"
16+
"dotenv": "^16.4.5",
17+
"@aptos-labs/ts-sdk": "1.33.1"
1718
}
1819
}

0 commit comments

Comments
 (0)