Skip to content

fix(39724): fix QR infinite loader on firefox#40014

Merged
owencraston merged 23 commits intomainfrom
fix/39724-qr-infite-loader
Mar 4, 2026
Merged

fix(39724): fix QR infinite loader on firefox#40014
owencraston merged 23 commits intomainfrom
fix/39724-qr-infite-loader

Conversation

@Olivier-BB
Copy link
Contributor

@Olivier-BB Olivier-BB commented Feb 12, 2026

Description

This PR fixes an issue where users using QR hardware wallets in popup mode would encounter an infinite loader after successfully signing a bridge/swap transaction. The problem occurred because when the user clicked "Get signature", MetaMask would transition from popup mode to fullscreen mode, causing the popup state to be lost. The AwaitingSignatures component could no longer track the active quote, leaving users stuck on the loading screen even after the transaction was successfully submitted.

The solution:

  1. Preserve requestId in URL: When navigating to the awaiting signatures page for hardware wallets, we now include the requestId as a URL query parameter. This allows the component to track the transaction even when popup state is lost during the popup-to-fullscreen transition.
  2. Detect completed transactions: The component now checks the bridge history to determine if a transaction with the matching requestId has been submitted. It uses the requestId from either the active quote (when available) or from the URL parameters (when popup state is lost).
  3. Auto-navigate on completion: Once the transaction is detected in the bridge history, the component automatically navigates the user to the home page with the activity tab, providing a smooth user experience.

Open in GitHub Codespaces

Changelog

CHANGELOG entry: Fixed infinite loader when using QR hardware wallets in popup mode after signing bridge transactions

Related issues

Fixes: #39724

Manual testing steps

  1. Open MetaMask in Firefox in popup mode
  2. Connect MetaMask with a QR hardware wallet
  3. Initiate a bridge/swap transaction
  4. Click "Get signature" and notice the transition to fullscreen mode in a new tab
  5. Proceed with signing the transaction on the hardware wallet
  6. Verify that after the transaction is successfully signed, the user is automatically taken to the main MetaMask screen (home page with activity tab) instead of being stuck on an infinite loader
  7. Verify that the transaction appears in the activity tab

Screenshots/Recordings

Before

https://github.com/user-attachments/assets/96988011-5533-4f3d-87d4-988f8ff365f5
User would see infinite loader with "Confirm with your hardware wallet" message even after successful transaction.

After

https://github.com/user-attachments/assets/04372124-46f2-40d6-842d-8e4e4e1dd20c
User is automatically navigated to home page with activity tab showing the completed transaction.

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Changes bridge/swaps hardware-wallet navigation and auto-redirect logic based on URL params, QR scan state, and bridge history; regressions could mis-route users during two-step flows or on failure/cancel paths.

Overview
Fixes the QR hardware-wallet popup→fullscreen transition getting stuck on the AwaitingSignatures loader by persisting requestId in the awaiting-signatures URL and using it to recover state when the popup store is lost.

Adds useBridgeTransactionNavigation to AwaitingSignatures to auto-navigate to home?tab=activity when the matching bridge tx appears in history, and to also navigate away on QR cancellation/failure while avoiding false positives between approval and bridge steps. Updates useSubmitBridgeTransaction to include requestId in the HW-wallet awaiting URL and to use replace: true on activity/prepare redirects, with new unit tests and console baseline updates.

Written by Cursor Bugbot for commit 74a1c07. This will update automatically on new commits. Configure here.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Feb 12, 2026

✨ Files requiring CODEOWNER review ✨

🔄 @MetaMask/swaps-engineers (5 files, +692 -5)
  • 📁 ui/
    • 📁 pages/
      • 📁 bridge/
        • 📁 awaiting-signatures/
          • 📄 awaiting-signatures.test.tsx +416 -0
          • 📄 awaiting-signatures.tsx +28 -2
        • 📁 hooks/
          • 📄 useBridgeTransactionNavigation.ts +193 -0
          • 📄 useSubmitBridgeTransaction.test.tsx +39 -1
          • 📄 useSubmitBridgeTransaction.ts +16 -2

@Olivier-BB
Copy link
Contributor Author

I have read the CLA Document and I hereby sign the CLA

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes the infinite loader in Firefox popup → fullscreen transitions for QR hardware wallet bridge/swap signing by persisting enough state to recover the “active quote” and detect completion after the popup state is lost.

Changes:

  • Append requestId as a query param when navigating to the Awaiting Signatures screen for hardware wallets.
  • In AwaitingSignatures, derive requestId from either Redux (activeQuote) or URL params and detect completion via bridge history; auto-navigate to Activity on completion.
  • Add a unit test to ensure the hardware wallet route includes the encoded requestId.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
ui/pages/bridge/hooks/useSubmitBridgeTransaction.ts Adds requestId query param to the awaiting-signatures navigation URL for HW flows.
ui/pages/bridge/hooks/useSubmitBridgeTransaction.test.tsx Adds a test validating navigation includes the encoded requestId when isHardwareWallet is true.
ui/pages/bridge/awaiting-signatures/awaiting-signatures.tsx Reads requestId from URL as fallback and auto-navigates to Activity once matching bridge history is detected.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return;
}

navigate(`${DEFAULT_ROUTE}?tab=activity`, {
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The auto-navigation effect can create a back-button loop: after navigating to the home activity tab, pressing Back returns to /awaiting-signatures, which will immediately navigate forward again because hasSubmittedBridgeTx remains true. Consider using navigate(..., { replace: true, state: { stayOnHomePage: true } }) (or otherwise clearing the condition) so the awaiting-signatures route isn’t kept in browser history once completion is detected.

Suggested change
navigate(`${DEFAULT_ROUTE}?tab=activity`, {
navigate(`${DEFAULT_ROUTE}?tab=activity`, {
replace: true,

Copilot uses AI. Check for mistakes.
dawnseeker8
dawnseeker8 previously approved these changes Feb 12, 2026
Copy link
Contributor

@dawnseeker8 dawnseeker8 left a comment

Choose a reason for hiding this comment

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

LGTM

@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Feb 12, 2026

Builds ready [e07e05e]
UI Startup Metrics (1367 ± 86 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyStandard HomeuiStartup1367117915968614001533
load117298913978312071317
domContentLoaded116598513778212041309
domInteractive271695182479
firstPaint161651238159198232
backgroundConnect24222128113246272
firstReactRender16103841723
initialActions105112
loadScripts9397701147839791092
setupStore1263051420
numNetworkReqs231589201583
BrowserifyPower User HomeuiStartup213014019808104421852750
load12531089183716213001663
domContentLoaded12321074180815612721624
domInteractive3921189273799
firstPaint211811707174262361
backgroundConnect4382973345415378647
firstReactRender24175472637
initialActions102112
loadScripts978828155515210051366
setupStore1766291833
numNetworkReqs1165125547139207
WebpackStandard HomeuiStartup86068313021159461032
load7476091223112828916
domContentLoaded7426051216111823909
domInteractive261597192382
firstPaint1126129252128210
backgroundConnect25166682744
firstReactRender1493251724
initialActions103112
loadScripts7396031214110821907
setupStore1264351221
numNetworkReqs231593201682
WebpackPower User HomeuiStartup1202895210118613031535
load7126191173110702950
domContentLoaded7036121168109691942
domInteractive38181973335128
firstPaint1366347672153274
backgroundConnect1771321253120157314
firstReactRender22173142429
initialActions101011
loadScripts7006101159108689934
setupStore1254251418
numNetworkReqs1304927849157221
FirefoxBrowserifyStandard HomeuiStartup15131287209817815361957
load13061113187313413491569
domContentLoaded13051113186813413491569
domInteractive68332163894135
firstPaint------
backgroundConnect5326161225198
firstReactRender1191811214
initialActions103112
loadScripts12821098184613213241538
setupStore145130161348
numNetworkReqs241395211784
BrowserifyPower User HomeuiStartup26672124416232827833240
load14931275241019915181918
domContentLoaded14921274240419815181918
domInteractive1143541077121310
firstPaint------
backgroundConnect3591161092268474915
firstReactRender18146061822
initialActions103122
loadScripts14531244238018814891843
setupStore1438762189137580
numNetworkReqs783716531101129
WebpackStandard HomeuiStartup15841364237718116271984
load13641187170911214161547
domContentLoaded13631186170811214161546
domInteractive812924848119159
firstPaint------
backgroundConnect55232153755135
firstReactRender14104751419
initialActions102122
loadScripts13371173168710413891520
setupStore185217351241
numNetworkReqs231296201774
WebpackPower User HomeuiStartup26401953400741028373379
load15111239252725516082022
domContentLoaded15111239252725516082021
domInteractive14731841178124644
firstPaint------
backgroundConnect3501131418270412902
firstReactRender22145672331
initialActions203122
loadScripts14711218247425015482005
setupStore1426998201128674
numNetworkReqs74361713293132
📊 Page Load Benchmark Results

Current Commit: e07e05e | Date: 2/12/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.05s (±43ms) 🟡 | historical mean value: 1.04s ⬆️ (historical data)
  • domContentLoaded-> current mean value: 734ms (±39ms) 🟢 | historical mean value: 728ms ⬆️ (historical data)
  • firstContentfulPaint-> current mean value: 79ms (±11ms) 🟢 | historical mean value: 79ms ⬆️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.05s 43ms 1.02s 1.40s 1.08s 1.40s
domContentLoaded 734ms 39ms 711ms 1.05s 750ms 1.05s
firstPaint 79ms 11ms 64ms 176ms 88ms 176ms
firstContentfulPaint 79ms 11ms 64ms 176ms 88ms 176ms
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 58 Bytes (0%)
  • ui: 1.25 KiB (0.02%)
  • common: 20 Bytes (0%)

@angelcheung22 angelcheung22 marked this pull request as ready for review February 13, 2026 09:36
@angelcheung22 angelcheung22 requested a review from a team as a code owner February 13, 2026 09:36
@Olivier-BB Olivier-BB force-pushed the fix/39724-qr-infite-loader branch from e07e05e to fbf144c Compare February 13, 2026 10:14
@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Feb 13, 2026

Builds ready [fbf144c]
UI Startup Metrics (1415 ± 104 ms)
PlatformBuildTypePageMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
ChromeBrowserifyStandard HomeuiStartup14151217181210414801588
load1220103015279712761382
domContentLoaded1213102715209712701375
domInteractive2916112212591
firstPaint158691139120212285
backgroundConnect25423129814258288
firstReactRender17103851829
initialActions106113
loadScripts97678412819710211139
setupStore1463361625
numNetworkReqs221584191580
BrowserifyPower User HomeuiStartup17781423251818118322124
load12041056191117911941679
domContentLoaded11881048190517511781664
domInteractive3918214353696
firstPaint190691686175237327
backgroundConnect34529565946349398
firstReactRender23154662535
initialActions105112
loadScripts92880316461759091416
setupStore1676881829
numNetworkReqs1074724844134181
WebpackStandard HomeuiStartup86871412161029061049
load745640107398798909
domContentLoaded739634106897794903
domInteractive2918115192577
firstPaint1296940167154252
backgroundConnect28195973138
firstReactRender15102951924
initialActions105112
loadScripts736632106696791893
setupStore1373761326
numNetworkReqs231597211583
WebpackPower User HomeuiStartup1302908189318913871673
load76166611761227451090
domContentLoaded75166011701217331082
domInteractive42201773538152
firstPaint1497347385164305
backgroundConnect17213540254169282
firstReactRender24184952532
initialActions103112
loadScripts74865811601187311068
setupStore1352141618
numNetworkReqs1164627955143262
FirefoxBrowserifyStandard HomeuiStartup15481332229020215812026
load13371141196315813841638
domContentLoaded13361136196315813841638
domInteractive64322103980141
firstPaint------
backgroundConnect5326169205398
firstReactRender12102111314
initialActions102012
loadScripts13131118193715513521613
setupStore146138171342
numNetworkReqs241490201784
BrowserifyPower User HomeuiStartup26722036390137327723406
load15571236231224715902107
domContentLoaded15571235231224715892107
domInteractive13338925144111403
firstPaint------
backgroundConnect2331151128182217583
firstReactRender20147591927
initialActions203123
loadScripts15221216226924015532064
setupStore139971619694583
numNetworkReqs64341583384130
WebpackStandard HomeuiStartup15971349203013816521874
load13821161165610714381589
domContentLoaded13821156165510714381589
domInteractive942736053131160
firstPaint------
backgroundConnect51232293156106
firstReactRender1611118111424
initialActions103012
loadScripts13581147163610114121541
setupStore156165241245
numNetworkReqs241390181874
WebpackPower User HomeuiStartup28202038388445030913729
load15791258249032517342278
domContentLoaded15781258248932517342278
domInteractive15133869183116621
firstPaint------
backgroundConnect3431211249288349923
firstReactRender21163742329
initialActions204123
loadScripts15321243246331316982158
setupStore2208913258364755
numNetworkReqs66361663695141
📊 Page Load Benchmark Results

Current Commit: fbf144c | Date: 2/13/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.04s (±39ms) 🟡 | historical mean value: 1.06s ⬇️ (historical data)
  • domContentLoaded-> current mean value: 727ms (±36ms) 🟢 | historical mean value: 743ms ⬇️ (historical data)
  • firstContentfulPaint-> current mean value: 79ms (±13ms) 🟢 | historical mean value: 82ms ⬇️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.04s 39ms 1.01s 1.32s 1.06s 1.32s
domContentLoaded 727ms 36ms 707ms 996ms 746ms 996ms
firstPaint 79ms 13ms 64ms 192ms 88ms 192ms
firstContentfulPaint 79ms 13ms 64ms 192ms 88ms 192ms
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 58 Bytes (0%)
  • ui: 1.25 KiB (0.02%)
  • common: 20 Bytes (0%)

@nikolastoimenovski-consensys
Copy link

nikolastoimenovski-consensys commented Feb 13, 2026

Tested on Firefox, the original issue is fixed. Adding "qa passed" label.

Infinite loader issue is happening for similar scenario: if wrong QR code is used for the signature and the user cancels the transaction. Created separate ticket for this issue #40088

@sonarqubecloud
Copy link

@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Feb 24, 2026

Builds ready [74a1c07]
⚡ Performance Benchmarks (1420 ± 108 ms)
👆 Interaction Benchmarks
ActionMetricMean (ms)Std Dev (ms)P75 (ms)P95 (ms)
Load New Accountload_new_account28119287309
total28119287309
Confirm Txconfirm_tx60661360766084
total60661360766084
Bridge User Actionsbridge_load_page2459253253
bridge_load_asset_picker20242233269
bridge_search_token71014702734
total11222811181168
🔌 Startup Benchmarks
BuildMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
Chrome Browserify Startup Standard HomeuiStartup14201201184410814571591
load119499515729512331349
domContentLoaded118799215679412271342
domInteractive291894182577
firstPaint165661260135218334
backgroundConnect20718825414209242
firstReactRender19133642025
initialActions106114
loadScripts99779313689310371143
setupStore1274651518
numNetworkReqs312289192384
Chrome Browserify Startup Power User HomeuiStartup210413759624100720863606
load1152102615829612081318
domContentLoaded1134101415548811871274
domInteractive3219146163356
firstPaint211741397175262324
backgroundConnect46924929524883421291
firstReactRender25164662835
initialActions104112
loadScripts9318121336879791074
setupStore17670102034
numNetworkReqs66341462278107
Chrome Webpack Startup Standard HomeuiStartup8456981128958671032
load73061999085771868
domContentLoaded72561498285768862
domInteractive2815164222373
firstPaint1125935053146203
backgroundConnect27196583342
firstReactRender17123651925
initialActions105112
loadScripts72261297384766859
setupStore1153951219
numNetworkReqs312297202586
Chrome Webpack Startup Power User HomeuiStartup1259930177815613401537
load74665611811127331042
domContentLoaded73664911751137241031
domInteractive40192273437132
firstPaint1467148178164302
backgroundConnect18013665664174280
firstReactRender24183642531
initialActions105111
loadScripts73364611671107221023
setupStore1355481432
numNetworkReqs1143526452147199
Firefox Browserify Startup Standard HomeuiStartup16771355292222816852069
load14131161269519814371730
domContentLoaded14121161269519814351730
domInteractive9634140914199162
firstPaint------
backgroundConnect61272253265119
firstReactRender14111911416
initialActions103122
loadScripts13851144267719314131638
setupStore217175311558
numNetworkReqs312099192686
Firefox Browserify Startup Power User HomeuiStartup26351948361332128193223
load15321283230118616331924
domContentLoaded15311283230118616331923
domInteractive1183244788121337
firstPaint------
backgroundConnect2531101084198259775
firstReactRender18146481821
initialActions203122
loadScripts14961247225017915761850
setupStore1348762193113683
numNetworkReqs72281883788145
Firefox Webpack Startup Standard HomeuiStartup17791505337925317912114
load15001256291821615221627
domContentLoaded15001256291821615221626
domInteractive992924445131192
firstPaint------
backgroundConnect63292433865151
firstReactRender16132731624
initialActions103122
loadScripts14741235288521514971600
setupStore21788183457
numNetworkReqs312096182776
Firefox Webpack Startup Power User HomeuiStartup27201907781664628563545
load15931259651756916852176
domContentLoaded15921259651756916852176
domInteractive1293677216696602
firstPaint------
backgroundConnect2981181296267292952
firstReactRender231586112436
initialActions207122
loadScripts15511225648156716032110
setupStore17981181240223677
numNetworkReqs66282193685132
🧭 User Journey Benchmarks
BenchmarkMetricMean (ms)Std Dev (ms)P75 (ms)P95 (ms)
Onboarding Import WalletimportWalletToSocialScreen2181219219
srpButtonToSrpForm9319394
confirmSrpToPwForm2202222
pwFormToMetricsScreen1501616
metricsToWalletReadyScreen1711717
doneButtonToHomeScreen92530412501289
openAccountMenuToAccountListLoaded716458376737868
total845844788448937
Onboarding New WalletcreateWalletToSocialScreen2202222224
srpButtonToPwForm1106108120
createPwToRecoveryScreen9099
skipBackupToMetricsScreen3703838
agreeButtonToOnboardingSuccess1701717
doneButtonToAssetList89938211691507
total130237515841892
Asset DetailsassetClickToPriceChart58248093
total58248093
Solana Asset DetailsassetClickToPriceChart5715859
total5715859
Import Srp HomeloginToHomeScreen18836318771988
openAccountMenuAfterLogin3924040
homeAfterImportWithNewWallet23183423242370
total444723146544769
Send TransactionsopenSendPageFromHome1711818
selectTokenToSendFormLoaded3093737
reviewTransactionToConfirmationPage8553855860
total9113915915
SwapopenSwapPageFromHome12110127134
fetchAndDisplaySwapQuotes532088463826422
total544188265106531
🌐 Dapp Page Load Benchmarks

Current Commit: 74a1c07 | Date: 2/24/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.03s (±37ms) 🟡 | historical mean value: 1.04s ⬇️ (historical data)
  • domContentLoaded-> current mean value: 717ms (±36ms) 🟢 | historical mean value: 726ms ⬇️ (historical data)
  • firstContentfulPaint-> current mean value: 75ms (±10ms) 🟢 | historical mean value: 81ms ⬇️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.03s 37ms 1.01s 1.30s 1.06s 1.30s
domContentLoaded 717ms 36ms 699ms 981ms 741ms 981ms
firstPaint 75ms 10ms 60ms 152ms 84ms 152ms
firstContentfulPaint 75ms 10ms 60ms 152ms 84ms 152ms
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 58 Bytes (0%)
  • ui: 3.04 KiB (0.04%)
  • common: 20 Bytes (0%)

Copy link
Contributor

@dawnseeker8 dawnseeker8 left a comment

Choose a reason for hiding this comment

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

LGTM

@nikolastoimenovski-consensys

After the refactor, retested both issues #40088 and #39724, works as expected. The "qa passed" label is already added.

Copy link
Contributor

@owencraston owencraston left a comment

Choose a reason for hiding this comment

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

I am unable to complete a swap with my keystone. This occurred after a failed swap and then I cleared the transaction history. Before this, I was brought to the get signature screen but I was still unable to complete the swap.

Screen.Recording.2026-02-26.at.3.00.32.PM.mov

@nikolastoimenovski-consensys
Copy link

nikolastoimenovski-consensys commented Feb 27, 2026

Thanks for the feedback @owencraston . Using keystone 3 pro, tried to reproduce the issue from your video, but wasn't able to. I have tried few times to swap/bridge different cryptocurrencies on different networks and it works for me. Attaching the video of swapping USDC to ETH on base.

Screen.Recording.2026-02-27.at.13.29.25.mov

Here are other things I noticed:

  • The previous failed transaction is not shown in the "activity" tab after the successful swap
  • On a transaction, there are no additional details once the user expands the "activity log"
no.activity.log.mov

@angelcheung22
Copy link

@owencraston and the issue you experience is prob outside of this fix, as its on main? Can we go ahead with merging this first?

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

Labels

QA Passed release-13.22.0 Issue or pull request that will be included in release 13.22.0 size-L team-accounts-framework Accounts team

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[Bug]: [Firefox] QR - Infinite loader is presented after a successful swap in pop up mode

10 participants