Skip to content

Commit ff68dd6

Browse files
author
vpatsenko
committed
bend susde adapter
1 parent ffe5e86 commit ff68dd6

File tree

7 files changed

+161
-1
lines changed

7 files changed

+161
-1
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ SOLANA_NODE_URL='https://api.mainnet-beta.solana.com'
1111
BASE_NODE_URL="https://mainnet.base.org"
1212
HYPEREVM_NODE_URL="https://rpc.hyperlend.finance/archive"
1313
PLASMA_NODE_URL="https://rpc.plasma.to"
14-
DERIVE_SUBGRAPH_API_KEY=''
14+
BERACHAIN_NODE_URL="https://rpc.berachain.com"
15+
DERIVE_SUBGRAPH_API_KEY=''

constants/bend_susde_susds.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import json
2+
from web3 import Web3
3+
from utils.web3_utils import w3_berachain
4+
5+
PAGINATION_SIZE = 1000
6+
BEND_ADDRESS = Web3.to_checksum_address("0x24147243f9c08d835C218Cda1e135f8dFD0517D0")
7+
8+
with open("abi/morpho.json") as f:
9+
BEND_ABI = json.load(f)
10+
11+
BEND_CONTRACT = w3_berachain.eth.contract(
12+
address=BEND_ADDRESS,
13+
abi=BEND_ABI,
14+
)
15+
16+
BEND_MARKET_IDS = [
17+
"0x1ba7904c73d337c39cb88b00180dffb215fc334a6ff47bbe829cd9ee2af00c97",
18+
]
19+
20+
# NOTE: per https://berascan.com/tx/0x14a3eddaa857a67b1c8cb143280b506617e191374810d41b3ffeef58c5d0a7bf, the first deployment tx
21+
BEND_SUSDE_SUSDS_START_BLOCK = 11572788

constants/chains.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ class Chain(Enum):
1919
TON = "Ton"
2020
HYPEREVM = "HyperEVM"
2121
PLASMA = "Plasma"
22+
BERACHAIN = "Berachain"

constants/summary_columns.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class SummaryColumn(Enum):
7373

7474
MORPHO_SUSDE_SUSDS_PTS = ("morpho_susde_susds_pts", SummaryColumnType.ETHENA_PTS)
7575

76+
BEND_SUSDE_SUSDS_PTS = ("bend_susde_susds_pts", SummaryColumnType.ETHENA_PTS)
77+
7678
# Silo Finance LP-sUSDe (25 Sep 2025)
7779
SILO_FINANCE_LP_SUSDE_SEP_25_EXPIRY_PTS = (
7880
"silo_finance_lp_usde_sep_25_expiry_pts",

integrations/bend_susde_susds.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from copy import deepcopy
2+
import logging
3+
4+
from typing import Dict, List
5+
from eth_typing import ChecksumAddress
6+
from constants.chains import Chain
7+
from constants.bend_susde_susds import (
8+
BEND_CONTRACT,
9+
BEND_MARKET_IDS,
10+
BEND_SUSDE_SUSDS_START_BLOCK,
11+
PAGINATION_SIZE,
12+
)
13+
from constants.summary_columns import SummaryColumn
14+
from integrations.cached_balances_integration import CachedBalancesIntegration
15+
from integrations.integration_ids import IntegrationID as IntID
16+
from utils.web3_utils import fetch_events_logs_with_retry
17+
18+
19+
class BendSusdeSusds(CachedBalancesIntegration):
20+
"""
21+
Bend sUSDe-sUSDS LP token market integration.
22+
"""
23+
24+
def __init__(self):
25+
super().__init__(
26+
integration_id=IntID.BEND_SUSDE_SUSDS,
27+
start_block=BEND_SUSDE_SUSDS_START_BLOCK,
28+
chain=Chain.BERACHAIN,
29+
summary_cols=[SummaryColumn.BEND_SUSDE_SUSDS_PTS],
30+
reward_multiplier=30,
31+
balance_multiplier=1,
32+
)
33+
34+
def get_block_balances(
35+
self, cached_data: Dict[int, Dict[ChecksumAddress, float]], blocks: List[int]
36+
) -> Dict[int, Dict[ChecksumAddress, float]]:
37+
logging.info("Getting block data for Morpho sUSDe-sUSDS LP collateral")
38+
new_block_data: Dict[int, Dict[ChecksumAddress, float]] = {}
39+
if not blocks:
40+
logging.error(
41+
"No blocks provided to Morpho sUSDe-sUSDS LP collateral get_block_balances"
42+
)
43+
return new_block_data
44+
45+
sorted_blocks = sorted(blocks)
46+
cache_copy: Dict[int, Dict[ChecksumAddress, float]] = deepcopy(cached_data)
47+
48+
for block in sorted_blocks:
49+
# find the closest prev block in the data
50+
# list keys parsed as ints and in descending order
51+
sorted_existing_blocks = sorted(
52+
cache_copy,
53+
reverse=True,
54+
)
55+
# loop through the sorted blocks and find the closest previous block
56+
prev_block = self.start_block
57+
start = prev_block
58+
bals = {}
59+
for existing_block in sorted_existing_blocks:
60+
if existing_block < block:
61+
prev_block = existing_block
62+
start = existing_block + 1
63+
bals = deepcopy(cache_copy[prev_block])
64+
break
65+
# parse supply/withdraw events since and update bals
66+
# NOTE: as per the morpho-blue EventsLib.sol comments,
67+
# `feeRecipient` receives supplied shares without any event,
68+
# so their sats are not accrued.
69+
while start <= block:
70+
to_block = min(start + PAGINATION_SIZE, block)
71+
72+
supplies = fetch_events_logs_with_retry(
73+
"Bend-Borrow Supply with sUSDe",
74+
BEND_CONTRACT.events.SupplyCollateral(),
75+
start,
76+
to_block,
77+
filter={"id": BEND_MARKET_IDS},
78+
)
79+
for supply in supplies:
80+
recipient = supply["args"]["onBehalf"]
81+
if recipient not in bals:
82+
bals[recipient] = 0
83+
bals[recipient] += round(supply["args"]["assets"] / 10**18, 4)
84+
85+
withdraws = fetch_events_logs_with_retry(
86+
"Bend-Borrow Supply with sUSDe",
87+
BEND_CONTRACT.events.WithdrawCollateral(),
88+
start,
89+
to_block,
90+
filter={"id": BEND_MARKET_IDS},
91+
)
92+
for withdraw in withdraws:
93+
recipient = withdraw["args"]["onBehalf"]
94+
if recipient not in bals:
95+
bals[recipient] = 0
96+
bals[recipient] -= round(withdraw["args"]["assets"] / 10**18, 4)
97+
if bals[recipient] < 0:
98+
bals[recipient] = 0
99+
start = to_block + 1
100+
new_block_data[block] = bals
101+
cache_copy[block] = bals
102+
return new_block_data
103+
104+
105+
if __name__ == "__main__":
106+
integration = BendSusdeSusds()
107+
108+
# Without cached data
109+
without_cached_data_output = integration.get_block_balances(
110+
cached_data={}, blocks=[11572788, 12155834]
111+
)
112+
113+
print("=" * 120)
114+
print("Run without cached data", without_cached_data_output)
115+
print("=" * 120, "\n" * 5)
116+
117+
# With cached data, using the previous output so there is no need
118+
# to fetch the previous blocks again
119+
with_cached_data_output = integration.get_block_balances(
120+
cached_data=without_cached_data_output, blocks=[12155834]
121+
)
122+
print("Run with cached data", with_cached_data_output)
123+
print("=" * 120)

integrations/integration_ids.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,13 @@ class IntegrationID(Enum):
529529
Token.SUSDE,
530530
)
531531

532+
# Bend sUSDS-sUSDe
533+
BEND_SUSDE_SUSDS = (
534+
"bend_susde_susds",
535+
"Bend sUSDe-sUSDS LP token",
536+
Token.SUSDE,
537+
)
538+
532539
# Affluent
533540
AFFLUENT_USDE = ("affluent_usde", "Affluent TON USDe", Token.USDE)
534541
AFFLUENT_SUSDE = ("affluent_susde", "Affluent TON sUSDe", Token.SUSDE)

utils/web3_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
w3_hyperevm = Web3(Web3.HTTPProvider(HYPEREVM_NODE_URL))
4343
PLASMA_NODE_URL = os.getenv("PLASMA_NODE_URL")
4444
w3_plasma = Web3(Web3.HTTPProvider(PLASMA_NODE_URL))
45+
BERACHAIN_NODE_URL = os.getenv("BERACHAIN_NODE_URL")
46+
w3_berachain = Web3(Web3.HTTPProvider(BERACHAIN_NODE_URL))
4547

4648
W3_BY_CHAIN = {
4749
Chain.ETHEREUM: {
@@ -89,6 +91,9 @@
8991
Chain.PLASMA: {
9092
"w3": w3_plasma,
9193
},
94+
Chain.BERACHAIN: {
95+
"w3": w3_berachain,
96+
},
9297
}
9398

9499
MULTICALL_ABI = [

0 commit comments

Comments
 (0)