Skip to content

✨ app: add bridge support#885

Open
franm91 wants to merge 1 commit intoi18nfrom
bridge-ui
Open

✨ app: add bridge support#885
franm91 wants to merge 1 commit intoi18nfrom
bridge-ui

Conversation

@franm91
Copy link
Copy Markdown
Member

@franm91 franm91 commented Mar 13, 2026


Open with Devin

Summary by CodeRabbit

  • New Features

    • Bridge support for deposits, provider-aware funding flows, and Terms-of-Service gating.
    • Multi-deposit selection with PIX/BR QR code support and per-deposit detail views.
    • New ramp provider buttons and a streamlined type-driven Add Funds flow.
    • Added EUR, GBP, and MXN currency options.
  • Improvements

    • Improved deposit UI: clearer network/asset labels, full-address display, dynamic copy/share behavior, and per-deposit copy actions.
    • Locale-aware disclaimers and expanded Spanish and Portuguese translations for onboarding and deposit flows.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 13, 2026

🦋 Changeset detected

Latest commit: 03427ee

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds provider-aware bridge support across the add-funds flow: new UI components, provider/network URL params, provider-specific onboarding/KYC (bridge vs manteca), multi-deposit ramp quoting (including PIX/BR), server/client API updates, i18n entries, and related utilities/resources.

Changes

Cohort / File(s) Summary
New bridge UI components
src/components/add-funds/AddRampButton.tsx, src/components/add-funds/BridgeDisclaimer.tsx
New components to surface provider-aware funding options and a localized bridge disclaimer with external link handling.
Add-funds pages (provider-aware)
src/components/add-funds/AddCrypto.tsx, src/components/add-funds/AddFunds.tsx, src/components/add-funds/Onboard.tsx, src/components/add-funds/KYC.tsx, src/components/add-funds/Status.tsx
Introduce provider & network URL params, provider-specific flows (bridge vs manteca), TOS handling and redirect capture, and conditional UI/routing changes across onboarding/KYC/status screens.
Ramp deposit UI refactor
src/components/add-funds/Ramp.tsx
Major refactor to support provider-aware quotes, multi-deposit responses, per-deposit detail rows, PIX/BR QR handling, animated deposit toggle, and provider-specific messaging/limits.
AddFunds option components
src/components/add-funds/AddFundsOption.tsx, src/components/add-funds/AddRampButton.tsx, src/components/add-funds/AddFiatButton.tsx
Add disabled prop to option component, add AddRampButton, and remove legacy AddFiatButton.
Shared copy/address sheet API change
src/components/shared/CopyAddressSheet.tsx
Expanded component signature to accept override address, network, networkLogo, and assets; network logo rendering and asset list made conditional.
Onboarding utilities & persona/server
src/utils/completeOnboarding.ts, src/utils/persona.ts, src/utils/server.ts
Generalize onboarding/KYC signatures and flows for provider types; add startAddressKYC; update server client signatures and startRampOnboarding error handling (recognize "invalid address"); adjust persisted token key names.
Resources & config
src/utils/currencies.ts, src/utils/networkLogos.ts, src/utils/queryClient.ts, cspell.json
Add EUR/GBP/MXN currencies, add networkLogos mapping, change dehydrate exclusion to ramp/kyc-tokens, and add CLABE/IBAN to dictionary.
i18n & changeset
.changeset/modern-jeans-write.md, src/i18n/es.json, src/i18n/pt.json
Add changeset and many Spanish/Portuguese translations for deposit/onramp, network, and account copy.
Disclaimer locale tweak
src/components/add-funds/MantecaDisclaimer.tsx
Make terms link locale-aware; conditional disclaimer rendering across flows.
Removed file
src/components/add-funds/AddFiatButton.tsx
Legacy AddFiatButton component deleted.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Client as AddFunds/Onboard/KYC UI
    participant Server
    participant Provider as Bridge/Manteca
    participant KYC as Persona

    User->>Client: Select currency + provider (+network)
    Client->>Server: Fetch ramp providers & tosLink (country, redirectURL)
    Client->>User: Show TOS if bridge / show onboarding UI
    alt Bridge flow (TOS)
        User->>Client: Accept TOS (redirect with signed_agreement_id)
        Client->>Server: startRampOnboarding(provider=bridge, acceptedTermsId, network)
    else Manteca flow
        Client->>Server: startRampOnboarding(provider=manteca)
    end
    Server->>Provider: Initiate onboarding request
    Provider-->>Server: Return inquiryId/sessionToken or none
    alt inquiry present
        Server-->>Client: Tokens (inquiryId, sessionToken)
        Client->>KYC: startAddressKYC or startMantecaKYC (type-based)
        KYC-->>Client: KYC result (complete/cancel/error)
        Client->>Client: Redirect to /add-funds/kyc or /add-funds/status with provider/network
    else no inquiry
        Server-->>Client: No inquiry → redirect to status (pending)
    end
Loading
sequenceDiagram
    participant User
    participant RampUI as Ramp
    participant Server
    participant Provider as Bridge/Manteca
    participant QRModal

    User->>RampUI: Open deposit view (provider, currency, network)
    RampUI->>Server: Request quote + deposits (provider-aware)
    Server->>Provider: Request deposit details
    Provider-->>Server: Return deposits list (PIX/BR/bank/crypto)
    Server-->>RampUI: Deliver quote + deposits
    RampUI->>User: Render deposits + toggle
    alt PIX/BR deposit selected
        User->>QRModal: Open QR modal
        QRModal-->>User: Display QR/BR code
    else bank/crypto deposit
        RampUI->>User: Show account/address rows with copy/share
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • franm91
  • dieguezguille
  • nfmelendez
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '✨ app: add bridge support' clearly summarizes the main change—introducing bridge functionality to the app. It is concise, specific, and directly reflects the core purpose of this comprehensive changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bridge-ui

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

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

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the add funds functionality by introducing Bridge support and refactoring the fiat onboarding flow. It includes UI improvements, new asset and network logos, and a TOS view for Bridge. The changes allow users to deposit funds from various networks and bank accounts, providing a more seamless and user-friendly experience.

Highlights

  • Bridge Support: This PR introduces support for adding funds via Bridge, allowing users to deposit funds from various networks and receive USDC in their Exa account.
  • Fiat Onboarding: The PR refactors the fiat onboarding flow, enabling users to add funds from their bank accounts and automatically receive USDC in their Exa account.
  • UI Improvements: Several UI enhancements were made, including adding new asset logos, network logos, and a toggle component for selecting deposit networks.
  • Terms of Service: The PR adds a TOS view for Bridge, ensuring users accept the terms and conditions before proceeding with the onboarding process.
Changelog
  • .changeset/modern-jeans-write.md
    • Added changeset to track the addition of bridge support.
  • src/assets/images/ars-back.svg
    • Added the ars-back.svg image for the Argentine Peso.
  • src/assets/images/ars-usdc.svg
    • Removed the ars-usdc.svg image.
  • src/assets/images/background.svg
    • Added the background.svg image.
  • src/assets/images/brl-back.svg
    • Added the brl-back.svg image for the Brazilian Real.
  • src/assets/images/brl-usdc.svg
    • Removed the brl-usdc.svg image.
  • src/assets/images/euro-back.svg
    • Added the euro-back.svg image for the Euro.
  • src/assets/images/pounds-back.svg
    • Added the pounds-back.svg image for the British Pound.
  • src/assets/images/solana-network.svg
    • Added the solana-network.svg image for the Solana network.
  • src/assets/images/stellar-network.svg
    • Added the stellar-network.svg image for the Stellar network.
  • src/assets/images/tron-network.svg
    • Added the tron-network.svg image for the Tron network.
  • src/assets/images/usd-back.svg
    • Added the usd-back.svg image for the US Dollar.
  • src/assets/images/usd-usdc.svg
    • Removed the usd-usdc.svg image.
  • src/assets/images/usdc-centered.svg
    • Added the usdc-centered.svg image.
  • src/assets/images/usdc-front.svg
    • Added the usdc-front.svg image.
  • src/assets/images/usdt-centered.svg
    • Added the usdt-centered.svg image.
  • src/components/add-funds/AddCrypto.tsx
    • Modified AddCrypto component to support bridge deposits and display deposit addresses.
  • src/components/add-funds/AddFiatButton.tsx
    • Removed AddFiatButton component.
  • src/components/add-funds/AddFunds.tsx
    • Refactored AddFunds component to support crypto and fiat deposit options.
  • src/components/add-funds/AddFundsOption.tsx
    • Modified AddFundsOption component to support disabled state.
  • src/components/add-funds/AddRampButton.tsx
    • Added AddRampButton component to handle fiat and crypto ramp options.
  • src/components/add-funds/BridgeDisclaimer.tsx
    • Added BridgeDisclaimer component to display disclaimer for Bridge deposits.
  • src/components/add-funds/KYC.tsx
    • Modified KYC component to support bridge and manteca KYC flows.
  • src/components/add-funds/MantecaDisclaimer.tsx
    • Modified MantecaDisclaimer component to update disclaimer text.
  • src/components/add-funds/Onboard.tsx
    • Modified Onboard component to support bridge onboarding and display terms of service.
  • src/components/add-funds/Ramp.tsx
    • Modified Ramp component to support bridge and manteca ramp flows.
  • src/components/add-funds/Status.tsx
    • Modified Status component to support bridge and manteca status updates.
  • src/components/shared/AssetLogo.tsx
    • Modified AssetLogo component to use chain ID 10.
  • src/components/shared/CopyAddressSheet.tsx
    • Modified CopyAddressSheet component to support bridge deposit addresses and network logos.
  • src/i18n/es.json
    • Added translations for new features and components.
  • src/i18n/pt.json
    • Added translations for new features and components.
  • src/utils/networkLogos.ts
    • Added network logos for Solana, Stellar, and Tron.
  • src/utils/persona.ts
    • Modified persona functions to support bridge and manteca KYC flows.
  • src/utils/queryClient.ts
    • Modified queryClient to persist kyc-tokens.
  • src/utils/server.ts
    • Modified server functions to support bridge and manteca ramp flows.
Activity
  • Added changeset to track the addition of bridge support.
  • Added the ars-back.svg image for the Argentine Peso.
  • Removed the ars-usdc.svg image.
  • Added the background.svg image.
  • Added the brl-back.svg image for the Brazilian Real.
  • Removed the brl-usdc.svg image.
  • Added the euro-back.svg image for the Euro.
  • Added the pounds-back.svg image for the British Pound.
  • Added the solana-network.svg image for the Solana network.
  • Added the stellar-network.svg image for the Stellar network.
  • Added the tron-network.svg image for the Tron network.
  • Added the usd-back.svg image for the US Dollar.
  • Removed the usd-usdc.svg image.
  • Added the usdc-centered.svg image.
  • Added the usdc-front.svg image.
  • Added the usdt-centered.svg image.
  • Modified AddCrypto component to support bridge deposits and display deposit addresses.
  • Removed AddFiatButton component.
  • Refactored AddFunds component to support crypto and fiat deposit options.
  • Modified AddFundsOption component to support disabled state.
  • Added AddRampButton component to handle fiat and crypto ramp options.
  • Added BridgeDisclaimer component to display disclaimer for Bridge deposits.
  • Modified KYC component to support bridge and manteca KYC flows.
  • Modified MantecaDisclaimer component to update disclaimer text.
  • Modified Onboard component to support bridge onboarding and display terms of service.
  • Modified Ramp component to support bridge and manteca ramp flows.
  • Modified Status component to support bridge and manteca status updates.
  • Modified AssetLogo component to use chain ID 10.
  • Modified CopyAddressSheet component to support bridge deposit addresses and network logos.
  • Added translations for new features and components.
  • Added network logos for Solana, Stellar, and Tron.
  • Modified persona functions to support bridge and manteca KYC flows.
  • Modified queryClient to persist kyc-tokens.
  • Modified server functions to support bridge and manteca ramp flows.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for a new 'bridge' provider, significantly enhancing the on-ramp capabilities for both fiat and crypto. The 'Add Funds' flow has been thoughtfully refactored into separate 'Crypto' and 'Fiat' sections, improving user experience and code organization. Key changes include the introduction of generic components like AddRampButton and CopyAddressSheet to handle multiple providers and networks, and the generalization of the KYC and onboarding processes. The addition of new assets, networks, and corresponding UI elements is well-integrated. Overall, this is a substantial and well-executed feature addition. I've included a couple of specific suggestions for improvement.

}

const styles = StyleSheet.create({
pill: { position: "absolute", top: 2, bottom: 2, left: 2, right: 2, borderRadius: 9999 },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In the pill style, right: 2 is redundant and potentially confusing since left and width are both specified for the animated view that uses this style. When left and width are set, right is ignored. Removing it would make the styling clearer.

Suggested change
pill: { position: "absolute", top: 2, bottom: 2, left: 2, right: 2, borderRadius: 9999 },
pill: { position: "absolute", top: 2, bottom: 2, left: 2, borderRadius: 9999 },

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 32.25806% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.80%. Comparing base (9ce007a) to head (03427ee).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/utils/persona.ts 0.00% 15 Missing ⚠️
src/utils/server.ts 16.66% 5 Missing ⚠️
src/components/shared/CopyAddressSheet.tsx 88.88% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             i18n     #885      +/-   ##
==========================================
- Coverage   71.41%   70.80%   -0.62%     
==========================================
  Files         227      227              
  Lines        8397     8230     -167     
  Branches     2707     2639      -68     
==========================================
- Hits         5997     5827     -170     
+ Misses       2177     2173       -4     
- Partials      223      230       +7     
Flag Coverage Δ
e2e 70.80% <32.25%> (-0.62%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

sentry[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@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: 5

Caution

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

⚠️ Outside diff range comments (3)
src/components/add-funds/Ramp.tsx (2)

98-103: ⚠️ Potential issue | 🟠 Major

Min is wired to the FX quote.

Line 101 derives minAmount from quote.buyRate, and Lines 241-248 render that same field again as the exchange rate. The footer can therefore show the FX rate under a Min label instead of the actual deposit floor. Use the real minimum-amount field from the quote/limits payload, or hide Min until that value exists.

Also applies to: 200-213, 241-248


267-285: ⚠️ Potential issue | 🟠 Major

The QR sheet still shows manteca-only transfer rules in bridge flows.

The main screen already hides the under your name alert for bridge, but the modal always renders it. That contradicts the bridge copy added in src/components/add-funds/AddRampButton.tsx (Any account) and can misdirect users right before a transfer.

🐛 Proposed fix
-              <InfoAlert title={t("All deposits must be from bank accounts under your name.")} />
+              {typedProvider === "manteca" && (
+                <InfoAlert title={t("All deposits must be from bank accounts under your name.")} />
+              )}
src/components/add-funds/KYC.tsx (1)

71-79: ⚠️ Potential issue | 🟠 Major

Keep kyc-tokens when the user cancels out of KYC.

This back handler now clears the cached tokens on cancel, which forces the user to restart onboarding instead of immediately retrying the same KYC step.

🐛 Proposed fix
             <Pressable
               onPress={() => {
-                queryClient.removeQueries({ queryKey: ["ramp", "kyc-tokens"] });
                 if (router.canGoBack()) {
                   router.back();
                 } else {
                   router.replace("/(main)/(home)");
                 }
Based on learnings, canceling the KYC flow should preserve these tokens; only success or error paths are meant to clear them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1302b94b-88c6-4f98-a8b0-be7e9c522596

📥 Commits

Reviewing files that changed from the base of the PR and between e75e683 and 88054cc.

⛔ Files ignored due to path filters (16)
  • src/assets/images/ars-back.svg is excluded by !**/*.svg
  • src/assets/images/ars-usdc.svg is excluded by !**/*.svg
  • src/assets/images/background.svg is excluded by !**/*.svg
  • src/assets/images/brl-back.svg is excluded by !**/*.svg
  • src/assets/images/brl-usdc.svg is excluded by !**/*.svg
  • src/assets/images/euro-back.svg is excluded by !**/*.svg
  • src/assets/images/mxn-back.svg is excluded by !**/*.svg
  • src/assets/images/pounds-back.svg is excluded by !**/*.svg
  • src/assets/images/solana-network.svg is excluded by !**/*.svg
  • src/assets/images/stellar-network.svg is excluded by !**/*.svg
  • src/assets/images/tron-network.svg is excluded by !**/*.svg
  • src/assets/images/usd-back.svg is excluded by !**/*.svg
  • src/assets/images/usd-usdc.svg is excluded by !**/*.svg
  • src/assets/images/usdc-centered.svg is excluded by !**/*.svg
  • src/assets/images/usdc-front.svg is excluded by !**/*.svg
  • src/assets/images/usdt-centered.svg is excluded by !**/*.svg
📒 Files selected for processing (21)
  • .changeset/modern-jeans-write.md
  • src/components/add-funds/AddCrypto.tsx
  • src/components/add-funds/AddFiatButton.tsx
  • src/components/add-funds/AddFunds.tsx
  • src/components/add-funds/AddFundsOption.tsx
  • src/components/add-funds/AddRampButton.tsx
  • src/components/add-funds/BridgeDisclaimer.tsx
  • src/components/add-funds/KYC.tsx
  • src/components/add-funds/MantecaDisclaimer.tsx
  • src/components/add-funds/Onboard.tsx
  • src/components/add-funds/Ramp.tsx
  • src/components/add-funds/Status.tsx
  • src/components/shared/CopyAddressSheet.tsx
  • src/i18n/es.json
  • src/i18n/pt.json
  • src/utils/completeOnboarding.ts
  • src/utils/currencies.ts
  • src/utils/networkLogos.ts
  • src/utils/persona.ts
  • src/utils/queryClient.ts
  • src/utils/server.ts
💤 Files with no reviewable changes (1)
  • src/components/add-funds/AddFiatButton.tsx

Comment on lines +305 to +316
if (Platform.OS === "web") {
return React.createElement("iframe", {
src: uri,
style: { flex: 1, border: "none", width: "100%", height: "100%" },
onLoad: (event: { target: { contentWindow?: { location: { href: string } } } }) => {
try {
const url = (event.target as { contentWindow?: { location: { href: string } } }).contentWindow?.location.href;
if (url?.startsWith(redirectURL)) handleRedirect(url);
} catch {} // eslint-disable-line no-empty -- cross-origin expected
},
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant type assertion on event.target.

Line 311 casts event.target to the same type already declared in the onLoad parameter type annotation. This duplication can be removed.

♻️ Proposed simplification
       onLoad: (event: { target: { contentWindow?: { location: { href: string } } } }) => {
         try {
-          const url = (event.target as { contentWindow?: { location: { href: string } } }).contentWindow?.location.href;
+          const url = event.target.contentWindow?.location.href;
           if (url?.startsWith(redirectURL)) handleRedirect(url);
         } catch {} // eslint-disable-line no-empty -- cross-origin expected
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Platform.OS === "web") {
return React.createElement("iframe", {
src: uri,
style: { flex: 1, border: "none", width: "100%", height: "100%" },
onLoad: (event: { target: { contentWindow?: { location: { href: string } } } }) => {
try {
const url = (event.target as { contentWindow?: { location: { href: string } } }).contentWindow?.location.href;
if (url?.startsWith(redirectURL)) handleRedirect(url);
} catch {} // eslint-disable-line no-empty -- cross-origin expected
},
});
}
if (Platform.OS === "web") {
return React.createElement("iframe", {
src: uri,
style: { flex: 1, border: "none", width: "100%", height: "100%" },
onLoad: (event: { target: { contentWindow?: { location: { href: string } } } }) => {
try {
const url = event.target.contentWindow?.location.href;
if (url?.startsWith(redirectURL)) handleRedirect(url);
} catch {} // eslint-disable-line no-empty -- cross-origin expected
},
});
}

Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (1)
src/components/add-funds/KYC.tsx (1)

71-79: ⚠️ Potential issue | 🟠 Major

Keep the cached KYC tokens when the user backs out.

This clears ["ramp", "kyc-tokens"] before navigation, so returning to the KYC step forces a full restart instead of resuming. The success and error paths already remove the cache entry; the back path should preserve it.

↩️ Proposed fix
             <Pressable
               onPress={() => {
-                queryClient.removeQueries({ queryKey: ["ramp", "kyc-tokens"] });
                 if (router.canGoBack()) {
                   router.back();
                 } else {

Based on learnings, the add-funds KYC flow intentionally keeps these tokens on cancel/back-out paths and only clears them on success or error.

♻️ Duplicate comments (2)
src/components/add-funds/Ramp.tsx (1)

344-394: ⚠️ Potential issue | 🟠 Major

Localize the deposit detail labels.

depositRows() hard-codes the new bank-detail labels in English, so translated builds will still show mixed-language instructions on this screen. Pass these labels through t(), or build the rows where translation is available.

Based on learnings, i18n keys in this repo should use the full user-facing message verbatim.

src/components/add-funds/Onboard.tsx (1)

305-316: 🧹 Nitpick | 🔵 Trivial

Redundant type assertion on event.target.

Line 311 casts event.target to the same type already declared in the onLoad parameter type annotation. This duplication can be removed.

♻️ Proposed simplification
       onLoad: (event: { target: { contentWindow?: { location: { href: string } } } }) => {
         try {
-          const url = (event.target as { contentWindow?: { location: { href: string } } }).contentWindow?.location.href;
+          const url = event.target.contentWindow?.location.href;
           if (url?.startsWith(redirectURL)) handleRedirect(url);
         } catch {} // eslint-disable-line no-empty -- cross-origin expected
       },

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: afae625d-60b7-49c5-b2f6-14e6d97471b4

📥 Commits

Reviewing files that changed from the base of the PR and between 88054cc and 73ac450.

⛔ Files ignored due to path filters (16)
  • src/assets/images/ars-back.svg is excluded by !**/*.svg
  • src/assets/images/ars-usdc.svg is excluded by !**/*.svg
  • src/assets/images/background.svg is excluded by !**/*.svg
  • src/assets/images/brl-back.svg is excluded by !**/*.svg
  • src/assets/images/brl-usdc.svg is excluded by !**/*.svg
  • src/assets/images/euro-back.svg is excluded by !**/*.svg
  • src/assets/images/mxn-back.svg is excluded by !**/*.svg
  • src/assets/images/pounds-back.svg is excluded by !**/*.svg
  • src/assets/images/solana-network.svg is excluded by !**/*.svg
  • src/assets/images/stellar-network.svg is excluded by !**/*.svg
  • src/assets/images/tron-network.svg is excluded by !**/*.svg
  • src/assets/images/usd-back.svg is excluded by !**/*.svg
  • src/assets/images/usd-usdc.svg is excluded by !**/*.svg
  • src/assets/images/usdc-centered.svg is excluded by !**/*.svg
  • src/assets/images/usdc-front.svg is excluded by !**/*.svg
  • src/assets/images/usdt-centered.svg is excluded by !**/*.svg
📒 Files selected for processing (21)
  • .changeset/modern-jeans-write.md
  • src/components/add-funds/AddCrypto.tsx
  • src/components/add-funds/AddFiatButton.tsx
  • src/components/add-funds/AddFunds.tsx
  • src/components/add-funds/AddFundsOption.tsx
  • src/components/add-funds/AddRampButton.tsx
  • src/components/add-funds/BridgeDisclaimer.tsx
  • src/components/add-funds/KYC.tsx
  • src/components/add-funds/MantecaDisclaimer.tsx
  • src/components/add-funds/Onboard.tsx
  • src/components/add-funds/Ramp.tsx
  • src/components/add-funds/Status.tsx
  • src/components/shared/CopyAddressSheet.tsx
  • src/i18n/es.json
  • src/i18n/pt.json
  • src/utils/completeOnboarding.ts
  • src/utils/currencies.ts
  • src/utils/networkLogos.ts
  • src/utils/persona.ts
  • src/utils/queryClient.ts
  • src/utils/server.ts
💤 Files with no reviewable changes (1)
  • src/components/add-funds/AddFiatButton.tsx

Comment on lines +98 to 102
const mantecaOnramp = providers?.manteca.onramp;
const limits = mantecaOnramp && "limits" in mantecaOnramp ? mantecaOnramp.limits : undefined;
const limitCurrency = limits?.monthly.symbol;
const minAmount = quote?.buyRate ? Number(quote.buyRate) : undefined;
const maxAmount = limits?.monthly.available ? Number(limits.monthly.available) : undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't display the exchange rate as the minimum deposit.

Line 101 derives minAmount from quote.buyRate, but Lines 241-249 already use that same field as the exchange rate. The footer will now show the FX rate under the Min label, which is incorrect. Read the minimum from the actual limits/quote field, or hide that label until the API exposes one.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 9 additional findings in Devin Review.

Open in Devin Review

const isCrypto = !!network;
const isBridge = provider === "bridge";

const [acknowledged, setAcknowledged] = useState(true);
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot Mar 13, 2026

Choose a reason for hiding this comment

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

📝 Info: T&C checkbox starts pre-checked in the Fees component

At src/components/add-funds/Fees.tsx:40, acknowledged is initialized to true, meaning the terms checkbox starts checked and the user can immediately proceed. This matches the old behavior in the deleted Onboard.tsx which also initialized acknowledged to true. While this is intentional for UX continuity, it's worth noting this is a pre-acceptance pattern for terms and conditions, which may have legal implications depending on jurisdiction.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

coderabbitai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +309 to +319
if (Platform.OS === "web") {
return React.createElement("iframe", {
src: uri,
style: { flex: 1, border: "none", width: "100%", height: "100%" },
onLoad: (event: { target: { contentWindow?: { location: { href: string } } } }) => {
try {
const url = (event.target as { contentWindow?: { location: { href: string } } }).contentWindow?.location.href;
if (url?.startsWith(redirectURL)) handleRedirect(url);
} catch {} // eslint-disable-line no-empty -- cross-origin expected
},
});
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot Mar 14, 2026

Choose a reason for hiding this comment

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

🚩 TOSView iframe redirect detection relies on same-origin access pattern

The TOSView web implementation at src/components/add-funds/Onboard.tsx:309-319 uses an iframe with an onLoad handler that tries to read contentWindow.location.href. This will throw cross-origin errors for the initial Bridge TOS page (hosted on bridge.xyz). The empty catch block silently handles this. When Bridge redirects to the app's redirectURL (same-origin https://{domain}/add-funds), the onLoad fires again and the same-origin URL is now readable. This pattern works for server-side redirects (HTTP 302) but may fail if Bridge uses client-side JavaScript navigation within the iframe, as onLoad doesn't fire for SPA-style route changes. The native WebView path (onShouldStartLoadWithRequest) is more robust since it intercepts all navigation requests. Worth verifying Bridge's redirect mechanism.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@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: 9

Caution

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

⚠️ Outside diff range comments (2)
src/components/add-funds/KYC.tsx (1)

71-79: ⚠️ Potential issue | 🟡 Minor

Keep kyc-tokens on the back path.

This back handler now clears the cached inquiry/session before the user leaves the screen, so “go back and retry” becomes a full restart instead of a resume.

♻️ Suggested fix
             <Pressable
               onPress={() => {
-                queryClient.removeQueries({ queryKey: ["ramp", "kyc-tokens"] });
                 if (router.canGoBack()) {
                   router.back();
                 } else {

Based on learnings, the ["ramp", "kyc-tokens"] entry should stay cached on KYC cancellation and only be cleared on success or error paths.

src/components/add-funds/Onboard.tsx (1)

235-254: ⚠️ Potential issue | 🟠 Major

Add checkbox semantics to the consent control.

This is a required acknowledgement gate, but the tappable square is only an XStack with no checkbox role or checked state, so screen readers can’t announce or operate it reliably.

♿ Suggested fix
             <XStack
               cursor="pointer"
+              accessibilityRole="checkbox"
+              accessibilityState={{ checked: acknowledged }}
+              accessibilityLabel={t("I accept the Terms and Conditions.")}
               onPress={() => {
                 setAcknowledged(!acknowledged);
               }}
             >
♻️ Duplicate comments (7)
src/components/add-funds/MantecaDisclaimer.tsx (1)

15-15: ⚠️ Potential issue | 🟡 Minor

Locale mapping misses regional Spanish codes.

Line 15 only matches "es" exactly. If i18n.language is es-AR, es-MX, etc., the URL falls back to /en/ unintentionally.

🔧 Proposed fix
-  const locale = language === "es" ? "es" : "en";
+  const locale = language.toLowerCase().startsWith("es") ? "es" : "en";
In i18next/react-i18next, can `i18n.language` be a regional tag like `es-AR` or `es-MX`, and what is the recommended way to branch locale URLs for Spanish?

Also applies to: 41-41

src/components/add-funds/AddFundsOption.tsx (1)

23-31: ⚠️ Potential issue | 🟡 Minor

Expose disabled semantics to accessibility APIs.

This card is visually/functionally disabled, but assistive tech still gets no explicit button role or disabled state. Add those here so the unavailable option is announced correctly.

♿ Suggested fix
     <YStack
+      accessibilityRole="button"
+      accessibilityState={{ disabled: !!disabled }}
       padding="$s4_5"
       backgroundColor="$backgroundSoft"
       borderRadius="$r5"
       borderWidth={1}
src/components/add-funds/AddRampButton.tsx (1)

34-35: ⚠️ Potential issue | 🟠 Major

Preserve pending: "true" on ONBOARDING routes.

Status.tsx only runs its delayed provider recheck when that flag is present. Without it, onboarding users land on the static status screen and never auto-redirect when the provider becomes ACTIVE.

🧭 Suggested fix
       case "ONBOARDING":
-        router.push({ pathname: "/add-funds/status", params: { ...params, status: "ONBOARDING" } });
+        router.push({ pathname: "/add-funds/status", params: { ...params, status: "ONBOARDING", pending: "true" } });
         break;

Based on learnings, src/components/add-funds/Status.tsx intentionally performs the delayed provider activation check only for pending routes.

src/components/add-funds/Onboard.tsx (1)

60-64: ⚠️ Potential issue | 🟡 Minor

Normalize locale prefixes before composing help URLs.

Exact equality on language drops supported regional locales like es-AR, es-MX, and pt-BR to the English article. Match on the prefix instead of the full locale code here.

Also applies to: 160-162

src/components/add-funds/Ramp.tsx (2)

98-102: ⚠️ Potential issue | 🟠 Major

minAmount is still derived from quote.buyRate, which is the exchange rate.

This was flagged in a previous review. Line 101 sets minAmount from buyRate, but Lines 241-249 display that same value as the exchange rate. The footer will show the FX rate under the Min label, which is misleading. Either read the actual minimum from the API limits or remove this display until a proper field is available.


344-394: ⚠️ Potential issue | 🟠 Major

Hard-coded English labels in depositRows() break localization.

This was flagged in a previous review. All row labels ("Beneficiary name", "Account number", "Routing number", etc.) are in English. Localized builds will display mixed-language bank instructions. Route these through t().

src/components/add-funds/AddCrypto.tsx (1)

148-151: 🧹 Nitpick | 🔵 Trivial

Conditional spread pattern for optional props.

This was noted in a prior review as a nitpick. The pattern works correctly—when isBridge && network && depositAddress is truthy, the additional props are spread. While explicit prop assignment would be clearer, this is functionally correct and the component handles missing props gracefully.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: abc7f336-516e-4142-babb-d084b0796d23

📥 Commits

Reviewing files that changed from the base of the PR and between 50d8d41 and 9f069dc.

⛔ Files ignored due to path filters (16)
  • src/assets/images/ars-back.svg is excluded by !**/*.svg
  • src/assets/images/ars-usdc.svg is excluded by !**/*.svg
  • src/assets/images/background.svg is excluded by !**/*.svg
  • src/assets/images/brl-back.svg is excluded by !**/*.svg
  • src/assets/images/brl-usdc.svg is excluded by !**/*.svg
  • src/assets/images/euro-back.svg is excluded by !**/*.svg
  • src/assets/images/mxn-back.svg is excluded by !**/*.svg
  • src/assets/images/pounds-back.svg is excluded by !**/*.svg
  • src/assets/images/solana-network.svg is excluded by !**/*.svg
  • src/assets/images/stellar-network.svg is excluded by !**/*.svg
  • src/assets/images/tron-network.svg is excluded by !**/*.svg
  • src/assets/images/usd-back.svg is excluded by !**/*.svg
  • src/assets/images/usd-usdc.svg is excluded by !**/*.svg
  • src/assets/images/usdc-centered.svg is excluded by !**/*.svg
  • src/assets/images/usdc-front.svg is excluded by !**/*.svg
  • src/assets/images/usdt-centered.svg is excluded by !**/*.svg
📒 Files selected for processing (22)
  • .changeset/modern-jeans-write.md
  • cspell.json
  • src/components/add-funds/AddCrypto.tsx
  • src/components/add-funds/AddFiatButton.tsx
  • src/components/add-funds/AddFunds.tsx
  • src/components/add-funds/AddFundsOption.tsx
  • src/components/add-funds/AddRampButton.tsx
  • src/components/add-funds/BridgeDisclaimer.tsx
  • src/components/add-funds/KYC.tsx
  • src/components/add-funds/MantecaDisclaimer.tsx
  • src/components/add-funds/Onboard.tsx
  • src/components/add-funds/Ramp.tsx
  • src/components/add-funds/Status.tsx
  • src/components/shared/CopyAddressSheet.tsx
  • src/i18n/es.json
  • src/i18n/pt.json
  • src/utils/completeOnboarding.ts
  • src/utils/currencies.ts
  • src/utils/networkLogos.ts
  • src/utils/persona.ts
  • src/utils/queryClient.ts
  • src/utils/server.ts
💤 Files with no reviewable changes (1)
  • src/components/add-funds/AddFiatButton.tsx

Comment on lines +77 to +80
const deposits = data?.depositInfo ?? [];
const quote = data?.quote;
const beneficiaryName = depositInfo && "beneficiaryName" in depositInfo ? depositInfo.beneficiaryName : undefined;
const pixKey = depositInfo?.network === "PIX" ? depositInfo.pixKey : undefined;
const pix = useMemo(() => {
if (!pixKey || !beneficiaryName) return null;
const activeDeposit = deposits.find((d) => d.network === selectedNetwork) ?? deposits.at(0);
const pixDeposit = deposits.find((d) => d.network === "PIX" || d.network === "PIX-BR");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

selectedNetwork state is never reset when deposits change.

If the user switches currencies or providers and deposits changes, selectedNetwork may reference a network that no longer exists in the new array. The fallback deposits.at(0) handles this gracefully, but the stale state remains. Consider resetting selectedNetwork when the query key changes.

Comment on lines +6 to +8
EUR: { name: "Euros", emoji: "🇪🇺" },
GBP: { name: "British Pounds", emoji: "🇬🇧" },
MXN: { name: "Mexican Pesos", emoji: "🇲🇽" },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Global Currency now includes values invalid for Manteca quote paths.

Line 6–8 broadens Currency for all flows, but server quote validation is provider-specific. EUR/GBP/MXN are valid for Bridge and rejected for Manteca, so this weakens compile-time guarantees and can surface as runtime 4xxs.

🛠️ Suggested direction
 export const currencies = {
   ARS: { name: "Argentine Pesos", emoji: "🇦🇷" },
   BRL: { name: "Brazilian Real", emoji: "🇧🇷" },
   EUR: { name: "Euros", emoji: "🇪🇺" },
   GBP: { name: "British Pounds", emoji: "🇬🇧" },
   MXN: { name: "Mexican Pesos", emoji: "🇲🇽" },
   USD: { name: "US Dollars", emoji: "🇺🇸" },
 } as const;

+export const mantecaCurrencies = ["ARS", "BRL", "USD"] as const;
+export type MantecaCurrency = (typeof mantecaCurrencies)[number];
+export const bridgeFiatCurrencies = ["BRL", "EUR", "GBP", "MXN", "USD"] as const;
+export type BridgeFiatCurrency = (typeof bridgeFiatCurrencies)[number];

Then type provider-specific request builders with MantecaCurrency vs BridgeFiatCurrency.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EUR: { name: "Euros", emoji: "🇪🇺" },
GBP: { name: "British Pounds", emoji: "🇬🇧" },
MXN: { name: "Mexican Pesos", emoji: "🇲🇽" },
export const currencies = {
ARS: { name: "Argentine Pesos", emoji: "🇦🇷" },
BRL: { name: "Brazilian Real", emoji: "🇧🇷" },
EUR: { name: "Euros", emoji: "🇪🇺" },
GBP: { name: "British Pounds", emoji: "🇬🇧" },
MXN: { name: "Mexican Pesos", emoji: "🇲🇽" },
USD: { name: "US Dollars", emoji: "🇺🇸" },
} as const;
export const mantecaCurrencies = ["ARS", "BRL", "USD"] as const;
export type MantecaCurrency = (typeof mantecaCurrencies)[number];
export const bridgeFiatCurrencies = ["BRL", "EUR", "GBP", "MXN", "USD"] as const;
export type BridgeFiatCurrency = (typeof bridgeFiatCurrencies)[number];

Comment on lines +1 to +7
const networkLogos: Record<string, string> = {
SOLANA: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png",
STELLAR: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/stellar/info/logo.png",
TRON: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/tron/info/logo.png",
};

export default networkLogos;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Inline the default export to satisfy repository export/extraction rules.

networkLogos is extracted only for a single export, and default export is placed at the bottom. Inline export keeps this file guideline-compliant.

♻️ Proposed refactor
-const networkLogos: Record<string, string> = {
+export default {
   SOLANA: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png",
   STELLAR: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/stellar/info/logo.png",
   TRON: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/tron/info/logo.png",
-};
-
-export default networkLogos;
+} satisfies Record<string, string>;

As per coding guidelines: place the default export at the top, and do not extract single-use values into variables.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const networkLogos: Record<string, string> = {
SOLANA: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png",
STELLAR: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/stellar/info/logo.png",
TRON: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/tron/info/logo.png",
};
export default networkLogos;
export default {
SOLANA: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png",
STELLAR: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/stellar/info/logo.png",
TRON: "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/tron/info/logo.png",
} satisfies Record<string, string>;

Comment on lines +133 to 135
function startRampKYC(type: "bridge" | "manteca", tokens?: { inquiryId: string; sessionToken: string }) {
if (current && !current.controller.signal.aborted && current.type === type && current.tokens === tokens)
return current.promise;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Deduplication check compares tokens by reference.

Line 134 checks current.tokens === tokens using reference equality. If the caller creates a new object with the same inquiryId and sessionToken values, this check will fail and a new inquiry will start. This is likely intentional (tokens from different calls are treated as different flows), but worth noting.

sentry[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

case "WIRE":
return [
{ label: "Beneficiary name", value: deposit.beneficiaryName },
{ label: "Beneficiary address", value: deposit.beneficiaryAddress },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 String literal instead of property access for beneficiary address in ACH/WIRE deposits

In depositRows for ACH/WIRE networks, the beneficiary address value is a string literal "deposit.beneficiaryAddress" instead of the property access deposit.beneficiaryAddress. Every other row in this function and neighboring cases correctly uses property access (e.g., deposit.beneficiaryName, deposit.accountNumber). This causes the UI to display the literal text "deposit.beneficiaryAddress" to users and copy that literal string when they tap the copy button, instead of showing and copying the actual beneficiary address.

Suggested change
{ label: "Beneficiary address", value: deposit.beneficiaryAddress },
{ label: "Beneficiary address", value: deposit.beneficiaryAddress },
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

</View>
}
title={t("{{currency}} from {{network}}", { currency, network })}
subtitle={t("Receive USDC on Optimism")}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Hardcoded "Receive USDC on Optimism" subtitle for crypto bridge options

At src/components/add-funds/AddRampButton.tsx:58, the crypto bridge subtitle is hardcoded as t("Receive USDC on Optimism") regardless of the source currency (could be USDT) or destination. This appears intentional since the bridge converts all incoming crypto to USDC on the Exa wallet's chain (Optimism), but it's worth confirming this is the desired UX — especially if other destination assets become supported in the future. The non-crypto fiat subtitle at line 76 similarly says t("Receive USDC") which is consistent.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const pixKey = depositInfo?.network === "PIX" ? depositInfo.pixKey : undefined;
const pix = useMemo(() => {
if (!pixKey || !beneficiaryName) return null;
const deposit = deposits.at(0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 DepositCard only renders the first deposit, other methods may be missed

In src/components/add-funds/Ramp.tsx:76,160, deposit is set to deposits.at(0) and only this first deposit is rendered in a DepositCard. When Bridge returns multiple deposit methods (e.g., ACH and WIRE for USD), only the first one gets a detail card. The header does show all deposit display names joined with "or" (line 131-133), so the user sees the method names, but can only view/copy details for the first one. This may be intentional as a simplified first implementation, but could confuse users who expect to see details for all listed methods.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

sentry[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

const pixKey = depositInfo?.network === "PIX" ? depositInfo.pixKey : undefined;
const pix = useMemo(() => {
if (!pixKey || !beneficiaryName) return null;
const deposit = deposits.at(0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Only first deposit card rendered when multiple deposit methods exist (USD via bridge has ACH + WIRE)

In Ramp.tsx, deposit = deposits.at(0) extracts only the first element of the depositInfo array, and only one DepositCard is rendered at line 160. For USD via bridge, the server returns two deposit details — ACH and WIRE (server/utils/ramps/bridge.ts:895-918). The title bar at lines 130-134 correctly joins all display names (deposits.map((d) => d.displayName).join(...)) showing "USD via ACH or WIRE", but only the ACH deposit card is actually rendered. Users see a title implying both methods are available but can only view ACH account details. The WIRE deposit details (which have a different estimatedProcessingTime of 300 seconds vs "1 - 3 business days") are completely inaccessible.

Prompt for agents
In src/components/add-funds/Ramp.tsx, line 76 extracts only the first deposit with `const deposit = deposits.at(0)`, and line 160 renders a single DepositCard. For currencies with multiple deposit methods (USD via bridge returns both ACH and WIRE), all deposit cards should be rendered.

1. Remove the single `deposit` variable at line 76 (keep `deposits` which is already correct).
2. At line 160, replace the single DepositCard rendering with a map over all deposits:
   Replace `{deposit && <DepositCard copyToClipboard={copyToClipboard} deposit={deposit} isLoading={isPending} />}` with `{deposits.map((d) => <DepositCard key={d.network} copyToClipboard={copyToClipboard} deposit={d} isLoading={isPending} />)}`
3. Update any other references to the single `deposit` variable (lines 249 for fee display) to iterate over all deposits or pick appropriately.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

case "WIRE":
return [
{ label: "Beneficiary name", value: deposit.beneficiaryName },
{ label: "Beneficiary address", value: deposit.beneficiaryAddress },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 depositRows for ACH/WIRE references beneficiaryAddress which may not exist in the server response

The depositRows function at src/components/add-funds/Ramp.tsx:365 includes { label: "Beneficiary address", value: deposit.beneficiaryAddress } for ACH and WIRE cases. However, the server's DepositDetails schema for ACH/WIRE (server/api/ramp.ts:271-291) does not include a beneficiaryAddress field — only beneficiaryName, routingNumber, accountNumber, bankName, and bankAddress. The bridge API response does have bank_beneficiary_address (server/utils/ramps/bridge.ts:797), but the server's getDepositDetailsFromVirtualAccount function (server/utils/ramps/bridge.ts:899) maps it to... nothing. TypeScript's discriminated union narrowing should catch this as a compile error (the narrowed type for ACH doesn't have beneficiaryAddress). If it somehow passes compilation, the row would show a perpetual loading Skeleton. Either the server should expose this field in the DepositDetails schema, or the client should remove this row.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@cruzdanilo cruzdanilo force-pushed the i18n branch 3 times, most recently from 3bacc54 to 9ece904 Compare March 27, 2026 13:25
@franm91 franm91 marked this pull request as draft March 27, 2026 18:05
sentry[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 4 new potential issues.

View 12 additional findings in Devin Review.

Open in Devin Review

Comment on lines +12 to +18
export const bridgeMethods: Partial<Record<Currency, string>> = {
USD: "ACH or WIRE",
EUR: "SEPA",
GBP: "FPS",
MXN: "SPEI",
BRL: "PIX",
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 BRL/PIX fees missing from bridge fee table

In src/utils/currencies.ts:17, bridgeMethods maps BRL to "PIX". However, rampFees.bridge at src/utils/currencies.ts:34-63 has no PIX key (only ACH, WIRE, SEPA, SPEI, FPS). This means bridgeMethod("BRL") in src/components/add-funds/Fees.tsx:308-313 returns undefined, and the rows() function returns an empty array for BRL/bridge users. The fees screen would display no fee information for this currency. This may be intentional if PIX fees are handled differently, but if BRL is expected to show fee rows like other bridge currencies, a PIX entry needs to be added to rampFees.bridge.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +315 to +319
function bridgeMethod(currency: Currency) {
if (currency === "USD") return "ACH" as const;
const method = bridgeMethods[currency];
if (!method || !(method in rampFees.bridge)) return;
return method as keyof typeof rampFees.bridge;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The rampFees.bridge object is missing an entry for PIX, causing the fees page to show no fee information for BRL Bridge deposits.
Severity: MEDIUM

Suggested Fix

Add the missing PIX fee definition to the rampFees.bridge object in currencies.ts. This will ensure that the fee information is correctly looked up and displayed for BRL Bridge deposits.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/components/add-funds/Fees.tsx#L315-L319

Potential issue: When a user selects the Bridge provider with BRL currency, the
`bridgeMethod` function in `Fees.tsx` correctly identifies the payment method as
`"PIX"`. However, the `rampFees.bridge` object in `currencies.ts` is missing a `PIX`
key. This causes the lookup to fail and the `bridgeMethod` function to return
`undefined`. As a result, the fees page is rendered without any fee information for this
supported payment flow, making the UI appear broken to the user.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 4 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +28 to +63
export const fees = {
manteca: {
transfer: {
fee: "0%-2%",
},
},
bridge: {
ACH: {
fee: "$0.5 + 0.2%",
creation: "$1",
maintenance: "$2",
},
WIRE: {
fee: "$10",
creation: "$1",
maintenance: "$2",
},
SEPA: {
// cspell:ignore sepa
fee: "0.35% + mid-market rate",
creation: "$2",
maintenance: "$2",
},

SPEI: {
// cspell:ignore spei
fee: "0.5% + mid-market rate",
creation: "~$1.5 (30 MXN)",
maintenance: "~$1.5 (30 MXN)",
},
FPS: {
fee: "0.5% + mid-market rate",
creation: "~$2 (1.5 GBP)",
maintenance: "~$2 (1.5 GBP)",
},
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Missing PIX entry in rampFees.bridge causes empty fee rows for BRL bridge users

bridgeMethods["BRL"] is "PIX" (src/utils/currencies.ts:17), but rampFees.bridge only contains keys ACH, WIRE, SEPA, SPEI, FPS — there is no PIX entry. In the bridgeMethod function at src/components/add-funds/Fees.tsx:315-320, when currency === "BRL", the lookup bridgeMethods["BRL"] returns "PIX", and then !("PIX" in rampFees.bridge) is true, so the function returns undefined. This causes rows() at line 301 to return an empty array, resulting in the Fees screen showing zero fee rows for BRL bridge users.

Prompt for agents
Add a PIX entry to the rampFees.bridge object in src/utils/currencies.ts (around line 57-62), with the same structure as other entries (fee, creation, maintenance). For example:

    PIX: {
      fee: "<appropriate fee>",
      creation: "<appropriate creation fee>",
      maintenance: "<appropriate maintenance fee>",
    },

The actual fee values need to come from the Bridge provider's fee schedule for PIX transfers.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +220 to +231
{typedProvider === "bridge" && (
<XStack gap="$s3" alignItems="center">
<View flexShrink={0} alignItems="center">
<Banknote size={16} color="$uiNeutralPrimary" />
</View>
<Text secondary caption2 color="$uiNeutralPlaceholder" flex={1}>
{t(
"We cover incoming transfers to your Bridge accounts in Exa App up to $3,000 or 60 transactions per month. Fees apply after that. {{method}}: {{fee}}.",
{ fee: bridgeFee, method: deposit?.displayName },
)}
</Text>
</XStack>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Bridge fee info text renders with empty method and fee during loading

In Ramp.tsx lines 220-231, the bridge fee info section is rendered whenever typedProvider === "bridge", regardless of whether deposit data has loaded. When the quote query is still pending (isPending is true), deposit is undefined (from deposits.at(0) on an empty array at line 76), and consequently both bridgeFee and deposit?.displayName are undefined. The i18n interpolation renders these as empty strings, producing broken text like: "...Fees apply after that. : ." with a dangling colon and period. This section should be conditionally hidden while loading or when deposit is undefined.

Suggested change
{typedProvider === "bridge" && (
<XStack gap="$s3" alignItems="center">
<View flexShrink={0} alignItems="center">
<Banknote size={16} color="$uiNeutralPrimary" />
</View>
<Text secondary caption2 color="$uiNeutralPlaceholder" flex={1}>
{t(
"We cover incoming transfers to your Bridge accounts in Exa App up to $3,000 or 60 transactions per month. Fees apply after that. {{method}}: {{fee}}.",
{ fee: bridgeFee, method: deposit?.displayName },
)}
</Text>
</XStack>
{typedProvider === "bridge" && deposit && (
<XStack gap="$s3" alignItems="center">
<View flexShrink={0} alignItems="center">
<Banknote size={16} color="$uiNeutralPrimary" />
</View>
<Text secondary caption2 color="$uiNeutralPlaceholder" flex={1}>
{t(
"We cover incoming transfers to your Bridge accounts in Exa App up to $3,000 or 60 transactions per month. Fees apply after that. {{method}}: {{fee}}.",
{ fee: bridgeFee, method: deposit?.displayName },
)}
</Text>
</XStack>
)}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

</Text>
)}
</YStack>
{deposit && <DepositCard copyToClipboard={copyToClipboard} deposit={deposit} isLoading={isPending} />}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Only the first deposit card is rendered despite header referencing all deposit methods

In Ramp.tsx:76, only the first deposit is selected via deposits.at(0), and only one DepositCard is rendered at line 164. However, the header title at lines 134-138 joins all deposit display names (e.g., "USD via ACH or WIRE"). For bridge providers that may return multiple deposit methods (like USD with both ACH and WIRE), the user sees a title referencing both methods but only sees account details for one. The old manteca-only code also only rendered one deposit, but bridge may have a different API contract with multiple deposit infos per currency.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const isBridge = provider === "bridge" && !!currency && !!network;

const { data } = useQuery({
queryKey: ["ramp", "quote", "bridge", currency, network],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Query key inconsistency between AddCrypto and Ramp for bridge quotes

In AddCrypto.tsx:43, the query key for bridge quotes is ["ramp", "quote", "bridge", currency, network], using the literal string "bridge" as the provider. In Ramp.tsx:54, the key is ["ramp", "quote", typedProvider, typedCurrency] where typedProvider is the validated provider value. These two components use different query key structures for potentially the same data, which means they won't share cache entries. This is likely intentional since AddCrypto includes network in the key (crypto deposits) while Ramp doesn't (fiat deposits), but worth confirming there's no cache staleness issue if both screens could display the same provider+currency combo.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03427ee36a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +77 to +79
const bridgeFee =
typedProvider === "bridge" && deposit && deposit.displayName in rampFees.bridge
? rampFees.bridge[deposit.displayName as keyof typeof rampFees.bridge].fee
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize bridge method key before looking up fees

The bridge fee banner derives bridgeFee from deposit.displayName, but several supported Bridge deposits use display names that are not keys in rampFees.bridge (for example, Faster Payments and PIX BR from server/utils/ramps/bridge.ts). In those cases this expression resolves to undefined, so the user-facing fee message renders without a valid fee value for GBP/BRL flows. Use a normalized key (e.g., based on deposit.network) instead of displayName for fee lookup.

Useful? React with 👍 / 👎.

Comment on lines +317 to +319
const method = bridgeMethods[currency];
if (!method || !(method in rampFees.bridge)) return;
return method as keyof typeof rampFees.bridge;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep bridge method and fee tables consistent for BRL

bridgeMethod() rejects methods that are not present in rampFees.bridge. With the new config, bridgeMethods.BRL is "PIX", but there is no corresponding PIX entry in rampFees.bridge, so BRL returns undefined here and rows() returns an empty list. That leaves the fees screen without fee rows for BRL onboarding even though BRL is advertised as supported.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants