Skip to content

Commit

Permalink
Merge pull request #74 from LawsonGraham/echelon-adapter-v2
Browse files Browse the repository at this point in the history
Echelon adapter
  • Loading branch information
Mostafatalaat770 authored Mar 4, 2025
2 parents fafa588 + 6bc6136 commit a6fc40c
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 1 deletion.
3 changes: 2 additions & 1 deletion constants/chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ class Chain(Enum):
SWELL = "Swell"
SOLANA = "Solana"
BASE = "Base"
SEPOLIA = "Sepolia"
APTOS = "Aptos"
SEPOLIA = "Sepolia"
7 changes: 7 additions & 0 deletions constants/echelon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
LENDING_CONTRACT_ADDRESS = "0xc6bc659f1649553c1a3fa05d9727433dc03843baac29473c817d06d39e7621ba"

SUSDE_MARKET_ADDRESS = "0x778362f04f7904ba0b76913ec7c0c5cc04e469b0b96929c6998b34910690a740"

SUSDE_TOKEN_ADDRESS = "0xb30a694a344edee467d9f82330bbe7c3b89f440a1ecd2da1f3bca266560fce69"

ETHENA_ADDRESS_API_URL = "https://app.echelon.market/api/ethena-addresses"
2 changes: 2 additions & 0 deletions constants/example_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@

KAMINO_SUSDE_COLLATERAL_START_BLOCK_EXAMPLE = 20471904

ECHELON_SUSDE_COLLATERAL_START_BLOCK = 2379805052

RATEX_EXAMPLE_USDE_START_BLOCK = 21202656
2 changes: 2 additions & 0 deletions constants/summary_columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class SummaryColumn(Enum):
AMBIENT_SCROLL_SHARDS = ("ambient_scroll_shards", SummaryColumnType.ETHENA_PTS)
AMBIENT_SWELL_SHARDS = ("ambient_swell_shards", SummaryColumnType.ETHENA_PTS)

ECHELON_SHARDS = ("echelon_shards", SummaryColumnType.ETHENA_PTS)

NURI_SHARDS = ("nuri_shards", SummaryColumnType.ETHENA_PTS)
LENDLE_MANTLE_SHARDS = ("lendle_mantle_shards", SummaryColumnType.ETHENA_PTS)

Expand Down
137 changes: 137 additions & 0 deletions integrations/echelon_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import logging
import subprocess
import json
import requests

from typing import Dict, List, Set
from dotenv import load_dotenv
from constants.summary_columns import SummaryColumn
from constants.example_integrations import (
ECHELON_SUSDE_COLLATERAL_START_BLOCK,
)
from constants.echelon import LENDING_CONTRACT_ADDRESS, SUSDE_MARKET_ADDRESS, SUSDE_TOKEN_ADDRESS, ETHENA_ADDRESS_API_URL
from constants.chains import Chain
from integrations.integration_ids import IntegrationID as IntID
from integrations.l2_delegation_integration import L2DelegationIntegration

load_dotenv()

class EchelonAptosIntegration(L2DelegationIntegration):
def __init__(
self,
integration_id: IntID,
start_block: int,
token_address: str,
market_address: str,
decimals: int,
chain: Chain = Chain.APTOS,
reward_multiplier: int = 1,
):
super().__init__(
integration_id=integration_id,
start_block=start_block,
chain=chain,
summary_cols=[SummaryColumn.ECHELON_SHARDS],
reward_multiplier=reward_multiplier,
)
self.token_address = token_address
self.market_address = market_address
self.decimals = str(decimals)
self.echelon_ts_location = "ts/echelon_balances.ts"

def get_l2_block_balances(
self, cached_data: Dict[int, Dict[str, float]], blocks: List[int]
) -> Dict[int, Dict[str, float]]:
logging.info("Getting block data for Echelon sUSDe Collateral...")
# Ensure blocks are sorted smallest to largest
block_data: Dict[int, Dict[str, float]] = {}
sorted_blocks = sorted(blocks)

# Populate block data from smallest to largest
for block in sorted_blocks:
user_addresses = self.get_participants(block)
result = self.get_participants_data(block, user_addresses[0:20])

# Store the balances and cache the exchange rate
block_data[block] = result

return block_data

def get_participants_data(self, block, user_addresses=[]):
print("Getting participants data for block", block)
try:
response = subprocess.run(
[
"ts-node",
self.echelon_ts_location,
LENDING_CONTRACT_ADDRESS,
self.market_address,
str(self.decimals),
str(block),
json.dumps(user_addresses),
],
capture_output=True,
text=True,
check=True
)

# Debug output
print("TypeScript stdout:", response.stdout)
print("TypeScript stderr:", response.stderr)

try:
result = json.loads(response.stdout)
return result # Now returns dict with both balances and exchange rate
except json.JSONDecodeError as e:
print(f"JSON Decode Error: {e}")
print(f"Raw output: {response.stdout}")
raise

except subprocess.CalledProcessError as e:
print(f"Process error: {e}")
print(f"stderr: {e.stderr}")
raise
except Exception as e:
print(f"Unexpected error: {e}")
raise

def get_participants(self, block: int) -> List[str]:
try:
response = requests.get(
f"{ETHENA_ADDRESS_API_URL}?block={block}",
timeout=10
)
response.raise_for_status()

data = response.json()['data']
if not isinstance(data, list):
logging.warning(f"Unexpected response format from API: {data}")
return []

return [addr for addr in data if isinstance(addr, str)]

except requests.RequestException as e:
logging.error(f"Request failed for block {block}: {str(e)}")
return []
except Exception as e:
logging.error(f"Error processing participants for block {block}: {str(e)}")
return []

if __name__ == "__main__":
example_integration = EchelonAptosIntegration(
integration_id=IntID.ECHELON_SUSDE_COLLATERAL,
start_block=ECHELON_SUSDE_COLLATERAL_START_BLOCK,
market_address=SUSDE_MARKET_ADDRESS,
token_address=SUSDE_TOKEN_ADDRESS,
decimals=6,
chain=Chain.APTOS,
reward_multiplier=5,
)

example_integration_output = example_integration.get_l2_block_balances(
cached_data={}, blocks=list(range(ECHELON_SUSDE_COLLATERAL_START_BLOCK, ECHELON_SUSDE_COLLATERAL_START_BLOCK + 40548182, 2000000))
)

print("=" * 120)
print("Run without cached data", example_integration_output)
print("=" * 120, "\n" * 5)
6 changes: 6 additions & 0 deletions integrations/integration_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ class IntegrationID(Enum):
)
PENDLE_ZIRCUIT_USDE_YT = ("pendle_zircuit_usde_yt_held", "Pendle Zircuit USDe YT")

# Echelon
ECHELON_SUSDE_COLLATERAL = (
"echelon_susde_collateral",
"Echelon sUSDe Collateral",
Token.SUSDE,
)
# Stake DAO
STAKEDAO_SUSDE_JULY_LPT = (
"stakedao_susde_july_effective_lpt_held",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@aptos-labs/ts-sdk": "^1.33.1",
"@kamino-finance/klend-sdk": "^3.2.7",
"@solana/web3.js": "^1.95.2",
"dotenv": "^16.4.5"
Expand Down
42 changes: 42 additions & 0 deletions ts/echelon_balances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as dotenv from "dotenv";
import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";

dotenv.config();

const config = new AptosConfig({ network: Network.MAINNET });
// Aptos is the main entrypoint for all functions
const client = new Aptos(config);

const args = process.argv.slice(2);
const LENDING_CONTRACT_ADDRESS = args[0];
const market_address = args[1];
const decimals = Number(args[2]);
const block = Number(args[3]);
const user_addresses: string[] = JSON.parse(args[4]);

async function getStrategy() {
// iterate over all users and get their susde balance
const user_balances: Record<string, number> = {};
for (const address of user_addresses) {
const susde_balance = await client.view({
payload: {
function: `${LENDING_CONTRACT_ADDRESS}::lending::account_coins`,
functionArguments: [address, market_address],
},
options: { ledgerVersion: block },
});

user_balances[address] = scaleDownByDecimals(
Number(susde_balance),
decimals
);
}

console.log(JSON.stringify(user_balances));
}

function scaleDownByDecimals(value: number, decimals: number) {
return value / 10 ** decimals;
}

const strategy = getStrategy().catch(console.error);

0 comments on commit a6fc40c

Please sign in to comment.