feat(settings): Crypto + Notifications hubs, wallet send/receive & multi-network balances#3027
Conversation
Recovery Phrase and Wallet Balances were scattered under the Account section. Mirror the Agents settings hub pattern and gather both under a dedicated Settings > Crypto section page. - Add a Crypto section page (cryptoSettingsItems) routed at /settings/crypto - Move recovery-phrase + wallet-balances out of accountSettingsItems - Register the Crypto hub on the Settings home menu - Reparent recovery-phrase/wallet-balances breadcrumbs under Crypto - Add settings.cryptoSection.* i18n keys across all 14 locales - Update about_app capability how_to strings to the new nav path - Extend useSettingsNavigation + SettingsHome tests for crypto
…ettings-crypto-hub
The Settings home menu carried two adjacent notification-related entries: 'Alerts' (the /notifications inbox) and 'Notifications' (the preferences + routing panel). Mirror the Crypto hub: gather both under a single 'Notifications' section page placed under Advanced (Developer Options), and drop the two top-level entries from the home menu. - Add a Notifications section hub routed at /settings/notifications-hub with two items: Alerts (-> /notifications inbox) and Notification settings (-> existing NotificationsTabbedPanel) - SettingsSectionItem gains optional onClick (route is now optional) so a hub item can link to a top-level route outside /settings - Register the hub on the Advanced (Developer Options) page - Remove Alerts + Notifications from the Settings home menu - Reparent the notifications panel breadcrumb under Settings > Developer Options > Notifications; hub sits under Advanced - Add settings.notificationsHub.* i18n keys across all 14 locales - Update useSettingsNavigation + SettingsHome tests
Wallet Balances previously hard-errored (red 'unable to load' + Retry)
whenever no recovery phrase was configured, because the core errors
wallet_balances until a wallet exists. That made the panel look broken
before setup.
Now the panel checks wallet_status first: when the wallet isn't
configured it drops the blocking error in favour of a non-blocking amber
hint ('set up your recovery phrase…') with a 'Set up recovery phrase'
CTA, and still renders the wallet layout as muted placeholder rows for
all four supported chains (EVM/BTC/SOL/TRX). Genuine fetch failures
while configured keep the error/retry path.
- WalletBalancesPanel checks fetchWalletStatus before fetchWalletBalances
- Add ChainPlaceholderRow + setupHint/setupCta/notSetUp i18n (14 locales)
- SettingsSectionItem already supports onClick; CTA routes to recovery-phrase
- Update WalletBalancesPanel tests (status mock + not-configured cases)
Wallet Balances now shows the EVM account on Ethereum, Base, and BNB Chain as separate rows, and each balance row gains Send / Receive actions. Core: - balances() fans the EVM account into one native-balance row per displayed network (EVM_BALANCE_NETWORKS = Ethereum/Base/BNB Chain), reading each live; BTC/Solana/Tron unchanged. Extract balance_row / native_asset helpers. Update the balances schema + add a unit test. Frontend: - walletApi: EvmNetwork/PreparedTransaction/ExecutionResult types + prepareTransfer / executePrepared; BalanceInfo.evmNetwork typed. - walletDisplay: EVM network labels/badges, balanceKey, and lossless toSmallestUnit/fromSmallestUnit (BigInt) helpers. - ReceiveModal: address QR + copy + same-network warning. - SendCryptoModal: prepare -> review (simulated fee) -> execute, with local-signing copy and explorer link. Native asset; tokens are a follow-up. - WalletBalancesPanel: per-network rows + Send/Receive; placeholders now mirror the configured layout (6 rows). - i18n: walletBalances.send/receive + walletReceive.*/walletSend.* across all 14 locales. Tests: Rust balances fan-out; walletDisplay conversions; SendCryptoModal flow; panel send/receive modal-open.
|
Warning Review limit reached
More reviews will be available in 34 minutes and 48 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (15)
📝 WalkthroughWalkthroughThis PR adds interactive send/receive crypto functionality to the wallet UI while reorganizing Settings navigation to include a new "Crypto" section with recovery phrase and wallet balances, and moving notifications to a hub under Developer Options. Changes span frontend settings wiring, wallet API contracts, interactive modal components, Rust-side balance fanning across EVM networks, and comprehensive internationalization updates. ChangesCrypto wallet send/receive and settings reorganization
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
The Settings reorg moved Recovery Phrase + Wallet Balances out of the Account section into the new Settings > Crypto hub. Update the account section spec to assert the remaining account items (team/privacy/ migration) and add a crypto-section spec covering recovery phrase + wallet balances.
balances() now returns one row per displayed EVM network (Ethereum, Base, BNB Chain) plus BTC/Solana/Tron, so the json_rpc_e2e wallet surface round-trip expects 6 rows (3 EVM) instead of 4.
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (2)
app/src/components/settings/SettingsSectionPage.tsx (1)
56-56: 💤 Low valueConsider explicit handling for items without
onClickorroute.The current logic
item.onClick ?? (() => item.route && navigateToSettings(item.route))will always assign a function toonClick, even when bothitem.onClickanditem.routeare undefined. This causesSettingsMenuItemto render a button (per context snippet) that does nothing when clicked.While no current items appear to lack both properties, an explicit check would be clearer and more defensive:
onClick={item.onClick ?? (item.route ? () => navigateToSettings(item.route) : undefined)}This way, items without navigation render as non-interactive divs (per
SettingsMenuItemcontract).🤖 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 `@app/src/components/settings/SettingsSectionPage.tsx` at line 56, Replace the current fallback that always supplies a no-op function for the onClick prop in SettingsSectionPage (the expression using item.onClick and navigateToSettings) with an explicit conditional: if item.onClick exists use it; else if item.route exists supply a handler that calls navigateToSettings(item.route); otherwise pass undefined so SettingsMenuItem renders non-interactive. Update the onClick assignment in the SettingsSectionPage component to reference item.onClick, item.route and navigateToSettings accordingly so items without either property are not rendered as clickable.tests/json_rpc_e2e.rs (1)
4007-4013: ⚡ Quick winAssert the exact EVM networks, not just the EVM row count.
The new fan-out contract is network-specific (Ethereum/Base/BNB).
count() == 3can still pass with the wrong networks. Please pinevmNetworkvalues too.🔧 Suggested test hardening
assert_eq!( rows.iter() .filter(|r| r.get("chain").and_then(Value::as_str) == Some("evm")) .count(), 3, "expected 3 EVM network rows: {result}" ); + let evm_networks: Vec<&str> = rows + .iter() + .filter(|r| r.get("chain").and_then(Value::as_str) == Some("evm")) + .filter_map(|r| r.get("evmNetwork").and_then(Value::as_str)) + .collect(); + for expected in ["ethereum_mainnet", "base_mainnet", "bsc_mainnet"] { + assert!( + evm_networks.iter().any(|n| *n == expected), + "expected balances row for {expected}, got {evm_networks:?}" + ); + }🤖 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 `@tests/json_rpc_e2e.rs` around lines 4007 - 4013, The test currently only asserts the EVM row count; modify the assertion to verify the actual evm networks returned. In the block using rows.iter().filter(|r| r.get("chain").and_then(Value::as_str) == Some("evm")), collect the evm network identifiers via r.get("evmNetwork").and_then(Value::as_str) (or the correct key used in the rows), build a set or sorted Vec of those strings, and assert it equals the expected set/Vec (e.g., ["ethereum","base","bnb"]) instead of only asserting count() == 3; update the assertion message to include the actual collected networks for easier debugging.
🤖 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/hooks/useSettingsNavigation.ts`:
- Around line 201-209: The breadcrumb labels (e.g., cryptoCrumb and
notificationsHubCrumb) are hardcoded and must use useT() for i18n; import useT
from app/src/lib/i18n/I18nContext, call const t = useT() in
useSettingsNavigation, replace label: 'Crypto' and label: 'Notifications' with
label: t('settings.breadcrumbs.crypto') and label:
t('settings.breadcrumbs.notifications') (and similarly update accountCrumb,
featuresCrumb, aiCrumb, teamCrumb, developerCrumb, agentsCrumb), then add the
corresponding keys to app/src/lib/i18n/en.ts and other locale files.
In `@app/src/components/settings/panels/wallet/ReceiveModal.tsx`:
- Around line 23-34: The timeout stored in copyTimerRef (set in handleCopy)
isn’t cleared on unmount; add a useEffect with an empty dependency array that
returns a cleanup function which clears copyTimerRef.current (and sets it to
null) to avoid calling setCopied on an unmounted component; reference
copyTimerRef, handleCopy, setCopied and ensure the cleanup uses clearTimeout
when copyTimerRef.current is not null.
In `@app/src/components/settings/panels/WalletBalancesPanel.tsx`:
- Around line 9-15: The current import block mixes type-only symbols with
runtime functions; change BalanceInfo, EvmNetwork, and WalletChain to type-only
imports using "import type" while keeping fetchWalletBalances and
fetchWalletStatus as regular imports so runtime behavior is unchanged—update the
import statement in WalletBalancesPanel.tsx to import type BalanceInfo,
EvmNetwork, WalletChain and import fetchWalletBalances, fetchWalletStatus
normally.
In `@app/src/features/wallet/walletDisplay.ts`:
- Around line 64-76: toSmallestUnit currently throws hard-coded English
messages; change it to throw well-defined, machine-readable error identifiers
(e.g., a typed Error subclass or an error.code string like
AmountValidationError.INVALID_FORMAT and AmountValidationError.EXCESS_DECIMALS)
instead of human text so callers (e.g., SendCryptoModal) can call useT() and map
those error codes to localized messages; update toSmallestUnit to throw these
error objects (preserve the same validation logic in toSmallestUnit) and update
the calling component to catch the errors and translate via useT() using the
decimals param for the EXCESS_DECIMALS case.
In `@app/src/lib/i18n/ar.ts`:
- Line 4100: Replace the English placeholder for the i18n key
settings.cryptoSection.title with a proper Arabic translation (e.g., "التشفير")
in the Arabic locale file; locate the entry with the symbol
settings.cryptoSection.title and update its value from "Crypto" to the correct
Arabic text so the Arabic locale contains a real translation for that key.
In `@app/src/lib/i18n/bn.ts`:
- Line 4173: The Bengali locale entry for settings.cryptoSection.title is still
in English; update the value in app/src/lib/i18n/bn.ts for the key
"settings.cryptoSection.title" to a proper Bengali translation (e.g.,
"ক্রিপ্টো") so the non-English locale mirrors the key added/changed in en.ts and
adheres to the i18n guideline.
In `@app/src/lib/i18n/hi.ts`:
- Line 4181: The key 'settings.cryptoSection.title' in the Hindi locale file
(hi.ts) is still in English; replace the English value 'Crypto' with an
appropriate Hindi translation (e.g., 'क्रिप्टो') so the locale is fully
localized; update the string for the settings.cryptoSection.title entry and
ensure formatting/quoting matches surrounding entries in hi.ts.
In `@app/src/lib/i18n/pl.ts`:
- Line 4245: Replace the English value for the i18n key
'settings.cryptoSection.title' in the Polish locale so it contains a proper
Polish translation (e.g., "Krypto") instead of "Crypto"; update the string in
pl.ts where the 'settings.cryptoSection.title' entry is defined to a correct
Polish word and save the file.
In `@app/src/lib/i18n/pt.ts`:
- Line 4244: Update the Portuguese translation for the key
'settings.cryptoSection.title' in the pt locale: replace the English value
'Crypto' with the Portuguese translation (e.g., 'Cripto') so the key reads
'settings.cryptoSection.title': 'Cripto'; ensure the change is applied in the
pt.ts i18n file where that key is defined.
- Around line 4249-4250: Update the Portuguese translation for the key
'settings.notificationsHub.description' to use the idiomatic verb "gerencie"
instead of "gira"; locate the string value for
'settings.notificationsHub.description' and replace "gira as preferências de
notificação e o encaminhamento" with "gerencie as preferências de notificação e
o encaminhamento" so the description reads naturally.
In `@app/src/lib/i18n/ru.ts`:
- Line 4214: The key settings.cryptoSection.title is still English in the
Russian locale; update the ru translation for settings.cryptoSection.title to a
proper Russian string (e.g., "Криптовалюта" or another accurate localized term)
in the ru.ts file, ensuring the value replaces 'Crypto' and matches neighboring
key style and punctuation.
In `@app/src/lib/i18n/zh-CN.ts`:
- Line 3972: The i18n key 'settings.cryptoSection.title' is left in English;
update its value in zh-CN.ts to a proper Chinese translation (for example '加密货币'
or '加密') so the entry reads 'settings.cryptoSection.title': '加密货币'; ensure the
key exists and uses a correct Chinese string in zh-CN.ts to match the en.ts
addition.
---
Nitpick comments:
In `@app/src/components/settings/SettingsSectionPage.tsx`:
- Line 56: Replace the current fallback that always supplies a no-op function
for the onClick prop in SettingsSectionPage (the expression using item.onClick
and navigateToSettings) with an explicit conditional: if item.onClick exists use
it; else if item.route exists supply a handler that calls
navigateToSettings(item.route); otherwise pass undefined so SettingsMenuItem
renders non-interactive. Update the onClick assignment in the
SettingsSectionPage component to reference item.onClick, item.route and
navigateToSettings accordingly so items without either property are not rendered
as clickable.
In `@tests/json_rpc_e2e.rs`:
- Around line 4007-4013: The test currently only asserts the EVM row count;
modify the assertion to verify the actual evm networks returned. In the block
using rows.iter().filter(|r| r.get("chain").and_then(Value::as_str) ==
Some("evm")), collect the evm network identifiers via
r.get("evmNetwork").and_then(Value::as_str) (or the correct key used in the
rows), build a set or sorted Vec of those strings, and assert it equals the
expected set/Vec (e.g., ["ethereum","base","bnb"]) instead of only asserting
count() == 3; update the assertion message to include the actual collected
networks for easier debugging.
🪄 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: 9214d12c-6ce2-4ba1-b372-9e16fa40edd9
📒 Files selected for processing (35)
app/src/components/settings/SettingsHome.tsxapp/src/components/settings/SettingsSectionPage.tsxapp/src/components/settings/__tests__/SettingsHome.test.tsxapp/src/components/settings/hooks/__tests__/useSettingsNavigation.test.tsxapp/src/components/settings/hooks/useSettingsNavigation.tsapp/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/WalletBalancesPanel.tsxapp/src/components/settings/panels/__tests__/WalletBalancesPanel.test.tsxapp/src/components/settings/panels/wallet/ReceiveModal.tsxapp/src/components/settings/panels/wallet/SendCryptoModal.tsxapp/src/components/settings/panels/wallet/__tests__/SendCryptoModal.test.tsxapp/src/features/wallet/__tests__/walletDisplay.test.tsapp/src/features/wallet/walletDisplay.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/Settings.tsxapp/src/services/walletApi.tsapp/test/playwright/specs/settings-account-preferences.spec.tssrc/openhuman/about_app/catalog_data.rssrc/openhuman/wallet/execution.rssrc/openhuman/wallet/execution_tests.rssrc/openhuman/wallet/schemas.rstests/json_rpc_e2e.rs
- ReceiveModal: clear the copy-reset timer on unmount (useEffect cleanup). - SendCryptoModal: surface a translated invalid-amount message instead of toSmallestUnit's raw English throw; toSmallestUnit now throws internal sentinel codes (dev-facing, caught + replaced at the call site). - i18n: translate settings.cryptoSection.title in all non-English locales (was left as the English 'Crypto' placeholder); fix pt notificationsHub description grammar (gira -> gerencie).
Summary
Problem
Crypto-related settings were scattered (recovery phrase and wallet balances lived under Account), Alerts/Notifications duplicated top-level menu space, the Wallet Balances panel hard-errored before a wallet was set up, there was no way to send/receive funds from the UI, and the single EVM balance row hid Base/BSC holdings.
Solution
SettingsSectionPage-based hubs routed at/settings/cryptoand/settings/notifications-hub, registered in the Settings home menu / Developer Options, with breadcrumbs reparented accordingly.SettingsSectionItemgained an optionalonClickso a hub item can link to a top-level route (the Alerts inbox).WalletBalancesPanelcheckswallet_statusfirst; unconfigured wallets get an amber hint + a "Set up recovery phrase" CTA and placeholder rows that mirror the configured layout.balances()fans the EVM account into one native-balance row per displayed network (EVM_BALANCE_NETWORKS= Ethereum/Base/BNB Chain), read live; BTC/Solana/Tron unchanged.ReceiveModal(QR + copy + same-network warning) andSendCryptoModal(drives the existingwallet.prepare_transfer→wallet.execute_preparedprimitives; native asset only). Signing stays local in the core.walletDisplayhelper for EVM network labels/badges and lossless BigInt amount conversion.Submission Checklist
Impact
balances()fan-out in the Rust core (existing transfer RPCs reused). Verified live in the desktop app (Crypto/Notifications hubs, the setup hint, send & receive modals, and the ETH/Base/BNB Chain rows).Related
EVM_BALANCE_NETWORKS.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
pnpm --filter openhuman-app format:checkpnpm typecheckpnpm debug unit wallet(30/30),useSettingsNavigation,SettingsHome;pnpm debug rust balances_fans_evmcargo fmt+cargo check --libValidation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
#routing) intact.Duplicate / Superseded PR Handling
Summary by CodeRabbit
Release Notes
New Features
Refactor