Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feature: STON.fi integration #63

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ db.sqlite3-journal
# VSCode
.vscode/

# Idea/PyCharm
.idea

# Flask stuff:
instance/
.webassets-cache
Expand Down
36 changes: 24 additions & 12 deletions campaign/campaign.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
from typing import List
from constants.example_integrations import (
ACTIVE_ENA_START_BLOCK_EXAMPLE,
BEEFY_ARBITRUM_START_BLOCK_EXAMPLE,
KAMINO_SUSDE_COLLATERAL_START_BLOCK_EXAMPLE,
RATEX_EXAMPLE_USDE_START_BLOCK,
)
from web3 import Web3
from integrations.beefy_cached_balance_example_integration import (
BeefyCachedBalanceIntegration,
)
Expand All @@ -15,20 +10,37 @@
from integrations.ratex_l2_delegation_example_integration import (
RatexL2DelegationExampleIntegration,
)
from utils import pendle
from web3 import Web3

from constants.chains import Chain
from constants.pendle import PENDLE_USDE_JULY_DEPLOYMENT_BLOCK
from constants.summary_columns import SummaryColumn
from integrations.stonfi_integration import StonFiIntegration
from integrations.integration import Integration
from integrations.integration_ids import IntegrationID
from integrations.pendle_lpt_integration import PendleLPTIntegration
from integrations.pendle_yt_integration import PendleYTIntegration
from integrations.template import ProtocolNameIntegration
from utils import pendle

from constants.example_integrations import (
ACTIVE_ENA_START_BLOCK_EXAMPLE,
BEEFY_ARBITRUM_START_BLOCK_EXAMPLE,
KAMINO_SUSDE_COLLATERAL_START_BLOCK_EXAMPLE,
RATEX_EXAMPLE_USDE_START_BLOCK,
)
from constants.stonfi import STONFI_USDE_START_BLOCK
from constants.chains import Chain
from constants.pendle import PENDLE_USDE_JULY_DEPLOYMENT_BLOCK
from constants.summary_columns import SummaryColumn

# TODO: Add your integration here
INTEGRATIONS: List[Integration] = [
# STON.fi L2 Delegation TON chain, based on API calls
StonFiIntegration(
integration_id=IntegrationID.STONFI_USDE,
start_block=STONFI_USDE_START_BLOCK,
summary_cols=[
SummaryColumn.STONFI_USDE_PTS,
],
chain=Chain.TON,
reward_multiplier=30,
),
# Template integration
ProtocolNameIntegration(
integration_id=IntegrationID.EXAMPLE,
Expand Down
1 change: 1 addition & 0 deletions constants/chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ class Chain(Enum):
LYRA = "Lyra"
SWELL = "Swell"
SOLANA = "Solana"
TON = "TON"
BASE = "Base"
SEPOLIA = "Sepolia"
1 change: 1 addition & 0 deletions constants/stonfi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
STONFI_USDE_START_BLOCK = 21819590 # FIXME: replace with real start block number
3 changes: 3 additions & 0 deletions constants/summary_columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ class SummaryColumn(Enum):

RATEX_EXAMPLE_PTS = ("ratex_example_pts", SummaryColumnType.ETHENA_PTS)

STONFI_USDE_PTS = ("stonfi_usde_pts", SummaryColumnType.ETHENA_PTS)

VENUS_SUSDE_PTS = ("venus_susde_pts", SummaryColumnType.ETHENA_PTS)


def __init__(self, column_name: str, col_type: SummaryColumnType):
self.column_name = column_name
self.col_type = col_type
Expand Down
3 changes: 3 additions & 0 deletions integrations/integration_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ class IntegrationID(Enum):

RATEX_USDE_EXAMPLE = ("ratex_usde_example", "Ratex USDe Example", Token.USDE)

# STON.fi
STONFI_USDE = ("stonfi_usde", "STON.fi USDe", Token.USDE)

# Upshift sUSDe
UPSHIFT_UPSUSDE = ("upshift_upsusde", "Upshift upsUSDe", Token.SUSDE)

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

from typing import Dict, List, Optional
from constants.integration_token import Token
from constants.stonfi import STONFI_USDE_START_BLOCK
from integrations.l2_delegation_integration import L2DelegationIntegration
from utils.web3_utils import get_block_date
from constants.chains import Chain
from constants.summary_columns import SummaryColumn
from integrations.integration_ids import IntegrationID
from utils.request_utils import requests_retry_session
from utils.slack import slack_message

STONFI_ENDPOINT = "https://api.ston.fi"

STONFI_LP_TOKEN_DECIMALS = 9

TOKEN_SYMBOL_POOLS_MAP = {
Token.USDE: [
"EQBbsMjyLRj-xJE4eqMbtgABvPq34TF_hwiAGEAUGUb5sNGO", # FIXME: replace with real USDe <-> USDT pool address
],
}

class StonFiIntegration(L2DelegationIntegration):
def __init__(
self,
integration_id: IntegrationID,
start_block: int,
summary_cols: Optional[List[SummaryColumn]] = None,
chain: Chain = Chain.TON,
reward_multiplier: int = 1,
end_block: Optional[int] = None,
):
super().__init__(
integration_id=integration_id,
start_block=start_block,
chain=chain,
summary_cols=summary_cols if summary_cols else [SummaryColumn.STONFI_USDE_PTS],
reward_multiplier=reward_multiplier,
end_block=end_block,
)

def get_token_symbol(self):
return self.integration_id.get_token()

def get_l2_block_balances(
self,
cached_data: Dict[int, Dict[str, float]],
blocks: List[int]
) -> Dict[int, Dict[str, float]]:
logging.info(
f"Getting block data for STON.fi L2 delegation and blocks {blocks}..."
)

data_per_block: Dict[int, Dict[str, float]] = {}

for target_block in blocks:
if self.start_block > target_block or (
self.end_block and target_block > self.end_block
):
data_per_block[target_block] = {}
continue
data_per_block[target_block] = self.get_participants_data(target_block)

return data_per_block

def get_participants_data(self, block: int) -> Dict[str, float]:
# block timestamp format "2025-01-16T01:00:00"
target_date = get_block_date(block, self.chain, adjustment=3600, fmt="%Y-%m-%dT%H:%M:%S")

logging.info(
f"Fetching participants data for STON.fi L2 delegation at block {block} timestamp {target_date}..."
)

pools_list = TOKEN_SYMBOL_POOLS_MAP[self.get_token_symbol()]
lp_token_decimals_base = 10 ** STONFI_LP_TOKEN_DECIMALS

block_data: Dict[str, float] = {}
try:
for pool_address in pools_list:
# example: https://api.ston.fi/v1/snapshots/liquidity_providers?timestamp=2025-02-11T01:00:00&pool_address=EQBbsMjyLRj-xJE4eqMbtgABvPq34TF_hwiAGEAUGUb5sNGO
res = requests_retry_session().get(
STONFI_ENDPOINT + "/v1/snapshots/liquidity_providers",
params={
"pool_address": pool_address,
"timestamp": target_date
},
timeout=60,
)
payload = res.json()

snapshot = payload["snapshot"]

if len(snapshot) != 1:
raise Exception("Invalid liquidity providers snapshot data")

pool_snapshot = snapshot[0]
lp_price_usd = float(pool_snapshot["lp_price_usd"])

for position in pool_snapshot["positions"]:
wallet_address = position["wallet_address"]
lp_token_amount = int(position["lp_amount"]) + int(position["staked_lp_amount"])
position_value_usd = lp_token_amount * lp_price_usd / lp_token_decimals_base
block_data[wallet_address] = block_data.get(wallet_address, 0) + position_value_usd

except Exception as e:
# pylint: disable=line-too-long
err_msg = f"Error getting participants data for STON.fi L2 delegation at block {block}: {e}"
logging.error(err_msg)
slack_message(err_msg)

return block_data


if __name__ == "__main__":
stonfi_integration = StonFiIntegration(
integration_id=IntegrationID.STONFI_USDE,
start_block=STONFI_USDE_START_BLOCK,
summary_cols=[
SummaryColumn.STONFI_USDE_PTS,
],
chain=Chain.TON,
reward_multiplier=1,
)

stonfi_integration_output = stonfi_integration.get_l2_block_balances(
cached_data={},
blocks=[21819590, 21819700],
)

print("=" * 120)
print("Run without cached data", json.dumps(stonfi_integration_output, indent=2))
print("=" * 120, "\n" * 5)
7 changes: 5 additions & 2 deletions utils/web3_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
Chain.SOLANA: {
"w3": w3,
},
Chain.TON: {
"w3": w3,
},
Chain.BASE: {
"w3": w3_base,
},
Expand Down Expand Up @@ -213,15 +216,15 @@ def multicall_by_address(
return decoded_results


def get_block_date(block: int, chain: Chain, adjustment: int = 0) -> str:
def get_block_date(block: int, chain: Chain, adjustment: int = 0, fmt: str = "%Y-%m-%d %H") -> str:
wb3 = W3_BY_CHAIN[chain]["w3"]
block_info = wb3.eth.get_block(block)
timestamp = (
block_info["timestamp"]
if adjustment == 0
else block_info["timestamp"] - adjustment
)
timestamp_date = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H")
timestamp_date = datetime.fromtimestamp(timestamp).strftime(fmt)
return timestamp_date


Expand Down
Loading