anthropics/tres-asset-balance-validation
> Validate wallet balances in TRES Finance against DeBank and generate a discrepancy report. Use this skill whenever the user asks to validate, verify, cross-check, or audit their TRES balances against on-chain data or DeBank. Also trigger when the user asks if their balances are correct or wants to see discrepancies. Only EVM-compatible wallets are supported.
npx skills add https://github.com/anthropics/claude-plugins-community --skill tres-asset-balance-validation
This skill validates wallet balances in Tres Finance against DeBank, providing a clear discrepancy report as both an interactive HTML file and a PDF. It compares per-asset token amounts (including DeFi position underlying tokens) for each EVM wallet and flags matches, minor differences, major discrepancies, missing assets, untracked tokens, and unmatched positions.
> Scope: Only EVM-compatible wallets are supported (DeBank limitation). Exchange accounts, non-EVM chains, and empty wallets are skipped.
Trigger this skill whenever a user asks to:
Example phrases:
| Requirement | Details |
|---|---|
| TRES Finance access | Must be authenticated via get_viewer |
| DeBank API key | Free key available at cloud.debank.com |
| EVM wallets | At least one 0x... wallet tracked in TRES |
Call get_viewer to confirm the organization name.
IMPORTANT — Timeout handling: The internalAccount query with both balances and positions will timeout for large orgs. Split into two separate queries:
Query 1: Wallets + Balances only (no positions)
query {
internalAccount {
results {
id
name
identifier
isExchange
platforms
balances {
amount
asset {
symbol
contract { identifier }
}
fiatValue { value unitPrice fiatCurrency }
}
}
}
}
> Note: The amount field is returned as a string, not a number. Always float() it before arithmetic.
Wallet classification:
| Type | Condition | Validated? |
|---|---|---|
| EVM | 0x... address + EVM platform + isExchange: false | ✅ Yes |
| Exchange | isExchange: true | ❌ No |
| Non-EVM | Bitcoin, Tezos, Tron, etc. | ❌ No |
| Empty | No asset balances | ❌ No |
Supported EVM platforms: Ethereum, Arbitrum, Optimism, Polygon, Base, Avalanche, Binance, Gnosis Chain, zkSync, Fantom, Celo, Berachain, Linea, Scroll, Sonic, HyperEVM.
Do NOT use the positions sub-field on internalAccount — it returns all historical snapshots (can be 3000+ entries per wallet) and will timeout or exceed token limits.
Instead, use the dedicated getStatelessWalletsPositions query which returns current positions only:
query {
getStatelessWalletsPositions(
walletIdentifiers: ["0x..."],
platform: ETHEREUM,
application: "aave-v3"
) {
walletIdentifier
displayName
positionType
platform
children {
symbol
amount
assetIdentifier
fiatValue
}
fiatValue
id
}
}
Required parameters:
walletIdentifiers: array of wallet addressesplatform: must be an enum value like ETHEREUM, POLYGON, ARBITRUM, etc.Optional but recommended:
application: filter by protocol (e.g. "aave-v3", "verse", "lido", "merkl", "uniswap-v4", "sablier", "ethena", "stakewise", "quickswap", "steer", "yieldnest", "morphoblue")IMPORTANT: Without the application filter, the query returns empty results. Always specify the application.
Batching strategy:
all_complex_protocol_list for each wallet to discover which protocols have positionsquery {
a1: getStatelessWalletsPositions(walletIdentifiers: ["0x..."], platform: ETHEREUM, application: "aave-v3") {
walletIdentifier displayName positionType platform id
children { symbol amount assetIdentifier fiatValue }
}
a2: getStatelessWalletsPositions(walletIdentifiers: ["0x..."], platform: ETHEREUM, application: "verse") {
walletIdentifier displayName positionType platform id
children { symbol amount assetIdentifier fiatValue }
}
}
Keep each batched query to ~6 aliases max to avoid timeouts.
Read DEBANK_API_KEY from plugin user config — do not ask the user to paste it in chat.
If the key is absent or empty, stop and display:
> "DEBANK_API_KEY is not configured. Please add it via the plugin settings (obtain your key at https://cloud.debank.com)."
For each EVM wallet, fetch two endpoints:
all_token_list — covers all chains without requiring a chain_idall_complex_protocol_list — returns LP, staking, lending positions with underlying token amountsUse --data-urlencode with -G so the wallet address is never shell-interpolated into the URL string:
# Token balances
curl -s -G \
-H "AccessKey: ${user_config.DEBANK_API_KEY}" \
--data-urlencode "id=$WALLET_ADDR" \
"https://pro-openapi.debank.com/v1/user/all_token_list"
# DeFi positions
curl -s -G \
-H "AccessKey: ${user_config.DEBANK_API_KEY}" \
--data-urlencode "id=$WALLET_ADDR" \
"https://pro-openapi.debank.com/v1/user/all_complex_protocol_list"
Fiat-value filter: Discard any token where price < 0.01 — these are excluded from all matching, display, and reporting.
Rate limiting: Add a 0.3s delay between wallet requests to avoid 429 errors.
Matching must be chain-aware. DeBank's all_token_list returns a chain field per
token (e.g. "eth", "arb", "bsc"). TRES balances are tied to a specific platform
via the wallet's platforms array or the balance's context. Always prefer a per-chain
match before falling back to cross-chain aggregation.
Chain ID mapping (DeBank chain → TRES platform):
| DeBank chain | TRES platform |
|---|---|
| eth | ETHEREUM |
| arb | ARBITRUM |
| op | OPTIMISM |
| matic | POLYGON |
| base | BASE |
| bsc | BNB (Binance) |
| avax | AVALANCHE |
| ftm | FANTOM |
| xdai | GNOSIS |
| era | ZKSYNC |
| celo | CELO |
| linea | LINEA |
| scrl | SCROLL |
| mnt | MOONBEAM |
Matching order (most specific first):
asset.contract.identifier (lowercase) vs DeBanktoken id (lowercase), on the same chain. This is the most precise match.
asset.symbol(case-insensitive) AND DeBank chain matching the TRES platform for that balance row.
This correctly handles the common case where a wallet holds ETH on both Ethereum and
Arbitrum — each TRES balance row matches its corresponding DeBank per-chain entry.
to any specific DeBank chain entry, fall back to symbol-only matching. This handles
edge cases where TRES or DeBank uses a different chain label.
This is a critical step that compares DeFi position underlying tokens between TRES and DeBank.
Remove position NFT tokens from the regular comparison (e.g. UNI-V3-POS, RCL, SAB-LOCKUP, SLP, STEER, UNI-V4-POS). These are replaced by the underlying token rows from positions.
From getStatelessWalletsPositions results, for each position:
children by symbol — a position may have multiple entries for the same token (e.g. supply + unclaimed rewards in QuickSwap or Uniswap V4), so sum the amountsdisplayName to identify the protocol and token composition# Example: aggregate children for a position
def aggregate_children(children):
agg = {}
for c in children:
sym = c['symbol']
amt = float(c['amount']) if isinstance(c['amount'], str) else c['amount']
if sym not in agg:
agg[sym] = {'amount': 0, 'assetIdentifier': c.get('assetIdentifier', '')}
agg[sym]['amount'] += amt
return agg
From all_complex_protocol_list, for each protocol's portfolio_item_list:
asset_token_list for supply tokens and reward_token_list for rewardsname and item name (e.g. "Lending", "Liquidity Pool") as the position labelMatch DeBank protocol positions to TRES positions by:
For each matched position token:
position status (purple badge)Create two outputs:
Save both to the outputs directory.
Each wallet card in the report MUST have two distinct sections:
#1a1033 bg, #7c3aed border) showing all position-related rows. This section appears BEFORE the regular token table.Every position asset row MUST have a purple dot indicator (CSS circle, 8px, #a855f7) next to the asset name. This ensures positions are instantly recognizable:
<span style="display:inline-block;width:8px;height:8px;background:#a855f7;border-radius:50%;margin-right:6px;vertical-align:middle;"></span>
> Do NOT use emojis or inline SVGs — they may not render in all viewers. Use pure CSS shapes only.
Position rows that have a TRES match show TWO badges:
POSITION badge (purple)MATCH/MINOR/MAJOR)Wallet cards that contain DeFi positions should be auto-expanded (<details open>) so positions are immediately visible.
846,492.356)0.386)3.54e-07)—function formatAmount(n) {
if (n === null || n === undefined) return '\u2014';
if (Math.abs(n) < 0.000001) return n.toExponential(2);
if (Math.abs(n) >= 1000) return n.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 3});
return n.toFixed(3);
}
846,492.356 ($17,562.21)5.33% ($81.21)($X,XXX.XX) — always 2 decimal places, comma-separated thousandsUse a single price source (DeBank's token price) to compute fiat values for both the TRES and DeBank columns. This ensures:
# For regular tokens:
price = debank_token['price'] # single source
tres_fiat = tres_amount * price
debank_fiat = debank_amount * price
delta_fiat = abs(tres_amount - debank_amount) * price
# For position tokens — same principle:
price = debank_asset_token['price']
tres_fiat = tres_aggregated_amount * price
debank_fiat = debank_amount * price
delta_fiat = abs(tres_aggregated_amount - debank_amount) * price
Do NOT use TRES fiatValue for the TRES column and DeBank price for the DeBank column — this creates misleading fiat deltas when TRES and DeBank use different token prices.
Native tokens (ETH, BNB, etc.) often appear on multiple chains in TRES for a single
wallet (e.g., ETH on Ethereum + ETH on Arbitrum). DeBank's all_token_list returns
separate per-chain entries with a chain field — it does NOT aggregate them.
Rule: match per-chain first, aggregate only as a last resort.
platform context to find the corresponding DeBank entry by symbol + chain. For example,
a wallet with 0.00956 ETH on Ethereum and 0.001168 ETH on Arbitrum should produce two
separate comparison rows — one matched to DeBank's chain: "eth" entry and one to
chain: "arb".
DeBank returns fewer entries than TRES for the same symbol. This can happen if DeBank
merges certain bridged token balances. When aggregating, add a note like
"Aggregated: ETH (eth) + ETH (arb)".
Why this matters: Blindly aggregating before comparing creates false discrepancies.
If Ethereum ETH matches perfectly but Arbitrum ETH has a gap, aggregation masks the
Ethereum match and produces a single misleading delta. Per-chain matching preserves
granularity and makes it easy to identify exactly which chain is out of sync.
| Badge | Color | Hex | Meaning |
|---|---|---|---|
| Match | Green | #22c55e | Delta < 1% |
| Minor | Orange | #f59e0b | Delta 1–10% |
| Major | Red | #ef4444 | Delta > 10% |
| Missing | Grey | #6b7280 | Asset in TRES but not found in DeBank |
| Untracked | Blue | #3b82f6 | Asset in DeBank but not tracked in TRES |
| Position | Purple | #a855f7 | DeFi position token (with or without TRES data) |
The rendered report (both HTML and PDF) includes:
chain field mapped to TRES platforms). Multi-chain balances are only aggregated as a fallback when DeBank returns fewer entries than TRES for the same symbol.all_token_list covers all chains; tokens on chains not configured in TRES appear as "Untracked."price < $0.01 (zero-price, null, or micro-cap) are excluded from all reports.| Situation | Response |
|---|---|
| DeBank 401 error | Display "Invalid API key" in artifact |
| DeBank 429 error | Display "Rate limited — reload in 60s" |
| Empty token list for non-empty wallet | Flag wallet as suspicious |
| Non-EVM wallet | Skip and list in "Skipped wallets" section |
| internalAccount positions timeout | Use getStatelessWalletsPositions per wallet/platform/application instead |
| getStatelessWalletsPositions returns empty | Ensure application parameter is provided; without it the query returns empty |
| Position ID not extractable | Skip that position (don't crash) |
| amount field is string not number | Always cast with float() before arithmetic |
Take anthropics/tres-asset-balance-validation from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.