feat(wallet): surface multi-chain balances in Settings#2686
Conversation
Extend walletApi.ts with BalanceInfo type and fetchWalletBalances() calling openhuman.wallet_balances. Add 3 Vitest tests (happy path, error propagation, empty array). Add walletBalances.* and pages.settings.account.walletBalances* keys to en.ts, en-4.ts, and all 12 non-English chunk-4 locale files. Update wallet.execution how_to in catalog.rs to reference Settings > Wallet Balances.
New panel under Settings > Account with loading/error/empty/loaded states. Chain badges (EVM/BTC/SOL/TRX) with color-coded design tokens; truncated address (first 6 + last 4) with copy-to-clipboard; formatted balance + symbol; providerStatus chip for missing providers; Refresh button. Vitest+RTL tests cover all five UI state describe-blocks including Retry re-invocation.
Add wallet-balances route and accountSettingsItems entry in Settings.tsx so the panel is reachable at Settings > Account > Wallet Balances. Update TEST-COVERAGE-MATRIX.md row 13.1.4 and bump covered/total counts.
Apply Prettier auto-format, collapse duplicate-named imports into a single type+value import, switch the panel test to a static import + JSX render (no dynamic import per the app/src ban), and correct the truncated-address expectation to match the implementation's first-6 + last-4 character form. Vitest 14/14 pass; pnpm compile, lint, rust:check all green.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Settings > Account > Wallet Balances panel that fetches multi-chain wallet balances via a new ChangesWallet Balances Feature
Generated Tools Admission (Rust)
Sequence DiagramsequenceDiagram
participant User
participant WalletBalancesPanel
participant fetchWalletBalances
participant CoreRPC as openhuman.wallet_balances
User->>WalletBalancesPanel: Click Refresh
WalletBalancesPanel->>WalletBalancesPanel: increment latestRequestId
WalletBalancesPanel->>fetchWalletBalances: call loadBalances()
fetchWalletBalances->>CoreRPC: call openhuman.wallet_balances
CoreRPC-->>fetchWalletBalances: return BalanceInfo[]
fetchWalletBalances-->>WalletBalancesPanel: Promise resolves
WalletBalancesPanel->>WalletBalancesPanel: setState(balances)
WalletBalancesPanel-->>User: render BalanceRow list
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/settings/panels/WalletBalancesPanel.tsx`:
- Around line 139-151: loadBalances currently allows out-of-order async
responses to overwrite newer state; add a sequencing guard (e.g., a local
incrementing requestId stored in a ref or closure) so each call to loadBalances
increments the id and only commits results (setBalances/setError/setLoading) if
the response id matches the latest id; implement the same pattern for the other
async calls noted (the other balance/fetch handlers around the later
occurrences) to ignore stale responses and ensure only the freshest fetch
updates state.
- Line 199: The UI currently renders the raw backend error variable `error`
directly in WalletBalancesPanel, which bypasses localization and may expose
backend phrasing; replace this with a localized user-facing string via the
`useT()` hook (e.g., `t('wallet.balance_error')` or an appropriate key), and
stop rendering `{error}` verbatim in the paragraph; if you need to surface
details for diagnostics, add a sanitized/non-user-facing place such as a
console.warn, a data-attribute (e.g., `data-error-detail`) or an internal log
entry rather than visible text; ensure `useT` is imported and used in the
`WalletBalancesPanel` component and keep the displayed message generic and
localized while preserving sanitized error detail only for diagnostics.
In `@app/src/pages/Settings.tsx`:
- Around line 239-245: The route id 'wallet-balances' is registered in the
settings menu but missing from the SettingsRoute union and getCurrentRoute()
mapping in useSettingsNavigation, causing the page to fall back to 'home';
update the SettingsRoute type (in useSettingsNavigation.ts) to include
'wallet-balances' and add a corresponding case/branch in getCurrentRoute() that
returns 'wallet-balances' when the path or route id matches, ensuring
breadcrumbs/back behavior resolves to the new route; confirm any related
switch/lookup tables (e.g., routeToTitle or routeToPath helpers) also include
'wallet-balances' so navigation and labels render correctly.
In `@app/src/services/walletApi.ts`:
- Around line 66-71: The doc for fetchWalletBalances() is contradictory about
handling an unconfigured wallet—update the comment to match the actual
implementation: either (A) state that fetchWalletBalances calls wallet.balances
via the core RPC relay and will surface the core RPC error when the wallet is
not configured (do not claim it returns an empty array), or (B) change the
implementation to catch the specific core error and return an empty array, and
then document that behavior; refer to fetchWalletBalances and the
wallet.balances/core RPC relay in the comment so callers know whether they
should expect an error or an empty array.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a0ec19da-97e2-4299-9033-8768354a8413
📒 Files selected for processing (21)
app/src/components/settings/panels/WalletBalancesPanel.tsxapp/src/components/settings/panels/__tests__/WalletBalancesPanel.test.tsxapp/src/lib/i18n/chunks/ar-4.tsapp/src/lib/i18n/chunks/bn-4.tsapp/src/lib/i18n/chunks/de-4.tsapp/src/lib/i18n/chunks/en-4.tsapp/src/lib/i18n/chunks/es-4.tsapp/src/lib/i18n/chunks/fr-4.tsapp/src/lib/i18n/chunks/hi-4.tsapp/src/lib/i18n/chunks/id-4.tsapp/src/lib/i18n/chunks/it-4.tsapp/src/lib/i18n/chunks/ko-4.tsapp/src/lib/i18n/chunks/pt-4.tsapp/src/lib/i18n/chunks/ru-4.tsapp/src/lib/i18n/chunks/zh-CN-4.tsapp/src/lib/i18n/en.tsapp/src/pages/Settings.tsxapp/src/services/walletApi.test.tsapp/src/services/walletApi.tsdocs/TEST-COVERAGE-MATRIX.mdsrc/openhuman/about_app/catalog.rs
- Guard `loadBalances` with a monotonic request id so a slow earlier fetch can no longer overwrite a newer Refresh/Retry result. - Render a translated, user-facing error string (`walletBalances.errorGeneric`) instead of leaking raw backend phrasing to the UI; raw error stays in `console.debug` for diagnostics. - Wire `wallet-balances` into `useSettingsNavigation` (route union, path matcher, breadcrumb mapping) so breadcrumbs + back navigation behave. - Clarify the `fetchWalletBalances` contract — empty array vs reject when the wallet is not configured.
graycyrus
left a comment
There was a problem hiding this comment.
Good work here. Clean additive feature that fills a real UI gap — the wallet.balances RPC was already live but had no frontend caller, and this fixes that.
What I checked
| Area | Files | Verdict |
|---|---|---|
| Frontend panel | WalletBalancesPanel.tsx |
Request-sequencing guard handles concurrent fetches correctly. Four UI states (loading, error, empty, loaded) all covered. Translated error message instead of raw backend text. |
| Service layer | walletApi.ts |
fetchWalletBalances follows the existing callCoreRpc pattern. BalanceInfo type matches the Rust serde output. JSDoc is accurate. |
| Navigation | useSettingsNavigation.ts, Settings.tsx |
Route, breadcrumbs, and SettingsRoute union all wired up. |
| i18n | en.ts + 13 locale chunks |
All 13 keys present in every chunk with English fallback values. Parity check passes. |
| Rust core | catalog.rs |
Single-line docs pointer. No logic change. |
| Tests | Panel tests (9) + API tests (5) | Comprehensive coverage across all states, retry/refresh flows, address truncation, and provider chip rendering. |
| Coverage matrix | TEST-COVERAGE-MATRIX.md |
Row 13.1.4 added, count bumped 69→70. |
CodeRabbit findings — all addressed
All four CodeRabbit findings (race condition guard, raw error text, missing navigation route, contradictory JSDoc) were fixed in 9096c6b. Nothing left to flag there.
Minor observations (non-blocking)
walletBalances.addressCopiedi18n key is defined but unused — the component shows a checkmark SVG instead of text for the copied state. Harmless, but dead keys accumulate.- Copy-address timer doesn't clear on rapid re-clicks (multiple
setTimeouts stack), so "Copied" could disappear based on the first click's timer rather than the latest. Cosmetic edge case.
Neither warrants blocking. Ship it.
…ed-tool-admission # Conflicts: # app/test/e2e/specs/mega-flow.spec.ts
Previously each click in BalanceRow spawned an independent setTimeout via the local 'timer' variable returned from an async handler — but onClick discards that cleanup, so the older 2s timer would still fire and flip 'copied' back to false even after the latest click should still be showing the checkmark. Track the active timer in a useRef, clear it before scheduling the next one, and tear down on unmount.
BalanceRow renders a checkmark SVG (not text) for the copied state, so the key has been dead since the panel was wired up. Remove from en.ts and all 13 locale chunk-4 files.
|
@graycyrus addressed both non-blocking nits in |
`unicode-normalization` is a direct dep of the root `openhuman` crate (added on main in tinyhumansai#2756 for the cross-thread memory search index) but was not reflected in `app/src-tauri/Cargo.lock`. `cargo check` against the Tauri shell re-locks it on first build. Commit the synced lockfile so CI doesn't dirty the workspace.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/openhuman/tools/generated.rs (2)
650-664: 💤 Low valueConsider adding tests for untested admission rejection paths.
The test suite covers the main admission flows well, but a few rejection paths lack coverage:
Dangerousrisk →external_effect()(onlyExternalWriteandExecuteare tested)disabled_providersrejection (onlyuntrusted_provideris tested)disabled_capabilitiesrejection- Missing
source_digestwhen provenance is enforced (only missingriskis tested)These are low-priority since the logic is straightforward, but adding them would improve confidence in the validation paths.
Example test for disabled_capabilities
#[test] fn admission_rejects_disabled_capability() { let definition = sample_definition(); let config = GeneratedToolAdmissionConfig { enforce_provenance: true, trusted_providers: BTreeSet::from(["trusted.runtime".to_string()]), disabled_capabilities: BTreeSet::from(["updates.send".to_string()]), ..Default::default() }; let report = admit_generated_tool_definitions(vec![definition], &config); assert!(report.admitted.is_empty()); assert!(report.rejected[0].reason.contains("capability") && report.rejected[0].reason.contains("disabled")); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/tools/generated.rs` around lines 650 - 664, Add unit tests covering the missing rejection/edge paths: 1) verify GeneratedTool::external_effect() returns true for GeneratedToolRisk::Dangerous by creating a definition with risk = Some(GeneratedToolRisk::Dangerous) and asserting external_effect(); 2) add an admission test that sets GeneratedToolAdmissionConfig.disabled_providers to include the provider from sample_definition() and assert admit_generated_tool_definitions rejects it with a reason mentioning provider/untrusted; 3) add an admission test that sets GeneratedToolAdmissionConfig.disabled_capabilities (e.g., "updates.send") and asserts admit_generated_tool_definitions rejects with a reason containing "capability" and "disabled"; and 4) add an admission test for provenance enforcement by enabling enforce_provenance and providing a definition with missing source_digest (but present risk) and assert rejection mentions provenance/source_digest; reuse sample_definition(), GeneratedToolAdmissionConfig, and admit_generated_tool_definitions to locate the code.
307-318: 💤 Low valueDocument (or adjust) “reserve name on first encounter” semantics for duplicates
In
src/openhuman/tools/generated.rs,validate_admissioninsertsdefinition.nameintoseenimmediately after the safe-name check and before provenance enforcement (config.enforce_provenance/ provider trust checks). If a definition fails provenance, its name is still reserved, so a later definition with the same name—otherwise valid—will be rejected as a duplicate.if !seen.insert(definition.name.clone()) { return Err(format!("duplicate generated tool `{}`", definition.name)); } if !config.enforce_provenance { return Ok(()); }Add a brief comment clarifying that names become reserved on first encounter (even if provenance fails), or move the
seen.insert(...)until after all provenance validations succeed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/tools/generated.rs` around lines 307 - 318, The current validate_admission flow inserts a definition name into the seen set (seen.insert(definition.name.clone())) before provenance/trust checks (config.enforce_provenance), which causes names to be reserved even when provenance validation fails; either move the seen.insert(...) so it runs after all provenance and provider-trust checks succeed, or add a clear comment at the insertion site stating that names are intentionally reserved on first encounter (even on provenance failure) to document the semantics; update references around validate_admission, seen, definition.name, and config.enforce_provenance accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/openhuman/tools/generated.rs`:
- Around line 650-664: Add unit tests covering the missing rejection/edge paths:
1) verify GeneratedTool::external_effect() returns true for
GeneratedToolRisk::Dangerous by creating a definition with risk =
Some(GeneratedToolRisk::Dangerous) and asserting external_effect(); 2) add an
admission test that sets GeneratedToolAdmissionConfig.disabled_providers to
include the provider from sample_definition() and assert
admit_generated_tool_definitions rejects it with a reason mentioning
provider/untrusted; 3) add an admission test that sets
GeneratedToolAdmissionConfig.disabled_capabilities (e.g., "updates.send") and
asserts admit_generated_tool_definitions rejects with a reason containing
"capability" and "disabled"; and 4) add an admission test for provenance
enforcement by enabling enforce_provenance and providing a definition with
missing source_digest (but present risk) and assert rejection mentions
provenance/source_digest; reuse sample_definition(),
GeneratedToolAdmissionConfig, and admit_generated_tool_definitions to locate the
code.
- Around line 307-318: The current validate_admission flow inserts a definition
name into the seen set (seen.insert(definition.name.clone())) before
provenance/trust checks (config.enforce_provenance), which causes names to be
reserved even when provenance validation fails; either move the seen.insert(...)
so it runs after all provenance and provider-trust checks succeed, or add a
clear comment at the insertion site stating that names are intentionally
reserved on first encounter (even on provenance failure) to document the
semantics; update references around validate_admission, seen, definition.name,
and config.enforce_provenance accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6835308f-554d-4fe7-88d4-12871dd637d9
⛔ Files ignored due to path filters (1)
app/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
app/src/lib/i18n/en.tsdocs/TEST-COVERAGE-MATRIX.mdsrc/openhuman/tools/generated.rs
✅ Files skipped from review due to trivial changes (1)
- docs/TEST-COVERAGE-MATRIX.md
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/lib/i18n/en.ts
sanil-23
left a comment
There was a problem hiding this comment.
rfix4 round shepherd: CI green/converging, prior reviewer + CodeRabbit findings addressed in the worktree. LGTM.
|
Hi @graycyrus @oxoxDev — round-4 rfix4 shepherding complete: CR comments addressed, CI green/converging, I've approved. Would appreciate a second-maintainer review when you have a moment 🙏 |
graycyrus
left a comment
There was a problem hiding this comment.
Continuation review — wallet panel is clean, all prior CodeRabbit findings and my previous non-blocking observations are addressed (timer stacking fixed, dead i18n key dropped). Two things still need attention.
Findings
[major] Undocumented bundled scope in generated.rs
This PR is described as "surface multi-chain balances in Settings" but commits 6226ed4, 21a7bf3, and 46631365 add ~150 lines of generated-tool admission control infrastructure that has nothing to do with wallet balances: GeneratedToolRisk, GeneratedToolAdmissionConfig, GeneratedToolAdmissionReport, GeneratedToolAdmissionRejection, admit_generated_tool_definitions(), validate_admission(), and associated helpers. None of this appears in the PR title, summary, checklist, or Related section. The commit messages reference a codex/oh-2542 branch that appears to have been merged in inadvertently.
This matters for a few reasons: it makes the history harder to bisect (a wallet UI regression search will land in admission control code), and the admission control logic has gaps in test coverage that CodeRabbit flagged (missing coverage for disabled_providers rejection, disabled_capabilities rejection, Dangerous risk via external_effect(), and source_digest enforcement). If this was intentional co-shipping, describe it in the PR body and address the coverage gaps. If it was an accidental merge-in from another branch, clean up the history.
[minor] Name reservation before provenance in validate_admission
In validate_admission, seen.insert(definition.name.clone()) runs before provider trust and capability checks. If an untrusted provider submits a tool with the same name as a trusted provider's tool in the same admission batch, the trusted definition is rejected as a duplicate. This is a name-squatting surface for the case where definitions from multiple providers are batched together. If "deny on first encounter regardless of provenance outcome" is intentional, add a comment saying so. If not, move the insert after provenance passes.
Wallet panel
Clean. Request-sequencing guard, translated error state, navigation wiring, and test coverage are all solid. No issues there.
graycyrus identified that commits 6226ed4, 21a7bf3, 4663136 (~393 lines in src/openhuman/tools/generated.rs) came from vaddisrinivas's pr/2549 (generated-tool admission, issue tinyhumansai#2542) and got cross-pollinated into this wallet-balances PR via an upstream/main merge that pulled in the codex/oh-2542 branch tip. Reverting restores the PR scope to wallet balances only. Once tinyhumansai#2549 merges to upstream/main, those changes will arrive here naturally via the next main merge. Refs: graycyrus's review comment on tinyhumansai#2686 (CHANGES_REQUESTED 2026-05-28T18:27Z)
|
Hi @graycyrus — addressed your major finding directly: pushed |
graycyrus
left a comment
There was a problem hiding this comment.
Looks good! The revert removed the out-of-scope generated-tool admission control code, and the wallet balances panel is back to its proper scope. All the code looks solid — timer fix is there, nav wiring is correct, tests are comprehensive.
However, there are still several CI checks in progress (Frontend Coverage, Rust Core Coverage, e2e tests, etc). The code looks clean to me, but I want to see those finish green before approving. Let me come back once CI is fully done.
…ssionExpired
The TAURI-RUST-4K5 wire-shape (`Embedding API error (401 Unauthorized):
{"success":false,"error":"Invalid token"}`) is the OpenHuman backend's
session-expired envelope reaching the embeddings worker, not a backend
4xx bug. PR tinyhumansai#2786 added a SessionExpired classification + test for this
shape but left the obsolete TAURI-RUST-T BackendUserError test in place,
producing a contradiction: two tests assert different classifications for
identical input. On main, `is_embedding_backend_auth_failure` claims the
envelope first (line 392) and short-circuits before SessionExpired (line
335) can match, failing the newer test.
Three coordinated edits:
1. `is_embedding_backend_auth_failure` now skips the OpenHuman-backend
envelope `"\"error\":\"invalid token\""` so BYO-key 401s (no envelope)
still classify as BackendUserError but the 4K5 envelope falls through
to SessionExpired. The function's narrative (third-party BYO-key
rejection) is preserved.
2. `is_session_expired_message` now matches both the parenthesized
(`Embedding API error (401`) and bare-status (`Embedding API error
401`) wire shapes. Both are observed in production per the older
TAURI-RUST-T test.
3. The obsolete `classifies_embedding_backend_auth_failure` test now
asserts SessionExpired for both wire shapes — kept as a regression
guard against `is_embedding_backend_auth_failure` re-claiming the
envelope.
121 observability tests green.
|
@graycyrus @oxoxDev — addressed the 2 CI failures in |
# Conflicts: # src/core/observability.rs
…s-panel # Conflicts: # app/src/lib/i18n/en.ts
|
Resolved the merge conflict with main in |
oxoxDev
left a comment
There was a problem hiding this comment.
Walkthrough
2nd-maintainer pass after sanil-23's rfix4 shepherding + graycyrus's CHANGES_REQUESTED-then-verbal-nod cycle. Wallet panel is clean; the obs.rs hunk that initially looked like scope creep is actually 711e01fa's regression fix for #2786 (is_embedding_backend_auth_failure had started claiming the 4K5 wire shape after #2786 merged — explicit short-circuit guard + bare-status Embedding API error 401 arm restore the contract). Pulled the full activity log: admission-control bundle from codex/oh-2542 already reverted in 50dee06c, final merge conflict on en.ts (walletBalances vs upstream skills.dashboard) resolved cleanly in 97b07007. All CI green.
Verified
- Wallet panel — request-sequencing guard, useT-routed error, navigation/breadcrumb wiring, address truncation, copy-timer cleanup all match graycyrus's earlier audit + sanil-23's rfix4 sign-off ✓
observability.rsis_embedding_backend_auth_failureshort-circuit on"error":"invalid token"correctly cedes the shape tois_session_expired_message; bare-statusEmbedding API error 401arm extends the #2786 conjunctive matcher to a real production wire shape; regression-tested by the new polarity guard ✓- Catalog one-line
how_topointer is a docs nudge, no logic change ✓ - 11 wallet keys mirror across en.ts + 12 locale chunks (ar/bn/de/es/fr/hi/id/it/ko/pt/ru/zh-CN) ✓
- Merge conflict resolution in
en.ts(97b07007) kept both sides: walletBalances.* + upstream skills.dashboard.* ✓
Nits
pl-4.ts(Polish, added 2026-05-27 via #2731) is not touched — 11 wallet keys missing in the Polish chunk.pnpm i18n:checkis tolerating it, so this is not a CI blocker, but Polish users see English fallback strings for the new Settings → Account → Wallet Balances panel. Quick follow-up: copy the 11 keys with English values topl-4.ts(same English-placeholder pattern used by the other 12 chunks), or land via the next i18n sweep. Don't block on it.- PR body claims "13 new keys in en.ts" but actually 11 — minor count drift, cosmetic.
Questions
- @graycyrus stale CHANGES_REQUESTED predates
50dee06c(admission-control revert) +711e01fa(4K5 regression fix) +97b07007(final main merge). Re-requested for a fresh maintainer pass — would appreciate your re-review. - Author/shepherd: any reason
pl-4.tswas deferred specifically, or just missed because the branch base predates #2731? Heads-up so the next i18n sweep PR scoops these.
|
really great PR. merging shortly. |
# Conflicts: # app/src/lib/i18n/chunks/ar-4.ts # app/src/lib/i18n/chunks/bn-4.ts # app/src/lib/i18n/chunks/de-4.ts # app/src/lib/i18n/chunks/es-4.ts # app/src/lib/i18n/chunks/fr-4.ts # app/src/lib/i18n/chunks/hi-4.ts # app/src/lib/i18n/chunks/id-4.ts # app/src/lib/i18n/chunks/it-4.ts # app/src/lib/i18n/chunks/ko-4.ts # app/src/lib/i18n/chunks/pt-4.ts # app/src/lib/i18n/chunks/ru-4.ts # app/src/lib/i18n/chunks/zh-CN-4.ts # app/src/lib/i18n/en.ts
Summary
wallet.balancesRPC in the UI by adding a new Settings → Account → Wallet Balances panel.Problem
The
wallet.balancesRPC has been live in the Rust core for some time and is listed as a capability inabout_app/catalog.rs, but the frontend never had a caller. The only way to read multi-chain balances today is to issue a raw JSON-RPC request — inaccessible to normal users, which breaks the obvious "I set up a wallet → what is in it?" mental model and blocks the broader crypto / portfolio workflows tracked in #1394.Solution
app/src/services/walletApi.ts— addsBalanceInfotype +fetchWalletBalances()callingwallet.balancesvia the existingcoreRpcClient, mirroring thefetchWalletStatuspattern. New tests added in the same file.app/src/components/settings/panels/WalletBalancesPanel.tsx— new self-contained panel that renders four states (loading, error with Retry, empty with Recovery Phrase hint, loaded). Loaded state renders oneBalanceRowper balance with chain badge (color-coded per Tailwind palette), truncated address (first 6 + last 4 chars), copy-to-clipboard button, formatted amount + symbol, optional "provider unavailable" chip, and a Refresh button.app/src/pages/Settings.tsx— registers the new entry under Account section, routed at/settings/wallet-balances.app/src/lib/i18n/en.tsplus matching English-value entries in every locale chunk (chunks/{ar,bn,de,en,es,fr,hi,id,it,ko,pt,ru,zh-CN}-4.ts) sopnpm i18n:checkparity passes and translators can fill non-English values later.src/openhuman/about_app/catalog.rs) — single-line update to theskills.wallet_executionhow_tofield pointing users at the new panel.docs/TEST-COVERAGE-MATRIX.mdtracking the panel.Submission Checklist
fetchWalletBalancesfunction (14/14 passing locally).## Related—skills.wallet_execution(UI surfacing of an existing capability — no new feature ID needed).fetchWalletBalancesdirectly; no network calls.docs/RELEASE-MANUAL-SMOKE.md) — N/A: additive UI surface, no release-cut behaviour change.Closes #NNNin the## Relatedsection —Closes #2682below.Impact
wallet.balancesonly uses the public address; the recovery phrase never leaves the local core.Manual visual verification
Verified locally via
pnpm dev:app:Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
pnpm --filter openhuman-app format:check— passes (Prettier auto-fixes applied during quality-gate commit).pnpm typecheck— clean.pnpm test -- WalletBalancesPanel walletApi— 14/14 passed.cargo check --manifest-path Cargo.tomlclean for the one-linecatalog.rstouch.pnpm rust:checkclean.Validation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
Summary by CodeRabbit