Skip to content
Merged
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
38 changes: 38 additions & 0 deletions blockapi/test/v2/api/debank/test_debank_portfolio_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,44 @@ def test_parse_pool_names(portfolio_parser, tokenset_portfolio_response):
assert parsed[1].pool_info.tokens == ['ETH', 'USDC']


def test_portfolio_detail_types(portfolio_parser, portfolio_response):
pool = portfolio_parser.parse([portfolio_response])[0]
assert pool.detail_types == ['lending']


def test_portfolio_asset_usd_value(portfolio_parser, portfolio_response):
pool = portfolio_parser.parse([portfolio_response])[0]
assert pool.asset_usd_value == Decimal('547045.4515305705')


def test_portfolio_debt_usd_value(portfolio_parser, portfolio_response):
pool = portfolio_parser.parse([portfolio_response])[0]
assert pool.debt_usd_value == Decimal('0')


def test_portfolio_net_usd_value(portfolio_parser, portfolio_response):
pool = portfolio_parser.parse([portfolio_response])[0]
assert pool.net_usd_value == Decimal('547045.4515305705')


def test_portfolio_update_at(portfolio_parser, portfolio_response):
pool = portfolio_parser.parse([portfolio_response])[0]
assert pool.update_at is not None
assert isinstance(pool.update_at, datetime)


def test_portfolio_debt_ratio_none_when_absent(portfolio_parser, portfolio_response):
pool = portfolio_parser.parse([portfolio_response])[0]
assert pool.debt_ratio is None


def test_portfolio_debt_ratio_when_present(portfolio_parser, portfolio_response):
"""debt_ratio appears for leveraged_farming positions."""
portfolio_response['portfolio_item_list'][0]['detail']['debt_ratio'] = 0.65
pool = portfolio_parser.parse([portfolio_response])[0]
assert pool.debt_ratio == Decimal('0.65')


def test_require_pool_or_pool_id():
with pytest.raises(ValueError, match="either pool or pool_id must have a value"):
detail = DebankModelPoolItemDetail()
Expand Down
11 changes: 11 additions & 0 deletions blockapi/v2/api/debank.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
DebankApp,
DebankModelApp,
DebankModelAppPortfolioItem,
DebankModelAppStats,
DebankModelPredictionDetail,
DebankPrediction,
FetchResult,
Expand Down Expand Up @@ -70,6 +71,7 @@ class DebankModelPoolItemDetail(BaseModel):
description: Optional[str] = None
health_rate: Optional[float] = None
unlock_at: Optional[float] = None
debt_ratio: Optional[float] = None
token_list: Optional[list[dict]] = None
supply_token_list: Optional[list[dict]] = None
borrow_token_list: Optional[list[dict]] = None
Expand All @@ -90,6 +92,9 @@ class DebankModelPortfolioItem(BaseModel):
pool_id: Optional[str] = None
pool: Optional[DebankModelPoolItem] = None
position_index: Optional[str] = None
stats: DebankModelAppStats
detail_types: list[str]
update_at: float

@validator('pool')
def require_pool_or_pool_id(cls, v, values, **kwargs):
Expand Down Expand Up @@ -481,6 +486,12 @@ def _parse_portfolio_item(
locked_until=locked_until,
health_rate=health_rate,
items=[],
detail_types=item.detail_types,
asset_usd_value=item.stats.asset_usd_value,
debt_usd_value=item.stats.debt_usd_value,
net_usd_value=item.stats.net_usd_value,
debt_ratio=detail.debt_ratio,
update_at=item.update_at,
)

items = list(self._parse_balances(detail, item, pool.pool_info))
Expand Down
33 changes: 32 additions & 1 deletion blockapi/v2/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,13 +1163,32 @@ def from_api(
)


# uint256.max / 1e18 — Aave's sentinel for "no debt / infinite health factor"
_HEALTH_RATE_SENTINEL_THRESHOLD = Decimal('1e18')


def _normalize_health_rate(value) -> Optional[Decimal]:
if value is None:
return None
d = to_decimal(value)
if d >= _HEALTH_RATE_SENTINEL_THRESHOLD:
return None
return d


@attr.s(auto_attribs=True, slots=True, frozen=True)
class Pool:
pool_info: PoolInfo
protocol: Protocol
items: List[BalanceItem]
locked_until: Optional[datetime] = attr.ib(default=None)
health_rate: Optional[Decimal] = attr.ib(default=None)
detail_types: List[str] = attr.ib(factory=list)
asset_usd_value: Decimal = attr.ib(default=Decimal('0'))
debt_usd_value: Decimal = attr.ib(default=Decimal('0'))
net_usd_value: Decimal = attr.ib(default=Decimal('0'))
debt_ratio: Optional[Decimal] = attr.ib(default=None)
update_at: Optional[datetime] = attr.ib(default=None)

@classmethod
def from_api(
Expand All @@ -1180,13 +1199,25 @@ def from_api(
locked_until: Optional[Union[int, str, float]] = None,
health_rate: Optional[Union[float, str]] = None,
items: List[BalanceItem],
detail_types: Optional[List[str]] = None,
asset_usd_value: Union[float, str] = 0,
debt_usd_value: Union[float, str] = 0,
net_usd_value: Union[float, str] = 0,
debt_ratio: Optional[Union[float, str]] = None,
update_at: Optional[Union[int, str, float]] = None,
) -> 'Pool':
return cls(
pool_info=pool_info,
protocol=protocol,
items=items,
locked_until=(parse_dt(locked_until) if locked_until is not None else None),
health_rate=to_decimal(health_rate) if health_rate is not None else None,
health_rate=_normalize_health_rate(health_rate),
detail_types=detail_types or [],
asset_usd_value=to_decimal(asset_usd_value),
debt_usd_value=to_decimal(debt_usd_value),
net_usd_value=to_decimal(net_usd_value),
debt_ratio=to_decimal(debt_ratio) if debt_ratio is not None else None,
update_at=(parse_dt(update_at) if update_at is not None else None),
)

def append_items(self, items: List[BalanceItem]) -> None:
Expand Down
Loading