Skip to content

[TASK-14780] Feat/add manteca currencies to exchange rate widget#1255

Merged
jjramirezn merged 4 commits intofeat/manteca-integrationfrom
feat/add-currencies-exchange-rate
Sep 30, 2025
Merged

[TASK-14780] Feat/add manteca currencies to exchange rate widget#1255
jjramirezn merged 4 commits intofeat/manteca-integrationfrom
feat/add-currencies-exchange-rate

Conversation

@Zishan-7
Copy link
Contributor

No description provided.

@Zishan-7 Zishan-7 requested a review from jjramirezn September 26, 2025 06:04
@notion-workspace
Copy link

@vercel
Copy link

vercel bot commented Sep 26, 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 30, 2025 6:13am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Replaces Bridge-centric exchange-rate logic with a centralized getExchangeRate using getCurrencyPrice for Manteca-like currencies and Frankfurter fallbacks, adds USD-intermediate conversion flows, improved error handling and caching, and enables BRL/ARS in the countryCurrencyMappings.

Changes

Cohort / File(s) Summary of Changes
Exchange rate API
src/app/api/exchange-rate/route.ts
Replaces Bridge-specific logic with getExchangeRate(from,to) that prefers getCurrencyPrice for MANTECA-style currencies and EUR/MXN, falls back to Frankfurter for others, adds MANTECA_CURRENCIES, fetchFromCurrencyPrice helper, USD-intermediate cross conversions, console warnings/error handling, and standardized s-maxage/stale-while-revalidate caching. Preserves 1:1 same-currency short-circuit.
Country currency mapping
src/constants/countryCurrencyMapping.ts
Removes comingSoon flags from BRL and ARS and reclassifies them under a LATAM grouping; no exported signatures changed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

Possibly related PRs

Suggested reviewers

  • Hugo0
  • kushagrasarathe

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request lacks any description, so there is no content that relates to or summarizes the changes made, making it impossible for reviewers to quickly grasp the scope or purpose of the PR. Please add a brief description summarizing the changes, objectives, and any important implementation details to help reviewers understand the context and purpose of this pull request.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly highlights the primary feature introduced—adding Manteca currencies to the exchange rate widget—and is specific enough for a reviewer to understand the main intent of the PR without excessive detail or irrelevant information.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-currencies-exchange-rate

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f68f0a8 and c489193.

📒 Files selected for processing (1)
  • src/app/api/exchange-rate/route.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/app/api/exchange-rate/route.ts
📚 Learning: 2025-05-19T19:40:43.138Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#868
File: src/components/Payment/PaymentForm/index.tsx:284-293
Timestamp: 2025-05-19T19:40:43.138Z
Learning: When converting between USD and token amounts, always check if the token price (divisor) is valid and non-zero before performing the division to prevent Infinity, NaN, or errors. Implementing validation like `if (!tokenPrice || isNaN(tokenPrice) || tokenPrice === 0)` before division operations is crucial for handling cases where price data might be unavailable.

Applied to files:

  • src/app/api/exchange-rate/route.ts
🧬 Code graph analysis (1)
src/app/api/exchange-rate/route.ts (2)
src/app/actions/exchange-rate.ts (2)
  • ExchangeRateResponse (8-15)
  • getExchangeRate (25-61)
src/app/actions/currency.ts (1)
  • getCurrencyPrice (9-48)
🔇 Additional comments (7)
src/app/api/exchange-rate/route.ts (7)

2-2: LGTM: Imports getCurrencyPrice from actions.

The import of getCurrencyPrice from the actions layer addresses the architectural concern raised in past reviews about directly calling external APIs from the route handler.


37-76: LGTM: USD pair handling correctly refactored.

The refactoring to have fetchFromFrankfurter return Promise<number | null> (instead of NextResponse) resolves the critical issue flagged in past reviews where .json() was being called on a NextResponse object.


102-120: LGTM: Clean delegation logic.

The getExchangeRate helper properly delegates to either fetchFromCurrencyPrice or fetchFromFrankfurter based on currency type, with appropriate error handling.


128-132: LGTM: Validation added for sell rate.

The validation checks for isFinite and positive values address the concerns raised in past reviews about handling invalid responses from getCurrencyPrice.


136-140: LGTM: Validation added for buy rate.

The validation properly checks for finite, positive values and guards against division by zero, addressing past review concerns.


149-152: LGTM: Validation added for cross-currency conversion.

The validation for both buy and sell rates in the cross-currency path ensures no invalid arithmetic, addressing the concerns from past reviews.


169-212: LGTM: Frankfurter functions properly refactored.

The refactoring to have both fetchFromFrankfurter and fetchDirectFromFrankfurter return Promise<number | null> instead of NextResponse resolves the critical architectural issue from past reviews. The 50bps reduction is correctly applied at line 207.


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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

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/app/api/exchange-rate/route.ts (1)

118-159: Inconsistent error handling between main handler and helper function.

The getExchangeRate helper returns the raw rate value from Bridge/Frankfurter responses, but these functions return NextResponse objects, not numbers. This will cause the JSON parsing on lines 144-145 and 153-154 to fail.

Fix the return type mismatch:

 if (bridgeResult) {
-    const data = await bridgeResult.json()
-    return data.rate
+    // bridgeResult is a NextResponse, extract the rate from it
+    if (bridgeResult instanceof NextResponse) {
+        // Bridge already returns the rate in the response
+        // We need to refactor fetchFromBridge to return just the rate for this helper
+        return null // Fall back to Frankfurter
+    }
+    return bridgeResult
 }

 // Use Frankfurter for all other pairs or as fallback
 const frankfurterResult = await fetchFromFrankfurter(from, to)
-const data = await frankfurterResult.json()
-return data.rate
+// Same issue here - fetchFromFrankfurter returns NextResponse
+// Need to refactor to return just the rate value
+return null

Actually, a better approach would be to refactor the helper functions to return rate values instead of NextResponse objects, or create separate internal functions for rate fetching:

+async function getBridgeRate(
+    from: string,
+    to: string,
+    rateType: 'buy_rate' | 'sell_rate',
+    shouldInvert: boolean
+): Promise<number | null> {
+    const bridgeAPIKey = process.env.BRIDGE_API_KEY
+    if (!bridgeAPIKey) {
+        console.warn('Bridge API key not set')
+        return null
+    }
+
+    try {
+        const url = `https://api.bridge.xyz/v0/exchange_rates?from=${from.toLowerCase()}&to=${to.toLowerCase()}`
+        const response = await fetch(url, {
+            method: 'GET',
+            headers: { 'Api-Key': bridgeAPIKey },
+        })
+
+        if (!response.ok) {
+            console.error(`Bridge API error: ${response.status} ${response.statusText}`)
+            return null
+        }
+
+        const bridgeData: BridgeExchangeRateResponse = await response.json()
+        if (!bridgeData[rateType]) {
+            console.error(`Invalid Bridge response: missing ${rateType}`)
+            return null
+        }
+
+        let rate = parseFloat(bridgeData[rateType])
+        if (shouldInvert) {
+            rate = 1 / rate
+        }
+        return rate
+    } catch (error) {
+        console.error('Bridge API exception:', error)
+        return null
+    }
+}
+
+async function getFrankfurterRate(from: string, to: string): Promise<number | null> {
+    try {
+        const url = `https://api.frankfurter.app/latest?from=${from}&to=${to}`
+        const response = await fetch(url)
+        
+        if (!response.ok) {
+            console.error(`Frankfurter API error: ${response.status} ${response.statusText}`)
+            return null
+        }
+        
+        const data = await response.json()
+        return data.rates[to] * 0.995 // Subtract 50bps
+    } catch (error) {
+        console.error('Frankfurter API exception:', error)
+        return null
+    }
+}

Then update the getExchangeRate function to use these new helpers.

🧹 Nitpick comments (3)
src/app/api/exchange-rate/route.ts (3)

271-271: Document the 50bps fee adjustment.

The 0.995 multiplier (50 basis points reduction) on Frankfurter rates should be documented to explain the business logic behind this adjustment.

 const exchangeRate: ExchangeRateResponse = {
-    rate: data.rates[to] * 0.995, // Subtract 50bps
+    rate: data.rates[to] * 0.995, // Apply 0.5% margin/fee to Frankfurter rates for consistency with other providers
 }

1-279: Add rate limiting and monitoring for external API calls.

The route makes multiple external API calls without rate limiting, which could lead to hitting provider limits or increased costs. Consider implementing request throttling and monitoring.

Consider implementing:

  1. Rate limiting per provider using a library like p-limit or bottleneck
  2. Metrics/monitoring for API call success rates and latencies
  3. Circuit breaker pattern for failing providers
  4. Caching layer (Redis/in-memory) to reduce API calls for frequently requested pairs

18-18: Externalize LATAM currency codes into a shared constant

  • Move the hardcoded MANTECA_CURRENCIES set to a shared export (e.g. in src/constants/manteca.consts.ts as
    export const MANTECA_SUPPORTED_CURRENCIES = ['ARS','BRL','COP','CRC','PUSD','GTQ','PHP','BOB']) and import it in src/app/api/exchange-rate/route.ts.
  • All current codes (ARS, BRL, COP, CRC, PUSD, GTQ, PHP, BOB) map to entries in MANTECA_COUNTRIES.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dfcb28 and 022dc6b.

📒 Files selected for processing (2)
  • src/app/api/exchange-rate/route.ts (4 hunks)
  • src/constants/countryCurrencyMapping.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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/app/api/exchange-rate/route.ts
📚 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/constants/countryCurrencyMapping.ts
🧬 Code graph analysis (1)
src/app/api/exchange-rate/route.ts (1)
src/services/manteca.ts (1)
  • mantecaApi (95-234)
🔇 Additional comments (2)
src/constants/countryCurrencyMapping.ts (1)

45-47: Good change enabling LATAM currencies!

The removal of comingSoon flags for BRL and ARS properly activates these currencies in the system, aligning with the new Manteca integration for LATAM currency support.

src/app/api/exchange-rate/route.ts (1)

46-92: Well-structured fallback chain for USD conversions.

The implementation properly prioritizes Manteca for LATAM currencies, falls back to Bridge for specific pairs, and ultimately uses Frankfurter as the final fallback. The error handling with console warnings helps with debugging provider failures.

Copy link
Contributor

@jjramirezn jjramirezn left a comment

Choose a reason for hiding this comment

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

Check comments

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: 4

🧹 Nitpick comments (2)
src/app/api/exchange-rate/route.ts (2)

9-9: Consider centralizing the MANTECA_CURRENCIES set.

The list of Manteca-supported currencies is duplicated between this file and src/constants/index.ts (where MANTECA_CURRENCIES array is defined in the relevant snippet from currency.ts). If the supported currencies change, both locations need updates.

Consider importing this set from a shared constants file:

-// LATAM currencies that should use Manteca API
-const MANTECA_CURRENCIES = new Set(['ARS', 'BRL', 'COP', 'CRC', 'PUSD', 'GTQ', 'PHP', 'BOB'])
+import { MANTECA_CURRENCIES } from '@/constants'
+
+const MANTECA_CURRENCIES_SET = new Set(MANTECA_CURRENCIES)

Then replace references to MANTECA_CURRENCIES with MANTECA_CURRENCIES_SET throughout this file.


37-65: Good fallback pattern for USD-based pairs.

The logic correctly attempts fetchFromCurrencyPrice first for Manteca/EUR/MXN currencies, then falls back to Frankfurter. The warning log on line 60 is helpful for debugging.

However, consider adding validation for the rate returned from fetchFromCurrencyPrice before returning it:

                 const currencyPriceRate = await fetchFromCurrencyPrice(fromUc, toUc)
-                if (currencyPriceRate !== null) {
+                if (currencyPriceRate !== null && isFinite(currencyPriceRate) && currencyPriceRate > 0) {
                     return NextResponse.json(
                         { rate: currencyPriceRate },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 022dc6b and f68f0a8.

📒 Files selected for processing (1)
  • src/app/api/exchange-rate/route.ts (2 hunks)
🧰 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/app/api/exchange-rate/route.ts
🧬 Code graph analysis (1)
src/app/api/exchange-rate/route.ts (2)
src/app/actions/exchange-rate.ts (2)
  • ExchangeRateResponse (8-15)
  • getExchangeRate (25-61)
src/app/actions/currency.ts (1)
  • getCurrencyPrice (9-48)
🔇 Additional comments (2)
src/app/api/exchange-rate/route.ts (2)

2-2: LGTM: Using action instead of direct API calls.

The import of getCurrencyPrice aligns with the feedback to use actions rather than calling mantecaApi.getPrices directly. This centralizes currency pricing logic appropriately.

Based on past review comments.


147-172: Function works correctly for direct HTTP responses.

fetchFromFrankfurter correctly fetches rates from the Frankfurter API, applies the 50 bps spread (line 164), and returns a properly formatted NextResponse.

However, this function returns NextResponse which makes it incompatible for internal rate calculations in getExchangeRate (lines 104-106). See the critical issue flagged in lines 91-111.

@jjramirezn jjramirezn merged commit 333c745 into feat/manteca-integration Sep 30, 2025
4 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 1, 2025
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