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

changeset-bot bot commented Mar 13, 2026

🦋 Changeset detected

Latest commit: 41fd673

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

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
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting to disable the changed files summary in the walkthrough.

@gemini-code-assist
Copy link

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

@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 },

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

sentry bot commented Mar 13, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
602 1 601 1
View the top 1 failed test(s) by shortest run time
local::local
Stack Traces | 405s run time
No visible element found: "Credit limit info"

To view more test analytics, go to the Prevent Tests Dashboard

sentry[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link

@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

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

@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

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

@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);

Choose a reason for hiding this comment

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

🚩 Acknowledged checkbox defaults to true (pre-checked)

At src/components/add-funds/Onboard.tsx:77, acknowledged is initialized to true, meaning the terms checkbox starts pre-checked. This was present in the old code too (same useState(true)), so it's not a regression. Whether this is appropriate UX for a terms acceptance flow (especially for the new Bridge provider) is a product decision, but regulators sometimes require affirmative opt-in (unchecked by default). Worth flagging for product review.

Open in Devin Review

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

Copy link

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

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

100-103: ⚠️ Potential issue | 🟠 Major

Do not derive “Min” amount from exchange rate.

Line 101 sets minAmount from quote.buyRate, but buyRate is already displayed as exchange rate (Line 248). This shows incorrect data under the Min label.


344-393: ⚠️ Potential issue | 🟠 Major

Localize deposit-row labels instead of hard-coded English text.

depositRows() returns raw English labels (for example, “Beneficiary name”, “Account number”), so localized builds will show mixed-language account instructions.

Based on learnings, i18n keys should use full-sentence strings and avoid non-localized descriptive keys/text in UI.

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

34-36: ⚠️ Potential issue | 🟠 Major

Preserve pending: "true" for ONBOARDING navigation.

Line 35 drops the pending flag, so the status screen treats the flow as non-pending and skips the delayed activation re-check.

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

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

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

309-320: 🧹 Nitpick | 🔵 Trivial

Redundant type assertion on event.target.

Line 315 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
       },
src/components/add-funds/AddCrypto.tsx (1)

46-51: 🧹 Nitpick | 🔵 Trivial

Type assertion bypasses compile-time safety.

Line 48 uses as Parameters<typeof getRampQuote>[0] to force-cast the object. Since the query is only enabled when isBridge is true (which requires currency and network to be defined), this is safe at runtime. However, if the isBridge condition logic changes, this could introduce silent failures.

Consider adding explicit type narrowing or a type guard to make the relationship clearer.

♻️ Alternative with explicit narrowing
   const { data } = useQuery({
     queryKey: ["ramp", "quote", "bridge", currency, network],
-    queryFn: () => getRampQuote({ provider: "bridge", currency, network } as Parameters<typeof getRampQuote>[0]),
+    queryFn: () => {
+      if (!currency || !network) throw new Error("missing currency or network");
+      return getRampQuote({ provider: "bridge", currency, network });
+    },
     enabled: isBridge,
     staleTime: 10_000,
   });

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9a0e244-6dfe-4abb-abbb-e34bf29cc395

📥 Commits

Reviewing files that changed from the base of the PR and between 73ac450 and 4b28723.

⛔ 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 +12 to +15
const {
i18n: { language },
} = useTranslation();
const locale = language === "es" ? "es" : "en";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/add-funds/MantecaDisclaimer.tsx | head -50

Repository: exactly/exa

Length of output: 2230


🌐 Web query:

react-i18next i18n.language locale format codes

💡 Result:

react-i18next just exposes the underlying i18next instance, so i18n.language is whatever language code string i18next has currently selected/detected (e.g. "en", "en-US", "pt-BR", "zh-Hant-HK"). i18next calls these values “language codes”. [1][2]

What format should those codes be?

  • You can use any format, but i18next strongly recommends BCP 47 language tags if you want built-in features (plural rules, fallback resolution) to work as intended:
    lng-(script)-REGION-(extensions) like en, en-US, en-GB, zh-Hant-HK. [1]
  • When your language code contains a dash (-), i18next will try to format it using Intl.getCanonicalLocales (i.e., canonical BCP 47 normalization). [1]

Related i18next options that affect the language string you’ll see in i18n.language

  • load: controls which variants i18next will look up when the current language is specific (e.g. en-US):
    • 'all' → tries ['en-US', 'en', 'dev']
    • 'currentOnly' → only en-US
    • 'languageOnly' → only en [3]
  • lowerCaseLng: if enabled, lowercases the full locale (e.g. en-USen-us). [3]
  • cleanCode: if enabled, lowercases just the main language (e.g. ENen) while keeping region/script casing for full tags (e.g. en-US). [3]

Practical tip (using i18n.language as an Intl “locale”)

If you pass i18n.language to things like Intl.DateTimeFormat, prefer keeping it BCP-47-ish (hyphenated, not underscored) and don’t enable lowerCaseLng unless you really want all-lowercase tags. [1][3]

Sources:
[1] i18next FAQ – language code formatting (BCP 47, canonicalization)
[2] i18next “Translation Resolution” – examples of language codes (en-US, pt-BR, etc.)
[3] i18next Configuration Options – load, lowerCaseLng, cleanCode


Handle regional Spanish locales in locale mapping.

Line 15 only matches "es" exactly. i18n.language can include regional values (e.g., es-AR, es-MX, es-CO), which would incorrectly fall back to English terms.

🔧 Proposed fix
-  const locale = language === "es" ? "es" : "en";
+  const locale = language.toLowerCase().startsWith("es") ? "es" : "en";
📝 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 {
i18n: { language },
} = useTranslation();
const locale = language === "es" ? "es" : "en";
const {
i18n: { language },
} = useTranslation();
const locale = language.toLowerCase().startsWith("es") ? "es" : "en";

Copy link

@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

@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.

Copy link

@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

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

101-101: ⚠️ Potential issue | 🟠 Major

Don’t derive “Min” from the exchange rate.

Line 101 assigns minAmount from quote.buyRate, while Lines 241-249 already treat buyRate as FX. That makes the footer show exchange rate values under the Min label.

Also applies to: 207-213, 241-249


344-394: ⚠️ Potential issue | 🟠 Major

Localize deposit detail labels before rendering.

Lines 344-394 hard-code labels in English, and DetailRow renders them as-is, so localized screens still show mixed-language account details.

Based on learnings, i18n in this repository should use user-facing message keys consistently, and these labels already have translated entries.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 23cced28-30d9-44ff-ae64-099d05d611d6

📥 Commits

Reviewing files that changed from the base of the PR and between 4b28723 and 50d8d41.

⛔ 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 41 to 58
const { mutateAsync: handleContinue, isPending } = useMutation({
mutationKey: ["kyc", "complete", "manteca"],
mutationKey: ["kyc", "complete", provider],
async mutationFn() {
if (!currency) return;
const result = await startMantecaKYC(invalidLegalId);
const result = isAddress ? await startAddressKYC(storedTokens) : await startMantecaKYC(storedTokens);
if (result.status === "cancel") return;
if (result.status !== "complete") {
queryClient.removeQueries({ queryKey: ["ramp", "invalid-legal-id"] });
router.replace({ pathname: "/add-funds/status", params: { status: "error", currency } });
queryClient.removeQueries({ queryKey: ["ramp", "kyc-tokens"] });
router.replace({
pathname: "/add-funds/status",
params: { status: "error", currency, provider, network },
});
return;
}
queryClient.removeQueries({ queryKey: ["ramp", "invalid-legal-id"] });
await completeOnboarding(router, currency);
queryClient.removeQueries({ queryKey: ["ramp", "kyc-tokens"] });
await completeOnboarding(router, currency, isBridge ? "bridge" : "manteca", acceptedTermsId, network);
},
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Mutation key may contain undefined provider.

The mutation key ["kyc", "complete", provider] can include undefined when provider is not in the URL params. While this works functionally, it may cause unintended cache collisions with queries that also use undefined in keys.

♻️ Consider defaulting provider in the key
   const { mutateAsync: handleContinue, isPending } = useMutation({
-    mutationKey: ["kyc", "complete", provider],
+    mutationKey: ["kyc", "complete", provider ?? "manteca"],

t,
i18n: { language },
} = useTranslation();
const locale = language === "es" ? "es" : "en";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle regional Spanish locales in locale mapping.

Same issue as MantecaDisclaimer.tsx: exact match on "es" will miss regional variants like es-AR, es-MX.

🐛 Proposed fix
-  const locale = language === "es" ? "es" : "en";
+  const locale = language.toLowerCase().startsWith("es") ? "es" : "en";
📝 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 locale = language === "es" ? "es" : "en";
const locale = language.toLowerCase().startsWith("es") ? "es" : "en";

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link

@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

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 +37 to +38
const displayAddress = overrideAddress ?? accountAddress;
const displayNetwork = network ?? chain.name;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Inline single-use display aliases to match repository style rules.

displayAddress and displayNetwork are only used once each, so keep them inline.

♻️ Proposed refactor
-  const displayAddress = overrideAddress ?? accountAddress;
-  const displayNetwork = network ?? chain.name;
   const displayAssets = assets ?? supportedAssets;
@@
-              {displayAddress}
+              {overrideAddress ?? accountAddress}
@@
-                    {displayNetwork}
+                    {network ?? chain.name}

As per coding guidelines: do not extract a value into a variable unless it is used in two or more places.

Also applies to: 65-65, 90-90

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

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

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

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

@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 },

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")}

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);

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.

Comment on lines +362 to +368
case "WIRE":
return [
{ label: "Beneficiary name", value: deposit.beneficiaryName },
{ label: "Beneficiary address", value: deposit.beneficiaryAddress },
{ label: "Account number", value: deposit.accountNumber },
{ label: "Routing number", value: deposit.routingNumber },
{ label: "Bank name", value: deposit.bankName },
Copy link

Choose a reason for hiding this comment

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

Bug: The client code accesses deposit.beneficiaryAddress for ACH/WIRE deposits, but the server API does not return this field, causing it to be undefined and hidden in the UI.
Severity: HIGH

Suggested Fix

Update the Zod schemas for ACH and WIRE deposits in server/api/ramp.ts to include the beneficiaryAddress field. Then, in server/utils/ramps/bridge.ts, map the virtualAccount.source_deposit_instructions.bank_beneficiary_address to the beneficiaryAddress field in the returned deposit objects.

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/Ramp.tsx#L362-L368

Potential issue: The server API for ACH and WIRE deposits does not include the
`beneficiaryAddress` field in its response. The server schema in `server/api/ramp.ts`
omits this field, and the `getDepositDetailsFromVirtualAccount` function in
`server/utils/ramps/bridge.ts` fails to map the `bank_beneficiary_address` from the
underlying Bridge API into the response object. Consequently, when the client component
`Ramp.tsx` attempts to display `deposit.beneficiaryAddress`, the value is `undefined`.
This results in the "Beneficiary address" row being empty for users, hiding important
information required for them to complete ACH or WIRE transfers.

Copy link

@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);

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 },

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.

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