Skip to content

Commit

Permalink
feature: STON.fi integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Gregory Ivantov committed Jan 23, 2025
1 parent 8d3f944 commit b4a01c7
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 2 deletions.
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
12 changes: 12 additions & 0 deletions campaign/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
KAMINO_SUSDE_COLLATERAL_START_BLOCK_EXAMPLE,
RATEX_EXAMPLE_USDE_START_BLOCK,
)
from constants.stonfi import STONFI_USDE_START_BLOCK
from integrations.beefy_cached_balance_example_integration import (
BeefyCachedBalanceIntegration,
)
Expand All @@ -15,6 +16,7 @@
from integrations.ratex_l2_delegation_example_integration import (
RatexL2DelegationExampleIntegration,
)
from integrations.stonfi_integration import StonFiIntegration
from utils import pendle
from web3 import Web3

Expand All @@ -29,6 +31,16 @@

# 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=1,
),
# 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,4 +13,5 @@ class Chain(Enum):
LYRA = "Lyra"
SWELL = "Swell"
SOLANA = "Solana"
TON = "TON"
BASE = "Base"
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 = 21671160 # 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 @@ -49,8 +49,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 @@ -415,6 +415,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
124 changes: 124 additions & 0 deletions integrations/stonfi_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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"

TOKEN_SYMBOL_ADDRESS_MAP = {
Token.USDE: "EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO", # FIXME: replace with real USDe address
}

TOKEN_DECIMALS_MAP = {
Token.USDE: 9, # FIXME: replace with real USDe decimals
}


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}..."
)

token_address = TOKEN_SYMBOL_ADDRESS_MAP[self.get_token_symbol()]
token_decimals = TOKEN_DECIMALS_MAP[self.get_token_symbol()]
token_decimals_base = 10 ** token_decimals

block_data: Dict[str, float] = {}
try:
# example: https://api.ston.fi/v1/stats/asset/EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO/holders?with_staked=true&timestamp=2025-01-16T01:00:00
res = requests_retry_session().get(
STONFI_ENDPOINT + "/v1/stats/asset/" + token_address + "/holders",
params={
"with_staked": "true",
"timestamp": target_date
},
timeout=60,
)
payload = res.json()

if "holders" in payload and len(payload["holders"]) > 0:
for holder in payload["holders"]:
wallet_address = holder["wallet_address"]
block_data[wallet_address] = int(holder["total_amount"]) / token_decimals_base
except Exception as e:
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=[21671160, 21683570],
)

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 @@ -66,6 +66,9 @@
Chain.SOLANA: {
"w3": w3,
},
Chain.TON: {
"w3": w3,
},
Chain.BASE: {
"w3": w3_base,
},
Expand Down Expand Up @@ -198,13 +201,13 @@ 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

0 comments on commit b4a01c7

Please sign in to comment.