Skip to content

FIX: incorrect exchange rate shown for non-euro using European countries#1173

Merged
Zishan-7 merged 2 commits intopeanut-wallet-devfrom
fix/incorrect-exchange-rate
Sep 5, 2025
Merged

FIX: incorrect exchange rate shown for non-euro using European countries#1173
Zishan-7 merged 2 commits intopeanut-wallet-devfrom
fix/incorrect-exchange-rate

Conversation

@Zishan-7
Copy link
Contributor

@Zishan-7 Zishan-7 commented Sep 3, 2025

TASK-14343
More info on why we used the Frankfurter API for getting exchange rates https://discord.com/channels/972435984954302464/1412418820756213790

@vercel
Copy link

vercel bot commented Sep 3, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
peanut-wallet Ready Ready Preview Comment Sep 3, 2025 0:27am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

Walkthrough

Adds country-aware currency resolution and path fields to country mappings, passes a resolved nonEuroCurrency into ExchangeRate, updates ExchangeRate and its hooks to support an enabled flag and dual-source rate fetching (USD→non-EUR when applicable), and adjusts SavedAccounts country lookup to prefer mapping path.

Changes

Cohort / File(s) Summary
Withdraw page: route → currency
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
Reads country from route params, looks up a non‑EUR currency via countryCurrencyMappings (matching by country or path, case‑insensitive), and passes nonEuroCurrency prop into ExchangeRate.
Saved accounts: country lookup behavior
src/components/Common/SavedAccountsView.tsx
For non‑US accounts with countryName, resolves country by c.path.toLowerCase() instead of c.title.toLowerCase(); falls back to c.id === threeLetterCountryCode; navigation path still uses countryInfo.path.
Exchange rate component and hooks
src/components/ExchangeRate/index.tsx, src/hooks/useExchangeRate.ts, src/hooks/useGetExchangeRate.tsx
ExchangeRate signature extended to accept optional nonEuroCurrency; when present, uses useExchangeRate (USD→nonEuroCurrency) and disables useGetExchangeRate via enabled flags. Both hooks accept an enabled option to gate fetching; rendering/loading logic in ExchangeRate adapted based on which rate source is used.
Country-currency mapping schema/data
src/constants/countryCurrencyMapping.ts
Adds optional path?: string to CountryCurrencyMapping interface and populates path values for many entries (SEPA non‑EUR, USA, Mexico, etc.); export shape otherwise unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • Chore/prod to dev 106 #1152 — Modifies/uses country "path" in country lookup/mapping, closely related to the path-based resolution changes in this PR.

Suggested reviewers

  • jjramirezn
  • kushagrasarathe
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/incorrect-exchange-rate

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Common/SavedAccountsView.tsx (1)

90-90: Guard against missing path when building navigation URL

If countryInfo exists but path is missing, this will render /withdraw/undefined/bank. Guard the path:

-                const path = countryInfo ? `/withdraw/${countryInfo.path}/bank` : '/withdraw'
+                const path = countryInfo?.path ? `/withdraw/${countryInfo.path}/bank` : '/withdraw'
🧹 Nitpick comments (10)
src/hooks/useExchangeRate.ts (2)

30-31: Sensible default for enabled

Defaulting enabled = true preserves existing behavior; callers can opt out. Consider documenting this in the hook JSDoc.


94-95: Add minor hardening to the query options

  • Normalize currency codes in the queryKey/URL to avoid cache duplication (e.g., 'usd' vs 'USD').
  • Consider retry: 1 to keep UI snappy on transient failures.

Example (conceptual):

  • const from = sourceCurrency?.toUpperCase(); const to = destinationCurrency?.toUpperCase();
src/constants/countryCurrencyMapping.ts (2)

7-7: path?: string addition is appropriate; clarify slug contract

Please document that path is a lowercase, hyphenated slug (no diacritics). This will prevent mismatches across consumers.


15-38: Ensure slug consistency and future-proof lookups

The slugs look consistent (e.g., czech-republic, united-kingdom). To avoid brittle string compares downstream, consider exporting a derived map keyed by path and by country for O(1) lookups.

src/components/Common/SavedAccountsView.tsx (1)

79-81: Optional: avoid broken flag images for unknown codes

When countryCodeMap misses, threeLetterCountryCode is used and may not exist on flagcdn. Consider hiding the flag unless you have a valid 2-letter ISO code.

src/hooks/useGetExchangeRate.tsx (1)

18-52: Avoid stale loading state when toggling enabled to false

If enabled becomes false mid-lifecycle, explicitly clear loading to prevent transient UI inconsistencies.

-    useEffect(() => {
-        const fetchExchangeRate = async () => {
+    useEffect(() => {
+        if (!enabled) {
+            setIsFetchingRate(false)
+            return
+        }
+        const fetchExchangeRate = async () => {
             setIsFetchingRate(true)
             // reset previous value to avoid showing a stale rate for a new account type
             setExchangeRate(null)
src/components/ExchangeRate/index.tsx (2)

6-8: Typo in prop name: nonEruoCurrency → nonEuroCurrency

Recommend correcting the spelling across the codebase for clarity and future discoverability. If changing now is risky, schedule a follow-up rename.

I can generate a codemod to rename this across the repo with minimal churn.


29-36: Simplify number formatting and tighten copy

  • nonEruoExchangeRate is a number; no need for parseFloat(...toString()).
  • Consider ending the sentence with a period.
-        displayValue = nonEruoExchangeRate
-            ? `1 USD = ${parseFloat(nonEruoExchangeRate.toString()).toFixed(4)} ${nonEruoCurrency}`
+        displayValue = nonEruoExchangeRate
+            ? `1 USD = ${nonEruoExchangeRate.toFixed(4)} ${nonEruoCurrency}`
             : '-'
@@
-        moreInfoText =
-            "This is an approximate value. The actual amount received may vary based on your bank's exchange rate"
+        moreInfoText =
+            "This is an approximate value. The actual amount received may vary based on your bank's exchange rate."
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (2)

34-41: Normalize params once and reuse; reduce repeated lowercasing

Pre-normalizing the country segment avoids repeating toLowerCase and makes the intent clearer.

Apply:

-    const params = useParams()
-    const country = params.country as string
-
-    const nonEruoCurrency = countryCurrencyMappings.find(
-        (currency) =>
-            country.toLowerCase() === currency.country.toLowerCase() ||
-            currency.path?.toLowerCase() === country.toLowerCase()
-    )?.currencyCode
+    const params = useParams()
+    const normalizedCountry = (params.country as string).toLowerCase()
+
+    const nonEruoCurrency = countryCurrencyMappings.find((currency) => {
+        return (
+            normalizedCountry === currency.country.toLowerCase() ||
+            currency.path?.toLowerCase() === normalizedCountry
+        )
+    })?.currencyCode

If you prefer stronger typing, you can also do:

-const params = useParams()
+const { country: countryParam } = useParams<{ country: string }>()
+const normalizedCountry = countryParam.toLowerCase()

238-238: Prop name confirmation (spelling) and API sync

You’re passing nonEruoCurrency to ExchangeRate. If the prop in ExchangeRate is intentionally spelled this way (per PR), LGTM. Otherwise, align to nonEuroCurrency across both files in this PR to avoid confusion.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2acd55b and aaeeded.

📒 Files selected for processing (6)
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (3 hunks)
  • src/components/Common/SavedAccountsView.tsx (1 hunks)
  • src/components/ExchangeRate/index.tsx (1 hunks)
  • src/constants/countryCurrencyMapping.ts (1 hunks)
  • src/hooks/useExchangeRate.ts (3 hunks)
  • src/hooks/useGetExchangeRate.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-14T14:42:54.411Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1094
File: src/utils/withdraw.utils.ts:181-191
Timestamp: 2025-08-14T14:42:54.411Z
Learning: The countryCodeMap in src/components/AddMoney/consts/index.ts uses uppercase 3-letter country codes as keys (like 'AUT', 'BEL', 'CZE') that map to 2-letter country codes, requiring input normalization to uppercase for proper lookups.

Applied to files:

  • src/components/Common/SavedAccountsView.tsx
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/constants/countryCurrencyMapping.ts
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
📚 Learning: 2025-08-13T18:22:01.941Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1094
File: src/components/AddWithdraw/DynamicBankAccountForm.tsx:0-0
Timestamp: 2025-08-13T18:22:01.941Z
Learning: In the DynamicBankAccountForm component, the countryName parameter from useParams will always resemble a country title, not a URL slug.

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
📚 Learning: 2025-08-12T17:47:28.362Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1089
File: src/app/api/bridge/exchange-rate/route.ts:4-19
Timestamp: 2025-08-12T17:47:28.362Z
Learning: In the Bridge exchange rate API route (src/app/api/bridge/exchange-rate/route.ts), the ExchangeRateResponse interface uses numeric types for rates because the route converts string values from the Bridge API to floats using parseFloat() before returning the response.

Applied to files:

  • src/hooks/useExchangeRate.ts
  • src/components/ExchangeRate/index.tsx
  • src/hooks/useGetExchangeRate.tsx
🧬 Code graph analysis (2)
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)
src/constants/countryCurrencyMapping.ts (1)
  • countryCurrencyMappings (10-48)
src/components/ExchangeRate/index.tsx (3)
src/hooks/useGetExchangeRate.tsx (2)
  • IExchangeRate (5-8)
  • useGetExchangeRate (14-55)
src/hooks/useExchangeRate.ts (1)
  • useExchangeRate (26-150)
src/components/Payment/PaymentInfoRow.tsx (1)
  • PaymentInfoRow (7-81)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (8)
src/hooks/useExchangeRate.ts (1)

11-11: Gated fetch via optional enabled flag looks good

Adding enabled?: boolean is a clean way to short-circuit requests when unused. No concerns.

src/constants/countryCurrencyMapping.ts (1)

40-44: Align US/MX path values with route directories
No src/app/(mobile-ui)/withdraw/usa or …/mexico folders exist—update the path fields to match your actual route slugs or add those route directories.

⛔ Skipped due to learnings
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".
src/hooks/useGetExchangeRate.tsx (2)

7-8: Enabled flag is a good addition

Interface change is backward compatible and matches the new usage.


14-17: Initialize isFetchingRate from enabled — good

Prevents a loading spinner when the hook is disabled.

src/components/ExchangeRate/index.tsx (3)

11-17: Gated dual-source fetching is correct

  • useGetExchangeRate disabled when nonEruoCurrency is present.
  • useExchangeRate enabled only when nonEruoCurrency exists and correctly uses USD→dest with amount=1.

Looks good.


37-40: Formatting is correct here

exchangeRate is a string; parseFloat(...).toFixed(4) is appropriate.


10-17: Resolved: /api/exchange-rate returns numeric rate matching hook expectation

src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)

15-15: Importing useParams is fine here

No issues with bringing in useParams alongside useRouter for this client component.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/components/ExchangeRate/index.tsx (3)

10-17: Avoid triggering the legacy hook for US accounts

Minor UX optimization: don’t enable useGetExchangeRate for AccountType.US; we already know the rate is 1 and currently we show a brief loading flicker.

Apply this diff:

-    const { exchangeRate, isFetchingRate } = useGetExchangeRate({ accountType, enabled: !nonEuroCurrency })
+    const { exchangeRate, isFetchingRate } = useGetExchangeRate({
+        accountType,
+        enabled: accountType !== AccountType.US && !nonEuroCurrency,
+    })

37-40: Consider aligning type flow to avoid parseFloat here

You parseFloat(exchangeRate) because useGetExchangeRate returns a string. Per the retrieved learning (Bridge route returns numeric rates), consider updating useGetExchangeRate to surface a number to eliminate parsing at call sites and reduce NaN risks.

Would you like me to propose a small PR to change useGetExchangeRate’s state to number | null and adapt getExchangeRate’s response typing?


12-12: Fix typo and simplify number formatting

  • Rename nonEruoExchangeRatenonEuroExchangeRate (lines 12, 30–31).
  • Replace parseFloat(nonEruoExchangeRate.toString()).toFixed(4) with nonEuroExchangeRate.toFixed(4).
-    const { exchangeRate: nonEruoExchangeRate, isLoading } = useExchangeRate({
+    const { exchangeRate: nonEuroExchangeRate, isLoading } = useExchangeRate({
-        displayValue = nonEruoExchangeRate
-            ? `1 USD = ${parseFloat(nonEruoExchangeRate.toString()).toFixed(4)} ${nonEuroCurrency}`
+        displayValue = nonEuroExchangeRate
+            ? `1 USD = ${nonEuroExchangeRate.toFixed(4)} ${nonEuroCurrency}`
             : '-'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between aaeeded and e780296.

📒 Files selected for processing (2)
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (3 hunks)
  • src/components/ExchangeRate/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-12T17:47:28.362Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1089
File: src/app/api/bridge/exchange-rate/route.ts:4-19
Timestamp: 2025-08-12T17:47:28.362Z
Learning: In the Bridge exchange rate API route (src/app/api/bridge/exchange-rate/route.ts), the ExchangeRateResponse interface uses numeric types for rates because the route converts string values from the Bridge API to floats using parseFloat() before returning the response.

Applied to files:

  • src/components/ExchangeRate/index.tsx
🧬 Code graph analysis (1)
src/components/ExchangeRate/index.tsx (3)
src/hooks/useGetExchangeRate.tsx (2)
  • IExchangeRate (5-8)
  • useGetExchangeRate (14-55)
src/hooks/useExchangeRate.ts (1)
  • useExchangeRate (26-150)
src/components/Payment/PaymentInfoRow.tsx (1)
  • PaymentInfoRow (7-81)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (2)
src/components/ExchangeRate/index.tsx (2)

4-4: LGTM: adds hook needed for USD→non-EUR path

Import is correct and scoped.


6-8: Prop API extension looks good

Extending IExchangeRate and adding nonEuroCurrency keeps the public API minimal.

@notion-workspace
Copy link

Copy link
Contributor

@kushagrasarathe kushagrasarathe left a comment

Choose a reason for hiding this comment

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

@Zishan-7 lgtm, left one suggestion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants