feat: deterministic getTokenBalanceAcrossChains (no new tool) - #34
Conversation
Design for a host-side aggregate (getTokenBalanceAcrossChains) that moves multichain token-balance computation off LLM-authored guest code into deterministic package code, exposed via TOOL_METADATA (no new MCP tool). Driven by BrainDAO/aiden#105.
- min_usd_value: 0 (complete sweep; a balance query must not under-report). - chain-restricted path uses single-chain getUserTokenListRaw, not aggregate-then-filter. - add partial / chains_skipped observability (extend the aggregate to report skipped chains). - matcher: consult name/symbol/display_symbol/optimized_symbol + 0x-address -> id; bridged-suffix variants explicitly out of scope; concrete normalization examples; home at src/lib/token-matcher.ts. - responseSchema amount is z.number().nullable(); fix garbled missing-amount bullet; note total precision limit. - tests use vitest (not node:test); bump tool-metadata length 35 -> 36. - cross-repo gating: reverts must not ship before the package is published+pinned.
- single response shape with optional `error` field (resolveChain miss returns
it with other fields zeroed; infra failures still throw) — resolves the
two-shape + single-chain `partial` conflation findings.
- skipped-chain observability via an internal `_getUserTokensWithSkippedChains`
helper returning { tokens, skipped }; public aggregate keeps its flat-array
contract (only tool-metadata + embedded-index register it).
- matcher: empty-reference guard, null-safe fields (display_symbol), canonical
/^0x[a-f0-9]{40}$/i with malformed-address fallback.
- bump this method's timeoutMs to 45_000 (min_usd_value:0 sweeps more chains);
verify with a whale wallet during dev-link.
- name TokenBalanceAcrossChainsSchema; add a search-docs embedded-index entry
(resolves search_docs discoverability); drop the Open-details section.
- Decision log marked non-normative to avoid drift.
- embedded-index.ts is AUTO-GENERATED from TOOL_METADATA via build:docs (run by prebuild + prepare); the TOOL_METADATA entry alone suffices. Do NOT hand-edit the generated file. - helper keeps options?: RequestOptions and the doc block; abort plumbing preserved end-to-end. - record skipped chains via closure-and-push AFTER the abort re-throw check (a cancellation is not a skip); no allSettled. - initial getUserTotalBalanceRaw lookup throws (like the single-chain per-call); only per-chain fan-out failures become chains_skipped. - clarify _ prefix is convention only; method is TS-public (sandbox exposure is gated by TOOL_METADATA registration, not visibility).
- new method signature includes options?: RequestOptions (mandatory — the
sandbox bridge calls rawFn(args, {signal, timeout})); thread it into both
fetch paths so abort/timeout reach upstream calls.
- cross-repo PR 1 checklist no longer says "embedded-index entries"
(regenerates via build:docs).
- resolve the embedded-index count hedge: no fixed-count test exists (only
results.length > 0), so only tool-metadata.test.ts needs the 35->36 bump.
Match the call sites to the options-threading mandate so a literal copy preserves the abort signal.
Cross-repo plan: PR 1 (debank-mcp — matcher, helper, method, schema, TOOL_METADATA, tests, changeset) and PR 2 (aiden — wire + revert #105 bullets + bump), gated so reverts don't ship before publish.
- TOOL_METADATA entry includes the required name field (debank_get_token_balance_across_chains). - build:docs regenerates only embedded-index.ts; instructions.generated.ts comes from instructions.md via build:instructions and is unaffected — dropped from the git add list. - Task 1 ships the correct failing test (no self-correcting buggy assertion); add an intentional post-normalize match case. - Task 2 shows the full refactored helper (early-return shape change, try/catch, throwIfAborted) + an explicit change checklist. - drop unnecessary (userService as any) casts (helper is TS-public). - Task 7 quotes the exact three aiden bullets to delete.
There was a problem hiding this comment.
Code Review
This pull request implements a deterministic host-side token balance aggregation method across chains (debank.user.getTokenBalanceAcrossChains) to replace brittle LLM-authored arithmetic. It introduces a pure token matcher with robust normalization rules, refactors the user service to track skipped chains during fan-out, and registers the new method in the tool metadata and schemas. Feedback on the changes suggests adding defensive checks, specifically a null/undefined check for the holding parameter in the token matcher and a fallback empty array when fetching token lists in the user service.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…crossChains Live verification showed the agent sometimes read result.total.amount (nested) instead of the flat result.total. Make the description + exampleCall state the result shape and demonstrate flat-field access so guest code reads it correctly.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a deterministic, host-side method debank.user.getTokenBalanceAcrossChains to calculate multichain token balances, moving arithmetic logic out of LLM-authored guest code. It adds a pure token matcher with normalization rules, refactors the user service to support skipped-chain observability, and registers the new method in the tool metadata. The review feedback suggests two robust improvements: normalizing consecutive whitespace characters to a single space in the token matcher, and normalizing token names before checking for unique representations to avoid false positives.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Compare matched holdings' names case-insensitively (trim + lower) so case-only differences don't surface a misleading mixed_representations flag to the agent. The matcher itself already compares case-insensitively, so this aligns the final aggregate with the matcher's intent.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a deterministic host-side token balance aggregation method, debank.user.getTokenBalanceAcrossChains, to replace brittle LLM-authored arithmetic in guest code. It adds a pure token matcher, refactors existing fan-out logic to track skipped chains, registers the new method in TOOL_METADATA, and updates corresponding schemas, types, and tests. Feedback on the changes identifies a potential TypeError if h.name is null or undefined when calling h.name.trim(), recommending defensive fallback handling to prevent runtime crashes and schema validation failures.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
DeBank occasionally returns null name/symbol on custom or newly-deployed tokens. The matcher already handles this (typed string-guard in matchesTokenReference), but the new aggregate's downstream string ops (.trim() in mixed_representations, the matches array return) trusted the declared non-null types and would TypeError at runtime on a matched-via-symbol holding with null name. Coalesce to "" at the use sites — matches the cookbook's defensive p.name precedent (commit b0b12f1). Add a regression test.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements a deterministic, host-side token balance aggregation method across multiple chains (debank.user.getTokenBalanceAcrossChains) to replace brittle LLM-authored arithmetic. The changes introduce a pure token matcher (matchesTokenReference) with robust normalization rules, refactor the existing user service to support skipped-chain observability via _getUserTokensWithSkippedChains, and expose the new aggregate method via TOOL_METADATA with comprehensive test coverage. There are no review comments, so no additional feedback is provided.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Tracking issue: #36 (deterministic multichain token balance). Driven by IQAIcom/aiden#105 cross-repo. |
closes #36
Adds a host-side aggregate
debank.user.getTokenBalanceAcrossChains(exposed viaTOOL_METADATA, no new MCP tool — the code-mode surface staysexecute+search_docs) so multichain token-balance computation is deterministic instead of LLM-authored guest arithmetic.What it does
Given a wallet + a named token (name or symbol), it fans out across all chains (reusing
getUserTokensAcrossChains), matches holdings via a shared pure matcher, and returns per-chain balances + a combined total — reading each holding's human-readableamount(no10**decimalsmath). Passchainto restrict to one chain (single-chain fetch).Changes
src/lib/token-matcher.ts— purematchesTokenReference(exact name/symbol/display_symbol/optimized_symbol, or 0x address → holding id).src/services/user.service.ts— extracted_getUserTokensWithSkippedChains(records skipped chains, preserves abort plumbing; publicgetUserTokensAcrossChainsRawkeeps its flat-array contract) + newgetTokenBalanceAcrossChainsRaw.src/mcp/legacy/response-schemas.ts+tool-metadata.ts—TokenBalanceAcrossChainsSchema+ aTOOL_METADATAentry (timeoutMs: 45_000);search_docsindex regenerated.Notes
USDC.e,USDC (PoS)) are out of scope for v1 (documented in the tool description).partial/chains_skippedsurface when a chain's fetch is skipped, so totals aren't silently under-counted.Spec + plan:
docs/superpowers/specs/2026-06-27-deterministic-token-balance-across-chains-design.md,docs/superpowers/plans/2026-06-27-deterministic-token-balance-across-chains.md. Driven by IQAIcom/aiden#105.