Repository: KomodoPlatform/komodo-wallet
Pull request #2608: fix(fiat-onramp): banxa onramp flow erroneous failure popup and return to komodo button
Author: login: takenagain
Labels: name: bug
description: Something isn't working
name: QA
description: Ready for QA Testing
Created: 2025-05-01T19:26:26Z | Merged: 2025-05-15T08:22:40Z

Fixes the issues mentioned in [#2570](https://github.com/KomodoPlatform/komodo-wallet/pull/2570#issuecomment-2800411995) where the Banxa flow incorrectly triggered a payment failed popup and failed to do anything when the button was pressed. The "Komodo" text appears to be linked to the partner name configured on the Banxa dashboard.

We are pinned to Banxa API v1.3. Further adjustments would be required to migrate to v2 (v2.1 being the latest). Ramp also has newer API versions that we could upgrade to (v2 and v3).

## Changes

- Banxa page now opens in a new tab rather than a webview dialog to circumvent dialog/popup z-order issues.
- Fixed a coin abbreviation/ticker parsing regression, so there are now more coins available in the coin selection dropdown (mostly from [Banxa](https://docs.banxa.com/docs/available-cryptocurrencies-and-blockchains))
- The provider error response is now shown if order creation fails when clicking "Buy Now". Incorrect address formats and minimum coin amounts are likely reasons for this, and the additional information could help with debugging/support.
- Improved documentation/explanation comments, refactoring, and implementation of coderabbitai recommendations around error handling from main PR
- Disabled the following coins due to address format errors, mostly on Banxa's side (done in [app_config.dart](https://github.com/KomodoPlatform/komodo-wallet/pull/2608/files#diff-3e5d75c494efdef3049c274ec12518d4a8f4bc56a7562faf1caa8425821c4b5e))
  <details>
  <summary>Disabled coins for fiat onramp</summary>
  
  Banxa:
  | Coin | Reason |
  |------|--------|
  | APE | Chain not configured for APE |
  | AVAX | Invalid wallet address error (avax & bep20) |
  | DOT | Invalid wallet address error (bep20) |
  | FIL | Invalid wallet address error (bep20) |
  | ONE | Invalid wallet address error (one**** native format expected) |
  | TON | Invalid wallet address error (erc20) |
  | TRX | Invalid wallet address error (bep20) |
  | XML | Invalid wallet address error |
  
  Ramp:
  | Coin | Reason |
  |------|--------|
  | ONE | Invalid wallet address error (one14**** format expected) |

  </details>

## Demo environment limitations

Due to limitations with Banxa demo environment, testing with real fiat transfers is required to confirm that the payment failure popup is not shown when payment succeeds.

Both Ramp and Banxa stop or fail before completing the mock transaction (no final success status).

<details>
<summary>Banxa & Ramp limitations</summary>

  - Ramp has test coins that are not currently displayed and will require significant changes to display (dynamic custom token importing or adding Ramp test network coins to `coins` config file).
  - Banxa returns a "cancelled" (failure) status at the end of the demo transaction flow, resulting in the failure popup being shown.
  
 </details>

<details>
<summary>Banxa demo testing information *Updated*</summary>

Banxa [demo testing information](https://docs.banxa.com/v1.3/docs/testing-information) can be used to test local `debug` builds up until the payment processing step. Note that Banxa returns a payment failed status at the end, so the payment failure popup is expected in the local `debug` builds with the demo environment.

- Select USD as the payment currency
- Verification code (Email or SMS): 7203
- Credit card: 4111 1111 1111 1111 (any expiry + CVV combination into the future should work)
- Identification number if required: Use one of the [country-specific demo identity numbers](https://docs.banxa.com/v1.3/docs/testing-information#local-payment-methods)
</details>

<details>
<summary>Ramp demo testing information</summary>

Note: The demo environment does not release funds currently, so the transaction status will remain in the "Processing transaction" state, and the transaction will not succeed.

1. Select "EUR" as the currency
2. Select an EVM coin in the "You Receive" dropdown (e.g. ETH, MATIC, BNB, etc.)
3. Select "Manual Bank Transfer" as the payment method.
4. Proceed with the payment, and select "I have transferred the funds" to complete the transaction.
5. Click on "Check transaction status", which should open a new tab with the transaction status window
6. Click on "Release the funds (test environments only)
</details>

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced multiple display modes for fiat payment webviews: dialog, fullscreen, and new browser tab/external browser.
  - Added secure webview settings restricting content to trusted payment provider domains.
  - Improved iframe sandbox permissions and added fallback content for unsupported browsers.

- **Improvements**
  - Enhanced payment status tracking with new and renamed statuses for clearer state representation.
  - Streamlined payment flow and webview handling for consistent cross-platform experience.
  - Improved error handling and JSON parsing for fiat order responses.
  - Strengthened type safety and input validation in fiat provider integrations.
  - Updated state management with lazy evaluation and platform-aware webview mode selection.
  - Refined fiat currency symbol abbreviation logic for consistency.
  - Enhanced UI components to use standardized currency abbreviations.
  - Added filtering and logging for unsupported coins in fiat providers.
  - Improved minimum purchase amount handling across currencies and payment methods.
  - Enhanced error reporting with provider-specific messages in payment dialogs.
  - Added new constants listing unsupported coins for Banxa and Ramp providers.

- **Bug Fixes**
  - Fixed error handling when parsing response data from fiat providers.

- **Refactor**
  - Modularized and clarified JavaScript and Dart code for maintainability.
  - Removed unused methods and imports to clean up the codebase.
  - Simplified webview message handling and removed redundant injected scripts.
  - Reordered constructor parameters for clarity without behavior changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/assets/web_pages/checkout_status_redirect.html b/assets/web_pages/checkout_status_redirect.html
index d9aa2631c1..fdb69020fc 100644
--- a/assets/web_pages/checkout_status_redirect.html
+++ b/assets/web_pages/checkout_status_redirect.html
@@ -1,48 +1,45 @@
 <!DOCTYPE html>
 <html>
+
 <head>
   <title>Komodo Payment Redirect</title>
 </head>
+
 <body>
   <script>
     // Extract URL parameters and put them into an object
     function getParams() {
-        var params = {};
-        window.location.search.substring(1).split("&").forEach(function (part) {
-            var item = part.split("=");
-            params[item[0]] = decodeURIComponent(item[1]);
-        });
-        return params;
+      var params = {};
+      window.location.search.substring(1).split("&").forEach(function (part) {
+        var item = part.split("=");
+        params[item[0]] = decodeURIComponent(item[1]);
+      });
+      return params;
     }
 
     // Send the parameters to the opener window
     var params = getParams();
-
     var status = params['status'];
 
     console.log('opener: ' + (window.opener?.location?.href ?? 'null'));
-    
-    if (window.opener != null && !window.opener.closed) {
-
-        // Temporary work-around because of Banxa checkout bug. We are polling the status of the payment
-        // using the order ID in the main app, but this would be better if we could get the post message
-        // callback to work.
-        if (status == null) {
-          status = 'unknown';
-        }
 
-        // Send the parameters to the opener window
-        window.opener.postMessage({ type: 'PAYMENT-STATUS', status: status, params: params }, '*');
-
-        // Change back to the opener tab
-        window.opener.focus();
+    if (window.opener != null && !window.opener.closed) {
+      // Temporary work-around because of Banxa checkout bug. We are polling the status of the payment
+      // using the order ID in the main app, but this would be better if we could get the post message
+      // callback to work.
+      if (status == null) {
+        status = 'unknown';
+      }
 
-        // Close this tab
-        window.close();
-    } else {
-        // Navigate to the home page as a fallback
-        window.location.href = 'https://app.komodoplatform.com/';
+      // Send the parameters to the opener window
+      window.opener.postMessage({ type: 'PAYMENT-STATUS', status: status, params: params }, '*');
+      window.opener.focus();
     }
+
+    // Both Ramp and Banxa checkout pages are either opened in a new tab or a popup dialog webview,
+    // so we need to close the current window rather than redirecting back to the website.
+    window.close();
   </script>
 </body>
+
 </html>
\ No newline at end of file
diff --git a/assets/web_pages/fiat_widget.html b/assets/web_pages/fiat_widget.html
index a42d14db13..67427c2307 100644
--- a/assets/web_pages/fiat_widget.html
+++ b/assets/web_pages/fiat_widget.html
@@ -2,109 +2,129 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
-        }
-
-        if (targetUrl) {
-            // Set the iframe's src attribute
-            document.getElementById('fiat-onramp-iframe').src = targetUrl;
-        } else {
-            console.error('No URL parameter provided');
-        }
-
-        function onMessageHandler(messageEvent) {
-            let messageData;
-            try {
-                // Try parsing the data as JSON 
-                messageData = JSON.parse(messageEvent.data);
-            } catch (parseError) {
-                // If parsing fails, just use it as a string
-                messageData = messageEvent.data;
-            }
-
-            try {
-                postMessageToParent(messageData);
-            } catch (postError) {
-                console.error('Error posting message', postError);
-            }
-        }
-
-        /** 
-         * Post a message to the parent window
-         * 
-         * @param {string|object} messageData 
-         */
-        function postMessageToParent(messageData) {
-            // Convert messageData to a string if it is an object
-            const messageString = (typeof messageData === 'object') ? JSON.stringify(messageData) : String(messageData);
-
-            // flutter_inappwebview
-            console.log(messageString);
-
-            // universal_url opener 
-            if (window.opener) {
-                return window.opener.postMessage(messageString, "*");
-            }
-
-            if (window.parent && window.parent !== window) {
-                return window.parent.postMessage(messageString, "*");
-            }
-
-            // Windows WebView2 (desktop_webview_window) - https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/communicate-btwn-web-native 
-            if (window.chrome && window.chrome.webview) {
-                return window.chrome.webview.postMessage(messageString);
-            }
-
-            console.error('No valid postMessage target found');
+  <iframe id="fiat-onramp-iframe" title="Fiat On-Ramp Widget"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation allow-top-navigation-by-user-action"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.addEventListener('message', _komodoOnMessageHandler, false);
+    window.addEventListener('load', function () {
+      _komodoSetIframeUrlFromParams();
+    });
+
+    /**
+     * Initialize the iframe URL based on URL parameters
+     * 
+     * @param {object} params - The URL parameters to use for initialization
+     */
+    function _komodoSetIframeUrlFromParams() {
+      const urlParam = _komodoGetUrlParameter('fiatUrl');
+
+      let targetUrl = null;
+      if (urlParam) {
+        try {
+          targetUrl = atob(urlParam); // base64 decode the `url` parameter
+        } catch (error) {
+          console.error('Error decoding base64 url parameter', error);
         }
-    </script>
+      }
+
+      if (targetUrl) {
+        document.getElementById('fiat-onramp-iframe').src = targetUrl;
+      } else {
+        console.error('No URL parameter provided');
+      }
+    }
+
+    /**
+     * Get URL parameter by name
+     * 
+     * @param {string} name - The name of the URL parameter to retrieve
+     * @returns {string|null} - The value of the URL parameter or null if not found
+     */
+    function _komodoGetUrlParameter(name) {
+      const params = new URLSearchParams(window.location.search);
+      return params.get(name);
+    }
+
+    /** 
+     * Handle messages from the iframe
+     * 
+     * @param {MessageEvent} messageEvent 
+     */
+    function _komodoOnMessageHandler(messageEvent) {
+      let messageData;
+      try {
+        messageData = typeof messageEvent.data === 'string' ? JSON.parse(messageEvent.data) : messageEvent.data;
+      } catch (parseError) {
+        messageData = messageEvent.data;
+      }
+
+      try {
+        _komodoPostMessageToParent(messageData);
+      } catch (postError) {
+        console.error('Error posting message', postError);
+      }
+    }
+
+    /** 
+     * Post a message to the parent window
+     * 
+     * @param {string|object} messageData 
+     */
+    function _komodoPostMessageToParent(messageData) {
+      const messageString = (typeof messageData === 'object') ? JSON.stringify(messageData) : String(messageData);
+
+      // flutter_inappwebview
+      console.log(messageString);
+
+      // universal_url opener 
+      if (window.opener) {
+        return window.opener.postMessage(messageString, "*");
+      }
+
+      if (window.parent && window.parent !== window) {
+        return window.parent.postMessage(messageString, "*");
+      }
+
+      // Windows WebView2 (desktop_webview_window)
+      // https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/communicate-btwn-web-native 
+      if (window.chrome && window.chrome.webview) {
+        return window.chrome.webview.postMessage(messageString);
+      }
+
+      console.error('No valid postMessage target found');
+    }
+  </script>
 </body>
 
 </html>
\ No newline at end of file
diff --git a/lib/app_config/app_config.dart b/lib/app_config/app_config.dart
index 7e2aa67527..e7bcbbf7a9 100644
--- a/lib/app_config/app_config.dart
+++ b/lib/app_config/app_config.dart
@@ -99,6 +99,24 @@ const List<String> excludedAssetListTrezor = [
   'VAL',
 ];
 
+/// Some coins returned by the Banxa API are returning errors when attempting
+/// to create an order. This is a temporary workaround to filter out those coins
+/// until the issue is resolved.
+const banxaUnsupportedCoinsList = [
+  'APE', // chain not configured for APE
+  'AVAX', // avax & bep20 - invalid wallet address error
+  'DOT', // bep20 - invalid wallet address error
+  'FIL', // bep20 - invalid wallet address error
+  'ONE', // invalid wallet address error (one**** (native) format expected)
+  'TON', // erc20 - invalid wallet address error
+  'TRX', // bep20 - invalid wallet address error
+  'XML', // invalid wallet address error
+];
+
+const rampUnsupportedCoinsList = [
+  'ONE', // invalid wallet address error (one**** format expected)
+];
+
 // Assets in wallet-only mode on app level,
 // global wallet-only assets are defined in coins config files.
 const List<String> appWalletOnlyAssetList = [
diff --git a/lib/bloc/fiat/banxa_fiat_provider.dart b/lib/bloc/fiat/banxa_fiat_provider.dart
index bd6e8cc018..27c79fd05e 100644
--- a/lib/bloc/fiat/banxa_fiat_provider.dart
+++ b/lib/bloc/fiat/banxa_fiat_provider.dart
@@ -1,5 +1,6 @@
 import 'dart:convert';
 
+import 'package:decimal/decimal.dart';
 import 'package:logging/logging.dart';
 import 'package:web_dex/app_config/app_config.dart';
 import 'package:web_dex/bloc/fiat/base_fiat_provider.dart';
@@ -24,7 +25,7 @@ class BanxaFiatProvider extends BaseFiatProvider {
   FiatOrderStatus _parseStatusFromResponse(Map<String, dynamic> response) {
     final statusString = response['data']?['order']?['status'] as String?;
 
-    return _parseOrderStatus(statusString ?? '');
+    return FiatOrderStatus.fromString(statusString ?? '');
   }
 
   Future<dynamic> _getPaymentMethods(
@@ -96,34 +97,6 @@ class BanxaFiatProvider extends BaseFiatProvider {
         },
       );
 
-  FiatOrderStatus _parseOrderStatus(String status) {
-    // The case statements are references to Banxa's order statuses. See the
-    // docs link here for more info: https://docs.banxa.com/docs/order-status
-    switch (status) {
-      case 'complete':
-        return FiatOrderStatus.success;
-
-      case 'cancelled':
-      case 'declined':
-      case 'expired':
-      case 'refunded':
-        return FiatOrderStatus.failed;
-
-      case 'extraVerification':
-      case 'pendingPayment':
-      case 'waitingPayment':
-        return FiatOrderStatus.pending;
-
-      case 'paymentReceived':
-      case 'inProgress':
-      case 'coinTransferred':
-        return FiatOrderStatus.inProgress;
-
-      default:
-        throw Exception('Unknown status: $status');
-    }
-  }
-
   // These will be in BLOC:
   @override
   Stream<FiatOrderStatus> watchOrderStatus(String orderId) async* {
@@ -162,8 +135,9 @@ class BanxaFiatProvider extends BaseFiatProvider {
     return data
         .map(
           (item) => FiatCurrency(
-            item['fiat_code'] as String,
-            item['fiat_name'] as String,
+            symbol: item['fiat_code'] as String,
+            name: item['fiat_name'] as String,
+            minPurchaseAmount: Decimal.zero,
           ),
         )
         .toList();
@@ -171,12 +145,19 @@ class BanxaFiatProvider extends BaseFiatProvider {
 
   @override
   Future<List<CryptoCurrency>> getCoinList() async {
+    // TODO: add model classes to parse responses like these when migrating to
+    // the SDK
     final response = await _getCoins();
     final data = response['data']['coins'] as List<dynamic>;
 
     final List<CryptoCurrency> currencyList = [];
     for (final item in data) {
       final coinCode = item['coin_code'] as String;
+      if (banxaUnsupportedCoinsList.contains(coinCode)) {
+        _log.warning('Banxa does not support $coinCode');
+        continue;
+      }
+
       final coinName = item['coin_name'] as String;
       final blockchains = item['blockchains'] as List<dynamic>;
 
@@ -186,11 +167,32 @@ class BanxaFiatProvider extends BaseFiatProvider {
           continue;
         }
 
+        // Parse min_value which can be a string, int, or double
+        final dynamic minValue = blockchain['min_value'];
+        Decimal minPurchaseAmount;
+
+        if (minValue == null) {
+          minPurchaseAmount = Decimal.fromInt(0);
+        } else if (minValue is String) {
+          minPurchaseAmount = Decimal.fromJson(minValue);
+        } else if (minValue is int) {
+          minPurchaseAmount = Decimal.fromInt(minValue);
+        } else if (minValue is double) {
+          minPurchaseAmount = Decimal.parse(minValue.toString());
+        } else {
+          // Default to zero for any other unexpected types
+          minPurchaseAmount = Decimal.fromInt(0);
+          _log.warning(
+            'Unexpected type for min_value: ${minValue.runtimeType}',
+          );
+        }
+
         currencyList.add(
           CryptoCurrency(
-            coinCode,
-            coinName,
-            coinType,
+            symbol: coinCode,
+            name: coinName,
+            chainType: coinType,
+            minPurchaseAmount: minPurchaseAmount,
           ),
         );
       }
@@ -206,6 +208,11 @@ class BanxaFiatProvider extends BaseFiatProvider {
     String sourceAmount,
   ) async {
     try {
+      if (banxaUnsupportedCoinsList.contains(target.configSymbol)) {
+        _log.warning('Banxa does not support ${target.configSymbol}');
+        return [];
+      }
+
       final response =
           await _getPaymentMethods(source, target, sourceAmount: sourceAmount);
       final List<FiatPaymentMethod> paymentMethods = (response['data']
diff --git a/lib/bloc/fiat/base_fiat_provider.dart b/lib/bloc/fiat/base_fiat_provider.dart
index 40c1cc745d..f9c34968ab 100644
--- a/lib/bloc/fiat/base_fiat_provider.dart
+++ b/lib/bloc/fiat/base_fiat_provider.dart
@@ -58,11 +58,6 @@ abstract class BaseFiatProvider {
     final domainUri = Uri.parse(domain);
     Uri url;
 
-    // Remove the leading '/' if it exists in /api/fiats kind of an endpoint
-    if (endpoint.startsWith('/')) {
-      endpoint = endpoint.substring(1);
-    }
-
     // Add `is_test_mode` query param to all requests if we are in debug mode
     final passedQueryParams = <String, dynamic>{}
       ..addAll(queryParams ?? {})
@@ -73,7 +68,8 @@ abstract class BaseFiatProvider {
     url = Uri(
       scheme: domainUri.scheme,
       host: domainUri.host,
-      path: endpoint,
+      // Remove the leading '/' if it exists in /api/fiats kind of an endpoint
+      path: endpoint.startsWith('/') ? endpoint.substring(1) : endpoint,
       query: Uri(queryParameters: passedQueryParams).query,
     );
 
@@ -98,7 +94,13 @@ abstract class BaseFiatProvider {
         return json.decode(response.body);
       } else {
         _log.warning('Request failed with status: ${response.statusCode}');
-        return Future.error(json.decode(response.body) as Object);
+        dynamic decoded;
+        try {
+          decoded = json.decode(response.body);
+        } catch (_) {
+          decoded = response.body;
+        }
+        return Future.error(decoded as Object);
       }
     } catch (e, s) {
       _log.severe('Network error', e, s);
@@ -132,7 +134,6 @@ abstract class BaseFiatProvider {
         return 'MATIC';
       case CoinType.mvr20:
         return 'MOVR';
-      // ignore: no_default_cases
       default:
         return null;
     }
@@ -234,6 +235,7 @@ abstract class BaseFiatProvider {
         return CoinType.etc;
       case 'FTM':
         return CoinType.ftm20;
+      case 'ARBITRUM':
       case 'ARB':
         return CoinType.arb20;
       case 'HARMONY':
diff --git a/lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart b/lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
index b0d8ba3bf8..1712098fc2 100644
--- a/lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
+++ b/lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
@@ -1,10 +1,12 @@
 import 'dart:convert';
+import 'dart:io' show Platform;
 
 import 'package:bloc/bloc.dart';
 import 'package:bloc_concurrency/bloc_concurrency.dart';
 import 'package:collection/collection.dart';
 import 'package:decimal/decimal.dart';
 import 'package:equatable/equatable.dart';
+import 'package:flutter/foundation.dart';
 import 'package:formz/formz.dart';
 import 'package:komodo_defi_sdk/komodo_defi_sdk.dart';
 import 'package:komodo_defi_types/komodo_defi_types.dart';
@@ -15,10 +17,10 @@ import 'package:web_dex/bloc/fiat/fiat_order_status.dart';
 import 'package:web_dex/bloc/fiat/fiat_repository.dart';
 import 'package:web_dex/bloc/fiat/models/models.dart';
 import 'package:web_dex/bloc/fiat/payment_status_type.dart';
-import 'package:web_dex/model/coin_type.dart';
 import 'package:web_dex/model/forms/fiat/currency_input.dart';
 import 'package:web_dex/model/forms/fiat/fiat_amount_input.dart';
 import 'package:web_dex/shared/utils/extensions/string_extensions.dart';
+import 'package:web_dex/views/fiat/webview_dialog.dart' show WebViewDialogMode;
 
 part 'fiat_form_event.dart';
 part 'fiat_form_state.dart';
@@ -43,8 +45,8 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
     on<FiatFormWebViewClosed>(_onWebViewClosed);
     on<FiatFormAssetAddressUpdated>(_onAssetAddressUpdated);
 
-    // debounce used here instead of restartable, since multiple user actions
-    // can trigger this event, and restartable results in hitching
+    // transformer used here to restart the stream when a new event is added
+    // (i.e. from user input).
     on<FiatFormRefreshed>(_onRefreshForm, transformer: restartable());
     on<FiatFormCurrenciesFetched>(
       _onLoadCurrencyLists,
@@ -90,7 +92,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
 
     try {
       if (!await _sdk.auth.isSignedIn()) {
-        return emit(state.copyWith(selectedAssetAddress: null));
+        return emit(state.copyWith(selectedAssetAddress: () => null));
       }
 
       final asset = event.selectedCoin.toAsset(_sdk);
@@ -99,13 +101,13 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
 
       emit(
         state.copyWith(
-          selectedAssetAddress: address,
-          selectedCoinPubkeys: assetPubkeys,
+          selectedAssetAddress: address != null ? () => address : null,
+          selectedCoinPubkeys: () => assetPubkeys,
         ),
       );
     } catch (e, s) {
       _log.shout('Error getting pubkeys for selected coin', e, s);
-      emit(state.copyWith(selectedAssetAddress: null));
+      emit(state.copyWith(selectedAssetAddress: () => null));
     }
   }
 
@@ -129,7 +131,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
           state.fiatAmount.value,
           selectedPaymentMethod: event.paymentMethod,
         ),
-        fiatOrderStatus: FiatOrderStatus.pending,
+        fiatOrderStatus: FiatOrderStatus.initial,
         status: FiatFormStatus.initial,
       ),
     );
@@ -151,13 +153,14 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
 
     try {
       final newOrder = await _fiatRepository.buyCoin(
-        state.selectedAssetAddress!.address,
-        state.selectedFiat.value!.symbol,
-        state.selectedAsset.value!,
-        state.selectedAssetAddress!.address,
-        state.selectedPaymentMethod,
-        state.fiatAmount.value,
-        BaseFiatProvider.successUrl(state.selectedAssetAddress!.address),
+        accountReference: state.selectedAssetAddress!.address,
+        source: state.selectedFiat.value!.getAbbr(),
+        target: state.selectedAsset.value!,
+        walletAddress: state.selectedAssetAddress!.address,
+        paymentMethod: state.selectedPaymentMethod,
+        sourceAmount: state.fiatAmount.value,
+        returnUrlOnSuccess:
+            BaseFiatProvider.successUrl(state.selectedAssetAddress!.address),
       );
 
       if (!newOrder.error.isNone) {
@@ -165,7 +168,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
         return emit(_parseOrderError(newOrder.error));
       }
 
-      final checkoutUrl = newOrder.checkoutUrl as String? ?? '';
+      var checkoutUrl = newOrder.checkoutUrl as String? ?? '';
       if (checkoutUrl.isEmpty) {
         _log.severe('Invalid checkout URL received.');
         return emit(
@@ -175,12 +178,19 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
         );
       }
 
+      // Only Ramp on web requires the intermediate html page to satisfy cors
+      // rules and allow for console.log and postMessage events to be handled.
+      // Banxa does not use `postMessage` and does not require this.
+      checkoutUrl = BaseFiatProvider.fiatWrapperPageUrl(checkoutUrl);
+      final webViewMode = _determineWebViewMode();
+
       emit(
         state.copyWith(
           checkoutUrl: checkoutUrl,
           orderId: newOrder.id,
           status: FiatFormStatus.success,
           fiatOrderStatus: FiatOrderStatus.submitted,
+          webViewMode: webViewMode,
         ),
       );
     } catch (e, s) {
@@ -194,6 +204,25 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
     }
   }
 
+  /// Determines the appropriate WebViewDialogMode based on platform and
+  /// environment
+  WebViewDialogMode _determineWebViewMode() {
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    const bool isWeb = kIsWeb || kIsWasm;
+    final bool isBanxa = state.selectedPaymentMethod.providerId == 'Banxa';
+
+    // Banxa "Return to Komodo" button attempts to navigate the top window to
+    // the return URL, which is not supported in a dialog. So we need to open
+    // it in a new tab.
+    if (isLinux || (isWeb && isBanxa)) {
+      return WebViewDialogMode.newTab;
+    } else if (isWeb) {
+      return WebViewDialogMode.dialog;
+    } else {
+      return WebViewDialogMode.fullscreen;
+    }
+  }
+
   Future<void> _onRefreshForm(
     FiatFormRefreshed event,
     Emitter<FiatFormState> emit,
@@ -219,14 +248,14 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
           return state;
         }
 
-        final asset =
-            _sdk.getSdkAsset(state.selectedAsset.value?.symbol ?? 'BTC-segwit');
+        final asset = _sdk
+            .getSdkAsset(state.selectedAsset.value?.getAbbr() ?? 'BTC-segwit');
         final pubkeys = await _sdk.pubkeys.getPubkeys(asset);
         final address = pubkeys.keys.firstOrNull;
 
         return state.copyWith(
-          selectedAssetAddress: address,
-          selectedCoinPubkeys: pubkeys,
+          selectedAssetAddress: address != null ? () => address : null,
+          selectedCoinPubkeys: () => pubkeys,
         );
       } catch (e, s) {
         if (attempts >= maxRetries) {
@@ -235,7 +264,9 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
             e,
             s,
           );
-          return state.copyWith(selectedAssetAddress: null);
+          if (state.selectedAssetAddress == null) {
+            return state.copyWith(selectedAssetAddress: () => null);
+          }
         }
 
         _log.warning(
@@ -244,11 +275,11 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
           s,
         );
 
-        await Future.delayed(Duration(milliseconds: 500 * attempts));
+        await Future<void>.delayed(Duration(milliseconds: 500 * attempts));
       }
     }
 
-    return state.copyWith(selectedAssetAddress: null);
+    return state.copyWith(selectedAssetAddress: () => null);
   }
 
   void _onPaymentStatusMessage(
@@ -274,15 +305,15 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
         case PaymentStatusType.widgetClose:
         case PaymentStatusType.widgetCloseRequest:
           updatedStatus = FiatOrderStatus.windowCloseRequested;
-          break;
         case PaymentStatusType.purchaseCreated:
           updatedStatus = FiatOrderStatus.inProgress;
-          break;
         case PaymentStatusType.paymentStatus:
           final status = data['status'] as String? ?? 'declined';
           updatedStatus = FiatOrderStatus.fromString(status);
-          break;
-        default:
+        case PaymentStatusType.widgetConfigFailed:
+        case PaymentStatusType.widgetConfigDone:
+        case PaymentStatusType.widgetCloseRequestCancelled:
+        case PaymentStatusType.offrampSaleCreated:
           break;
       }
 
@@ -300,7 +331,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
   ) {
     emit(
       state.copyWith(
-        selectedAssetAddress: event.address,
+        selectedAssetAddress: () => event.address,
       ),
     );
   }
@@ -322,11 +353,13 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
       emit(state.copyWith(fiatList: fiatList, coinList: coinList));
     } catch (e, s) {
       _log.shout('Error loading currency list', e, s);
-      emit(state.copyWith(
-        fiatList: [],
-        coinList: [],
-        status: FiatFormStatus.failure,
-      ));
+      emit(
+        state.copyWith(
+          fiatList: [],
+          coinList: [],
+          status: FiatFormStatus.failure,
+        ),
+      );
     }
   }
 
@@ -368,13 +401,16 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
     // to allow the user to submit another order
     if (state.fiatOrderStatus != FiatOrderStatus.inProgress) {
       _log.info('WebView closed, resetting order status to pending');
-      emit(state.copyWith(
-        fiatOrderStatus: FiatOrderStatus.pending,
-        checkoutUrl: '',
-      ));
+      emit(
+        state.copyWith(
+          fiatOrderStatus: FiatOrderStatus.initial,
+          checkoutUrl: '',
+        ),
+      );
     } else {
       _log.info(
-          'WebView closed, but order is in progress. Keeping current status.');
+        'WebView closed, but order is in progress. Keeping current status.',
+      );
     }
   }
 
@@ -382,7 +418,9 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
     FiatFormAssetAddressUpdated event,
     Emitter<FiatFormState> emit,
   ) {
-    emit(state.copyWith(selectedAssetAddress: event.selectedAssetAddress));
+    emit(
+      state.copyWith(selectedAssetAddress: () => event.selectedAssetAddress),
+    );
   }
 
   FiatFormState _parseOrderError(FiatBuyOrderError error) {
@@ -394,6 +432,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
       checkoutUrl: '',
       status: FiatFormStatus.failure,
       fiatOrderStatus: FiatOrderStatus.failed,
+      providerError: () => error.title,
     );
   }
 
@@ -423,6 +462,22 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
       maxAmount = Decimal.tryParse(firstLimit.max.toString());
     }
 
+    // Use the minimum transaction amount provided by Ramp and Banxa per coin
+    // to determine the minimum amount that can be purchased. The payment
+    // method list provides a minimum amount for the fiat currency, but this is
+    // not always the same as the minimum amount for the coin.
+    final coinAmount = paymentMethod.priceInfo.coinAmount;
+    final fiatAmount = paymentMethod.priceInfo.fiatAmount;
+    final minPurchaseAmount =
+        state.selectedAsset.value?.minPurchaseAmount ?? Decimal.zero;
+    if (coinAmount < minPurchaseAmount && coinAmount > Decimal.zero) {
+      final minFiatAmount = ((minPurchaseAmount * fiatAmount) / coinAmount)
+          .toDecimal(scaleOnInfinitePrecision: 18);
+      minAmount = minAmount != null && minAmount > minFiatAmount
+          ? minAmount
+          : minFiatAmount;
+    }
+
     return FiatAmountInput.dirty(
       amount,
       minValue: minAmount,
@@ -435,6 +490,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
 
     yield state.copyWith(
       fiatAmount: _getAmountInputWithBounds(state.fiatAmount.value),
+      providerError: () => null,
     );
 
     try {
@@ -445,6 +501,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
       yield state.copyWith(
         paymentMethods: [],
         status: FiatFormStatus.failure,
+        providerError: () => null,
       );
     }
   }
@@ -453,7 +510,8 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
     if (_hasValidFiatAmount()) {
       yield state.copyWith(
         status: FiatFormStatus.loading,
-        fiatOrderStatus: FiatOrderStatus.pending,
+        fiatOrderStatus: FiatOrderStatus.initial,
+        providerError: () => null,
       );
     } else {
       yield _defaultPaymentMethods();
@@ -462,7 +520,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
 
   Stream<FiatFormState> _fetchAndUpdatePaymentMethods() async* {
     final methodsStream = _fiatRepository.getPaymentMethodsList(
-      state.selectedFiat.value!.symbol,
+      state.selectedFiat.value!.getAbbr(),
       state.selectedAsset.value!,
       _getSourceAmount(),
     );
@@ -476,6 +534,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
       yield state.copyWith(
         paymentMethods: [],
         status: FiatFormStatus.failure,
+        providerError: () => null,
       );
     }
   }
@@ -507,6 +566,7 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
           paymentMethods: methods,
           selectedPaymentMethod: method,
           status: FiatFormStatus.success,
+          providerError: () => null,
           fiatAmount: _getAmountInputWithBounds(
             state.fiatAmount.value,
             selectedPaymentMethod: method,
@@ -514,10 +574,16 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
         );
       }
 
-      return state.copyWith(status: FiatFormStatus.success);
+      return state.copyWith(
+        status: FiatFormStatus.success,
+        providerError: () => null,
+      );
     } catch (e, s) {
       _log.shout('Error updating payment methods', e, s);
-      return state.copyWith(paymentMethods: []);
+      return state.copyWith(
+        paymentMethods: [],
+        providerError: () => null,
+      );
     }
   }
 
@@ -526,7 +592,8 @@ class FiatFormBloc extends Bloc<FiatFormEvent, FiatFormState> {
       paymentMethods: defaultFiatPaymentMethods,
       selectedPaymentMethod: defaultFiatPaymentMethods.first,
       status: FiatFormStatus.initial,
-      fiatOrderStatus: FiatOrderStatus.pending,
+      fiatOrderStatus: FiatOrderStatus.initial,
+      providerError: () => null,
     );
   }
 }
diff --git a/lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart b/lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart
index a736470ee2..2c266731e1 100644
--- a/lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart
+++ b/lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart
@@ -18,20 +18,18 @@ final class FiatFormState extends Equatable with FormzMixin {
     required this.fiatList,
     required this.coinList,
     this.status = FiatFormStatus.initial,
-    this.fiatOrderStatus = FiatOrderStatus.pending,
+    this.fiatOrderStatus = FiatOrderStatus.initial,
     this.fiatMode = FiatMode.onramp,
     this.selectedAssetAddress,
     this.selectedCoinPubkeys,
+    this.webViewMode = WebViewDialogMode.fullscreen,
+    this.providerError,
   });
 
   /// Creates an initial state with default values.
   FiatFormState.initial()
-      : selectedFiat = const CurrencyInput.dirty(
-          FiatCurrency('USD', 'United States Dollar'),
-        ),
-        selectedAsset = const CurrencyInput.dirty(
-          CryptoCurrency('BTC-segwit', 'Bitcoin', CoinType.utxo),
-        ),
+      : selectedFiat = CurrencyInput.dirty(FiatCurrency.usd()),
+        selectedAsset = CurrencyInput.dirty(CryptoCurrency.bitcoin()),
         fiatAmount = const FiatAmountInput.pure(),
         selectedAssetAddress = null,
         selectedPaymentMethod = FiatPaymentMethod.none,
@@ -41,9 +39,11 @@ final class FiatFormState extends Equatable with FormzMixin {
         paymentMethods = const [],
         fiatList = const [],
         coinList = const [],
-        fiatOrderStatus = FiatOrderStatus.pending,
+        fiatOrderStatus = FiatOrderStatus.initial,
         fiatMode = FiatMode.onramp,
-        selectedCoinPubkeys = null;
+        selectedCoinPubkeys = null,
+        webViewMode = WebViewDialogMode.fullscreen,
+        providerError = null;
 
   /// The selected fiat currency to use to purchase [selectedAsset].
   final CurrencyInput<FiatCurrency> selectedFiat;
@@ -90,15 +90,21 @@ final class FiatFormState extends Equatable with FormzMixin {
   /// once the order history tab is implemented
   final FiatMode fiatMode;
 
+  /// The mode to use for displaying the WebView dialog
+  final WebViewDialogMode webViewMode;
+
+  /// Raw error message from the provider when there is an order error
+  final String? providerError;
+
   /// Gets the transaction limit from the selected payment method
   FiatTransactionLimit? get transactionLimit =>
       selectedPaymentMethod.transactionLimits.firstOrNull;
 
   /// The minimum fiat amount that is allowed for the selected payment method
-  Decimal? get minFiatAmount => transactionLimit?.min;
+  Decimal? get minFiatAmount => fiatAmount.minValue ?? transactionLimit?.min;
 
   /// The maximum fiat amount that is allowed for the selected payment method
-  Decimal? get maxFiatAmount => transactionLimit?.max;
+  Decimal? get maxFiatAmount => fiatAmount.maxValue ?? transactionLimit?.max;
 
   /// Whether currencies are still being loaded
   bool get isLoadingCurrencies => fiatList.length < 2 || coinList.length < 2;
@@ -119,7 +125,7 @@ final class FiatFormState extends Equatable with FormzMixin {
     CurrencyInput<CryptoCurrency>? selectedAsset,
     FiatAmountInput? fiatAmount,
     FiatPaymentMethod? selectedPaymentMethod,
-    PubkeyInfo? selectedAssetAddress,
+    ValueGetter<PubkeyInfo?>? selectedAssetAddress,
     String? checkoutUrl,
     String? orderId,
     FiatFormStatus? status,
@@ -128,14 +134,18 @@ final class FiatFormState extends Equatable with FormzMixin {
     Iterable<CryptoCurrency>? coinList,
     FiatOrderStatus? fiatOrderStatus,
     FiatMode? fiatMode,
-    AssetPubkeys? selectedCoinPubkeys,
+    ValueGetter<AssetPubkeys?>? selectedCoinPubkeys,
+    WebViewDialogMode? webViewMode,
+    ValueGetter<String?>? providerError,
   }) {
     return FiatFormState(
       selectedFiat: selectedFiat ?? this.selectedFiat,
       selectedAsset: selectedAsset ?? this.selectedAsset,
       selectedPaymentMethod:
           selectedPaymentMethod ?? this.selectedPaymentMethod,
-      selectedAssetAddress: selectedAssetAddress ?? this.selectedAssetAddress,
+      selectedAssetAddress: selectedAssetAddress != null
+          ? selectedAssetAddress()
+          : this.selectedAssetAddress,
       checkoutUrl: checkoutUrl ?? this.checkoutUrl,
       orderId: orderId ?? this.orderId,
       fiatAmount: fiatAmount ?? this.fiatAmount,
@@ -145,7 +155,12 @@ final class FiatFormState extends Equatable with FormzMixin {
       coinList: coinList ?? this.coinList,
       fiatOrderStatus: fiatOrderStatus ?? this.fiatOrderStatus,
       fiatMode: fiatMode ?? this.fiatMode,
-      selectedCoinPubkeys: selectedCoinPubkeys ?? this.selectedCoinPubkeys,
+      selectedCoinPubkeys: selectedCoinPubkeys != null
+          ? selectedCoinPubkeys()
+          : this.selectedCoinPubkeys,
+      webViewMode: webViewMode ?? this.webViewMode,
+      providerError:
+          providerError != null ? providerError() : this.providerError,
     );
   }
 
@@ -172,5 +187,7 @@ final class FiatFormState extends Equatable with FormzMixin {
         fiatOrderStatus,
         fiatMode,
         selectedCoinPubkeys,
+        webViewMode,
+        providerError,
       ];
 }
diff --git a/lib/bloc/fiat/fiat_order_status.dart b/lib/bloc/fiat/fiat_order_status.dart
index 2a39a337cc..72bcf77a31 100644
--- a/lib/bloc/fiat/fiat_order_status.dart
+++ b/lib/bloc/fiat/fiat_order_status.dart
@@ -1,12 +1,17 @@
 // TODO: Differentiate between different error and in-progress statuses
+import 'package:logging/logging.dart';
+
 enum FiatOrderStatus {
-  /// User has not yet started the payment process
-  pending,
+  /// Initial status: User has not yet started the payment process
+  initial,
 
   /// User has started the process, and the payment method has been opened.
   /// E.g. Ramp or Banxa websites have been opened
   submitted,
 
+  /// Payment is awaiting user action (e.g., user needs to complete payment)
+  pendingPayment,
+
   /// Payment has been submitted with the provider, and is being processed
   inProgress,
 
@@ -14,16 +19,18 @@ enum FiatOrderStatus {
   success,
 
   /// Payment has been cancelled, declined, expired or refunded
-  failed, 
-  
-  /// The user closed the payment window using the provider close button 
+  failed,
+
+  /// The user closed the payment window using the provider close button
   /// or "return to Komodo Wallet" button
   windowCloseRequested;
 
   bool get isTerminal =>
       this == FiatOrderStatus.success || this == FiatOrderStatus.failed;
   bool get isSubmitting =>
-      this == FiatOrderStatus.inProgress || this == FiatOrderStatus.submitted;
+      this == FiatOrderStatus.inProgress ||
+      this == FiatOrderStatus.submitted ||
+      this == FiatOrderStatus.pendingPayment;
   bool get isFailed => this == FiatOrderStatus.failed;
   bool get isSuccess => this == FiatOrderStatus.success;
 
@@ -32,7 +39,8 @@ enum FiatOrderStatus {
   static FiatOrderStatus fromString(String status) {
     // The case statements are references to Banxa's order statuses. See the
     // docs link here for more info: https://docs.banxa.com/docs/order-status
-    switch (status) {
+    final normalized = status.toLowerCase();
+    switch (normalized) {
       case 'complete':
         return FiatOrderStatus.success;
 
@@ -42,18 +50,25 @@ enum FiatOrderStatus {
       case 'refunded':
         return FiatOrderStatus.failed;
 
-      case 'extraVerification':
-      case 'pendingPayment':
-      case 'waitingPayment':
-        return FiatOrderStatus.pending;
+      case 'extraverification':
+      case 'pendingpayment':
+      case 'waitingpayment':
+        return FiatOrderStatus.pendingPayment;
 
-      case 'paymentReceived':
-      case 'inProgress':
-      case 'coinTransferred':
+      case 'paymentreceived':
+      case 'inprogress':
+      case 'cointransferred':
+      case 'cryptotransferred':
         return FiatOrderStatus.inProgress;
 
       default:
-        throw Exception('Unknown status: $status');
+        // Default to in progress if the status is not recognized
+        // to avoid alarming users with "Payment failed" popup messages
+        // unless we are sure that the payment has failed.
+        // Ideally, this section should not be reached.
+        Logger('FiatOrderStatus')
+            .warning('Unknown status: $status, defaulting to in progress');
+        return FiatOrderStatus.inProgress;
     }
   }
 }
diff --git a/lib/bloc/fiat/fiat_repository.dart b/lib/bloc/fiat/fiat_repository.dart
index f006032102..a6444b5267 100644
--- a/lib/bloc/fiat/fiat_repository.dart
+++ b/lib/bloc/fiat/fiat_repository.dart
@@ -1,5 +1,4 @@
 import 'package:decimal/decimal.dart';
-import 'package:universal_html/html.dart';
 import 'package:web_dex/app_config/app_config.dart';
 import 'package:web_dex/bloc/coins_bloc/coins_repo.dart';
 import 'package:web_dex/bloc/fiat/base_fiat_provider.dart';
@@ -51,8 +50,12 @@ class FiatRepository {
 
     for (final currencyList in results) {
       for (final currency in currencyList) {
-        bool isCoinUnknown() => !knownCoins.containsKey(currency.getAbbr());
-        if (isCoin && (currency.isFiat || isCoinUnknown())) {
+        final isCoinSupported = knownCoins.containsKey(currency.getAbbr());
+        if (isCoin && (currency.isFiat || !isCoinSupported)) {
+          _log.fine(
+            'Skipping ${currency.getAbbr()} because it is not a coin or '
+            'not supported (${currency.configSymbol})',
+          );
           continue;
         }
 
@@ -252,15 +255,15 @@ class FiatRepository {
     );
   }
 
-  Future<FiatBuyOrderInfo> buyCoin(
-    String accountReference,
-    String source,
-    ICurrency target,
-    String walletAddress,
-    FiatPaymentMethod paymentMethod,
-    String sourceAmount,
-    String returnUrlOnSuccess,
-  ) async {
+  Future<FiatBuyOrderInfo> buyCoin({
+    required String accountReference,
+    required String source,
+    required ICurrency target,
+    required String walletAddress,
+    required FiatPaymentMethod paymentMethod,
+    required String sourceAmount,
+    required String returnUrlOnSuccess,
+  }) async {
     final provider = _getPaymentMethodProvider(paymentMethod);
     if (provider == null) return Future.error('Provider not found');
 
diff --git a/lib/bloc/fiat/models/fiat_buy_order_error.dart b/lib/bloc/fiat/models/fiat_buy_order_error.dart
index d4d436e8d4..ddfe524cbd 100644
--- a/lib/bloc/fiat/models/fiat_buy_order_error.dart
+++ b/lib/bloc/fiat/models/fiat_buy_order_error.dart
@@ -18,6 +18,11 @@ class FiatBuyOrderError extends Equatable {
 
   const FiatBuyOrderError.none() : this(code: 0, status: 0, title: '');
 
+  /// Error indicating a parsing issue with the response data
+  const FiatBuyOrderError.parsing({
+    String message = 'Failed to parse response data',
+  }) : this(code: -1, status: 400, title: message);
+
   bool get isNone => this == const FiatBuyOrderError.none();
 
   final int code;
diff --git a/lib/bloc/fiat/models/fiat_buy_order_info.dart b/lib/bloc/fiat/models/fiat_buy_order_info.dart
index 3c7a3e78cc..cdf29a0ee9 100644
--- a/lib/bloc/fiat/models/fiat_buy_order_info.dart
+++ b/lib/bloc/fiat/models/fiat_buy_order_info.dart
@@ -21,7 +21,7 @@ class FiatBuyOrderInfo extends Equatable {
     required this.error,
   });
 
-  FiatBuyOrderInfo.info()
+  FiatBuyOrderInfo.fromCheckoutUrl(String url)
       : this(
           id: '',
           accountId: '',
@@ -34,12 +34,12 @@ class FiatBuyOrderInfo extends Equatable {
           extAccountId: '',
           network: '',
           paymentCode: '',
-          checkoutUrl: '',
+          checkoutUrl: url,
           createdAt: '',
           error: const FiatBuyOrderError.none(),
         );
 
-  FiatBuyOrderInfo.fromCheckoutUrl(String url)
+  FiatBuyOrderInfo.empty()
       : this(
           id: '',
           accountId: '',
@@ -52,18 +52,30 @@ class FiatBuyOrderInfo extends Equatable {
           extAccountId: '',
           network: '',
           paymentCode: '',
-          checkoutUrl: url,
+          checkoutUrl: '',
           createdAt: '',
           error: const FiatBuyOrderError.none(),
         );
 
   factory FiatBuyOrderInfo.fromJson(Map<String, dynamic> json) {
-    Map<String, dynamic> data = json;
-    if (json['data'] != null) {
-      final orderData = json['data'] as Map<String, dynamic>? ?? {};
-      data = orderData['order'] as Map<String, dynamic>? ?? {};
+    final jsonData = json['data'] as Map<String, dynamic>?;
+    final errors = json['errors'] as Map<String, dynamic>?;
+
+    if (json['data'] == null && errors == null) {
+      return FiatBuyOrderInfo.empty().copyWith(
+        error:
+            const FiatBuyOrderError.parsing(message: 'Missing order payload'),
+      );
     }
 
+    if (jsonData == null && errors != null) {
+      return FiatBuyOrderInfo.empty().copyWith(
+        error: FiatBuyOrderError.fromJson(errors),
+      );
+    }
+
+    final data = jsonData!['order'] as Map<String, dynamic>;
+
     return FiatBuyOrderInfo(
       id: data['id'] as String? ?? '',
       accountId: data['account_id'] as String? ?? '',
@@ -78,8 +90,8 @@ class FiatBuyOrderInfo extends Equatable {
       paymentCode: data['payment_code'] as String? ?? '',
       checkoutUrl: data['checkout_url'] as String? ?? '',
       createdAt: assertString(data['created_at']) ?? '',
-      error: data['errors'] != null
-          ? FiatBuyOrderError.fromJson(data['errors'] as Map<String, dynamic>)
+      error: errors != null
+          ? FiatBuyOrderError.fromJson(errors)
           : const FiatBuyOrderError.none(),
     );
   }
diff --git a/lib/bloc/fiat/models/fiat_price_info.dart b/lib/bloc/fiat/models/fiat_price_info.dart
index eed0081407..774a6e19d7 100644
--- a/lib/bloc/fiat/models/fiat_price_info.dart
+++ b/lib/bloc/fiat/models/fiat_price_info.dart
@@ -10,25 +10,25 @@ class FiatPriceInfo extends Equatable {
     required this.spotPriceIncludingFee,
   });
 
-  static final zero = FiatPriceInfo(
-    fiatAmount: Decimal.zero,
-    coinAmount: Decimal.zero,
-    fiatCode: '',
-    coinCode: '',
-    spotPriceIncludingFee: Decimal.zero,
-  );
-
   factory FiatPriceInfo.fromJson(Map<String, dynamic> json) {
     return FiatPriceInfo(
-      fiatAmount: Decimal.parse(json['fiat_amount']?.toString() ?? '0'),
-      coinAmount: Decimal.parse(json['coin_amount']?.toString() ?? '0'),
+      fiatAmount: _safeParseDecimal(json['fiat_amount']),
+      coinAmount: _safeParseDecimal(json['coin_amount']),
       fiatCode: json['fiat_code'] as String? ?? '',
       coinCode: json['coin_code'] as String? ?? '',
       spotPriceIncludingFee:
-          Decimal.parse(json['spot_price_including_fee']?.toString() ?? '0'),
+          _safeParseDecimal(json['spot_price_including_fee']),
     );
   }
 
+  static final zero = FiatPriceInfo(
+    fiatAmount: Decimal.zero,
+    coinAmount: Decimal.zero,
+    fiatCode: '',
+    coinCode: '',
+    spotPriceIncludingFee: Decimal.zero,
+  );
+
   final Decimal fiatAmount;
   final Decimal coinAmount;
   final String fiatCode;
@@ -62,6 +62,14 @@ class FiatPriceInfo extends Equatable {
     };
   }
 
+  static Decimal _safeParseDecimal(dynamic value) {
+    try {
+      return Decimal.parse(value?.toString() ?? '0');
+    } on FormatException {
+      return Decimal.zero;
+    }
+  }
+
   @override
   List<Object?> get props => [
         fiatAmount,
diff --git a/lib/bloc/fiat/models/i_currency.dart b/lib/bloc/fiat/models/i_currency.dart
index 6ed95577e8..623251ea13 100644
--- a/lib/bloc/fiat/models/i_currency.dart
+++ b/lib/bloc/fiat/models/i_currency.dart
@@ -1,3 +1,4 @@
+import 'package:decimal/decimal.dart';
 import 'package:komodo_defi_sdk/komodo_defi_sdk.dart';
 import 'package:komodo_defi_types/komodo_defi_types.dart';
 import 'package:web_dex/bloc/coins_bloc/asset_coin_extension.dart';
@@ -6,11 +7,24 @@ import 'package:web_dex/model/coin_utils.dart';
 
 /// Base class for all currencies
 abstract class ICurrency {
-  const ICurrency(this.symbol, this.name);
+  const ICurrency({
+    required this.symbol,
+    required this.name,
+    required this.minPurchaseAmount,
+  });
 
+  /// The symbol/code of the currency (e.g. BTC, USD). Note that this usually
+  /// excludes any chain identifiers like "-segwit" or "-erc20".
   final String symbol;
+
+  /// The full name of the currency (e.g. Bitcoin, US Dollar).
   final String name;
 
+  /// The minimum purchase amount for this currency. This is used to
+  /// determine the minimum amount that can be purchased in a fiat
+  /// transaction.
+  final Decimal minPurchaseAmount;
+
   /// Returns true if the currency is a fiat currency (e.g. USD)
   bool get isFiat;
 
@@ -29,11 +43,22 @@ abstract class ICurrency {
   ICurrency copyWith({
     String? symbol,
     String? name,
+    Decimal? minPurchaseAmount,
   });
 }
 
 class FiatCurrency extends ICurrency {
-  const FiatCurrency(super.symbol, super.name);
+  const FiatCurrency({
+    required super.symbol,
+    required super.name,
+    required super.minPurchaseAmount,
+  });
+
+  factory FiatCurrency.usd() => FiatCurrency(
+        symbol: 'USD',
+        name: 'United States Dollar',
+        minPurchaseAmount: Decimal.zero,
+      );
 
   @override
   bool get isFiat => true;
@@ -41,7 +66,6 @@ class FiatCurrency extends ICurrency {
   @override
   bool get isCrypto => false;
 
-  
   @override
   String get configSymbol => symbol;
 
@@ -49,23 +73,41 @@ class FiatCurrency extends ICurrency {
   FiatCurrency copyWith({
     String? symbol,
     String? name,
+    Decimal? minPurchaseAmount,
   }) {
     return FiatCurrency(
-      symbol ?? this.symbol,
-      name ?? this.name,
+      symbol: symbol ?? this.symbol,
+      name: name ?? this.name,
+      minPurchaseAmount: minPurchaseAmount ?? this.minPurchaseAmount,
     );
   }
 }
 
 class CryptoCurrency extends ICurrency {
-  const CryptoCurrency(super.symbol, super.name, this.chainType);
+  const CryptoCurrency({
+    required super.symbol,
+    required super.name,
+    required this.chainType,
+    required super.minPurchaseAmount,
+  });
+
+  factory CryptoCurrency.bitcoin() => CryptoCurrency(
+        symbol: 'BTC-segwit',
+        name: 'Bitcoin',
+        chainType: CoinType.utxo,
+        minPurchaseAmount: Decimal.zero,
+      );
 
-  factory CryptoCurrency.fromAsset(Asset asset) {
+  factory CryptoCurrency.fromAsset(
+    Asset asset, {
+    required Decimal minPurchaseAmount,
+  }) {
     final coin = asset.toCoin();
     return CryptoCurrency(
-      coin.id.id,
-      coin.name,
-      coin.type,
+      symbol: coin.id.id,
+      name: coin.name,
+      chainType: coin.type,
+      minPurchaseAmount: minPurchaseAmount,
     );
   }
 
@@ -82,10 +124,26 @@ class CryptoCurrency extends ICurrency {
 
   @override
   String getAbbr() {
-    return symbol;
-
-    // TODO: figure out if this is still necessary
-    // return '$symbol-${getCoinTypeName(chainType).replaceAll('-', '')}';
+    // TODO: look into a better way to do this when migrating to the SDK
+    // Providers return "ETH" with chain type "ERC20", resultning in abbr of
+    // "ETH-ERC20", which is not how it is stored in our coins configuration
+    // files. "ETH" is the expected abbreviation, which would just be `symbol`.
+    if (chainType == CoinType.utxo ||
+        (chainType == CoinType.cosmos && symbol == 'ATOM') ||
+        (chainType == CoinType.erc20 && symbol == 'ETH') ||
+        (chainType == CoinType.bep20 && symbol == 'BNB') ||
+        (chainType == CoinType.avx20 && symbol == 'AVAX') ||
+        (chainType == CoinType.etc && symbol == 'ETC') ||
+        (chainType == CoinType.ftm20 && symbol == 'FTM') ||
+        (chainType == CoinType.arb20 && symbol == 'ARB') ||
+        (chainType == CoinType.hrc20 && symbol == 'ONE') ||
+        (chainType == CoinType.plg20 && symbol == 'MATIC') ||
+        (chainType == CoinType.mvr20 && symbol == 'MOVR') ||
+        (chainType == CoinType.krc20 && symbol == 'KCS')) {
+      return symbol;
+    }
+
+    return '$symbol-${getCoinTypeName(chainType).replaceAll('-', '')}';
   }
 
   @override
@@ -103,11 +161,13 @@ class CryptoCurrency extends ICurrency {
     String? symbol,
     String? name,
     CoinType? chainType,
+    Decimal? minPurchaseAmount,
   }) {
     return CryptoCurrency(
-      symbol ?? this.symbol,
-      name ?? this.name,
-      chainType ?? this.chainType,
+      symbol: symbol ?? this.symbol,
+      name: name ?? this.name,
+      chainType: chainType ?? this.chainType,
+      minPurchaseAmount: minPurchaseAmount ?? this.minPurchaseAmount,
     );
   }
 }
diff --git a/lib/bloc/fiat/payment_status_type.dart b/lib/bloc/fiat/payment_status_type.dart
index 7e691efe1c..7f4200af90 100644
--- a/lib/bloc/fiat/payment_status_type.dart
+++ b/lib/bloc/fiat/payment_status_type.dart
@@ -1,18 +1,18 @@
 enum PaymentStatusType {
-  widgetClose("WIDGET_CLOSE"),
-  widgetConfigDone("WIDGET_CONFIG_DONE"),
-  widgetConfigFailed("WIDGET_CONFIG_FAILED"),
-  widgetCloseRequest("WIDGET_CLOSE_REQUEST"),
-  widgetCloseRequestCancelled("WIDGET_CLOSE_REQUEST_CANCELLED"),
-  widgetCloseRequestConfirmed("WIDGET_CLOSE_REQUEST_CONFIRMED"),
-  purchaseCreated("PURCHASE_CREATED"),
-  offrampSaleCreated("OFFRAMP_SALE_CREATED"),
-  paymentStatus("PAYMENT-STATUS");
-
-  final String value;
+  widgetClose('WIDGET_CLOSE'),
+  widgetConfigDone('WIDGET_CONFIG_DONE'),
+  widgetConfigFailed('WIDGET_CONFIG_FAILED'),
+  widgetCloseRequest('WIDGET_CLOSE_REQUEST'),
+  widgetCloseRequestCancelled('WIDGET_CLOSE_REQUEST_CANCELLED'),
+  widgetCloseRequestConfirmed('WIDGET_CLOSE_REQUEST_CONFIRMED'),
+  purchaseCreated('PURCHASE_CREATED'),
+  offrampSaleCreated('OFFRAMP_SALE_CREATED'),
+  paymentStatus('PAYMENT-STATUS');
 
   const PaymentStatusType(this.value);
 
+  final String value;
+
   /// Creates a RampWidgetStatusEvents from a JSON input
   static PaymentStatusType fromJson(Map<String, dynamic> json) {
     try {
@@ -21,7 +21,7 @@ enum PaymentStatusType {
       if (json.containsKey('type')) {
         typeValue = json['type'] as String;
       } else {
-        throw FormatException('Missing "type" field in JSON object');
+        throw const FormatException('Missing "type" field in JSON object');
       }
 
       return PaymentStatusType.values.firstWhere(
diff --git a/lib/bloc/fiat/ramp/models/ramp_asset_info.dart b/lib/bloc/fiat/ramp/models/ramp_asset_info.dart
index 40ac520f4d..72add2ffc6 100644
--- a/lib/bloc/fiat/ramp/models/ramp_asset_info.dart
+++ b/lib/bloc/fiat/ramp/models/ramp_asset_info.dart
@@ -58,9 +58,6 @@ class RampAssetInfo {
     required this.symbol,
     required this.decimals,
     required this.price,
-    this.minPurchaseAmount,
-    this.maxPurchaseAmount,
-    this.address,
     required this.chain,
     required this.currencyCode,
     required this.enabled,
@@ -69,6 +66,9 @@ class RampAssetInfo {
     required this.minPurchaseCryptoAmount,
     required this.networkFee,
     required this.type,
+    this.minPurchaseAmount,
+    this.maxPurchaseAmount,
+    this.address,
   });
 
   /// Returns true if this asset has a valid minimum purchase amount.
diff --git a/lib/bloc/fiat/ramp/ramp_fiat_provider.dart b/lib/bloc/fiat/ramp/ramp_fiat_provider.dart
index bcabcca3cd..a25b8a06a3 100644
--- a/lib/bloc/fiat/ramp/ramp_fiat_provider.dart
+++ b/lib/bloc/fiat/ramp/ramp_fiat_provider.dart
@@ -33,13 +33,13 @@ class RampFiatProvider extends BaseFiatProvider {
     return providerId;
   }
 
-  String getFullCoinCode(ICurrency target) {
-    return '${getCoinChainId(target as CryptoCurrency)}_${target.configSymbol}';
+  String getFullCoinCode(CryptoCurrency target) {
+    return '${getCoinChainId(target)}_${target.configSymbol}';
   }
 
   Future<dynamic> _getPaymentMethods(
     String source,
-    ICurrency target, {
+    CryptoCurrency target, {
     String? sourceAmount,
   }) =>
       apiRequest(
@@ -51,6 +51,8 @@ class RampFiatProvider extends BaseFiatProvider {
         body: {
           'fiatCurrency': source,
           'cryptoAssetSymbol': getFullCoinCode(target),
+          // fiatValue has to be a number, and not a string. Force it to be a
+          // double here to ensure that it is in the expected format.
           'fiatValue': sourceAmount != null
               ? Decimal.tryParse(sourceAmount)?.toDouble()
               : null,
@@ -59,7 +61,7 @@ class RampFiatProvider extends BaseFiatProvider {
 
   Future<dynamic> _getPricesWithPaymentMethod(
     String source,
-    ICurrency target,
+    CryptoCurrency target,
     String sourceAmount,
     FiatPaymentMethod paymentMethod,
   ) =>
@@ -101,8 +103,9 @@ class RampFiatProvider extends BaseFiatProvider {
         .where((item) => item['onrampAvailable'] as bool)
         .map(
           (item) => FiatCurrency(
-            item['fiatCurrency'] as String,
-            item['name'] as String,
+            symbol: item['fiatCurrency'] as String,
+            name: item['name'] as String,
+            minPurchaseAmount: Decimal.zero,
           ),
         )
         .toList();
@@ -110,23 +113,37 @@ class RampFiatProvider extends BaseFiatProvider {
 
   @override
   Future<List<CryptoCurrency>> getCoinList() async {
-    final response = await _getCoins();
-    final data = response['assets'] as List<dynamic>;
-    return data
-        .map((item) {
-          final coinType = getCoinType(item['chain'] as String);
-          if (coinType == null) {
-            return null;
-          }
-          return CryptoCurrency(
-            item['symbol'] as String,
-            item['name'] as String,
-            coinType,
-          );
-        })
-        .where((e) => e != null)
-        .cast<CryptoCurrency>()
-        .toList();
+    try {
+      final response = await _getCoins();
+      final config =
+          HostAssetsConfig.fromJson(response as Map<String, dynamic>);
+
+      return config.assets
+          .map((asset) {
+            final coinType = getCoinType(asset.chain);
+            if (coinType == null) {
+              return null;
+            }
+
+            if (rampUnsupportedCoinsList.contains(asset.symbol)) {
+              _log.warning('Ramp does not support ${asset.symbol}');
+              return null;
+            }
+
+            return CryptoCurrency(
+              symbol: asset.symbol,
+              name: asset.name,
+              chainType: coinType,
+              minPurchaseAmount: asset.minPurchaseAmount ?? Decimal.zero,
+            );
+          })
+          .where((e) => e != null)
+          .cast<CryptoCurrency>()
+          .toList();
+    } catch (e, s) {
+      _log.severe('Failed to parse coin list from Ramp', e, s);
+      return [];
+    }
   }
 
   // Turns `APPLE_PAY` to `Apple Pay`
@@ -144,6 +161,10 @@ class RampFiatProvider extends BaseFiatProvider {
     String sourceAmount,
   ) async {
     try {
+      if (target is! CryptoCurrency) {
+        throw ArgumentError('Target currency must be a CryptoCurrency');
+      }
+
       final List<FiatPaymentMethod> paymentMethodsList = [];
 
       final paymentMethodsFuture =
@@ -210,11 +231,12 @@ class RampFiatProvider extends BaseFiatProvider {
     return paymentMethod.transactionFees.first.fees.first.amount;
   }
 
-  Decimal _getFeeAdjustedPrice(
-    FiatPaymentMethod paymentMethod,
-    Decimal price,
-  ) {
-    return (price / (Decimal.one - _getPaymentMethodFee(paymentMethod)))
+  Decimal _getFeeAdjustedPrice(FiatPaymentMethod paymentMethod, Decimal price) {
+    final fee = _getPaymentMethodFee(paymentMethod);
+    if (fee >= Decimal.one) {
+      throw ArgumentError.value(fee, 'fee', 'Fee ratio must be < 1');
+    }
+    return (price / (Decimal.one - fee))
         .toDecimal(scaleOnInfinitePrecision: scaleOnInfinitePrecision);
   }
 
@@ -233,6 +255,10 @@ class RampFiatProvider extends BaseFiatProvider {
     String sourceAmount,
     FiatPaymentMethod paymentMethod,
   ) async {
+    if (target is! CryptoCurrency) {
+      throw ArgumentError('Target currency must be a CryptoCurrency');
+    }
+
     final response = await _getPricesWithPaymentMethod(
       source,
       target,
@@ -272,6 +298,10 @@ class RampFiatProvider extends BaseFiatProvider {
     String sourceAmount,
     String returnUrlOnSuccess,
   ) async {
+    if (target is! CryptoCurrency) {
+      throw ArgumentError('Target currency must be a CryptoCurrency');
+    }
+
     final payload = {
       'hostApiKey': hostId,
       'hostAppName': appShortTitle,
@@ -283,6 +313,8 @@ class RampFiatProvider extends BaseFiatProvider {
       'fiatCurrency': source,
       'fiatValue': sourceAmount,
       'defaultAsset': getFullCoinCode(target),
+      'hideExitButton': 'true',
+      // 'variant': 'hosted', // desktop, mobile, auto, hosted-mobile
       // if(coinsBloc.walletCoins.isNotEmpty)
       //   "swapAsset": coinsBloc.walletCoins.map((e) => e.abbr).toList().toString(),
       // "swapAsset": fullAssetCode, // This limits the crypto asset list at the redirect page
diff --git a/lib/shared/widgets/coin_item/coin_item.dart b/lib/shared/widgets/coin_item/coin_item.dart
index 0e593547ca..b1005da540 100644
--- a/lib/shared/widgets/coin_item/coin_item.dart
+++ b/lib/shared/widgets/coin_item/coin_item.dart
@@ -6,8 +6,8 @@ import 'package:web_dex/shared/widgets/coin_item/coin_logo.dart';
 
 class CoinItem extends StatelessWidget {
   const CoinItem({
-    super.key,
     required this.coin,
+    super.key,
     this.amount,
     this.size = CoinItemSize.medium,
     this.subtitleText,
diff --git a/lib/views/fiat/fiat_asset_icon.dart b/lib/views/fiat/fiat_asset_icon.dart
index 24474f1cd4..27bbf99c9f 100644
--- a/lib/views/fiat/fiat_asset_icon.dart
+++ b/lib/views/fiat/fiat_asset_icon.dart
@@ -24,12 +24,15 @@ class FiatAssetIcon extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     if (currency.isFiat) {
-      return FiatIcon(symbol: currency.symbol);
+      return FiatIcon(symbol: currency.getAbbr());
     }
 
+    // TODO: standardise the icon layout. CoinItem contains the icon and the 
+    // coin name + protocol, but the provided icon Widget could be anything
+    // and on failure it's usually just the Icon
     if (assetExists ?? false) {
       final sdk = RepositoryProvider.of<KomodoDefiSdk>(context);
-      final asset = sdk.getSdkAsset(currency.symbol);
+      final asset = sdk.getSdkAsset(currency.getAbbr());
       return CoinItem(coin: asset.toCoin(), size: CoinItemSize.large);
     } else {
       return icon;
diff --git a/lib/views/fiat/fiat_currency_list_tile.dart b/lib/views/fiat/fiat_currency_list_tile.dart
index 6923a79897..7ce77fb18f 100644
--- a/lib/views/fiat/fiat_currency_list_tile.dart
+++ b/lib/views/fiat/fiat_currency_list_tile.dart
@@ -51,7 +51,7 @@ class FiatCurrencyListTile extends StatelessWidget {
                 ),
               ],
             )
-          : SizedBox.shrink(),
+          : const SizedBox.shrink(),
       onTap: onTap,
     );
   }
diff --git a/lib/views/fiat/fiat_form.dart b/lib/views/fiat/fiat_form.dart
index b3e3070643..15e5188aa7 100644
--- a/lib/views/fiat/fiat_form.dart
+++ b/lib/views/fiat/fiat_form.dart
@@ -2,14 +2,11 @@ import 'dart:async';
 
 import 'package:app_theme/app_theme.dart';
 import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:komodo_defi_types/komodo_defi_types.dart';
 import 'package:komodo_ui_kit/komodo_ui_kit.dart';
 import 'package:web_dex/bloc/auth_bloc/auth_bloc.dart';
-import 'package:web_dex/bloc/coins_bloc/coins_bloc.dart';
-import 'package:web_dex/bloc/fiat/base_fiat_provider.dart';
 import 'package:web_dex/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart';
 import 'package:web_dex/bloc/fiat/fiat_order_status.dart';
 import 'package:web_dex/bloc/fiat/models/fiat_mode.dart';
@@ -22,6 +19,7 @@ import 'package:web_dex/shared/widgets/connect_wallet/connect_wallet_wrapper.dar
 import 'package:web_dex/views/fiat/fiat_action_tab.dart';
 import 'package:web_dex/views/fiat/fiat_inputs.dart';
 import 'package:web_dex/views/fiat/fiat_payment_methods_grid.dart';
+import 'package:web_dex/views/fiat/fiat_provider_web_view_settings.dart';
 import 'package:web_dex/views/fiat/webview_dialog.dart';
 import 'package:web_dex/views/wallets_manager/wallets_manager_events_factory.dart';
 
@@ -152,7 +150,7 @@ class _FiatFormState extends State<FiatForm> {
   }
 
   void _completeOrder() =>
-      context.read<FiatFormBloc>().add(FiatFormSubmitted());
+      context.read<FiatFormBloc>().add(const FiatFormSubmitted());
 
   void _onFiatChanged(FiatCurrency value) => context.read<FiatFormBloc>()
     ..add(FiatFormFiatSelected(value))
@@ -185,31 +183,6 @@ class _FiatFormState extends State<FiatForm> {
     }
   }
 
-  void _showOrderFailedSnackbar() {
-    ScaffoldMessenger.of(context).showSnackBar(
-      SnackBar(
-        content: Text(LocaleKeys.orderFailedTryAgain.tr()),
-      ),
-    );
-  }
-
-  Future<void> _openCheckoutPage(String checkoutUrl, String orderId) async {
-    if (checkoutUrl.isEmpty) return;
-
-    // Only web requires the intermediate html page to satisfy cors rules and
-    // allow for console.log and postMessage events to be handled.
-    final url =
-        kIsWeb ? BaseFiatProvider.fiatWrapperPageUrl(checkoutUrl) : checkoutUrl;
-
-    return WebViewDialog.show(
-      context,
-      url: url,
-      title: LocaleKeys.buy.tr(),
-      onConsoleMessage: _onConsoleMessage,
-      onCloseWindow: _onCloseWebView,
-    );
-  }
-
   void _onConsoleMessage(String message) {
     context
         .read<FiatFormBloc>()
@@ -230,61 +203,65 @@ class _FiatFormState extends State<FiatForm> {
 
     final status = stateSnapshot.fiatOrderStatus;
     if (status == FiatOrderStatus.submitted) {
-      // ignore: use_build_context_synchronously
       context.read<FiatFormBloc>().add(const FiatFormOrderStatusWatchStarted());
-      await _openCheckoutPage(stateSnapshot.checkoutUrl, stateSnapshot.orderId);
-      return;
-    }
 
-    if (status == FiatOrderStatus.failed) {
-      _showOrderFailedSnackbar();
+      await WebViewDialog.show(
+        context,
+        url: stateSnapshot.checkoutUrl,
+        mode: stateSnapshot.webViewMode,
+        title: LocaleKeys.buy.tr(),
+        onMessage: _onConsoleMessage,
+        onCloseWindow: _onCloseWebView,
+        settings: FiatProviderWebViewSettings.createSecureProviderSettings(),
+      );
     }
 
     if (status == FiatOrderStatus.windowCloseRequested) {
       Navigator.of(context).pop();
     }
 
-    if (status != FiatOrderStatus.pending) {
-      await _showPaymentStatusDialog(status);
+    if (status != FiatOrderStatus.initial) {
+      await _showPaymentStatusDialog(stateSnapshot);
     }
   }
 
-  Future<void> _showPaymentStatusDialog(FiatOrderStatus status) async {
+  Future<void> _showPaymentStatusDialog(FiatFormState state) async {
     if (!mounted) return;
 
     String? title;
     String? content;
-
-    // TODO: Use theme-based semantic colors
     Icon? icon;
 
+    final status = state.fiatOrderStatus;
+
     switch (status) {
       case FiatOrderStatus.inProgress:
       case FiatOrderStatus.windowCloseRequested:
-      case FiatOrderStatus.pending:
+      case FiatOrderStatus.initial:
+      case FiatOrderStatus.pendingPayment:
         debugPrint('Pending status should not be shown in dialog.');
         return;
-
       case FiatOrderStatus.submitted:
         title = LocaleKeys.fiatPaymentSubmittedTitle.tr();
         content = LocaleKeys.fiatPaymentSubmittedMessage.tr();
         icon = const Icon(Icons.open_in_new);
-
       case FiatOrderStatus.success:
         title = LocaleKeys.fiatPaymentSuccessTitle.tr();
         content = LocaleKeys.fiatPaymentSuccessMessage.tr();
         icon = const Icon(Icons.check_circle_outline);
-
       case FiatOrderStatus.failed:
         title = LocaleKeys.fiatPaymentFailedTitle.tr();
-        // TODO: If we implement provider-specific error messages,
-        // we can include support details.
         content = LocaleKeys.fiatPaymentFailedMessage.tr();
+        if (state.providerError != null && state.providerError!.isNotEmpty) {
+          content = '$content\n\n${LocaleKeys.errorDetails.tr()}: '
+              '${state.providerError}';
+        }
         icon = const Icon(Icons.error_outline, color: Colors.red);
     }
 
     await showAdaptiveDialog<void>(
       context: context,
+      barrierDismissible: true,
       builder: (context) => AlertDialog.adaptive(
         title: Text(title!),
         icon: icon,
@@ -311,7 +288,7 @@ extension on FiatAmountValidationError {
       case FiatAmountValidationError.belowMinimum:
         return LocaleKeys.fiatMinimumAmount.tr(
           args: [
-            state.minFiatAmount?.toString() ?? '',
+            state.minFiatAmount?.toStringAsFixed(2) ?? '',
             fiatId,
           ],
         );
diff --git a/lib/views/fiat/fiat_inputs.dart b/lib/views/fiat/fiat_inputs.dart
index fbd2fbe936..4045971097 100644
--- a/lib/views/fiat/fiat_inputs.dart
+++ b/lib/views/fiat/fiat_inputs.dart
@@ -116,10 +116,12 @@ class FiatInputsState extends State<FiatInputs> {
     final fiatListLoading = widget.fiatList.length <= 1;
     final coinListLoading = widget.coinList.length <= 1;
 
-    final boundariesString = widget.fiatMaxAmount == null &&
-            widget.fiatMinAmount == null
-        ? ''
-        : '(${widget.fiatMinAmount ?? '1'} - ${widget.fiatMaxAmount ?? '∞'})';
+    final minFiatAmount = widget.fiatMinAmount?.toStringAsFixed(2);
+    final maxFiatAmount = widget.fiatMaxAmount?.toStringAsFixed(2);
+    final boundariesString =
+        widget.fiatMaxAmount == null && widget.fiatMinAmount == null
+            ? ''
+            : '(${minFiatAmount ?? '1'} - ${maxFiatAmount ?? '∞'})';
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
@@ -135,8 +137,8 @@ class FiatInputsState extends State<FiatInputs> {
             disabled: fiatListLoading,
             currency: widget.initialFiat,
             icon: FiatIcon(
-              key: Key('fiat_icon_${widget.initialFiat.symbol}'),
-              symbol: widget.initialFiat.symbol,
+              key: Key('fiat_icon_${widget.initialFiat.getAbbr()}'),
+              symbol: widget.initialFiat.getAbbr(),
             ),
             onTap: () => _showAssetSelectionDialog('fiat'),
             isListTile: false,
diff --git a/lib/views/fiat/fiat_payment_methods_grid.dart b/lib/views/fiat/fiat_payment_methods_grid.dart
index c3c340e050..6fb7893dfb 100644
--- a/lib/views/fiat/fiat_payment_methods_grid.dart
+++ b/lib/views/fiat/fiat_payment_methods_grid.dart
@@ -43,8 +43,8 @@ class FiatPaymentMethodsGrid extends StatelessWidget {
         child: Text(
           LocaleKeys.noOptionsToPurchase.tr(
             args: [
-              state.selectedAsset.value!.symbol,
-              state.selectedFiat.value!.symbol,
+              state.selectedAsset.value!.getAbbr(),
+              state.selectedFiat.value!.getAbbr(),
             ],
           ),
           textAlign: TextAlign.center,
diff --git a/lib/views/fiat/fiat_provider_web_view_settings.dart b/lib/views/fiat/fiat_provider_web_view_settings.dart
new file mode 100644
index 0000000000..ea62c52b23
--- /dev/null
+++ b/lib/views/fiat/fiat_provider_web_view_settings.dart
@@ -0,0 +1,63 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter_inappwebview/flutter_inappwebview.dart';
+
+/// Default trusted domains for the WebView content blockers
+const List<String> kDefaultTrustedDomainFilters = [
+  r'komodo\.banxa\.com.*',
+  r'app\.demo\.ramp\.network.*',
+  r'app\.ramp\.network.*',
+  r'embed\.bitrefill\.com.*',
+];
+
+/// Factory methods for creating webview settings for specific providers
+class FiatProviderWebViewSettings {
+  /// Creates secure webview settings for fiat providers like Banxa, Ramp, etc.
+  ///
+  /// The [trustedDomainFilters] parameter allows filtering content to only
+  /// trusted domains for security.
+  static InAppWebViewSettings createSecureProviderSettings({
+    List<String> trustedDomainFilters = kDefaultTrustedDomainFilters,
+  }) {
+    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+    return InAppWebViewSettings(
+      isInspectable: kDebugMode,
+      iframeSandbox: {
+        // Required for cookies and localStorage access
+        Sandbox.ALLOW_SAME_ORIGIN,
+        // Required for dynamic iframe content to load in Banxa and Ramp
+        // webviews.
+        Sandbox.ALLOW_SCRIPTS,
+        // Required for Ramp and Banxa form submissions throughout the KYC
+        // and payment process.
+        Sandbox.ALLOW_FORMS,
+        // Required for Ramp "Check transaction status" button after payment
+        // to work.
+        Sandbox.ALLOW_POPUPS,
+        // Deliberately NOT including ALLOW_TOP_NAVIGATION to prevent
+        // parent navigation
+      },
+      contentBlockers: [
+        // Block all content by default
+        ContentBlocker(
+          trigger: ContentBlockerTrigger(
+            urlFilter: '.*',
+          ),
+          action: ContentBlockerAction(
+            type: ContentBlockerActionType.BLOCK,
+          ),
+        ),
+        // Allow the specific domains we trust
+        ...trustedDomainFilters.map(
+          (urlFilter) => ContentBlocker(
+            trigger: ContentBlockerTrigger(
+              urlFilter: urlFilter,
+            ),
+            action: ContentBlockerAction(
+              type: ContentBlockerActionType.IGNORE_PREVIOUS_RULES,
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/lib/views/fiat/webview_dialog.dart b/lib/views/fiat/webview_dialog.dart
index e31311a675..edb1734791 100644
--- a/lib/views/fiat/webview_dialog.dart
+++ b/lib/views/fiat/webview_dialog.dart
@@ -5,47 +5,80 @@ import 'package:flutter/material.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
 import 'package:web_dex/common/screen.dart';
 import 'package:web_dex/shared/utils/utils.dart';
+import 'package:web_dex/shared/utils/window/window.dart';
+
+/// The display mode for the webview dialog.
+enum WebViewDialogMode {
+  /// Show the webview in a dialog or popup window.
+  dialog,
+
+  /// Show the webview in fullscreen mode, as a new material navigation route.
+  fullscreen,
+
+  /// Show the webview in a new browser tab (web) or external browser.
+  newTab,
+}
 
 class WebViewDialog {
+  /// Shows a webview dialog with the given [url] and [title].
+  /// The [onMessage] callback is called with the console messages from
+  /// the webview.
+  /// The [onCloseWindow] callback is called when the webview is closed.
+  /// The [settings] parameter allows you to customize [InAppWebViewSettings]
+  /// The [mode] parameter allows you to choose how the webview is shown.
+  /// The [width] and [height] parameters allow you to customize the size of the
+  /// dialog.
   static Future<void> show(
     BuildContext context, {
     required String url,
     required String title,
-    void Function(String)? onConsoleMessage,
+    void Function(String)? onMessage,
     VoidCallback? onCloseWindow,
     InAppWebViewSettings? settings,
+    WebViewDialogMode? mode,
+    double width = 700,
+    double height = 700,
   }) async {
+    final webviewSettings = settings ??
+        InAppWebViewSettings(isInspectable: kDebugMode, iframeSandbox: {
+          Sandbox.ALLOW_SAME_ORIGIN,
+          Sandbox.ALLOW_SCRIPTS,
+          Sandbox.ALLOW_FORMS,
+          Sandbox.ALLOW_POPUPS,
+        });
+
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    final bool isWeb = (kIsWeb || kIsWasm) && !isMobile;
+    final WebViewDialogMode defaultMode =
+        isWeb ? WebViewDialogMode.dialog : WebViewDialogMode.fullscreen;
+    final WebViewDialogMode resolvedMode = mode ?? defaultMode;
+
+    // If on Linux, always use newTab mode (open in external browser)
     // `flutter_inappwebview` does not yet support Linux, so use `url_launcher`
     // to launch the URL in the default browser.
-    if (!kIsWeb && !kIsWasm && Platform.isLinux) {
-      return launchURLString(url);
+    final bool shouldOpenInNewTab =
+        resolvedMode == WebViewDialogMode.newTab || isLinux;
+    if (shouldOpenInNewTab) {
+      await launchURLString(url, inSeparateTab: true);
+      return;
     }
 
-    final webviewSettings = settings ??
-        InAppWebViewSettings(
-          isInspectable: kDebugMode,
-          iframeSandbox: {
-            Sandbox.ALLOW_SAME_ORIGIN,
-            Sandbox.ALLOW_SCRIPTS,
-            Sandbox.ALLOW_FORMS,
-            Sandbox.ALLOW_POPUPS,
-          },
-        );
-
-    if (kIsWeb && !isMobile) {
+    if (resolvedMode == WebViewDialogMode.dialog) {
       await showDialog<dynamic>(
         context: context,
         builder: (BuildContext context) {
           return InAppWebviewDialog(
             title: title,
             webviewSettings: webviewSettings,
-            onConsoleMessage: onConsoleMessage ?? (_) {},
+            onConsoleMessage: onMessage ?? (_) {},
             onCloseWindow: onCloseWindow,
             url: url,
+            width: width,
+            height: height,
           );
         },
       );
-    } else {
+    } else if (resolvedMode == WebViewDialogMode.fullscreen) {
       await Navigator.of(context).push(
         MaterialPageRoute<void>(
           fullscreenDialog: true,
@@ -53,7 +86,7 @@ class WebViewDialog {
             return FullscreenInAppWebview(
               title: title,
               webviewSettings: webviewSettings,
-              onConsoleMessage: onConsoleMessage ?? (_) {},
+              onConsoleMessage: onMessage ?? (_) {},
               onCloseWindow: onCloseWindow,
               url: url,
             );
@@ -71,6 +104,8 @@ class InAppWebviewDialog extends StatelessWidget {
     required this.onConsoleMessage,
     required this.url,
     this.onCloseWindow,
+    this.width = 700,
+    this.height = 700,
     super.key,
   });
 
@@ -79,17 +114,20 @@ class InAppWebviewDialog extends StatelessWidget {
   final void Function(String) onConsoleMessage;
   final String url;
   final VoidCallback? onCloseWindow;
+  final double width;
+  final double height;
 
   @override
   Widget build(BuildContext context) {
     return Dialog(
       shape: RoundedRectangleBorder(
-        borderRadius:
-            BorderRadius.circular(12.0), // Match your app's corner radius
+        borderRadius: BorderRadius.circular(12.0),
       ),
+      insetPadding:
+          const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
       child: SizedBox(
-        width: 700,
-        height: 700,
+        width: width,
+        height: height,
         child: Column(
           mainAxisSize: MainAxisSize.min,
           children: [
@@ -156,6 +194,13 @@ class FullscreenInAppWebview extends StatelessWidget {
         title: Text(title),
         foregroundColor: Theme.of(context).textTheme.bodyMedium?.color,
         elevation: 0,
+        leading: IconButton(
+          icon: const Icon(Icons.arrow_back),
+          onPressed: () {
+            onCloseWindow?.call();
+            Navigator.of(context).pop();
+          },
+        ),
       ),
       body: SafeArea(
         child: MessageInAppWebView(
@@ -198,30 +243,10 @@ class _MessageInAppWebviewState extends State<MessageInAppWebView> {
       initialUrlRequest: urlRequest,
       onConsoleMessage: _onConsoleMessage,
       onUpdateVisitedHistory: _onUpdateHistory,
-      onCloseWindow: (_) {
-        widget.onCloseWindow?.call();
-      },
-      onLoadStop: (controller, url) async {
-        await controller.evaluateJavascript(
-          source: '''
-            window.addEventListener("message", (event) => {
-              let messageData;
-              try {
-                  messageData = JSON.parse(event.data);
-              } catch (parseError) {
-                  messageData = event.data;
-              }
-
-              try {
-                const messageString = (typeof messageData === 'object') ? JSON.stringify(messageData) : String(messageData);
-                console.log(messageString);
-              } catch (postError) {
-                  console.error('Error posting message', postError);
-              }
-            }, false);
-          ''',
-        );
-      },
+      onCloseWindow: (_) => widget.onCloseWindow?.call(),
+      // injected JS is done in the HTML wrapper iframe in fiat_widget.html,
+      // so we don't need to inject it here. E.g. onLoadStop,
+      // evaluateJavascript, etc.
     );
   }
 
@@ -229,12 +254,15 @@ class _MessageInAppWebviewState extends State<MessageInAppWebView> {
     widget.onConsoleMessage(consoleMessage.message);
   }
 
+  // Banxa and Ramp both redirect to the provided success URL on completion,
+  // and Banxa recommends closing the webview when this happens.
+  // https://docs.banxa.com/v1.3/docs/mobile-applications-webview
   void _onUpdateHistory(
     InAppWebViewController controller,
     WebUri? url,
     bool? isReload,
   ) {
-    if (url.toString() == 'https://app.komodoplatform.com/') {
+    if (url.toString() == getOriginUrl()) {
       Navigator.of(context).pop();
     }
   }


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR fixes issues in the Banxa onramp flow where an erroneous failure popup was displayed and the "Return to Komodo" button did not work. It opens the Banxa page in a new tab, refactors URL parameter retrieval and message event handling, and updates documentation/comments to improve maintainability and clarity.
- Updated iframe sandbox configuration with fallback messaging.
- Refactored message handling with new _komodo* functions.
- Improved documentation and error handling flow.

<details>
<summary>Files not reviewed (14)</summary>

* **lib/bloc/fiat/banxa_fiat_provider.dart**: Language not supported
* **lib/bloc/fiat/base_fiat_provider.dart**: Language not supported
* **lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart**: Language not supported
* **lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart**: Language not supported
* **lib/bloc/fiat/fiat_order_status.dart**: Language not supported
* **lib/bloc/fiat/fiat_repository.dart**: Language not supported
* **lib/bloc/fiat/models/fiat_buy_order_error.dart**: Language not supported
* **lib/bloc/fiat/models/fiat_buy_order_info.dart**: Language not supported
* **lib/bloc/fiat/models/fiat_price_info.dart**: Language not supported
* **lib/bloc/fiat/payment_status_type.dart**: Language not supported
* **lib/bloc/fiat/ramp/ramp_fiat_provider.dart**: Language not supported
* **lib/views/fiat/fiat_form.dart**: Language not supported
* **lib/views/fiat/fiat_provider_web_view_settings.dart**: Language not supported
* **lib/views/fiat/webview_dialog.dart**: Language not supported
</details>



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:34:28Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 10**

<details>
<summary>🔭 Outside diff range comments (2)</summary><blockquote>

<details>
<summary>lib/bloc/fiat/fiat_order_status.dart (1)</summary><blockquote>

`28-33`: _⚠️ Potential issue_

**Include `pendingPayment` in `isSubmitting` check**

`pendingPayment` represents an intermediate “work-in-progress” state that still requires user action, yet it is not covered by `isSubmitting`.  Consumers that rely on `isSubmitting` (e.g. disabling the *Buy* button or showing spinners) will therefore treat `pendingPayment` like an idle state and may let the user re-submit the form or close the dialog prematurely.

```diff
-  bool get isSubmitting =>
-      this == FiatOrderStatus.inProgress || this == FiatOrderStatus.submitted;
+  bool get isSubmitting =>
+      this == FiatOrderStatus.inProgress ||
+      this == FiatOrderStatus.submitted ||
+      this == FiatOrderStatus.pendingPayment;
```

</blockquote></details>
<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart (1)</summary><blockquote>

`302-317`: _⚠️ Potential issue_

**Switch statement is missing `break;` → illegal fall-through & wrong logic**  
Dart does not allow implicit fall-through between `case` labels.  This block will not compile and, even if allowed, would overwrite `updatedStatus` (e.g. a *widgetClose* event becomes *inProgress*).

```diff
-    switch (eventType) {
-      case PaymentStatusType.widgetCloseRequestConfirmed:
-      case PaymentStatusType.widgetClose:
-      case PaymentStatusType.widgetCloseRequest:
-        updatedStatus = FiatOrderStatus.windowCloseRequested;
-      case PaymentStatusType.purchaseCreated:
-        updatedStatus = FiatOrderStatus.inProgress;
-      case PaymentStatusType.paymentStatus:
-        final status = data['status'] as String? ?? 'declined';
-        updatedStatus = FiatOrderStatus.fromString(status);
-      ...
-        break;
-    }
+    switch (eventType) {
+      case PaymentStatusType.widgetCloseRequestConfirmed:
+      case PaymentStatusType.widgetClose:
+      case PaymentStatusType.widgetCloseRequest:
+        updatedStatus = FiatOrderStatus.windowCloseRequested;
+        break;
+      case PaymentStatusType.purchaseCreated:
+        updatedStatus = FiatOrderStatus.inProgress;
+        break;
+      case PaymentStatusType.paymentStatus:
+        final status = data['status'] as String? ?? 'declined';
+        updatedStatus = FiatOrderStatus.fromString(status);
+        break;
+      default:
+        break;
+    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (6)</summary><blockquote>

<details>
<summary>lib/bloc/fiat/fiat_order_status.dart (1)</summary><blockquote>

`40-60`: **Normalisation could be more robust**

1. `status.toLowerCase()` is a good start, but many providers sometimes return trimmed / spaced variants (e.g. “Pending Payment”).  
2. `extraVerification` in the docs is spelt with a capital “V”; the lower-case mapping works, but a quick `replaceAll(RegExp(r'\s'), '')` would make the parser future-proof against white-space.

```diff
- final normalized = status.toLowerCase();
+ final normalized = status.toLowerCase().replaceAll(RegExp(r'\s+'), '');
```

</blockquote></details>
<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart (1)</summary><blockquote>

`122-161`: **`copyWith` signature: `ValueGetter` pattern can’t set fields to a non-null default**

Using lazy `ValueGetter` is clever for heavy computations, but:

1. Call-sites that want to *null-out* `selectedAssetAddress` must wrap `() => null`, which is unintuitive.
2. The new pattern is inconsistent with other simple fields (`checkoutUrl`, `orderId`) that still accept nullable values directly.

Consider overloading for clarity or reverting to nullable parameters and documenting expensive cases separately.

</blockquote></details>
<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart (1)</summary><blockquote>

`157-165`: **Force-unwrap of `selectedAssetAddress` risks runtime crash**  
`state.selectedAssetAddress!` is force-unwrapped four times. Although `getFormIssue()` currently prevents `_onSubmitForm` when it’s `null`, future edits might bypass that guard. Consider a safer access pattern:

```diff
-      final newOrder = await _fiatRepository.buyCoin(
-        accountReference: state.selectedAssetAddress!.address,
+      final address = state.selectedAssetAddress?.address;
+      if (address == null) {
+        _log.severe('submit called without a wallet address');
+        return;
+      }
+      final newOrder = await _fiatRepository.buyCoin(
+        accountReference: address,
```

</blockquote></details>
<details>
<summary>assets/web_pages/fiat_widget.html (1)</summary><blockquote>

`40-67`: **Unused parameter & double query – minor cleanup**  
`_komodoSetIframeUrlFromParams` accepts a `params` argument but immediately ignores it and re-reads the query string. This is harmless but confusing.

```diff
-function _komodoSetIframeUrlFromParams(params) {
-  const urlParam = _komodoGetUrlParameter('fiatUrl');
+function _komodoSetIframeUrlFromParams() {
+  const urlParam = _komodoGetUrlParameter('fiatUrl');
```

Also, registering the same handler via both `window.onmessage = …` and `addEventListener` is redundant. Retain only the `addEventListener` form unless you specifically need legacy support.

</blockquote></details>
<details>
<summary>lib/views/fiat/webview_dialog.dart (1)</summary><blockquote>

`60-64`: **`launchURLString` success not awaited or reported**  
`launchURLString` returns a boolean indicating success.  Swallowing that result hides failures (e.g., popup blocked). Suggest logging or surfacing an error:

```diff
-      await launchURLString(url, inSeparateTab: true);
+      final launched = await launchURLString(url, inSeparateTab: true);
+      if (!launched) {
+        debugPrint('Failed to open $url in external browser');
+      }
```

</blockquote></details>
<details>
<summary>lib/bloc/fiat/ramp/ramp_fiat_provider.dart (1)</summary><blockquote>

`301-303`: **UI control parameter addition**

The `hideExitButton: 'true'` parameter addresses the "Return to Komodo" button functionality issue mentioned in the PR objectives. This forces the Ramp UI to hide its exit button, likely because the flow now opens in a new tab where the browser's back/close functionality can be used instead.

The commented-out `variant` parameter seems to be preparation for potentially changing how the UI is displayed (hosted, desktop, mobile, etc.). Consider adding a comment explaining why this is commented out and under what conditions it should be uncommented.


```diff
  'hideExitButton': 'true',
- // 'variant': 'hosted', // desktop, mobile, auto, hosted-mobile
+ // 'variant': 'hosted', // Options: desktop, mobile, auto, hosted-mobile
+ // Note: Uncomment when we migrate to a newer Banxa API version that supports this parameter
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9b46ffe4535178003d8a0a891052954eb670fbe3 and af5698b18a4ea534e04983be07eaa90d5bace597.

</details>

<details>
<summary>📒 Files selected for processing (15)</summary>

* `assets/web_pages/fiat_widget.html` (1 hunks)
* `lib/bloc/fiat/banxa_fiat_provider.dart` (1 hunks)
* `lib/bloc/fiat/base_fiat_provider.dart` (2 hunks)
* `lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart` (17 hunks)
* `lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart` (7 hunks)
* `lib/bloc/fiat/fiat_order_status.dart` (3 hunks)
* `lib/bloc/fiat/fiat_repository.dart` (1 hunks)
* `lib/bloc/fiat/models/fiat_buy_order_error.dart` (1 hunks)
* `lib/bloc/fiat/models/fiat_buy_order_info.dart` (3 hunks)
* `lib/bloc/fiat/models/fiat_price_info.dart` (2 hunks)
* `lib/bloc/fiat/payment_status_type.dart` (2 hunks)
* `lib/bloc/fiat/ramp/ramp_fiat_provider.dart` (8 hunks)
* `lib/views/fiat/fiat_form.dart` (4 hunks)
* `lib/views/fiat/fiat_provider_web_view_settings.dart` (1 hunks)
* `lib/views/fiat/webview_dialog.dart` (5 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (10)</summary>

* GitHub Check: Build Desktop (linux)
* GitHub Check: Test web-app-macos
* GitHub Check: Build Desktop (windows)
* GitHub Check: Build Mobile (Android)
* GitHub Check: build_and_preview
* GitHub Check: Build Desktop (macos)
* GitHub Check: Test web-app-linux-profile
* GitHub Check: validate_code_guidelines
* GitHub Check: unit_tests
* GitHub Check: Build Mobile (iOS)

</details>

<details>
<summary>🔇 Additional comments (22)</summary><blockquote>

<details>
<summary>lib/views/fiat/fiat_provider_web_view_settings.dart (4)</summary>

`5-10`: **Well-structured trusted domain configuration**

The explicit list of trusted domains is a good security practice, allowing fine-grained control over which external domains can be loaded in the WebView. The regex patterns accommodate the necessary variations while maintaining security boundaries.

---

`18-20`: **Good default parameter implementation**

Using a default parameter for `trustedDomainFilters` with the predefined constants makes the method flexible while providing secure defaults. This approach allows for easy customization when needed without compromising security by default.

---

`24-38`: **Comprehensive sandbox configuration with clear documentation**

The sandbox configuration is well-documented with explanations for each permission. The deliberate exclusion of `ALLOW_TOP_NAVIGATION` enhances security by preventing parent navigation hijacking, which is an important security consideration for payment flows.

---

`39-61`: **Strong security implementation with content blocking**

The implementation of content blockers with a default deny-all policy followed by explicit allowances for trusted domains follows security best practices. This defense-in-depth approach ensures that only explicitly trusted content can be loaded.

</details>
<details>
<summary>lib/bloc/fiat/models/fiat_buy_order_error.dart (1)</summary>

`21-24`: **Good addition of specific error type for parsing failures**

Adding a dedicated constructor for parsing errors improves error handling specificity and provides a standardized way to handle parsing failures. This will help with debugging and appropriate error messaging in the UI.

</details>
<details>
<summary>lib/bloc/fiat/banxa_fiat_provider.dart (1)</summary>

`27-27`: **Improved status parsing with centralized logic**

Replacing the custom parsing logic with a call to `FiatOrderStatus.fromString` centralizes the status parsing responsibility and ensures consistent handling across the application. This should help resolve issues with erroneous failure popups mentioned in the PR objectives.

</details>
<details>
<summary>lib/bloc/fiat/payment_status_type.dart (3)</summary>

`2-10`: **Consistent string literal style**

The change to use single quotes for string literals improves consistency with Dart style guidelines. This kind of consistency makes the code more maintainable.

---

`14-15`: **Improved field declaration positioning**

Repositioning the field declaration improves code readability and follows standard Dart style guidelines.

---

`24-24`: **Enhanced error handling with const FormatException**

Using a `const` FormatException is more efficient as it doesn't create a new object each time the exception is thrown. This is a small but valuable optimization.

</details>
<details>
<summary>lib/bloc/fiat/models/fiat_price_info.dart (1)</summary>

`24-30`: **Repositioned static zero instance**

The static `zero` instance was moved after the `fromJson` constructor for better code organization. The functionality remains unchanged.

</details>
<details>
<summary>lib/bloc/fiat/base_fiat_provider.dart (1)</summary>

`71-73`: **Improved URI path handling**

The code now properly handles endpoints with leading slashes, avoiding potential double-slash issues when constructing the final API URL.

</details>
<details>
<summary>lib/bloc/fiat/models/fiat_buy_order_info.dart (2)</summary>

`24-40`: **Renamed and improved constructor semantics**

The constructor was renamed from `info()` to `fromCheckoutUrl(String url)` to better reflect its purpose, and it now properly initializes the `checkoutUrl` with the provided URL.

The name now clearly indicates the constructor's behavior, improving code readability.

---

`42-58`: **Renamed and repurposed empty constructor**

The constructor was renamed from `fromCheckoutUrl(String url)` to `empty()` which better reflects its purpose of creating an empty instance with default values and an empty checkout URL.

</details>
<details>
<summary>lib/views/fiat/fiat_form.dart (1)</summary>

`208-217`: **Guard against empty or malformed checkout URLs**

`stateSnapshot.checkoutUrl` is passed straight to `WebViewDialog.show`.  
If the Bloc ever emits `submitted` before the URL is resolved (e.g. slow network), an empty string will yield a blank web-view or, on Linux, an external browser error.

```diff
if (status == FiatOrderStatus.submitted) {
-  await WebViewDialog.show(
+  if (stateSnapshot.checkoutUrl.isEmpty) {
+    Logger('FiatForm').warning('Checkout URL missing for submitted order');
+    return;
+  }
+  await WebViewDialog.show(
```

</details>
<details>
<summary>lib/bloc/fiat/ramp/ramp_fiat_provider.dart (8)</summary>

`36-37`: **Type safety improvement**

Changing the parameter type from `ICurrency` to the more specific `CryptoCurrency` improves type safety by ensuring that only crypto currencies can be used with this method. This aligns with the method implementation which requires crypto-specific properties.

---

`42-43`: **Type safety improvement**

Refining the parameter type from the generic `ICurrency` to the concrete `CryptoCurrency` ensures proper type safety in the API request, preventing runtime errors that could occur if an incompatible currency type was passed.

---

`54-56`: **Helpful comment addition**

The added comment clarifies the expected format for the `fiatValue` parameter, making it clear why the conversion to double is necessary. This improves code maintainability.

---

`64-65`: **Type safety improvement**

Same pattern as other methods - replacing `ICurrency` with `CryptoCurrency` improves type safety and makes the API contract more explicit.

---

`149-152`: **Defensive runtime type checking**

The added type check with a clear error message ensures that if the method is called with an incorrect currency type, it will fail fast with a descriptive error instead of failing later with a potentially cryptic error.

---

`219-224`: **Improved fee validation logic**

The refactored method extracts the fee calculation for better readability and adds defensive validation to ensure the fee ratio is valid (less than 1). This prevents potential division by zero or negative values that could result from invalid fee configurations.

---

`243-246`: **Consistent type checking**

This type check maintains consistency with the pattern established in other methods, ensuring that the API is used correctly with appropriate parameter types.

---

`286-289`: **Consistent type checking**

Same pattern applied to the `buyCoin` method, maintaining consistency across the API surface and preventing type-related errors.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:38:27Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:52:01Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:52:27Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:55:58Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:56:37Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:00:06Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:00:16Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:00:38Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:00:58Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:01:32Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:01:41Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:01:51Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:01:56Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>lib/bloc/fiat/models/fiat_buy_order_info.dart (1)</summary><blockquote>

`60-97`: **Robust error handling for JSON parsing.**

The `fromJson` factory now explicitly checks for the presence of nested `data` and `order` keys, returning an error state with a descriptive message when the expected structure is missing. This defensive approach prevents null reference exceptions during JSON deserialization.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>assets/web_pages/fiat_widget.html (4)</summary><blockquote>

`5-6`: **Add a charset meta tag for proper encoding.**

Without an explicit `<meta charset>` declaration, some browsers may misinterpret the page’s character encoding, leading to garbled text.  


Apply this diff:
```diff
   <title>Fiat OnRamp</title>
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
```

---

`28-30`: **Include a descriptive `title` attribute on the iframe for accessibility.**

Adding a `title` helps screen readers understand the purpose of the embedded content.  


Example diff:
```diff
-  <iframe id="fiat-onramp-iframe"
-    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
-    src="">
+  <iframe
+    id="fiat-onramp-iframe"
+    title="Fiat On-Ramp Widget"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
```

---

`38-41`: **Use `DOMContentLoaded` instead of `load` for faster initialization.**

The `load` event waits for all resources (images, iframes) to finish loading, delaying your iframe setup. Switching to `DOMContentLoaded` ensures the script runs as soon as the DOM is ready.  


```diff
-    window.addEventListener('load', function () {
-      _komodoSetIframeUrlFromParams();
-    });
+    window.addEventListener('DOMContentLoaded', () => {
+      _komodoSetIframeUrlFromParams();
+    });
```

---

`113-116`: **Remove or scope the debug `console.log` to avoid leaking sensitive data.**

Logging raw messages may expose PII or internal transaction details in the browser console. Consider removing it or gating it behind a debug flag.  


```diff
-      // flutter_inappwebview
-      console.log(messageString);
+      // flutter_inappwebview
+      // TODO: remove or wrap in debug check before logging
+      // console.debug(messageString);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between af5698b18a4ea534e04983be07eaa90d5bace597 and 34e296597306e70546260546e7554ec7fb4510c3.

</details>

<details>
<summary>📒 Files selected for processing (14)</summary>

* `assets/web_pages/checkout_status_redirect.html` (1 hunks)
* `assets/web_pages/fiat_widget.html` (1 hunks)
* `lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart` (23 hunks)
* `lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart` (7 hunks)
* `lib/bloc/fiat/fiat_order_status.dart` (3 hunks)
* `lib/bloc/fiat/fiat_repository.dart` (2 hunks)
* `lib/bloc/fiat/models/fiat_buy_order_info.dart` (4 hunks)
* `lib/bloc/fiat/models/i_currency.dart` (1 hunks)
* `lib/shared/widgets/coin_item/coin_item.dart` (1 hunks)
* `lib/views/fiat/fiat_asset_icon.dart` (1 hunks)
* `lib/views/fiat/fiat_currency_list_tile.dart` (1 hunks)
* `lib/views/fiat/fiat_form.dart` (3 hunks)
* `lib/views/fiat/fiat_inputs.dart` (1 hunks)
* `lib/views/fiat/fiat_payment_methods_grid.dart` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* lib/views/fiat/fiat_currency_list_tile.dart
* lib/shared/widgets/coin_item/coin_item.dart

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* lib/bloc/fiat/fiat_repository.dart
* lib/views/fiat/fiat_form.dart
* lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart (3)</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:2-3
Timestamp: 2025-05-01T21:00:36.970Z
Learning: It's acceptable to use unconditional `dart:io` imports in the Komodo wallet codebase when the usage is guarded by `!kIsWeb` conditional checks that prevent the platform-specific code from being executed in web environments.
```

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:208-225
Timestamp: 2025-05-01T20:56:36.180Z
Learning: The `kIsWasm` constant is available in Flutter version 3.22+ (released May 2024) and is used to detect if an application is running on the WebAssembly backend. When writing platform-specific code, use both `kIsWeb` and `kIsWasm` checks to properly handle both JavaScript and WebAssembly compilation targets.
```

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/views/fiat/webview_dialog.dart:50-55
Timestamp: 2025-05-01T21:00:56.962Z
Learning: `kIsWasm` is a valid constant in Flutter 3.22+ (released May 2024) that detects if an application is running on WebAssembly. It complements `kIsWeb` for more precise platform detection.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (10)</summary>

* GitHub Check: Build Desktop (linux)
* GitHub Check: Build Mobile (Android)
* GitHub Check: Build Desktop (windows)
* GitHub Check: Test web-app-macos
* GitHub Check: build_and_preview
* GitHub Check: Build Mobile (iOS)
* GitHub Check: validate_code_guidelines
* GitHub Check: Build Desktop (macos)
* GitHub Check: Test web-app-linux-profile
* GitHub Check: unit_tests

</details>

<details>
<summary>🔇 Additional comments (28)</summary><blockquote>

<details>
<summary>lib/views/fiat/fiat_inputs.dart (1)</summary>

`138-139`: **Appropriate use of getAbbr() method for consistent currency abbreviation handling.**

The switch from using `widget.initialFiat.symbol` to `widget.initialFiat.getAbbr()` ensures consistent currency abbreviation handling, which helps resolve the coin abbreviation parsing regression mentioned in the PR objectives.

</details>
<details>
<summary>lib/views/fiat/fiat_payment_methods_grid.dart (1)</summary>

`46-47`: **Good change for consistent currency abbreviation handling.**

Replacing direct symbol access with the `getAbbr()` method ensures consistent display of currency abbreviations in the "no options to purchase" message, aligning with the coin abbreviation handling improvements in this PR.

</details>
<details>
<summary>lib/views/fiat/fiat_asset_icon.dart (3)</summary>

`27-27`: **Proper use of getAbbr() method for fiat currency icon.**

Using `currency.getAbbr()` instead of direct symbol access provides consistent abbreviation handling for fiat currency icons.

---

`30-32`: **Good documentation with clear TODO comment.**

The TODO comment clearly explains the inconsistency in icon layout handling between `CoinItem` and other icon widgets, which will be valuable for future standardization efforts.

---

`35-35`: **Consistent use of getAbbr() for SDK asset retrieval.**

Using `currency.getAbbr()` for SDK asset retrieval ensures that the correct abbreviation is used when looking up assets, which is critical for proper coin recognition.

</details>
<details>
<summary>lib/bloc/fiat/models/i_currency.dart (1)</summary>

`84-104`: **Strong implementation for handling special case currency abbreviations.**

The enhanced `getAbbr()` method properly addresses the inconsistency between provider-returned symbols and configuration storage. The detailed TODO comment explains the issue well, and the implementation handles known exception cases appropriately.

This change is central to fixing the coin abbreviation/ticker parsing regression mentioned in the PR objectives, ensuring more coins are correctly available in the selection dropdown.

</details>
<details>
<summary>assets/web_pages/checkout_status_redirect.html (3)</summary>

`12-17`: **Code formatting improvements look good.**

The parameter extraction logic has been reformatted with consistent indentation and variable naming patterns.

---

`26-37`: **Improved handling of window communication.**

The revised code properly handles the parent window communication, including the addition of a fallback for when status is null. The code now also focuses the opener window after sending the message, which improves the user experience.

---

`39-41`: **Clearer window closing strategy.**

The added comments explain the rationale behind using `window.close()` for both Ramp and Banxa checkout pages. This aligns with the PR objectives of improving the Banxa onramp flow and fixing dialog/popup z-order issues.

</details>
<details>
<summary>lib/bloc/fiat/models/fiat_buy_order_info.dart (2)</summary>

`24-40`: **Constructor naming improvements enhance code clarity.**

The renamed constructors better reflect their purpose: `fromCheckoutUrl` for creating an instance with a provided URL, and `empty` for creating an empty instance.

---

`42-58`: **Renamed constructor provides semantic clarity.**

Renaming this constructor to `empty()` clearly communicates its purpose of creating an empty instance with default values.

</details>
<details>
<summary>lib/bloc/fiat/fiat_order_status.dart (5)</summary>

`2-3`: **Added logging for better diagnostics.**

The import of the logging package allows for better error reporting when handling unknown status values.

---

`5-6`: **Improved status naming for better semantic clarity.**

Renaming `pending` to `initial` better describes the state where the user has not yet started the payment process.

---

`12-14`: **Added new status for better flow representation.**

The new `pendingPayment` status correctly differentiates between the initial state and a state where user action is required to complete payment.

---

`28-33`: **Updated isSubmitting getter to include new status.**

The getter now correctly includes `pendingPayment` as a submitting state, ensuring consistent behavior across the application.

---

`38-72`: **Enhanced status parsing with improved error handling.**

The `fromString` method now:
1. Performs case-insensitive parsing
2. Handles additional Banxa status types
3. Defaults to `inProgress` for unknown statuses instead of failing

This directly addresses the PR objective of fixing the "erroneous failure popup" by not marking unknown statuses as failed.

</details>
<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart (12)</summary>

`2-3`: **Platform import usage is properly guarded.**

Following the learnings in this project, the `dart:io` import is acceptable as its usage is properly guarded with `!kIsWeb` checks to prevent issues in web environments.

---

`24-25`: **Added explicit import for WebViewDialogMode.**

The explicit import for `WebViewDialogMode` from the webview_dialog.dart file improves code clarity by making the dependency more obvious.

---

`94-112`: **Improved null safety with lazy evaluation.**

The code now uses lazy evaluation for setting `selectedAssetAddress` and `selectedCoinPubkeys`, which better aligns with Flutter's patterns for state management and prevents potential null reference issues.

---

`135-136`: **Updated order status to use renamed enum value.**

The code now uses `FiatOrderStatus.initial` instead of `pending`, consistent with the enum changes.

---

`172-187`: **Enhanced checkout URL handling.**

The code now:
1. More explicitly casts the checkout URL
2. Adds clearer comments about the HTML wrapper page requirements
3. Uses a dedicated method to determine the appropriate WebView mode

This aligns with the PR objective of opening the Banxa payment page in a new browser tab to avoid dialog/popup issues.

---

`208-225`: **Well-designed WebView mode selection logic.**

The new `_determineWebViewMode()` method:
1. Properly handles all platform variations (Web, Wasm, Linux)
2. Takes into account specific provider requirements
3. Provides clear comments explaining the implementation choices

The use of both `kIsWeb` and `kIsWasm` checks is appropriate given the project's use of Flutter 3.22+.

---

`405-406`: **Updated order status reset.**

The code now resets the order status to `initial` instead of `pending` when the WebView is closed, consistent with the enum changes.

---

`420-422`: **Consistent use of lazy evaluation pattern.**

The asset address update now uses the lazy evaluation pattern for the `selectedAssetAddress` property, consistent with other similar state updates in the file.

---

`434-435`: **Improved error handling with provider-specific errors.**

The error parsing now captures provider-specific error messages in the `providerError` field, which allows for more detailed error reporting to users.

---

`476-477`: **Proper nulling of provider errors on form refresh.**

The code now clears provider errors when refreshing the form, preventing stale error messages from persisting.

---

`496-498`: **Consistent initial state setting.**

The code now sets the order status to `initial` and clears any provider errors when initializing the form, providing a clean slate for new form submissions.

---

`578-580`: **Consistent default state.**

The default payment methods state now properly initializes with the renamed `initial` status and clears any provider errors.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T08:38:48Z
user: login: takenagain
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T08:54:19Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T08:55:28Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🔭 Outside diff range comments (2)</summary><blockquote>

<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart (1)</summary><blockquote>

`303-318`: _⚠️ Potential issue_

**Missing `break` statements cause unintended fall-through in the `switch`**  

Each `case` re-assigns `updatedStatus`, but the absence of `break` lets execution fall through to subsequent cases, overwriting the intended value and masking states such as `windowCloseRequested`.  

```diff
-        case PaymentStatusType.widgetCloseRequestConfirmed:
-        case PaymentStatusType.widgetClose:
-        case PaymentStatusType.widgetCloseRequest:
-          updatedStatus = FiatOrderStatus.windowCloseRequested;
+        case PaymentStatusType.widgetCloseRequestConfirmed:
+        case PaymentStatusType.widgetClose:
+        case PaymentStatusType.widgetCloseRequest:
+          updatedStatus = FiatOrderStatus.windowCloseRequested;
+          break;
         case PaymentStatusType.purchaseCreated:
           updatedStatus = FiatOrderStatus.inProgress;
+          break;
         case PaymentStatusType.paymentStatus:
           final status = data['status'] as String? ?? 'declined';
           updatedStatus = FiatOrderStatus.fromString(status);
+          break;
```

Add `break;` after each logical block (or restructure into separate `switch`/`if` chains) to prevent logic errors.

</blockquote></details>
<details>
<summary>lib/bloc/fiat/ramp/ramp_fiat_provider.dart (1)</summary><blockquote>

`202-210`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Possible undefined properties `minPurchaseAmount` / `maxPurchaseAmount` on `HostAssetsConfig`**  

`coins.minPurchaseAmount` and `coins.maxPurchaseAmount` are accessed here, but `coins` is a `HostAssetsConfig` instance (not an individual asset). If the class doesn’t expose aggregate min/max fields, this will be a compile-time error.

Please verify that `HostAssetsConfig` indeed defines these getters or change the code to derive the limits from the selected asset.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if HostAssetsConfig declares the expected properties
rg -n --context 2 $'class HostAssetsConfig'
rg -n $'minPurchaseAmount' | head
```

Length of output: 1384

---



</details>

**Fix missing `minPurchaseAmount` / `maxPurchaseAmount` on HostAssetsConfig**

HostAssetsConfig (lib/bloc/fiat/ramp/models/host_assets_config.dart) only declares:
```dart
class HostAssetsConfig {
  final List<RampAssetInfo> assets;
  // …
}
```
It does not expose `minPurchaseAmount` or `maxPurchaseAmount`, so these lines in ramp_fiat_provider.dart will fail to compile:

- lib/bloc/fiat/ramp/ramp_fiat_provider.dart:202-210

Please address this by either:
- **Adding getters** to HostAssetsConfig, for example:
  ```dart
  class HostAssetsConfig {
    final List<RampAssetInfo> assets;
    // …

    Decimal get minPurchaseAmount => /* aggregate or default value */;
    Decimal get maxPurchaseAmount => /* aggregate or default value */;
  }
  ```
- **Or changing the fallback** in ramp_fiat_provider.dart to use a value available on the selected asset (e.g., `asset.defaultMinPurchaseAmount` or another valid source):

  ```diff
  --- lib/bloc/fiat/ramp/ramp_fiat_provider.dart
  @@ -202,7 +202,7 @@
                'min': (asset.hasValidMinPurchaseAmount()
                        ? asset.minPurchaseAmount
  -                     : coins.minPurchaseAmount)
  +                     : asset.minPurchaseAmount)  // or other valid fallback
                    .toString(),
                'max': (asset.hasValidMaxPurchaseAmount()
                        ? asset.maxPurchaseAmount
  ```

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart (1)</summary><blockquote>

`210-213`: **Platform check looks good – no web build issues**  

The familiar `!kIsWeb && !kIsWasm && Platform.isLinux` pattern safely short-circuits the `Platform` call for web builds, so the unconditional `dart:io` import won’t break compilation.  
No action needed.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>lib/bloc/fiat/ramp/models/ramp_asset_info.dart (1)</summary><blockquote>

`76-79`: **`hasValidMinPurchaseAmount` treats only `-1` as “no-limit”; clarify edge-cases**  

The helper currently returns `true` for `0` even though the docs say “Null or **-1** indicates no minimum limit.”  
If `0` should also mean “no limit”, the comparison needs to be changed to `> Decimal.zero`.  
Otherwise, consider amending the comment to avoid confusion.

```diff
-    return minPurchaseAmount! > Decimal.fromInt(-1);
+    // Treat 0 as an explicit minimum, -1 as sentinel "no limit".
+    return minPurchaseAmount! > Decimal.fromInt(-1);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 34e296597306e70546260546e7554ec7fb4510c3 and efc6ceaf995d7fb2bcb17878d47e8c17d61b94f3.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `assets/web_pages/fiat_widget.html` (1 hunks)
* `lib/app_config/app_config.dart` (1 hunks)
* `lib/bloc/fiat/banxa_fiat_provider.dart` (5 hunks)
* `lib/bloc/fiat/base_fiat_provider.dart` (3 hunks)
* `lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart` (25 hunks)
* `lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart` (7 hunks)
* `lib/bloc/fiat/models/i_currency.dart` (5 hunks)
* `lib/bloc/fiat/ramp/models/ramp_asset_info.dart` (1 hunks)
* `lib/bloc/fiat/ramp/ramp_fiat_provider.dart` (9 hunks)
* `lib/views/fiat/fiat_form.dart` (4 hunks)
* `lib/views/fiat/fiat_inputs.dart` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* lib/app_config/app_config.dart

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (6)</summary>

* lib/bloc/fiat/base_fiat_provider.dart
* lib/views/fiat/fiat_inputs.dart
* lib/bloc/fiat/banxa_fiat_provider.dart
* lib/views/fiat/fiat_form.dart
* lib/bloc/fiat/models/i_currency.dart
* assets/web_pages/fiat_widget.html

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart (3)</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:2-3
Timestamp: 2025-05-01T21:00:36.970Z
Learning: It's acceptable to use unconditional `dart:io` imports in the Komodo wallet codebase when the usage is guarded by `!kIsWeb` conditional checks that prevent the platform-specific code from being executed in web environments.
```

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:208-225
Timestamp: 2025-05-01T20:56:36.180Z
Learning: The `kIsWasm` constant is available in Flutter version 3.22+ (released May 2024) and is used to detect if an application is running on the WebAssembly backend. When writing platform-specific code, use both `kIsWeb` and `kIsWasm` checks to properly handle both JavaScript and WebAssembly compilation targets.
```

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/views/fiat/webview_dialog.dart:50-55
Timestamp: 2025-05-01T21:00:56.962Z
Learning: `kIsWasm` is a valid constant in Flutter 3.22+ (released May 2024) that detects if an application is running on WebAssembly. It complements `kIsWeb` for more precise platform detection.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Build Desktop (windows)
* GitHub Check: Test web-app-macos
* GitHub Check: Build Mobile (Android)
* GitHub Check: Build Mobile (iOS)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart (1)</summary>

`30-47`: **State initialisation & lazy setters are well-designed**  

Switching to factory constructors for default currencies, adding `webViewMode` / `providerError`, and using `ValueGetter` for lazy updates improves clarity and avoids unnecessary rebuilds. Nice work!

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T11:52:42Z
user: login: smk762
body: - "Return to Komodo Wallet" button functions as expected :heavy_check_mark: 
- No erroneous error message about failed payment :heavy_check_mark: 
- Order fulfilled and coins received successfully :tada: 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-13T06:12:03Z
user: login: CharlVS
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-15T08:21:17Z

Review comments:
diff_hunk: @@ -2,109 +2,137 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
+  <iframe id="fiat-onramp-iframe"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.onmessage = _komodoOnMessageHandler;
path: assets/web_pages/fiat_widget.html
user: login: Copilot
body: Assigning the message event handler via both window.onmessage and addEventListener may result in duplicate processing of messages. Consider removing one assignment to ensure the handler is only triggered once per event.
```suggestion

```
created_at: 2025-05-01T20:34:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,109 +2,137 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
+  <iframe id="fiat-onramp-iframe"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.onmessage = _komodoOnMessageHandler;
+    window.addEventListener('message', _komodoOnMessageHandler, false);
+    window.addEventListener('load', function () {
+      _komodoSetIframeUrlFromParams(_komodoGetUrlParameter('fiatUrl'));
+    });
+
+    /**
+     * Initialize the iframe URL based on URL parameters
+     * 
+     * @param {object} params - The URL parameters to use for initialization
+     */
+    function _komodoSetIframeUrlFromParams(params) {
+      const urlParam = _komodoGetUrlParameter('fiatUrl');
+
+      let targetUrl = null;
+      if (urlParam) {
+        try {
+          targetUrl = atob(urlParam); // base64 decode the `url` parameter
path: assets/web_pages/fiat_widget.html
user: login: Copilot
body: [nitpick] Since the function _komodoSetIframeUrlFromParams already receives a 'params' argument, consider using this parameter instead of re-fetching the URL parameter to avoid redundancy.
created_at: 2025-05-01T20:34:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,6 +62,14 @@ class FiatPriceInfo extends Equatable {
     };
   }
 
+  static Decimal _safeParseDecimal(dynamic value) {
+    try {
+      return Decimal.parse(value?.toString() ?? '0');
+    } on FormatException {
+      return Decimal.zero;
+    }
+  }
path: lib/bloc/fiat/models/fiat_price_info.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Added robust decimal parsing helper method**

This new helper method safely parses decimal values and returns `Decimal.zero` when encountering formatting errors, preventing crashes when processing malformed data from the API.


This is an excellent defensive programming practice that makes the code more resilient to unexpected input formats.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-01T20:38:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,25 +10,25 @@
     required this.spotPriceIncludingFee,
   });
 
-  static final zero = FiatPriceInfo(
-    fiatAmount: Decimal.zero,
-    coinAmount: Decimal.zero,
-    fiatCode: '',
-    coinCode: '',
-    spotPriceIncludingFee: Decimal.zero,
-  );
-
   factory FiatPriceInfo.fromJson(Map<String, dynamic> json) {
     return FiatPriceInfo(
-      fiatAmount: Decimal.parse(json['fiat_amount']?.toString() ?? '0'),
-      coinAmount: Decimal.parse(json['coin_amount']?.toString() ?? '0'),
+      fiatAmount: _safeParseDecimal(json['fiat_amount']),
+      coinAmount: _safeParseDecimal(json['coin_amount']),
path: lib/bloc/fiat/models/fiat_price_info.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improved robustness by adding safe decimal parsing**

The code now uses a helper method `_safeParseDecimal` instead of direct `Decimal.parse` calls, which provides better error handling for malformed or missing decimal values in the JSON.




Also applies to: 20-20

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-01T20:38:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -98,7 +94,13 @@ abstract class BaseFiatProvider {
         return json.decode(response.body);
       } else {
         _log.warning('Request failed with status: ${response.statusCode}');
-        return Future.error(json.decode(response.body) as Object);
+        dynamic decoded;
+        try {
+          decoded = json.decode(response.body);
+        } catch (_) {
+          decoded = response.body;
+        }
+        return Future.error(decoded as Object);
path: lib/bloc/fiat/base_fiat_provider.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhanced error response handling**

The error handling now attempts to parse the response body as JSON first, falling back to returning the raw body text if JSON parsing fails. This provides more structured error information when available.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-01T20:38:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -252,15 +251,15 @@ class FiatRepository {
     );
   }
 
-  Future<FiatBuyOrderInfo> buyCoin(
-    String accountReference,
-    String source,
-    ICurrency target,
-    String walletAddress,
-    FiatPaymentMethod paymentMethod,
-    String sourceAmount,
-    String returnUrlOnSuccess,
-  ) async {
+  Future<FiatBuyOrderInfo> buyCoin({
+    required String accountReference,
+    required String source,
+    required ICurrency target,
+    required String walletAddress,
+    required FiatPaymentMethod paymentMethod,
+    required String sourceAmount,
+    required String returnUrlOnSuccess,
+  }) async {
path: lib/bloc/fiat/fiat_repository.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improved method signature with named parameters**

The `buyCoin` method now uses named parameters with `required` keywords instead of positional parameters, which enhances code readability and maintainability by making parameter roles explicit at call sites.


This change also makes the code more maintainable as future parameter additions won't require updating all call sites if they provide default values.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-01T20:38:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -52,17 +52,20 @@ class FiatBuyOrderInfo extends Equatable {
           extAccountId: '',
           network: '',
           paymentCode: '',
-          checkoutUrl: url,
+          checkoutUrl: '',
           createdAt: '',
           error: const FiatBuyOrderError.none(),
         );
 
   factory FiatBuyOrderInfo.fromJson(Map<String, dynamic> json) {
-    Map<String, dynamic> data = json;
-    if (json['data'] != null) {
-      final orderData = json['data'] as Map<String, dynamic>? ?? {};
-      data = orderData['order'] as Map<String, dynamic>? ?? {};
+    final jsonData = json['data'] as Map<String, dynamic>?;
+    if (json['data'] == null || jsonData?['order'] == null) {
+      return FiatBuyOrderInfo.empty().copyWith(
+        error:
+            const FiatBuyOrderError.parsing(message: 'Missing order payload'),
+      );
     }
+    final data = jsonData!['order'] as Map<String, dynamic>;
 
path: lib/bloc/fiat/models/fiat_buy_order_info.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Added robust error handling for JSON parsing**

The `fromJson` factory now explicitly checks for the presence of nested `data` and `order` keys, returning an error state with a descriptive message when the expected structure is missing.


This defensive approach prevents null reference exceptions during JSON deserialization and provides meaningful error information when parsing fails.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-01T20:38:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,18 +48,21 @@ enum FiatOrderStatus {
       case 'refunded':
         return FiatOrderStatus.failed;
 
-      case 'extraVerification':
-      case 'pendingPayment':
-      case 'waitingPayment':
-        return FiatOrderStatus.pending;
+      case 'extraverification':
+      case 'pendingpayment':
+      case 'waitingpayment':
+        return FiatOrderStatus.pendingPayment;
 
-      case 'paymentReceived':
-      case 'inProgress':
-      case 'coinTransferred':
+      case 'paymentreceived':
+      case 'inprogress':
+      case 'cointransferred':
+      case 'cryptotransferred':
         return FiatOrderStatus.inProgress;
 
       default:
-        throw Exception('Unknown status: $status');
+        Logger('FiatOrderStatus')
+            .warning('Unknown status: $status, defaulting to failed');
+        return FiatOrderStatus.failed;
     }
path: lib/bloc/fiat/fiat_order_status.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Fail-open vs. fail-closed default – reconsider mapping to `failed`**

Silently downgrading any unknown status to `failed` may surface false failure pop-ups and unnecessarily alarm users.  Consider a safer fallback such as `inProgress` (with telemetry) or re-throwing so upper layers can decide.

```diff
-  Logger('FiatOrderStatus')
-      .warning('Unknown status: $status, defaulting to failed');
-  return FiatOrderStatus.failed;
+  Logger('FiatOrderStatus')
+      .warning('Unknown status: $status, defaulting to inProgress');
+  return FiatOrderStatus.inProgress;
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
        Logger('FiatOrderStatus')
            .warning('Unknown status: $status, defaulting to inProgress');
        return FiatOrderStatus.inProgress;
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T20:38:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -254,37 +230,32 @@ class _FiatFormState extends State<FiatForm> {
 
     String? title;
     String? content;
-
-    // TODO: Use theme-based semantic colors
     Icon? icon;
 
     switch (status) {
       case FiatOrderStatus.inProgress:
       case FiatOrderStatus.windowCloseRequested:
-      case FiatOrderStatus.pending:
+      case FiatOrderStatus.initial:
+      case FiatOrderStatus.pendingPayment:
         debugPrint('Pending status should not be shown in dialog.');
         return;
-
       case FiatOrderStatus.submitted:
         title = LocaleKeys.fiatPaymentSubmittedTitle.tr();
         content = LocaleKeys.fiatPaymentSubmittedMessage.tr();
         icon = const Icon(Icons.open_in_new);
-
       case FiatOrderStatus.success:
         title = LocaleKeys.fiatPaymentSuccessTitle.tr();
         content = LocaleKeys.fiatPaymentSuccessMessage.tr();
         icon = const Icon(Icons.check_circle_outline);
-
       case FiatOrderStatus.failed:
         title = LocaleKeys.fiatPaymentFailedTitle.tr();
-        // TODO: If we implement provider-specific error messages,
-        // we can include support details.
         content = LocaleKeys.fiatPaymentFailedMessage.tr();
         icon = const Icon(Icons.error_outline, color: Colors.red);
     }
path: lib/views/fiat/fiat_form.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing `break` statements cause a compile-time error**

Dart requires each `case` body to terminate.  
The current implementation falls through from `submitted → success → failed`, which will not compile.

```diff
      case FiatOrderStatus.submitted:
        title = LocaleKeys.fiatPaymentSubmittedTitle.tr();
        content = LocaleKeys.fiatPaymentSubmittedMessage.tr();
        icon = const Icon(Icons.open_in_new);
+       break;
      case FiatOrderStatus.success:
        title = LocaleKeys.fiatPaymentSuccessTitle.tr();
        content = LocaleKeys.fiatPaymentSuccessMessage.tr();
        icon = const Icon(Icons.check_circle_outline);
+       break;
      case FiatOrderStatus.failed:
        title = LocaleKeys.fiatPaymentFailedTitle.tr();
        content = LocaleKeys.fiatPaymentFailedMessage.tr();
        icon = const Icon(Icons.error_outline, color: Colors.red);
+       break;
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
    switch (status) {
      case FiatOrderStatus.inProgress:
      case FiatOrderStatus.windowCloseRequested:
      case FiatOrderStatus.initial:
      case FiatOrderStatus.pendingPayment:
        debugPrint('Pending status should not be shown in dialog.');
        return;
      case FiatOrderStatus.submitted:
        title = LocaleKeys.fiatPaymentSubmittedTitle.tr();
        content = LocaleKeys.fiatPaymentSubmittedMessage.tr();
        icon = const Icon(Icons.open_in_new);
        break;
      case FiatOrderStatus.success:
        title = LocaleKeys.fiatPaymentSuccessTitle.tr();
        content = LocaleKeys.fiatPaymentSuccessMessage.tr();
        icon = const Icon(Icons.check_circle_outline);
        break;
      case FiatOrderStatus.failed:
        title = LocaleKeys.fiatPaymentFailedTitle.tr();
        content = LocaleKeys.fiatPaymentFailedMessage.tr();
        icon = const Icon(Icons.error_outline, color: Colors.red);
        break;
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T20:38:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,10 +1,12 @@
 import 'dart:convert';
+import 'dart:io' show Platform;
 
path: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`dart:io` import breaks web builds – use conditional imports or `kIsWeb` checks only**  
Importing `dart:io` unconditionally prevents this file from compiling for Flutter Web (dart2js) because `dart:io` is not available there.  Either:

1. Use a conditional import (`import 'platform_stub.dart' if (dart.library.io) 'dart:io' show Platform;`) or  
2. Drop the `Platform` usage and rely solely on `kIsWeb`/`defaultTargetPlatform`.

Without this, the entire web build will fail at compile-time.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T20:38:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -194,6 +205,25 @@
     }
   }
 
+  /// Determines the appropriate WebViewDialogMode based on platform and
+  /// environment
+  WebViewDialogMode _determineWebViewMode() {
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    const bool isWeb = kIsWeb || kIsWasm;
+    final bool isBanxa = state.selectedPaymentMethod.providerId == 'Banxa';
+
+    // Banxa "Return to Komodo" button attempts to navigate the top window to
+    // the return URL, which is not supported in a dialog. So we need to open
+    // it in a new tab.
+    if (isLinux || (isWeb && isBanxa)) {
+      return WebViewDialogMode.newTab;
+    } else if (isWeb) {
+      return WebViewDialogMode.dialog;
+    } else {
+      return WebViewDialogMode.fullscreen;
+    }
+  }
path: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`kIsWasm` is undefined – compilation will fail**  
`kIsWasm` is not a constant provided by `package:flutter/foundation.dart`.  The expression will throw a “getter not found” error.  
Replace the wasm check with `kIsWeb` (since current Flutter Web output can be JS or Wasm) or define your own constant behind a conditional import.

```diff
-    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
-    const bool isWeb = kIsWeb || kIsWasm;
+    final bool isLinux = !kIsWeb && Platform.isLinux;
+    const bool isWeb = kIsWeb;
```

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T20:38:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,55 +5,88 @@ import 'package:flutter/material.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
 import 'package:web_dex/common/screen.dart';
 import 'package:web_dex/shared/utils/utils.dart';
+import 'package:web_dex/shared/utils/window/window.dart';
+
+/// The display mode for the webview dialog.
+enum WebViewDialogMode {
+  /// Show the webview in a dialog or popup window.
+  dialog,
+
+  /// Show the webview in fullscreen mode, as a new material navigation route.
+  fullscreen,
+
+  /// Show the webview in a new browser tab (web) or external browser.
+  newTab,
+}
 
 class WebViewDialog {
+  /// Shows a webview dialog with the given [url] and [title].
+  /// The [onMessage] callback is called with the console messages from
+  /// the webview.
+  /// The [onCloseWindow] callback is called when the webview is closed.
+  /// The [settings] parameter allows you to customize [InAppWebViewSettings]
+  /// The [mode] parameter allows you to choose how the webview is shown.
+  /// The [width] and [height] parameters allow you to customize the size of the
+  /// dialog.
   static Future<void> show(
     BuildContext context, {
     required String url,
     required String title,
-    void Function(String)? onConsoleMessage,
+    void Function(String)? onMessage,
     VoidCallback? onCloseWindow,
     InAppWebViewSettings? settings,
+    WebViewDialogMode? mode,
+    double width = 700,
+    double height = 700,
   }) async {
+    final webviewSettings = settings ??
+        InAppWebViewSettings(isInspectable: kDebugMode, iframeSandbox: {
+          Sandbox.ALLOW_SAME_ORIGIN,
+          Sandbox.ALLOW_SCRIPTS,
+          Sandbox.ALLOW_FORMS,
+          Sandbox.ALLOW_POPUPS,
+        });
+
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    final bool isWeb = (kIsWeb || kIsWasm) && !isMobile;
+    final WebViewDialogMode defaultMode =
+        isWeb ? WebViewDialogMode.dialog : WebViewDialogMode.fullscreen;
+    final WebViewDialogMode resolvedMode = mode ?? defaultMode;
+
path: lib/views/fiat/webview_dialog.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`kIsWasm` again undefined & `dart:io` coupling**  
The same two compile-time issues appear here:

1. `kIsWasm` is undefined.  
2. `Platform` from `dart:io` is imported unconditionally, breaking web builds.

Proposed fix (mirrors earlier suggestion):

```diff
-    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
-    final bool isWeb = (kIsWeb || kIsWasm) && !isMobile;
+    final bool isLinux = !kIsWeb && Platform.isLinux;
+    final bool isWeb = kIsWeb && !isMobile;
```

Consider extracting the platform helpers into a single `platform_utils.dart` to avoid duplication.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T20:38:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -254,37 +230,32 @@ class _FiatFormState extends State<FiatForm> {
 
     String? title;
     String? content;
-
-    // TODO: Use theme-based semantic colors
     Icon? icon;
 
     switch (status) {
       case FiatOrderStatus.inProgress:
       case FiatOrderStatus.windowCloseRequested:
-      case FiatOrderStatus.pending:
+      case FiatOrderStatus.initial:
+      case FiatOrderStatus.pendingPayment:
         debugPrint('Pending status should not be shown in dialog.');
         return;
-
       case FiatOrderStatus.submitted:
         title = LocaleKeys.fiatPaymentSubmittedTitle.tr();
         content = LocaleKeys.fiatPaymentSubmittedMessage.tr();
         icon = const Icon(Icons.open_in_new);
-
       case FiatOrderStatus.success:
         title = LocaleKeys.fiatPaymentSuccessTitle.tr();
         content = LocaleKeys.fiatPaymentSuccessMessage.tr();
         icon = const Icon(Icons.check_circle_outline);
-
       case FiatOrderStatus.failed:
         title = LocaleKeys.fiatPaymentFailedTitle.tr();
-        // TODO: If we implement provider-specific error messages,
-        // we can include support details.
         content = LocaleKeys.fiatPaymentFailedMessage.tr();
         icon = const Icon(Icons.error_outline, color: Colors.red);
     }
path: lib/views/fiat/fiat_form.dart
user: login: takenagain
body: Only use a break in a non-empty switch case statement if you need to break before the end of the case body. Dart does not support fallthrough execution for non-empty cases, so breaks at the end of non-empty switch case statements are unnecessary.

https://dart.dev/tools/linter-rules/unnecessary_breaks
created_at: 2025-05-01T20:52:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -254,37 +230,32 @@ class _FiatFormState extends State<FiatForm> {
 
     String? title;
     String? content;
-
-    // TODO: Use theme-based semantic colors
     Icon? icon;
 
     switch (status) {
       case FiatOrderStatus.inProgress:
       case FiatOrderStatus.windowCloseRequested:
-      case FiatOrderStatus.pending:
+      case FiatOrderStatus.initial:
+      case FiatOrderStatus.pendingPayment:
         debugPrint('Pending status should not be shown in dialog.');
         return;
-
       case FiatOrderStatus.submitted:
         title = LocaleKeys.fiatPaymentSubmittedTitle.tr();
         content = LocaleKeys.fiatPaymentSubmittedMessage.tr();
         icon = const Icon(Icons.open_in_new);
-
       case FiatOrderStatus.success:
         title = LocaleKeys.fiatPaymentSuccessTitle.tr();
         content = LocaleKeys.fiatPaymentSuccessMessage.tr();
         icon = const Icon(Icons.check_circle_outline);
-
       case FiatOrderStatus.failed:
         title = LocaleKeys.fiatPaymentFailedTitle.tr();
-        // TODO: If we implement provider-specific error messages,
-        // we can include support details.
         content = LocaleKeys.fiatPaymentFailedMessage.tr();
         icon = const Icon(Icons.error_outline, color: Colors.red);
     }
path: lib/views/fiat/fiat_form.dart
user: login: coderabbitai[bot]
body: @takenagain You're absolutely right! Thank you for pointing this out and sharing the documentation link. In Dart, non-empty case statements don't fall through to the next case by default, making explicit `break` statements at the end of each case unnecessary. 

This is indeed different from languages like C, Java, or JavaScript where fallthrough is the default behavior. I appreciate the correction!

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/views/fiat/fiat_form.dart:235-254
Timestamp: 2025-05-01T20:52:26.700Z
Learning: In Dart, non-empty switch case statements do not require explicit `break` statements at the end as they don't fall through to the next case by default. Adding unnecessary breaks is against the "unnecessary_breaks" linter rule (https://dart.dev/tools/linter-rules/unnecessary_breaks).
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@takenagain You're absolutely right! Thank you for pointing this out and sharing the documentation link. In Dart, non-empty case statements don't fall through to the next case by default, making explicit `break` statements at the end of each case unnecessary. 

This is indeed different from languages like C, Java, or JavaScript where fallthrough is the default behavior. I appreciate the correction!

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/views/fiat/fiat_form.dart:235-254
Timestamp: 2025-05-01T20:52:26.700Z
Learning: In Dart, non-empty switch case statements do not require explicit `break` statements at the end as they don't fall through to the next case by default. Adding unnecessary breaks is against the "unnecessary_breaks" linter rule (https://dart.dev/tools/linter-rules/unnecessary_breaks).
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T20:52:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -194,6 +205,25 @@
     }
   }
 
+  /// Determines the appropriate WebViewDialogMode based on platform and
+  /// environment
+  WebViewDialogMode _determineWebViewMode() {
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    const bool isWeb = kIsWeb || kIsWasm;
+    final bool isBanxa = state.selectedPaymentMethod.providerId == 'Banxa';
+
+    // Banxa "Return to Komodo" button attempts to navigate the top window to
+    // the return URL, which is not supported in a dialog. So we need to open
+    // it in a new tab.
+    if (isLinux || (isWeb && isBanxa)) {
+      return WebViewDialogMode.newTab;
+    } else if (isWeb) {
+      return WebViewDialogMode.dialog;
+    } else {
+      return WebViewDialogMode.fullscreen;
+    }
+  }
path: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
user: login: takenagain
body: It is defined in the latest flutter version: https://api.flutter.dev/flutter/foundation/kIsWasm-constant.html
created_at: 2025-05-01T20:55:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -194,6 +205,25 @@
     }
   }
 
+  /// Determines the appropriate WebViewDialogMode based on platform and
+  /// environment
+  WebViewDialogMode _determineWebViewMode() {
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    const bool isWeb = kIsWeb || kIsWasm;
+    final bool isBanxa = state.selectedPaymentMethod.providerId == 'Banxa';
+
+    // Banxa "Return to Komodo" button attempts to navigate the top window to
+    // the return URL, which is not supported in a dialog. So we need to open
+    // it in a new tab.
+    if (isLinux || (isWeb && isBanxa)) {
+      return WebViewDialogMode.newTab;
+    } else if (isWeb) {
+      return WebViewDialogMode.dialog;
+    } else {
+      return WebViewDialogMode.fullscreen;
+    }
+  }
path: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

The link you shared confirms that `kIsWasm` exists in the Flutter API. I need to verify when it was added to make sure I provide accurate information.

---

🌐 Web query:

```web
When was kIsWasm constant added to Flutter? What version?
```

💡 Result:

The `kIsWasm` constant was added to Flutter with the introduction of stable WebAssembly (Wasm) support. This significant update arrived in Flutter version **3.22**, which was released in May 2024. In this version, `kIsWasm` became available as a compile-time constant (in the `foundation` library) that is `true` when the application was compiled to WebAssembly[2][4][7]. 

This constant allows developers to check at runtime if their Flutter app is running using the Wasm backend, which brings performance improvements and expanded web capabilities in Flutter 3.22[7].

Citations:

- 1: https://api.flutter.dev/flutter/foundation/kIsWasm-constant.html
- 2: https://api.flutter.dev/flutter/foundation/
- 3: https://github.com/SAP/gigya-flutter-plugin/issues/44
- 4: https://api.flutter.dev/flutter/foundation/foundation-library.html
- 5: https://github.com/rive-app/rive-flutter/issues/391
- 6: https://docs.flutter.dev/release/archive
- 7: https://www.walturn.com/insights/flutter-322-whats-new
- 8: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html
- 9: https://docs.flutter.dev/release/release-notes
- 10: https://github.com/firebase/firebase-tools/issues/7554

---



</details>

@takenagain You're absolutely right! Thank you for the correction. The `kIsWasm` constant was added in Flutter 3.22 (released in May 2024) and is available in the `foundation` library. It's used to detect when a Flutter app is running using the WebAssembly backend.

I apologize for my incorrect suggestion. Your code correctly distinguishes between the different web rendering backends and platform environments.



ʕ•ᴥ•ʔ

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:208-225
Timestamp: 2025-05-01T20:56:36.153Z
Learning: The `kIsWasm` constant is available in Flutter version 3.22+ (released May 2024) and is used to detect if an application is running on the WebAssembly backend. When writing platform-specific code, use both `kIsWeb` and `kIsWasm` checks to properly handle both JavaScript and WebAssembly compilation targets.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><details>
<summary>🧩 Analysis chain</summary>

The link you shared confirms that `kIsWasm` exists in the Flutter API. I need to verify when it was added to make sure I provide accurate information.

---

🌐 Web query:

```web
When was kIsWasm constant added to Flutter? What version?
```

💡 Result:

The `kIsWasm` constant was added to Flutter with the introduction of stable WebAssembly (Wasm) support. This significant update arrived in Flutter version **3.22**, which was released in May 2024. In this version, `kIsWasm` became available as a compile-time constant (in the `foundation` library) that is `true` when the application was compiled to WebAssembly[2][4][7]. 

This constant allows developers to check at runtime if their Flutter app is running using the Wasm backend, which brings performance improvements and expanded web capabilities in Flutter 3.22[7].

Citations:

- 1: https://api.flutter.dev/flutter/foundation/kIsWasm-constant.html
- 2: https://api.flutter.dev/flutter/foundation/
- 3: https://github.com/SAP/gigya-flutter-plugin/issues/44
- 4: https://api.flutter.dev/flutter/foundation/foundation-library.html
- 5: https://github.com/rive-app/rive-flutter/issues/391
- 6: https://docs.flutter.dev/release/archive
- 7: https://www.walturn.com/insights/flutter-322-whats-new
- 8: https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html
- 9: https://docs.flutter.dev/release/release-notes
- 10: https://github.com/firebase/firebase-tools/issues/7554

---



</details>

@takenagain You're absolutely right! Thank you for the correction. The `kIsWasm` constant was added in Flutter 3.22 (released in May 2024) and is available in the `foundation` library. It's used to detect when a Flutter app is running using the WebAssembly backend.

I apologize for my incorrect suggestion. Your code correctly distinguishes between the different web rendering backends and platform environments.



ʕ•ᴥ•ʔ

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:208-225
Timestamp: 2025-05-01T20:56:36.153Z
Learning: The `kIsWasm` constant is available in Flutter version 3.22+ (released May 2024) and is used to detect if an application is running on the WebAssembly backend. When writing platform-specific code, use both `kIsWeb` and `kIsWasm` checks to properly handle both JavaScript and WebAssembly compilation targets.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T20:56:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,10 +1,12 @@
 import 'dart:convert';
+import 'dart:io' show Platform;
 
path: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
user: login: takenagain
body: The `isLinux` check fails early with the `!kIsWeb` condition, so compilation succeeds without issues for web
created_at: 2025-05-01T21:00:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,55 +5,88 @@ import 'package:flutter/material.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
 import 'package:web_dex/common/screen.dart';
 import 'package:web_dex/shared/utils/utils.dart';
+import 'package:web_dex/shared/utils/window/window.dart';
+
+/// The display mode for the webview dialog.
+enum WebViewDialogMode {
+  /// Show the webview in a dialog or popup window.
+  dialog,
+
+  /// Show the webview in fullscreen mode, as a new material navigation route.
+  fullscreen,
+
+  /// Show the webview in a new browser tab (web) or external browser.
+  newTab,
+}
 
 class WebViewDialog {
+  /// Shows a webview dialog with the given [url] and [title].
+  /// The [onMessage] callback is called with the console messages from
+  /// the webview.
+  /// The [onCloseWindow] callback is called when the webview is closed.
+  /// The [settings] parameter allows you to customize [InAppWebViewSettings]
+  /// The [mode] parameter allows you to choose how the webview is shown.
+  /// The [width] and [height] parameters allow you to customize the size of the
+  /// dialog.
   static Future<void> show(
     BuildContext context, {
     required String url,
     required String title,
-    void Function(String)? onConsoleMessage,
+    void Function(String)? onMessage,
     VoidCallback? onCloseWindow,
     InAppWebViewSettings? settings,
+    WebViewDialogMode? mode,
+    double width = 700,
+    double height = 700,
   }) async {
+    final webviewSettings = settings ??
+        InAppWebViewSettings(isInspectable: kDebugMode, iframeSandbox: {
+          Sandbox.ALLOW_SAME_ORIGIN,
+          Sandbox.ALLOW_SCRIPTS,
+          Sandbox.ALLOW_FORMS,
+          Sandbox.ALLOW_POPUPS,
+        });
+
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    final bool isWeb = (kIsWeb || kIsWasm) && !isMobile;
+    final WebViewDialogMode defaultMode =
+        isWeb ? WebViewDialogMode.dialog : WebViewDialogMode.fullscreen;
+    final WebViewDialogMode resolvedMode = mode ?? defaultMode;
+
path: lib/views/fiat/webview_dialog.dart
user: login: takenagain
body: It is defined in the latest flutter version: https://api.flutter.dev/flutter/foundation/kIsWasm-constant.html

The `isLinux` check fails early with the `!kIsWeb` condition, so compilation succeeds without issues for web
created_at: 2025-05-01T21:00:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,10 +1,12 @@
 import 'dart:convert';
+import 'dart:io' show Platform;
 
path: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart
user: login: coderabbitai[bot]
body: @takenagain You're right, the short-circuit evaluation in the boolean expression `!kIsWeb && !kIsWasm && Platform.isLinux` prevents `Platform.isLinux` from being evaluated in web environments. This is why your web compilation succeeds despite the unconditional `dart:io` import.

The Dart compiler is likely optimizing this code path out during the web build process. While unconditional `dart:io` imports are generally not recommended for cross-platform code, your implementation with the early check is working effectively for your use case.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:2-3
Timestamp: 2025-05-01T21:00:36.900Z
Learning: It's acceptable to use unconditional `dart:io` imports in the Komodo wallet codebase when the usage is guarded by `!kIsWeb` conditional checks that prevent the platform-specific code from being executed in web environments.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@takenagain You're right, the short-circuit evaluation in the boolean expression `!kIsWeb && !kIsWasm && Platform.isLinux` prevents `Platform.isLinux` from being evaluated in web environments. This is why your web compilation succeeds despite the unconditional `dart:io` import.

The Dart compiler is likely optimizing this code path out during the web build process. While unconditional `dart:io` imports are generally not recommended for cross-platform code, your implementation with the early check is working effectively for your use case.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:2-3
Timestamp: 2025-05-01T21:00:36.900Z
Learning: It's acceptable to use unconditional `dart:io` imports in the Komodo wallet codebase when the usage is guarded by `!kIsWeb` conditional checks that prevent the platform-specific code from being executed in web environments.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T21:00:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,55 +5,88 @@ import 'package:flutter/material.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
 import 'package:web_dex/common/screen.dart';
 import 'package:web_dex/shared/utils/utils.dart';
+import 'package:web_dex/shared/utils/window/window.dart';
+
+/// The display mode for the webview dialog.
+enum WebViewDialogMode {
+  /// Show the webview in a dialog or popup window.
+  dialog,
+
+  /// Show the webview in fullscreen mode, as a new material navigation route.
+  fullscreen,
+
+  /// Show the webview in a new browser tab (web) or external browser.
+  newTab,
+}
 
 class WebViewDialog {
+  /// Shows a webview dialog with the given [url] and [title].
+  /// The [onMessage] callback is called with the console messages from
+  /// the webview.
+  /// The [onCloseWindow] callback is called when the webview is closed.
+  /// The [settings] parameter allows you to customize [InAppWebViewSettings]
+  /// The [mode] parameter allows you to choose how the webview is shown.
+  /// The [width] and [height] parameters allow you to customize the size of the
+  /// dialog.
   static Future<void> show(
     BuildContext context, {
     required String url,
     required String title,
-    void Function(String)? onConsoleMessage,
+    void Function(String)? onMessage,
     VoidCallback? onCloseWindow,
     InAppWebViewSettings? settings,
+    WebViewDialogMode? mode,
+    double width = 700,
+    double height = 700,
   }) async {
+    final webviewSettings = settings ??
+        InAppWebViewSettings(isInspectable: kDebugMode, iframeSandbox: {
+          Sandbox.ALLOW_SAME_ORIGIN,
+          Sandbox.ALLOW_SCRIPTS,
+          Sandbox.ALLOW_FORMS,
+          Sandbox.ALLOW_POPUPS,
+        });
+
+    final bool isLinux = !kIsWeb && !kIsWasm && Platform.isLinux;
+    final bool isWeb = (kIsWeb || kIsWasm) && !isMobile;
+    final WebViewDialogMode defaultMode =
+        isWeb ? WebViewDialogMode.dialog : WebViewDialogMode.fullscreen;
+    final WebViewDialogMode resolvedMode = mode ?? defaultMode;
+
path: lib/views/fiat/webview_dialog.dart
user: login: coderabbitai[bot]
body: @takenagain You're absolutely right about `kIsWasm` - it is indeed available in Flutter 3.22+ as the documentation confirms. Thank you for providing the link.

Regarding the `dart:io` import, there's a subtle distinction to consider: while the runtime evaluation with `!kIsWeb && ...` would indeed short-circuit as you mentioned, the unconditional import itself can still cause compilation errors for web targets regardless of whether the code inside gets executed. 

For web-compatible code, you might consider one of these approaches:
1. Use conditional imports: 
   ```dart
   // Import dart:io only on non-web platforms
   import 'dart:io' if (dart.library.js) 'package:web_dex/shared/utils/platform/web_platform.dart';
   ```
2. Or move platform checks to a utility class that handles the conditional imports internally.

That said, if your current approach is working without compilation issues, that's great! I'm just highlighting best practices for cross-platform Flutter code.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/views/fiat/webview_dialog.dart:50-55
Timestamp: 2025-05-01T21:00:56.935Z
Learning: `kIsWasm` is a valid constant in Flutter 3.22+ (released May 2024) that detects if an application is running on WebAssembly. It complements `kIsWeb` for more precise platform detection.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>



---

<details>
<summary>🧠 Learnings used</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:208-225
Timestamp: 2025-05-01T20:56:36.153Z
Learning: The `kIsWasm` constant is available in Flutter version 3.22+ (released May 2024) and is used to detect if an application is running on the WebAssembly backend. When writing platform-specific code, use both `kIsWeb` and `kIsWasm` checks to properly handle both JavaScript and WebAssembly compilation targets.
```

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@takenagain You're absolutely right about `kIsWasm` - it is indeed available in Flutter 3.22+ as the documentation confirms. Thank you for providing the link.

Regarding the `dart:io` import, there's a subtle distinction to consider: while the runtime evaluation with `!kIsWeb && ...` would indeed short-circuit as you mentioned, the unconditional import itself can still cause compilation errors for web targets regardless of whether the code inside gets executed. 

For web-compatible code, you might consider one of these approaches:
1. Use conditional imports: 
   ```dart
   // Import dart:io only on non-web platforms
   import 'dart:io' if (dart.library.js) 'package:web_dex/shared/utils/platform/web_platform.dart';
   ```
2. Or move platform checks to a utility class that handles the conditional imports internally.

That said, if your current approach is working without compilation issues, that's great! I'm just highlighting best practices for cross-platform Flutter code.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/views/fiat/webview_dialog.dart:50-55
Timestamp: 2025-05-01T21:00:56.935Z
Learning: `kIsWasm` is a valid constant in Flutter 3.22+ (released May 2024) that detects if an application is running on WebAssembly. It complements `kIsWeb` for more precise platform detection.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>



---

<details>
<summary>🧠 Learnings used</summary>

```
Learnt from: takenagain
PR: KomodoPlatform/komodo-wallet#2608
File: lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart:208-225
Timestamp: 2025-05-01T20:56:36.153Z
Learning: The `kIsWasm` constant is available in Flutter version 3.22+ (released May 2024) and is used to detect if an application is running on the WebAssembly backend. When writing platform-specific code, use both `kIsWeb` and `kIsWasm` checks to properly handle both JavaScript and WebAssembly compilation targets.
```

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T21:00:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,18 +48,21 @@ enum FiatOrderStatus {
       case 'refunded':
         return FiatOrderStatus.failed;
 
-      case 'extraVerification':
-      case 'pendingPayment':
-      case 'waitingPayment':
-        return FiatOrderStatus.pending;
+      case 'extraverification':
+      case 'pendingpayment':
+      case 'waitingpayment':
+        return FiatOrderStatus.pendingPayment;
 
-      case 'paymentReceived':
-      case 'inProgress':
-      case 'coinTransferred':
+      case 'paymentreceived':
+      case 'inprogress':
+      case 'cointransferred':
+      case 'cryptotransferred':
         return FiatOrderStatus.inProgress;
 
       default:
-        throw Exception('Unknown status: $status');
+        Logger('FiatOrderStatus')
+            .warning('Unknown status: $status, defaulting to failed');
+        return FiatOrderStatus.failed;
     }
path: lib/bloc/fiat/fiat_order_status.dart
user: login: takenagain
body: Done in [58f2e0e](https://github.com/KomodoPlatform/komodo-wallet/pull/2608/commits/58f2e0e385d7107eb610e7763e62a1c395984136)
created_at: 2025-05-01T21:01:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,109 +2,137 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
+  <iframe id="fiat-onramp-iframe"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.onmessage = _komodoOnMessageHandler;
+    window.addEventListener('message', _komodoOnMessageHandler, false);
+    window.addEventListener('load', function () {
+      _komodoSetIframeUrlFromParams(_komodoGetUrlParameter('fiatUrl'));
+    });
+
+    /**
+     * Initialize the iframe URL based on URL parameters
+     * 
+     * @param {object} params - The URL parameters to use for initialization
+     */
+    function _komodoSetIframeUrlFromParams(params) {
+      const urlParam = _komodoGetUrlParameter('fiatUrl');
+
+      let targetUrl = null;
+      if (urlParam) {
+        try {
+          targetUrl = atob(urlParam); // base64 decode the `url` parameter
path: assets/web_pages/fiat_widget.html
user: login: takenagain
body: Done in [58f2e0e](https://github.com/KomodoPlatform/komodo-wallet/pull/2608/commits/58f2e0e385d7107eb610e7763e62a1c395984136)
created_at: 2025-05-01T21:01:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,109 +2,137 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
+  <iframe id="fiat-onramp-iframe"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.onmessage = _komodoOnMessageHandler;
path: assets/web_pages/fiat_widget.html
user: login: takenagain
body: Done in [58f2e0e](https://github.com/KomodoPlatform/komodo-wallet/pull/2608/commits/58f2e0e385d7107eb610e7763e62a1c395984136)
created_at: 2025-05-01T21:01:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,18 +48,21 @@ enum FiatOrderStatus {
       case 'refunded':
         return FiatOrderStatus.failed;
 
-      case 'extraVerification':
-      case 'pendingPayment':
-      case 'waitingPayment':
-        return FiatOrderStatus.pending;
+      case 'extraverification':
+      case 'pendingpayment':
+      case 'waitingpayment':
+        return FiatOrderStatus.pendingPayment;
 
-      case 'paymentReceived':
-      case 'inProgress':
-      case 'coinTransferred':
+      case 'paymentreceived':
+      case 'inprogress':
+      case 'cointransferred':
+      case 'cryptotransferred':
         return FiatOrderStatus.inProgress;
 
       default:
-        throw Exception('Unknown status: $status');
+        Logger('FiatOrderStatus')
+            .warning('Unknown status: $status, defaulting to failed');
+        return FiatOrderStatus.failed;
     }
path: lib/bloc/fiat/fiat_order_status.dart
user: login: coderabbitai[bot]
body: @takenagain Thank you for implementing this change! This is a great improvement to your error handling approach - users will now have a more graceful experience when encountering unexpected status values, and it prevents unnecessary alarm bells from ringing. 



(⌐■_■)🐰 Excellent!

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20highlights%20a%20key%20safety%20fix%3A%20default%20unknown%20statuses%20to%20%60inProgress%60%20instead%20of%20%60failed%60%20to%20avoid%20false%20alarms%20and%20improve%20user%20trust.%20Smarter%20error%20handling%20matters!%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2FKomodoPlatform%2Fkomodo-wallet%2Fpull%2F2608%23discussion_r2070775536)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@takenagain Thank you for implementing this change! This is a great improvement to your error handling approach - users will now have a more graceful experience when encountering unexpected status values, and it prevents unnecessary alarm bells from ringing. 



(⌐■_■)🐰 Excellent!

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20highlights%20a%20key%20safety%20fix%3A%20default%20unknown%20statuses%20to%20%60inProgress%60%20instead%20of%20%60failed%60%20to%20avoid%20false%20alarms%20and%20improve%20user%20trust.%20Smarter%20error%20handling%20matters!%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2FKomodoPlatform%2Fkomodo-wallet%2Fpull%2F2608%23discussion_r2070775536)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T21:01:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,109 +2,136 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
+  <iframe id="fiat-onramp-iframe"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.addEventListener('message', _komodoOnMessageHandler, false);
+    window.addEventListener('load', function () {
+      _komodoSetIframeUrlFromParams();
+    });
+
+    /**
+     * Initialize the iframe URL based on URL parameters
+     * 
+     * @param {object} params - The URL parameters to use for initialization
+     */
+    function _komodoSetIframeUrlFromParams() {
+      const urlParam = _komodoGetUrlParameter('fiatUrl');
+
+      let targetUrl = null;
+      if (urlParam) {
+        try {
+          targetUrl = atob(urlParam); // base64 decode the `url` parameter
+        } catch (error) {
+          console.error('Error decoding base64 url parameter', error);
         }
-
-        if (targetUrl) {
-            // Set the iframe's src attribute
-            document.getElementById('fiat-onramp-iframe').src = targetUrl;
-        } else {
-            console.error('No URL parameter provided');
-        }
-
-        function onMessageHandler(messageEvent) {
-            let messageData;
-            try {
-                // Try parsing the data as JSON 
-                messageData = JSON.parse(messageEvent.data);
-            } catch (parseError) {
-                // If parsing fails, just use it as a string
-                messageData = messageEvent.data;
-            }
-
-            try {
-                postMessageToParent(messageData);
-            } catch (postError) {
-                console.error('Error posting message', postError);
-            }
-        }
-
-        /** 
-         * Post a message to the parent window
-         * 
-         * @param {string|object} messageData 
-         */
-        function postMessageToParent(messageData) {
-            // Convert messageData to a string if it is an object
-            const messageString = (typeof messageData === 'object') ? JSON.stringify(messageData) : String(messageData);
-
-            // flutter_inappwebview
-            console.log(messageString);
-
-            // universal_url opener 
-            if (window.opener) {
-                return window.opener.postMessage(messageString, "*");
-            }
-
-            if (window.parent && window.parent !== window) {
-                return window.parent.postMessage(messageString, "*");
-            }
-
-            // Windows WebView2 (desktop_webview_window) - https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/communicate-btwn-web-native 
-            if (window.chrome && window.chrome.webview) {
-                return window.chrome.webview.postMessage(messageString);
-            }
-
-            console.error('No valid postMessage target found');
+      }
+
+      if (targetUrl) {
+        document.getElementById('fiat-onramp-iframe').src = targetUrl;
+      } else {
+        console.error('No URL parameter provided');
+      }
+    }
+
+    /**
+     * Get URL parameter by name
+     * 
+     * @param {string} name - The name of the URL parameter to retrieve
+     * @returns {string|null} - The value of the URL parameter or null if not found
+     */
+    function _komodoGetUrlParameter(name) {
+      const params = new URLSearchParams(window.location.search);
+      return params.get(name);
+    }
+
+    /** 
+     * Handle messages from the iframe
+     * 
+     * @param {MessageEvent} messageEvent 
+     */
+    function _komodoOnMessageHandler(messageEvent) {
+      let messageData;
+      try {
+        messageData = typeof messageEvent.data === 'string' ? JSON.parse(messageEvent.data) : messageEvent.data;
+      } catch (parseError) {
+        messageData = messageEvent.data;
+      }
+
+      // forward navigation requests to parent
+      if (messageData && typeof messageData === 'object') {
+        if ((messageData.type === 'navigation' || messageData.action === 'checkout_complete') && messageData.url) {
+          window.location.href = messageData.url;
+          return;
         }
path: assets/web_pages/fiat_widget.html
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Validate `messageEvent.origin` before processing to prevent malicious redirects.**

Right now, any window can post a message that triggers `window.location.href`, leading to open-redirect or navigation attacks. You should compare `messageEvent.origin` to the trusted iframe origin and ignore unexpected sources.  


Example within the handler:
```diff
 function _komodoOnMessageHandler(messageEvent) {
+  const iframeSrc = document.getElementById('fiat-onramp-iframe').src;
+  const trustedOrigin = new URL(iframeSrc).origin;
+  if (messageEvent.origin !== trustedOrigin) {
+    console.warn('Ignored message from untrusted origin:', messageEvent.origin);
+    return;
+  }
   let messageData;
   try {
     messageData = typeof messageEvent.data === 'string'
       ? JSON.parse(messageEvent.data)
       : messageEvent.data;
   } catch (parseError) {
     messageData = messageEvent.data;
   }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

`````suggestion
    function _komodoOnMessageHandler(messageEvent) {
      // Only accept messages from our trusted iframe origin
      const iframeSrc = document.getElementById('fiat-onramp-iframe').src;
      const trustedOrigin = new URL(iframeSrc).origin;
      if (messageEvent.origin !== trustedOrigin) {
        console.warn('Ignored message from untrusted origin:', messageEvent.origin);
        return;
      }

      let messageData;
      try {
        messageData = typeof messageEvent.data === 'string'
          ? JSON.parse(messageEvent.data)
          : messageEvent.data;
      } catch (parseError) {
        messageData = messageEvent.data;
      }

      // forward navigation requests to parent
      if (messageData && typeof messageData === 'object') {
        if (
          (messageData.type === 'navigation' ||
           messageData.action === 'checkout_complete') &&
          messageData.url
        ) {
          window.location.href = messageData.url;
          return;
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-07T08:38:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,109 +2,136 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
+  <iframe id="fiat-onramp-iframe"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.addEventListener('message', _komodoOnMessageHandler, false);
+    window.addEventListener('load', function () {
+      _komodoSetIframeUrlFromParams();
+    });
+
+    /**
+     * Initialize the iframe URL based on URL parameters
+     * 
+     * @param {object} params - The URL parameters to use for initialization
+     */
+    function _komodoSetIframeUrlFromParams() {
+      const urlParam = _komodoGetUrlParameter('fiatUrl');
+
+      let targetUrl = null;
+      if (urlParam) {
+        try {
+          targetUrl = atob(urlParam); // base64 decode the `url` parameter
+        } catch (error) {
+          console.error('Error decoding base64 url parameter', error);
         }
-
-        if (targetUrl) {
-            // Set the iframe's src attribute
-            document.getElementById('fiat-onramp-iframe').src = targetUrl;
-        } else {
-            console.error('No URL parameter provided');
-        }
-
-        function onMessageHandler(messageEvent) {
-            let messageData;
-            try {
-                // Try parsing the data as JSON 
-                messageData = JSON.parse(messageEvent.data);
-            } catch (parseError) {
-                // If parsing fails, just use it as a string
-                messageData = messageEvent.data;
-            }
-
-            try {
-                postMessageToParent(messageData);
-            } catch (postError) {
-                console.error('Error posting message', postError);
-            }
-        }
-
-        /** 
-         * Post a message to the parent window
-         * 
-         * @param {string|object} messageData 
-         */
-        function postMessageToParent(messageData) {
-            // Convert messageData to a string if it is an object
-            const messageString = (typeof messageData === 'object') ? JSON.stringify(messageData) : String(messageData);
-
-            // flutter_inappwebview
-            console.log(messageString);
-
-            // universal_url opener 
-            if (window.opener) {
-                return window.opener.postMessage(messageString, "*");
-            }
-
-            if (window.parent && window.parent !== window) {
-                return window.parent.postMessage(messageString, "*");
-            }
-
-            // Windows WebView2 (desktop_webview_window) - https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/communicate-btwn-web-native 
-            if (window.chrome && window.chrome.webview) {
-                return window.chrome.webview.postMessage(messageString);
-            }
-
-            console.error('No valid postMessage target found');
+      }
+
+      if (targetUrl) {
+        document.getElementById('fiat-onramp-iframe').src = targetUrl;
+      } else {
+        console.error('No URL parameter provided');
+      }
+    }
+
+    /**
+     * Get URL parameter by name
+     * 
+     * @param {string} name - The name of the URL parameter to retrieve
+     * @returns {string|null} - The value of the URL parameter or null if not found
+     */
+    function _komodoGetUrlParameter(name) {
+      const params = new URLSearchParams(window.location.search);
+      return params.get(name);
+    }
+
+    /** 
+     * Handle messages from the iframe
+     * 
+     * @param {MessageEvent} messageEvent 
+     */
+    function _komodoOnMessageHandler(messageEvent) {
+      let messageData;
+      try {
+        messageData = typeof messageEvent.data === 'string' ? JSON.parse(messageEvent.data) : messageEvent.data;
+      } catch (parseError) {
+        messageData = messageEvent.data;
+      }
+
+      // forward navigation requests to parent
+      if (messageData && typeof messageData === 'object') {
+        if ((messageData.type === 'navigation' || messageData.action === 'checkout_complete') && messageData.url) {
+          window.location.href = messageData.url;
+          return;
         }
path: assets/web_pages/fiat_widget.html
user: login: takenagain
body: Reverted in [ced4fa8](https://github.com/KomodoPlatform/komodo-wallet/pull/2608/commits/ced4fa8e133e382c15435cf10373d789a411bec6)
created_at: 2025-05-07T08:54:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,109 +2,136 @@
 <html style="height: 100%;">
 
 <head>
-    <title>Fiat OnRamp</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <style>
-        body,
-        html {
-            margin: 0;
-            padding: 0;
-            height: 100%;
-            overflow: hidden;
-        }
-
-        iframe {
-            position: relative;
-            top: 0;
-            left: 0;
-            width: 100%;
-            height: 100%;
-            border: none;
-        }
-    </style>
+  <title>Fiat OnRamp</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <style>
+    body,
+    html {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+    }
+
+    iframe {
+      position: relative;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      border: none;
+    }
+  </style>
 </head>
 
 <body>
-    <iframe id="fiat-onramp-iframe" sandbox="allow-same-origin allow-popups allow-scripts allow-forms" src="">
-    </iframe>
-    <script>
-        // Intercept console.log messages and route them through the onMessageHandler
-        // console.log = function (...args) {
-        //     onMessageHandler({ data: JSON.stringify(args) });
-        // };
-
-        window.onmessage = onMessageHandler;
-
-        function getUrlParameter(name) {
-            const params = new URLSearchParams(window.location.search);
-            return params.get(name);
-        }
-
-        // Get the `url` parameter
-        const urlParam = getUrlParameter('fiatUrl');
-        let targetUrl = null;
-
-        if (urlParam) {
-            try {
-                targetUrl = atob(urlParam); // base64 decode the `url` parameter
-            } catch (error) {
-                console.error('Error decoding base64 url parameter', error);
-            }
+  <iframe id="fiat-onramp-iframe"
+    sandbox="allow-forms allow-scripts allow-same-origin allow-top-navigation allow-top-navigation-by-user-action allow-popups"
+    src="">
+    <!-- Placeholder fallback message -->
+    <p>Your browser does not support iframes.</p>
+    <p>Please use a modern browser to view this content.</p>
+    <p>If you are using a mobile device, please try opening this page in a different app or browser.</p>
+    <p>If you are using a desktop, please try opening this page in a different browser.</p>
+  </iframe>
+  <script>
+    window.addEventListener('message', _komodoOnMessageHandler, false);
+    window.addEventListener('load', function () {
+      _komodoSetIframeUrlFromParams();
+    });
+
+    /**
+     * Initialize the iframe URL based on URL parameters
+     * 
+     * @param {object} params - The URL parameters to use for initialization
+     */
+    function _komodoSetIframeUrlFromParams() {
+      const urlParam = _komodoGetUrlParameter('fiatUrl');
+
+      let targetUrl = null;
+      if (urlParam) {
+        try {
+          targetUrl = atob(urlParam); // base64 decode the `url` parameter
+        } catch (error) {
+          console.error('Error decoding base64 url parameter', error);
         }
-
-        if (targetUrl) {
-            // Set the iframe's src attribute
-            document.getElementById('fiat-onramp-iframe').src = targetUrl;
-        } else {
-            console.error('No URL parameter provided');
-        }
-
-        function onMessageHandler(messageEvent) {
-            let messageData;
-            try {
-                // Try parsing the data as JSON 
-                messageData = JSON.parse(messageEvent.data);
-            } catch (parseError) {
-                // If parsing fails, just use it as a string
-                messageData = messageEvent.data;
-            }
-
-            try {
-                postMessageToParent(messageData);
-            } catch (postError) {
-                console.error('Error posting message', postError);
-            }
-        }
-
-        /** 
-         * Post a message to the parent window
-         * 
-         * @param {string|object} messageData 
-         */
-        function postMessageToParent(messageData) {
-            // Convert messageData to a string if it is an object
-            const messageString = (typeof messageData === 'object') ? JSON.stringify(messageData) : String(messageData);
-
-            // flutter_inappwebview
-            console.log(messageString);
-
-            // universal_url opener 
-            if (window.opener) {
-                return window.opener.postMessage(messageString, "*");
-            }
-
-            if (window.parent && window.parent !== window) {
-                return window.parent.postMessage(messageString, "*");
-            }
-
-            // Windows WebView2 (desktop_webview_window) - https://learn.microsoft.com/en-us/microsoft-edge/webview2/how-to/communicate-btwn-web-native 
-            if (window.chrome && window.chrome.webview) {
-                return window.chrome.webview.postMessage(messageString);
-            }
-
-            console.error('No valid postMessage target found');
+      }
+
+      if (targetUrl) {
+        document.getElementById('fiat-onramp-iframe').src = targetUrl;
+      } else {
+        console.error('No URL parameter provided');
+      }
+    }
+
+    /**
+     * Get URL parameter by name
+     * 
+     * @param {string} name - The name of the URL parameter to retrieve
+     * @returns {string|null} - The value of the URL parameter or null if not found
+     */
+    function _komodoGetUrlParameter(name) {
+      const params = new URLSearchParams(window.location.search);
+      return params.get(name);
+    }
+
+    /** 
+     * Handle messages from the iframe
+     * 
+     * @param {MessageEvent} messageEvent 
+     */
+    function _komodoOnMessageHandler(messageEvent) {
+      let messageData;
+      try {
+        messageData = typeof messageEvent.data === 'string' ? JSON.parse(messageEvent.data) : messageEvent.data;
+      } catch (parseError) {
+        messageData = messageEvent.data;
+      }
+
+      // forward navigation requests to parent
+      if (messageData && typeof messageData === 'object') {
+        if ((messageData.type === 'navigation' || messageData.action === 'checkout_complete') && messageData.url) {
+          window.location.href = messageData.url;
+          return;
         }
path: assets/web_pages/fiat_widget.html
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# Fetch the specific commit details to understand what was reverted and why
git show ced4fa8e133e382c15435cf10373d789a411bec6
```

Length of output: 94244

---

🏁 Script executed:
```
#!/bin/bash
# Let's look specifically at the changes made to the fiat_widget.html file in the revert commit
git show ced4fa8e133e382c15435cf10373d789a411bec6 -- assets/web_pages/fiat_widget.html
```

Length of output: 4383

---



</details>

@takenagain I see you've taken a different but equally effective approach to address the security concern. Instead of adding origin validation, you've removed the automatic navigation functionality entirely. This is a clean solution that eliminates the potential redirect vulnerability completely. Thanks for addressing this!


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# Fetch the specific commit details to understand what was reverted and why
git show ced4fa8e133e382c15435cf10373d789a411bec6
```

Length of output: 94244

---

🏁 Script executed:
```
#!/bin/bash
# Let's look specifically at the changes made to the fiat_widget.html file in the revert commit
git show ced4fa8e133e382c15435cf10373d789a411bec6 -- assets/web_pages/fiat_widget.html
```

Length of output: 4383

---



</details>

@takenagain I see you've taken a different but equally effective approach to address the security concern. Instead of adding origin validation, you've removed the automatic navigation functionality entirely. This is a clean solution that eliminates the potential redirect vulnerability completely. Thanks for addressing this!</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-07T08:55:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-01T19:26:32Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces significant refactoring and enhancements to the fiat onramp flow, including both backend Dart logic and frontend HTML/JavaScript components. The fiat widget HTML is modularized, with improved sandboxing and message handling. Dart code sees changes to enums, state management, error handling, and type safety, especially around fiat order status and webview integration. New mechanisms for secure webview settings and flexible dialog display modes are added, supporting dialog, fullscreen, and external tab/browser. Several method signatures are updated for clarity, type safety, and consistency, and error handling is strengthened throughout the codebase.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `assets/web_pages/fiat_widget.html` | Refactored HTML/JS: modularized functions, improved iframe sandboxing, added fallback content, enhanced message handling, and robust parent communication. |
| `lib/bloc/fiat/banxa_fiat_provider.dart` | Replaced custom order status parsing with `FiatOrderStatus.fromString`; removed redundant method; added filtering of unsupported coins with logging; parsed min purchase amounts with type handling. |
| `lib/bloc/fiat/base_fiat_provider.dart` | Refactored endpoint path logic, improved error handling for HTTP responses with JSON fallback, removed unused lint ignore, and added recognition of 'ARBITRUM' coin type. |
| `lib/bloc/fiat/fiat_onramp_form/fiat_form_bloc.dart` | Adopted lazy evaluation for state fields, changed default order status, introduced platform-aware webview mode selection, improved error handling, and adjusted minimum amount calculation. |
| `lib/bloc/fiat/fiat_onramp_form/fiat_form_state.dart` | Changed default order status to `initial`, added `webViewMode` and `providerError` fields, updated `copyWith` for lazy evaluation and new fields, and adjusted equality props and min/max fiat amount getters. |
| `lib/bloc/fiat/fiat_order_status.dart` | Renamed `pending` to `initial`, added `pendingPayment`, made status parsing case-insensitive, improved Banxa status mapping, and changed error handling to log and fallback. |
| `lib/bloc/fiat/fiat_repository.dart` | Removed unused import, updated `buyCoin` to use named required parameters, inverted coin support check with debug logging. |
| `lib/bloc/fiat/models/fiat_buy_order_error.dart` | Added named constructor for parsing errors with default message and status. |
| `lib/bloc/fiat/models/fiat_buy_order_info.dart` | Renamed and swapped constructors (`info` ↔ `fromCheckoutUrl`), improved JSON parsing with explicit error handling for missing keys and top-level errors. |
| `lib/bloc/fiat/models/fiat_price_info.dart` | Added helper for safe decimal parsing, moved `zero` instance, improved error resilience. |
| `lib/bloc/fiat/payment_status_type.dart` | Changed string literals to single quotes, moved field declaration, used const for exceptions, no logic changes. |
| `lib/bloc/fiat/ramp/ramp_fiat_provider.dart` | Enforced `CryptoCurrency` type, added runtime checks, clarified numeric conversions, validated fee ratios, extended buy order payload, improved coin list parsing and logging. |
| `lib/views/fiat/fiat_form.dart` | Removed unused imports and methods, streamlined checkout and payment status handling, consolidated webview dialog usage with enhanced settings, improved dialog logic and error display. |
| `lib/views/fiat/fiat_provider_web_view_settings.dart` | New file: defines secure webview settings with trusted domain filters and content blockers for fiat providers. |
| `lib/views/fiat/webview_dialog.dart` | Added `WebViewDialogMode` enum, enhanced `WebViewDialog` to support dialog/fullscreen/new tab, unified callback naming, improved platform support, and cleaned up JS injection. |
| `assets/web_pages/checkout_status_redirect.html` | Removed fallback redirect to home page; always calls `window.close()` on script completion. |
| `lib/bloc/fiat/models/i_currency.dart` | Added `minPurchaseAmount` field to `ICurrency`, `FiatCurrency`, and `CryptoCurrency`; updated constructors and `copyWith`; revised `CryptoCurrency.getAbbr()` with chain-specific logic. |
| `lib/shared/widgets/coin_item/coin_item.dart` | Moved `super.key` parameter position in constructor after required parameters. |
| `lib/views/fiat/fiat_asset_icon.dart` | Replaced direct `symbol` usage with `getAbbr()` calls for currency abbreviation; added TODO for icon layout standardization. |
| `lib/views/fiat/fiat_currency_list_tile.dart` | Changed `SizedBox.shrink()` to `const SizedBox.shrink()` for compile-time constant widget. |
| `lib/views/fiat/fiat_inputs.dart` | Updated fiat amount boundary formatting to fixed two decimals; updated `FiatIcon` key and symbol to use `getAbbr()`. |
| `lib/views/fiat/fiat_payment_methods_grid.dart` | Changed symbol retrieval to use `getAbbr()` for selected asset and fiat currency in "no options" message. |
| `lib/app_config/app_config.dart` | Added `banxaUnsupportedCoinsList` and `rampUnsupportedCoinsList` constants listing coins unsupported by respective providers with explanatory comments. |
| `lib/bloc/fiat/ramp/models/ramp_asset_info.dart` | Reordered constructor parameters, moving optional parameters to the end without logic changes. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant App
    participant FiatWidget (iframe)
    participant WebViewDialog

    User->>App: Initiates fiat onramp
    App->>FiatWidget: Loads iframe with base64-decoded URL
    FiatWidget->>App: Sends message (order status, navigation, etc.)
    App->>WebViewDialog: Opens webview in dialog/fullscreen/new tab (mode-dependent)
    WebViewDialog->>FiatWidget: Displays provider UI
    FiatWidget->>App: Forwards status/events via postMessage
    App->>User: Updates UI based on status (success, failure, pending)
```

## Possibly related PRs

- KomodoPlatform/komodo-wallet#2570: Introduces HD wallet address selection and refactors fiat form event and state handling, closely related to this PR's changes in fiat form state management and event naming.

## Suggested labels

`documentation`, `QA`

## Suggested reviewers

- smk762  
- CharlVS  
- AndrewDelaney

## Poem

> A rabbit hopped through code so neat,  
> Refactored flows, a tidy feat!  
> Webviews now secure and bright,  
> Status enums set just right.  
> Lazy fields and dialogs new,  
> Messages passing safely through.  
> With every hop, the code feels light—  
> Onramp journeys, pure delight! 🐇✨

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


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

<!-- internal state end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=KomodoPlatform/komodo-wallet&utm_content=2608):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-01T19:30:44Z
body: Visit the preview URL for this PR (updated for commit efc6cea):

[https://walletrc--pull-2608-merge-8ugezf4h.web.app](https://walletrc--pull-2608-merge-8ugezf4h.web.app)

<sub>(expires Fri, 16 May 2025 11:47:24 GMT)</sub>

<sub>🔥 via [Firebase Hosting GitHub Action](https://github.com/marketplace/actions/deploy-to-firebase-hosting) 🌎</sub>

<sub>Sign: f66a4ff03faa546f12f0ae5a841bd9eff2714dcc</sub>
author_association: NONE
user: login: takenagain
created_at: 2025-05-01T20:34:21Z
body: @coderabbitai review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-01T20:34:26Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: takenagain
created_at: 2025-05-07T08:35:57Z
body: @coderabbitai review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-07T08:36:02Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR
user: login: takenagain
created_at: 2025-05-09T11:46:22Z
body: @coderabbitai review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-09T11:46:27Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR