Skip to content
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
7 changes: 6 additions & 1 deletion blockapi/test/v2/api/debank/test_debank_app_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
DebankAppDeposit,
DebankPrediction,
)
from blockapi.v2.models import Blockchain


@pytest.fixture
Expand Down Expand Up @@ -129,6 +130,7 @@ def test_parse_polymarket_app(debank_app_parser, polymarket_response):

def test_parse_polymarket_deposits(debank_app_parser, polymarket_response):
"""Deposits should be parsed as DebankAppDeposit objects."""

parsed_apps = debank_app_parser.parse(polymarket_response)
app = parsed_apps[0]

Expand All @@ -142,10 +144,11 @@ def test_parse_polymarket_deposits(debank_app_parser, polymarket_response):
assert deposit.debt_usd_value == Decimal("0")
assert deposit.net_usd_value == Decimal("290915.13432776055")
assert deposit.position_index == "cash_0x5c23dead9ecf271448411096f349133e0bb9c465"
assert deposit.chain == Blockchain.POLYGON

# Should have 1 token (USDC)
assert len(deposit.tokens) == 1
assert deposit.tokens[0]["symbol"] == "USDC"
assert deposit.tokens[0].symbol == "USDC"
assert deposit.token_symbols == ["USDC"]


Expand All @@ -168,13 +171,15 @@ def test_parse_polymarket_predictions(debank_app_parser, polymarket_response):
assert pred1.usd_value == Decimal("27068.1993")
assert pred1.claimable is True
assert pred1.is_market_closed is False
assert pred1.chain == Blockchain.POLYGON

pred2 = app.predictions[1]
assert pred2.prediction_name == "Gensyn FDV above $600M one day after launch?"
assert pred2.side == "Yes"
assert pred2.amount == Decimal("19999.9704")
assert pred2.price == Decimal("0.255")
assert pred2.claimable is False
assert pred2.chain == Blockchain.POLYGON


def test_parse_multiple_apps(debank_app_parser):
Expand Down
6 changes: 6 additions & 0 deletions blockapi/test/v2/api/debank/test_debank_fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def test_fetch_chains(real_debank_api):
def test_fetch_usage():
assert False
Comment on lines 35 to 36
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_fetch_usage contains assert False, which will always fail when integration tests are enabled. Either implement the test (assert on the response / save fixture data) or mark it as skipped/xfail so it doesn’t break CI runs that include integration tests.

Suggested change
def test_fetch_usage():
assert False
@pytest.mark.skip(reason="Pending implementation of fetch_usage integration test")
def test_fetch_usage():
pass

Copilot uses AI. Check for mistakes.

@pytest.mark.integration
def test_fetch_debank_apps(real_debank_api):
response = real_debank_api.fetch_debank_apps('0x807A2E2e469df84b299Da5f90f15DdA4380dAcA1')
pass
Comment on lines +38 to +41
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_fetch_debank_apps currently discards the fetched response and just passes, so it won’t detect regressions and may still hit the external API. Add at least a minimal assertion on the returned payload (or reuse _save(...) like the other integration tests), or remove the test until it has meaningful checks.

Copilot uses AI. Check for mistakes.



def _save(name: str, data: FetchResult):
if not OPTION_SAVE_DATA:
Expand Down
34 changes: 13 additions & 21 deletions blockapi/v2/api/debank.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from blockapi.utils.num import decimals_to_raw, to_decimal
from blockapi.v2.api.debank_maps import (
COINGECKO_IDS_BY_CONTRACTS,
DEBANK_APP_CHAIN_MAP,
DEBANK_ASSET_TYPES,
NATIVE_COIN_MAP,
REWARD_ASSET_TYPE_MAP,
Expand Down Expand Up @@ -40,7 +41,6 @@
DebankPrediction,
DebankModelAppPortfolioItem,
DebankModelApp,
DebankModelDepositDetail,
DebankModelPredictionDetail,
)

Expand Down Expand Up @@ -626,16 +626,21 @@ def _parse_app(self, raw_app: dict) -> Optional[DebankApp]:
deposits = []
predictions = []

chain = DEBANK_APP_CHAIN_MAP.get(model.id)
if not chain:
logger.warning(f'Unknown chain for app {model.id}, using default')
chain = Blockchain.ETHEREUM

for portfolio_item in model.portfolio_item_list:
detail_types = portfolio_item.detail_types

if 'prediction' in detail_types:
prediction = self._parse_prediction(portfolio_item)
prediction = self._parse_prediction(portfolio_item, chain)
if prediction:
predictions.append(prediction)
else:
# Parse as deposit (common, etc.)
deposit = self._parse_deposit(portfolio_item)
deposit = self._parse_deposit(portfolio_item, chain)
if deposit:
deposits.append(deposit)

Expand All @@ -650,7 +655,7 @@ def _parse_app(self, raw_app: dict) -> Optional[DebankApp]:
)

def _parse_prediction(
self, item: DebankModelAppPortfolioItem
self, item: DebankModelAppPortfolioItem, chain: Blockchain
) -> Optional[DebankPrediction]:
"""Parse a prediction market position."""
try:
Expand All @@ -668,35 +673,22 @@ def _parse_prediction(
claimable=detail.claimable,
event_end_at=detail.event_end_at,
is_market_closed=detail.is_market_closed,
chain=chain,
position_index=item.position_index,
update_at=item.update_at,
)

def _parse_deposit(
self, item: DebankModelAppPortfolioItem
self, item: DebankModelAppPortfolioItem, chain: Blockchain
) -> Optional[DebankAppDeposit]:
"""Parse a deposit/common type portfolio item."""
try:
detail = DebankModelDepositDetail(**item.detail)
except Exception as e:
logger.warning(f'Failed to parse deposit detail: {e}')
detail = DebankModelDepositDetail()

# Collect all tokens from supply, borrow, and reward lists
tokens = []
if detail.supply_token_list:
tokens.extend(detail.supply_token_list)
if detail.borrow_token_list:
tokens.extend(detail.borrow_token_list)
if detail.reward_token_list:
tokens.extend(detail.reward_token_list)

return DebankAppDeposit.from_api(
name=item.name,
asset_usd_value=item.stats.asset_usd_value,
debt_usd_value=item.stats.debt_usd_value,
net_usd_value=item.stats.net_usd_value,
tokens=tokens,
tokens=item.asset_token_list,
chain=chain,
position_index=item.position_index,
update_at=item.update_at,
)
Expand Down
8 changes: 8 additions & 0 deletions blockapi/v2/api/debank_maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
if coin.info and coin.info.coingecko_id
}

# From DeBank API /v1/app_protocol/list endpoint (docs.cloud.debank.com)
DEBANK_APP_CHAIN_MAP: dict[str, Blockchain] = {
'hyperliquid': Blockchain.HYPERLIQUID,
'lighter': Blockchain.ARBITRUM,
'opinion': Blockchain.BINANCE_SMART_CHAIN,
'polymarket': Blockchain.POLYGON,
}

COINGECKO_IDS_BY_CONTRACTS: list[CoingeckoMapping] = [
CoingeckoMapping(
symbol='ETH',
Expand Down
30 changes: 21 additions & 9 deletions blockapi/v2/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1197,12 +1197,18 @@ class DebankModelPredictionDetail(BaseModel):
event_end_at: Optional[float] = None


class DebankModelDepositDetail(BaseModel):
"""Detail for deposit/common type portfolio items."""
class DebankDepositToken(BaseModel):
"""Token within deposit/common type portfolio items."""

id: str
symbol: str
name: str
amount: float
app_id: str
price: float
logo_url: Optional[str] = None


supply_token_list: Optional[list[dict]] = None
borrow_token_list: Optional[list[dict]] = None
reward_token_list: Optional[list[dict]] = None


class DebankModelAppPortfolioItem(BaseModel):
Expand All @@ -1214,7 +1220,7 @@ class DebankModelAppPortfolioItem(BaseModel):
detail: dict
position_index: str
asset_dict: Optional[dict] = None
asset_token_list: Optional[list[dict]] = None
asset_token_list: list[DebankDepositToken] = []
update_at: Optional[float] = None
Comment on lines 1221 to 1224
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asset_token_list uses a mutable default (=[]) on a Pydantic model. This can lead to shared state between instances and unexpected cross-test/request contamination. Prefer Field(default_factory=list) (or make it Optional[...] = None and normalize at use sites).

Copilot uses AI. Check for mistakes.
proxy_detail: Optional[dict] = None

Expand Down Expand Up @@ -1242,6 +1248,7 @@ class DebankPrediction:
claimable: bool
event_end_at: Optional[datetime]
is_market_closed: bool
chain: Blockchain
position_index: Optional[str]
update_at: Optional[datetime]

Expand All @@ -1256,6 +1263,7 @@ def from_api(
usd_value: Union[str, float, int],
claimable: bool,
is_market_closed: bool,
chain: Blockchain,
event_end_at: Optional[Union[int, float]] = None,
position_index: Optional[str] = None,
update_at: Optional[Union[int, float]] = None,
Expand All @@ -1269,6 +1277,7 @@ def from_api(
claimable=claimable,
event_end_at=parse_dt(event_end_at) if event_end_at is not None else None,
is_market_closed=is_market_closed,
chain=chain,
position_index=position_index,
update_at=parse_dt(update_at) if update_at is not None else None,
)
Expand All @@ -1282,7 +1291,8 @@ class DebankAppDeposit:
asset_usd_value: Decimal
debt_usd_value: Decimal
net_usd_value: Decimal
tokens: list[dict] # Raw token data for flexibility
tokens: list[DebankDepositToken]
chain: Blockchain
position_index: Optional[str]
update_at: Optional[datetime]

Expand All @@ -1295,7 +1305,8 @@ def from_api(
debt_usd_value: Union[str, float, int],
net_usd_value: Union[str, float, int],
position_index: str,
tokens: Optional[list[dict]] = None,
tokens: Optional[list[DebankDepositToken]] = None,
chain: Blockchain,
update_at: Optional[Union[int, float]] = None,
) -> 'DebankAppDeposit':
return cls(
Expand All @@ -1304,14 +1315,15 @@ def from_api(
debt_usd_value=to_decimal(debt_usd_value),
net_usd_value=to_decimal(net_usd_value),
tokens=tokens or [],
chain=chain,
position_index=position_index,
update_at=parse_dt(update_at) if update_at else None,
)

@property
def token_symbols(self) -> list[str]:
"""Get list of token symbols in this deposit."""
return [t.get('symbol', t.get('name', '')) for t in self.tokens]
return [t.symbol for t in self.tokens]


@attr.s(auto_attribs=True, slots=True, frozen=True)
Expand Down
Loading