Skip to content

Commit

Permalink
implementation for THALA SUSDE LP tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
LawsonGraham committed Jan 13, 2025
1 parent de65fbf commit cb10453
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 0 deletions.
2 changes: 2 additions & 0 deletions constants/example_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@

ECHELON_SUSDE_COLLATERAL_START_BLOCK = 273913668

THALA_SUSDE_START_BLOCK = 276439225

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 @@ -21,6 +21,8 @@ class SummaryColumn(Enum):

ECHELON_SHARDS = ("echelon_shards", SummaryColumnType.ETHENA_PTS)

THALA_SHARDS = ("thala_shards", SummaryColumnType.ETHENA_PTS)

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

Expand Down
1 change: 1 addition & 0 deletions constants/thala.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SUSDE_LPT_METADATA = "0x99d34f16193e251af236d5a5c3114fa54e22ca512280317eda2f8faf1514c395" # TODO: APT/USDT Address for now
8 changes: 8 additions & 0 deletions integrations/integration_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ class IntegrationID(Enum):
"Echelon sUSDe Collateral",
Token.SUSDE,
)

# Thala
THALA_SUSDE_LP = (
"thala_susde_lp",
"Thala sUSDe LP",
Token.SUSDE,
)

# Stake DAO
STAKEDAO_SUSDE_JULY_LPT = (
"stakedao_susde_july_effective_lpt_held",
Expand Down
112 changes: 112 additions & 0 deletions integrations/thala_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import logging
import subprocess
import json

from typing import Dict, List
from dotenv import load_dotenv
from constants.summary_columns import SummaryColumn
from constants.example_integrations import (
THALA_SUSDE_START_BLOCK,
)
from constants.thala import THALASWAP_CONTRACT_ADDRESS, SUSDE_LPT_METADATA, SUSDE_TOKEN_ADDRESS
from constants.chains import Chain
from integrations.integration_ids import IntegrationID as IntID
from integrations.l2_delegation_integration import L2DelegationIntegration

load_dotenv()

class ThalaAptosIntegration(L2DelegationIntegration):
def __init__(
self,
integration_id: IntID,
start_block: int,
token_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.THALA_SHARDS],
reward_multiplier=reward_multiplier,
)
self.token_address = token_address
self.decimals = str(decimals)
self.thala_ts_location = "ts/thala_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 Thala sUSDe LP...")
# 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:
# Check block_data first, then cached_data for previous block balances
prev_block_user_balances = block_data.get(block - 1, cached_data.get(block - 1, {}))
result = self.get_participants_data(block, prev_block_user_balances)

# Store the balances
block_data[block] = result['balances']

return block_data

def get_participants_data(self, block, prev_block_user_balances=None):
print("Getting participants data for block", block)
try:
response = subprocess.run(
[
"ts-node",
self.thala_ts_location,
SUSDE_LPT_METADATA,
str(self.decimals),
str(block),
json.dumps(prev_block_user_balances or {}),
],
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
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


if __name__ == "__main__":
example_integration = ThalaAptosIntegration(
integration_id=IntID.THALA_SUSDE_LP,
start_block=THALA_SUSDE_START_BLOCK,
token_address=SUSDE_LPT_METADATA,
decimals=8,
chain=Chain.APTOS,
reward_multiplier=5,
)

example_integration_output = example_integration.get_l2_block_balances(
cached_data={}, blocks=list(range(THALA_SUSDE_START_BLOCK, THALA_SUSDE_START_BLOCK + 300))
)

print("=" * 120)
print("Run without cached data", example_integration_output)
print("=" * 120, "\n" * 5)
127 changes: 127 additions & 0 deletions ts/thala_balances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as dotenv from "dotenv";
import {
Aptos,
AptosConfig,
Network,
WriteSetChangeWriteResource,
isUserTransactionResponse,
MoveResource,
} 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 SUSDE_LPT_METADATA = args[0];
const decimals = Number(args[1]);
const block = Number(args[2]);
const user_balances: Record<string, number> = new Proxy(
args[3] ? JSON.parse(args[3]) : {},
{
get: (target, prop) => target[prop] || 0,
}
);

type FungibleStoreResource = WriteSetChangeWriteResource & {
data: MoveResource<FungibleStoreData>;
};

type ObjectCoreResource = WriteSetChangeWriteResource & {
data: MoveResource<ObjectCoreData>;
};

type FungibleStoreData = {
balance: string;
metadata: {
inner: string;
};
};

type ObjectCoreData = {
owner: string;
};

async function getStrategy() {
const block_data = await client.getBlockByHeight({
blockHeight: block,
options: {
withTransactions: true,
},
});

if (!block_data) {
throw new Error(`Block ${block} not found`);
}

if (!block_data.transactions) {
throw new Error(`No transactions found in Block ${block}`);
}

const user_transactions = block_data.transactions.filter(
isUserTransactionResponse
);

for (const transaction of user_transactions) {
// First, collect all ObjectCore and LPT changes
const objectCoreChanges = new Map<string, string>();
const lptChanges: Array<{ store: string; balance: string }> = [];

// Collect all relevant changes
for (const change of transaction.changes as WriteSetChangeWriteResource[]) {
if (isObjectCoreChange(change)) {
objectCoreChanges.set(change.address, change.data.data.owner);
} else if (isLPTFungibleStoreChange(change)) {
lptChanges.push({
store: change.address,
balance: change.data.data.balance,
});
}
}

// Process LPT changes after we have all the data
for (const { store, balance } of lptChanges) {
const userAddress = objectCoreChanges.get(store);
if (userAddress) {
user_balances[userAddress] = scaleDownByDecimals(
Number(balance),
decimals
);
}
}
}

console.log(
JSON.stringify({
balances: user_balances,
})
);
}

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

function isLPTFungibleStoreChange(
change: WriteSetChangeWriteResource
): change is FungibleStoreResource {
return (
change.type === "write_resource" &&
change.data.type === "0x1::fungible_asset::FungibleStore" &&
(change as FungibleStoreResource).data.data.metadata.inner ===
SUSDE_LPT_METADATA
);
}

function isObjectCoreChange(
change: WriteSetChangeWriteResource
): change is ObjectCoreResource {
return (
change.type === "write_resource" &&
change.data.type === "0x1::object::ObjectCore"
);
}

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

0 comments on commit cb10453

Please sign in to comment.