Skip to content

feat: Integrate injected wallet support in PhoneVerificationModal and ProfileView components - #623

Merged
chibie merged 4 commits into
mainfrom
fix/injected-mode-not-equal-privy
Jul 24, 2026
Merged

feat: Integrate injected wallet support in PhoneVerificationModal and ProfileView components#623
chibie merged 4 commits into
mainfrom
fix/injected-mode-not-equal-privy

Conversation

@Dprof-in-tech

@Dprof-in-tech Dprof-in-tech commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Description

  • Added useInjectedWallet context to both PhoneVerificationModal and ProfileView components to handle wallet address selection.
  • Updated logic to use the injected wallet address when in injected mode, ensuring a seamless user experience across wallet types.
  • Enhanced code clarity by differentiating between embedded and injected wallet addresses in both components.

This pull request adds comprehensive support for "injected wallet" authentication across the app, enabling users to authenticate and perform actions using an injected wallet (e.g., MetaMask) instead of only Privy-embedded wallets. The main changes involve updating API calls and UI logic to conditionally use an injected wallet's address and custom headers for authentication, rather than always requiring a Privy access token. This improves compatibility and user experience for users with browser wallets.

Injected Wallet Authentication Support:

  • Updated API functions (saveTransaction, updateBridgeTransactionStatus, submitSmileIDData) in app/api/aggregator.ts to accept an isInjectedWallet parameter and set the appropriate headers (x-injected-wallet or Authorization) based on wallet type. [1] [2] [3] [4]
  • Modified KYC and phone verification flows in KycModal.tsx and PhoneVerificationModal.tsx to allow authentication with injected wallets by handling cases where the Privy access token is not required, and setting headers accordingly. [1] [2] [3] [4] [5] [6] [7] [8]

UI & Context Integration:

  • Updated components (ProfileView.tsx, WalletDetails.tsx, BridgeForm.tsx) to use the injected wallet context, ensuring the correct wallet address and authentication method are used throughout the UI and when making API calls. [1] [2] [3] [4] [5] [6] [7] [8] [9]

Bridge and Transaction Handling:

  • Refactored bridge and transaction logic to support both embedded and injected wallets by creating a wallet adapter and passing the correct wallet and authentication details to hooks and API calls. [1] [2] [3]

Conditional UI Elements:

  • Adjusted UI elements such as referral CTAs and bridge buttons to be hidden or shown based on whether the user is in embedded or injected wallet mode, improving user experience and preventing unsupported actions. [1] [2]

These changes collectively enable seamless operation for users with injected wallets, improving flexibility and compatibility across the app.

References

Testing

  • This change adds test coverage for new/changed/fixed functionality

Checklist

  • I have added documentation and tests for new/changed functionality in this PR
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not main

By submitting a PR, I agree to Paycrest's Contributor Code of Conduct and Contribution Guide.

Summary by CodeRabbit

  • New Features
    • Added injected/bridge wallet support across phone verification, KYC (SmileID), and bridge quote/status/execute flows using injected wallet addressing and headers.
  • Bug Fixes
    • Improved OTP, KYC, and bridge API handling when no bearer token is present for injected wallets.
    • Prevented KYC requests until injected wallet info is ready.
  • UI / Flow Updates
    • Updated wallet actions and mobile wallet UI: Convert can appear for injected wallets; Earn is hidden; referrals CTA is hidden in embed mode.
  • Network Updates
    • Removed Scroll chain support from supported networks, tokens fallback, and related configuration.

… ProfileView components

- Added `useInjectedWallet` context to both PhoneVerificationModal and ProfileView components to handle wallet address selection.
- Updated logic to use the injected wallet address when in injected mode, ensuring a seamless user experience across wallet types.
- Enhanced code clarity by differentiating between embedded and injected wallet addresses in both components.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3595c40-f088-4178-b519-616a8fca4ff8

📥 Commits

Reviewing files that changed from the base of the PR and between da57b22 and ef00e4a.

📒 Files selected for processing (2)
  • app/components/WalletDetails.tsx
  • app/components/wallet-mobile-modal/WalletView.tsx

📝 Walkthrough

Walkthrough

Injected-wallet authentication now flows through OTP, KYC, bridge quoting, execution, and status tracking. Wallet UI selects injected addresses where available, while Scroll is removed from chain configuration, metadata, mocks, and fallback tokens.

Changes

Injected wallet support

Layer / File(s) Summary
Injected authentication contracts
middleware.ts, app/lib/bridge.ts, app/api/aggregator.ts
Requests accept injected-wallet or bearer authentication, with nullable access-token API signatures.
OTP and KYC authentication flows
app/components/PhoneVerificationModal.tsx, app/components/KycModal.tsx, app/context/KYCContext.tsx
OTP, SmileID, Tier 3, transaction-summary, and KYC-status requests construct authentication headers for injected or Privy wallets.
Injected bridge quoting and execution
app/components/bridge/BridgeForm.tsx, app/hooks/bridge.ts
Bridge quotes and status calls receive injected addresses, while injected providers switch chains and send bridge transactions.
Bridge status and wallet presentation
app/hooks/useBridgeStatusTracker.ts, app/components/ProfileView.tsx, app/components/WalletDetails.tsx, app/components/wallet-mobile-modal/WalletView.tsx
Status updates use injected authentication, profile addresses prefer ready injected wallets, and wallet actions reflect injected and embed state.

Scroll support removal

Layer / File(s) Summary
Scroll chain configuration removal
app/lib/bundler/chains.ts, app/lib/privy-config.ts, app/lib/bridge.ts
Scroll is removed from supported chain mappings, Privy configuration, RPC overrides, and NEAR chain mapping.
Scroll network data removal
app/mocks.ts, app/utils.ts, app/context/BalanceContext.tsx
Scroll mock network data and fallback tokens are removed, and migration documentation is updated.
Scroll metadata removal
app/layout.tsx
Scroll is removed from metadata keywords and structured supported-network data.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WalletUser
  participant BridgeForm
  participant BridgeHooks
  participant InjectedProvider
  participant BridgeAPI
  WalletUser->>BridgeForm: Connect injected wallet
  BridgeForm->>BridgeAPI: Request quote with injected address
  BridgeAPI->>BridgeForm: Return bridge quote
  BridgeForm->>BridgeHooks: Execute injected bridge flow
  BridgeHooks->>InjectedProvider: Switch chain and submit transactions
  InjectedProvider->>BridgeHooks: Return transaction receipts
  BridgeHooks->>BridgeAPI: Poll bridge status with injected authentication
  BridgeAPI->>BridgeForm: Return updated status
Loading

Possibly related PRs

Suggested reviewers: chibie, 5ran6

Poem

A rabbit hops through wallet gates,
Injected paths and bridge updates.
OTPs and swaps now know the way,
While Scroll steps softly out today.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding injected wallet support to key UI components.
Description check ✅ Passed The description covers the PR purpose, implementation details, and API/auth impacts, but the References and Testing sections are largely unfilled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/components/PhoneVerificationModal.tsx`:
- Around line 96-103: Update the walletAddress selection near isInjectedWallet
so injected mode does not fall back to embeddedWallet when injectedAddress is
unavailable or the injected account is disconnected. Use the
InjectedWalletContext readiness/connection state to fail closed or apply the
established consistent fallback, while preserving embeddedWallet usage only for
non-injected mode.
- Around line 96-103: The wallet readiness policy is inconsistent between
PhoneVerificationModal and ProfileView when injected mode is enabled without an
injectedAddress. Update walletAddress selection in
app/components/PhoneVerificationModal.tsx at lines 96-103 and the corresponding
address handling in app/components/ProfileView.tsx at lines 47-58 to use the
same approved policy: require a ready injected address, otherwise consistently
preserve kycWalletAddress or hide/block the address; do not silently fall back
in only one component.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 348ac598-1373-4fda-ac82-331b8c84f2cc

📥 Commits

Reviewing files that changed from the base of the PR and between e0e0a47 and cd0648d.

📒 Files selected for processing (2)
  • app/components/PhoneVerificationModal.tsx
  • app/components/ProfileView.tsx

Comment thread app/components/PhoneVerificationModal.tsx Outdated
- Updated authorizationMiddleware to support both Privy JWT and injected wallet authentication, improving error handling and analytics tracking.
- Removed references to the Scroll chain across multiple components, including layout, mocks, and utils, to streamline the codebase.
- Enhanced KycModal, PhoneVerificationModal, and BridgeForm components to handle injected wallet logic, ensuring seamless user experience across wallet types.
- Refactored bridge-related hooks and API calls to accommodate injected wallet authentication, improving overall functionality and maintainability.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (3)
app/components/PhoneVerificationModal.tsx (2)

204-260: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

handlePhoneSubmit's deps omit isInjectedWallet/injectedAddress.

Both are read inside the callback (lines 215, 227-229) but missing from the dependency array (line 260). injectedAddress changing without isInjectedWallet toggling (e.g. the injected wallet switches account) would not recreate the callback, sending a stale address in x-injected-wallet.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/PhoneVerificationModal.tsx` around lines 204 - 260, Update the
dependency array of handlePhoneSubmit to include both isInjectedWallet and
injectedAddress, ensuring the callback uses the current injected-wallet state
and address when sending the OTP request.

262-307: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

OTP verification and resend are unreachable for injected wallets.

resolveAccessToken() intentionally returns null for injected wallets (lines 90-92), but handleOtpSubmit and handleResendOtp were not updated to match — they still do if (!accessToken) { return; } and only ever send Authorization: Bearer …. For injected-wallet users this means every OTP submit/resend call silently no-ops: they can request an OTP via handlePhoneSubmit (which was correctly updated) but can never complete verification, since these two handlers always bail out before hitting fetch.

Apply the same pattern used in handlePhoneSubmit: allow accessToken to be null when isInjectedWallet, and add the x-injected-wallet header.

🐛 Proposed fix
   const handleOtpSubmit = useCallback(async () => {
     if (!otpCode.trim() || otpCode.length !== 6) {
       toast.error("Please enter the 6-digit OTP code");
       return;
     }

     const accessToken = await resolveAccessToken();
-    if (!accessToken) {
+    if (!accessToken && !isInjectedWallet) {
       return;
     }

     setIsLoading(true);

     try {
+      const headers: Record<string, string> = { "Content-Type": "application/json" };
+      if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
+      if (isInjectedWallet && injectedAddress) headers["x-injected-wallet"] = injectedAddress;
       const response = await fetch("/api/phone/verify-otp", {
         method: "POST",
-        headers: {
-          "Content-Type": "application/json",
-          Authorization: `Bearer ${accessToken}`,
-        },
+        headers,
         body: JSON.stringify({ phoneNumber: formattedPhone, otpCode }),
       });

The same change applies to handleResendOtp.

Also applies to: 309-344

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/PhoneVerificationModal.tsx` around lines 262 - 307, Update
handleOtpSubmit and handleResendOtp to support injected wallets like
handlePhoneSubmit: permit a null accessToken when isInjectedWallet is true,
still require a token otherwise, and include the x-injected-wallet header in
their fetch requests for injected-wallet flows while preserving the existing
Authorization header behavior.
app/components/KycModal.tsx (1)

120-129: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Missing injectedReady gate — inconsistent with the fix applied to sibling components.

PhoneVerificationModal.tsx and ProfileView.tsx were fixed (per past review) to require injectedReady before treating injectedAddress as valid. This component doesn't destructure or check injectedReady at all, so walletAddress (and everything downstream: SmileID capture context, Tier 3 upload headers, fetchStatus) can use an injected address before the wallet is actually ready.

🛡️ Suggested fix
-  const { isInjectedWallet, injectedAddress } = useInjectedWallet();
+  const { isInjectedWallet, injectedAddress, injectedReady } = useInjectedWallet();

   const embeddedWallet = wallets.find(
     (wallet) => wallet.walletClientType === "privy",
   );
   const walletAddress = isInjectedWallet
-    ? injectedAddress
+    ? (injectedReady ? injectedAddress : null)
     : embeddedWallet?.address;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/KycModal.tsx` around lines 120 - 129, Update the
useInjectedWallet destructuring and walletAddress selection in KycModal so
injectedAddress is used only when injectedReady is true; otherwise preserve the
embedded wallet fallback behavior. Ensure all downstream consumers of
walletAddress, including SmileID context, Tier 3 upload headers, and
fetchStatus, receive only a ready injected address.
🧹 Nitpick comments (1)
app/components/WalletDetails.tsx (1)

537-579: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant !isInjectedWallet check on the Convert button.

Line 565 adds && !isInjectedWallet, but this button is already inside the {!isInjectedWallet && (...)} block opened at line 537 — the added check is dead code and can't change behavior. Separately, since the whole Fund/Transfer/Convert row (and the modals rendered at line 906, including BridgeForm) is gated out for injected wallets here, worth confirming the new injected-wallet bridge quoting/execution work added to BridgeForm.tsx/app/hooks/bridge.ts is actually reachable from some other UI entry point.

🧹 Suggested cleanup
-                            {isBridgeUiVisible() && !isInjectedWallet && (
+                            {isBridgeUiVisible() && (
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/WalletDetails.tsx` around lines 537 - 579, Remove the
redundant !isInjectedWallet condition from the Convert button’s
isBridgeUiVisible() guard in WalletDetails, since it is already nested inside
the outer !isInjectedWallet block. Separately verify that injected-wallet bridge
quoting and execution remain reachable through an appropriate UI entry point
outside this gated Fund/Transfer/Convert row, including BridgeForm usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/context/KYCContext.tsx`:
- Around line 104-121: The KYC fetch flow in KYCProvider must wait for the
injected wallet context’s injectedReady flag before using walletAddress or
requesting KYC status and transaction-summary data. Include injectedReady from
useInjectedWallet and gate the relevant refresh effects/requests, while
preserving the existing Privy authentication path and normal behavior once the
wallet is ready.

In `@app/mocks.ts`:
- Around line 158-167: Update the documentation comment above
migrationChecklistNetworks to accurately describe that the checklist excludes
Celo, Starknet, and Tron, or use a generic description that does not enumerate
exclusions. Keep the filtering logic and MIGRATION_EXCLUDED_CHAIN_IDS unchanged.

In `@middleware.ts`:
- Around line 172-183: The injected-wallet branch in middleware must not trust
x-injected-wallet alone. Before assigning walletAddress or privyUserId, validate
a signed nonce/challenge proving control of the claimed wallet, and reject the
request when proof is missing or invalid; preserve the existing token-based
authentication path and only derive downstream headers after successful
verification.

---

Outside diff comments:
In `@app/components/KycModal.tsx`:
- Around line 120-129: Update the useInjectedWallet destructuring and
walletAddress selection in KycModal so injectedAddress is used only when
injectedReady is true; otherwise preserve the embedded wallet fallback behavior.
Ensure all downstream consumers of walletAddress, including SmileID context,
Tier 3 upload headers, and fetchStatus, receive only a ready injected address.

In `@app/components/PhoneVerificationModal.tsx`:
- Around line 204-260: Update the dependency array of handlePhoneSubmit to
include both isInjectedWallet and injectedAddress, ensuring the callback uses
the current injected-wallet state and address when sending the OTP request.
- Around line 262-307: Update handleOtpSubmit and handleResendOtp to support
injected wallets like handlePhoneSubmit: permit a null accessToken when
isInjectedWallet is true, still require a token otherwise, and include the
x-injected-wallet header in their fetch requests for injected-wallet flows while
preserving the existing Authorization header behavior.

---

Nitpick comments:
In `@app/components/WalletDetails.tsx`:
- Around line 537-579: Remove the redundant !isInjectedWallet condition from the
Convert button’s isBridgeUiVisible() guard in WalletDetails, since it is already
nested inside the outer !isInjectedWallet block. Separately verify that
injected-wallet bridge quoting and execution remain reachable through an
appropriate UI entry point outside this gated Fund/Transfer/Convert row,
including BridgeForm usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fdfebd9c-01f4-4c1f-8fd7-52d80bff717d

📥 Commits

Reviewing files that changed from the base of the PR and between cd0648d and 893df60.

📒 Files selected for processing (17)
  • app/api/aggregator.ts
  • app/components/KycModal.tsx
  • app/components/PhoneVerificationModal.tsx
  • app/components/ProfileView.tsx
  • app/components/WalletDetails.tsx
  • app/components/bridge/BridgeForm.tsx
  • app/context/BalanceContext.tsx
  • app/context/KYCContext.tsx
  • app/hooks/bridge.ts
  • app/hooks/useBridgeStatusTracker.ts
  • app/layout.tsx
  • app/lib/bridge.ts
  • app/lib/bundler/chains.ts
  • app/lib/privy-config.ts
  • app/mocks.ts
  • app/utils.ts
  • middleware.ts
💤 Files with no reviewable changes (3)
  • app/utils.ts
  • app/layout.tsx
  • app/lib/privy-config.ts

Comment thread app/context/KYCContext.tsx
Comment thread app/mocks.ts
Comment thread middleware.ts
…let readiness

- Enhanced KycModal and PhoneVerificationModal to ensure injected wallet address is only used when the wallet is ready, preventing premature KYC requests.
- Updated PhoneVerificationModal to include injected wallet details in API calls, improving authentication handling.
- Refactored WalletDetails and KYCContext to streamline injected wallet logic and improve code clarity.
- Adjusted migrationChecklistNetworks documentation to reflect the exclusion of additional networks.
…allet support

- Updated rendering logic in WalletDetails and WalletView to conditionally display fund and transfer buttons based on wallet type, enhancing user experience for both injected and non-injected wallets.
- Moved the Convert modal outside the injected wallet condition to ensure accessibility for all wallet types.
- Improved code clarity by adding comments to explain the rendering conditions for wallet actions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
app/components/PhoneVerificationModal.tsx (1)

77-99: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require a ready injected wallet for every OTP request.

resolveAccessToken() returns null for any injected mode, while OTP verification and resend treat that as authenticated solely because isInjectedWallet is true. If the wallet is disconnected or still initializing, these requests send neither Authorization nor x-injected-wallet. Gate all handlers on injectedReady && injectedAddress, and include readiness in the callback dependencies. Move the injected-wallet branch before the Privy ready check to avoid misleading Privy-loading errors.

Based on the supplied authentication contract, injected requests must authenticate through x-injected-wallet when no bearer token exists.

Also applies to: 204-229, 260-260, 262-288, 314-336, 358-358

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/PhoneVerificationModal.tsx` around lines 77 - 99, Update
resolveAccessToken and every OTP request handler to require injectedReady and
injectedAddress when isInjectedWallet is true, returning early with the existing
authentication error if the wallet is unavailable. Move the injected-wallet
branch before the Privy ready check so injected flows do not show Privy-loading
errors, include injectedReady and injectedAddress in callback dependencies, and
ensure injected requests authenticate via x-injected-wallet when no bearer token
is returned.
app/components/WalletDetails.tsx (1)

14-14: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the bridge visibility change effective for injected wallets.

The Convert button at Line 565 is still nested under the outer !isInjectedWallet block at Line 537, so injected users never see it. Removing the inner restriction is a no-op. Move only Fund/Transfer/Earn behind the embedded-wallet guard, render Convert separately according to the intended embed policy, and either use or remove isEmbed.

Based on the PR objective for injected bridge execution, injected users need an accessible Convert entry point.

Also applies to: 169-169, 565-579

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/WalletDetails.tsx` at line 14, Update the WalletDetails
component so the Convert entry point is rendered independently of the outer
!isInjectedWallet guard, allowing injected wallets to access bridge execution.
Keep only Fund, Transfer, and Earn behind the embedded-wallet restriction, and
use isEmbed in the resulting visibility logic or remove it if no longer needed.
🧹 Nitpick comments (1)
app/mocks.ts (1)

145-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the network-list contract.

Please test that Scroll is absent from networks, while Celo, Starknet, and Tron remain excluded from migrationChecklistNetworks. These assertions will prevent future chain/configuration edits from silently changing the migration scope.

Also applies to: 158-166

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/mocks.ts` around lines 145 - 146, Add regression tests for the
network-list configuration in app/mocks.ts: assert that Scroll is absent from
networks, and that Celo, Starknet, and Tron remain absent from
migrationChecklistNetworks. Keep the assertions focused on preserving these
existing migration-scope exclusions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/components/PhoneVerificationModal.tsx`:
- Around line 77-99: Update resolveAccessToken and every OTP request handler to
require injectedReady and injectedAddress when isInjectedWallet is true,
returning early with the existing authentication error if the wallet is
unavailable. Move the injected-wallet branch before the Privy ready check so
injected flows do not show Privy-loading errors, include injectedReady and
injectedAddress in callback dependencies, and ensure injected requests
authenticate via x-injected-wallet when no bearer token is returned.

In `@app/components/WalletDetails.tsx`:
- Line 14: Update the WalletDetails component so the Convert entry point is
rendered independently of the outer !isInjectedWallet guard, allowing injected
wallets to access bridge execution. Keep only Fund, Transfer, and Earn behind
the embedded-wallet restriction, and use isEmbed in the resulting visibility
logic or remove it if no longer needed.

---

Nitpick comments:
In `@app/mocks.ts`:
- Around line 145-146: Add regression tests for the network-list configuration
in app/mocks.ts: assert that Scroll is absent from networks, and that Celo,
Starknet, and Tron remain absent from migrationChecklistNetworks. Keep the
assertions focused on preserving these existing migration-scope exclusions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df20c8e8-3954-4224-8aa4-a95a594b1038

📥 Commits

Reviewing files that changed from the base of the PR and between 893df60 and da57b22.

📒 Files selected for processing (5)
  • app/components/KycModal.tsx
  • app/components/PhoneVerificationModal.tsx
  • app/components/WalletDetails.tsx
  • app/context/KYCContext.tsx
  • app/mocks.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/components/KycModal.tsx
  • app/context/KYCContext.tsx

@chibie
chibie merged commit 6788d2e into main Jul 24, 2026
2 checks passed
@chibie
chibie deleted the fix/injected-mode-not-equal-privy branch July 24, 2026 15:21
@chibie chibie mentioned this pull request Jul 24, 2026
4 tasks
chibie added a commit that referenced this pull request Jul 27, 2026
* fix(status): poll order status with injected-wallet auth and harden polling

TransactionStatus was the only status surface still using Privy-only auth
(getAccessToken), so in injected/embed mode the poll interval was never
created and the UI stayed on pending/processing forever while the order
settled server-side. Migrate to useApiAuth and pass the SIWE token to the
order fetchers and the transaction update, and persist status rows for
injected wallets too.

Also harden the poll loop:
- a missing token now skips the cycle instead of permanently clearing the
  interval (session-expired toast only after several consecutive misses)
- the interval is created synchronously so an effect re-run can no longer
  orphan an in-flight bootstrap
- envelope fallbacks surfacing "success"/"error" are rejected by a status
  whitelist instead of leaking into UI state and DB writes
- status writes are pinned to the order's own DB row, so a later transfer
  overwriting the global currentTransactionId key can't redirect them

Resolve chained forwarding terminally instead of spinning: injected mode
skips immediately (leg 1 already paid the user's wallet), a not-yet-hydrated
embedded wallet schedules a recheck, and an unsupported token resolves as
skipped once the token list is loaded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FVH15Dsqay4qpRfwTeyov

* fix(preview): derive isOnramp from form mode, not walletAddress

A Buy reaching the preview with an empty wallet address was misclassified
as an offramp (isOnramp was derived from !!walletAddress), rendering the
offramp row set with a blank recipient and "Account: undefined", and
building offramp order params from empty bank fields. Derive the flow from
the form's own swapMode/isSwapped instead, and never render "undefined"
when an institution code has no display name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FVH15Dsqay4qpRfwTeyov

* fix(kyc): identity-scoped phone flag and honest status staleness

Two causes of repeated phone-verification prompts for already-verified
users:

1. A wallet that inherited tier >= 2 through the ID triple holds no phone
   of its own, so the tier-2 phone gate fired on every swap — and
   re-verifying the same number is rejected as already in use by the
   sibling wallet, an unsatisfiable loop. /api/kyc/status now reports
   identityHasVerifiedPhone (any wallet in the identity pool with an
   OTP-verified phone), fail-soft to the per-wallet answer.

2. KYCContext stamped the staleness window even when the status fetch
   failed, serving the reset tier-0 snapshot for 30s — verified users
   transiently read as unverified. Only a successful fetch now counts as
   fresh, a forced refresh no longer piggybacks on an older in-flight
   fetch (post-OTP refresh must observe post-promotion data), and a new
   hasLoadedStatus flag lets gating UIs distinguish "unknown" from
   "unverified".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FVH15Dsqay4qpRfwTeyov

* fix(form): require a recipient before submit; don't skip it after phone OTP

After phone verification handlePhoneVerified continued straight into
handleSwap, which submitted with empty recipient fields — the recipient
section only mounts once the user is verified, so it was never shown. The
empty onramp wallet address passed validation (empty is "valid" for the
field validator; the required rule lives on a field that never registered)
and the preview rendered a blank recipient.

handleSwap now requires a recipient for both directions before proceeding;
the post-OTP continuation lands back on the form with the freshly unlocked
recipient section and an informational prompt instead of auto-submitting.

Also use the KYC identity's phone (identityHasVerifiedPhone) for the tier-2
gate, and load real status once before deciding between the phone modal and
the limit modal when the snapshot hasn't loaded yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FVH15Dsqay4qpRfwTeyov

* fix(profile): hide Privy email linking for injected wallets; honest link errors

ProfileView offered "Connect my email" to injected (external) wallet users,
who have no Privy session — Privy linkEmail always failed and the hardcoded
error claimed the email was already linked. Gate the email block on
!isInjectedWallet, matching SettingsDropdown and the mobile SettingsView
(missed when injected support was retrofitted in #623).

Both link handlers now read the Privy error code: closing the modal shows
no error, linked_to_another_user keeps the "already linked" copy, and
everything else gets a truthful generic message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FVH15Dsqay4qpRfwTeyov

* feat(reconciler): reconcile stuck onramp orders server-side

The reconcile-pending-orders cron was offramp-only, so an onramp row whose
tab closed before settlement stayed pending forever even though the crypto
was delivered — the same failure the offramp scan exists to fix, and one the
client poll alone cannot cover.

Scan onramp rows too, against /v2/sender/orders/{uuid} with the sender API
key. Onramp terminal semantics match the app exactly: only settled completes
an order, validated stays pending. The app's validUntil expiry inference is
ported as well, so unfunded orders become expired and leave the scan set
instead of being re-fetched on every run.

Operational details:
- offramp is capped at half the wall-clock budget so a backlog there can
  never starve the onramp scan; if it finishes early onramp gets the rest
- a missing AGGREGATOR_SENDER_API_KEY_ID skips only the onramp scan and is
  reported as onrampSkippedReason, so offramp keeps reconciling
- a 404 carrying an "api key not found" message is surfaced as an error
  rather than counted as "not indexed yet", which would silently no-op
  every onramp row
- companion partial index mirrors the onramp scan predicate

Also adds tests pinning the mapping semantics the Edge Function ports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FVH15Dsqay4qpRfwTeyov

* fix: address review findings on gating, status casing and reconciler reporting

- handleSwap consumed the post-OTP continuation flag only inside the tier-2
  branch, so verifying a phone below tier 2 with a recipient already filled
  left the flag set permanently — later waving an ungated wallet past the
  tier-2 phone gate. Consume it exactly once per invocation instead.
- TransactionStatus accepted an order status case-insensitively but every
  comparison and the persisted value are lowercase, so a mixed-case terminal
  status would poll forever and store a non-canonical value. Canonicalize
  right after the guard.
- KYCContext left a previous wallet's in-flight refresh attached across a
  wallet switch; the forced refresh chains onto it, so the new wallet's
  status waited on an unrelated untimed fetch. Detach it on reset — the
  run's ownership check already prevents cross-clobbering.
- Reconciler: bound summary.details (non-reconcilable rows never leave the
  scan set and re-emitted an entry every run) and stop reporting the offramp
  half-budget cap as a run timeout; truncation is now per scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FVH15Dsqay4qpRfwTeyov

---------

Co-authored-by: Claude <noreply@anthropic.com>
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